home *** CD-ROM | disk | FTP | other *** search
/ DOS/V Power Report 1997 August / VPR9708A.ISO / D3TRIAL / INSTALL / DATA.Z / DATAMOD.PAS < prev    next >
Pascal/Delphi Source File  |  1997-05-13  |  21KB  |  710 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.     CustByLastInvQuery: TQuery;
  134.     CustByLastInvQueryCustNo: TFloatField;
  135.     CustByLastInvQueryCompany: TStringField;
  136.     CustByLastInvQueryAddr1: TStringField;
  137.     CustByLastInvQueryAddr2: TStringField;
  138.     CustByLastInvQueryCity: TStringField;
  139.     CustByLastInvQueryState: TStringField;
  140.     CustByLastInvQueryZip: TStringField;
  141.     CustByLastInvQueryCountry: TStringField;
  142.     CustByLastInvQueryPhone: TStringField;
  143.     CustByLastInvQueryFAX: TStringField;
  144.     CustByLastInvQueryTaxRate: TFloatField;
  145.     CustByLastInvQueryContact: TStringField;
  146.     CustByLastInvQueryLastInvoiceDate: TDateTimeField;
  147.     OrdersByDateQuery: TQuery;
  148.     OrdersSalesPerson: TStringField;
  149.     OrdersByDateQueryOrderNo: TFloatField;
  150.     OrdersByDateQueryCustNo: TFloatField;
  151.     OrdersByDateQuerySaleDate: TDateTimeField;
  152.     OrdersByDateQueryShipDate: TDateTimeField;
  153.     OrdersByDateQueryEmpNo: TIntegerField;
  154.     OrdersByDateQueryShipToContact: TStringField;
  155.     OrdersByDateQueryShipToAddr1: TStringField;
  156.     OrdersByDateQueryShipToAddr2: TStringField;
  157.     OrdersByDateQueryShipToCity: TStringField;
  158.     OrdersByDateQueryShipToState: TStringField;
  159.     OrdersByDateQueryShipToZip: TStringField;
  160.     OrdersByDateQueryShipToCountry: TStringField;
  161.     OrdersByDateQueryShipToPhone: TStringField;
  162.     OrdersByDateQueryShipVIA: TStringField;
  163.     OrdersByDateQueryPO: TStringField;
  164.     OrdersByDateQueryTerms: TStringField;
  165.     OrdersByDateQueryPaymentMethod: TStringField;
  166.     OrdersByDateQueryItemsTotal: TCurrencyField;
  167.     OrdersByDateQueryTaxRate: TFloatField;
  168.     OrdersByDateQueryFreight: TCurrencyField;
  169.     OrdersByDateQueryAmountPaid: TCurrencyField;
  170.     OrdersByDateQueryCompany: TStringField;
  171.     procedure PartsBeforeOpen(DataSet: TDataSet);
  172.     procedure PartsCalcFields(DataSet: TDataSet);
  173.     procedure PartsQueryCalcFields(DataSet: TDataSet);
  174.     procedure OrdersAfterCancel(DataSet: TDataSet);
  175.     procedure OrdersAfterPost(DataSet: TDataSet);
  176.     procedure OrdersBeforeCancel(DataSet: TDataSet);
  177.     procedure OrdersBeforeClose(DataSet: TDataSet);
  178.     procedure OrdersBeforeDelete(DataSet: TDataSet);
  179.     procedure OrdersBeforeInsert(DataSet: TDataSet);
  180.     procedure OrdersBeforeOpen(DataSet: TDataSet);
  181.     procedure OrdersCalcFields(DataSet: TDataSet);
  182.     procedure OrdersNewRecord(DataSet: TDataSet);
  183.     procedure ItemsAfterDelete(DataSet: TDataSet);
  184.     procedure ItemsAfterPost(DataSet: TDataSet);
  185.     procedure EnsureOrdersEdit(DataSet: TDataSet);
  186.     procedure ItemsBeforeEdit(DataSet: TDataSet);
  187.     procedure ItemsBeforeOpen(DataSet: TDataSet);
  188.     procedure ItemsBeforePost(DataSet: TDataSet);
  189.     procedure ItemsCalcFields(DataSet: TDataSet);
  190.     procedure ItemsNewRecord(DataSet: TDataSet);
  191.     procedure EmpsCalcFields(DataSet: TDataSet);
  192.     procedure OrdersCustNoChange(Sender: TField);
  193.     procedure ItemsQtyValidate(Sender: TField);
  194.     procedure OrdersFreightValidate(Sender: TField);
  195.     procedure ItemsPartNoValidate(Sender: TField);
  196.     procedure OrdersSaleDateValidate(Sender: TField);
  197.     procedure CustBeforeOpen(DataSet: TDataSet);
  198.     procedure OrdByCustCalcFields(DataSet: TDataSet);
  199.     procedure CustBeforePost(DataSet: TDataSet);
  200.     procedure OrdersAfterDelete(DataSet: TDataSet);
  201.     procedure OrdersBeforeEdit(DataSet: TDataSet);
  202.     procedure EditUpdateError(DataSet: TDataSet; E: EDatabaseError;
  203.       UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
  204.   private
  205.     PrevPartNo: Double;       { remembers Item's previous part# }
  206.     PrevQty: Longint;         { remembers Item's previous qty }
  207.     DeletingItems: Boolean;   { suppress totals calc. if deleting items }
  208.     FItemNo: Integer;
  209.     function DataDirectory: string;
  210.     procedure SetDatabaseAlias(AliasName: string);
  211.     procedure UpdateTotals;
  212.     procedure DeleteItems;
  213.   public
  214.     procedure UseLocalData;
  215.     procedure UseRemoteData;
  216.     function DataSetApplyUpdates(DataSet: TDataSet; Apply: Boolean): Boolean;
  217.   end;
  218.  
  219. function Confirm(Msg: string): Boolean;
  220.  
  221. var
  222.   MastData: TMastData;
  223.  
  224. implementation
  225.  
  226. {$R *.DFM}
  227.  
  228. { Utility Functions }
  229.  
  230. function Confirm(Msg: string): Boolean;
  231. begin
  232.   Result := MessageDlg(Msg, mtConfirmation, mbYesNoCancel, 0) = mrYes;
  233. end;
  234.  
  235. function TMastData.DataDirectory: string;
  236. begin
  237.   { Assume data is in ..\..\data relative to where we are }
  238.   Result := ExtractFilePath(ParamStr(0));
  239.   Result := ExpandFileName(Result + '..\..\DATA\');
  240. end;
  241.  
  242. { This function switches the database to a different alias }
  243.  
  244. procedure TMastData.SetDatabaseAlias(AliasName: string);
  245. begin
  246.   Screen.Cursor := crHourGlass;
  247.   try
  248.     Database.Close;
  249.     Database.AliasName := AliasName;
  250.     Database.Open;
  251.   finally
  252.     Screen.Cursor := crDefault;
  253.   end;
  254. end;
  255.  
  256. { Create an alias for the local data if needed, then swith the Database
  257.   to use it }
  258.  
  259. procedure TMastData.UseLocalData;
  260. var
  261.   DataDir: string;
  262. begin
  263.   { See if the target alias exists, if not then add it. }
  264.   if not Session.IsAlias('DBDEMOS') then
  265.   begin
  266.     DataDir := DataDirectory;
  267.     if not FileExists(DataDir+'ORDERS.DB') then
  268.       raise Exception.Create('Cannot locate Paradox data files');
  269.     Session.AddStandardAlias('DBDEMOS', DataDir, 'PARADOX');
  270.   end;
  271.   SetDatabaseAlias('DBDEMOS');
  272. end;
  273.  
  274. { Create an alias to point to the MastSQL.GDB file if needed }
  275.  
  276. procedure TMastData.UseRemoteData;
  277. var
  278.   Params: TStringList;
  279.   DataFile: string;
  280. begin
  281.  
  282.   { See if the alias exists.  if not then add it. }
  283.   if not Session.IsAlias('MASTSQL') then
  284.   begin
  285.     DataFile := DataDirectory + 'MASTSQL.GDB';
  286.     if not FileExists(DataFile) then
  287.       raise Exception.Create('Cannot locate Interbase data file: MASTSQL.GDB');
  288.     Params := TStringList.create;
  289.     try
  290.       Params.Values['SERVER NAME'] := DataFile;
  291.       Params.Values['USER NAME'] := 'SYSDBA';
  292.       Session.AddAlias('MASTSQL', 'INTRBASE', Params);
  293.     finally
  294.        Params.Free;
  295.     end;
  296.   end;
  297.   SetDatabaseAlias('MASTSQL');
  298. end;
  299.  
  300. { Event Handlers }
  301.  
  302. procedure TMastData.PartsBeforeOpen(DataSet: TDataSet);
  303. begin
  304.   Vendors.Open;
  305. end;
  306.  
  307. procedure TMastData.PartsCalcFields(DataSet: TDataSet);
  308. begin
  309.   PartsBackOrd.Value := PartsOnOrder.Value > PartsOnHand.Value;
  310. end;
  311.  
  312. procedure TMastData.PartsQueryCalcFields(DataSet: TDataSet);
  313. begin
  314.   PartsQueryBackOrd.Value := PartsOnOrder.Value > PartsOnHand.Value;
  315. end;
  316.  
  317. { If user cancels the updates to the orders table, cancel the updates to
  318.   the line items as well }
  319.  
  320. procedure TMastData.OrdersAfterCancel(DataSet: TDataSet);
  321. begin
  322.   Cust.CancelUpdates;
  323.   Parts.CancelUpdates;
  324.   Items.CancelUpdates;
  325.   Orders.CancelUpdates;
  326. end;
  327.  
  328. procedure TMastData.OrdersAfterDelete(DataSet: TDataSet);
  329. begin
  330.   Database.ApplyUpdates([Cust, Parts, Items, Orders]);
  331. end;
  332.  
  333. { Order Entry }
  334.  
  335. { Post new LastInvoiceDate to CUST table. }
  336.  
  337. procedure TMastData.OrdersAfterPost(DataSet: TDataSet);
  338.  
  339. begin
  340.   if Cust.Locate('CustNo', OrdersCustNo.Value, []) and
  341.     (CustLastInvoiceDate.Value < OrdersShipDate.Value) then
  342.   begin
  343.     Cust.Edit;
  344.     CustLastInvoiceDate.Value := OrdersShipDate.Value;
  345.     Cust.Post;
  346.   end;
  347.   Database.ApplyUpdates([Orders, Items, Parts, Cust]);
  348. end;
  349.  
  350. procedure TMastData.OrdersBeforeCancel(DataSet: TDataSet);
  351. begin
  352.   if (Orders.State = dsInsert) and not (Items.BOF and Items.EOF) then
  353.     if not Confirm('Cancel order being inserted and delete all line items?') then
  354.       Abort;
  355. end;
  356.  
  357. procedure TMastData.OrdersBeforeClose(DataSet: TDataSet);
  358. begin
  359.   Items.Close;
  360.   Emps.Close;
  361.   CustByOrd.Close;
  362. end;
  363.  
  364. procedure TMastData.OrdersBeforeDelete(DataSet: TDataSet);
  365. begin
  366.   if not Confirm('Delete order and line items?') then
  367.     Abort
  368.   else
  369.     DeleteItems;
  370. end;
  371.  
  372. procedure TMastData.OrdersBeforeInsert(DataSet: TDataSet);
  373. begin
  374.   if Orders.State in dsEditModes then
  375.   begin
  376.     if Confirm('An order is being processed.  Save changes and start a new one?') then
  377.       Orders.Post
  378.     else
  379.       Abort;
  380.   end;
  381.   FItemNo := 1;
  382. end;
  383.  
  384. procedure TMastData.OrdersBeforeOpen(DataSet: TDataSet);
  385. begin
  386.   CustByComp.Open;
  387.   CustByOrd.Open;
  388.   Cust.Open;
  389.   Emps.Open;
  390.   Items.Open;
  391. end;
  392.  
  393. { Calculate the order's tax totals and amount due }
  394.  
  395. procedure TMastData.OrdersCalcFields(DataSet: TDataSet);
  396. begin
  397.   OrdersTaxTotal.Value := OrdersItemsTotal.Value * (OrdersTaxRate.Value / 100);
  398.   OrdersAmountDue.Value := OrdersItemsTotal.Value + OrdersTaxTotal.Value +
  399.     OrdersFreight.Value - OrdersAmountPaid.Value;
  400. end;
  401.  
  402. { Inititializes the record values as a result of an Orders.Insert. }
  403.  
  404. procedure TMastData.OrdersNewRecord(DataSet: TDataSet);
  405. begin
  406.  
  407.   { Get the Next Order Value from the NextOrd Table }
  408.  
  409.   with NextOrd do
  410.   begin
  411.     Open;
  412.     try
  413.       Edit;
  414.       OrdersOrderNo.Value := NextOrdNewKey.Value;
  415.       NextOrdNewKey.Value := NextOrdNewKey.Value + 1;
  416.       Post;
  417.     finally
  418.       Close;
  419.     end;
  420.   end;
  421.   OrdersSaleDate.Value := Date;
  422.   OrdersShipVia.Value := 'UPS';
  423.   OrdersTerms.Value := 'net 30';
  424.   OrdersPaymentMethod.Value := 'Check';
  425.   OrdersItemsTotal.Value := 0;
  426.   OrdersTaxRate.Value := 0;
  427.   OrdersFreight.Value := 0;
  428.   OrdersAmountPaid.Value := 0;
  429. end;
  430.  
  431. procedure TMastData.ItemsAfterDelete(DataSet: TDataSet);
  432. begin
  433.   UpdateTotals;
  434. end;
  435.  
  436. { Update the order totals and the Parts table }
  437.  
  438. procedure TMastData.ItemsAfterPost(DataSet: TDataSet);
  439.  
  440.   { Reduce/increase Parts table's OnOrder field }
  441.  
  442.   procedure UpdateParts(PartNo: Double; Qty : Longint);
  443.   begin
  444.     if (PartNo > 0) and (Qty <> 0) then
  445.     try
  446.       if not Parts.Locate('PartNo', PartNo, []) then Abort;
  447.       Parts.Edit;
  448.       PartsOnOrder.Value := PartsOnOrder.Value + Qty;
  449.       Parts.Post;
  450.     except
  451.       on E: Exception do
  452.         ShowMessage(Format('Error updating parts table for PartNo: %d', [PartNo]));
  453.     end;
  454.   end;
  455.  
  456. begin
  457.   { Maintain next available item number }
  458.   Inc(FItemNo);
  459.   UpdateTotals;
  460.   if not ((PrevPartNo = ItemsPartNo.Value) and (PrevQty = ItemsQty.Value)) then
  461.   begin
  462.    { Reduce previous Part#'s OnOrder field by previous Qty }
  463.     UpdateParts(PrevPartNo, -PrevQty);
  464.    { Increase new Part#'s OnOrder field by previous Qty }
  465.     UpdateParts(ItemsPartNo.Value, ItemsQty.Value);
  466.   end;
  467. end;
  468.  
  469. {  When a change to the detail table affects a field in the master, always make
  470.   sure the master (orders) table is in edit or insert mode before allowing the
  471.   detail table to be modified. }
  472.  
  473. procedure TMastData.EnsureOrdersEdit(DataSet: TDataSet);
  474. begin
  475.   Orders.Edit;
  476. end;
  477.  
  478. { Remember previous PartNo and Qty for updating Parts.OnOrder after post.
  479.   When a change to the detail table affects a field in the master, always make
  480.   sure the master table is in edit or insert mode before allowing the
  481.   detail table to be modified. }
  482.  
  483. procedure TMastData.ItemsBeforeEdit(DataSet: TDataSet);
  484. begin
  485.   Orders.Edit;
  486.   PrevPartNo := ItemsPartNo.Value;
  487.   PrevQty := ItemsQty.Value;
  488. end;
  489.  
  490. { Make sure the Parts table opens before the Items table, since there are
  491.   lookups which depend on it. }
  492.  
  493. procedure TMastData.ItemsBeforeOpen(DataSet: TDataSet);
  494. begin
  495.   Parts.Open;
  496. end;
  497.  
  498. { Complete the item's key by initializing its NextItemNo field }
  499.  
  500. procedure TMastData.ItemsBeforePost(DataSet: TDataSet);
  501. begin
  502.   ItemsItemNo.Value := FItemNo;
  503. end;
  504.  
  505. { Lookup PartNo info for the item; calculate its extended price }
  506.  
  507. procedure TMastData.ItemsCalcFields(DataSet: TDataSet);
  508. begin
  509.   ItemsExtPrice.Value := ItemsQty.Value *
  510.     ItemsSellPrice.Value * (100 - ItemsDiscount.Value) / 100;
  511. end;
  512.  
  513. { New item. Zero the "prev" buckets, initialize the key }
  514.  
  515. procedure TMastData.ItemsNewRecord(DataSet: TDataSet);
  516. begin
  517.   PrevPartNo := 0;
  518.   PrevQty := 0;
  519.   ItemsOrderNo.Value := OrdersOrderNo.Value;
  520.   ItemsQty.Value := 1;
  521.   ItemsDiscount.Value := 0;
  522. end;
  523.  
  524. { Concatenate last name + first name for the order's SoldBy DBLookupCombo }
  525.  
  526. procedure TMastData.EmpsCalcFields(DataSet: TDataSet);
  527. begin
  528.   EmpsFullName.Value := Format('%s, %s', [EmpsLastName.Value, EmpsFirstName.Value]);
  529. end;
  530.  
  531. procedure TMastData.DeleteItems;
  532. begin
  533.   DeletingItems := True;    { suppress recalc of totals during delete }
  534.   Items.DisableControls;    { for faster table traversal }
  535.   try
  536.     Items.First;
  537.     while not Items.EOF do Items.Delete;
  538.   finally
  539.     DeletingItems := False;
  540.     Items.EnableControls;   { always re-enable controls after disabling }
  541.   end;
  542. end;
  543.  
  544. { Steps through Items and gathers sum of ExtPrice. After OrdersItemsTotal
  545.   is calculated, OrdersCalcFields is automatically called (which
  546.   updates other calculated fields. }
  547.   
  548. procedure TMastData.UpdateTotals;
  549. var
  550.   TempTotal: Extended;
  551.   PrevRecord: TBookmark;
  552. begin
  553.   if DeletingItems then Exit;        { don't calculate if deleting all items }
  554.   PrevRecord := Items.GetBookmark;    { returns nil if table is empty }
  555.   try
  556.     Items.DisableControls;
  557.     Items.First;
  558.     TempTotal := 0;            { use temp for efficiency }
  559.     while not Items.EOF do
  560.     begin
  561.       TempTotal := TempTotal + ItemsExtPrice.Value;
  562.       Items.Next;
  563.     end;
  564.     OrdersItemsTotal.Value := TempTotal;
  565.   finally
  566.      Items.EnableControls;
  567.      if PrevRecord <> nil then
  568.      begin
  569.        Items.GoToBookmark(PrevRecord);
  570.        Items.FreeBookmark(PrevRecord);
  571.      end;
  572.   end;
  573. end;
  574.  
  575. procedure TMastData.OrdersCustNoChange(Sender: TField);
  576. var
  577.   TaxRate: Variant;
  578. begin
  579.   OrdersShipToContact.Value := '';
  580.   OrdersShipToPhone.Value := '';
  581.   OrdersShipToAddr1.Value := '';
  582.   OrdersShipToAddr2.Value := '';
  583.   OrdersShipToCity.Value := '';
  584.   OrdersShipToState.Value := '';
  585.   OrdersShipToZip.Value := '';
  586.   OrdersShipToCountry.Value := '';
  587.   TaxRate := Cust.Lookup('CustNo', OrdersCustNo.Value, 'TaxRate');
  588.   if not VarIsNull(TaxRate) then
  589.     OrdersTaxRate.Value := TaxRate;
  590. end;
  591.  
  592. { Alternatively, could set the Qty field's Min and Max values in code
  593.   or in the Object Inspector. }
  594.  
  595. procedure TMastData.ItemsQtyValidate(Sender: TField);
  596. begin
  597.   if ItemsQty.Value < 1 then
  598.     raise Exception.Create('Must specify quantity');
  599. end;
  600.  
  601. { Alternatively, could set the Freight field's Min and Max values in code
  602.   or in the Object Inspector. }
  603.  
  604. procedure TMastData.OrdersFreightValidate(Sender: TField);
  605. begin
  606.   if OrdersFreight.Value < 0 then
  607.     raise Exception.Create('Freight cannot be less than zero');
  608. end;
  609.  
  610. procedure TMastData.ItemsPartNoValidate(Sender: TField);
  611. begin
  612.   if not Parts.Locate('PartNo', ItemsPartNo.Value, []) then
  613.     raise Exception.Create('You must specify a valid PartNo');
  614. end;
  615.  
  616. procedure TMastData.OrdersSaleDateValidate(Sender: TField);
  617. begin
  618.   if OrdersSaleDate.Value > Now then
  619.     raise Exception.Create('Cannot enter a future date');
  620. end;
  621.  
  622. { Browse Customers }
  623.  
  624. procedure TMastData.CustBeforeOpen(DataSet: TDataSet);
  625. begin
  626.   OrdByCust.Open;
  627. end;
  628.  
  629. procedure TMastData.OrdByCustCalcFields(DataSet: TDataSet);
  630. begin
  631.   OrdByCustAmountDue.Value := OrdByCustItemsTotal.Value +
  632.     OrdByCustItemsTotal.Value * OrdByCustTaxRate.Value / 100 +
  633.     OrdByCustFreight.Value - OrdByCustAmountPaid.Value;
  634. end;
  635.  
  636. { Get the next available customer number from the NextCust table }
  637.  
  638. procedure TMastData.CustBeforePost(DataSet: TDataSet);
  639. begin
  640.   if Cust.State = dsInsert then
  641.     with NextCust do
  642.     begin
  643.       Open;
  644.       try
  645.         Edit;
  646.         CustCustNo.Value := NextCustNewCust.Value;
  647.         NextCustNewCust.Value := NextCustNewCust.Value + 1;
  648.         Post;
  649.       finally
  650.         Close;
  651.       end;
  652.     end;
  653. end;
  654.  
  655. function TMastData.DataSetApplyUpdates(DataSet: TDataSet; Apply: Boolean): Boolean;
  656. begin
  657.   Result := True;
  658.   with TDBDataSet(DataSet) do
  659.   begin
  660.     if (State in dsEditModes) or UpdatesPending then
  661.     begin
  662.       if Apply then
  663.       begin
  664.         Database.ApplyUpdates([DataSet as TDBDataSet]);
  665.        { Always call CancelUpdates to remove any discard changes }
  666.         CancelUpdates;
  667.       end
  668.       else
  669.       begin
  670.         if (MessageDlg('Unsaved changes, exit anyway?', mtConfirmation,
  671.           [mbYes, mbCancel], 0) = mrYes) then
  672.           CancelUpdates
  673.         else
  674.           Result := False;
  675.       end;
  676.     end;
  677.   end;
  678. end;
  679.  
  680. { Determine the next available ItemNo for this order }
  681.  
  682. procedure TMastData.OrdersBeforeEdit(DataSet: TDataSet);
  683. begin
  684.   LastItemQuery.Close;
  685.   LastItemQuery.Open;
  686.   { SQL servers return Null for some aggregates if no items are present }
  687.   with LastItemQuery.Fields[0] do
  688.     if IsNull then FItemNo := 1
  689.     else FItemNo := AsInteger + 1;
  690. end;
  691.  
  692. procedure TMastData.EditUpdateError(DataSet: TDataSet; E: EDatabaseError;
  693.   UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
  694. var
  695.   Key: Variant;
  696. const
  697.   UpdErrMsg = '%s.'#13#10'Discard the edits to %S %S and continue updating?';
  698. begin
  699.   if UpdateKind = ukDelete then
  700.     Key := Dataset.Fields[0].OldValue else
  701.     Key := Dataset.Fields[0].NewValue;
  702.   if MessageDlg(Format(UpdErrMsg, [E.Message, DataSet.Fields[0].DisplayLabel, Key]),
  703.     mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
  704.     UpdateAction := uaSkip else
  705.     UpdateAction := uaAbort;
  706. end;
  707.  
  708. end.
  709.  
  710.