home *** CD-ROM | disk | FTP | other *** search
/ DOS/V Power Report 1996 August / VPR9608A.BIN / del20try / install / data.z / DATAMOD.PAS < prev    next >
Pascal/Delphi Source File  |  1996-05-08  |  20KB  |  682 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. { Verify that the Local Interbase server is running, then create an
  237.   alias to point to the MastSQL.GDB file if needed }
  238.  
  239. procedure TMastData.UseRemoteData;
  240. const
  241.   SNoIBServer = 'Cannot view remote data; the Local InterBase Server is '+
  242.    'not running.  Launch the Server and then try again';
  243. var
  244.   Params: TStringList;
  245.   IBServer: HWND;         { Handle of IBSERVER, if it's running }
  246.   DataFile: string;
  247. begin
  248.  
  249.   { First, see if the Local InterBase Server Manager is running by trying
  250.     to locate its window.  if it isn't running, then abort the process. }
  251.  
  252.   IBServer := FindWindow(NIL, 'InterBase Server');
  253.   if IBServer = 0 then
  254.     raise Exception.Create(SNoIBServer);
  255.  
  256.   { See if the alias exists.  if not then add it. }
  257.   if not Session.IsAlias('MASTSQL') then
  258.   begin
  259.     DataFile := DataDirectory + 'MASTSQL.GDB';
  260.     if not FileExists(DataFile) then
  261.       raise Exception.Create('Cannot locate Interbase data file: MASTSQL.GDB');
  262.     Params := TStringList.create;
  263.     try
  264.       Params.Values['SERVER NAME'] := DataFile;
  265.       Params.Values['USER NAME'] := 'SYSDBA';
  266.       Session.AddAlias('MASTSQL', 'INTRBASE', Params);
  267.     finally
  268.        Params.Free;
  269.     end;
  270.   end;
  271.   SetDatabaseAlias('MASTSQL');
  272. end;
  273.  
  274. { Event Handlers }
  275.  
  276. procedure TMastData.PartsBeforeOpen(DataSet: TDataSet);
  277. begin
  278.   Vendors.Open;
  279. end;
  280.  
  281. procedure TMastData.PartsCalcFields(DataSet: TDataSet);
  282. begin
  283.   PartsBackOrd.Value := PartsOnOrder.Value > PartsOnHand.Value;
  284. end;
  285.  
  286. procedure TMastData.PartsQueryCalcFields(DataSet: TDataSet);
  287. begin
  288.   PartsQueryBackOrd.Value := PartsOnOrder.Value > PartsOnHand.Value;
  289. end;
  290.  
  291. { If user cancels the updates to the orders table, cancel the updates to
  292.   the line items as well }
  293.  
  294. procedure TMastData.OrdersAfterCancel(DataSet: TDataSet);
  295. begin
  296.   Cust.CancelUpdates;
  297.   Parts.CancelUpdates;
  298.   Items.CancelUpdates;
  299.   Orders.CancelUpdates;
  300. end;
  301.  
  302. procedure TMastData.OrdersAfterDelete(DataSet: TDataSet);
  303. begin
  304.   Database.ApplyUpdates([Cust, Parts, Items, Orders]);
  305. end;
  306.  
  307. { Order Entry }
  308.  
  309. { Post new LastInvoiceDate to CUST table. }
  310.  
  311. procedure TMastData.OrdersAfterPost(DataSet: TDataSet);
  312.  
  313. begin
  314.   if Cust.Locate('CustNo', OrdersCustNo.Value, []) and
  315.     (CustLastInvoiceDate.Value < OrdersShipDate.Value) then
  316.   begin
  317.     Cust.Edit;
  318.     CustLastInvoiceDate.Value := OrdersShipDate.Value;
  319.     Cust.Post;
  320.   end;
  321.   Database.ApplyUpdates([Orders, Items, Parts, Cust]);
  322. end;
  323.  
  324. procedure TMastData.OrdersBeforeCancel(DataSet: TDataSet);
  325. begin
  326.   if (Orders.State = dsInsert) and not (Items.BOF and Items.EOF) then
  327.     if not Confirm('Cancel order being inserted and delete all line items?') then
  328.       Abort;
  329. end;
  330.  
  331. procedure TMastData.OrdersBeforeClose(DataSet: TDataSet);
  332. begin
  333.   Items.Close;
  334.   Emps.Close;
  335.   CustByOrd.Close;
  336. end;
  337.  
  338. procedure TMastData.OrdersBeforeDelete(DataSet: TDataSet);
  339. begin
  340.   if not Confirm('Delete order and line items?') then
  341.     Abort
  342.   else
  343.     DeleteItems;
  344. end;
  345.  
  346. procedure TMastData.OrdersBeforeInsert(DataSet: TDataSet);
  347. begin
  348.   if Orders.State in dsEditModes then
  349.   begin
  350.     if Confirm('An order is being processed.  Save changes and start a new one?') then
  351.       Orders.Post
  352.     else
  353.       Abort;
  354.   end;
  355.   FItemNo := 1;
  356. end;
  357.  
  358. procedure TMastData.OrdersBeforeOpen(DataSet: TDataSet);
  359. begin
  360.   CustByComp.Open;
  361.   CustByOrd.Open;
  362.   Cust.Open;
  363.   Emps.Open;
  364.   Items.Open;
  365. end;
  366.  
  367. { Calculate the order's tax totals and amount due }
  368.  
  369. procedure TMastData.OrdersCalcFields(DataSet: TDataSet);
  370. begin
  371.   OrdersTaxTotal.Value := OrdersItemsTotal.Value * (OrdersTaxRate.Value / 100);
  372.   OrdersAmountDue.Value := OrdersItemsTotal.Value + OrdersTaxTotal.Value +
  373.     OrdersFreight.Value - OrdersAmountPaid.Value;
  374. end;
  375.  
  376. { Inititializes the record values as a result of an Orders.Insert. }
  377.  
  378. procedure TMastData.OrdersNewRecord(DataSet: TDataSet);
  379. begin
  380.  
  381.   { Get the Next Order Value from the NextOrd Table }
  382.  
  383.   with NextOrd do
  384.   begin
  385.     Open;
  386.     try
  387.       Edit;
  388.       OrdersOrderNo.Value := NextOrdNewKey.Value;
  389.       NextOrdNewKey.Value := NextOrdNewKey.Value + 1;
  390.       Post;
  391.     finally
  392.       Close;
  393.     end;
  394.   end;
  395.   OrdersSaleDate.Value := Date;
  396.   OrdersShipVia.Value := 'FedEx';
  397.   OrdersTerms.Value := 'Net 30';
  398.   OrdersPaymentMethod.Value := 'AnEx';
  399.   OrdersItemsTotal.Value := 0;
  400.   OrdersTaxRate.Value := 0;
  401.   OrdersFreight.Value := 0;
  402.   OrdersAmountPaid.Value := 0;
  403. end;
  404.  
  405. procedure TMastData.ItemsAfterDelete(DataSet: TDataSet);
  406. begin
  407.   UpdateTotals;
  408. end;
  409.  
  410. { Update the order totals and the Parts table }
  411.  
  412. procedure TMastData.ItemsAfterPost(DataSet: TDataSet);
  413.  
  414.   { Reduce/increase Parts table's OnOrder field }
  415.  
  416.   procedure UpdateParts(PartNo: Double; Qty : Longint);
  417.   begin
  418.     if (PartNo > 0) and (Qty <> 0) then
  419.     try
  420.       if not Parts.Locate('PartNo', PartNo, []) then Abort;
  421.       Parts.Edit;
  422.       PartsOnOrder.Value := PartsOnOrder.Value + Qty;
  423.       Parts.Post;
  424.     except
  425.       on E: Exception do
  426.         ShowMessage(Format('Error updating parts table for PartNo: %d', [PartNo]));
  427.     end;
  428.   end;
  429.  
  430. begin
  431.   { Maintain next available item number }
  432.   Inc(FItemNo);
  433.   UpdateTotals;
  434.   if not ((PrevPartNo = ItemsPartNo.Value) and (PrevQty = ItemsQty.Value)) then
  435.   begin
  436.    { Reduce previous Part#'s OnOrder field by previous Qty }
  437.     UpdateParts(PrevPartNo, -PrevQty);
  438.    { Increase new Part#'s OnOrder field by previous Qty }
  439.     UpdateParts(ItemsPartNo.Value, ItemsQty.Value);
  440.   end;
  441. end;
  442.  
  443. {  When a change to the detail table affects a field in the master, always make
  444.   sure the master (orders) table is in edit or insert mode before allowing the
  445.   detail table to be modified. }
  446.  
  447. procedure TMastData.EnsureOrdersEdit(DataSet: TDataSet);
  448. begin
  449.   Orders.Edit;
  450. end;
  451.  
  452. { Remember previous PartNo and Qty for updating Parts.OnOrder after post.
  453.   When a change to the detail table affects a field in the master, always make
  454.   sure the master table is in edit or insert mode before allowing the
  455.   detail table to be modified. }
  456.  
  457. procedure TMastData.ItemsBeforeEdit(DataSet: TDataSet);
  458. begin
  459.   Orders.Edit;
  460.   PrevPartNo := ItemsPartNo.Value;
  461.   PrevQty := ItemsQty.Value;
  462. end;
  463.  
  464. { Make sure the Parts table opens before the Items table, since there are
  465.   lookups which depend on it. }
  466.  
  467. procedure TMastData.ItemsBeforeOpen(DataSet: TDataSet);
  468. begin
  469.   Parts.Open;
  470. end;
  471.  
  472. { Complete the item's key by initializing its NextItemNo field }
  473.  
  474. procedure TMastData.ItemsBeforePost(DataSet: TDataSet);
  475. begin
  476.   ItemsItemNo.Value := FItemNo;
  477. end;
  478.  
  479. { Lookup PartNo info for the item; calculate its extended price }
  480.  
  481. procedure TMastData.ItemsCalcFields(DataSet: TDataSet);
  482. begin
  483.   ItemsExtPrice.Value := ItemsQty.Value *
  484.     ItemsSellPrice.Value * (100 - ItemsDiscount.Value) / 100;
  485. end;
  486.  
  487. { New item. Zero the "prev" buckets, initialize the key }
  488.  
  489. procedure TMastData.ItemsNewRecord(DataSet: TDataSet);
  490. begin
  491.   PrevPartNo := 0;
  492.   PrevQty := 0;
  493.   ItemsOrderNo.Value := OrdersOrderNo.Value;
  494.   ItemsQty.Value := 1;
  495.   ItemsDiscount.Value := 0;
  496. end;
  497.  
  498. { Concatenate last name + first name for the order's SoldBy DBLookupCombo }
  499.  
  500. procedure TMastData.EmpsCalcFields(DataSet: TDataSet);
  501. begin
  502.   EmpsFullName.Value := Format('%s, %s', [EmpsLastName.Value, EmpsFirstName.Value]);
  503. end;
  504.  
  505. procedure TMastData.DeleteItems;
  506. begin
  507.   DeletingItems := True;    { suppress recalc of totals during delete }
  508.   Items.DisableControls;    { for faster table traversal }
  509.   try
  510.     Items.First;
  511.     while not Items.EOF do Items.Delete;
  512.   finally
  513.     DeletingItems := False;
  514.     Items.EnableControls;   { always re-enable controls after disabling }
  515.   end;
  516. end;
  517.  
  518. { Steps through Items and gathers sum of ExtPrice. After OrdersItemsTotal
  519.   is calculated, OrdersCalcFields is automatically called (which
  520.   updates other calculated fields. }
  521.   
  522. procedure TMastData.UpdateTotals;
  523. var
  524.   TempTotal: Extended;
  525.   PrevRecord: TBookmark;
  526. begin
  527.   if DeletingItems then Exit;        { don't calculate if deleting all items }
  528.   PrevRecord := Items.GetBookmark;    { returns nil if table is empty }
  529.   try
  530.     Items.DisableControls;
  531.     Items.First;
  532.     TempTotal := 0;            { use temp for efficiency }
  533.     while not Items.EOF do
  534.     begin
  535.       TempTotal := TempTotal + ItemsExtPrice.Value;
  536.       Items.Next;
  537.     end;
  538.     OrdersItemsTotal.Value := TempTotal;
  539.   finally
  540.      Items.EnableControls;
  541.      if PrevRecord <> nil then
  542.      begin
  543.        Items.GoToBookmark(PrevRecord);
  544.        Items.FreeBookmark(PrevRecord);
  545.      end;
  546.   end;
  547. end;
  548.  
  549. procedure TMastData.OrdersCustNoChange(Sender: TField);
  550. begin
  551.   OrdersShipToContact.Value := '';
  552.   OrdersShipToPhone.Value := '';
  553.   OrdersShipToAddr1.Value := '';
  554.   OrdersShipToAddr2.Value := '';
  555.   OrdersShipToCity.Value := '';
  556.   OrdersShipToState.Value := '';
  557.   OrdersShipToZip.Value := '';
  558.   OrdersShipToCountry.Value := '';
  559.   OrdersTaxRate.Value := Cust.Lookup('CustNo', OrdersCustNo.Value, 'TaxRate');
  560. end;
  561.  
  562. { Alternatively, could set the Qty field's Min and Max values in code
  563.   or in the Object Inspector. }
  564.  
  565. procedure TMastData.ItemsQtyValidate(Sender: TField);
  566. begin
  567.   if ItemsQty.Value < 1 then
  568.     raise Exception.Create('Must specify quantity');
  569. end;
  570.  
  571. { Alternatively, could set the Freight field's Min and Max values in code
  572.   or in the Object Inspector. }
  573.  
  574. procedure TMastData.OrdersFreightValidate(Sender: TField);
  575. begin
  576.   if OrdersFreight.Value < 0 then
  577.     raise Exception.Create('Freight cannot be less than zero');
  578. end;
  579.  
  580. procedure TMastData.ItemsPartNoValidate(Sender: TField);
  581. begin
  582.   if not Parts.Locate('PartNo', ItemsPartNo.Value, []) then
  583.     raise Exception.Create('You must specify a valid PartNo');
  584. end;
  585.  
  586. procedure TMastData.OrdersSaleDateValidate(Sender: TField);
  587. begin
  588.   if OrdersSaleDate.Value > Now then
  589.     raise Exception.Create('Cannot enter a future date');
  590. end;
  591.  
  592. { Browse Customers }
  593.  
  594. procedure TMastData.CustBeforeOpen(DataSet: TDataSet);
  595. begin
  596.   OrdByCust.Open;
  597. end;
  598.  
  599. procedure TMastData.OrdByCustCalcFields(DataSet: TDataSet);
  600. begin
  601.   OrdByCustAmountDue.Value := OrdByCustItemsTotal.Value +
  602.     OrdByCustItemsTotal.Value * OrdByCustTaxRate.Value / 100 +
  603.     OrdByCustFreight.Value - OrdByCustAmountPaid.Value;
  604. end;
  605.  
  606. { Get the next available customer number from the NextCust table }
  607.  
  608. procedure TMastData.CustBeforePost(DataSet: TDataSet);
  609. begin
  610.   if Cust.State = dsInsert then
  611.     with NextCust do
  612.     begin
  613.       Open;
  614.       try
  615.         Edit;
  616.         CustCustNo.Value := NextCustNewCust.Value;
  617.         NextCustNewCust.Value := NextCustNewCust.Value + 1;
  618.         Post;
  619.       finally
  620.         Close;
  621.       end;
  622.     end;
  623. end;
  624.  
  625. function TMastData.DataSetApplyUpdates(DataSet: TDataSet; Apply: Boolean): Boolean;
  626. var
  627.   MsgResult: Integer;
  628. begin
  629.   Result := True;
  630.   with DataSet do
  631.   begin
  632.     if (State in dsEditModes) or UpdatesPending then
  633.     begin
  634.       if Apply then
  635.       begin
  636.         Database.ApplyUpdates([DataSet as TDBDataSet]);
  637.        { Always call CancelUpdates to remove any discard changes }
  638.         DataSet.CancelUpdates;
  639.       end
  640.       else
  641.       begin
  642.         if (MessageDlg('Unsaved changes, exit anyway?', mtConfirmation,
  643.           [mbYes, mbCancel], 0) = mrYes) then
  644.           DataSet.CancelUpdates
  645.         else
  646.           Result := False;
  647.       end;
  648.     end;
  649.   end;
  650. end;
  651.  
  652. { Determine the next available ItemNo for this order }
  653.  
  654. procedure TMastData.OrdersBeforeEdit(DataSet: TDataSet);
  655. begin
  656.   LastItemQuery.Close;
  657.   LastItemQuery.Open;
  658.   { SQL servers return Null for some aggregates if no items are present }
  659.   with LastItemQuery.Fields[0] do
  660.     if IsNull then FItemNo := 1
  661.     else FItemNo := AsInteger + 1;
  662. end;
  663.  
  664. procedure TMastData.EditUpdateError(DataSet: TDataSet; E: EDatabaseError;
  665.   UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
  666. var
  667.   Key: Variant;
  668. const
  669.   UpdErrMsg = '%s.'#13#10'Discard the edits to %S %S and continue updating?';
  670. begin
  671.   if UpdateKind = ukDelete then
  672.     Key := Dataset.Fields[0].OldValue else
  673.     Key := Dataset.Fields[0].NewValue;
  674.   if MessageDlg(Format(UpdErrMsg, [E.Message, DataSet.Fields[0].DisplayLabel, Key]),
  675.     mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
  676.     UpdateAction := uaSkip else
  677.     UpdateAction := uaAbort;
  678. end;
  679.  
  680. end.
  681.  
  682.