{*******************************************************} { } { Responsive Software http://www.responsive.co.nz } { } { Copyright (c) 2003-2006 Responsive Software Limited } { } {*******************************************************} unit IncomeStatement; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, BaseFrameUnit, ExtCtrls, StdCtrls, ImgList, ComCtrls, BusinessObjects; type TIncomeStatementNodeType = (plnUndefined,plnHeading,plnGroup,plnAccount,plnSubtotal,plnTotal,plnRetainedEarnings); TIncomeStatementFrame = class(TBaseFrame) HeadingLabel: TLabel; HeadingShape: TShape; TreeView: TTreeView; HeadingButton: TButton; GroupButton: TButton; SubtotalButton: TButton; DeleteButton: TButton; EmailButton: TButton; PrintButton: TButton; EditButton: TButton; ImageList1: TImageList; UseBeginPeriodCheckBox: TCheckBox; BeginPeriodDateDateTimePicker: TDateTimePicker; UseEndPeriodCheckBox: TCheckBox; EndPeriodDateDateTimePicker: TDateTimePicker; procedure HeadingButtonClick(Sender: TObject); procedure GroupButtonClick(Sender: TObject); procedure SubtotalButtonClick(Sender: TObject); procedure EditButtonClick(Sender: TObject); procedure DeleteButtonClick(Sender: TObject); procedure EmailButtonClick(Sender: TObject); procedure PrintButtonClick(Sender: TObject); procedure TreeViewDragDrop(Sender, Source: TObject; X, Y: Integer); procedure TreeViewDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure TreeViewEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean); procedure TreeViewChange(Sender: TObject; Node: TTreeNode); procedure UseBeginPeriodCheckBoxClick(Sender: TObject); procedure UseEndPeriodCheckBoxClick(Sender: TObject); procedure BeginPeriodDateDateTimePickerChange(Sender: TObject); procedure EndPeriodDateDateTimePickerChange(Sender: TObject); 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) : TIncomeStatementNodeType; 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 IncomeStatementFrame: TIncomeStatementFrame; implementation uses DatabaseManager, Globals, Utilities, Main; {$R *.dfm} procedure TIncomeStatementFrame.SetPeriod (UseBeginPeriod : boolean; BeginPeriodDate : TDateTime; UseEndPeriod : boolean; EndPeriodDate : TDateTime); begin UseBeginPeriodCheckBox.Checked := UseBeginPeriod; BeginPeriodDateDateTimePicker.Date := BeginPeriodDate; BeginPeriodDateDateTimePicker.Enabled := UseBeginPeriodCheckBox.Checked; UseEndPeriodCheckBox.Checked := UseEndPeriod; EndPeriodDateDateTimePicker.Date := EndPeriodDate; EndPeriodDateDateTimePicker.Enabled := UseEndPeriodCheckBox.Checked; end; procedure TIncomeStatementFrame.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 TIncomeStatementFrame.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 if (NodeType(DraggedNode) in [plnAccount]) then begin // if the target is a group then add it if NodeType(TargetNode) = plnGroup then NodeAttachMode := naAddChild // otherwise insert it before the target else NodeAttachMode := naInsert; // if we are dragging the retained earnings element end else if (NodeType(DraggedNode) in [plnRetainedEarnings]) then begin // don't allow this to be moved Exit; // 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 TIncomeStatementFrame.NodeType (Node : TTreeNode) : TIncomeStatementNodeType; begin if TBusinessObject(Node.Data) is TReportHeadingElement then Result := plnHeading else if TBusinessObject(Node.Data) is TReportGroupElement then Result := plnGroup else if TBusinessObject(Node.Data) is TReportSubtotalElement then Result := plnSubtotal else if TBusinessObject(Node.Data) is TReportAccountElement then Result := plnAccount else if TBusinessObject(Node.Data) is TReportRetainedEarningsElement then Result := plnRetainedEarnings else if TBusinessObject(Node.Data) is TReportTotalElement then Result := plnTotal else Result := plnUndefined; end; function TIncomeStatementFrame.ExistingReportLayout : TReportLayout; begin Result := ReportLayout(WorkstationConfiguration.CompanyId,rtIncomeStatement); end; procedure TIncomeStatementFrame.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 [plnAccount]); DeleteButton.Enabled := not (NodeType(SelectedNode) in [plnAccount,plnTotal,plnRetainedEarnings]); end else begin EditButton.Enabled := false; DeleteButton.Enabled := false; end; IgnoreChangeEvents := false; end; procedure TIncomeStatementFrame.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 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 TIncomeStatementFrame.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 := rtIncomeStatement; for i := 0 to TreeView.Items.Count - 1 do begin Node := TreeView.Items[i]; if NodeType(Node) = plnAccount 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) = plnRetainedEarnings then begin ReportRetainedEarningsElement := TReportRetainedEarningsElement.Create; ReportRetainedEarningsElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportRetainedEarningsElement); end else if NodeType(Node) = plnTotal then begin ReportTotalElement := TReportTotalElement.Create; ReportTotalElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportTotalElement); end else if NodeType(Node) = plnHeading then begin ReportHeadingElement := TReportHeadingElement.Create; ReportHeadingElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportHeadingElement); end else if NodeType(Node) = plnGroup then begin ReportGroupElement := TReportGroupElement.Create; ReportGroupElement.Text := Node.Text; NewReportLayout.Elements.Add(ReportGroupElement); end else if NodeType(Node) = plnSubtotal 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 TIncomeStatementFrame.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 := rtIncomeStatement; FReportLayout.CreateDefaultIncomeStatement; // otherwise assign details from existing one end else begin FReportLayout.Assign(ExistingReportLayout); FReportLayout.UpdateIncomeStatement; end; end; procedure TIncomeStatementFrame.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 TIncomeStatementFrame.UpdateDisplay; begin GetReportLayout; UpdateTreeView; UpdateControlStates; end; procedure TIncomeStatementFrame.PrintReport (Email : boolean); begin SaveTreeViewToReportLayout; UpdateReportLayouts; SaveReportLayoutsToWorkstationConfiguration; SaveWorkstationConfiguration; PrintIncomeStatement( WorkstationConfiguration.CompanyId, UseBeginPeriodCheckBox.Checked, Trunc(BeginPeriodDateDateTimePicker.Date), UseEndPeriodCheckBox.Checked, Trunc(EndPeriodDateDateTimePicker.Date), Email); end; {******************************************************************************} procedure TIncomeStatementFrame.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 TIncomeStatementFrame.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 TIncomeStatementFrame.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 TIncomeStatementFrame.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 TIncomeStatementFrame.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) = plnGroup then begin while SelectedNode.Count > 0 do SelectedNode.Item[0].MoveTo(SelectedNode,naInsert); end; SelectedNode.Delete; TreeView.Items.EndUpdate; end; TreeView.SetFocus; end; procedure TIncomeStatementFrame.EmailButtonClick(Sender: TObject); begin PrintReport(true); end; procedure TIncomeStatementFrame.PrintButtonClick(Sender: TObject); begin PrintReport(false); end; {***** TTreeView event handling ***********************************************} procedure TIncomeStatementFrame.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 TIncomeStatementFrame.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 TIncomeStatementFrame.TreeViewChange(Sender: TObject; Node: TTreeNode); begin if IgnoreChangeEvents then Exit; UpdateControlStates; end; procedure TIncomeStatementFrame.TreeViewEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean); begin // don't allow text to be edited on an account node if NodeType(Node) = plnAccount then AllowEdit := false else AllowEdit := true; end; procedure TIncomeStatementFrame.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) = plnAccount) then begin MainForm.SwitchToFrame('Accounts'); MainForm.AccountsFrame.ShowAccount(TReportAccountElement(Node.Data).AccountId); end; end; {******************************************************************************} procedure TIncomeStatementFrame.UseBeginPeriodCheckBoxClick(Sender: TObject); begin BeginPeriodDateDateTimePicker.Enabled := UseBeginPeriodCheckBox.Checked; end; procedure TIncomeStatementFrame.UseEndPeriodCheckBoxClick(Sender: TObject); begin EndPeriodDateDateTimePicker.Enabled := UseEndPeriodCheckBox.Checked; end; procedure TIncomeStatementFrame.BeginPeriodDateDateTimePickerChange( Sender: TObject); var BeginDate, EndDate : TDateTime; EndDateDateTimePicker : TDateTimePicker; begin EndDateDateTimePicker := EndPeriodDateDateTimePicker; BeginDate := Trunc(TDateTimePicker(Sender).Date); EndDate := EndDateDateTimePicker.Date; // adjust end date if necessary if EndDate < BeginDate then begin EndDate := BeginDate; EndDateDateTimePicker.Date := EndDate; end; end; procedure TIncomeStatementFrame.EndPeriodDateDateTimePickerChange( Sender: TObject); var BeginDate, EndDate : TDateTime; BeginDateDateTimePicker : TDateTimePicker; begin BeginDateDateTimePicker := BeginPeriodDateDateTimePicker; BeginDate := BeginDateDateTimePicker.Date; EndDate := Trunc(TDateTimePicker(Sender).Date); // adjust begin date if necessary if EndDate < BeginDate then begin BeginDate := EndDate; BeginDateDateTimePicker.Date := BeginDate; end; end; end.