{****************************************************************************
 ***                                                                      ***
 *** New Fabbo Singing Dancing OOP                                        ***
 ***                       ORDER PROCESSING                               ***
 ***                                                                      ***
 *** M Hill                                                      Apr 1996 ***
 ****************************************************************************}
{$I compdirs}  {Compiler directives}
{Provides a root order type, to be descendend to purchase orders, sales orders,
estimates, invoices, delivery notes and packing slips}

unit OrdProc;

INTERFACE

uses
		{$IFDEF Windows}
		winedit,
		{$ELSE}
		tuiedit,
		{$ENDIF}
		lstrings,
		files,
		drivers,
		scodes,
		jimprint,
		linklist,
		forms, devices,
		jimhooks,
		views,
		dattime,
		objects,
		notes,
		multcurr,
		global,
		jimmys;

const
	{--- order status ----}
	osSent = $01; {ie printed}
	osEdited = $02; {needing sent}

	{---- order item status -----}
	isOrdered = $01; {sales orders}
	isDelivered = $02;
	isInvoiced = $04;
	isPaid = $08;

	isOnOrder = $10; {for purchase orders}
	isReceived = $20;

	{hooking types}
	hkOrderItems = 1;
	hkPayments = 2;

	{-- display flag markers ---}
	flDisc = #25; 		{dn arrow for showing discount}
	flPastDisc = #23; {up/dn arrow to show expired discount}


type
	TDiscount = record
		Rate 		: single;
		ByRate 	: boolean; {marks whether doing by rate or amount}
		Amount 	: TMoney;
	end;

	{for items}
	PPriceGroup = ^TPriceGroup;
	TPriceGroup = object(TObject)
		Price 				: TMoney;

		Discount 			: TDiscount;
		CashDiscount 	: TDiscount;

		SubTotal 			: TMoney; {always calculated with discount}

		VATScode 	: TSCode;
		VATRate 	: single;
		VAT 			: TMOney;      {also always calculated with discount}

		Total 		: TMOney;		 {Total with cashdiscount}

		constructor Init;
		procedure CommonInit;
		constructor Load(var S : TDataStream);
		procedure Store(var S : TDataStream); {about 36 bytes?}
		procedure SetFormCodes(const FormCodes: PFormCodeCollection);
		procedure CalculateFromPrice; {set price then call this}
	end;

	{for inputting/displaying price, vat, total, etc}
	PInputPriceGroup = ^TInputPriceGroup;
	TinputPriceGroup = object(TInputGroup)
		{for attaching inputlinkers}
		EditBox : PEditBox;{}

		{fields maybe used by other input linkers wanting to add stuff}
		PriceLine : PInputMoney;
		VATSCodeLine : PInputSCode;

		constructor Init(X,Y : integer; NEditBox : PEditBox);
		procedure INsertEditFields; virtual;{}
	end;

	{for invoices/estimates/etc}
	PTotallerGroup = ^TTotallerGroup;
	TTotallerGroup = object(TObject)

		SubTotal  		: TMoney;		{total of "prices"}
		CashDisc			: TMoney;
		VAT						: TMoney;

		{calculated}
		DiscTotal			: TMoney;
		UnDiscTotal		: TMOney;
		Total					: TMoney; {depending on discountable or not}

		constructor Init;
		procedure CommonInit;
		constructor Load(var S : TDataStream);
		procedure Store(var S : TDataStream);
		procedure SetFormCodes(const FormCodes: PFormCodeCollection);
		procedure Calculate(const Discountable : boolean);
	end;

	{for inputting/displaying price, vat, total, etc}
	PInputTotallerGroup = ^TInputTotallerGroup;
	TinputTotallerGroup = object(TInputGroup)
		{for attaching inputlinkers}
		EditBox : PEditBox;{}

		SubTotalLine, DiscLine, VATLine, MarkerLine, TotalLine : PView;

		constructor Init(X,Y : integer; NEditBox : PEditBox);
		procedure INsertEditFields; virtual;{}
	end;


	{**********************************
	 ***     ORDER ITEMS            ***
	 **********************************}

	{==== ROOT ORDER ITEM ===================}
	{there'll be plenty of jimmys that will only appear in the order items -
	eg coded invoice items, rongai's delivery stuff, etc, so here's a root
	one for ease of writing}
	POrderItem = ^TOrderItem;
	TOrderItem = object(TJimmy)

		ForOrder 			: longint; {pointer to "Order" descendant that this is an item for}
		ParentItemID 	: longint; {pointer to parent jimmy item in item tree (if any)}
		From, Following : longint; {pointer to related entry in prev order and next "orders"}

		PriceGroup : TPriceGroup;
		NomCat : TSCode;

		constructor Init(Param : PJimmyInitParam);
		procedure COmmonInit; virtual;

		function DisplayLine(ListForWho : longint; lstype : byte; Maxlen : integer; View : word) : string; virtual;

		procedure SetFormCodes(const FormCodes: PFormCodeCollection); virtual;
		{separate print type as different forms might be reqd for different prints}
		procedure PrintItem(const Device : PDeviceStream; const Param : TFCodeStr; const FormType : string); virtual;
		function FormRoot : string; virtual; {for specifying CODED, FTEXT, etc for printing item}

		function PtrOffset : byte; virtual;{}

		procedure Storefields(var S : TDataStream); virtual; {56 bytes?}
		constructor Load(var S : TDataStream);

		{for fixit, etc}
		function NumIDs : byte; virtual; {the number of jimmy ID ptrs in the data}
		function GetJimmyID(const jiType : byte) : PLongint; virtual; {each jimmy ID}

		{-- 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;

{		procedure DoRepeater(const NewDate : TDate; const DeltaDays : integer); virtual;{}

	end;


	POrder = ^TOrder;
	TOrder = object(TJimmy)

		Ptr2Items : longint;

		{admin/display (not edit) fields}
		LastPrint : TDate;
		State			: byte;

		TotallerGroup : TTotallerGroup;

		Ref				: longint;
		OldRef		: longint; {for seeing if ref has changed}
		Date			: TDate;
		ForWho    : longint; {customer/supplier}
		ForWhoRef	: Pstring; {their reference}
		ByWho			: longint; {user/staff member responsible}

		Notes			: PFreeTextData;

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

		{--- Printing ----}
		procedure SetFormCodes(const FormCodes: PFormCodeCollection); virtual;

		{--- Database ----}
		procedure Storefields(var S : TDataStream); virtual;
		constructor Load(var S : TDataStream);

		{Other Jimmy ID ptrs - for fixit, etc}
		function NumIDs : byte; virtual; {the number of jimmy ID ptrs in the data}
		function GetJimmyID(const jiType : byte) : PLongint; virtual; {each jimmy ID}

		{--- 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;

		procedure DoRepeater(const NewDate : TDate; const DeltaDays : integer); virtual;

		{--- Database ----}
		procedure PreStoreing(const DiskJImmy : PJimmy); virtual;    {extra storing method, done by storeself}
		procedure OnPrinting(const PrintType : TJimmyPrintType); virtual;
		{for changing prices}
		function HookingOn(const hkType,htType : byte; const HookingJimmy : PJimmy) : boolean; virtual;
		function UnHooking(const hkType,htType : byte; const HookingJimmy : PJimmy) : boolean; virtual;

		procedure PrintLabel(const Device : PDeviceStream; LabelAs : word); virtual; {default to one for forwho}

		procedure Send; virtual; {printed/sent/whatever}
		procedure ReCalculate; virtual; {re-adds up all fields}

		function CashDiscountable : boolean; virtual; {only really applies to invoices?  estimates?}
		procedure CalculateTotals; virtual; {ie totallergroup, using cashdiscountable, also setdue for invoice}
	end;



	{=============== REPORTS/FORM CODES =========================}
{	POrderItemFormCode = ^TOrderItemFormCode;
	TOrderItemFormCode = object(TFormCode)
		OrderID : longint;
		Device : PDeviceStream;
		constructor Init(const NCode : string; const NOrderID : longint; NDevice : PDeviceStream);
		function Replace(const SubCode, Param : TFCodeStr; const FormCodes : PFormCodeCollection;
																													var LString : TLongString) : boolean; virtual;
	end;{}

type
	PStateField = ^TStateField;
	TStateField = object(TInputByte)
		JimmyID : longint;
		constructor Init(const NRecNo : longint);
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	{button to provide access to order from item, eg for rongai deliveries
	which appear in the diary}
	PItemToOrderButton = ^TItemToOrderButton;
	TItemToOrderButton = object(TOurButton)
		constructor Init(X,Y : integer; NTitle : string; OrderItem : POrderItem);
		procedure Press; virtual;
	end;

	{==== STANDARD ORDER ITEM - CODED ===============}
	PCodedOrderItem = ^TCodedOrderItem;
	TCodedOrderItem = object(TOrderItem)
		Date : TDate;
		Code : string[3];        {Event code}
		CodeLine : string[12];   {STring of action codes}
		Comment : PFreeTextData;

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

		constructor Init(Param : PJimmyInitParam);
		procedure COmmonInit; virtual;
		destructor Done; virtual;

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

		{--- Viewing -----}
		function DisplayLine(ListForWho : longint; lstype : byte; Maxlen : integer; View : word) : string; 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}

		procedure GetHookTo(const htType : byte; var HookToID,SubHookToID : PLongint;
												var hkType : byte; var Key : longint; var InsertBias : boolean); virtual;

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

		procedure SetFormCodes(const FormCodes: PFormCodeCollection); virtual;
		function FormRoot : string; virtual; {for specifying CODED, FTEXT, etc for printing item}
	end;

	{==== STANDARD ORDER ITEM - CODED ===============}
	PFreeTextOrderItem = ^TFreeTextOrderItem;
	TFreeTextOrderItem = object(TOrderItem)

		FreeText : PFreeTextData;

		constructor Init(Param : PJimmyInitParam);
		procedure COmmonInit; virtual;
		destructor Done; virtual;

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

		{--- Viewing -----}
		function DisplayLine(ListForWho : longint; lstype : byte; Maxlen : integer; View : word) : string; 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}


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

		procedure SetFormCodes(const FormCodes: PFormCodeCollection); virtual;
		function FormRoot : string; virtual; {for specifying CODED, FTEXT, etc for printing item}
	end;

type
	PRecalcButton = ^TRecalcButton;
	TRecalcButton = object(TOurButton)
		procedure Press; virtual;
	end;


IMPLEMENTATION

uses
			inpdnt,
			idindex,
			tasks,
			tuijimmy,
			vat,
			help,
			dosutils, {for testing forms' existence}
			{$IFDEF kdiary} kdiary, {$ENDIF} {for setting date with new order}
			{$IFDEF kusers} kusers,  {$ENDIF}
			{$IFDEF kbooks} kbooks, {$ENDIF}
			kdirctry,
			kamsetup,
			dialogs, tui, tuimsgs,
			minilib;


{********************************************
 *** FOR DEVELOPMENT BUG-FIX USE          ***
 ********************************************}
procedure TRecalcButton.Press;
begin
	DrawState(True);

	Owner^.GetData(PJimmyEditBox(Owner)^.Jimmy^);

	POrder(PJimmyEditBox(Owner)^.Jimmy)^.Recalculate;

	Owner^.SetData(PJimmyEditBox(Owner)^.Jimmy^);
	Owner^.ReDraw;

	DrawState(False);
end;

{***********************************************************************
 ***                                                                 ***
 ***                    PRICING & TOTALLING GROUPS                   ***
 ***                                                                 ***
 ***********************************************************************}
constructor TPriceGroup.Init;
begin
	inherited Init;
	VATSCode := ProgramSetup.Get(siDefaultVAT,'STD');
	COmmonInit;
end;

procedure TPriceGroup.CommonInit;
begin
	Price.Init;
	with CashDiscount do begin
		Amount.Init;
		Rate := 0;
		ByRate := True;
	end;
	with Discount do begin
		Amount.Init;
		Rate := 0;
		ByRate := True;
	end;
	SubTotal.Init;
	VAT.Init;
	Total.Init;
end;

constructor TPriceGroup.Load(var S : TDataStream);
var Ver,B : byte;
begin
	CommonInit;
	S.Read(Ver, 1);
	case Ver of
		0 : begin
			{original price group}
			Price.Load(s);

			VAT.Load(S);
			S.REad(VATScode, 4);
			if not Price.Blank then VATRate := VAT.Value * 100 / Price.Value;
		end;
		1 : begin
			{added discounts, etc}
			Price.Load(S);
			S.Read(CashDiscount.Rate, 4);
			CashDiscount.Amount.Load(S);
			S.Read(Discount.Rate, 4);
			Discount.Amount.Load(S);

			VATSCode := S.REadFixedStr(3);
			S.Read(VATRate, 4); {store rate in case code's rate changes}
		end;
		2 : begin
			{added byrate markers to discounts}
			Price.Load(S);
			S.Read(CashDiscount.Rate, 4);
			CashDiscount.Amount.Load(S);
			S.Read(Discount.Rate, 4);
			Discount.Amount.Load(S);

			S.Read(B, 1);
			CashDiscount.ByRate := (B and $1)>0;
			Discount.ByRate := (B and $2)>0;

			VATSCode := S.REadFixedStr(3);
			S.Read(VATRate, 4); {store rate in case code's rate changes}
		end;
	else
		DBaseMessage(@S,'Version '+N2Str(Ver)+' not known'#13'TPriceGroup.Load', mfError,hcInternalErrorMsg);
		fail;
	end;

	{calculate}
	CalculateFromPrice;
end;

procedure TPriceGroup.Store(var S : TDataStream);
var Ver,B : byte;
begin
	Ver := 2;
	S.Write(Ver, 1);

	Price.Store(S);

	S.Write(CashDiscount.Rate, 4);
	CashDiscount.Amount.Store(S);
	S.Write(Discount.Rate, 4);
	Discount.Amount.Store(S);
	B := 	$01*byte(CashDiscount.ByRate)
			+ $02*byte(Discount.ByRate);
	S.Write(B,1);

	S.WriteFixedStr(@VATSCode, 3);
	S.Write(VATRate, 4); {store rate in case code's rate changes}
end;

procedure TPriceGroup.SetFormCodes;
var VATSCodeItem : PSCodeItem;
begin
	with FormCodes^ do begin
		Insert(New(PMoneyFormCOde, init('PRICE', Price)));

		Insert(New(PMoneyFormCOde, init('DISC', Discount.Amount)));
		Insert(New(PMoneyFormCOde, init('CDISC', CashDiscount.Amount)));

		SetStr('%DISC', R2Str(Discount.Rate, dcAuto,0));
		SetStr('%CDISC', R2Str(CashDiscount.Rate, dcAuto, 0));

		Insert(New(PMoneyFormCOde, init('SUBTOT', SubTotal)));
		Insert(New(PMoneyFormCOde, init('ST', SubTotal))); {abbreviated}

		Insert(New(PMoneyFormCode, init('VAT', VAT)));
		Insert(New(PMoneyFormCode, init('TOT', Total)));

		Insert(New(PSCodeFormCode, init('VATR', VATSCode, scVATRates)));

		VATSCodeItem := GetSCode(scVATRates, VATSCode);
		if VATSCodeItem<>nil then begin
			SetStr('%VAT', R2Str(PVATRateSCodeItem(VATSCodeItem)^.Rate, dcAuto, 0));
			SetStr('%V', R2Str(PVATRateSCodeItem(VATSCodeItem)^.Rate, dcAuto, 0)); {abbreviated}
		end else begin
			SetStr('%VAT', '');
			SetStr('%V','');
		end;
	end;
end;

procedure TPriceGroup.CalculateFromPrice;
begin
	SubTotal.SetTo(Price);
	subTotal.Subtract(CashDiscount.Amount);
	SubTotal.Subtract(Discount.Amount);

	VAT.SetTo(SubTotal);
	VAT.MultiplyBy(VATRate/100);

	Total.SetTo(SubTotal);
	Total.Add(VAT);

{	UnDiscTotal.SetTo(Total);
	UnDiscTotal.Add(CashDiscount.Amount);{}
end;

{****************************
 ***      INPUT           ***
 ****************************}
{putting all these together in one place makes it easier to insert into
all the orderitem descendants}
{const
	svPrice = 1;
	svVATScode = 2;
	svDiscountRate = 3;
	svDiscount = 4;
	svTotaller = 5; {if it's a totaller only, don't work out vat, etc}
{	tvTotal = 1;
	tvVAT = 2;{}

	{for displaying/marking discounttype}
	{uses little arrow to point to rate or amount}
type
	PByRateView = ^TByRateView;
	TByRateView = object(TInputBoolean)
		procedure Draw; virtual;
	end;

	procedure TByRateView.Draw;
	begin
		if Data^='X' then writechar(0,0, #17, 1,1) else writechar(0,0, #16, 1,1);
	end;

{(a) if rate s[2] changed, calculates discount amount s[3] from rate s[2]
and prev total s[1], setting byrate s[4].
(b) if amount s[3] changed, sets byrate s[4] (other calcs done in LinkAmounts below)
(c) if prev total s[1] changed, and byrate s[4] is set to rate then calcs
s[3] as case (a) above}

procedure LinkDiscounts(const Linker : PINputLinker; const CallingView : PView); far;
var Money,Money2 : TMoney;
		S : single;
		ByRate : boolean;
begin
	Money.Init;
	with Linker^ do begin
		SourceView[4]^.GetData(ByRate);
		if (CallingView = SourceView[2]) or
				((CallingView= SourceView[1]) and ByRate) then begin
			{rate or total changed}
			SourceView[1]^.GetData(Money);
			SourceView[2]^.GetData(S);

			{calculate discount amount}
			Money.MultiplyBy(S/100);
			SourceView[3]^.SetData(Money);
			SourceView[3]^.DrawView;
			Message(SourceView[3], evBroadCast, cmForceLink, nil);

			{set byrate to true - do *after* above message as it will call set below}
			ByRate := True;
			SourceView[4]^.SetData(ByRate);
			SourceView[4]^.DrawView;

		end;
		{the check for linkchanged is to see if the user changed the entry (true)
		or if it's a forcelink followon, eg from the message() above}
		if (CallingView = SourceView[3]) and (PInputELine(CallingView)^.LinkChanged) then begin
			{discount *amount* changed, set byrate to false}
			ByRate := False;
			SourceView[4]^.SetData(ByRate);
			SourceView[4]^.DrawView;

			{calculate rate?}
			SourceView[1]^.GetData(MOney); {prev total}
			SourceView[3]^.GetData(Money2); {discount amount}
			if Money.Value<>0 then S := Money2.Value * 100 div Money.Value else S := 0;

			SourceView[2]^.SetData(S);
			SourceView[2]^.DrawView;
		end;
	end;
end;

{see above, except that the total is worked out by s[1] the price,
and subtracting s[5], the previous discount}
procedure LinkCashDiscounts(const Linker : PINputLinker; const CallingView : PView); far;
var Money,Money2, Disc : TMoney;
		S : single;
		ByRate : boolean;
begin
	Money.Init;
	with Linker^ do begin
		SourceView[4]^.GetData(ByRate);
		if (CallingView = SourceView[2]) or
				((CallingView= SourceView[1]) and ByRate) then begin
			{rate or total or other discount changed}
			SourceView[1]^.GetData(Money);
			SourceView[5]^.GetData(Disc); Money.Subtract(Disc);
			SourceView[2]^.GetData(S); 		Money.MultiplyBy(S/100);

			{set amount}
			SourceView[3]^.SetData(Money);
			SourceView[3]^.DrawView;
			Message(SourceView[3], evBroadCast, cmForceLink, nil);

			{set byrate to true - do *after* message above}
			ByRate := True;
			SourceView[4]^.SetData(ByRate);
			SourceView[4]^.DrawView;
		end;
		{the check for linkchanged is to see if the user changed the entry (true)
		or if it's a forcelink followon, eg from the message() above}
		if (CallingView = SourceView[3]) and (PInputELine(CallingView)^.LinkChanged)then begin
			{discount *amount* changed, set byrate to false}
			ByRate := False;
			SourceView[4]^.SetData(ByRate);
			SourceView[4]^.DrawView;

			{calculate rate?}
			SourceView[1]^.GetData(MOney); {prev total}
			SourceView[5]^.GetData(Disc); Money.Subtract(Disc); {subtract prev discount}
			SourceView[3]^.GetData(Money2); {discount amount}
			S := Money2.Value * 100 div Money.Value;

			SourceView[2]^.SetData(S);
			SourceView[2]^.DrawView;
		end;
	end;
end;

{calculate VAT amount from subtotal sourceview[1] and vat code line[2], placing
rate into rate line (single real) targetview[1] and amount into targetview[2],
and total subtotal+vat amount line into Targetview[3]}
{Also used by inputtotallergroup where there is no rate/code line}
procedure LinkVAT(const  Linker : PINputLinker; const CallingView : PView); far;
var Code : TSCode;
		VATSCode : PVATRateSCodeItem;
		SubTotal, VATAmount : TMoney;
begin
	SubTotal.Init;
	VATAmount.Init;

	Linker^.SourceView[1]^.GetData(SubTotal);
	if Linker^.SourceView[2]<>nil then Linker^.SourceView[2]^.GetData(Code)
																else COde := Programsetup.Get(siDefaultVAT, 'STD');
	if Code<>'' then begin
		VATSCode := PVATRateSCodeItem(GetSCode(scVATRates, Code));
		if VATSCode<>nil then begin
			if Linker^.TargetView[1]<>nil then begin
				Linker^.TargetView[1]^.SetData(VATSCode^.Rate);
				Linker^.TArgetView[1]^.DrawView;
			end;

			VATAmount.SetTo(SubTotal);
			VATAMount.MultiplyBy(VATScode^.Rate/100);
			Linker^.TargetView[2]^.SetData(VATAmount);
			Linker^.TArgetView[2]^.DrawView;

			if Linker^.TargetView[3]<>nil then begin
				SubTotal.Add(VATAmount);
				Linker^.TargetView[3]^.SetData(SubTotal);
				Linker^.TargetView[3]^.DrawView;
			end;

		end;
	end;
end;

procedure LinkAmounts(const Linker : PINputLinker; const CallingView : PView); far;
var Money, SubMoney : TMoney;

begin
	{runs through taking price sv[1], subtracting discounts sv[2..3]
	to get subtotal tv[1], adding	vat amount sv[4] to get total tv[2]}
	Money.Init; SubMoney.Init;

	with Linker^ do begin
		SourceView[1]^.GetData(Money);

		{discounts}
		SourceView[2]^.GetData(SubMoney); Money.Subtract(SubMoney);
		SourceView[3]^.GetData(SubMoney); Money.Subtract(SubMoney);

		{subtotal}
		TargetView[1]^.SetData(Money);
		Message(TargetView[1], evBroadCast, cmForceLink, nil); {do vat update}

		{vat}
		SourceView[4]^.GetData(SubMoney); Money.Add(SubMoney);

		{total}
		TargetView[2]^.SetData(Money);

		{display}
		TargetView[1]^.DrawView;
		TargetView[2]^.DrawView;
	end;
end;


constructor TINputPriceGroup.Init;
var R : TRect;
begin
	R.Assign(X,Y,X+29,Y+6);
	inherited Init(R);
	EditBox := NEditBox; {for setting linker}

	InsertEditFields;
end;

procedure TInputPriceGroup.InsertEditFields;
var S : string;
		R : TRect;
		AmountLinker,VATLinker, CashDiscountLinker, DiscountLinker : PInputLinker;
begin
	inherited InsertEditFields; {clear old & set background}

	New(AmountLinker, init(@LinkAmounts, EditBox));
	New(VATLinker, init(@LinkVAT, EditBox));
	New(DiscountLinker, init(@LinkDiscounts, EditBox));
	New(CashDiscountLinker, init(@LinkCashDiscounts, EditBox));

	Insert(New(PSkipBytes, init(2))); {skip vmt of TPriceGroup}

	{price}
	R.XYLD(17,0,12,1); Insert(New(PInputMoney, init(R)));	PriceLine := PInputMoney(Current);
	AmountLinker^.				SetSourceView(Current, 1);
	CashDiscountLinker^.SetSourceView(Current, 1);
	DiscountLinker^.		SetSourceView(Current, 1);
	AddLabel('S~u~bTotal       ', Current);

	{discount rate}
	R.XYLD(10,1, 6,1); Insert(New(PInputPerc, init(R,4)));{}
	DiscountLinker^.		SetSourceView(Current, 2);
	AddLabel('Disc', Current);

	{discount byrate marker}
	R.XYLD(16,1, 1,1); Insert(new(PByRateView, init(R)));
	DiscountLinker^.		SetSourceView(Current, 4);
	Current^.SetState(sfDisabled, True);

	{discount amount}
	R.XYLD(17,1,12,1); Insert(New(PInputMoney, init(R)));{}
	AmountLinker^.				SetSourceView(Current,2);
	DiscountLinker^.		SetSourceView(Current, 3);
	CashDiscountLinker^.SetSourceView(Current, 5);
	AddLabel('', Current);

	{cash discount rate}
	R.XYLD(10,2, 6,1); Insert(New(PInputPerc, init(R,4)));{}
	CashDiscountLinker^.SetSourceView(Current, 2);
	AddLabel('Ca~s~h Disc', Current);

	{cash discount byrate marker}
	R.XYLD(16,2, 1,1); Insert(new(PByRateView, init(R)));
	CashDiscountLinker^.		SetSourceView(Current, 4);
	Current^.SetState(sfDisabled, True);

	{cash discount amount}
	R.XYLD(17,2,12,1); Insert(New(PInputMoney, init(R)));{}
	AmountLinker^.				SetSourceView(Current,3);
	CashDiscountLinker^.SetSourceView(Current,3);
	AddLabel('', Current);

	{subtotal}
	R.XYLD(17,3,12,1); Insert(New(PInputMoney, init(R)));{}
	AmountLinker^.SetTargetView(Current,1);
	VATLinker^.SetSourceView(Current,1);
	Current^.SetState(sfDisabled, True);
	AddLabel('SubTotal       ', Current);

	{VAT code}
	R.XYLD(10,4, 5,1); Insert(New(PInputScode, init(R, scVATRates)));{}
	VATScodeLine := PInputScode(Current);
	VATLinker^.SetSourceView(Current,2);
	AddLabel('~V~AT', Current);

	{VAT rate (hidden)}
	R.XYLD(10,4, 0,0); Insert(New(PInputPerc, init(R,4)));{}
	VATLinker^.SetTargetView(Current, 1);
	Current^.SetState(sfDisabled, True);

	{VAT amount}
	R.XYLD(17,4,12,1); Insert(New(PInputMoney, init(R)));{}
	VATLinker^.SetTargetView(Current,2);
	AmountLinker^.SetSourceView(Current,4);
	AddLabel('', Current);
	Current^.SetState(sfDisabled, True);

	{Total}
	R.XYLD(17,5,12,1); Insert(New(PInputMoney, init(R)));{}
	AmountLinker^.SetTargetView(Current,2);
	VATLinker^.SetTargetView(Current, 3);
	AddLabel('TOTAL       ', Current);
	Current^.SetState(sfDisabled, True);

	{UnDisc Total}
{	Insert(New(PSkipBytes, init(sizeof(TMoney))));{}

{	LastEditField := Current;{}
	SelectNext(False); {move to first one}
{	FirstEditField := Current; {first line of edit fields}

{	if Current<>nil then Current^.SetState(sfSelected, False); {so hotkeys work}
{	Current := nil; {doing above causes a problem when tabbing into a field -
	because it's already focused, it doesn't get selected...}
end;

{***********************************************************************
 ***                                                                 ***
 ***                    PRICING & TOTALLING GROUPS                   ***
 ***                                                                 ***
 ***********************************************************************}
constructor TTotallerGroup.Init;
begin
	inherited Init;
	COmmonInit;
end;

procedure TTotallerGroup.CommonInit;
begin
	SubTotal.Init;
	CashDisc.Init;
	VAT.Init;

	DiscTotal.Init;
	UnDiscTotal.Init;
	Total.Init;
end;

constructor TTotallerGroup.Load(var S : TDataStream);
var Ver : byte;
		VATSCode : TSCode; {rubbish}
begin
	CommonInit;
	S.Read(Ver, 1);
	case Ver of
		0,1 : begin
			{originally price grou[}
			SubTotal.Load(s);

			VAT.Load(S);
			S.Read(VATSCode, 4);
		end;
		2 : begin
			{new totaller group}
			CashDisc.Load(S);
			SubTotal.Load(S); {dummy}

			SubTotal.Load(S);
			VAT.Load(S);
		end;
		3 : begin
			{v4.3 changed money values}
			SubTotal.Load(S);
			CashDisc.Load(S);
			VAT.Load(S);
		end;
	else
		DBaseMessage(@S,'Version '+N2Str(Ver)+' not known'#13'TTotallerGroup.Load', mfError,hcInternalErrorMsg);
		fail;
	end;
	{caller then needs to calculate}
end;

procedure TTotallerGroup.Store(var S : TDataStream);
var Ver : byte;
begin
	Ver := 3;  S.Write(Ver, 1);

	SubTotal.Store(S);
	CashDisc.Store(S);
	VAT.Store(S);
end;

procedure TTotallerGroup.SetFormCodes;
begin
	with FormCodes^ do begin
		Insert(New(PMoneyFormCOde, init('SUBTOT', SubTotal)));
		Insert(New(PMoneyFormCode, init('VAT', VAT)));
		Insert(New(PMoneyFormCode, init('TOTAL', Total)));
		Insert(New(PMoneyFormCode, init('CDISC', CashDisc)));
	end;
end;

procedure TTotallerGroup.Calculate;
begin
	UnDiscTotal.SetTo(SubTotal);
	UnDiscTotal.Add(VAT);

	DiscTotal.SetTo(UnDiscTotal);
	DiscTotal.Subtract(CashDisc);

	if Discountable then Total.SetTo(DiscTotal) else Total.SetTo(UnDiscTotal);
end;


{****************************
 ***      INPUT           ***
 ****************************}
constructor TINputTotallerGroup.Init;
var R : TRect;
begin
	R.Assign(X,Y,X+21,Y+3);
	inherited Init(R);
	EditBox := NEditBox; {for setting linker}

	InsertEditFields;
end;

{usually the totaller group will be disabled, but in particular situations
(eg the QuickInvoice), the user will be inputting direct, in which case the
VAT code needs set too.  For the moment...  Just use the default VAT rate}
{The above needs checking now....}

{Takes source views of subtotal (sv1) and vat (sv2) and discount total (sv3)
and checks if the orders discountable for today, then puts a "*" in the
discount marker and calculates appropriate total tv[1]}
procedure LinkTotal(const Linker : PINputLinker; const CallingView : PView); far;
var Money, VAT, Disc : TMoney;
		Mark : string[2];
		Order : POrder;
		Discountable : boolean;

begin
	{takes price sv[1], adds vat amount sv[2], to get total tv[1]}
	Money.Init; VAT.Init; Disc.Init;

	with Linker^ do begin
		SourceView[1]^.GetData(MOney);

		{vat}
		SourceView[2]^.GetData(VAT); Money.Add(VAT);

		SourceView[3]^.GetData(Disc);

		if Disc.Blank then begin
			Discountable := False; {no discount}
		end else begin
			{clumsy check to see if discountable - has to allow for possible days discount in invoice}
			{get order from disk, so it can be changed}
			{assumes callingview is child of totallergroup, ie child of child of edit box}
			Order := POrder(GetJimmy(PJimmyEditBox(CallingView^.Owner^.Owner)^.Jimmy^.RecNo));
			if Order<>nil then begin {could be new}
				CallingView^.Owner^.Owner^.GetData(Order^);
				Discountable := Order^.CashDiscountable;
				dispose(Order, done);
			end;
		end;

		if Discountable then begin
			Mark := '*';
			Money.Subtract(Disc);
		end else begin
			Mark := ' ';
		end;

		with PInputMoney(TargetView[1])^ do begin
			SetData(Money);
			DrawView;
			ForceLink;
		end;

		with PStaticText(TargetView[2])^ do begin
			Text^ := Mark;
			DrawView;
		end;
	end;
end;

procedure TInputTotallerGroup.InsertEditFields;
var R : TRect;
		TotalLinker : PInputLinker;

begin
	inherited InsertEditFields; {clear old & set background}

{	New(VATLinker, init(@LinkVAT, EditBox));{}
	New(TotalLinker, init(@LinkTotal, EditBox));
	TotalLinker^.ForceInitlink :=True;

	Insert(New(PSkipBytes, init(2))); {skip vmt of TTotallerGroup}

	{laid out for bottom of invoice/estimate}

	{subtotal}
	R.XYLD( 0,0,12,1); Insert(New(PInputMoney, init(R))); SubTotalLine := Current;

	{cash disc total}
	R.XYLD( 0,1,12,1); Insert(New(PInputMoney, init(R))); DiscLine := Current;
	PInputMoney(DiscLine)^.BlankIfZero := True;

	{marker for if cash discount is valid}
	R.XYLD(11,1,3,1);	MarkerLine := New(PStaticText, init(R,' ')); Insert(MarkerLine);

	{VAT amount}
	R.XYLD(12,0,10,1); Insert(New(PInputMoney, init(R)));	VATLine := Current;

	{skip totals}
	Insert(New(PSkipBytes, init(sizeof(TMoney))));
	Insert(New(PSkipBytes, init(sizeof(TMoney))));

	{Total}
	R.XYLD(12,1,10,1); Insert(New(PInputMoney, init(R)));	TotalLine := Current;
{	AddLabel('Total', Current);{}

	{disable all views}
	SubTotalLine^.SetState(sfDisabled, True);
	VATLine^.SetState(sfDisabled, True);
	TotalLine^.SetState(sfDisabled, True);
	DiscLine^.SetState(sfDisabled, True);

	{VAT amount}
	with TotalLinker^ do begin
		SetSourceView(SubTotalLine,1);
		SetSourceView(VATLine, 2);
		SetSourceView(DiscLine,3);

		SetTargetView(TotalLine, 1);
		SetTargetView(MarkerLine,2);
	end;
end;






constructor TStateField.Init;
var R  : TRect;
begin
	R.Assign(0,0,0,0);
	inherited Init(R,5);
	EventMask := EventMask or evBRoadcast;
	JimmyID := NRecNo;
end;

procedure TStateField.HandleEvent;
var B : byte;
begin
	inherited HandleEvent(Event);

	{marks osEdited if jimmy stored}
	if (Event.What = evBroadCast) and (Event.Command = cmStoreJImmy) then begin
{wrong test - cmstorejimmyonly passed around relevant view by okbutton anyway
					if PJimmyStoredInfo(Event.InfoPtr)^.Jimmy^.RecNo=JimmyID then begin{}
			GetData(B);
			B := B or osEdited;
			SetData(B);
		end;
end;

{************************************************
 ***            OUTPUT/FORM CODES             ***
 ************************************************}
type
	{special re-sort depending on control point type}
	POrderItemsFormCode = ^TOrderItemsFormCode;
	TOrderItemsFormCode = object(THookListFormCode)
		RTot, RVAT, RSubTot : TMoney;
		procedure DoPrint(Device : PDeviceStream; Jimmy : PJimmy; Param : string); virtual;
		procedure DoSpecial(var Tree : PTree; Param : TFCodeStr); virtual;
	end;

procedure TOrderItemsFormCode.DoSpecial;
begin
{		Tree^.ReverseOrder;{}
end;{}

procedure TOrderItemsFormCode.DoPrint;
var OrderItem : POrderItem;
		FormType : string;
begin
	OrderItem := POrderItem(Jimmy);

	{--- Running totals ----}
	{assume page break check is done *before* printing item --> running totals printed before
	item's totals are displayed, so add & set before calling print}
	RTot.Add(OrderItem^.PriceGroup.Total);
	RVAT.Add(OrderItem^.PriceGroup.VAT);
	RSubTot.Add(OrderItem^.PriceGroup.subTotal);
	Device^.FormCodes^.Insert(New(PMoneyFormCode, init('RTOT',RTot)));
	Device^.FormCodes^.Insert(New(PMoneyFormCode, init('RVAT',RVAT)));
	Device^.FormCodes^.Insert(New(PMoneyFormCode, init('RSUBTOT',RSubTot)));

	{---- Work out formtype ----}
	{assume from ordersrtype...}
	case PJimmy(Info)^.srType of
		srInvoice	 : FormType := 'II'; {invoice item}
		srEstimate : FormType := 'EI'; {estimate item}
		srJobSheet : FOrmType := 'WS'; {work/job sheet item}
	else
		FormType := 'OI'; {general purpose Order Item}
	end;

	{...unless param overrides}
	if IsParam(Param, '/INV') then FormType := 'II';
	if IsParam(Param, '/EST') then FormType := 'EI';
	if IsParam(Param, '/JOB') then FormType := 'WS';
	if IsParam(Param, '/ORD') then FormType := 'OI';

	{--- Print -----}
	if IsParam(Param, '/SUM') then
		OrderItem^.PrintSummary(Device, 0)
	else
		if IsParam(param, '/LINE') then
			OrderItem^.PrintLine(Device)
		else
			OrderItem^.PrintItem(Device,'', FormType);
end;


{***********************************************************************
 ***                                                                 ***
 ***                    ORDER ITEMS                                  ***
 ***                                                                 ***
 ***********************************************************************}
constructor TOrderItem.Init;
{var Jimmy : PJimmy;{}
begin
	inherited Init;

	if Param=nil then begin {shouldn't be, but anyway}
		ForOrder := -1;
		ParentItemID := -1;
	end else begin
		ForOrder := Param^.ForWho;
		{set parentitem to focused's parentitem}
		ParentItemID := Param^.FocusedParentID;
	end;

	From := -1;
	Following := -1;
end;

procedure TOrderItem.CommonInit;
begin
	inherited CommonINit;
	PriceGroup.Init;
	{$IFDEF KBooks}
	NomCat := acIncome;
	{$ENDIF}
end;

function TorderItem.PtrOffset;
begin PtrOffset := inherited PtrOffset+1; end;

constructor TORderItem.Load;
var Ver : byte;
		W : word;
begin
	S.Read(Ver, 1);
	case Ver of
		0 : begin
			{old ver, applies only to rongai & sbs v4.1a to v4.1d}
			S.Seek(S.GetPos-1); {old one - pre ver - first byte used to be lock}
			inherited Load(S);
			S.Read(ForOrder, 4);
			S.Read(From, 4);
			S.Read(Following, 4);
			S.Read(W,2); {used to be opsrtype}
			PriceGroup.Load(S);
			ParentItemID := -1;
		end;
		1 : begin
			{added parentitemId (and ver handling)}
			inherited Load(S);
			S.Read(ParentItemID,4);
			S.Read(ForOrder, 4);
			S.Read(From, 4);
			S.Read(Following, 4);
			PriceGroup.Load(S);
		end;
		2 : begin
			{v4.3, added nom ledg}
			inherited Load(S);
			S.Read(ParentItemID,4);
			S.Read(ForOrder, 4);
			S.Read(From, 4);
			S.Read(Following, 4);
			PriceGroup.Load(S);
			NomCat := S.ReadStr;
		end;
	else
		DBaseMessage(@S,'Version '+N2Str(Ver)+' not known'#13'TOrderItem.Load', mfError,hcInternalErrorMsg)
	end;
end;

procedure TOrderItem.StoreFields;
var Ver : word;
begin
	Ver := 2; S.Write(Ver, 1);

	inherited StoreFields(S);
	S.Write(ParentItemID, 4);
	S.Write(ForOrder, 4);
	S.Write(From, 4);
	S.Write(Following, 4);
	PriceGroup.Store(S);
	S.WriteStr(@NomCat);
end;

{============== POINTERS TO OTHER JIMMYS===================}
function TOrderItem.NumIDs;
begin NumIDs := 4; end;

function TOrderItem.GetJImmyID;
begin
	case jiType of
		1 : GetJimmyID := @ParentItemID;
		2 : GetJImmyID := @ForOrder;
		3 : GetJimmyID := @From;
		4 : GetJimmyID := @Following;
	else
		GetJimmyID := nil;
	end;
end;

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

{for returning which jimmys ID's this jimmys should be hooked *to*}
procedure TOrderItem.GetHookTo;
begin
	inherited GetHookTo(htType, HookToID,SubHookToID, hkType, Key, InsertBias);
	case httype of
		1 : begin
			HookToID := @ForOrder;
			SubHookToID := @ParentItemID;
			hkType := hkOrderItems;
			InsertBias := biEnd;
		end;
	end;
end;


function TOrderItem.DisplayLine(ListForWho : longint; lstype : byte; Maxlen : integer; View : word) : string;
var S : string;
begin
	S := PadSpaceL(PriceGroup.Price.Text(mtValue+mtBlankZero),10);
	if PriceGroup.CashDiscount.Amount.Blank then S := S +' ' else S := S +flDisc;
	DisplayLine := S+PadSpaceL(PriceGroup.VAT.Text(mtValue+mtBlankZero),9);
end;

procedure TOrderItem.SetFormCodes;
begin
	inherited SetFormCodes(FormCodes);

	with FormCodes^ do begin
		PriceGroup.SetFormCodes(FormCodes);
	end;
end;

{descendants should always override with, eg 'CODED', 'FTEXT', 'GOODS', etc}
function TOrderItem.FormRoot : string;
begin
	RunError(211); {abstract}
end;

procedure TOrderItem.PrintItem;
var Form : string;
begin
	Form := FormRoot + FormType +'.FRM';
	if not FileExists(FormsPath+Form) then begin
		Form := FormRoot +'OI.FRM';
		if not FileExists(FormsPath+Form) then begin
			Form := 'ORDRITEM.FRM';
			if not FileExists(FormsPath+Form) then begin
				Form := '';
				ProgramWarning('Could not print Item - no form'#13#10
												+'Create '+FormRoot+FormType+', '+FormRoot+' OI, or ORDRITEM.FRM in Maintenance',hcOrderFOrms);
			end;
		end;
	end;

	if Form<>'' then PrintForm(Device, Form);
end;

{procedure TOrderItem.DoRepeater;
begin
	{** WATCH THIS - whatever is calling this one ought to reset ForOrder if
	nec - if they don't, it will quite happily stick itself onto the existing
	sales order.... which might be what you want after all}

{	inherited DoRepeater(NewDate,DeltaDays);

end;

{**************************************************
 ***             STD - CODED                    ***
 **************************************************}
constructor TCodedOrderItem.Init;
var Order : POrder;
		View : PView;
begin
	inherited Init(Param);

	Date.SetToToday;
	Quantity := '';
	Code := '';
	CodeLine := '';

	Order := POrder(FindJimmy(ForOrder, View)); {set before}
	if Order<>nil then begin
		Date.SetToDate(Order^.Date);
		if View=nil then dispose(Order, done);
	end;
end;

procedure TCodedOrderItem.CommonInit;
begin
	inherited CommonInit;
	SCodeCollection[scEvents]^.LogOn;
	SCodeCollection[scActions]^.LogOn;

	New(Comment, init);

	BasePrice.Init;
	PriceEach.Init;
end;

destructor TCodedOrderItem.Done;
begin
	SCodeCollection[scEvents]^.LogOff;
	SCodeCollection[scActions]^.LogOff;
	dispose(COmment, done);
	inherited Done;
end;


procedure LastLineRSet(var S : string; AS : string; Maxlen : byte);
var SS : String;
		L : byte;
begin
	if Maxlen <=length(AS) then
		S := S + AS
	else begin
		SS := '';
		for L := 1 to NumLines(S)-1 do SS := SS + GetLine(S,L) + #13;
		S := SS + SetLength(GetLine(S, NumLines(S)), Maxlen - length(AS)) + AS;
	end;
end;



{======= DISPLAY LINE ====================}
function TCodedOrderItem.DisplayLine;
var S,SE,SA : string;

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

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

	if (delspaceR(Quantity)<>'') and (SA<>'') then SA := SA + ' x'+Quantity;

	{First line}
	{date - leave as nothing if undated for undated types of orders - eg stock}
	if Date.Blank then S := '' else S := Date.Digit8+' ';

	if SE<>'' then S := S + SE
		else if SA<>'' then S := S +SA
			else S := S + LSGetLine(Comment^.Text, 1);

	if (delspaceR(Quantity) <> '') and (SA='') then S := S+' x'+Quantity;

	{Extra lines}
	if (SE<>'') and (Codeline<>'') then S := S +#13+space(9)+SA; {Add description if not already on first line}
	if ((SE<>'') or (Codeline<>'')) and (LSLen(Comment^.Text)<>0) then S := S + #13+space(9)+LSGetLine(Comment^.Text,1);

	{on last line add total}
{	case lsType of
		lsSalesItems : LastLineRSet(S, ' '+PriceGroup.Price.Text(mtValue), maxlen);
	else{}
		LastLineRSet(S, inherited DisplayLine(ListForWho, lstype, Maxlen, View), maxlen);
{	end;
{	SE := inherited DisplayLine(ListForWho, lstype, Maxlen, View);
{  S := S + Space(Maxlen-XPos(S)-10)+SE; {need to right set}
{	S := S + ' '+SE;{}

	{$IFDEF Fixit}
	S := S + #13+'      TEK:'+N2Str(RecNo)+' For'+N2Str(ForOrder);
	{$ENDIF}

	DisplayLine := S;
end;


procedure LinkCodedItemPrices(const Linker : PInputLinker; const CallingView : PView); far;
var BasePrice,UnitPrice,Price : TMoney;
		S : String[10];
		Q : real; {quantity}
begin
	PInputMoney(Linker^.SourceView[1])^.GetData(BasePrice);
	PInputELine(Linker^.SourceView[2])^.GetData(S);
	PInputMOney(Linker^.SourceView[3])^.GetDAta(UnitPrice);

	{calculate}
	Price.SetTo(UnitPrice);
	Q := S2Real(S);
	if Q=0 then Q := Frac2Real(S); {try 1/4, etc}
	if Q<>0 then Price.MultiplyBy(Q);
	Price.Add(BasePrice);

	with PInputPriceGroup(LInker^.TargetView[1])^ do begin
		PriceLine^.SetData(price);
		PriceLine^.DrawView;
		PriceLine^.ForceLink; {do vat, etc}
	end;
end;



{================ EDIT BOX ============================}
procedure TCodedOrderItem.MakeEditBox;
var Bounds, R : TRect;
		PriceLine, ActionLine, EventLine : PVIew;
		ActionLinker, EventLinker,PriceLinker : PInputLinker;

begin
	Bounds.Assign(0, 0, 45,15);
	CentreOnView(Bounds, Caller);
	EditBox := New(PJimmyEditBox, init(Bounds, 'Coded Item',Caller,@Self));
	New(ActionLinker, init(nil, EditBox));
	New(EventLinker, init(nil, EditBox));
	New(PriceLinker, init(@LinkCodedItemPrices, EditBox));

	{----Position box, in centre of calling view----}
	with EditBox^ do begin
		Insert(New(PSkipBytes, init(sizeof(TOrderItem)-sizeof(TPriceGroup)-sizeof(NomCat))));  {Skip detail fields & VMT}

		Insert(New(PInputPriceGroup, init(Size.X-30,Size.Y-7, EditBox))); {for prices, vat, etc}
		PriceLinker^.SetTargetView(Current,1);

		Insert(New(PSkipBytes, init(sizeof(NomCat))));

		{-- Buttons --}
		{inserted here so after price group}
		if AllowChanges then
			Insert(New(PJimmyOKButton, Init(2,Size.Y-5, @Self)));
		Insert(New(PjimmyCancelButton, init(2,Size.Y-3, @Self)));

		InsTitledField(10,  1, 10, 1, '~D~ate ', New(PInputDate, Init(R)));
		InsTitledField(10,  2, 32, 1, '~E~vent', New(PInputSCode, Init(R, scEvents)));
		EventLine := Current;
		InsTitledField(10,  3, 32, 1, '~A~ction', New(PInputSCLine, Init(R, 12, scActions)));
		ActionLine := Current;
		InsTitledField(10,  4, 32, 2, 'C~o~mment', New(PInputFreeText, Init(R,200,30,nil)));
		Eventlinker^.SetSourceView(EventLine,1);
		Actionlinker^.SetSourceView(ActionLine,1);

		InsTitledField(10,  6,  7, 1, '~P~rice', New(PInputSCodePrice, init(R)));
		PriceLinker^.SetSourceView(Current,1);
		EventLinker^.SetTargetView(Current,1);
		InsTitledField(25,  6,  5, 1, '+~Q~ty', New(PInputELine, Init(R,10)));
		PriceLinker^.SetSourceView(Current,2);
		InsTitledField(35,  6,  7, 1, '@',     New(PInputScodePrice, Init(R)));
		PriceLinker^.SetSourceView(Current,3);
		ActionLinker^.SetTargetView(Current,1);
		PriceLine := Current;

		EndInit;
		EventLine^.Focus;
	end;

end;

{************************************
 ***         DATABASE             ***
 ************************************}
const
	{--- Required for Stream ----}
	RCodedOrderItem : TStreamRec = (
		ObjType : srCodedOrderItem;
		VmtLink : Ofs(TypeOf(TCodedOrderItem)^);
		Load : @TCodedOrderItem.Load;
		Store : @TCodedOrderItem.Store
	);

function TCodedOrderItem.RecSize;
begin RecSize := 150; end;

function TCodedOrderItem.srType;
begin srType := srCodedOrderItem; end;


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

	case Ver of
		1 : begin
			inherited Load(S);
			Date.Load(S);
			ReadSCode(S, @Code);
			ReadSCLine(S, @CodeLine, 12);
			BasePrice.Load(S);
			Quantity := S.ReadStr;
			PriceEach.Load(S);
			Comment^.Load(S);
		end;
	else
		DBaseMessage(@S,'Version '+N2Str(Ver)+' not known'#13'TCodedOrderItem.Load', mfError,hcInternalErrorMsg)
	end;

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

{========== STORE ===================}
procedure TCodedOrderItem.StoreFields;
var ver : byte;

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

	inherited StoreFields(S);

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


{-- Hooking to others -----}
procedure TCodedOrderItem.GetHookTo;
begin
	{defaults by returning key as date}
	inherited GetHookTo(htType, HookToID,SubHookToID, hkType, Key, InsertBias);
	if Date.Blank then Key := 1
	else Key := Date.Days; {proper date order}
end;


procedure TCodedOrderItem.SetFormCodes;
var S : string;
begin
	inherited SetFormCodes(formCodes);

	with FormCodes^ do begin
		{check previous date}
		if QDecode('ITEMDT')=Date.Digit10 then SetStr('CDT','') {changed date}
			else SetDate('CDT', Date);
		SetDate('ITEMDT', Date); {for checking above.  Can't check <DT> as it
		is often set to today for printing the invoice itself}

		SetDate('DT', Date);
		Insert(New(PSCodeFormCode, init('EVENT', Code, scEvents)));
		Insert(New(PScodeFormCode, init('ACTIONS', CodeLine, scActions)));
		Insert(NEw(PFreeTextFormCode, init('NOTE', Comment^)));
		Insert(new(PMoneyFormCode, init('PBASE', BasePrice)));
		Insert(New(PMoneyFOrmCode, init('PEACH', PriceEach)));
		SetStr('QTY', Quantity);

		{By replacing text with codes we can avoid loading notes unnecessarily}
		S := '';
		if delspaceR(Code)<>'' then S := '<EVENT>'+CRLF;
		if delspaceR(CodeLine)<>'' then S:= S +'<ACTIONS>'+CRLF;
		if Comment^.First<>-1 then S := S + '<NOTE>'+CRLF;
		if S<>'' then	S := Copy(S,1,length(S)-2); {remove last crlf}
		SetStr('TEXT', S);
	end;
end;


function TCodedOrderItem.FormRoot : string;
begin	FormRoot := 'CODED'; end;


{**************************************************
 ***             STD - free text                ***
 **************************************************}
constructor TFreeTextOrderItem.Init;
begin
	inherited Init(Param);
end;

procedure TFreeTextOrderItem.CommonInit;
begin
	inherited CommonInit;
	New(FreeText, init);
end;

destructor TFreeTextOrderItem.Done;
begin
	dispose(Freetext, done);
	inherited Done;
end;


{======= DISPLAY LINE ====================}
function TFreeTextOrderItem.DisplayLine;
var S : string;
		I : byte;

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

	S := '';
	LSReWidth(FreeText^.Text, MaxOf(30,Maxlen));	 {rewidth to fit maxlen, at least 30 chars}
	for I := 1 to LSNumLines(FreeText^.Text) do
		S := S+LSGetLine(FreeText^.Text, I)+CRLF;

	{remove last line & add value}
	while Copy(S,length(S)-1,2)=CRLF do S := Copy(S,1,length(S)-2);

	{add in price, etc}
	LastLineRSet(S, inherited DisplayLine(ListForWho, lstype, Maxlen, View), maxlen);

	DisplayLine := S;
end;


{================ EDIT BOX ============================}
procedure TFreeTextOrderItem.MakeEditBox;
var Bounds, R : TRect;
		FView : PVIew;

begin
	Bounds.Assign(0, 0, 45,15);
	CentreOnView(Bounds, Caller);
	EditBox := New(PJimmyEditBox, init(Bounds, 'FreeText Item',Caller,@Self));

	{----Position box, in centre of calling view----}
	with EditBox^ do begin
		Insert(New(PSkipBytes, init(sizeof(TOrderItem)-sizeof(TPriceGroup)-sizeof(NomCat))));  {Skip detail fields & VMT}

		Insert(New(PInputPriceGroup, init(Size.X-30,Size.Y-7, EditBox))); {for prices, vat, etc}

		Insert(New(PSkipBytes, init(sizeof(NomCat))));

		{-- Buttons --}
		{inserted here so after price group}
		if AllowChanges then
			Insert(New(PJimmyOKButton, Init(2,Size.Y-5, @Self)));
		Insert(New(PjimmyCancelButton, init(2,Size.Y-3, @Self)));

		FView := InsTitledField(1,  1, 41, 7, '', New(PInputFreeText, Init(R,200,40,nil)));
		EndInit;
		FView^.Focus;
	end;

end;

{************************************
 ***         DATABASE             ***
 ************************************}
const
	{--- Required for Stream ----}
	RFreeTextOrderItem : TStreamRec = (
		ObjType : srFreeTextOrderItem;
		VmtLink : Ofs(TypeOf(TFreeTextOrderItem)^);
		Load : @TFreeTextOrderItem.Load;
		Store : @TFreeTextOrderItem.Store
	);

function TFreeTextOrderItem.RecSize;
begin RecSize := 100; end;

function TFreeTextOrderItem.srType;
begin srType := srFreeTextOrderItem; end;


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

	case Ver of
		1 : begin
			inherited Load(S);
			FreeText^.Load(S);
		end;
	else
		DBaseMessage(@S,'Version '+N2Str(Ver)+' not known'#13'TFreeTExtOrderItem.Load', mfError,hcInternalErrorMsg)
	end;
end;

{========== STORE ===================}
procedure TFreeTextOrderItem.StoreFields;
var
	ver : byte;

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

	inherited StoreFields(S);

	FreeText^.Store(S);
end;


function TFreeTextOrderItem.FormRoot : string;
begin	FormRoot := 'FTEXT'; end;

procedure TFreeTextOrderItem.SetFormCodes;
begin
	inherited SetFormCodes(FormCodes);
	FormCodes^.Insert(New(PFreeTextFormCode, init('TEXT', FreeText^)));
end;

{***********************************************************************
 ***                                                                 ***
 ***                ROOT OBJECT FOR ORDERS/INVOICES/ETC              ***
 ***                                                                 ***
 ***********************************************************************}
{--- Initialise - set ptrs to SC ---}
constructor TOrder.Init;
begin
	inherited Init;
	Date.SetToToday;
	ForWho := -1;
	ByWho := -1;

	if Param <> nil then begin
		ForWho := Param^.ForWho;
		{$IFDEF kdiary}
			if (Param^.ListVIew<>nil) and (Param^.ListView^.lsType=lsDiary) then
				Date.SetToDate(PDiaryView(Param^.ListView)^.PageDate);
		{$ENDIF}
	end;

	{$IFDEF kusers} if CurrentUser<>nil then ByWho := CurrentUser^.RecNo; {$ENDIF}

	Ptr2Items := -1;
	LastPrint.Clear;
	Ref := -1;
	OldRef := -1;

	ForWhoRef := nil;
end;

procedure TOrder.CommonInit;  {Init shared betweeen load and init above}
begin
	inherited CommonInit;

	TotallerGroup.Init;

	New(Notes, init);
end;


destructor TOrder.Done;
begin
	dispose(Notes, done);
	TotallerGroup.Done;
	inherited Done;
end;


constructor TOrder.Load(var S : TDataStream);
var Ver : byte;

begin
	S.Read(Ver, 1);

	case ver of
		1 : begin
			inherited Load(S);
			S.REad(Ref, 4); OldRef := Ref;
			S.Read(ForWho, 4);
			Date.Load(S);
			LastPrint.Load(S);
			TotallerGroup.Load(S);
			{descendants will read bywho, etc}
			ForWhoRef := nil; ByWho := -1;
		end;
		2 : begin
			{added state}
			inherited Load(S);
			S.REad(Ref, 4); OldRef := Ref;
			S.Read(ForWho, 4);
			Date.Load(S);
			LastPrint.Load(S);
			TotallerGroup.Load(S);
			S.Read(State, 1);
			{descendants will read bywho, etc}
			ForWhoRef := nil; ByWho := -1;
		end;
		3 : begin
			{v4.3a - added bywho, forwhoref & notes}
			inherited Load(S);
			S.REad(Ref, 4); OldRef := Ref;
			S.Read(ForWho, 4);
			Date.Load(S);
			LastPrint.Load(S);
			TotallerGroup.Load(S);
			S.Read(State, 1);
			S.Read(ByWho, 4);
			ForWhoRef := NewStr(S.ReadStr);
			Notes^.Load(S);
		end;
	else
		DBaseMessage(@S, 'Version '+N2Str(Ver)+' not understood'#13#10'TOrder.Load',mfError,hcInternalErrorMsg);
		fail;
	end;

	TotallerGroup.Calculate(CashDiscountable);
end;

procedure TOrder.StoreFields(var S : TDataStream);
var	ver : byte;

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

	inherited StoreFields(S);

	S.Write(Ref, 4);
	S.Write(ForWho, 4);
	Date.Store(S);
	LastPrint.Store(S);
	TotallerGroup.Store(S);
	S.WRite(State, 1);
	S.WRite(ByWHo, 4);
	S.WriteStr(ForWhoRef);
	Notes^.Store(S);
end;

{============== POINTERS TO OTHER JIMMYS===================}
function TOrder.NumIDs;
begin NumIDs := 2; end;

function TOrder.GetJImmyID;
begin
	case jiType of
		1 : GetJimmyID := @ForWho;
		2 : GetJimmyID := @ByWho;
	else
		GetJimmyID := nil;
	end;
end;



{--- Hooking on others -----}
function TOrder.NumhkTypes;
begin NumhkTypes := 1; end; {hkorderitems max}

procedure TOrder.GetHookOn(const hkType : byte; var HookRec : PLongint);
begin
	inherited GetHookOn(hkType, HookRec);
	case hktype of
		hkOrderItems : HookRec := @Ptr2Items;
	end;
end;

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

{for returning which jimmys ID's this jimmys should be hooked *to*}
procedure TOrder.GetHookTo;
begin
	inherited GetHookTo(htType, HookToID,SubHookToID, hkType, Key, InsertBias);

	if Date.Blank then Key := SortKeyStart {Make sure appears at beginning}
	else Key := -Date.Days;

	case htType of
		1 : begin
			HookToID := @ForWho;
			hkType := hkHistory;
			InsertBias := biStart;
		end;
	end;
end;

procedure TOrder.PreStoreing;
begin
	{$IFNDEF fixit} {always do if in fixit mode}
	if OldRef<>Ref then
	{$ENDIF}
	begin
		{the extra check first, ie getidptr, stops it from, eg, setting a job
		index for a ptr way past end of file}
		if OldRef<>-1 then if GetIDPtr(srType, OldRef)<>-1 then SetIDPtr(srType, OldRef, -1); {clear old reference}
		if Ref<>0 then SetIDPtr(srType, Ref, RecNo); {ignore no ref}
	end;{}
end;

procedure TOrder.DoRepeater;
var Ptr : longint;
		OrderItem : POrderItem;
		D : integer;
		WorkDate : TDate;
begin
	Ptr := Ptr2Items; {temp store}

	inherited DoRepeater(NewDate,DeltaDays); {clears ptrs, etc}

	D := NewDate.Days - Date.Days; {change in date, for working out change to items}
	Date.SetToDate(NewDate);

	{now needs to run through order item tree, doing dorepeater for each one
	and resetting ForOrder to point to new self...{}
	Ref := GetNewID(srType); StoreSelf; {so first, re-store self}

	{now run through old chain, re-creating for new order}
	WorkDate.Clear;
	FileAdmin(fiHooks)^.LogOn;
	OrderItem := PorderItem(HookFile^.GetFirst(Ptr, 0));

	while OrderItem<>nil do begin
		{now, how are we going to work out the new date?...}
		OrderItem^.DoRepeater(WorkDate,D);
		OrderItem^.ForOrder := RecNo;
{		OrderItem^.Date^.AddDays({}

		OrderItem^.StoreSelf;

		dispose(OrderItem, done);
		OrderItem := POrderItem(HookFile^.GetNextJimmy);
	end;

	FileAdmin(fiHooks)^.LogOff;

	{caller will then do a storeself for this order, so first load hook pointers
	set by above}
	LoadFirstHookPtr(hkOrderItems);
end;



{=========== SET CODES ==========}
procedure TOrder.SetFormCodes;
var ItemsFormCode : POrderItemsFormCode;
		S : string;
begin
	inherited SetFormCodes(FormCodes);

	TotallerGroup.SetFormCodes(FormCodes);

	with FormCodes^ do begin
		SetStr('RTITLE', 'Order');

		{SetFunc('ITEMS',PrintInvoiceTree, @ItemTree);{}

		SetStr('REF', N2Str(Ref));
		if ForWhoRef<>nil then SetStr('FORREF', ForWhoRef^) else SetStr('FORREF','');

		Insert(New(PJimmyFormCode, init('FOR', ForWho)));
		Insert(New(PJimmyFormCode, init('BY', ByWho)));

		SetDate('SDT', Date); {sent date}
		SetDate('PDT', LastPrint); {last print date}

		SetStr('CDT',''); {"changed" date - for use by items}

		New(ItemsFormCode, init('ITEMS', GetJimmy(RecNo), hkOrderItems, 'ORDRITEM.FRM',0));
		Insert(ItemsFormCode);
		with ItemsFormCode^ do begin
			RTot.Init;
			RVAT.Init;
			RSubTot.Init;
		end;

		Insert(New(PFreeTextFormCode, init('NOTES', Notes^)));


		S := Prefix; SetPrefix('');
		SetStr('FAXTO.NAME','<'+S+'FOR.NAME>');
		SetStr('FAXTO.NUM', '<'+S+'FOR.FAX>');
		SetStr('FAXTO.CNTRY', '<'+S+'FOR.ADD.CNTRY>');
		SetPrefix(S);

	end;

end;

{assume only orderitem descendants will be hooked on}
function TOrder.HookingOn;
begin
	HookingOn := True;

	if hktype = hkOrderItems then
		with TotallerGroup do begin
			SubTotal.Add(			PorderItem(HookingJimmy)^.PriceGroup.Price);
			SubTotal.Subtract(PorderItem(HookingJimmy)^.PriceGroup.Discount.Amount);
			CashDisc.Add(	PorderItem(HookingJimmy)^.PriceGroup.CashDiscount.Amount);
			VAT.Add(			PorderItem(HookingJimmy)^.PriceGroup.VAT);
			Calculate(CashDiscountable);
		end;
end;

function TOrder.Unhooking;
begin
	UnHooking := True;

	if hktype = hkOrderItems then
		with TotallerGroup do begin
			SubTotal.Subtract(PorderItem(HookingJimmy)^.PriceGroup.Price);
			SubTotal.Add(			PorderItem(HookingJimmy)^.PriceGroup.Discount.Amount);
			CashDisc.Subtract(PorderItem(HookingJimmy)^.PriceGroup.CashDiscount.Amount);
			VAT.Subtract(			PorderItem(HookingJimmy)^.PriceGroup.VAT);
			Calculate(CashDiscountable);
		end;
end;


{==== PRINT ALL ITEMS ==============}
{procedure TOrder.PrintItems;
var RTot, RVAT, RSubTot : TMoney;

	procedure PrintItem(Hook : PHook); far;
	var OrderItem : POrderItem;
	begin
		OrderItem := POrderItem(GetJimmy(Hook^.JimmyID));

		if OrderItem<>nil then begin

			{assume page break check is done *before* printing item --> running totals printed before
			item's totals are displayed, so add & set before calling print}
{			RTot.Add(OrderItem^.PriceGroup.Total);
			RVAT.Add(OrderItem^.PriceGroup.VAT);
			RSubTot.Add(OrderItem^.PriceGroup.subTotal);
			Device^.FormCodes^.Insert(New(PMoneyFormCode, init('RTOT',RTot)));
			Device^.FormCodes^.Insert(New(PMoneyFormCode, init('RVAT',RVAT)));
			Device^.FormCodes^.Insert(New(PMoneyFormCode, init('RSUBTOT',RSubTot)));

			OrderItem^.PrintItem(Device,'');
			if (Hook^.NextID=-1) and (Hook^.ChildID<>-1) then
				Device^.writeln(''); {extra line at end of tree lines}

{			dispose(OrderItem, done);
		end;
	end;

begin
	with Device^.FormCodes^ do begin
		SetStr('CDT',''); {clear changed date}
{	end;
	RTot.Init; RVAT.Init; RSubTot.Init;

	FileAdmin(fiHooks)^.LogOn;
	HookFile^.ForEach(Ptr2Items, @PrintItem);
	FileAdmin(fiHooks)^.LogOff;
end;


{**************************************
 ***         PRINT Order           ***
 **************************************}
procedure TOrder.PrintLabel;
var ForWhoJ : PDirectoryItem;
begin
	ForWhoJ := PDirectoryItem(GetJimmy(ForWho));
	ForWhoJ^.PrintLabel(Device,LabelAs);
	dispose(ForWhoJ, done);
end;

procedure TOrder.OnPrinting;
begin
	LastPrint.SetToToday;
	if Date.Blank then Date.SetToToday;
	{if (State and osSent) = 0 then Send; not necessarily - eg printing invoice as proforma}
	State := (State or osSent) and not osEdited; {clear edited bit}
	StoreSelf;
end;

procedure TOrder.Send;
begin
	State := State or osSent;
end;

{kind of a fixit thing for recalculating totals from items}
{descendants clear extra fields then call this}
procedure TOrder.Recalculate;

	procedure RecalcItem(Hook : PHook); far;
	var OrderItem : POrderItem;
	begin
		OrderItem := POrderItem(GetJimmy(Hook^.JimmyID));
		if OrderItem<>nil then begin
			HookingOn(hkOrderItems, 1, OrderItem); {hookto context of 1...}
			dispose(OrderItem, done);
		end;
	end;

begin
	ThinkingOn('Recalculating');
	TotallerGroup.Init;

	FileAdmin(fiHooks)^.LogOn;
	HookFile^.ForEach(Ptr2Items, @RecalcItem);
	FileAdmin(fiHooks)^.LogOff;
	ThinkingOff;
end;

procedure TOrder.CalculateTotals;
begin
	TotallerGroup.Calculate(CashDiscountable);
end;

function TOrder.CashDiscountable;
begin
	CashDiscountable := True;
end;

{***********************************************************************
 ***                                                                 ***
 ***                INPUTGROUP FOR ORDERITEM PRICE STUFF             ***
 ***                                                                 ***
 ***********************************************************************}
constructor TItemToOrderButton.Init;
begin
	inherited Init(x,Y, NTitle, cmNone, bfNormal, OrderItem);
end;

procedure TItemToORderButton.Press;
var OrderID : longint;
		Order : POrder;
begin
	OrderID := POrderItem(DataItem)^.ForOrder;

	Order := POrder(GetJimmy(OrderID));

	Order^.Edit(@Self, nil);
end;

{**************************************
 ***       INITIALISER              ***
 **************************************}
function CreateCodedOrderItem(P : pointer) : pointer; far;
begin	CreateCodedOrderItem := New(PCodedOrderItem, init(P)); end;

function CreateFreeTextOrderItem(P : pointer) : pointer; far;
begin	CreateFreeTextOrderItem := New(PFreeTextOrderItem, init(P)); end;

begin
	RegisterType(RCodedORderItem);
	RegisterType(RFreeTextORderItem);

	RegisterNewWithList(lsSalesItems, '~C~oded Item', cmNewCodedOrderItem);
	RegisterNewWithList(lsInvoiceItems, '~C~oded Item', cmNewCodedOrderItem);
	RegisterNewWithList(lsSalesItems, '~F~ree text', cmNewFreeTextOrderItem);
	RegisterNewWithList(lsInvoiceItems, '~F~ree text', cmNewFreeTextOrderItem);

	RegisterCreator(cmNewCodedOrderItem, CreateCodedOrderItem);
	RegisterCreator(cmNewFreeTextOrderItem, CreateFreeTextOrderItem);

	RegisterSCodeType(scEvents, 'Events.SC',  'Event Types', CostedSCodeCreator);
	RegisterSCodeType(scActions,'Actions.SC', 'Action Codes', CostedSCodeCreator);
end.
