{************************************************************************
 ***                                                                  ***
 *** New Fabbo Singing Dancing OOP                                    ***
 ***                            INVOICES                              ***
 ***                                                            Nov 94***
 *** M Hill                                                 rev May 96***
 ***********************************************************************}
{For making Invoices{}

{$I compdirs}  {Compiler directives}
unit kInvoice;

INTERFACE

uses 	linklist,
			notes, dattime,
			multcurr,
			jimprint,
			jimmys,
			tuiedit,
			objects,
			global,
			views,
			forms, devices,
			files,
			dialogs,
			kestimat, ordproc;

{*****************************
 ***  Invoice              ***
 *****************************}
const
	TInvoiceIndexSize = 40;

type
	PInvoice = ^TInvoice;
	TInvoice = object(TEstimate)

{now in Torder		Comment   : PFreeTextData;            {Text on Invoice}

		DaysDiscount : byte;

		PaidTotal : TMoney;
		Due : TMoney;

		Ptr2Payments : longint; {payments hooklist}
		OutTrayIdx : longint; 	{pointer to out-tray}
		Quick : boolean; {marks a quick invoice - no items chain}
		LastPmnt : TDate; {Last payment date, for working out if still discountable}

		constructor Init(Param : PJimmyInitParam);
		procedure CommonInit; virtual; {init procedures common to Init and Load (eg scode logging)}
		destructor Done; virtual;

		{--- Editing ----}
		procedure MakeEditBox(var EditBox : PEditBox; Caller : PView); virtual;

		{--- Viewing -----}
		function DisplayLine(ListForWho : longint; lstype : byte; Maxlen : integer; View : word) : string; virtual;
		function GetName(naType : byte; Maxlen : integer) : string; virtual; {used for various displays/prints -
																																					eg selection lines, window headers, etc}
		{--- Printing ----}
		procedure SetFormCodes(const FormCodes: PFormCodeCollection); virtual;
		procedure OnPrinting(const PrintType : TJimmyPrintType); virtual;
		procedure PrintFull(const Device : PDeviceStream; const PrintAs : word); virtual;
		procedure GetDefaultPrintType(var PrintType : TJimmyPrintType; var PrintAs : PSItem; var PrintAsLink : pointer); virtual;
		procedure PrintLabel(const Device : PDeviceStream; LabelAs : word); virtual;

		{--- Database ----}
		function RecSize : word; virtual; {space to be reserved in jimmy file}
		function srType : word; virtual; {descendants set as fixed, so can be
																			used to identify jimmy for file operations, etc}

		function PtrOffset : byte; virtual; {extra ver no}

		procedure Storefields(var S : TDataStream); virtual;
		constructor Load(var S : TDataStream);

		{--- Indexing -----}
		function NumixTypes : byte; virtual;
		procedure GetIndex(const ixType : byte; var IdxRec : Plongint; var fiType : byte); virtual;
		function GetIndexKey(const ixType : byte) : string; virtual;

		{--- Hooking on others -----}
		function NumhkTypes : byte; virtual;
		procedure GetHookOn(const hkType : byte; var HookRec : PLongint); virtual;

		{-- Hooking to others -----}
		function NumHookTo : byte; virtual;
		procedure GetHookTo(const htType : byte; var HookToID,SubHookToID : PLongint;
												var hkType : byte; var Key : longint; var InsertBias : boolean); virtual;
		{for payments}
		function HookingOn(const hkType,htType : byte; const HookingJimmy : PJimmy) : boolean; virtual;
		function UnHooking(const hkType,htType : byte; const HookingJimmy : PJimmy) : boolean; virtual;

		function OverdueLevel(const TestDate : TDate) : byte;
		function CashDiscountable : boolean; virtual;
		function AllowDeletion : boolean; virtual;
		procedure SetLastPmnt;{}

		procedure Send; virtual;
		procedure Recalculate; virtual;
		procedure CalculateTotals; virtual;
	end;

procedure AddItemToInvoiceFor(ForWho : longint; OrderItem : POrderItem);

{pass person it's to, date, code, action & comment.  code & action blank for
a free text item}
procedure AddToInvoiceFor(ForWho : longint; NDate : TDate; NCode,NAction,NComment : string; NAmount : TMoney);


IMPLEMENTATION

uses tui, tuimsgs,
			tuiboxes, app, {for looking up by ref #}
			tuilist,
			idindex,
			{$IFDEF kbooks} kbooks, {$ENDIF}
			payments,
			setup,
			kamsetup,{for copying to history}
			inpdnt,
			trays,
			kinvsetu, {for checking overdue level}
			kinvrpts, {if you've got invoices, you need invoice reports}
			kdirctry,
			address,
			tasks,
			lstrings,
			help,
			tuijimmy,
			jimhooks,
			jimindxs,
			minilib;

{***********************************************************************
 ***                                                                 ***
 ***                      AUTO BILL                                  ***
 ***                                                                 ***
 ***********************************************************************}
{Locates person passed in ForWho, looks up first open invoice and adds to
main peer list.  If no open invoice, makes one}
procedure ADdItemToInvoiceFor;
var DirectoryItem : PDirectoryItem;
		Invoice : PInvoice;
		InitParam : TJimmyInitParam;

begin
	DirectoryItem := PDirectoryItem(GetJimmy(ForWho));

	FileAdmin(fiHooks)^.LogOn;
	Invoice := PInvoice(Hookfile^.GetFirst(DirectoryItem^.Ptr2History, srInvoice));

	if Invoice = nil then begin
		with InitParam do begin
			ListView 	:= nil;
			ForWho 		:= ForWho;
			FocusedID := -1;
			FocusedParentID := -1;
		end;

		New(Invoice, init(@InitParam));
		Invoice^.StoreSelf;
	end;

	OrderItem^.ForOrder := Invoice^.RecNo;
{leave it in case caller wants to override  OrderItem^.ParentItemID := -1; {not attached to any other one}
	OrderItem^.StoreSelf;

	dispose(Invoice, done);
	dispose(DirectoryItem, done);
end;

{As above but pass a code/text (for a coded item), or just text for a free text item}
procedure AddToInvoiceFor;
var OrderItem : POrderItem;
begin
	if delspaceR(NCode+NAction)='' then begin
		OrderItem := New(PFreeTextOrderItem, init(nil));
		with PFreeTextOrderItem(OrderItem)^ do begin
			LSAppendStr(FreeText^.Text, NComment);
		end;
	end else begin
		OrderItem := New(PCodedOrderItem, init(nil));
		with PCodedOrderItem(OrderItem)^ do begin
			Date.SetToDate(Date);
			Code := NCode;        {Event code}
			CodeLine := NAction;
			LSAppendStr(Comment^.Text, NComment);

			BasePrice.SetTo(NAmount);
		end;
	end;

	with OrderItem^.PriceGroup do begin
		Price.SetTo(NAmount);
		CalculateFromPrice;
	end;

	AddItemToInvoiceFor(ForWho, OrderItem);
end;



{***********************************************************************
 ***                                                                 ***
 ***                      Invoice                                ***
 ***                                                                 ***
 ***********************************************************************}
constructor TInvoice.Init(Param : PJimmyInitParam);
begin
	inherited init(param);

	Ptr2Payments := -1;

	DaysDiscount := InvoiceSetup.DefaultDiscountDays;
end;

procedure TInvoice.COmmonInit;
begin
	inherited CommonInit;
	PaidTotal.Init;
	Due.Init;
	Quick := False; {needs to be in here, rather than in init, at least
		until we get rid of KQInv}
	LastPmnt.Clear;
end;

destructor TInvoice.Done;
begin
	inherited Done;
end;

{==== CREATE DISPLAY-LINE-FORMAT STRING ============}
function TInvoice.DisplayLine;
var S : string;
{		LPDate : TDate;{}
begin
	if lsType=lsAccounts then
		{dialog/pure invoice view}
		S := Date.Digit8+PadSpaceL(N2Str(Ref),7)+PadspaceL(TotallerGroup.Total.Text(mtValue),10)
	else
		{ordinary list view}
		S := Date.Digit8+' INVOICE'+PadSpaceL(N2Str(Ref),6)+'  For '+PadspaceL(TotallerGroup.Total.Text(mtValue),9);

	{show discount, if any}
	if not TotallerGroup.CashDisc.Blank then begin
		if CashDiscountable then
			S := S + flDisc {mark as discounted}
		else
			S := S + flPastDisc {mark as would be discounted but expired}
	end else
		S := S + ' ';

	{show amount due and whether overdue}
	if (State and osSent)>0 then begin
		if Due.Blank then begin
			if not LastPmnt.Blank then
				if LastPmnt.Days=Date.Days then
					S := S + ' Paid immediately'
				else
					S := S + ' Paid in '+N2Str(longint(LastPmnt.Days)-Date.Days)+' days'
		end else begin
			{if accounts list, put value first}
			if lsType=lsAccounts then	S := S + PadspaceL(Due.Text(mtValue),8);

			if OverdueLevel(Today)>0 then
				S := S + ' OVERDUE '
			else
				if lsType<>lsAccounts then S := S + ' Due ';

			if lsType<>lsAccounts then S := S + Due.Text(mtValue); {otherwise put last}
		end;

{		if OverdueLtr[1] then S := S + ' /1';
		if OverdueLtr[2] then S := S + '/2';
		if OverdueLtr[3] then S := S + '/3';{}
	end else
		S := S + ' Unsent';

	DisplayLine := S;
end;

function TInvoice.GetName;
begin
	GetName := 'INV #'+N2Str(Ref)+' '+GetJimmyIDName(ForWho,naRef,0)+' '+Date.Text(daDigit8);
end;


{**************************************************
 ***             EDIT BOX                       ***
 **************************************************}
{very simple - causes a totla recalc if DD or item list changed}
procedure LinkDD(const Linker : PINputLinker; const CallingView : PView); far;
begin
	PInputELine(Linker^.TargetView[1])^.ForceLink;
end;

{when totaller group's total changes (sv1), with total paid (sv2), changes
due (tv(1)}
procedure LinkTotalPaid(const Linker : PINputLinker; const CallingView : PView); far;
var MOney,Paid : TMoney;
begin
	with Linker^ do begin
		SourceView[1]^.GetData(Money);
		SourceView[2]^.GetData(Paid);
		Money.Subtract(Paid);
		TargetView[1]^.SetData(Money);
		TargetView[1]^.DrawView;
	end;
end;


procedure TInvoice.MakeEditBox;
var	R: TRect;
		ForWhoLine, ForWhatLine,RefLine, ByLine, PmntView, ItemView : PView;
		DDLinker,TotalPaidLinker : PInputLinker;

begin
	if Quick then begin

		{===== QUICK INVOICE ===========================}
		R.Assign(0, 0, 45, 20);
		CentreOnView(R, Caller);
		EditBox := New(PJimmyEditBox, Init(R, 'Quick Invoice',Caller, @Self));

		with EditBox^ do begin
			Insert(New(PSkipBytes, init(sizeof(TJimmy))));
			Insert(New(PSkipBytes, init(4))); {skip ptr2items}

			InsTitledField(25, 2, 10, 1, 'Last Print', New(PInputDate, init(R)));
			Current^.SetState(sfDisabled, true);

			R.Assign(0,0,0,0); Insert(New(PStateField, init(RecNo)));
			Current^.SetState(sfDisabled, True);

			Insert(New(PInputTotallerGroup, init(23, 9, EditBox)));
			PInputTotallerGroup(Current)^.SubTotalLine^.SetState(sfDisabled, False);
			PInputTotallerGroup(Current)^.SubTotalLine^.Select;

			{payment view - pos as buttons}
			PmntView := InsTitledField( 7,12,35,3, '~P~mnts',
																New(PDlgHookView,	Init(R, lsPayments, 0, hkPayments, @Self, PJimmyEditBox(EditBox))));
			Insert(PHookViewer(PmntView)^.VScrollBar);

			{-- Buttons --}
			{put just after totallers for input sequence purposes}
			if State and osSent = 0 then
				Insert(New(PJimmyOKButton, Init(13,Size.Y-3, @Self)));
			Insert(New(PjimmyCancelButton, init(23,Size.Y-3, @Self)));

			{$IFDEF development}
			Insert(New(PRecalcButton, init(13,Size.Y-1, '~R~ecalc', 0, bfNormal, nil)));
			{$ENDIF}

			{ref, etc}
			InsTitledField(7,  1, 5, 1, 'Ref', New(PInputRefNum, init(R,5, srInvoice)));
			Insert(New(PSkipBytes, init(4))); {oldref}
			InsTitledField(25, 1,10, 1, 'D~a~te', New(PinputDate, Init(R)));

			ForWhoLine := InsTitledField( 7,  4,35,1, '~T~o',  New(PInputDirectory, init(R, 30,  fiFullDirIdx, lsDirectory,'')));
			PInputELine(ForWhoLine)^.MustInput := True;

			Insert(New(PSkipBytes, init(sizeof(PString)))); {skip cust ref}
			Insert(New(PSkipBytes, init(4))); {by who}

			{Quick INvoice - notes becomes invoice what-for}
			R.XYLD(7,6,37,3); Insert(New(PInputFreeText, Init(R,200,40,nil)));
			AddLabel('~F~or', Current);
			ForWhatLine := Current;
			Insert(PInputFreeText(Current)^.VScrollBar);

			Insert(New(PSkipBytes, init(4))); {ptr2goods, kestimate}
			Insert(New(PSkipBytes, init(1))); {DD}

			InsTitledField(33, 15, 10, 1, 'Paid Total', New(PInputMoney, init(R)));
			Current^.SetState(sfDisabled, True);
			InsTitledField(33, 16, 10, 1, 'Due', New(PInputMoney, init(R)));
			Current^.SetState(sfDisabled, True);

			EndInit;
		end;

		if ForWho=-1 then
			ForWhoLine^.FOcus
		else
			ForWhatLine^.Focus;

	end else begin

		{========= ORDINARY INVOICE ========================}

		R.Assign(0, 0, 70, 21);
		CentreOnView(R, Caller);
		EditBox := New(PJimmyEditBox, Init(R, 'Invoice',Caller, @Self));

		New(DDLinker, init(@LinkDD, EditBox));
		New(TotalPaidLinker, init(@LinkTotalPaid, EditBox));

		with EditBox^ do begin
			Insert(New(PSkipBytes, init(sizeof(TJimmy))));
			Insert(New(PSkipBytes, init(4))); {skip ptr2items}

			InsTitledField(55, 1, 10, 1, 'Last Print', New(PInputDate, init(R)));
			Current^.SetState(sfDisabled, true);

			R.Assign(0,0,0,0); Insert(New(PStateField, init(RecNo)));
			Current^.SetState(sfDisabled, True);

			Insert(New(PINputTotallerGroup, init(Size.X-22, 13, EditBox)));
			DDLinker^.SetTargetView(PInputTotallerGroup(Current)^.SubTotalLine,1);
			TotalPaidLinker^.SetSourceView(PInputTotallerGroup(Current)^.TotalLine,1);
			Current^.SetState(sfDisabled, True);

			{ref, etc}
			InsTitledField(7,  1, 5, 1, 'Ref', New(PInputRefNum, init(R,5, srInvoice)));
			if not AllowChanges then Current^.SetState(sfDisabled, True);
			Insert(New(PSkipBytes, init(4))); {oldref}
			InsTitledField(20, 1,10, 1, 'D~a~te', New(PinputDate, Init(R)));
			if not AllowChanges then Current^.SetState(sfDisabled, True);

			ForWhoLine := InsTitledField( 7,  3,30,1, '~T~o',  New(PInputDirectory, init(R, 30,  fiFullDirIdx, lsDirectory,'')));
			if not AllowChanges then Current^.SetState(sfDisabled, True);
			PInputELine(ForWhoLine)^.MustInput := True;
			R.XYLD(50,3,10,1); Insert(New(PInputPStr, init(R, 10)));	AddLabel('Cust Ref', Current);
			if not AllowChanges then Current^.SetState(sfDisabled, True);

			ByLine  := InsTitledField( 7,  4,30,1, '~B~y',  New(PInputDirectory, init(R, 30,  fiCatDirIdx, lsDirectory,'STA')));
			if not AllowChanges then Current^.SetState(sfDisabled, True);

			R.XYLD(1,6,68,7); Insert(New(PDlgHookView,	Init(R, lsSalesItems, 0, hkOrderItems, @Self, PJimmyEditBox(EditBox))));
			ItemView := Current;
			Insert(PHookViewer(ItemView)^.VScrollBar);
			if not AllowChanges then PListView(Current)^.ViewOnly := True;{}
			R.XYLD(1, 5, 68,1); Insert(New(PLabel, init(R,'~F~or'+Space(47)+'SubTotal       VAT',Current)));
			DDLinker^.SetSourceView(Current, 2);

			InsTitledField(7, 14, 27, 3, 'N~o~tes', New(PInputFreeText, Init(R,200,26,nil)));

			Insert(New(PSkipBytes, init(4))); {ptr2goods, kestimate}
			InsTitledField(7, 18, 5, 1, '~D~D', New(PInputByte, init(R,3)));
			DDLinker^.SetSourceView(Current,1);
			if not AllowChanges then Current^.SetState(sfDisabled, True);

			PmntView := InsTitledField(45,15,22,3, '~P~mnts',
																New(PDlgHookView,	Init(R, lsPayments, 0, hkPayments, @Self, PJimmyEditBox(EditBox))));
{		PHookViewer(PmntView)^.DoneNew := True; {prevents irritating auto-new when tabbing thru}
			Insert(PHookViewer(PmntView)^.VScrollBar);

			{can't insert at back, because of order of getdata, etc}
			R.XYLD(Size.X-12, 18, 12, 1); Insert(New(PInputMoney, init(R))); AddLabel('Paid', Current);
			TotalPaidLinker^.SetSourceView(Current,2);
			Current^.SetState(sfDisabled, True);
			R.Move(0, 1);									Insert(New(PInputMoney, init(R))); AddLabel('Due', Current);
			TotalPaidLinker^.SetTargetView(Current,1);
			Current^.SetState(sfDisabled, True);

			{-- Buttons --}
			if AllowChanges then{}
				Insert(New(PJimmyOKButton, Init(12,Size.Y-3, @Self)));
			Insert(New(PjimmyCancelButton, init(22,Size.Y-3, @Self)));

{			Insert(New(PSetupButton, init(13,Size.Y-2, '~S~etup', @InvoiceSetup)));{}

			{$IFDEF development}
			Insert(New(PRecalcButton, init(32,Size.Y-3, '~R~ecalc', 0, bfNormal, nil)));
			{$ENDIF}

			EndInit;
		end;

		if ForWho=-1 then
			ForWhoLine^.FOcus
		else
			ItemView^.Focus; {hmmm, this makes it automatically save...}

	end; {ordinary invoice}
end;

{*****************************************
 ***     STREAMING DEFINITIONS         ***
 *****************************************}
function TInvoice.RecSize;
begin RecSize := 100; end;

function TInvoice.srType;
begin srType := srInvoice; end;

function TInvoice.PtrOffset;
begin PtrOffset := inherited PtrOffset + 1; end; {extra ver in load}

constructor TInvoice.Load(var S : TDataStream);
var	Ver,OV,B : byte;

begin
	S.Read(Ver, 1);
{	Quick := False; done in commoninit}

	{replacing old inherited Load below now that we have another index...}
	case Ver of
		1,2,3 : begin
			S.Read(OV,1); {order version}

			CommonInit;
			S.Read(B,1);
			S.Read(B,1);

			S.Read(Ptr2Items, 4);
			S.Read(Ptr2Payments, 4);

			if OV<>2 then
				RunError(200)
			else begin
				S.REad(Ref, 4); OldRef := Ref;
				S.Read(ForWho, 4);
				Date.Load(S);
				LastPrint.Load(S);
				TotallerGroup.Load(S);
				S.Read(State, 1);
			end;
			ForWhoRef := nil;
		end;
	end;

	case Ver of
		1 : begin
			{old one, didn't work out totals properly}
			S.Read(ByWho, 4);
			Notes^.Load(S);
			Due.Load(S);
			DaysDiscount := 0;
			if not LastPrint.Blank then State := State or osSent; {botch fix for old}
			Recalculate;
		end;
		2 : begin
			{added days discount}
			S.Read(ByWho, 4);
			Notes^.Load(S);
			Due.Load(S);
			S.Read(DaysDiscount,1);
			if not LastPrint.Blank then State := State or osSent; {botch fix for old}
			PaidTotal.SetTo(TotallerGroup.Total); PaidTotal.Subtract(Due);
			SetLastPmnt;
		end;
		3 : begin
			{added paid total instead of due, for quick invoices}
			S.Read(ByWho, 4);
			Notes^.Load(S);
			PaidTotal.Load(S);
			S.Read(DaysDiscount,1);
			SetLastPmnt;
		end;
		4 : begin
			{4.3a - added Quick marker, moved bywho & notes to TOrder (and added idx)}
			inherited Load(S); {common init, lock, delete marker}
			PaidTotal.Load(S);
			S.Read(DaysDiscount,1);
			S.REad(Quick, 1);
			OldRef := -1; {temp fix for NSS}
			SetLastPmnt;
		end;
		5 : begin
			{4.3a - added Last Payment date}
			inherited Load(S); {common init, lock, delete marker}
			PaidTotal.Load(S);
			S.Read(DaysDiscount,1);
			S.REad(Quick, 1);
			LastPmnt.Load(S);
			Recalculate;
		end;
		6 : begin
			{4.3e - above needed Recalculate added due to cocked up Stromsholm update}
			inherited Load(S); {common init, lock, delete marker}
			PaidTotal.Load(S);
			S.Read(DaysDiscount,1);
			S.REad(Quick, 1);
			LastPmnt.Load(S);
		end;

	else
		DBaseMessage(@S, 'Version '+N2Str(Ver)+' not understood'#13#10'Invoice.Load',mfError,hcInternalErrorMsg);
		fail;
	end;

	CalculateTotals;
{	if State and osSent <>0 then AllowChanges := False;{}
end;

procedure TInvoice.StoreFields(var S : TDataStream);
var	Ver : byte;

begin
	Ver := 6; S.Write(Ver, 1);

	inherited StoreFields(S);

	PaidTotal.Store(S);
	S.Write(DaysDiscount,1);
	S.Write(Quick, 1);
	LastPmnt.Store(S);
end;


{assume only orderitem descendants will be hooked on}
function TInvoice.HookingOn;
begin
	if hktype = hkPayments then begin
		HookingOn := True;
		PaidTotal.Add(PPayment(HookingJimmy)^.Paid);
		if PPayment(HookingJimmy)^.Date.Days>LastPmnt.Days then LastPmnt.SetToDate(PPayment(HookingJimmy)^.Date);
	end else
		HookingOn := inherited HookingOn(Hktype, htType, HookingJimmy);

	CalculateTotals;
end;

function TInvoice.Unhooking;
var Payment : PPayment;
begin
	if hktype = hkPayments then begin
		UnHooking := True;
		PaidTotal.Subtract(PPayment(HookingJimmy)^.Paid);

		SetLastPmnt;		{-- work out new last payment date ---}
	end else
		UnHooking := inherited UnHooking(Hktype, htType, HookingJimmy);

	CalculateTotals;
end;


{--- Hooking on others -----}
function TInvoice.NumhkTypes;
begin NumhkTypes := 2; end; {hkStudents}

procedure TInvoice.GetHookOn(const hkType : byte; var HookRec : PLongint);
begin
	inherited GetHookOn(hkType, HookRec);
	case hktype of
		{order items done in inherited}
		hkPayments : HookRec := @Ptr2Payments;
	end;
end;

{-- Hooking to others -----}
function TInvoice.NumHookTo;
begin NumHookTo := 2; end; {only hooked to company's history}

{for returning which jimmys ID's this jimmys should be hooked *to*}
{ie override inherited hooktoid which auto does history}
procedure TInvoice.GetHookTo;
begin
	inherited GetHookTo(htType, HookToID,SubHookToID, hkType, Key, InsertBias); {0 always returns blank}
	case httype of
		1 : if not InvoiceSetup.CopyToHistory then hkType := 0; {o/w leave as inherited}
		2 : begin
			HookTOID := @ForWho;
			hkType := hkAccounts;
			Key := Key * 100 + 99-(Ref mod 100); {sort by last 2 digits of ref if more than one for a particular day}
			InsertBias := biStart;
		end;
	end;
end;

{============ INDEXING ==============}
{To out tray}
function TInvoice.NumixTypes : byte;
begin Numixtypes := 1; end;

procedure TInvoice.GetIndex;
begin
	inherited GetIndex(ixType, IdxRec, fiType);
	case ixType of
		1 : begin IdxRec := @OutTrayIdx; 	fiType := fiInvoicesOutIdx; end;
	end;
end;

function TInvoice.GetIndexKey;
begin
	GetIndexKey := '';

	case ixType of
		1 : if not Deleted and (State and osEdited >0) then GetIndexKey := Date.AsKey;
	end;
end;


{*****************************************
 ***  PRINTING                         ***
 *****************************************}
const
	InvoicePrintType : TJimmyPrintType =
		(Editor : edInternal;
		 Target : ptPrint;
		 DeviceName : '';
		 PrintAs : 0;
		 FormName : 'INVOICE';
		 NumCopies : 1;
		 PlusLabel : False);

procedure LinkInvoiceForm(const Linker : PInputLinker; const CallingView : PView); far;
var PrintAs : word;
		FormName : string;
begin
	CallingView^.GetData(PrintAs);

	case PrintAs of
		2 	: FOrmName := 'REMSLIP';
	else
		FormName := 'INVOICE';
	end;

	Linker^.TargetView[1]^.SetData(FOrmName);
	Linker^.TargetView[1]^.DrawView;
end;

procedure TInvoice.GetDefaultPrintType
										(var PrintType : TJimmyPrintType; var PrintAs : PSItem; var PrintAsLink : pointer);
begin
	PrintAs := 	NewSItem('~I~nvoice',
							NewSItem('P~r~oForma',
							NewSITem('R~e~mittance',nil)));
	PrintType := InvoicePrintType;
	if Quick then PrintType.FormName := 'QUICKINV';
	PrintAsLink := @LinkInvoiceForm;
end;

procedure TInvoice.PrintLabel;
begin
	inherited PrintLabel(Device, apInvoice);
end;

procedure TInvoice.SetFormCodes;
begin
	inherited SetFormCodes(FormCodes);
	CalculateTotals;

	with FormCodes^ do begin


		Insert(New(PJimmyFormCode, init('TO', ForWho))); {duplicates <FOR> but makes more sense on form}


		SetStr('DD', N2Str(DaysDiscount));

		Insert(New(PMoneyFormCode, init('PAID', PaidTotal)));

		Insert(New(PMoneyFormCode, init('DUE', Due)));

		Insert(New(PHookListFormCode, init('PAYMENTS', GetJimmy(RecNo), hkPayments, 'PAYMENT.FRM',0)));

		if Quick then
			Insert(New(PFreeTextFormCode, init('FOR', Notes^)));
	end;
end;

procedure TInvoice.PrintFull;
begin
	if Quick 	then PrintForm(Device, 'QUICKINV')
						else PrintForm(Device, 'INVOICE');
end;


{==== PRINT ALL ITEMS ==============}
{procedure TInvoice.PrintItems;
var Payment : PPayment;
begin
	inherited PrintItems(Device);

	FileAdmin(fiHooks)^.LogOn;

	Payment := PPayment(HookFile^.GetFirst(Ptr2Payments, 0));

	while Payment<>nil do begin
{		Payment^.PrintItem(Device, '');{}
{		dispose(Payment, done);
		Payment := PPayment(HookFile^.GetNextJimmy);
	end;

	FileAdmin(fiHooks)^.LogOff;
end;{}



procedure TInvoice.OnPrinting;
begin
	if PrintType.PrintAs = 0 then begin {if printed as invoice}
		{--- Send! -----}
		if (State and osSent)=0 then Send;

		inherited OnPrinting(PrintType); {set sent marker}
	end;
end;

procedure TInvoice.Send;
var Desc : string;
begin
	State := State or osSent;

	CalculateTotals;

	{$IFDEF kbooks}
		{CREATE NOMINAL LEDGER TRANSACTIONS}
		{create one for total of invoice}
		{get name as description for transaction}
		Desc :=  GetJimmyIDName(ForWho, naReport, 30);

		FileAdmin(fiTransactions)^.LogOn; {prevents inserttrans opening and closing and opening, etc}

		{---- Create transaction for income/debtors ----}
		InsertTransaction(Date, acIncome, 		acDebtors, Desc, TotallerGroup.Total, 'INV'+N2Str(Ref));

		{--- Create transaction for VAT -----------------}
		InsertTransaction(Date, acVATAccount, acDebtors, Desc+' (VAT)', TotallerGroup.VAT, 'INV'+N2Str(Ref));{}

		FileAdmin(fiTransactions)^.LogOff;
	{$ENDIF}
end;



procedure TInvoice.Recalculate;
var Payment : PPayment;
		SFindRec : longint;
		SFindsrtype : word;
begin
	PaidTotal.Clear;

	if not Quick then
		inherited Recalculate; {calculate for items}

	{--- Now do for payments ----}
	FileAdmin(fiHooks)^.LogOn;
	SetLastPmnt;

	Payment := PPayment(HookFile^.GetFirst(Ptr2Payments, 0));
	while Payment<>nil do begin
		{store findrec, etc as hookingon will "corrupt"}
		SFindRec := HookFile^.FindRec;
		SFindsrType := HookFile^.FindsrType;

		HookingOn(hkPayments, 1, Payment); {using httype of 1...}

		{restore}
		HookFile^.FindRec := SFindRec;
		HookFile^.FindsrType := SFindsrType;

		dispose(Payment, done);
		Payment := PPayment(HookFile^.GetNextJimmy);
	end;
	FileAdmin(fiHooks)^.LogOff;

	CalculateTotals;
end;


procedure TInvoice.CalculateTotals;
begin
	inherited CalculateTotals; {works out totaller group totals}

	if (State and osSent) = 0 then
		Due.Clear
	else
		Due.SetTo(TotallerGroup.Total); {totaller group total set to discounted/normal as appropriate}
	Due.Subtract(PaidTotal);
end;

function TInvoice.AllowDeletion;
begin
	AllowDeletion := (State and osSent) = 0; {only allowed if not sent yet}
end;


function TInvoice.OverdueLevel;
var Days : longint;
		First : integer;
begin
	Days := longint(TestDate.Days) - Date.Days; {forces a longint calculation}

	if (DaysDiscount<>0) and InvoiceSetup.DDOverride then
		First := DaysDiscount
	else
		First := InvoiceSetup.OverduePeriod[1];

	if Days>=InvoiceSetup.OverduePeriod[3] then
		OverdueLevel := 3
	else
		if Days>=InvoiceSetup.OverduePeriod[2] then
			OverdueLevel := 2
		else
			if Days>=First then
				OverdueLevel := 1
			else
				OverdueLevel := 0;
end;


function TInvoice.CashDiscountable;
var D : longint;
begin
	CashDiscountable := True;
	{has it been sent}
	if State and osSent >0 then begin
		{if it's still "current", no need to bother with rest of check}
		if Date.Days + DaysDiscount<Today.Days then begin
			{Ok, it's expired - now if it's been fully paid within the days
			allowed, it's still cashdiscountable}
			{can't really do a test on due as it might *not* be fully paid if we
			don't take into account the discount....{}
			CashDiscountable := False;
{			if Due.Blank or (Due.Value<0) then begin {check for <0 in case of miscalc previously - ie overpaid}
				D := Date.Days;
				D := D + DaysDiscount;
				D := D - LastPmnt.Days;
				if not LastPmnt.Blank and ((Date.Days+DaysDiscount)>=LastPmnt.Days) then
					CashDiscountable := True;
{			end;{}
		end;
	end;
end;

{Only used for older versions, recalc, etc}
procedure TInvoice.SetLastPmnt;
var Payment : PPayment;
begin
	LastPmnt.Clear;

	if Ptr2Payments = -1 then exit; {saves opening and closing file}

	FileAdmin(fiHooks)^.LogOn;
	Payment := PPayment(HookFile^.GetFirst(Ptr2Payments, 0));
	if Payment<>nil then begin
		LastPmnt.SetToDate(Payment^.Date);
		dispose(payment, done);
	end;
	FileAdmin(fiHooks)^.Logoff;
end;{}

{*****************************************
 ***    FIND INVOICE BY ID             ***
 *****************************************}
procedure FindInvoiceByID; far;
var Control : word;
		Ref : longint;
		Invoice : PInvoice;
		ID : longint;

begin
	Ref := 0;
	Control := InputLintBox('FIND INVOICE', 'Ref #', Ref);

	if Control<>cmCancel then begin
		ID := GetIDPtr(srInvoice, Ref);
		if ID=-1 then
			ProgramWarning('Could not find Invoice Ref '+N2Str(Ref),hcNoContext)
		else begin
			Invoice := PInvoice(GetJimmy(ID));
			Invoice^.Edit(Desktop, nil);
		end;
	end;
end;

{*****************************************
 ***        INVOICES OUT TRAY          ***
 *****************************************}

procedure StartInvoicesOutTray; far;
var Bounds : TRect;
		List : POutTrayListView;
begin
	Bounds.Assign(0,0,60,15);
	CentreOnView(Bounds, Desktop);

	List := New(POutTrayListView, Init(Bounds, lsInvoicesOut, fiInvoicesOutIdx,''));

	Application^.InsertWindow(New(PIndexedJimmyListWindow, init(Bounds, 'Invoices Out', List)));{}
end;

function NewInvoicesOutIndex : PStream; far;
begin NewInvoicesOutIndex := New(PIndexedJimmyStream, init('OUTINVS.IDX',TInvoiceIndexSize)); end;{}



{*****************************************
 ***  INITIALISATION/REGISTRATION      ***
 *****************************************}
const
	{--- Required for Stream ----}
	RInvoice : TStreamRec = (
		ObjType : srInvoice;
		VmtLink : Ofs(TypeOf(TInvoice)^);
		Load : @TInvoice.Load;
		Store : @TInvoice.Store
	);

function CreateInvoice(P : pointer) : pointer; far;
begin	CreateInvoice := New(PInvoice, init(P)); end;


{for when KQInv is removed...}
function CreateQuickInvoice(P : pointer) : pointer; far;
var Q : PInvoice;
begin
	New(Q, init(P));
	Q^.Quick := True;
	Q^.State := Q^.State or osSent; {automatically sent - for kilimanjaro...}
	CreateQuickInvoice := Q;
end;{}



begin
{$IFDEF fixit} writeln('KInvoice...'); {$ENDIF}

{$IFDEF kinvoice}
	{Register ordinary Invoice}
	RegisterJimmy(RInvoice, CreateInvoice, lsAccounts, '~I~nvoice');
	RegisterNewWithList(lsDesktop, '~I~nvoice', cmNewInvoice);{register with desktop}
	RegisterNewWithList(lsHistory, '~I~nvoice', cmNewInvoice);{history}

	{Register Quick Invoice creator & commands.  RType same as invoice}
{when KQInv is removed	RegisterCreator(cmNewQuickInvoice, CreateQuickInvoice);
	RegisterNewWithList(lsAccounts, 'Q~u~ick Invoice', cmNewQuickInvoice);{accounts list}
	RegisterNewWithList(lsDesktop, 'Q~u~ick Invoice', cmNewQuickInvoice);{desktop}
{	RegisterNewWithList(lsHistory, 'Q~u~ick Invoice', cmNewQuickInvoice);{history list}

	RegisterTask(DesktopTasks, cmFindInvoiceByID, @FindInvoiceByID);
{$ENDIF}

	RegisterTask(DesktopTasks, cmStartInvoicesOutTray, @StartInvoicesOutTray);
	NewFileAdmin(fiInvoicesOutIdx, 'Invoices Out Index',NewInvoicesOutIndex);{}

end.
