{*******************************************************} { } { Responsive Software http://www.responsive.co.nz } { } { Copyright (c) 2003-2006 Responsive Software Limited } { } {*******************************************************} unit BalanceSheet; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, BaseFrameUnit, ComCtrls, StdCtrls, ExtCtrls, ImgList, BusinessObjects; type TBalanceSheetNodeType = (bsnUndefined,bsnHeading,bsnGroup,bsnAccount,bsnSubtotal,bsnTotal,bsnRetainedEarnings); TBalanceSheetFrame = class(TBaseFrame) TreeView: TTreeView; HeadingButton: TButton; GroupButton: TButton; SubtotalButton: TButton; DeleteButton: TButton; EmailButton: TButton; PrintButton: TButton; HeadingLabel: TLabel; HeadingShape: TShape; EditButton: TButton; ImageList1: TImageList; AsAtDateDateTimePicker: TDateTimePicker; Label1: TLabel; procedure TreeViewDragDrop(Sender, Source: TObject; X, Y: Integer); procedure TreeViewDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure PrintButtonClick(Sender: TObject); procedure HeadingButtonClick(Sender: TObject); procedure EditButtonClick(Sender: TObject); procedure GroupButtonClick(Sender: TObject); procedure SubtotalButtonClick(Sender: TObject); procedure DeleteButtonClick(Sender: TObject); procedure EmailButtonClick(Sender: TObject); procedure TreeViewChange(Sender: TObject; Node: TTreeNode); procedure TreeViewEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean); procedure TreeViewDblClick(Sender: TObject); private { Private declarations } IgnoreChangeEvents : boolean; // set this to true when updating controls in code // this is a locally held copy of the report layout as it appears in the // ReportLayouts collection // note that the nodes in the tree view contain references to this // so no changes should be made to it without updating the tree view FReportLayout : TReportLayout; // these are only used to attach to the Data field of new nodes in the tree view // to indicate their type DummyHeadingElement : TReportHeadingElement; DummyGroupElement : TReportGroupElement; DummySubtotalElement : TReportSubtotalElement; function AllowDragDrop (DraggedNode : TTreeNode; TargetNode : TTreeNode; var NodeAttachMode : TNodeAttachMode) : boolean; function NodeType (Node : TTreeNode) : TBalanceSheetNodeType; function ExistingReportLayout : TReportLayout; procedure UpdateControlStates; procedure UpdateTreeView; procedure SaveTreeViewToReportLayout; procedure GetReportLayout; procedure UpdateReportLayouts; procedure PrintReport (Email : boolean); public { Public declarations } procedure UpdateDisplay; procedure Setup; override; procedure SetPeriod (UseBeginPeriod : boolean; BeginPeriodDate : TDateTime; UseEndPeriod : boolean; EndPeriodDate : TDateTime); end; var BalanceSheetFrame: TBalanceSheetFrame; implementation uses DatabaseManager, Globals, Utilities, Main; {$R *.dfm} procedure TBalanceSheetFrame.SetPeriod (UseBeginPeriod : boolean; BeginPeriodDate : TDateTime; UseEndPeriod : boolean; EndPeriodDate : TDateTime); begin // if there is an end period date set then use this as the default // date otherwise use the current date if UseEndPeriod then AsAtDateDateTimePicker.Date := EndPeriodDate else AsAtDateDateTimePicker.Date := Date; end; procedure TBalanceSheetFrame.Setup; begin // create dummy elements DummyHeadingElement := TReportHeadingElement.Create; DummyGroupElement := TReportGroupElement.Create; DummySubtotalElement := TReportSubtotalElement.Create; // default period values to that set as the default period SetPeriod( WorkstationConfiguration.UseBeginPeriod, WorkstationConfiguration.BeginPeriodDate, WorkstationConfiguration.UseEndPeriod, WorkstationConfiguration.EndPeriodDate); UpdateDisplay; end; function TBalanceSheetFrame.AllowDragDrop (DraggedNode : TTreeNode; TargetNode : TTreeNode; var NodeAttachMode : TNodeAttachMode) : boolean; begin Result := false; // both nodes must exist if (TargetNode = nil) or (DraggedNode = nil) then Exit; // if we are dragging an account node or retained earnings if (NodeType(DraggedNode) in [bsnAccount,bsnRetainedEarnings]) then begin // if the target is a group then add it if NodeType(TargetNode) = bsnGroup then NodeAttachMode := naAddChild // otherwise insert it before the target else NodeAttachMode := naInsert; // if we are dragging any other sort of node end else begin // insert it before the target providing it is at level 0 if TargetNode.Level = 0 then NodeAttachMode := naInsert // otherwise don't allow it else Exit; end; Result := true; end; function TBalanceSheetFrame.NodeType (Node : TTreeNode) : TBalanceSheetNodeType; begin if TBusinessObject(Node.Data) is TReportHeadingElement then Result := bsnHeading else if TBusinessObject(Node.Data) is TReportGroupElement then Result := bsnGroup else if TBusinessObject(Node.Data) is TReportSubtotalElement then Result := bsnSubtotal else if TBusinessObject(Node.Data) is TReportAccountElement then Result := bsnAccount else if TBusinessObject(Node.Data) is TReportRetainedEarningsElement then Result := bsnRetainedEarnings else if TBusinessObject(Node.Data) is TReportTotalElement then Result := bsnTotal else Result := bsnUndefined; end; function TBalanceSheetFrame.ExistingReportLayout : TReportLayout; begin Result := ReportLayout(WorkstationConfiguration.CompanyId,rtBalanceSheet); end; procedure TBalanceSheetFrame.UpdateControlStates; var SelectedNode : TTreeNode; begin IgnoreChangeEvents := true; // change the enabled status of the buttons depending on the selected node type SelectedNode := TreeView.Selected; if SelectedNode <> nil then begin EditButton.Enabled := not (NodeType(SelectedNode) in [bsnAccount]); DeleteButton.Enabled := not (NodeType(SelectedNode) in [bsnAccount,bsnTotal,bsnRetainedEarnings]); end else begin EditButton.Enabled := false; DeleteButton.Enabled := false; end; IgnoreChangeEvents := false; end; procedure TBalanceSheetFrame.UpdateTreeView; var i : integer; Element : TBusinessObject; NewNode : TTreeNode; LastGroupNode : TTreeNode; begin IgnoreChangeEvents := true; LastGroupNode := nil; // clear all existing nodes TreeView.Items.BeginUpdate; TreeView.Items.Clear; // now add appropriate nodes to the tree view control for i := 0 to FReportLayout.Elements.Count - 1 do begin Element := FReportLayout.Elements[i]; if Element is TReportHeadingElement then begin NewNode := TreeView.Items.Add(nil,TReportHeadingElement(Element).Text); NewNode.Data := pointer(Element); NewNode.ImageIndex := 0; NewNode.SelectedIndex := 0; end else if Element is TReportGroupElement then begin NewNode := TreeView.Items.Add(nil,TReportGroupElement(Element).Text); NewNode.Data := pointer(Element); NewNode.ImageIndex := 1; NewNode.SelectedIndex := 1; LastGroupNode := NewNode; end else if Element is TReportAccountElement then begin if TReportAccountElement(Element).InGroup then NewNode := TreeView.Items.AddChild(LastGroupNode,TReportAccountElement(Element).AccountName) else NewNode := TreeView.Items.Add(nil,TReportAccountElement(Element).AccountName); NewNode.Data := pointer(Element); NewNode.ImageIndex := -1; NewNode.SelectedIndex := -1; end else if Element is TReportSubtotalElement then begin NewNode := TreeView.Items.Add(nil,TReportSubtotalElement(Element).Text); NewNode.Data := pointer(Element); NewNode.ImageIndex := 2; NewNode.SelectedIndex := 2; end else if Element is TReportTotalElement then begin NewNode := TreeView.Items.Add(nil,TReportTotalElement(Element).Text); NewNode.Data := pointer(Element); NewNode.ImageIndex := 3; NewNode.SelectedIndex := 3; end else if Element is TReportRetainedEarningsElement then begin if TReportRetainedEarningsElement(Element).InGroup then NewNode := TreeView.Items.AddChild(LastGroupNode,TReportRetainedEarningsElement(Element).Text) else NewNode := TreeView.Items.Add(nil,TReportRetainedEarningsElement(Element).Text); NewNode.Data := pointer(Element); NewNode.ImageIndex := 4; NewNode.SelectedIndex := 4; end; end; TreeView.Items.EndUpdate; IgnoreChangeEvents := false; end; procedure TBalanceSheetFrame.SaveTreeViewToReportLayout; var i : integer; NewReportLayout : TReportLayout; Node : TTreeNode; ReportAccountElement : TReportAccountElement; ReportHeadingElement : TReportHeadingElement; ReportTotalElement : TReportTotalElement; ReportRetainedEarningsElement : TReportRetainedEarningsElement; ReportSubtotalElement : TReportSubtotalElement; ReportGroupElement : TReportGroupElement; begin // create a new report layout and populate with the information from the tree view NewReportLayout := TReportLayout.Create; NewReportLayout.CompanyId := WorkstationConfiguration.CompanyId; NewReportLayout.ReportLayoutType := rtBalanceSheet; for i := 0 to TreeView.Items.Count - 1 do begin Node := TreeView.Items[i]; if NodeType(Node) = bsnAccount then begin ReportAccountElement := TReportAccountElement.Create; ReportAccountElement.AccountId := TReportAccountElement(Node.Data).AccountId; ReportAccountElement.InGroup := (Node.Level > 0); NewReportLayout.Elements.Add(ReportAccountElement); end else if NodeType(Node) = bsnRetainedEarnings then begin ReportRetainedEarningsElement := TReportRetainedEarningsElement.Create; ReportRetainedEarningsElement.Text := Node.Text; ReportRetainedEarningsElement.InGroup := (Node.Level > 0); NewReportLayout.Elements.Add(ReportRetainedEarningsElement); end else if NodeType(Node) = bsnTotal then begin ReportTotalElement := TReportTotalElement.Create; ReportTotalElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportTotalElement); end else if NodeType(Node) = bsnHeading then begin ReportHeadingElement := TReportHeadingElement.Create; ReportHeadingElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportHeadingElement); end else if NodeType(Node) = bsnGroup then begin ReportGroupElement := TReportGroupElement.Create; ReportGroupElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportGroupElement); end else if NodeType(Node) = bsnSubtotal then begin ReportSubtotalElement := TReportSubtotalElement.Create; ReportSubtotalElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportSubtotalElement); end; end; // now free the existing copy and replace it with this one FReportLayout.Free; FReportLayout := NewReportLayout; // then update the tree view so the nodes now refer to the new one UpdateTreeView; end; procedure TBalanceSheetFrame.GetReportLayout; begin // create a local copy of the report layout FReportLayout.Free; FReportLayout := TReportLayout.Create; // if not found in global collection then create one from scratch if ExistingReportLayout = nil then begin FReportLayout.CompanyId := WorkstationConfiguration.CompanyId; FReportLayout.ReportLayoutType := rtBalanceSheet; FReportLayout.CreateDefaultBalanceSheet; // otherwise assign details from existing one end else begin FReportLayout.Assign(ExistingReportLayout); FReportLayout.UpdateBalanceSheet; end; end; procedure TBalanceSheetFrame.UpdateReportLayouts; var NewReportLayout : TReportLayout; begin // if not found in global collection then create one if ExistingReportLayout = nil then begin NewReportLayout := TReportLayout.Create; NewReportLayout.Assign(FReportLayout); ReportLayouts.Add(NewReportLayout); end else // update the existing report in the global collection ExistingReportLayout.Assign(FReportLayout); end; procedure TBalanceSheetFrame.UpdateDisplay; begin GetReportLayout; UpdateTreeView; UpdateControlStates; end; procedure TBalanceSheetFrame.PrintReport (Email : boolean); begin SaveTreeViewToReportLayout; UpdateReportLayouts; SaveReportLayoutsToWorkstationConfiguration; SaveWorkstationConfiguration; PrintBalanceSheet( WorkstationConfiguration.CompanyId, Trunc(AsAtDateDateTimePicker.Date), Email); end; {******************************************************************************} procedure TBalanceSheetFrame.HeadingButtonClick(Sender: TObject); var SelectedNode : TTreeNode; NewNode : TTreeNode; begin SelectedNode := TreeView.Selected; if (SelectedNode <> nil) and (SelectedNode.Level = 0) then begin TreeView.Items.BeginUpdate; NewNode := TreeView.Items.Insert(SelectedNode,'HEADING'); NewNode.Data := pointer(DummyHeadingElement); NewNode.ImageIndex := 0; NewNode.SelectedIndex := 0; TreeView.Items.EndUpdate; TreeView.SetFocus; NewNode.EditText; end else TreeView.SetFocus; end; procedure TBalanceSheetFrame.GroupButtonClick(Sender: TObject); var SelectedNode : TTreeNode; NewNode : TTreeNode; begin SelectedNode := TreeView.Selected; if (SelectedNode <> nil) and (SelectedNode.Level = 0) then begin TreeView.Items.BeginUpdate; NewNode := TreeView.Items.Insert(SelectedNode,'GROUP'); NewNode.Data := pointer(DummyGroupElement); NewNode.ImageIndex := 1; NewNode.SelectedIndex := 1; TreeView.Items.EndUpdate; TreeView.SetFocus; NewNode.EditText; end else TreeView.SetFocus; end; procedure TBalanceSheetFrame.SubtotalButtonClick(Sender: TObject); var SelectedNode : TTreeNode; NewNode : TTreeNode; begin SelectedNode := TreeView.Selected; if (SelectedNode <> nil) and (SelectedNode.Level = 0) then begin TreeView.Items.BeginUpdate; NewNode := TreeView.Items.Insert(SelectedNode,'SUBTOTAL'); NewNode.Data := pointer(DummySubtotalElement); NewNode.ImageIndex := 2; NewNode.SelectedIndex := 2; TreeView.Items.EndUpdate; TreeView.SetFocus; NewNode.EditText; end else TreeView.SetFocus; end; procedure TBalanceSheetFrame.EditButtonClick(Sender: TObject); var SelectedNode : TTreeNode; begin // allow user to edit the text of the selected node SelectedNode := TreeView.Selected; if SelectedNode <> nil then SelectedNode.EditText; end; procedure TBalanceSheetFrame.DeleteButtonClick(Sender: TObject); var SelectedNode : TTreeNode; begin // allow user to delete the selected node SelectedNode := TreeView.Selected; if SelectedNode <> nil then begin // if it is a group then move all children to the top level // before deleting TreeView.Items.BeginUpdate; if NodeType(SelectedNode) = bsnGroup then begin while SelectedNode.Count > 0 do SelectedNode.Item[0].MoveTo(SelectedNode,naInsert); end; SelectedNode.Delete; TreeView.Items.EndUpdate; end; TreeView.SetFocus; end; procedure TBalanceSheetFrame.EmailButtonClick(Sender: TObject); begin PrintReport(true); end; procedure TBalanceSheetFrame.PrintButtonClick(Sender: TObject); begin PrintReport(false); end; {***** TTreeView event handling ***********************************************} procedure TBalanceSheetFrame.TreeViewDragDrop(Sender, Source: TObject; X, Y: Integer); var DraggedNode : TTreeNode; TargetNode : TTreeNode; NodeAttachMode : TNodeAttachMode; Expanded : boolean; begin TargetNode := TreeView.GetNodeAt(X,Y); DraggedNode := TreeView.Selected; if AllowDragDrop(DraggedNode,TargetNode,NodeAttachMode) then begin TreeView.Items.BeginUpdate; Expanded := DraggedNode.Expanded; DraggedNode.MoveTo(TargetNode,NodeAttachMode); DraggedNode.Expanded := Expanded; TreeView.Items.EndUpdate; end; end; procedure TBalanceSheetFrame.TreeViewDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); var DraggedNode : TTreeNode; TargetNode : TTreeNode; NodeAttachMode : TNodeAttachMode; begin TargetNode := TreeView.GetNodeAt(X,Y); DraggedNode := TreeView.Selected; Accept := AllowDragDrop(DraggedNode,TargetNode,NodeAttachMode); end; procedure TBalanceSheetFrame.TreeViewChange(Sender: TObject; Node: TTreeNode); begin if IgnoreChangeEvents then Exit; UpdateControlStates; end; procedure TBalanceSheetFrame.TreeViewEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean); begin // don't allow text to be edited on an account node if NodeType(Node) = bsnAccount then AllowEdit := false else AllowEdit := true; end; procedure TBalanceSheetFrame.TreeViewDblClick(Sender: TObject); var Node : TTreeNode; begin // if node is an account then switch to this account on accounts frame Node := TreeView.Selected; if (Node <> nil) and (NodeType(Node) = bsnAccount) then begin MainForm.SwitchToFrame('Accounts'); MainForm.AccountsFrame.ShowAccount(TReportAccountElement(Node.Data).AccountId); end; end; {******************************************************************************} end.