{****************************************************************************
 ***                                                                      ***
 ***                                                                      ***
 ***                        INVOICE/JOB/SALES/ETC NODES                   ***
 ***                                                                      ***
 *** M Hill                                                Split Dec 1994 ***
 ****************************************************************************}
{$I compdirs}  {Compiler directives}

unit kinvnode;

INTERFACE

uses  money, objects,
			scodes,
			editview, views,
			dattime,
{$IFDEF kdirctry}	inpdir, {$ENDIF}
			output,
			files,
			menus,
			drivers,
			notes,
			global,
			trees;


{********************************************
 ***        INVOICE NODE                  ***
 ********************************************}
{printing coded function for invoice/estimate forms
 to use it, set a code in setformcodes of the following format:
 Output^.SetCodedFunc('ITEMS',PrintInvoiceTree, @ItemTree);{}

function PrintInvoiceTree(Code,Param : string; Output,Info : pointer) : String;

{Setting codes to call above function}
{function SetInvoiceItemsCode(ItemTree : PNodeTree; Output : POutputStream) : string;{}

const
	TInvoiceNodeSize = TNodeItemSize + 30;

var
	LastDiscountPerc : single;  {remembers last discount and applies to all new}

type
	PInvoiceNode = ^TInvoiceNode;
	TInvoiceNode = object(TNodeItem)

		{Editor Fields}
		Total : TMoney;
    DiscountPerc : single;  {Modifier - margin/discount}
		Discount : TMoney;
		DiscountedTotal : TMoney;  {calculated}
		VATCode : TSCode; {VAT sentence code}
		VAT   : TMoney;  {VAT on total - stored in case of rate changes}
		DiscountedTotalincVAT : TMoney; {calculated}
		NomCat : TSCode;

    DiscountType : boolean; {True: by percentage, False: by amount}

		{Calculated fields - not stored}
		TotalincVAT : TMoney;

		NodeTotal : TMoney;    {Total including subnodes}
		NodeDiscount : TMoney;
		NodeDiscountedTotal : TMoney;
		NodeVAT : TMoney;
		NodeTotalincVAT : TMoney;
		NodeDiscountedTotalincVAT : TMoney;


		OldVerDate : TDate; {rubbish/dummy for load versions 1 & 2- the date will need to be passed to descendants}

		{-- Methods --}
		constructor Init;
		procedure CommonInit; virtual;
		destructor Done; virtual;

		procedure CalculateTotals;												virtual;
		procedure CalculateDiscountedTotal;
		procedure CalculateVAT;
		procedure CalculateTotalincVAT;
		procedure CalculateNodeTotal;							virtual;

		{DataBase}
		constructor Load(var S : TDataStream);
		procedure   Store(var S : TDataSTream);

		procedure MakeEditBox(var EditBox : PEditBox; Caller : PView);

		function Edit(Caller : PView; Focused : PObject) : word; virtual;

		procedure SetFormCodes(Output : POutputStream; PreviousItem : PInvoiceNode); virtual;
		function Print(prType : word; Output : POutputStream; PreviousItem : PNodeItem) : word; virtual;
	end;

type
	PInvTotalButton = ^TInvTotalButton;
	TInvTotalButton = object(TGetDataButton)
		constructor Init(X,Y : byte; ATiedObject : pointer);
		procedure Press; virtual;
	end;



type {required for hardware job nodes and product selection nodes that
			need to know the calling directory item to know which hardware list
			to pick up from/price band to select/etc}
{NB - ONLY PASS DIRECTORYLINE IF IT IS OF TYPE TINPUTDIRECTORY - THERE IS NO
TYPE CHECKING HERE AS WHEN COMPILING FOR EG CURTAINQUOTE, WE DON'T HAVE
ANY TINPUTDIRECTORY TYPES}

PDirNodeViewer = ^TDirNodeViewer;
TDirNodeViewer = object(TNodeViewer)
	DirectoryID : longint;
{	DirectoryLine : PInputELine;{}
	constructor Init(var Bounds: TRect;
										 NsrRestrictor : word;
										 NNewMenu : PMenu;
										 DirectoryLine : PInputELine);

	procedure HandleEvent(var Event : TEvent); virtual; {for update from link}
end;



{******************************************
 ***           CODED INVOICE ITEM       ***
 ******************************************}
function CreateCodedInvNode(P : pointer) : pointer; far;

const
	TCodedInvNodeSize = TInvoiceNodeSize + 40;

type
	PCodedInvNode = ^TCodedInvNode;
	TCodedInvNode = object(TInvoiceNode)

		Date : TDate;
		Code : string[3];        {Event code}
		CodeLine : string[12];   {STring of treatment codes}
		Comment : PNoteData;

		BasePrice : TMoney;
		Quantity : string[10];
		PriceEach : TMoney;

		{-- Methods --}
		constructor Init;
		procedure CommonInit; virtual;
		destructor Done; virtual;
		procedure CalculateTotals;												virtual;

		function GetKey : word; virtual;

		{Display}
		function DisplayLine(Maxlen : integer) : string; virtual;

		{DataBase}
		constructor Load(var S : TDataStream);
		procedure   Store(var S : TDataSTream);

		procedure MakeEditBox(var EditBox : PEditBox; Caller : PVIew);
		function Edit(Caller : PView; Focused : PObject) : word; virtual;

		procedure SetFormCodes(Output : POutputStream; PreviousItem : PInvoiceNode); virtual;
		function Print(prType : word; Output : POutputStream; PreviousItem : PNodeItem) : word;  virtual;
	end;

const
	{--- Required for Stream ----}
	RCodedInvNode : TStreamRec = (
		ObjType : srCodedInvNode;
		VmtLink : Ofs(TypeOf(TCodedInvNode)^);
		Load : @TCodedInvNode.Load;
		Store : @TCodedInvNode.Store
	);


{******************************************
 ***       FREE TEXT INVOICE ITEM       ***
 ******************************************}
function CreateFreeTextInvNode(P : pointer) : pointer; far;

const
	TFreeTextInvNodeSize = TInvoiceNodeSize + 40;

type
	PFreeTextInvNode = ^TFreeTextInvNode;
	TFreeTextInvNode = object(TInvoiceNode)

	 FreeText : PNoteData;

	 {-- Methods --}
	 constructor Init;
	 destructor Done; virtual;

		{Display}
		function DisplayLine(Maxlen : integer) : string; virtual;

		{DataBase}
		constructor Load(var S : TDataStream);
		procedure   Store(var S : TDataSTream);
		function GetKey : word; virtual;

		procedure MakeEditBox(var EditBox : PEditBox; Caller : PVIew);
		function Edit(Caller : PView; Focused : PObject) : word; virtual;

		procedure SetFormCodes(Output : POutputStream; PreviousItem : PInvoiceNode); virtual;
		function Print(prType : word; Output : POutputStream; PreviousItem : PNodeItem) : word;  virtual;
	end;

const
	{--- Required for Stream ----}
	RFreeTextInvNode : TStreamRec = (
		ObjType : srFreeTextInvNode;
		VmtLink : Ofs(TypeOf(TFreeTextInvNode)^);
		Load : @TFreeTextInvNode.Load;
		Store : @TFreeTextInvNode.Store
	);

{******************************************
 ***           Product INVOICE ITEM       ***
 ******************************************}
{$IFDEF kproduct}
function CreateProductInvNode(P : pointer) : pointer; far;

const
	TProductInvNodeSize = TInvoiceNodeSize + 40;

type
	PProductInvNode = ^TProductInvNode;
	TProductInvNode = object(TInvoiceNode)

	 ProductID : longint;

	 Quantity : string[10];
	 Units    : TScode;

	 PriceEach : TMoney;

	 {-- Methods --}
	 constructor Init;
		procedure CommonInit; virtual;
		destructor Done; virtual;
	 procedure CalculateTotals;												virtual;

		function GetKey : word; virtual;{}

		{Display}
		function DisplayLine(Maxlen : integer) : string; virtual;

		{DataBase}
		constructor Load(var S : TDataStream);
		procedure   Store(var S : TDataSTream);

		procedure MakeEditBox(var EditBox : PEditBox; Caller : PVIew);
		function Edit(Caller : PView; Focused : PObject) : word; virtual;

		procedure SetFormCodes(Output : POutputStream; PreviousItem : PInvoiceNode); virtual;
		function Print(prType : word; Output : POutputStream; PreviousItem : PNodeItem) : word;  virtual;
	end;

const
	{--- Required for Stream ----}
	RProductInvNode : TStreamRec = (
		ObjType : srProductInvNode;
		VmtLink : Ofs(TypeOf(TProductInvNode)^);
		Load : @TProductInvNode.Load;
		Store : @TProductInvNode.Store
	);
{$ENDIF}

{$IFDEF khrdware}
{******************************************
 ***         HARDWARE OWNED JOB ITEM    ***
 ******************************************}
function CreateHWInvNode(P : pointer) : pointer; far;

const
	THWInvNodeSize = TInvoiceNodeSize + 40;

type
	PHWInvNode = ^THWInvNode;
	THWInvNode = object(TInvoiceNode)

	 	HardWareID : longint;

		{-- Methods --}
		constructor Init;

		function GetKey : word; virtual;{}

		{Display}
		function DisplayLine(Maxlen : integer) : string; virtual;

		{DataBase}
		constructor Load(var S : TDataStream);
		procedure   Store(var S : TDataSTream);

		procedure MakeEditBox(var EditBox : PEditBox; Caller : PView);
		function Edit(Caller : PView; Focused : PObject) : word; virtual;

		function Print(prType : word; Output : POutputStream; PreviousItem : PNodeItem) : word;  virtual;
	end;

const
	{--- Required for Stream ----}
	RHWInvNode : TStreamRec = (
		ObjType : srHWInvNode;
		VmtLink : Ofs(TypeOf(THWInvNode)^);
		Load : @THWInvNode.Load;
		Store : @THWInvNode.Store
	);

{$ENDIF}


IMPLEMENTATION

uses
{$IFDEF kaccounts}			kaccount, {$ENDIF}
			errors,  msgview,
			inpdnt,
      tui, dialogs,
			setup,
			vat,
			app,
{$IFDEF kproduct}
			kproduct,inpprod, inplist, jimmys, kdirctry, {for product item}
{$ENDIF}
{$IFDEF khrdware}
			khrdware, inphware, jimhooks,{}    {for input hardware type}
{$ENDIF}
			kinvoice,
			forms,
			tasks,
			help,
			minilib;

function NewInvoiceNodeStream : PStream;
begin
	NewInvoiceNodeStream :=New(PNodeStream, init('InvItems.nde',1));  {Invoice item list}
end;

function CreateCodedInvNode(P : pointer) : pointer;
begin	CreateCodedInvNode := New(PCodedInvNode, init); end;

function CreateFreeTextInvNode(P : pointer) : pointer;
begin	CreateFreeTextInvNode := New(PFreeTextInvNode, init); end;


{**********************************************************
 ***           NODE VIEWER LINKED TO DIRECTORY LINE     ***
 **********************************************************}
{============== USED FOR MANY JIMMY TYPES WHERE NODE LIST NNEDS TO KNOW
WHO THE LIST WILL BELONG TO =============================================}
{required for hardware job nodes and product selection nodes that
			need to know the calling
			directory item to know which hardware list to pick up from/price band
			to select/etc}

constructor TDirNodeViewer.Init;
begin
	inherited Init(Bounds, NsrRestrictor, NNewMenu);
{	DirectoryLine := NDirectoryLine;{}
	if DirectoryLine<>nil then begin
		DirectoryLine^.SetTargetLink(@Self);
		DirectoryLine^.LinkChanged := True; {Force update below as soon as operated, so it will set directoryid}
	end;
	DIrectoryID := -1;
end;

procedure TDirNodeViewer.HandleEvent;
begin
{$IFDEF kdirctry}
	{update from link of directory item this list is linked to}
	if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
		DirectoryID := PInputDirectory(Event.InfoPtr)^.ID;
		{Continue on to do other updatefromlink stuff}
	end;
{$ENDIF}

	inherited HandleEvent(Event);
end;

{*************************************************************************
 ***                                                                   ***
 ***            STANDARD INVOICE ITEM PARENT                           ***
 ***                                                                   ***
 *************************************************************************}

function CreateInvoiceNode(P : pointer) : pointer; far;
begin	CreateInvoiceNode := New(PInvoiceNode, init); end;

constructor TInvoiceNode.Init;
begin
	inherited Init;

	Total.SetTo(0);
	VAT.SetTo(0);  {VAT on total}
	Discount.SetTo(0);
	DiscountPerc := LastDiscountPerc; {remembers last discount for block discounts of items}
	DiscountType := True; {do by percentage}

	OldVerDate.Clear;

{$IFDEF kaccounts}
	VATCode := AccountsSetup.DefaultVATRate;
	NomCat := AccountsSetup.acIncome;
{$ELSE}
	VATCode := 'STD';
	NomCat := '';
{$ENDIF}
end;

procedure TInvoiceNode.CommonInit;
begin
	inherited CommonInit;
{$IFDEF kaccounts}	ScodeAdmin[scAccounts]^.LogOn; {$ENDIF}
	SCodeAdmin[scVATRates]^.LogOn;

  {not stored, so always need set to 0}
	TotalincVAT.SetTo(0);
	DiscountedTotal.SetTo(0);
	DiscountedTotalincVAT.SetTo(0);
	NodeTotal.SetTo(0);
	NodeDiscount.SetTo(0);
	NodeDiscountedTotal.SetTo(0);
	NodeVAT.SetTo(0);
	NodeTotalincVAT.SetTo(0);
	NodeDiscountedTotalincVAT.SetTo(0);
end;

destructor TInvoiceNode.Done;
begin
{$IFDEF kaccounts}	ScodeAdmin[scAccounts]^.LogOff; {$ENDIF}
	SCodeAdmin[scVATRates]^.LogOff;
	inherited Done;
end;

procedure TInvoiceNode.CalculateTotals;
begin
  {Once total has been calculated, a call to CalculateTotals will set up
	everything else (Discounted tota, VAT, etc) ready for TInvoiceNode.Edit}
  CalculateDiscountedTotal;

	CalculateVAT;

	CalculateTotalincVAT;
end;

procedure TInvoiceNode.CalculateVAT;
var
	VATSCodeItem : PVATRAteScodeItem;
begin
	VATSCodeItem := PVATRateScodeItem(GetSCode(scVATRates, VATCode));

	if VATSCodeItem<>nil then
		VAT.SetTo(DiscountedTotal.inPounds * VATSCodeItem^.Rate/100)
	else
		VAT.SetTo(0); {nothing entered}
end;


procedure TInvoiceNode.CalculateDiscountedTotal;
begin
	if DiscountType then {by percentage}
		Discount.SetTo(Total.inPounds * DiscountPerc / 100)
	else
		if Total.Blank then DiscountPerc := 0 else DiscountPerc := Discount.inPounds/Total.inPounds;

	DiscountedTotal.SetToPence(Total.inPence - Discount.inPence);
end;

procedure TInvoiceNode.CalculateTotalincVAT;
begin
	{Set VAT Rate - VAT always calculated on discounted total}
{	if DiscountedTotal.inPence<>0 then VATRate := (VAT.inPence/DiscountedTotal.inPence)*100 else VATRate := 0;{}

	{Calculate totals.  Stored both with and without discount for period discounts}
	TotalincVAT.SetToPence(Total.inPence + VAT.inPence);
	DiscountedTotalincVAT.SetToPence(DiscountedTotal.inPence + VAT.inPence);
end;

procedure TInvoiceNode.CalculateNodeTotal;
var WorkItem : PInvoiceNode;
begin
	NodeTotal.SetTo(Total.inPounds);
	NodeVAT.SetTo(VAT.inPounds);
	NodeDiscountedTotal.SetTo(DiscountedTotal.inPounds);
	NodeDiscount.SetTo(Discount.inPounds);

	{Total children's node totals -> recursive}
  WorkItem := PInvoiceNode(Heap.FirstChild);
	while WorkItem<>nil do begin
		WorkItem^.CalculateNodeTotal;

		NodeTotal.Add(WorkItem^.NodeTotal);
		NodeVAT.Add(WorkItem^.NodeVAT);
		NodeDiscountedTotal.Add(WorkItem^.NodeDiscountedTotal);
		NodeDiscount.Add(WorkItem^.NodeDiscount);

		WorkItem := PInvoiceNode(WorkItem^.Heap.Next);
	end;

	NodeTotalincVAT.SetToPence(NodeTotal.inPence + NodeVAT.inPence);
	NodeDiscountedTotalincVAT.SetToPence(NodeDiscountedTotal.inPence + NodeVAT.inPence);
end;


{**************************************************
 ***                  EDIT NODE                 ***
 **************************************************}
{========= SPECIAL AUTOMATIC LINES ==================}
type
	PDisplayVAT = ^TDisplayVAT;
	TDisplayVAT = object(TInputMoney)
		DiscountedLine,VATCodeLine : PInputELine;
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	PInputDiscount = ^TInputDiscount;
	TInputDiscount = object(TinputMoney)
		TotalLine, DiscountPercLine,DiscountTypeLine : PInputELine;
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	PInputDiscountPerc = ^TInputDiscountPerc;
	TInputDiscountPerc = object(TinputSingle)
		TotalLine, DiscountLine,DiscountTypeLine : PInputELine;
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	PInputDiscounted = ^TInputDiscounted;
	TInputDiscounted = object(TinputMoney)
		TotalLine, DiscountLine,DiscountPercLine, DiscountTypeLine : PInputELine;
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	PInputGross = ^TInputGross;
	TInputGross = object(TinputMoney)
		DiscountedLine, VATLine : PInputELine;
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	{for displaying/marking discounttype}
	PDisplayDiscountType = ^TDisplayDiscountType;
	TDisplayDiscountType = object(TInputBoolean)
		procedure HandleEvent(var Event : TEvent); virtual;
		procedure Draw; virtual;
	end;


	{=== Display VAT ====}
	procedure TDisplayVAT.HandleEvent;
	var Total : TMoney;
			VATSCodeItem : PVATRateSCodeItem;
			VATCode : TSCode;
	begin
		{updates from two possible places - a change to the total, and a change to
		the VAT Code}
		if (Event.What= evCommand) and (Event.Command = cmUpdatefromLink) then begin
			Total.Init; DiscountedLine^.GetData(Total);
			VATCodeLine^.GetData(VATCode); VATSCodeItem := PVATRateScodeItem(GetScode(scVATRates,VATCode));

			if VATScodeItem<>nil then begin
				Total.SetToPence(trunc(Total.inPence * VATScodeItem^.Rate/100));    {Turn into VAT}
				SetData(Total);
			end;
		end;

		inherited HandleEVent(Event);
	end;

	{=== Discount (money value) line ========}
	procedure TInputDiscount.HandleEvent;
	var NewDiscMoney : TMoney;
			DiscountPerc : single;
			S : string;
			B : boolean;

	begin
		if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
			TotalLine^.GetData(NewDiscMoney);
			DiscountPercLine^.GetData(DiscountPerc);
			NewDiscMoney.SetTo(NewDiscMoney.inPounds * DiscountPerc/100);
			SetData(NewDiscMoney);

			DiscountTypeLine^.GetData(B);
			if B then begin
				ClearEvent(Event); {don't go on to check link if calc is by % not œ}
				Draw;
			end;
		end;

		S := Data^; {for checking to see if data changed - *not* by updatefromlink}
		inherited HandleEvent(Event);
		if S<>Data^ then
			Message(DiscountTypeLine, evCommand, cmUpdateFromLink, nil); {if data changed, switch to discount by amount}
	end;


	procedure TInputDiscountPerc.HandleEvent;
	var Total,Discount : TMoney;
			S : single;
			SS : string;
      L : longint;
			B : boolean;

	begin
		if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
			TotalLine^.GetData(Total);
			DiscountLine^.GetData(Discount);
      if Total.inPence <> 0 then begin
        {round to nearest .1%}
      	L := 1000*Discount.inPence div total.inPence;
        S := L/10;
			  SetData(S);
			  Draw;
      end;
			DiscountTypeLine^.GetData(B);
			if not B then ClearEvent(Event); {don't go on to check link if calc is by œ not %}
		end;

		SS := Data^; {for checking to see if data changed - *not* by updatefromlink}
		inherited HandleEvent(Event);
		if SS<>Data^ then
			Message(DiscountTypeLine, evCommand, cmUpdateFromLink, @Self); {if data changed, switch to discount by perc}
	end;

	procedure TInputDiscounted.HandleEvent;
	var Total,Discount : TMoney;
			Perc : single;
			B : boolean;
	begin
		if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
			DiscountTypeLine^.GetData(B);
			TotalLine^.GetData(Total);

			if B then begin
				{calc by percentage}
				DiscountPercLine^.GetData(Perc);
				Total.SetTo(Total.inPounds * (100 - Perc) /100);
			end else begin
				{calc by amount}
				DiscountLine^.GetData(Discount);
        Total.SetTo(Total.inPounds-Discount.inPounds);
			end;
			SetData(Total);
		end;

		inherited HandleEvent(Event);
	end;

	procedure TInputGross.HandleEvent;
	var Discounted,VAT : TMoney;
	begin
		if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
			DiscountedLine^.GetData(Discounted);
			VATLine^.GetData(VAT);
			Discounted.Add(VAT);
			SetData(Discounted);

	  	{The recalculation of the targetlink's data should *only* occur if
	    the *user* has changed the entry, not if the link update does it.  This
			is because 1) There is no need - it's been calculated from the other
			fields, 2) If you do it will loop as it recalculates the total line, etc
			and that then recalculates this one, etc...}
      Draw;
      ClearEvent(Event);
		end;

		inherited HandleEvent(Event);

	end;

	procedure TDisplayDiscountType.HandleEvent;
	var B : boolean;
	begin
		if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
			if Event.InfoPtr = nil then B := False else B := True;
			SetData(B);
		end;

		inherited HandleEvent(Event);
	end;

	procedure TDisplayDiscountType.Draw;
	begin
		if Data^='X' then writechar(0,0, '%', 1,1) else writechar(0,0, 'œ', 1,1);
  end;


procedure TInvoiceNode.MakeEditBox;
var R : Trect;
		TotalLine,VATCodeLine : PInputELine;
		VATLine : PDisplayVAT;
		DiscountPercLine : PInputDiscountPerc;
		DiscountLine : PInputDiscount;
		DiscountedLine : PInputDiscounted;
		DiscountTypeLine : PDisplayDiscountType;
		GrossLine : PInputGross;

begin
	CalculateDiscountedTotal;

	R.Assign(0,0,30,13);
	New(EditBox, init(R, 'Item Totals',Caller));

	CentreInView(Caller, EditBox^);

	with EditBox^ do begin
		Insert(New(PSkipBytes, init(sizeof(TNodeItem))));

		InsTitledField(14, 2, 8, 1,      '~T~otal      ',	New(PInputMoney, init(R,8)));
		TotalLine := PInputELine(Current);
		if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
		InsTitledField( 8, 3, 3, 1, '- %',			New(PinputDiscountPerc, init(R, 3)));
		if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
		DiscountPercLine := PInputDiscountPerc(Current);
		InsTitledField(14, 3, 8, 1, '', 			New(PInputDiscount, init(R,8)));
		if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
		DiscountLine := PInputDIscount(Current);
		InsTitledField(14, 4, 8, 1,       'Nett      ', New(PInputDiscounted, init(R, 8)));
		if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
		DiscountedLine := PInputDiscounted(Current);
		Current^.SetState(sfDisabled, True); {Display only}

		InsTitledField( 8, 5, 3, 1,        '~V~AT', New(PInputSCode, init(R, scVATRates)));
		VATCodeLine := PInputELine(Current);	if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
		InsTitledField(14, 5, 8, 1,        '', New(PDisplayVAT, init(R,8)));
		VATLine := PDisplayVAT(Current);  Current^.SetState(sfDisabled, True); {display line, not editable}

		InsTitledField(14, 6, 8, 1,      'Gross      ',  New(PInputGross, init(R,8)));
		if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
		GrossLine := PInputGross(Current);
		Current^.SetState(sfDisabled, True);{At the moment, there is no backwards calculation}

{$IFDEF kaccounts}
		InsTitledField( 8, 8,16, 1, '~A~/c', New(PinputSCode, init(R, scAccounts)));
		if not CommandEnabled(cmOK) then Current^.SetState(sfDisabled, True);
{$ENDIF}

		R.Assign(25,3,26,4); Insert(New(PDisplayDiscountType, init(R)));
		DiscountTypeLine := PDisplayDiscountType(Current);
		Current^.SetState(sfDisabled, True);{At the moment, there is no backwards calculation}
	end;

	{BEWARE! The targetlinks below run around in a loop - thus the LinkChanged
	marker has to be cleared in the .handleevent methods above, or it will
	quite happily loop on forever...}

	TotalLine^.SetTargetLink(DiscountedLine);

	{discount percentage line is calced from total line and discount amount line, and changes calc discount amount}
	DiscountPercLine^.TotalLine := TotalLine;
	DiscountPercLine^.DiscountLine := DiscountLine;
	DiscountPercLine^.DiscountTypeLine := DiscountTypeLine;
	DiscountPercLine^.SetTargetLink(DiscountLine); {discountperc line calcs discount line}
	DiscountPercLine^.SetTargetLink(DiscountedLine); {discountperc line calcs discount line}

	{discount amount line is calced from total line & discount percentage, and changes calc the discounted total}
	DiscountLine^.TotalLine := TotalLine;
	DiscountLine^.DiscountPercLine := DiscountPercLine;
	DiscountLine^.DiscountTypeLine := DiscountTypeLine;
	DiscountLine^.SetTargetLink(DiscountedLine);
	DiscountLine^.SetTargetLink(DiscountPercLine);

	{discounted line is calced from total line & discount amount, and calcs VAT}
	DiscountedLine^.TotalLine := TotalLine;
	DiscountedLine^.DiscountLine := DiscountLine;
	DiscountedLine^.DiscountPercLine := DiscountPercLine;
	DiscountedLine^.DiscountTypeLine := DiscountTypeLine;
	DiscountedLine^.SetTargetLink(VATLine); {VAT from discounted total}

	{VAT line is calced from VAT code & Change in VAT --> change in Net Line}
	VATCodeLine^.SetTargetLink(VATLine);

	VATLine^.DiscountedLine := DiscountedLine;
	VATLine^.VATCodeLine := VATCodeLine;
	VATLine^.SetTargetLink(GrossLine);

	{Gross Line - for working backwards from a given total.  Not used.}
	GrossLine^.DiscountedLine := DiscountedLine;
	GrossLine^.VATLine := VATLine;
	GrossLine^.SetTargetLink(TotalLine); {if this is changed, rework total & VAT}

	{--Buttons--}
	Editbox^.InsOKButton( 8,10, @Self);
	EditBox^.InsCancelButton(19,10);

	EditBox^.EndInit;
end;


function  TInvoiceNode.Edit;
var EditBox : PEditBox;
		Control : word;

begin
	MakeEditBox(EditBox, Caller);

	EditBox^.SetData(Self);

	Control := Desktop^.ExecView(EditBox);  {return Ok/Cancel/etc}

	LastDiscountPerc := DiscountPerc;  {remember for next time}

	dispose(EditBox, done);

	Edit := Control;
end;



{************************
 *** LOAD            ****
 ************************}
constructor TInvoiceNode.Load;
var Ver : byte;
		Rubbish : longint;
		OldNomCat : word; {old ver had a word not a scode}

begin
	S.Read(Ver, 1);

{	CommonInit;  {set to nil, etc - done by inherited load}
  VATCode := '';
	DiscountType := True; {default to %}

	case Ver of
		1 : begin
			inherited Load(S);
			S.Read(Rubbish, 4);
			OldVerDate.Load(S);
			Total.SetTo(0);
			VAT.SetTO(0);
			DiscountPerc := 0;
			Discount.SetTo(0);
		end;
		2 : begin
			inherited Load(S);
			OldVerDate.Load(S);{}
			Total.Load(S);
			VAT.Load(S);
{			S.Read(Discount, sizeof(Discount));{}
			S.Read(OldNomCat, 2); if OldNomCat>0 then NomCat := L2Str(OldNomCat) else NomCat := '';
			DiscountPerc := 0;
			Discount.SetTo(0);
		end;
		3 : begin
			inherited Load(S);
			Total.Load(S);
			VAT.Load(S);
			S.Read(DiscountPerc, sizeof(DiscountPerc));{}
			Discount.Load(S);
			S.Read(NomCat, sizeof(NomCat));
{$IFDEF Kaccounts}		VATCode := AccountsSetup.DefaultVATRate; {$ENDIF}
		end;
		4 : begin
			{added VAT Code & DiscountType}
			inherited Load(S);
			Total.Load(S);
			S.Read(VATCode, sizeof(VATCOde));
			VAT.Load(S);
			S.Read(DiscountPerc, sizeof(DiscountPerc));{}
			Discount.Load(S);
			S.Read(NomCat, sizeof(NomCat));
			S.Read(DiscountType, sizeof(DiscountType));{}
		end;
	else
		{OH DEAR OH DEAR.  Lowest byte gets stored first, so ver now contains, if
		reading the old version, any number as it is the low byte of the disk.next pointer...}
{		DBaseError(@S,'Version '+L2Str(Ver)+' not recognised','Retreiving Index Item');{}

		CommonInit;
		{Old chain item version}
		S.Seek(S.GetPos-1);  {Recover from having read ver}
		S.Read(Disk.Next, 4);
		S.Read(Disk.Prev, 4);
		S.Read(Rubbish, 4);  {Was pointer to invoice}
{		Disk.Parent := -1;{}
		Disk.FirstChild := -1;
		OldVerDate.Load(S);
		Total.SetTo(0);
		VAT.SetTO(0);
		DiscountPerc := 0;{}
		Discount.SetTo(0);
	end;

	CalculateDiscountedTotal;
	CalculateTotalincVAT;
end;

procedure TInvoiceNode.Store;
var Ver : byte;
begin
	Ver := 4; S.Write(Ver, 1);

	inherited Store(S);

	Total.Store(S);
	S.Write(VATCode, sizeof(VATCOde));
	VAT.Store(S);
	S.Write(DiscountPerc, sizeof(DiscountPerc));{}
	Discount.Store(S);
	S.Write(NomCat, sizeof(NomCat));
	S.Write(DiscountType, sizeof(DiscountType));{}
end;


{***************************
 *** PRINT               ***
 ***************************}
    {---- Running totals ----}
    {*make sure* that before calling invoicenode tree print,
		that rdtot and rtot are cleared...}

		{Problem here with the fact that this setformcodes method is called
    *before* the print routine.  Therefore the print routine may force a
    new page during the print, and this may be before the total is printed
		or afterwards.  So the TOT codes have to be function codes, so that as
    soon as they are "replaced", the running total is updated}

    {So although the value is stored in TOTVAL code, and the user should
		uses <TOT> to actually insert the amount...}

function InvoiceNodeTot(Code,Param : string; Output,Info : pointer) : string;
var Tot,Rtot : TMoney;
begin
	{get value from TOTVAL code}
	Tot.SetTo(S2Real(POutputStream(Output)^.Usercodes.Decode('TOTVAL',''))); {store actual value in totval code}

	{Set running total}
	Rtot.SetTo(S2Real(POutputStream(Output)^.Usercodes.deCode('RTOT','')));
	Rtot.Add(Tot);
	POutputStream(Output)^.SetCode('RTOT',Rtot.Text(mtPoundsPence));

	{Return for <tot>}
	InvoiceNodeTot := POutputStream(Output)^.UserCodes.Decode('TOTVAL',Param);
end;

function InvoiceNodeDiscountedTot(Code,Param : string; Output,Info : pointer) : string;
var DTot,RDtot : TMoney;
begin
	{get value from TOTVAL code}
	DTot.SetTo(S2Real(POutputStream(Output)^.Usercodes.Decode('DTOTVAL',''))); {store actual value in totval code}

	{Set running total}
	RDtot.SetTo(S2Real(POutputStream(Output)^.Usercodes.deCode('RDTOT','')));
	RDtot.Add(DTot);
	POutputStream(Output)^.SetCode('RDTOT',RDtot.Text(mtPoundsPence));

	{Return for <Dtot>}
	InvoiceNodeDiscountedTot := POutputStream(Output)^.UserCodes.Decode('DTOTVAL',Param);
end;



procedure TInvoiceNode.SetFormCodes;
var RTot,Tot : TMoney;
begin
	{Don't clear all codes o/w you end up clearing header/etc codes.... how much room?!}
	with Output^ do begin

		SetCode('TOTVAL','');
		SetCode('DTOTVAL','');

		SetCode('V%','');
		SetCode('VAT','');

		SetCode('CDT',''); {set "changed date" to blank for those items with no date field}

		if DiscountType = True then
			{do by percentage - work out discount}
       Discount.SetToPence(Total.inPence - DiscountedTotal.inPence)
		else
			{do by amount - work out percentage}
			if Total.Blank then DiscountPerc := 0 else DiscountPerc := Discount.inPence * 100/Total.inPence;

		if DiscountPerc = 0 then SetCode('D%', '') else SetCode('D%',R2Str(DiscountPerc,dcUpTo+1,0)+'%');
		if Discount.inPence = 0 then SetCode('DISC', '') else SetCode('DISC',Discount.PoundsPenceStr);

		if Expanded then begin
			{Expanded, so just this items totals}
			{At the moment blanks if nothing there...}
			if Total.inPence <> 0 then SetCode('TOTVAL',Total.PoundsPenceStr);
			if DiscountedTotal.inPence <> 0 then SetCode('DTOTVAL',DiscountedTotal.PoundsPenceStr);

			if (VATCode<>'') then begin   {Only if some sort of VAT rate entered}
				if not Total.Blank then SetCode('V%',R2Str(VAT.inPounds * 100/Total.inPounds,dcUpTo +1,0));
				SetCode('VAT',VAT.PoundsPenceStr);  {VAT total}
			end;

			SetCode('VTOT',TotalincVAT.PoundsPenceStr);  {item total inc VAT}
		end else begin
			{not expanded, so set node total}
			if NodeTotal.inPence <> 0 then SetCode('TOTVAL',NodeTotal.PoundsPenceStr);
			if NodeDiscountedTotal.inPence <> 0 then SetCode('DTOTVAL',NodeDiscountedTotal.PoundsPenceStr);

			if VATCode<>'' then begin   {Only if some sort of VAT rate entered}
				if not NodeTotal.Blank then SetCode('V%',R2Str(NodeVAT.inPounds * 100/NodeTotal.inPounds, dcUpTo +1,0));
				SetCode('VAT',NodeVAT.PoundsPenceStr);  {VAT total}
			end;

			SetCode('VTOT',NodeTotalincVAT.PoundsPenceStr);  {item total}
		end;

		{At any rate, having the node total is good for subtotals, so}
		SetCode('SUBTOT', NodeTotal.PoundsPenceStr);
		SetCode('SUBVAT', NodeVAT.PoundsPenceStr);
		SetCode('SUBVTOT', NodeTotalincVAT.PoundsPenceStr);

  	{and set TOT and DTOT to functions that will update running totals}
		SetCodedFunc('TOT',InvoiceNodeTot, nil);
    SetCodedFunc('DTOT', InvoiceNodeDiscountedTot, nil);
	end;
end;


function TInvoiceNode.Print;  {Could call if form not found for descendant}
begin
	SetFormCodes(Output, PInvoiceNode(PreviousItem));

	case (prType and pmPrintAs) of
		prEstimate : Print := Output^.PrintForm('STDEI.FRM');
		prWorkSheet :	Print := Output^.PrintForm('STDWS.FRM');
		prPurchaseOrder :	Print := Output^.PrintForm('STDPO.FRM');
	else
		Print := Output^.PrintForm('STDII.FRM');
	end;

	if not Output^.FormFound then Print := Output^.PrintForm('STDII.FRM');
end;

{*************************************************************************
 ***                                                                   ***
 ***               CODED INVOICE ITEM PARENT                           ***
 ***                                                                   ***
 *************************************************************************}
constructor TCodedInvNode.Init;
begin
	inherited Init;
	Date.SetToToday;
	Quantity := '';
	BasePrice.Init;
	PriceEach.Init;
	Code := '';
	CodeLine := '';
end;

procedure TCodedInvNode.CommonInit;
begin
	inherited CommonInit;
	SCodeAdmin[scEvents]^.LogOn;
	SCodeAdmin[scActions]^.LogOn;

	New(Comment, init);
end;

destructor TCodedInvNode.Done;
begin
	ScodeAdmin[scEvents]^.LogOff;
	ScodeAdmin[scActions]^.LogOff;
	dispose(Comment, done);
	inherited Done;
end;

procedure TCodedInvNode.CalculateTotals;
var Q : real;   {forces inlongintrange parameter to be calced in real}
begin
	Q := S2Real(Quantity); if Q=0 then Q:=1;  {No quantity as such, but assume there will be one of price if anything entered}

	if inLongintRange(Q * PriceEach.inPence) then begin
		{Check in range}
		Total.SetToPence(round(BasePrice.inPence + (Q * PriceEach.inPence)));
	end else begin
		Total.SetToPence(0);     {Overload warning}
	end;

	inherited CalculateTotals;
end;

function TCodedInvNode.GetKey;
begin
	if Date.Blank then
		GetKey := 1   {appear in line with non-dated items - paymentnode uses 0 to make sure it appears at start}
	else
		GetKey := Date.Days; {date order}
end;


{******************************
*** DISPLAY LINE           ***
******************************}

function TCodedInvNode.DisplayLine;
var S,SE,SD : string;
	 SCErr : boolean;

begin
	if not Comment^.Loaded then Comment^.LoadText;

	if Maxlen = 0 then Maxlen := 70;

	SE := SCodeAdmin[scEvents]^.Collection^.Desc(Code);
	SD := SCodeAdmin[scActions]^.Collection^.ExpandLine(CodeLine, SCErr);

	{First line}
	if Date.Blank then S := '' else S := Date.Digit8+' ';

	if SE<>'' then S := S + SE
		else if SD<>'' then S := S +SD
			else S := S + Comment^.Line(1);

	if delspace(Quantity) <> '' then S := S+' x'+Quantity;

	{Extra lines}
	if (SE<>'') and (Codeline<>'') then S := S +#13+space(10)+SD; {Add description if not already on first line}
	if ((SE<>'') or (Codeline<>'')) and (Comment^.ExtractAll<>'') then S := S + #13+Comment^.ExtractAll;

	if TekkyMode then
		S := S + #13+'TEK: Rec '+L2Str(RecNo)+' N'+L2Str(Disk.Next)+' P'+L2Str(Disk.Prev)+' C'+L2Str(Disk.FirstChild);


	DisplayLine := S;
end;

{*******************************************
 ***            EDIT                     ***
 *******************************************}
constructor TinvTotalButton.Init;
var R : TRect;
begin
	R.ASsign(X,Y, X+10,Y+2);
	inherited Init(R, 'T~o~tal', cmTotal, bfDefault, ATiedObject);

	kbType := kbF10;
	{Do check for enabled here & disable if necessary, as some inputlines
	calling lists end up restoring cmOK}
	{This is no good as we should be able to view the discount amount...}
{	if not CommandEnabled(cmOK) then SetState(sfDisabled);{}
end;

procedure TinvTotalButton.Press;
var Control : word;
begin
	if (TiedObject <> nil) and CommandEnabled(Command) then begin
		if Owner^.Valid(Command) then begin
			DrawState(True);

			Owner^.GetData(TiedObject^);  {Read data from box into curtain item}
			Owner^.SetState(sfActive, False);  {frame off, etc}

			if CommandEnabled(cmOK) then {if not viewonly, ie invoice sent}
				PInvoiceNode(TiedObject)^.CalculateTotals;

			Control := PInvoiceNode(TiedObject)^.Edit(@Self,nil);
			Owner^.SetState(sfActive, True);
			if Control <> cmCancel then begin
				Command := Control;  {get what was pressed for the totals OK button and "press" that}
				inherited Press;
			end;

			DrawState(False);
		end;
	end;
end;

procedure TCodedInvNode.MakeEditBox;
var Bounds, R : TRect;
		PriceLine, ActionLine, EventLine : PVIew;

begin
	if not Comment^.Loaded then Comment^.LoadText;

	Bounds.Assign(0, 0, 45,11);

	{This was an invoiceeditbox, can't remember why...}
	EditBox := New(PEditBox, init(Bounds, 'CODED ITEM',Caller));
	EditBox^.HelpCtx := hcEditBox;

	{----Position box, in centre of calling view----}
	CentreinView(Caller, EditBox^);

	with EditBox^ do begin
		Insert(New(PSkipBytes, init(sizeof(TInvoiceNode))));  {Skip detail fields & VMT}
		InsTitledField(10,  1, 10, 1, 'Date ', New(PInputDate, Init(R)));
		InsTitledField(10,  2, 30, 1, 'Event', New(PInputSCode, Init(R, scEvents)));
		EventLine := Current;
		InsTitledField(10,  3, 30, 1, 'Action', New(PInputSCLine, Init(R, 12, scActions)));
		ActionLine := Current;
		InsTitledField(10,  4, 30, 2, 'Comment', New(PInputNote, Init(R,200,0,nil, EditBox)));

		InsTitledField(10,  6,  7, 1, 'Price', New(PInputMoney, init(R, 9)));
		InsTitledField(25,  6,  5, 1, '+Qty', New(PInputELine, Init(R, 10)));
		InsTitledField(35,  6,  7, 1, '@',     New(PInputScodePrice, Init(R, 9,EventLine)));
		PriceLine := Current;

		{--Buttons--}
		{if it's a priced view - eg sales order, invoice, estimate - ie caller
		is TInvoiceNodeViewer - then put in total button, o/w use ordinary OK
		button}
		if typeof(Caller^)=typeof(TInvoiceNodeViewer) then
			Insert(New(PinvTotalButton, init(22, 8, @Self)))
		else
			InsOKButton(22, 8, @Self);
		InsCancelButton(32, 8);

		EndInit;
	end;

	{Make links}
	PInputELine(ActionLine)^.SetTargetLink(PriceLine);
	{Event link already done in inputscodeprice.init above}

{	EditBox^.SetData(Self);   {Sets data's so that following lines will work.  Will be set again by edit method}
{	PInputELine(EventLine)^.SetOldData; {So it doesn't cause a change}
{	PInputELine(ActionLine)^.SetOldData;{}

end;


function  TCodedInvNode.Edit;
var EditBox : PEditBox;
begin
	if typeof(Caller^)=typeof(TinvTotalButton) then
		Edit := inherited Edit(Caller, nil)  {Edit previous level - BEWARE LOOPING!}
	else begin
		MakeEditBox(EditBox, Caller);

		EditBox^.SetData(Self);

		Edit := Desktop^.ExecView(EditBox);  {return Ok/Cancel/etc}

		dispose(editbox, done);
	end;
end;


{************************
 *** LOAD            ****
 ************************}
constructor TCodedInvNode.Load;
var Ver : byte;
		Com : string[20]; {old comment line}
begin
{	CommonInit; done by inherited load}
	S.Read(Ver, 1);

{	Date.Load(S);{}
	inherited Load(S);

	case Ver of
		1 : begin
			Date.SetToDate(OldVerDate);
			ReadSCode(S, @Code);
			ReadSCLine(S, @CodeLine, 12);
			Quantity := S.ReadFixedStr(5);
			PriceEach.Load(S);  BasePrice.SettoPence(0);
			VAT.Load(S);
			S.Read(NomCat, 2);
			S.Read(Com, sizeof(Com)); Comment^.Append(Com);
			if S2Real(Quantity) = 0 then begin
				BasePRice.SetToPence(PriceEach.inPence);
				PriceEach.SetToPence(0);
			end;
			CalculateTotals; {calculate old versions only - new ones have totals stored}
		end;
		2 : begin
			{Base price added}
			Date.SetToDate(OldVerDate);
			ReadSCode(S, @Code);
			ReadSCLine(S, @CodeLine, 12);
			BasePrice.Load(S);
			Quantity := S.ReadFixedStr(5);
			PriceEach.Load(S);
			S.Read(Com, sizeof(Com)); Comment^.Append(Com);
		end;
		3 : begin
			{Comment made into note form}
			Date.Load(S);
			ReadSCode(S, @Code);
			ReadSCLine(S, @CodeLine, 12);
			BasePrice.Load(S);
			Quantity := S.ReadFixedStr(5);
			PriceEach.Load(S);
			Comment^.Load(S);
		end;
		4 : begin
			{Quantity extended to 10 to cover large units}
			Date.Load(S);
			ReadSCode(S, @Code);
			ReadSCLine(S, @CodeLine, 12);
			BasePrice.Load(S);
			Quantity := S.ReadFixedStr(10);
			PriceEach.Load(S);
			Comment^.Load(S);
		end;
	else
		DBaseError(@S,'Version '+L2Str(Ver)+' not known','Retrieving Invoice Item')
	end;

	Quantity := DelSpaceR(Quantity); {just for display/etc purposes}
end;

procedure TCodedInvNode.Store;
var
	StartPos : longint;
	ver : byte;

begin
	StartPos := S.GetPos;
	Ver := 4; S.Write(Ver, 1);

	inherited Store(S);

	Date.Store(S);
	WriteSCode(S, @Code);
	WriteSCLine(S, @CodeLine, 12);
	BasePrice.Store(S);
	S.WriteFixedStr(@Quantity, 10);
	PriceEach.Store(S);
	Comment^.Store(S);

  {Before 8/11/94, CheckRecOver.... could be problems with overwriting
	except that most fields are pretty much fixed.  Note that TopUpRecord
	only operates if this is the last one on the stream (ie a new one) so
	it won't overwrite any other jimmys}
	TopUpRecord(S, TCodedInvNodeSize, StartPos);{}
end;


{***************************
 *** PRINT               ***
 ***************************}
procedure TCodedInvNode.SetFormCodes;
var SE, SA, S : string;

begin
	inherited SetFormCodes(Output, PreviousItem);

	if not Comment^.Loaded then Comment^.LoadText;

	Output^.SetCode('DT',Date.Digit10);
	{Date, if changed from previous one}
	if (PreviousItem<>nil) and (typeof(PreviousItem^)=typeof(TCodedInvNode)) then
		if (PCOdedInvNode(PreviousItem)^.Date.Days = Date.Days) then
			Output^.SetCode('CDT','')
		else
			Output^.SetCode('CDT',Date.Digit10)
	else
		Output^.SetCode('CDT',Date.Digit10);

	SA := ExpandSCode(scActions, CodeLine);
	SE := ExpandSCode(scEvents, Code);

	Output^.SetCode('EVENT',SE);
	Output^.SetCode('BPR',BasePrice.PoundsPenceStr);
	Output^.SetCode('QTY',DelSpace(Quantity));
	Output^.SetCode('PREA',PriceEach.PoundsPenceStr);
	Output^.SetCode('ACTION',SA);
	Output^.SetCode('COMMENT',Comment^.ExtractAll);

	{Changed code - if code not changed then blank}
{	if (PreviousItem<>nil) and (PreviousItem^.Code = Code) then SE := '';

	{Set up general <TEXT> codes}
	S := '';
	if SE <> '' then S := S + SE + #13#10;
	if SA <> '' then S := S + SA+#13#10; {Break every 40 chars}
	if Comment^.ExtractAll <> '' then S := S + Comment^.ExtractAll + #13#10;

	S := Copy(S,1,length(S)-2); {Delete last #13#10}

	Output^.SetCode('TEXT', S);  {Use <TEXT1> etc to extract lines}
end;


function TCodedInvNode.Print;
var S : string;
		L : longint;
		PrevItem, WorkItem : PInvoiceNode;
		PrintControl : word;
		FormFile : text;

begin
	Print := cmCancel; {didn't print}

	Output^.CheckForNewPage(+1); {let form do this with <PBT> if any more than 1 line}

	{Print invoice item}
	SetFormCodes(Output, PInvoiceNode(PreviousItem));

	case prType and pmPrintAs of
		prEstimate : Print := Output^.PrintForm('CODEDEI.FRM');
		prWorksheet :	Print := Output^.PrintForm('CODEDWS.FRM');
		prPurchaseOrder :	Print := Output^.PrintForm('CODEDPO.FRM');
	else
		Print := Output^.PrintForm('CODEDII.FRM');
	end;

	if not Output^.FormFound then Print := Output^.PrintForm('CODEDII.FRM');
end;

{*************************************************************************
 ***                                                                   ***
 ***                   FREE TEXT ITEM                                  ***
 ***                                                                   ***
 *************************************************************************}
constructor TFreeTextInvNode.Init;
begin
	inherited Init;
	New(FreeText, init);
end;

destructor TFreeTextInvNode.Done;
begin
	dispose(FreeText, done);
	inherited Done;
end;

{******************************
*** DISPLAY LINE           ***
******************************}

function TFreeTextInvNode.DisplayLine;
var S :String;
begin
	if not FreeText^.Loaded then FreeText^.LoadText;

	if Maxlen = 0 then Maxlen := 30;

	S := FreeText^.Extract(1, Maxlen);
	while pos(CRLF,S)>0 do S := copy(S,1,pos(CRLF,S)-1)+'/'+copy(S,pos(CRLF,S)+2,length(S)); {Remove funny chars}

  if TekkyMode then
  	S := S + #13+'TEK: Rec '+L2Str(RecNo)+' N'+L2Str(Disk.Next)+' P'+L2Str(Disk.Prev)+' C'+L2Str(Disk.FirstChild);


	DisplayLine := S;
end;

function TFreeTextInvNode.GetKey;
begin
	GetKey := 1;	{paymentnode forces to start with 0}
end;

{*******************************************
 ***            EDIT                     ***
 *******************************************}
procedure TFreeTextInvNode.MakeEditBox;
var Bounds, R : TRect;
    Indicator : PIndicator;

begin
	if not FreeText^.Loaded then FreeText^.LoadText;

	Bounds.Assign(0, 0, 45,11);

	EditBox := New(PEditBox, init(Bounds, 'FREE TEXT ITEM',Caller));
	EditBox^.HelpCtx := hcEditBox;

	{----Position box, in centre of calling view----}
	CentreinView(Caller, EditBox^);

	with EditBox^ do begin
		Insert(New(PSkipBytes, init(sizeof(TInvoiceNode))));  {Skip detail fields & VMT}

		R.Assign(2,Size.Y-1,13,Size.Y); New(Indicator, init(R));  {Row, Col, modified indicator}
		Insert(Indicator);{}

		R.Assign(1, 1, 44, 8);
		Insert(New(PInputNote, Init(R, 200, 0, Indicator, EditBox)));

		{--Buttons--}
		{if it's a priced view - eg sales order, invoice, estimate - ie caller
		is TInvoiceNodeViewer - then put in total button, o/w use ordinary OK
		button}
		if typeof(Caller^)=typeof(TInvoiceNodeViewer) then
			Insert(New(PinvTotalButton, init(22, 8, @Self)))
		else
			InsOKButton(22, 8, @Self);
		InsCancelButton(32, 8);

		EndInit;
	end;
end;


function  TFreeTextInvNode.Edit;
var EditBox : PEditBox;
		Control : word;
begin
	if typeof(Caller^)=typeof(TinvTotalButton) then
		Edit := inherited Edit(Caller, nil)  {Edit previous level - BEWARE LOOPING!}
	else begin
		MakeEditBox(EditBox, Caller);

		EditBox^.SetData(Self);

		Edit := Desktop^.ExecView(EditBox);  {return Ok/Cancel/etc}

		dispose(EditBox, done);
  end;
end;


{************************
 *** LOAD            ****
 ************************}
constructor TFreeTextInvNode.Load;
var Ver : byte;
begin
	S.Read(Ver, 1);

	inherited Load(S);

	New(FreeText, init);

	case Ver of
		1 : begin
			FreeText^.Load(S);
		end;
	else
		DBaseError(@S,'Version '+L2Str(Ver)+' not known','Retrieving Invoice Item')
	end;
end;

procedure TFreeTextInvNode.Store;
var
	StartPos : longint;
	ver : byte;

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

	inherited Store(S);

	FreeText^.Store(S);

  {Before 8/11/94, CheckRecOver.... could be problems with overwriting
	except that most fields are pretty much fixed.  Note that TopUpRecord
	only operates if this is the last one on the stream (ie a new one) so
	it won't overwrite any other jimmys}
	TopUpRecord(S, TFreeTextInvNodeSize, StartPos);{}

end;


{***************************
 *** PRINT               ***
 ***************************}
procedure TFreeTextInvNode.SetFormCodes;
begin
	inherited SetFormCodes(Output, PreviousItem);

	Output^.SetCode('TEXT', FreeText^.ExtractAll);
end;


function TFreeTextInvNode.Print;
var S : string;
		L : longint;
		PrevItem, WorkItem : PInvoiceNode;
		PrintControl : word;
		FormFile : text;

begin
	if not FreeText^.Loaded then FreeText^.LoadText;

	Print := cmCancel; {didn't print}

	Output^.CheckForNewPage(+1); {let form do this with <PBT> if any more than 1 line}

	{Print invoice item}
	SetFormCodes(Output, PInvoiceNode(PreviousItem));

	case prType and pmPrintAs of
		prEstimate : Print := Output^.PrintForm('FTEXTEI.FRM');
		prWorkSheet : Print := Output^.PrintForm('FTEXTWS.FRM');
		prPurchaseOrder : Print := Output^.PrintForm('FTEXTPO.FRM');
	else
		Print := Output^.PrintForm('FTEXTII.FRM');
	end;

	if not Output^.FormFound then Print := Output^.PrintForm('FTEXTII.FRM');

end;

{$IFDEF kproduct}
{*************************************************************************
 ***                                                                   ***
 ***               PRODUCT INVOICE ITEM                                ***
 ***                                                                   ***
 *************************************************************************}
function CreateProductInvNode(P : pointer) : pointer;
begin	CreateProductInvNode := New(PProductInvNode, init); end;


constructor TProductInvNode.Init;
begin
	inherited Init;
	Quantity := '';
	PriceEach.Init;
  ProductID := -1;
end;

procedure TProductInvNode.CommonInit;
begin
	inherited CommonInit;
	ScodeAdmin[scProductCategory]^.LogOn;
	ScodeAdmin[scProductUnits]^.LogOn;
	ScodeAdmin[scLocations]^.LogOn;
end;

destructor TProductInvNode.Done;
begin
	ScodeAdmin[scProductCategory]^.LogOff;
	ScodeAdmin[scProductUnits]^.LogOff;
	ScodeAdmin[scLocations]^.LogOff;
	inherited Done;
end;

procedure TProductInvNode.CalculateTotals;
var Q : real;   {forces inlongintrange parameter to be calced in real}
begin
	Q := S2Real(Quantity); if Q=0 then Q:=1;

	if inLongintRange(Q * PriceEach.inPence) then begin
		{Check in range}
		Total.SetToPence(round(Q * PriceEach.inPence));
	end else begin
		Total.SetToPence(0);     {Overload warning}
	end;

	inherited CalculateTotals;
end;

{******************************
*** DISPLAY LINE           ***
******************************}

function TProductInvNode.DisplayLine;
var S : string;
    Product : PProduct;

begin
	if Maxlen = 0 then Maxlen := 70;

	if ProductID<>-1 then begin
  	FileAdmin(fiJimmys)^.Logon;
  	Product := PProduct(JimmyStream^.GetAt(ProductID));
  	JimmyStream^.CheckStatus('Getting for ProductInvNode.DisplayLine');
  	FileAdmin(fiJimmys)^.LogOff;
	end else
		Product := nil;

	S := '';
  if Product <> nil then begin
		S := S + ExpandSCode(scProductCategory, Product^.Categories)+' '+Product^.Desc;
		dispose(product, done);
	end;

	if delspace(Quantity) <> '' then begin
		if pos('x', Quantity)=0 then S := S+' x' else S := S +' '; {Stromsholm enter it themselves...}
		S := S+Quantity;
  end;

	if delspace(Units)<>'' then S:=S+' '+ExpandUnitSCode(Units, S2Lint(Quantity)>1); {adds plural/singular}

  if TekkyMode then
  	S := S 	+ #13+'TEK: Rec '+L2Str(RecNo)+' N'+L2Str(Disk.Next)+' P'+L2Str(Disk.Prev)+' C'+L2Str(Disk.FirstChild)
						+' Prod'+L2Str(ProductID);

	DisplayLine := S;
end;


function TProductInvNode.GetKey;
begin
	GetKey := 1;	{paymentnode forces to start with 0}
end;

{*******************************************
 ***            EDIT                     ***
 *******************************************}
type
	PInputUnits = ^TInputUnits;
  TInputUnits = object(TinputSCode)
    Restrictor : string[99];
  	constructor Init(Bounds : TREct);
		procedure HandleEvent(var Event : TEvent); virtual;
    function Valid(Command : word) : boolean; virtual;
	end;

constructor TInputUnits.Init;
begin
	inherited Init(Bounds, scProductUnits);
  Restrictor := '';
end;

procedure TInputUnits.HandleEvent;
var Product : PProduct;
		U : byte;
begin
	if (Event.what = evcommand) and (Event.Command = cmUpdateFromLink) then begin
		if (PInputProduct(Event.InfoPtr)^.ID <> -1) then begin
    	{get product from source line}
			FileAdmin(fiJimmys)^.LogOn;
			Product := PProduct(JimmyStream^.GetAt(PInputProduct(Event.InfoPtr)^.ID));
  		FileAdmin(fiJimmys)^.LogOff;
      Restrictor := '';
      for U := 0 to 3 do Restrictor := Restrictor + padspaceR(Product^.Units[U],4);
      if (length(delspace(Restrictor))<4) and (delspace(Restrictor)<>'') then
																				Data^ := delspace(restrictor); {only one, make default}
			if (delspace(Restrictor)<>'') and (pos(Data^, Restrictor)=0) then Data^ := ''; {no longer valid}
    	dispose(Product, done);
    end;
	end;

	inherited HandleEvent(Event);
end;

function TInputUnits.Valid;
var V : boolean;
begin
	V := inherited Valid(Command);
{  if V then begin
  	if (delspace(Restrictor)<>'') and (delspace(Data^)<>'') and (pos(Data^, restrictor)=0) then begin
    	InputWarning('Units not valid for product','Use: '+delspace(Restrictor));
      V := False;
    end;
  end;{Don't do this check like this as we may want to override...}
  Valid := V;
end;


type
	PInputProductPrice = ^TInputProductPrice;
	TInputProductPrice = object(TInputMoney)
    DirectoryID : longint;
		UnitsLine : PInputScode;
		ProductLine : PInputProduct;
		constructor Init(Bounds : TRect; NMaxlen : byte; NDirectoryID :longint;
																					NUnitsLine : PInputScode; NProductLine : PInputProduct);
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

constructor TINputProductPrice.Init;
begin
	inherited init(Bounds, NMaxlen);
	DirectoryID := NDirectoryID;
	UnitsLine := NUnitsLine;
	ProductLine := NProductLine;
end;

	procedure TInputProductPrice.HandleEvent;
	var	Product : PProduct;
{$IFDEF kdirctry}
			DirectoryItem : PDirectoryItem;
{$ENDIF}
			B,Units,U : byte;
			Price : TMoney;

	begin
{$IFDEF kdirctry}
		if (Event.what = evcommand) and (Event.Command = cmUpdateFromLink) then begin
			if (ProductLine^.ID <> -1) and (DirectoryID<>-1) then begin
				{get product from source line}
				FileAdmin(fiJimmys)^.LogOn;
				Product := PProduct(JimmyStream^.GetAt(ProductLine^.ID));
				DirectoryItem := PDirectoryItem(JimmyStream^.GEtAT(DirectoryID));
				FileAdmin(fiJimmys)^.LogOff;

				if (Product<>nil) and (DirectoryItem<>nil) then begin
					Price.Clear;
					{locate units - if given}
					{work backwards so that if blank units, it eventually returns the price
					from first column, not the last}
					Units := 0;
					for U := 3 downto 0 do
						if (delspace(Product^.Units[U])=delspace(UnitsLine^.Data^)) then
							Units := U;

					{if directory item is also product supplier, *assume* it's a sales
					order and pick up cost price (hardly likely to sell something to
					a supplier that they sell to us!}
					if DirectoryID = Product^.Supplier then
						Price.SetTo(Product^.QuotedCost[Units].inPounds)
					else begin
						{customer}
						{got the right units, now work out which price band}
						for B := 1 to 3 do begin
							if ((Pos(' '+delspace(Product^.PriceBand[B].Band)+' ', ' '+DirectoryItem^.GetSearch+' ')>0) and
									(DirectoryItem^.GetSearch<>'')) or
									((Price.inPence=0) and (delspace(Product^.PriceBand[B].Band) = '')) then
										Price.SetTo(Product^.PriceBand[B].Price[Units].inPounds);
						end;
					end;

					if not Price.Blank then SetData(Price);
					dispose(Product, done);
					dispose(DirectoryItem, done);
				end;
			end;
		end;
{$ENDIF}

		inherited HandleEvent(Event);
	end;

procedure TProductInvNode.MakeEditBox;
var Bounds, R : TRect;
		PriceLine, ProductLine, UnitLine : PVIew;
    DirectoryID : longint;

begin
	DirectoryID := PDirNodeViewer(Caller)^.DirectoryID;

	Bounds.Assign(0, 0, 45,8);

	{This was an invoiceeditbox, can't remember why...}
	EditBox := New(PEditBox, init(Bounds, 'PRODUCT ITEM',Caller));
	EditBox^.HelpCtx := hcEditBox;

	{----Position box, in centre of calling view----}
  CentreinView(Caller, EditBox^);

	with EditBox^ do begin
    if TekkyMode then begin
			Insert(New(PSkipBytes, init(sizeof(TObject))));  {Skip detail fields & VMT}

			Insert(New(PSkipBytes, init(4)));  {RecNo}

    	InsTitledField(5,1,6, 1, 'Nxt', New(PInputLint, init(R, 10)));
    	InsTitledField(19,1,6, 1, 'Prv', New(PInputLint, init(R, 10)));
    	InsTitledField(33,1,6, 1, 'Chld', New(PInputLint, init(R, 10)));

      Insert(New(PSkipBytes, init(Sizeof(Heap)+1))); {skip heap next/prev/etc and expanded marker}

			Insert(New(PSkipBytes, init(sizeof(TInvoiceNode)-sizeof(TNodeItem))));  {Skip detail fields & VMT}

    end else
			Insert(New(PSkipBytes, init(sizeof(TInvoiceNode))));  {Skip detail fields & VMT}

		InsTitledField(10,  2, 29, 1, 'Product', New(PInputProduct, Init(R,29)));
		ProductLine := Current;
		PInputELine(Current)^.MustInput := True;
		PInputProduct(Current)^.ListOptions := PInputProduct(Current)^.ListOptions or ipAutoList;

		InsTitledField(10,  3, 10, 1, 'x Qty', New(PInputELine, Init(R, 10)));
		InsTitledField(10,  4, 20, 1, 'Units', New(PInputUnits, init(R)));
		UnitLine := Current;
		InsTitledField(10,  5,  6, 1, '@',     New(PInputProductPrice, Init(R, 9, DirectoryID,
																																			PInputSCode(UnitLine), PInputProduct(ProductLine))));
		PriceLine := Current;

		{--Buttons--}
		{if it's a priced view - eg sales order, invoice, estimate - ie caller
		is TInvoiceNodeViewer - then put in total button, o/w use ordinary OK
		button}
		if typeof(Caller^)=typeof(TInvoiceNodeViewer) then
			Insert(New(PinvTotalButton, init(22, 5, @Self)))
		else
			InsOKButton(22, 5, @Self);
		InsCancelButton(32, 5);

  	EndInit;
	end;

	{Make links}
	PInputELine(ProductLine)^.SetTargetLink(UnitLine); {unit line will then knock on to price line}
	PInputELine(UnitLine)^.SetTargetLink(PriceLine);

	EditBox^.SetData(Self);   {Sets data's so that following lines will work.  Will be set again by edit method}
{	PInputELine(ProductLine)^.SetOldData; {So it doesn't cause an immediate change}
{	PInputELine(UnitLine)^.SetOldData; {So it doesn't cause an immediate change}

end;


function  TProductInvNode.Edit;
var EditBox : PEditBox;
	 	Control : word;
begin
	if typeof(Caller^)=typeof(TinvTotalButton) then
		Edit := inherited Edit(Caller,nil)  {Edit previous level - BEWARE LOOPING!}
	else begin
		MakeEditBox(EditBox, Caller);

		EditBox^.SetData(Self);

		Edit := Desktop^.ExecView(EditBox);  {return Ok/Cancel/etc}

		dispose(EditBox, done);
  end;
end;


{************************
 *** LOAD            ****
 ************************}
constructor TProductInvNode.Load;
var Ver : byte;
begin
	S.Read(Ver, 1);
  Units := '';

	inherited Load(S);

	case Ver of
		1 : begin
			S.Read(ProductID,4);
			Quantity := S.ReadFixedStr(5);
			PriceEach.Load(S);
		end;
		2 : begin
			{Quantity extended to 10}
			S.Read(ProductID,4);
			Quantity := S.ReadFixedStr(10);
			PriceEach.Load(S);
		end;
		3 : begin
			{Units added}
			S.Read(ProductID,4);
			Quantity := S.ReadFixedStr(10);
			Units := S.ReadFixedStr(3);
			PriceEach.Load(S);
		end;
	else
		DBaseError(@S,'Version '+L2Str(Ver)+' not known','Retrieving Product Invoice Item')
	end;

	Quantity := delspaceR(Quantity);
end;

procedure TProductInvNode.Store;
var
	StartPos : longint;
	ver : byte;

begin
	StartPos := S.GetPos;
	Ver := 3; S.Write(Ver, 1);

	inherited Store(S);

	S.Write(ProductID, 4);
	S.WriteFixedStr(@Quantity, 10);
	S.WriteFixedStr(@Units, 3);
	PriceEach.Store(S);

  {Before 8/11/94, CheckRecOver.... could be problems with overwriting
	except that most fields are pretty much fixed.  Note that TopUpRecord
	only operates if this is the last one on the stream (ie a new one) so
	it won't overwrite any other jimmys}
	TopUpRecord(S, TProductInvNodeSize, StartPos);{}
end;


{***************************
 *** PRINT               ***
 ***************************}
{Debug}
procedure TProductInvNode.SetFormCodes;
var Product : PProduct;
		SCode : PSCodeItem;
		Q,U,Text : string;

begin
	inherited SetFormCodes(Output, PInvoiceNode(PreviousItem));

	Output^.SetCode('QTY',DelSpace(Quantity));

	Q := delspace(Quantity); if Q<>'' then Q := Q + ' ';
	U := ''; if delspace(Units)<>'' then U := ExpandUnitSCode(Units, S2Lint(Quantity)>1); {see kproduct}
	Output^.SetCode('QTYUNIT', Q+U);

	Output^.SetCode('PREA',PriceEach.PoundsPenceStr);

	if ProductID>-1 then begin
		FileAdmin(fiJimmys)^.Logon;
		Product := PProduct(JimmyStream^.GetAt(ProductID));
		FileAdmin(fiJimmys)^.Logoff;
	end else
		Product := nil;

	if Product <> nil then begin
		{Use <TEXT1> etc to extract lines}
		Text := ExpandSCode(scProductCategory, Product^.Categories)+CRLF+Product^.Desc;
		Product^.SetFormCodes(Output,'');{}
		dispose(Product, done);
	end else
		Text := '';

	Output^.SetCode('TEXT', Text);

	if U<>'' then U := U +' ';
	Output^.SetCode('UTEXT',U+Text); {make units part of text}
end;


function TProductInvNode.Print;
begin
	Print := cmCancel; {didn't print}

	Output^.CheckForNewPage(+1); {let form do this with <PBT> if any more than 1 line}

	{Print invoice item}
	SetFormCodes(Output, PInvoiceNode(PreviousItem));

	case (prType and pmPrintAs) of
		prEstimate 			: Print := Output^.PrintForm('PRODCTEI.FRM');
		prWorkSheet			: Print := Output^.PrintForm('PRODCTWS.FRM');
		prPurchaseOrder : Print := Output^.PrintForm('PRODCTPO.FRM'); {can put in part numbers}
	else
		Print := Output^.PrintForm('PRODCTII.FRM');
	end;

	{above forms not available, use invoice item auto-created in defforms}
	if not Output^.FormFound then Print := Output^.PrintForm('PRODCTII.FRM');
end;

{$ENDIF} {end of IF kproduct defined}


{$IFDEF khrdware}
{**************************************************************
 ***                                                        ***
 ***          DEFINE HW OWNED JOB ITEM                      ***
 ***                                                        ***
 **************************************************************}

function CreateHWInvNode(P : pointer) : pointer;
begin	CreateHWInvNode := New(PHWInvNode, init); end;

constructor THWInvNode.Init;
begin
	inherited Init;
  HardwareID := -1;
end;

{******************************
*** DISPLAY LINE           ***
******************************}

function THWInvNode.DisplayLine;
var S : string;
    Hardware : PHardware;

begin
	FileAdmin(fiJimmys)^.Logon;
  if HardwareID<>-1 then Hardware := PHardware(JImmyStream^.GetAt(HardwareID)) else Hardware := nil;
	FileAdmin(fiJimmys)^.Logoff;

	if Hardware<>nil then begin
		DisplayLine := Hardware^.DisplayLine(Maxlen,0);
		dispose(Hardware, done);
	end else begin
		DisplayLine := 'Could not get Hardware Jimmy #'+L2Str(HardwareID)+' ('+JimmyStream^.StatusNums+')';
		JimmyStream^.REset;
	end;
end;


function THWInvNode.GetKey;
begin
	GetKey := 1;	{paymentnode forces to start with 0}
end;

{*******************************************
 ***            EDIT                     ***
 *******************************************}
{unused if unpriced - goes straight to list}
procedure THWInvNode.MakeEditBox;
var Bounds, R : TRect;

begin
	Bounds.Assign(0, 0, 45,7);

	EditBox := New(PEditBox, init(Bounds, 'HARDWARE OWNED',Caller));

	{----Position box, in centre of calling view----}
	CentreinView(Caller, EditBox^);

	with EditBox^ do begin
		Insert(New(PSkipBytes, init(sizeof(TInvoiceNode))));  {Skip detail fields & VMT}
		InsTitledField(10,  2, 29, 1, 'Hardware',
													New(PInputHardware, Init(R,29,PDIrNodeViewer(Caller)^.DirectoryID)));{}
		PInputELine(Current)^.MustInput := True;

		{--Buttons--}
		Insert(new(PInvTotalButton, init(20, 4, @Self))); {use ordinary OK button}
		InsCancelButton(31, 4);

		EndInit;
	end;

end;{}


function  THWInvNode.Edit;
var	R : TRect;
		HardwareView : PJimmyHookWindow;
		DirectoryID : longint;
		InputHardWare : PInputHardWare;
		Jimmy : PJimmy;
		EditBox : PEditBox;

begin
	if typeof(Caller^)=typeof(TinvTotalButton) then
		{total button pressed, so call inherited Edit which will do totalling}
		Edit := inherited Edit(Caller, nil)  {Edit previous level - BEWARE LOOPING!}
	else
		if typeof(Caller^)=typeof(TInvoiceNodeViewer) then begin
			{Ordinary edit from invoice (ie requires pricing) list}
			MakeEditBox(EditBox, Caller);

			EditBox^.SetData(Self);

			Edit := Desktop^.ExecView(EditBox);  {return Ok/Cancel/etc}

			dispose(editbox, done);
		end else begin
			{Ordinary edit from unpricing list, eg packing slip}

			Jimmy := PJimmy(JimmyStream^.GetAt(PDirNodeViewer(Caller)^.DirectoryID));
			HardwareID := -1;

			{Don't use an edit box - just go straight to list}
			R.Assign(0,0,50,15);
			New(HardwareView, Init(R, 'HARDWARE LIST', Caller, srHardware, hkMore, Jimmy, nil,nil));   {Restrict to just Hardwares}

			if HardwareView <> nil then begin	{ie Directory not locked, etc}
				{Centre on caller}
				CascadeInView(Caller, HardwareView^);

				{Set acceptor link to inputhardware - it's not displayed, but it will provide the "accept" methods}
				New(InputHardware, init(R, 10, -1));
				InputHardware^.SetState(sfSelected, True); {to fool jimmy acceptor part of inherited into
				thinking this is the currently selected view}
				HardwareView^.List^.AcceptorLink := InputHardware;

				{Make modal and input}
				Desktop^.ExecView(HardwareView);
				Dispose(HardwareView, Done);

				{retrieve accepted number, if there is one}
				InputHardware^.GetData(HardwareID);
				dispose(InputHardware, done);
			end;

			if Jimmy<>nil then dispose(Jimmy, done);

			if HardwareID = -1 then Edit := cmCancel else Edit := cmOK;
		end;
end;


{************************
 *** LOAD            ****
 ************************}
constructor THWInvNode.Load;
var Ver : byte;
begin
	S.Read(Ver, 1);

	inherited Load(S);

	case Ver of
		1 : begin
			S.Read(HardwareID,4);
		end;
	else
		DBaseError(@S,'Version '+L2Str(Ver)+' not known','Retrieving Product Invoice Item')
	end;
end;

procedure THWInvNode.Store;
var
	StartPos : longint;
	ver : byte;

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

	inherited Store(S);
	S.Write(HardwareID, 4);

	CheckRecOver(S, THWInvNodeSize, StartPos);
end;


{***************************
 *** PRINT               ***
 ***************************}
function THWInvNode.Print;
var	Hardware : PHardware;

begin
	Print := cmCancel; {didn't print}

	FileAdmin(fiJimmys)^.LogOn;
	Hardware := PHardware(JimmyStream^.GetAt(HardwareID));
	FileAdmin(fiJimmys)^.LogOff;

	if Hardware<>nil then begin
		Hardware^.SetFormCodes(Output, '');
		SetFormCodes(Output, PInvoiceNode(PreviousItem)); {set totals, etc}

		if (prType and pmPrintas)=prWorkSheet then begin
			Print := Hardware^.Print(prWorkSheet + prFullBlck, Output); {prtype of full/deferred/etc}
			Output^.writeln(''); {set a gap between each one}
			Dispose(Hardware, done);
			exit;
		end;

		dispose(Hardware, done);

		if (prType and prEstimate)>0 then begin
			Print := Output^.PrintForm('HWAREEI.FRM');
			if Output^.FormFound then exit; {OK}
		end;

		Print := Output^.PrintForm('HWAREII.FRM');

	end;
end;

{$ENDIF} {if khrdware defined}






{*************************************************
 ***  SET CODES (FOR PRINT)                    ***
 *************************************************}
{invoice items as a function.  Code ITEMS with parameter /INV or /EST for
type/fullness of item print, or /JOB for printing as a job sheet}

{Needs to be able to print from unsaved items, so we set up a global
var to store the tree information, including whether loaded or not and
pointers to first.  This way we can also print invoice summaries without
having to load the full tree every time we do a setformcodes}
{var
	{this is very naughty, and I count on the fact that only one invoice/etc
	will be printing at a time.  Rather than try and pass a pointer through
	the coded items, set this "global" variable, and use *it* to print tree
	from.  Allows us to print from memory, ie allowing to print unsaved
	trees}
{	PrintInvoiceItemTree : TNodeTree;{}

{function SetInvoiceItemsCode(ItemTree : PNodeTree; Output : POutputStream) : string;
begin
{	if not ItemTree.Loaded then ItemTree.LoadTree; {do at print time}
	{copy itemtree to above}
{	PrintInvoiceItemTree.Init(ItemTree.fiType);
	PrintInvoiceItemTree.RootNodeID := ItemTree.RootNodeID;
	if ItemTree.Loaded then begin
		PrintInvoiceItemTree.RootNode := ItemTree.RootNode;
		PrintInvoiceItemTree.DummyRoot := ItemTRee.DummyRoot;
	end else
		PrintInvoiceItemTree.Loaded := False;

{  Output^.SetCode('ITEMSID', L2Str(ItemTree.RootNodeID)); {just so that the next two funcs have access to it}
{	Output^.SetCodedFunc('ITEMS',PrintInvoiceTree, ItemTree);{}
{end;{}

function PrintInvoiceTree(Code,Param : string; Output,Info : pointer) : String;
var RootItem : PInvoiceNode;
		RootNodeID : longint;
		prType : word;

begin
{	RootNodeID := S2Lint(POutputStream(Output)^.UserCodes.decode('ITEMSID',''));

	{--- Load items ----}
{	RootItem := PInvoiceNode(PNodeStream(FileAdmin(fiInvoiceNodes]^.FilePtr)^.LoadTree(RootNodeID, nil));
{use naughty global variable PrintInvoiceItemTree instead, so we can print
from unsaved items}
	if not PNodeTree(Info)^.Loaded then PNodeTree(Info)^.LoadTree;

	RootItem := PInvoiceNode(PNodeTree(Info)^.RootNode);

	{--- Set Codes ----}
	Poutputstream(Output)^.SetCode('RTOT',''); {set running total}
	Poutputstream(Output)^.SetCode('RDTOT',''); {set running discounted total}
	Poutputstream(Output)^.SetCode('TOT',''); {set running total}
	Poutputstream(Output)^.SetCode('DTOT',''); {set running discounted total}
	{Stop header prints from starting this again, say when going over page breaks}
	Poutputstream(Output)^.SetCodedFunc('ITEMS',BlankCodedFunc, nil);

	{--- Print type ----}
	prType := prInvoice;
	if pos('/EST',Param)>0 then prType := prEstimate;
	if pos('/JOB',Param)>0 then prType := prWorkSheet;
	if pos('/PO', Param)>0 then prType := prPurchaseOrder;

	{--- Print ---}
	PrintTree(prType, Output, RootItem);

	{--- Tidy up ----}
	PrintInvoiceTree := 'SKIPLINE';
{	dispose(RootItem, done);{}
end;



begin
	RegisterCreator(cmCodedInvNode, CreateCodedInvNode);
	RegisterType(RCodedInvNode);

	New(SCodeAdmin[scEvents], Init('kEvents.SC',   'Events', CreateCostedSCodeItem));
	New(SCodeAdmin[scActions],Init('kAction.SC', 'Action Codes', CreateCostedSCodeItem));{}

	RegisterCreator(cmNewFreeTextInvNode, CreateFreeTextInvNode);
	RegisterType(RFreeTextInvNode);
	{$IFDEF kproduct}
		RegisterCreator(cmNewProductInvNode, CreateProductInvNode);
		RegisterType(RProductInvNode);
	{$ENDIF}
	{$IFDEF khrdware}
		RegisterCreator(cmNewHWInvNode, CreateHWInvNode);
		RegisterType(RHWInvNode);{}
	{$ENDIF}

	{Invoice Item file}
	NewFileAdmin(fiInvoiceNodes, 'Invoice Node Stream',NewInvoiceNodeStream);

end.
