home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 1999 February / DPPCPRO0299.ISO / February / Delphi / Install / DATA.Z / DATAMOD.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-06-11  |  19.3 KB  |  671 lines

  1. unit DataMod;
  2.  
  3. { See the comments in MAIN.PAS for information about this project }
  4.  
  5. interface
  6.  
  7. uses
  8.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  9.   DB, DBTables;
  10.  
  11. type
  12.   TMastData = class(TDataModule)
  13.     Database: TDatabase;
  14.     NextCust: TTable;
  15.     NextCustNewCust: TFloatField;
  16.     Parts: TTable;
  17.     PartsPartNo: TFloatField;
  18.     PartsDescription: TStringField;
  19.     PartsOnHand: TFloatField;
  20.     PartsOnOrder: TFloatField;
  21.     PartsSource: TDataSource;
  22.     PartsQuery: TQuery;
  23.     PartsQueryPartNo: TFloatField;
  24.     PartsQueryDescription: TStringField;
  25.     PartsQueryOnHand: TFloatField;
  26.     PartsQueryOnOrder: TFloatField;
  27.     VendorSource: TDataSource;
  28.     Vendors: TTable;
  29.     PartsVendorNo: TFloatField;
  30.     PartsCost: TCurrencyField;
  31.     PartsListPrice: TCurrencyField;
  32.     PartsBackOrd: TBooleanField;
  33.     PartsQueryVendorNo: TFloatField;
  34.     PartsQueryCost: TCurrencyField;
  35.     PartsQueryListPrice: TCurrencyField;
  36.     PartsQueryBackOrd: TBooleanField;
  37.     Orders: TTable;
  38.     OrdersOrderNo: TFloatField;
  39.     OrdersCustNo: TFloatField;
  40.     OrdersSaleDate: TDateTimeField;
  41.     OrdersShipDate: TDateTimeField;
  42.     OrdersShipToContact: TStringField;
  43.     OrdersShipToAddr1: TStringField;
  44.     OrdersShipToAddr2: TStringField;
  45.     OrdersShipToCity: TStringField;
  46.     OrdersShipToState: TStringField;
  47.     OrdersShipToZip: TStringField;
  48.     OrdersShipToCountry: TStringField;
  49.     OrdersShipToPhone: TStringField;
  50.     OrdersShipVIA: TStringField;
  51.     OrdersPO: TStringField;
  52.     OrdersEmpNo: TIntegerField;
  53.     OrdersTerms: TStringField;
  54.     OrdersPaymentMethod: TStringField;
  55.     OrdersItemsTotal: TCurrencyField;
  56.     OrdersTaxRate: TFloatField;
  57.     OrdersTaxTotal: TCurrencyField;
  58.     OrdersFreight: TCurrencyField;
  59.     OrdersAmountPaid: TCurrencyField;
  60.     OrdersAmountDue: TCurrencyField;
  61.     OrdersSource: TDataSource;
  62.     CustByOrd: TTable;
  63.     CustByOrdCustNo: TFloatField;
  64.     CustByOrdCompany: TStringField;
  65.     CustByOrdAddr1: TStringField;
  66.     CustByOrdAddr2: TStringField;
  67.     CustByOrdCity: TStringField;
  68.     CustByOrdState: TStringField;
  69.     CustByOrdZip: TStringField;
  70.     CustByOrdCountry: TStringField;
  71.     CustByOrdPhone: TStringField;
  72.     CustByOrdFAX: TStringField;
  73.     CustByOrdTaxRate: TFloatField;
  74.     CustByOrdContact: TStringField;
  75.     CustByOrdLastInvoiceDate: TDateTimeField;
  76.     CustByOrdSrc: TDataSource;
  77.     Items: TTable;
  78.     ItemsItemNo: TFloatField;
  79.     ItemsOrderNo: TFloatField;
  80.     ItemsDescription: TStringField;
  81.     ItemsSellPrice: TCurrencyField;
  82.     ItemsQty: TIntegerField;
  83.     ItemsDiscount: TFloatField;
  84.     ItemsExtPrice: TCurrencyField;
  85.     ItemsSource: TDataSource;
  86.     NextOrd: TTable;
  87.     NextOrdNewKey: TFloatField;
  88.     Emps: TTable;
  89.     EmpsEmpNo: TIntegerField;
  90.     EmpsFullName: TStringField;
  91.     EmpsLastName: TStringField;
  92.     EmpsFirstName: TStringField;
  93.     EmpsPhoneExt: TStringField;
  94.     EmpsHireDate: TDateTimeField;
  95.     EmpsSalary: TFloatField;
  96.     EmpsSource: TDataSource;
  97.     LastItemQuery: TQuery;
  98.     Cust: TTable;
  99.     CustCustNo: TFloatField;
  100.     CustCompany: TStringField;
  101.     CustPhone: TStringField;
  102.     CustLastInvoiceDate: TDateTimeField;
  103.     CustSource: TDataSource;
  104.     CustQuery: TQuery;
  105.     CustQueryCustNo: TFloatField;
  106.     CustQueryCompany: TStringField;
  107.     CustQueryPhone: TStringField;
  108.     CustQueryLastInvoiceDate: TDateTimeField;
  109.     OrdByCustSrc: TDataSource;
  110.     OrdByCust: TTable;
  111.     OrdByCustOrderNo: TFloatField;
  112.     OrdByCustCustNo: TFloatField;
  113.     OrdByCustSaleDate: TDateTimeField;
  114.     OrdByCustShipDate: TDateTimeField;
  115.     OrdByCustItemsTotal: TCurrencyField;
  116.     OrdByCustTaxRate: TFloatField;
  117.     OrdByCustFreight: TCurrencyField;
  118.     OrdByCustAmountPaid: TCurrencyField;
  119.     OrdByCustAmountDue: TCurrencyField;
  120.     ItemsPartNo: TFloatField;
  121.     CustAddr1: TStringField;
  122.     CustAddr2: TStringField;
  123.     CustCity: TStringField;
  124.     CustState: TStringField;
  125.     CustZip: TStringField;
  126.     CustCountry: TStringField;
  127.     CustFAX: TStringField;
  128.     CustTaxRate: TFloatField;
  129.     CustContact: TStringField;
  130.     CustMasterSrc: TDataSource;
  131.     CustByComp: TTable;
  132.     CustByCompSrc: TDataSource;
  133.     procedure PartsBeforeOpen(DataSet: TDataSet);
  134.     procedure PartsCalcFields(DataSet: TDataSet);
  135.     procedure PartsQueryCalcFields(DataSet: TDataSet);
  136.     procedure OrdersAfterCancel(DataSet: TDataSet);
  137.     procedure OrdersAfterPost(DataSet: TDataSet);
  138.     procedure OrdersBeforeCancel(DataSet: TDataSet);
  139.     procedure OrdersBeforeClose(DataSet: TDataSet);
  140.     procedure OrdersBeforeDelete(DataSet: TDataSet);
  141.     procedure OrdersBeforeInsert(DataSet: TDataSet);
  142.     procedure OrdersBeforeOpen(DataSet: TDataSet);
  143.     procedure OrdersCalcFields(DataSet: TDataSet);
  144.     procedure OrdersNewRecord(DataSet: TDataSet);
  145.     procedure ItemsAfterDelete(DataSet: TDataSet);
  146.     procedure ItemsAfterPost(DataSet: TDataSet);
  147.     procedure EnsureOrdersEdit(DataSet: TDataSet);
  148.     procedure ItemsBeforeEdit(DataSet: TDataSet);
  149.     procedure ItemsBeforeOpen(DataSet: TDataSet);
  150.     procedure ItemsBeforePost(DataSet: TDataSet);
  151.     procedure ItemsCalcFields(DataSet: TDataSet);
  152.     procedure ItemsNewRecord(DataSet: TDataSet);
  153.     procedure EmpsCalcFields(DataSet: TDataSet);
  154.     procedure OrdersCustNoChange(Sender: TField);
  155.     procedure ItemsQtyValidate(Sender: TField);
  156.     procedure OrdersFreightValidate(Sender: TField);
  157.     procedure ItemsPartNoValidate(Sender: TField);
  158.     procedure OrdersSaleDateValidate(Sender: TField);
  159.     procedure CustBeforeOpen(DataSet: TDataSet);
  160.     procedure OrdByCustCalcFields(DataSet: TDataSet);
  161.     procedure CustBeforePost(DataSet: TDataSet);
  162.     procedure OrdersAfterDelete(DataSet: TDataSet);
  163.     procedure OrdersBeforeEdit(DataSet: TDataSet);
  164.     procedure EditUpdateError(DataSet: TDataSet; E: EDatabaseError;
  165.       UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
  166.   private
  167.     PrevPartNo: Double;       { remembers Item's previous part# }
  168.     PrevQty: Longint;         { remembers Item's previous qty }
  169.     DeletingItems: Boolean;   { suppress totals calc. if deleting items }
  170.     FItemNo: Integer;
  171.     function DataDirectory: string;
  172.     procedure SetDatabaseAlias(AliasName: string);
  173.     procedure UpdateTotals;
  174.     procedure DeleteItems;
  175.   public
  176.     procedure UseLocalData;
  177.     procedure UseRemoteData;
  178.     function DataSetApplyUpdates(DataSet: TDataSet; Apply: Boolean): Boolean;
  179.   end;
  180.  
  181. function Confirm(Msg: string): Boolean;
  182.  
  183. var
  184.   MastData: TMastData;
  185.  
  186. implementation
  187.  
  188. {$R *.DFM}
  189.  
  190. { Utility Functions }
  191.  
  192. function Confirm(Msg: string): Boolean;
  193. begin
  194.   Result := MessageDlg(Msg, mtConfirmation, mbYesNoCancel, 0) = mrYes;
  195. end;
  196.  
  197. function TMastData.DataDirectory: string;
  198. begin
  199.   { Assume data is in ..\..\data relative to where we are }
  200.   Result := ExtractFilePath(ParamStr(0));
  201.   Result := ExpandFileName(Result + '..\..\DATA\');
  202. end;
  203.  
  204. { This function switches the database to a different alias }
  205.  
  206. procedure TMastData.SetDatabaseAlias(AliasName: string);
  207. begin
  208.   Screen.Cursor := crHourGlass;
  209.   try
  210.     Database.Close;
  211.     Database.AliasName := AliasName;
  212.     Database.Open;
  213.   finally
  214.     Screen.Cursor := crDefault;
  215.   end;
  216. end;
  217.  
  218. { Create an alias for the local data if needed, then swith the Database
  219.   to use it }
  220.  
  221. procedure TMastData.UseLocalData;
  222. var
  223.   DataDir: string;
  224. begin
  225.   { See if the target alias exists, if not then add it. }
  226.   if not Session.IsAlias('DBDEMOS') then
  227.   begin
  228.     DataDir := DataDirectory;
  229.     if not FileExists(DataDir+'ORDERS.DB') then
  230.       raise Exception.Create('Cannot locate Paradox data files');
  231.     Session.AddStandardAlias('DBDEMOS', DataDir, 'PARADOX');
  232.   end;
  233.   SetDatabaseAlias('DBDEMOS');
  234. end;
  235.  
  236. { Create an alias to point to the MastSQL.GDB file if needed }
  237.  
  238. procedure TMastData.UseRemoteData;
  239. var
  240.   Params: TStringList;
  241.   IBServer: HWND;         { Handle of IBSERVER, if it's running }
  242.   DataFile: string;
  243. begin
  244.  
  245.   { See if the alias exists.  if not then add it. }
  246.   if not Session.IsAlias('MASTSQL') then
  247.   begin
  248.     DataFile := DataDirectory + 'MASTSQL.GDB';
  249.     if not FileExists(DataFile) then
  250.       raise Exception.Create('Cannot locate Interbase data file: MASTSQL.GDB');
  251.     Params := TStringList.create;
  252.     try
  253.       Params.Values['SERVER NAME'] := DataFile;
  254.       Params.Values['USER NAME'] := 'SYSDBA';
  255.       Session.AddAlias('MASTSQL', 'INTRBASE', Params);
  256.     finally
  257.        Params.Free;
  258.     end;
  259.   end;
  260.   SetDatabaseAlias('MASTSQL');
  261. end;
  262.  
  263. { Event Handlers }
  264.  
  265. procedure TMastData.PartsBeforeOpen(DataSet: TDataSet);
  266. begin
  267.   Vendors.Open;
  268. end;
  269.  
  270. procedure TMastData.PartsCalcFields(DataSet: TDataSet);
  271. begin
  272.   PartsBackOrd.Value := PartsOnOrder.Value > PartsOnHand.Value;
  273. end;
  274.  
  275. procedure TMastData.PartsQueryCalcFields(DataSet: TDataSet);
  276. begin
  277.   PartsQueryBackOrd.Value := PartsOnOrder.Value > PartsOnHand.Value;
  278. end;
  279.  
  280. { If user cancels the updates to the orders table, cancel the updates to
  281.   the line items as well }
  282.  
  283. procedure TMastData.OrdersAfterCancel(DataSet: TDataSet);
  284. begin
  285.   Cust.CancelUpdates;
  286.   Parts.CancelUpdates;
  287.   Items.CancelUpdates;
  288.   Orders.CancelUpdates;
  289. end;
  290.  
  291. procedure TMastData.OrdersAfterDelete(DataSet: TDataSet);
  292. begin
  293.   Database.ApplyUpdates([Cust, Parts, Items, Orders]);
  294. end;
  295.  
  296. { Order Entry }
  297.  
  298. { Post new LastInvoiceDate to CUST table. }
  299.  
  300. procedure TMastData.OrdersAfterPost(DataSet: TDataSet);
  301.  
  302. begin
  303.   if Cust.Locate('CustNo', OrdersCustNo.Value, []) and
  304.     (CustLastInvoiceDate.Value < OrdersShipDate.Value) then
  305.   begin
  306.     Cust.Edit;
  307.     CustLastInvoiceDate.Value := OrdersShipDate.Value;
  308.     Cust.Post;
  309.   end;
  310.   Database.ApplyUpdates([Orders, Items, Parts, Cust]);
  311. end;
  312.  
  313. procedure TMastData.OrdersBeforeCancel(DataSet: TDataSet);
  314. begin
  315.   if (Orders.State = dsInsert) and not (Items.BOF and Items.EOF) then
  316.     if not Confirm('Cancel order being inserted and delete all line items?') then
  317.       Abort;
  318. end;
  319.  
  320. procedure TMastData.OrdersBeforeClose(DataSet: TDataSet);
  321. begin
  322.   Items.Close;
  323.   Emps.Close;
  324.   CustByOrd.Close;
  325. end;
  326.  
  327. procedure TMastData.OrdersBeforeDelete(DataSet: TDataSet);
  328. begin
  329.   if not Confirm('Delete order and line items?') then
  330.     Abort
  331.   else
  332.     DeleteItems;
  333. end;
  334.  
  335. procedure TMastData.OrdersBeforeInsert(DataSet: TDataSet);
  336. begin
  337.   if Orders.State in dsEditModes then
  338.   begin
  339.     if Confirm('An order is being processed.  Save changes and start a new one?') then
  340.       Orders.Post
  341.     else
  342.       Abort;
  343.   end;
  344.   FItemNo := 1;
  345. end;
  346.  
  347. procedure TMastData.OrdersBeforeOpen(DataSet: TDataSet);
  348. begin
  349.   CustByComp.Open;
  350.   CustByOrd.Open;
  351.   Cust.Open;
  352.   Emps.Open;
  353.   Items.Open;
  354. end;
  355.  
  356. { Calculate the order's tax totals and amount due }
  357.  
  358. procedure TMastData.OrdersCalcFields(DataSet: TDataSet);
  359. begin
  360.   OrdersTaxTotal.Value := OrdersItemsTotal.Value * (OrdersTaxRate.Value / 100);
  361.   OrdersAmountDue.Value := OrdersItemsTotal.Value + OrdersTaxTotal.Value +
  362.     OrdersFreight.Value - OrdersAmountPaid.Value;
  363. end;
  364.  
  365. { Inititializes the record values as a result of an Orders.Insert. }
  366.  
  367. procedure TMastData.OrdersNewRecord(DataSet: TDataSet);
  368. begin
  369.  
  370.   { Get the Next Order Value from the NextOrd Table }
  371.  
  372.   with NextOrd do
  373.   begin
  374.     Open;
  375.     try
  376.       Edit;
  377.       OrdersOrderNo.Value := NextOrdNewKey.Value;
  378.       NextOrdNewKey.Value := NextOrdNewKey.Value + 1;
  379.       Post;
  380.     finally
  381.       Close;
  382.     end;
  383.   end;
  384.   OrdersSaleDate.Value := Date;
  385.   OrdersShipVia.Value := 'UPS';
  386.   OrdersTerms.Value := 'net 30';
  387.   OrdersPaymentMethod.Value := 'Check';
  388.   OrdersItemsTotal.Value := 0;
  389.   OrdersTaxRate.Value := 0;
  390.   OrdersFreight.Value := 0;
  391.   OrdersAmountPaid.Value := 0;
  392. end;
  393.  
  394. procedure TMastData.ItemsAfterDelete(DataSet: TDataSet);
  395. begin
  396.   UpdateTotals;
  397. end;
  398.  
  399. { Update the order totals and the Parts table }
  400.  
  401. procedure TMastData.ItemsAfterPost(DataSet: TDataSet);
  402.  
  403.   { Reduce/increase Parts table's OnOrder field }
  404.  
  405.   procedure UpdateParts(PartNo: Double; Qty : Longint);
  406.   begin
  407.     if (PartNo > 0) and (Qty <> 0) then
  408.     try
  409.       if not Parts.Locate('PartNo', PartNo, []) then Abort;
  410.       Parts.Edit;
  411.       PartsOnOrder.Value := PartsOnOrder.Value + Qty;
  412.       Parts.Post;
  413.     except
  414.       on E: Exception do
  415.         ShowMessage(Format('Error updating parts table for PartNo: %d', [PartNo]));
  416.     end;
  417.   end;
  418.  
  419. begin
  420.   { Maintain next available item number }
  421.   Inc(FItemNo);
  422.   UpdateTotals;
  423.   if not ((PrevPartNo = ItemsPartNo.Value) and (PrevQty = ItemsQty.Value)) then
  424.   begin
  425.    { Reduce previous Part#'s OnOrder field by previous Qty }
  426.     UpdateParts(PrevPartNo, -PrevQty);
  427.    { Increase new Part#'s OnOrder field by previous Qty }
  428.     UpdateParts(ItemsPartNo.Value, ItemsQty.Value);
  429.   end;
  430. end;
  431.  
  432. {  When a change to the detail table affects a field in the master, always make
  433.   sure the master (orders) table is in edit or insert mode before allowing the
  434.   detail table to be modified. }
  435.  
  436. procedure TMastData.EnsureOrdersEdit(DataSet: TDataSet);
  437. begin
  438.   Orders.Edit;
  439. end;
  440.  
  441. { Remember previous PartNo and Qty for updating Parts.OnOrder after post.
  442.   When a change to the detail table affects a field in the master, always make
  443.   sure the master table is in edit or insert mode before allowing the
  444.   detail table to be modified. }
  445.  
  446. procedure TMastData.ItemsBeforeEdit(DataSet: TDataSet);
  447. begin
  448.   Orders.Edit;
  449.   PrevPartNo := ItemsPartNo.Value;
  450.   PrevQty := ItemsQty.Value;
  451. end;
  452.  
  453. { Make sure the Parts table opens before the Items table, since there are
  454.   lookups which depend on it. }
  455.  
  456. procedure TMastData.ItemsBeforeOpen(DataSet: TDataSet);
  457. begin
  458.   Parts.Open;
  459. end;
  460.  
  461. { Complete the item's key by initializing its NextItemNo field }
  462.  
  463. procedure TMastData.ItemsBeforePost(DataSet: TDataSet);
  464. begin
  465.   ItemsItemNo.Value := FItemNo;
  466. end;
  467.  
  468. { Lookup PartNo info for the item; calculate its extended price }
  469.  
  470. procedure TMastData.ItemsCalcFields(DataSet: TDataSet);
  471. begin
  472.   ItemsExtPrice.Value := ItemsQty.Value *
  473.     ItemsSellPrice.Value * (100 - ItemsDiscount.Value) / 100;
  474. end;
  475.  
  476. { New item. Zero the "prev" buckets, initialize the key }
  477.  
  478. procedure TMastData.ItemsNewRecord(DataSet: TDataSet);
  479. begin
  480.   PrevPartNo := 0;
  481.   PrevQty := 0;
  482.   ItemsOrderNo.Value := OrdersOrderNo.Value;
  483.   ItemsQty.Value := 1;
  484.   ItemsDiscount.Value := 0;
  485. end;
  486.  
  487. { Concatenate last name + first name for the order's SoldBy DBLookupCombo }
  488.  
  489. procedure TMastData.EmpsCalcFields(DataSet: TDataSet);
  490. begin
  491.   EmpsFullName.Value := Format('%s, %s', [EmpsLastName.Value, EmpsFirstName.Value]);
  492. end;
  493.  
  494. procedure TMastData.DeleteItems;
  495. begin
  496.   DeletingItems := True;    { suppress recalc of totals during delete }
  497.   Items.DisableControls;    { for faster table traversal }
  498.   try
  499.     Items.First;
  500.     while not Items.EOF do Items.Delete;
  501.   finally
  502.     DeletingItems := False;
  503.     Items.EnableControls;   { always re-enable controls after disabling }
  504.   end;
  505. end;
  506.  
  507. { Steps through Items and gathers sum of ExtPrice. After OrdersItemsTotal
  508.   is calculated, OrdersCalcFields is automatically called (which
  509.   updates other calculated fields. }
  510.   
  511. procedure TMastData.UpdateTotals;
  512. var
  513.   TempTotal: Extended;
  514.   PrevRecord: TBookmark;
  515. begin
  516.   if DeletingItems then Exit;        { don't calculate if deleting all items }
  517.   PrevRecord := Items.GetBookmark;    { returns nil if table is empty }
  518.   try
  519.     Items.DisableControls;
  520.     Items.First;
  521.     TempTotal := 0;            { use temp for efficiency }
  522.     while not Items.EOF do
  523.     begin
  524.       TempTotal := TempTotal + ItemsExtPrice.Value;
  525.       Items.Next;
  526.     end;
  527.     OrdersItemsTotal.Value := TempTotal;
  528.   finally
  529.      Items.EnableControls;
  530.      if PrevRecord <> nil then
  531.      begin
  532.        Items.GoToBookmark(PrevRecord);
  533.        Items.FreeBookmark(PrevRecord);
  534.      end;
  535.   end;
  536. end;
  537.  
  538. procedure TMastData.OrdersCustNoChange(Sender: TField);
  539. begin
  540.   OrdersShipToContact.Value := '';
  541.   OrdersShipToPhone.Value := '';
  542.   OrdersShipToAddr1.Value := '';
  543.   OrdersShipToAddr2.Value := '';
  544.   OrdersShipToCity.Value := '';
  545.   OrdersShipToState.Value := '';
  546.   OrdersShipToZip.Value := '';
  547.   OrdersShipToCountry.Value := '';
  548.   OrdersTaxRate.Value := Cust.Lookup('CustNo', OrdersCustNo.Value, 'TaxRate');
  549. end;
  550.  
  551. { Alternatively, could set the Qty field's Min and Max values in code
  552.   or in the Object Inspector. }
  553.  
  554. procedure TMastData.ItemsQtyValidate(Sender: TField);
  555. begin
  556.   if ItemsQty.Value < 1 then
  557.     raise Exception.Create('Must specify quantity');
  558. end;
  559.  
  560. { Alternatively, could set the Freight field's Min and Max values in code
  561.   or in the Object Inspector. }
  562.  
  563. procedure TMastData.OrdersFreightValidate(Sender: TField);
  564. begin
  565.   if OrdersFreight.Value < 0 then
  566.     raise Exception.Create('Freight cannot be less than zero');
  567. end;
  568.  
  569. procedure TMastData.ItemsPartNoValidate(Sender: TField);
  570. begin
  571.   if not Parts.Locate('PartNo', ItemsPartNo.Value, []) then
  572.     raise Exception.Create('You must specify a valid PartNo');
  573. end;
  574.  
  575. procedure TMastData.OrdersSaleDateValidate(Sender: TField);
  576. begin
  577.   if OrdersSaleDate.Value > Now then
  578.     raise Exception.Create('Cannot enter a future date');
  579. end;
  580.  
  581. { Browse Customers }
  582.  
  583. procedure TMastData.CustBeforeOpen(DataSet: TDataSet);
  584. begin
  585.   OrdByCust.Open;
  586. end;
  587.  
  588. procedure TMastData.OrdByCustCalcFields(DataSet: TDataSet);
  589. begin
  590.   OrdByCustAmountDue.Value := OrdByCustItemsTotal.Value +
  591.     OrdByCustItemsTotal.Value * OrdByCustTaxRate.Value / 100 +
  592.     OrdByCustFreight.Value - OrdByCustAmountPaid.Value;
  593. end;
  594.  
  595. { Get the next available customer number from the NextCust table }
  596.  
  597. procedure TMastData.CustBeforePost(DataSet: TDataSet);
  598. begin
  599.   if Cust.State = dsInsert then
  600.     with NextCust do
  601.     begin
  602.       Open;
  603.       try
  604.         Edit;
  605.         CustCustNo.Value := NextCustNewCust.Value;
  606.         NextCustNewCust.Value := NextCustNewCust.Value + 1;
  607.         Post;
  608.       finally
  609.         Close;
  610.       end;
  611.     end;
  612. end;
  613.  
  614. function TMastData.DataSetApplyUpdates(DataSet: TDataSet; Apply: Boolean): Boolean;
  615. var
  616.   MsgResult: Integer;
  617. begin
  618.   Result := True;
  619.   with DataSet do
  620.   begin
  621.     if (State in dsEditModes) or UpdatesPending then
  622.     begin
  623.       if Apply then
  624.       begin
  625.         Database.ApplyUpdates([DataSet as TDBDataSet]);
  626.        { Always call CancelUpdates to remove any discard changes }
  627.         DataSet.CancelUpdates;
  628.       end
  629.       else
  630.       begin
  631.         if (MessageDlg('Unsaved changes, exit anyway?', mtConfirmation,
  632.           [mbYes, mbCancel], 0) = mrYes) then
  633.           DataSet.CancelUpdates
  634.         else
  635.           Result := False;
  636.       end;
  637.     end;
  638.   end;
  639. end;
  640.  
  641. { Determine the next available ItemNo for this order }
  642.  
  643. procedure TMastData.OrdersBeforeEdit(DataSet: TDataSet);
  644. begin
  645.   LastItemQuery.Close;
  646.   LastItemQuery.Open;
  647.   { SQL servers return Null for some aggregates if no items are present }
  648.   with LastItemQuery.Fields[0] do
  649.     if IsNull then FItemNo := 1
  650.     else FItemNo := AsInteger + 1;
  651. end;
  652.  
  653. procedure TMastData.EditUpdateError(DataSet: TDataSet; E: EDatabaseError;
  654.   UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
  655. var
  656.   Key: Variant;
  657. const
  658.   UpdErrMsg = '%s.'#13#10'Discard the edits to %S %S and continue updating?';
  659. begin
  660.   if UpdateKind = ukDelete then
  661.     Key := Dataset.Fields[0].OldValue else
  662.     Key := Dataset.Fields[0].NewValue;
  663.   if MessageDlg(Format(UpdErrMsg, [E.Message, DataSet.Fields[0].DisplayLabel, Key]),
  664.     mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
  665.     UpdateAction := uaSkip else
  666.     UpdateAction := uaAbort;
  667. end;
  668.  
  669. end.
  670.  
  671.