unit MCacheTree;
// CacheTree For Measure, Inherit From CacheTree

interface

uses
  Classes, CacheTree, Math, ZhAPI, sdIDTree;

type
  // For Import Temp Excel
  TBillsCacheNode = class(TCacheNode)
  private
    FLevelCode: string;
    FCode: string;
    FB_Code: string;
    FName: string;
    FUnits: string;
    FCanDelete: Boolean;
    FOrgQuantity: Double;
    FMisQuantity: Double;
    FOthQuantity: Double;
    FDgnQuantity1: Double;
    FDgnQuantity2: Double;
    FMemoStr: string;
    FPrice: Double;
    FDrawingCode: string;
  public
    property LevelCode: string read FLevelCode write FLevelCode;
    property Code: string read FCode write FCode;
    property B_Code: string read FB_Code write FB_Code;
    property Name: string read FName write FName;
    property Units: string read FUnits write FUnits;
    property CanDelete: Boolean read FCanDelete write FCanDelete;
    property Price: Double read FPrice write FPrice;
    property OrgQuantity: Double read FOrgQuantity write FOrgQuantity;
    property MisQuantity: Double read FMisQuantity write FMisQuantity;
    property OthQuantity: Double read FOthQuantity write FOthQuantity;
    property DgnQuantity1: Double read FDgnQuantity1 write FDgnQuantity1;
    property DgnQuantity2: Double read FDgnQuantity2 write FDgnQuantity2;
    property DrawingCode: string read FDrawingCode write FDrawingCode;
    property MemoStr: string read FMemoStr write FMemoStr;
  end;

  TBillsCacheTree = class(TCacheTree)
  private
    FLastNode: TCacheNode;
    FSeparateChar: Char;
    FAutoSort: Boolean;

    FFixedIDNodes: TList;
    function GetNewNode(AID: Integer = -1): TBillsCacheNode; overload;

    function FindNode(const ACode: string): TBillsCacheNode; overload;
    function FindNode(AParent: TBillsCacheNode; const ACode: string): TBillsCacheNode; overload;
    function FindParent(const ACode: string): TBillsCacheNode;
    function FindNextSibling(const ACode: string): TBillsCacheNode;

    procedure SetSeparateChar(const Value: Char);
  public
    constructor Create; override;
    destructor Destroy; override;

    function AddNode(AParent: TCacheNode; ANextSibling: TCacheNode = nil; AFixedID: Integer = -1): TBillsCacheNode;
    function AddNodeByCode(const ACode: string; AFixedID: Integer = -1): TBillsCacheNode;
    function AddLeafBillsNode(const AB_Code: string): TBillsCacheNode;

    function FindXmjChild(AParent: TBillsCacheNode; const ACode, AName: string): TBillsCacheNode;
    function FindGclChild(AParent: TBillsCacheNode; const AB_Code, AName, AUnits: string; APrice: Double): TBillsCacheNode;

    function FindFixedIDNode(AID: Integer): TBillsCacheNode;

    // Only for Debugging lot of Data
    procedure SaveTreeToFile(const AFileName: string);

    property SeparateChar: Char read FSeparateChar write SetSeparateChar;
    property AutoSort: Boolean read FAutoSort write FAutoSort;

    property FixedIDNodes: TList read FFixedIDNodes;
  end;

  // 此树仅用于导入工程量清单，禁止作为它用
  // 如须使用应遵守以下两点：1.完全清楚相关的导入方法类及需求 2.派生子类。
  TGclCacheNode = class(TCacheNode)
  private
    FB_Code: string;
    FName: string;
    FUnits: string;
    FPrice: Double;
    FQuantity: Double;
  public
    property B_Code: string read FB_Code write FB_Code;
    property Name: string read FName write FName;
    property Units: string read FUnits write FUnits;
    property Price: Double read FPrice write FPrice;
    property Quantity: Double read FQuantity write FQuantity;
  end;

  TGclCacheTree = class(TCacheTree)
  private
    FLastBlank1: TGclCacheNode;
    FLastNode: TGclCacheNode;

    function AddNodeByName(const AName: string): TGclCacheNode;
    function AddNodeByB_Code(const AB_Code: string): TGclCacheNode;
  protected
    function GetNewNode: TCacheNode; override;
  public
    function AddNodeByData(const AB_Code, AName: string): TGclCacheNode;

    procedure SaveTreeToFile(const AFileName: string);
  end;

  {For Report Memory(Gather or Compare Projects)}
  TDoubleArray = array of Double;

  TReportCacheNode = class(TCacheNode)
  private
    FCode: string;
    FB_Code: string;
    FName: string;
    FUnits: string;
    FMemoStr: string;

    FXiangCode: string;
    FMuCode: string;
    FJieCode: string;
    FXiMuCode: string;

    FPrice: Double;
    FQuantity: Double;
    FTotalPrice: Double;
    FRatioPercent: Double; // 0号台账 - 各项费用所占比例
    FDesignQuantity1: Double;
    FDesignQuantity2: Double;

    FAddQcQuantity: Double;
    FAddPcTotalPrice: Double;
    FAddQcTotalPrice: Double;
    FAddDealQuantity: Double;
    FAddDealTotalPrice: Double;
    FAddPcQuantity: Double;
    FAddRatioPercent: Double; // 决算 - 各项费用所占比例

    FDealDesignQuantity1: Double;
    FDealDesignQuantity2: Double;
    FCDesignQuantity1: Double;
    FCDesignQuantity2: Double;

    FPDQuantity: Double;
    FPDTotalPrice: Double;
    FPDDesignQuantity1: Double;
    FPDDesignQuantity2: Double;
    FPDDesignPrice: Double;

    FCDDQuantity: Double;
    FCDDTotalPrice: Double;
    FCDDDesignQuantity1: Double;
    FCDDDesignQuantity2: Double;
    FCDDDesignPrice: Double;

    FABTotalPrice: Double;
    FABQuantity: Double;
    FABDesignQuantity1: Double;
    FABDesignQuantity2: Double;
    FABDesignPrice: Double;

    FProjectCount: Integer;
    FP_TotalPrice: TDoubleArray;
    FP_Quantity: TDoubleArray;
    FP_Price: TDoubleArray;
    FP_DgnQuantity1: TDoubleArray;
    FP_DgnQuantity2: TDoubleArray;

    procedure ResolveCode;
    function GetDoubleArrayTotal(ADoubleArray: TDoubleArray): Double;

    procedure SetCode(const Value: string);
    function GetGatherP_TotalPrice: Double;
    function GetAddGatherQuantity: Double;
    function GetAddGatherTotalPrice: Double;
  public
    constructor Create(ACacheTree: TCacheTree; AID, AProjectCount: Integer);

    property Code: string read FCode write SetCode;
    property B_Code: string read FB_Code write FB_Code;
    property Name: string read FName write FName;
    property Units: string read FUnits write FUnits;
    property MemoStr: string read FMemoStr write FMemoStr;

    property XiangCode: string read FXiangCode;
    property MuCode: string read FMuCode;
    property JieCode: string read FJieCode;
    property XiMuCode: string read FXiMuCode;

    // 用于汇总多个项目的合同、变更（数量、金额）
    property Price: Double read FPrice write FPrice;
    // 0号台账合同
    property Quantity: Double read FQuantity write FQuantity;
    property TotalPrice: Double read FTotalPrice write FTotalPrice;
    property RatioPercent: Double read FRatioPercent write FRatioPercent;
    property DesignQuantity1: Double read FDesignQuantity1 write FDesignQuantity1;
    property DesignQuantity2: Double read FDesignQuantity2 write FDesignQuantity2;
    // 累计各值
    property AddDealQuantity: Double read FAddDealQuantity write FAddDealQuantity;
    property AddDealTotalPrice: Double read FAddDealTotalPrice write FAddDealTotalPrice;
    property AddQcQuantity: Double read FAddQcQuantity write FAddQcQuantity;
    property AddQcTotalPrice: Double read FAddQcTotalPrice write FAddQcTotalPrice;
    property AddPcQuantity: Double read FAddPcQuantity write FAddPcQuantity;
    property AddPcTotalPrice: Double read FAddPcTotalPrice write FAddPcTotalPrice;
    property AddGatherQuantity: Double read GetAddGatherQuantity;
    property AddGatherTotalPrice: Double read GetAddGatherTotalPrice;
    property AddRatioPercent: Double read FAddRatioPercent write FAddRatioPercent;
    // 合同&变更 设计数量
    property DealDesignQuantity1: Double read FDealDesignQuantity1 write FDealDesignQuantity1;
    property DealDesignQuantity2: Double read FDealDesignQuantity2 write FDealDesignQuantity2;
    property CDesignQuantity1: Double read FCDesignQuantity1 write FCDesignQuantity1;
    property CDesignQuantity2: Double read FCDesignQuantity2 write FCDesignQuantity2;

    // ----仅用于汇总生成决算02表----
    // 初步设计 Preliminary Design
    property PDQuantity: Double read FPDQuantity write FPDQuantity;
    property PDTotalPrice: Double read FPDTotalPrice write FPDTotalPrice;
    property PDDesignQuantity1: Double read FPDDesignQuantity1 write FPDDesignQuantity1;
    property PDDesignQuantity2: Double read FPDDesignQuantity2 write FPDDesignQuantity2;
    property PDDesignPrice: Double read FPDDesignPrice write FPDDesignPrice;
    // 施工图设计 Construction Drawing Design
    property CDDQuantity: Double read FCDDQuantity write FCDDQuantity;
    property CDDTotalPrice: Double read FCDDTotalPrice write FCDDTotalPrice;
    property CDDDesignQuantity1: Double read FCDDDesignQuantity1 write FCDDDesignQuantity1;
    property CDDDesignQuantity2: Double read FCDDDesignQuantity2 write FCDDDesignQuantity2;
    property CDDDesignPrice: Double read FCDDDesignPrice write FCDDDesignPrice;
    // ------------------------------
    // ----仅用于汇总生成决算02表(部颁)----
    // 批准概(预算)算 Approved Budget
    property ABQuantity: Double read FABQuantity write FABQuantity;
    property ABTotalPrice: Double read FABTotalPrice write FABTotalPrice;
    property ABDesignQuantity1: Double read FABDesignQuantity1 write FABDesignQuantity1;
    property ABDesignQuantity2: Double read FABDesignQuantity2 write FABDesignQuantity2;
    property ABDesignPrice: Double read FABDesignPrice write FABDesignPrice;
    // ------------------------------------

    // 用于记录多个项目的数量、单价、金额、设计数量
    property P_Price: TDoubleArray read FP_Price write FP_Price;
    property P_Quantity: TDoubleArray read FP_Quantity write FP_Quantity;
    property P_TotalPrice: TDoubleArray read FP_TotalPrice write FP_TotalPrice;
    property P_DgnQuantity1: TDoubleArray read FP_DgnQuantity1 write FP_DgnQuantity1;
    property P_DgnQuantity2: TDoubleArray read FP_DgnQuantity2 write FP_DgnQuantity2;

    property GatherP_TotalPrice: Double read GetGatherP_TotalPrice;

    property ProjectCount: Integer read FProjectCount;
  end;

  TStringArray = array of string;

  TReportCacheTree = class(TCacheTree)
  private
    FProjectCount: Integer;
    FGatherCacheNode: TReportCacheNode;
    FProjectName: TStringArray;

    function GetNewNode(AProjectCount: Integer): TReportCacheNode; overload;
  public
    constructor Create(AProjectCount: Integer);
    destructor Destroy; override;

    function AddNode(AParent: TCacheNode; ANextSibling: TCacheNode = nil): TReportCacheNode;

    function FindNextSibling(AParent: TCacheNode; ACode, AB_Code: string): TReportCacheNode;
    function FindNode(AParent: TCacheNode; ACode, AB_Code: string): TReportCacheNode; overload;
    function FindNode(AParent: TCacheNode; AName: string): TReportCacheNode; overload;
    function FindNode(AParent: TCacheNode; ACode, AB_Code, AName: string): TReportCacheNode; overload;

    procedure ReCalcGatherData;
    // 调用此方法先须先调用ReCalcGatherData
    // RatioPercent = 金额/总金额，这里的总金额取GatherCacheNode的金额，故须先汇总计算GatherCacheNode。
    procedure ReCalcRatioPercent;

    // Only for Debugging lot of Data
    procedure SaveTreeToFile(const AFileName: string);

    property ProjectCount: Integer read FProjectCount;
    property GatherCacheNode: TReportCacheNode read FGatherCacheNode;
    property ProjectName: TStringArray read FProjectName write FProjectName;
  end;

  TapDoubleArray = array [1..50] of Double;

  TAllPhaseCacheNode = class(TCacheNode)
  private
    FCode: string;
    FB_Code: string;
    FName: string;
    FUnits: string;
    FPrice: Double;
    FQuantity: Double;
    FTotalPrice: Double;
    FMemoStr: string;
  public
    FP_Quantity: TapDoubleArray;
    FP_TotalPrice: TapDoubleArray;

    property Code: string read FCode write FCode;
    property B_Code: string read FB_Code write FB_Code;
    property Name: string read FName write FName;
    property Units: string read FUnits write FUnits;
    property Price: Double read FPrice write FPrice;
    property Quantity: Double read FQuantity write FQuantity;
    property TotalPrice: Double read FTotalPrice write FTotalPrice;
    property MemoStr: string read FMemoStr write FMemoStr;
  end;

  // 仅用于汇总同一项目的不同期数据
  TAllPhaseCacheTree = class(TCacheTree)
  private
    function GetNewNode(AID: Integer): TAllPhaseCacheNode;
  public
    function AddNode(AID: Integer; AParent: TCacheNode; ANextSibling: TCacheNode = nil): TAllPhaseCacheNode;
    function FindNode(AID: Integer): TAllPhaseCacheNode;

    // Only for Debugging lot of Data
    procedure SaveTreeToFile(const AFileName: string);
  end;

implementation

uses
  SysUtils, UtilMethods;

{ TBillsCacheTree }
function TBillsCacheTree.AddNodeByCode(const ACode: string;
  AFixedID: Integer): TBillsCacheNode;
var
  Parent, NextSibling: TBillsCacheNode;
begin
  Result := FindNode(ACode);
  FLastNode := Result;
  if Assigned(Result) then Exit;

  Parent := FindParent(ACode);
  if AutoSort then
    NextSibling := FindNextSibling(ACode)
  else
    NextSibling := nil;
  Result := AddNode(Parent, NextSibling, AFixedID);
  Result.FLevelCode := ACode;
  FLastNode := Result;
end;

function TBillsCacheTree.FindNode(const ACode: string): TBillsCacheNode;
begin
  Result := FindNode(TBillsCacheNode(Root), ACode);
end;

function TBillsCacheTree.FindNextSibling(
  const ACode: string): TBillsCacheNode;
var
  Parent, Node: TBillsCacheNode;
  sCodeID, sCodeID2: string;
begin
  Parent := FindParent(ACode);
  if Assigned(Parent) then
    Node := TBillsCacheNode(Parent.FirstChild)
  else
    Node := TBillsCacheNode(Root.FirstChild);
  Result := nil;
  sCodeID := ConvertDigitCode(ACode, 3, '-');
  while Assigned(Node) do
  begin
    sCodeID2 := ConvertDigitCode(Node.LevelCode, 3, SeparateChar);
    if sCodeID < sCodeID2 then
    begin
      Result := Node;
      Break;
    end;
    Node := TBillsCacheNode(Node.NextSibling);
  end;
end;

function TBillsCacheTree.FindNode(AParent: TBillsCacheNode;
  const ACode: string): TBillsCacheNode;
begin
  Result := TBillsCacheNode(AParent.FirstChild);
  while Assigned(Result) do
  begin
    if Result.LevelCode = ACode then
      Break
    else if Pos(Result.LevelCode + SeparateChar, ACode) = 1 then
    begin
      Result := FindNode(Result, ACode);
      Break;
    end
    else
      Result := TBillsCacheNode(Result.NextSibling);
  end;
end;

function TBillsCacheTree.FindParent(const ACode: string): TBillsCacheNode;
var
  sCode: string;
begin
  Result := nil;
  sCode := GetPrefixOfCode(ACode, SeparateChar);
  while (Result = nil) and (sCode <> '') do
  begin
    Result := FindNode(sCode);
    sCode := GetPrefixOfCode(sCode, SeparateChar);
  end;
end;

function TBillsCacheTree.GetNewNode(AID: Integer): TBillsCacheNode;
begin
  if AID = -1 then
    Result := TBillsCacheNode.Create(Self, GetNewNodeID)
  else
    Result := TBillsCacheNode.Create(Self, AID);
  CacheNodes.Add(Result);
  if Result.ID < 100 then
    FFixedIDNodes.Add(Result);
end;

function TBillsCacheTree.AddNode(AParent, ANextSibling: TCacheNode;
  AFixedID: Integer): TBillsCacheNode;
begin
  Result := GetNewNode(AFixedID);
  if Assigned(ANextSibling) then
    ANextSibling.InsertPreSibling(Result)
  else if Assigned(AParent) then
    AParent.InsertChild(Result)
  else
    Root.InsertChild(Result);
end;

function TBillsCacheTree.AddLeafBillsNode(const AB_Code: string): TBillsCacheNode;

  function GetLastXmjParent: TBillsCacheNode;
  begin
    Result := TBillsCacheNode(FLastNode);
    while Assigned(Result) and (Result.B_Code <> '') do
      Result := TBillsCacheNode(Result.Parent);
  end;

var
  Parent: TBillsCacheNode;
begin
  Parent := GetLastXmjParent;
  Result := AddNodeByCode(Parent.Code + '-' + AB_Code, -1);
end;

procedure TBillsCacheTree.SetSeparateChar(const Value: Char);
var
  I: Integer;
  Node: TBillsCacheNode;
begin
  for I := 0 to CacheNodes.Count - 1 do
  begin
    Node := TBillsCacheNode(CacheNodes.Items[I]);
    Node.FLevelCode := StringReplace(Node.FLevelCode, FSeparateChar, Value, [rfReplaceAll]);
  end;
  FSeparateChar := Value;
end;

procedure TBillsCacheTree.SaveTreeToFile(const AFileName: string);
var
  sgs: TStringList;
  I: Integer;
  Node: TBillsCacheNode;
begin
  sgs := TStringList.Create;
  try
    for I := 0 to CacheNodes.Count - 1 do
    begin
      Node := TBillsCacheNode(CacheNodes.Items[I]);
      sgs.Add(Format('ID: %3d; ParentID: %3d; NextID: %3d; Code: %s; B_Code: %s; Name: %s;',
        [Node.ID, Node.ParentID, Node.NextSiblingID, Node.Code, Node.B_Code, Node.Name]));
    end;
    sgs.SaveToFile(AFileName);
  finally
    sgs.Free;
  end;
end;

function TBillsCacheTree.FindGclChild(AParent: TBillsCacheNode;
  const AB_Code, AName, AUnits: string; APrice: Double): TBillsCacheNode;
var
  vChild: TBillsCacheNode;
begin
  Result := nil;

  if Assigned(AParent) then
    vChild := TBillsCacheNode(AParent.FirstChild)
  else
    vChild := TBillsCacheNode(Root.FirstChild);

  while Assigned(vChild) and not Assigned(Result) do
  begin
    if SameText(AB_Code, vChild.B_Code) and
        SameText(AName, vChild.Name) and
        SameText(AUnits, vChild.Units) and
        (APrice = vChild.Price) then
      Result := vChild;
    vChild := TBillsCacheNode(vChild.NextSibling);
  end;
end;

function TBillsCacheTree.FindXmjChild(AParent: TBillsCacheNode;
  const ACode, AName: string): TBillsCacheNode;
var
  vChild: TBillsCacheNode;
begin
  Result := nil;

  if Assigned(AParent) then
    vChild := TBillsCacheNode(AParent.FirstChild)
  else
    vChild := TBillsCacheNode(Root.FirstChild);

  while Assigned(vChild) and not Assigned(Result) do
  begin
    if SameText(ACode, vChild.Code) and SameText(AName, vChild.Name) then
      Result := vChild;
    vChild := TBillsCacheNode(vChild.NextSibling);
  end;
end;

constructor TBillsCacheTree.Create;
begin
  inherited;
  FFixedIDNodes := TList.Create;
end;

destructor TBillsCacheTree.Destroy;
begin
  FFixedIDNodes.Free;
  inherited;
end;

function TBillsCacheTree.FindFixedIDNode(AID: Integer): TBillsCacheNode;
var
  iNode: Integer;
  vNode: TCacheNode;
begin
  Result := nil;
  for iNode := 0 to FFixedIDNodes.Count - 1 do
  begin
    vNode := TCacheNode(FFixedIDNodes.Items[iNode]);
    if vNode.ID = AID then
    begin
      Result := TBillsCacheNode(vNode);
      Break;
    end;
  end;
end;

{ TReportCacheNode }

constructor TReportCacheNode.Create(ACacheTree: TCacheTree; AID,
  AProjectCount: Integer);
begin
  inherited Create(ACacheTree, AID);
  FProjectCount := AProjectCount;
  SetLength(FP_Quantity, AProjectCount);
  SetLength(FP_Price, AProjectCount);
  SetLength(FP_TotalPrice, AProjectCount);
  SetLength(FP_DgnQuantity1, AProjectCount);
  SetLength(FP_DgnQuantity2, AProjectCount);
end;

function TReportCacheNode.GetAddGatherQuantity: Double;
begin
  Result := AddDealQuantity + AddQcQuantity;
end;

function TReportCacheNode.GetAddGatherTotalPrice: Double;
begin
  Result := AddDealTotalPrice + AddQcTotalPrice + AddPcTotalPrice;
end;

function TReportCacheNode.GetDoubleArrayTotal(
  ADoubleArray: TDoubleArray): Double;
var
  i: Integer;
begin
  Result := 0;
  for i := Low(ADoubleArray) to High(ADoubleArray) do
    Result := Result + ADoubleArray[i];
end;

function TReportCacheNode.GetGatherP_TotalPrice: Double;
begin
  Result := GetDoubleArrayTotal(FP_TotalPrice);
end;

procedure TReportCacheNode.ResolveCode;
var
  sgs: TStrings;
  i: Integer;
begin
  sgs := TStringList.Create;
  try
    sgs.Delimiter := '-';
    sgs.DelimitedText := FCode;
    FXiangCode := '';
    FMuCode := '';
    FJieCode := '';
    FXiMuCode := '';
    case sgs.Count of
      1: FXiangCode := '';
      2: FXiangCode := ChinessNum(StrToIntDef(sgs[1], 0));
      3: FMuCode := sgs[2];
      4: FJieCode := sgs[3];
      else
      begin
        for i := 4 to sgs.Count - 1 do
          if FXiMuCode = '' then
            FXiMuCode := sgs[i]
          else
            FXiMuCode := FXiMuCode + '-' + sgs[i];
      end;
    end;
  finally
    sgs.Free;
  end;
end;

procedure TReportCacheNode.SetCode(const Value: string);
begin
  FCode := Value;
  ResolveCode;
end;

{ TReportCacheTree }

function TReportCacheTree.AddNode(AParent,
  ANextSibling: TCacheNode): TReportCacheNode;
begin
  Result := GetNewNode(FProjectCount);
  if Assigned(ANextSibling) then
    ANextSibling.InsertPreSibling(Result)
  else if Assigned(AParent) then
    AParent.InsertChild(Result)
  else
    Root.InsertChild(Result);
end;

constructor TReportCacheTree.Create(AProjectCount: Integer);
begin
  inherited Create;
  FProjectCount := AProjectCount;
  FGatherCacheNode := TReportCacheNode.Create(nil, -2, AProjectCount);
  SetLength(FProjectName, AProjectCount);
end;

destructor TReportCacheTree.Destroy;
begin
  FGatherCacheNode.Free;
  inherited;
end;

function TReportCacheTree.FindNextSibling(AParent: TCacheNode; ACode,
  AB_Code: string): TReportCacheNode;
var
  Node: TReportCacheNode;
  sCodeID, sCodeID2, sB_CodeID, sB_CodeID2: string;
begin
  if Assigned(AParent) then
    Node := TReportCacheNode(AParent.FirstChild)
  else
    Node := TReportCacheNode(Root.FirstChild);
  Result := nil;
  if (ACode = '') and (AB_Code = '') then Exit;
  sCodeID := ConvertDigitCode(ACode, 3, '-');
  sB_CodeID := ConvertDigitCode(AB_Code, 4, '-');
  while Assigned(Node) do
  begin
    sCodeID2 := ConvertDigitCode(Node.Code, 3, '-');
    sB_CodeID2 := ConvertDigitCode(Node.B_Code, 4, '-');
    if sCodeID < sCodeID2 then
    begin
      Result := Node;
      Break;
    end
    else if sB_CodeID < sB_CodeID2 then
    begin
      Result := Node;
      Break;
    end;
    Node := TReportCacheNode(Node.NextSibling);
  end;
end;

function TReportCacheTree.FindNode(AParent: TCacheNode; ACode,
  AB_Code: string): TReportCacheNode;
var
  Node: TReportCacheNode;
begin
  if Assigned(AParent) then
    Node := TReportCacheNode(AParent.FirstChild)
  else
    Node := TReportCacheNode(Root.FirstChild);
  Result := nil;
  while Assigned(Node) do
  begin
    if (Node.Code = ACode) and (Node.B_Code = AB_Code) then
    begin
      Result := Node;
      Break;
    end;
    Node := TReportCacheNode(Node.NextSibling);
  end;
end;

function TReportCacheTree.FindNode(AParent: TCacheNode;
  AName: string): TReportCacheNode;
var
  Node: TReportCacheNode;
begin
  if Assigned(AParent) then
    Node := TReportCacheNode(AParent.FirstChild)
  else
    Node := TReportCacheNode(Root.FirstChild);
  Result := nil;
  while Assigned(Node) do
  begin
    if SameText(Node.Name, AName) then
    begin
      Result := Node;
      Break;
    end;
    Node := TReportCacheNode(Node.NextSibling);
  end;
end;

function TReportCacheTree.FindNode(AParent: TCacheNode; ACode, AB_Code,
  AName: string): TReportCacheNode;
var
  Node: TReportCacheNode;
begin
  if Assigned(AParent) then
    Node := TReportCacheNode(AParent.FirstChild)
  else
    Node := TReportCacheNode(Root.FirstChild);
  Result := nil;
  while Assigned(Node) do
  begin
    if SameText(Node.Code, ACode) and SameText(Node.B_Code, AB_Code)
        and SameText(Node.Name, AName) then
    begin
      Result := Node;
      Break;
    end;
    Node := TReportCacheNode(Node.NextSibling);
  end;
end;

function TReportCacheTree.GetNewNode(
  AProjectCount: Integer): TReportCacheNode;
begin
  Result := TReportCacheNode.Create(Self, GetNewNodeID, AProjectCount);
  CacheNodes.Add(Result);
end;

procedure TReportCacheTree.ReCalcGatherData;
var
  i: Integer;
  CacheNode: TReportCacheNode;
begin
  FGatherCacheNode.Free;
  FGatherCacheNode := TReportCacheNode.Create(nil, -2, FProjectCount);

  CacheNode := TReportCacheNode(FirstNode);
  while Assigned(CacheNode) do
  begin
    FGatherCacheNode.TotalPrice := FGatherCacheNode.TotalPrice + CacheNode.TotalPrice;

    FGatherCacheNode.AddDealTotalPrice := FGatherCacheNode.AddDealTotalPrice + CacheNode.AddDealTotalPrice;
    FGatherCacheNode.AddQcTotalPrice := FGatherCacheNode.AddQcTotalPrice + CacheNode.AddQcTotalPrice;
    FGatherCacheNode.AddPcTotalPrice := FGatherCacheNode.AddPcTotalPrice + CacheNode.AddPcTotalPrice;

    FGatherCacheNode.PDTotalPrice := FGatherCacheNode.PDTotalPrice + CacheNode.PDTotalPrice;
    FGatherCacheNode.CDDTotalPrice := FGatherCacheNode.CDDTotalPrice + CacheNode.CDDTotalPrice;
    FGatherCacheNode.ABTotalPrice := FGatherCacheNode.ABTotalPrice + CacheNode.ABTotalPrice;

    for i := 0 to FProjectCount - 1 do
      FGatherCacheNode.P_TotalPrice[i] := FGatherCacheNode.P_TotalPrice[i] + CacheNode.P_TotalPrice[i];
    CacheNode := TReportCacheNode(CacheNode.NextSibling);
  end;
end;

procedure TReportCacheTree.ReCalcRatioPercent;
var
  i: Integer;
  CacheNode: TReportCacheNode;
begin
  for i := 0 to CacheNodes.Count - 1 do
  begin
    CacheNode := TReportCacheNode(CacheNodes.Items[i]);
    if GatherCacheNode.TotalPrice <> 0 then
      CacheNode.RatioPercent := AdvRoundTo(CacheNode.TotalPrice/GatherCacheNode.TotalPrice*100);
    if GatherCacheNode.AddGatherTotalPrice <> 0 then
      CacheNode.AddRatioPercent := AdvRoundTo(CacheNode.AddGatherTotalPrice/GatherCacheNode.AddGatherTotalPrice*100);
  end;
end;

procedure TReportCacheTree.SaveTreeToFile(const AFileName: string);
var
  sgs: TStringList;
  I: Integer;
  Node: TReportCacheNode;
begin
  sgs := TStringList.Create;
  try
    for I := 0 to CacheNodes.Count - 1 do
    begin
      Node := TReportCacheNode(CacheNodes.Items[I]);
      sgs.Add(Format('ID: %3d; ParentID: %3d; NextID: %3d; Code: %s; B_Code: %s; Name: %s;',
        [Node.ID, Node.ParentID, Node.NextSiblingID, Node.Code, Node.B_Code, Node.Name]));
    end;
    sgs.SaveToFile(AFileName);
  finally
    sgs.Free;
  end;
end;

{ TAllPhaseCacheTree }

function TAllPhaseCacheTree.AddNode(AID: Integer; AParent,
  ANextSibling: TCacheNode): TAllPhaseCacheNode;
begin
  Result := GetNewNode(AID);
  if Assigned(ANextSibling) then
    ANextSibling.InsertPreSibling(Result)
  else if Assigned(AParent) then
    AParent.InsertChild(Result)
  else
    Root.InsertChild(Result);
end;

function TAllPhaseCacheTree.FindNode(AID: Integer): TAllPhaseCacheNode;
var
  i: Integer;
  Node: TAllPhaseCacheNode;
begin
  Result := nil;
  for i := 0 to CacheNodes.Count - 1 do
  begin
    Node := TAllPhaseCacheNode(CacheNodes.Items[i]);
    if Node.ID = AID then
    begin
      Result := Node;
      Break;
    end;
  end;
end;

function TAllPhaseCacheTree.GetNewNode(
  AID: Integer): TAllPhaseCacheNode;
begin
  Result := TAllPhaseCacheNode.Create(Self, AID);
  CacheNodes.Add(Result);
end;

procedure TAllPhaseCacheTree.SaveTreeToFile(const AFileName: string);
var
  sgs: TStringList;
  I: Integer;
  Node: TAllPhaseCacheNode;
begin
  sgs := TStringList.Create;
  try
    for I := 0 to CacheNodes.Count - 1 do
    begin
      Node := TAllPhaseCacheNode(CacheNodes.Items[I]);
      sgs.Add(Format('ID: %3d; ParentID: %3d; NextID: %3d; Code: %s; B_Code: %s; Name: %s;',
        [Node.ID, Node.ParentID, Node.NextSiblingID, Node.Code, Node.B_Code, Node.Name]));
    end;
    sgs.SaveToFile(AFileName);
  finally
    sgs.Free;
  end;
end;

{ TGclCacheTree }

function TGclCacheTree.AddNodeByB_Code(
  const AB_Code: string): TGclCacheNode;

  function FindParent: TGclCacheNode;
  begin
    Result := FLastNode;
    while Assigned(Result) and (Result <> Root) and (Result.B_Code <> '') and (Pos(Result.B_Code + '-', AB_Code) <> 1) do
      Result := TGclCacheNode(Result.Parent);
  end;

var
  vParent: TGclCacheNode;
begin
  vParent := FindParent;
  Result := TGclCacheNode(AddNode(vParent));
  FLastNode := Result;
end;

function TGclCacheTree.AddNodeByData(const AB_Code,
  AName: string): TGclCacheNode;
begin
  if AB_Code = '' then
    Result := AddNodeByName(AName)
  else
    Result := AddNodeByB_Code(AB_Code);
end;

function TGclCacheTree.AddNodeByName(const AName: string): TGclCacheNode;
begin
  if Pos('第100章至', AName) <> 0 then
  begin
    Result := TGclCacheNode(AddNode(nil));
    FLastBlank1 := Result;
  end
  else
    Result := TGclCacheNode(AddNode(FLastBlank1));
  FLastNode := Result;
end;

function TGclCacheTree.GetNewNode: TCacheNode;
begin
  Result := TGclCacheNode.Create(Self, GetNewNodeID);
  CacheNodes.Add(Result);
end;

procedure TGclCacheTree.SaveTreeToFile(const AFileName: string);
var
  sgs: TStringList;
  I: Integer;
  Node: TGclCacheNode;
begin
  sgs := TStringList.Create;
  try
    for I := 0 to CacheNodes.Count - 1 do
    begin
      Node := TGclCacheNode(CacheNodes.Items[I]);
      sgs.Add(Format('ID: %3d; ParentID: %3d; NextID: %3d; B_Code: %s; Name: %s;',
        [Node.ID, Node.ParentID, Node.NextSiblingID, Node.B_Code, Node.Name]));
    end;
    sgs.SaveToFile(AFileName);
  finally
    sgs.Free;
  end;
end;

end.
