Codeunit 5870 Calculate BOM Tree

App
Base Application
Namespace
Microsoft.Inventory.BOM.Tree
Versions
17-28

Procedures, 37Events, 43Obsolete, 24

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Inventory/BOM/Tree/CalculateBOMTree.Codeunit.al1151 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Inventory.BOM.Tree;

using Microsoft.Foundation.Enums;
using Microsoft.Foundation.UOM;
using Microsoft.Inventory.Availability;
using Microsoft.Inventory.BOM;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Setup;

codeunit 5870 "Calculate BOM Tree"
{

    trigger OnRun()
    begin
    end;

    var
        TempItemAvailByDate: Record "Item Availability by Date" temporary;
        TempMemoizedResult: Record "Memoized Result" temporary;
        ItemFilter: Record Item;
        TempItem: Record Item temporary;
        AvailableToPromise: Codeunit "Available to Promise";
        UOMMgt: Codeunit "Unit of Measure Management";
        Window: Dialog;
        WindowUpdateDateTime: DateTime;
        LocationSpecific: Boolean;
        HideDialog: Boolean;
        EntryNo: Integer;
        AvailToUse: Option UpdatedQtyOnItemAvail,QtyOnItemAvail,QtyAvail;
        MarkBottleneck: Boolean;
        ShowTotalAvailability: Boolean;
        GlobalTreeType: Enum "BOM Tree Type";

#pragma warning disable AA0074
        Text000: Label 'Generating Tree @1@@@@@@@';
#pragma warning restore AA0074

    local procedure OpenWindow()
    begin
        if HideDialog or not GuiAllowed() then
            exit;

        Window.Open(Text000);
        WindowUpdateDateTime := CurrentDateTime;
    end;

    local procedure UpdateWindow(ProgressValue: Integer)
    begin
        if HideDialog or not GuiAllowed() then
            exit;

        if CurrentDateTime - WindowUpdateDateTime >= 300 then begin
            WindowUpdateDateTime := CurrentDateTime;
            Window.Update(1, ProgressValue);
        end;
    end;

    procedure InitVars()
    begin
        TempItemAvailByDate.Reset();
        TempItemAvailByDate.DeleteAll();
        TempMemoizedResult.Reset();
        TempMemoizedResult.DeleteAll();
        TempItem.Reset();
        TempItem.DeleteAll();
    end;

    procedure InitBOMBuffer(var BOMBuffer: Record "BOM Buffer")
    begin
        BOMBuffer.Reset();
        BOMBuffer.DeleteAll();
    end;

    procedure InitTreeType(NewTreeType: Enum "BOM Tree Type")
    begin
        GlobalTreeType := NewTreeType;
    end;

    procedure GetTreeType(): Enum "BOM Tree Type"
    begin
        exit(GlobalTreeType);
    end;

#if not CLEAN27
    [Obsolete('Replaced by procedure GenerateTreeForManyItems', '27.0')]
    procedure GenerateTreeForItems(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; TreeType: Option " ",Availability,Cost)
    begin
        GenerateTreeForManyItems(ParentItem, BOMBuffer, "BOM Tree Type".FromInteger(TreeType));
    end;
#endif

    procedure GenerateTreeForManyItems(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; TreeType: Enum "BOM Tree Type")
    var
        i: Integer;
        NoOfRecords: Integer;
        DemandDate: Date;
        IsHandled: Boolean;
    begin
        OnBeforeGenerateTreeForItems(HideDialog);

        OpenWindow();

        IsHandled := false;
        OnBeforeInitBOMBuffer(BOMBuffer, IsHandled);
        if not IsHandled then
            InitBOMBuffer(BOMBuffer);
        InitTreeType(TreeType);
        ItemFilter.Copy(ParentItem);

        if ParentItem.GetFilter(ParentItem."Date Filter") <> '' then
            DemandDate := ParentItem.GetRangeMax(ParentItem."Date Filter")
        else
            DemandDate := 99981231D;
        NoOfRecords := ParentItem.Count;
        if ParentItem.FindSet() then
            repeat
                i += 1;
                UpdateWindow(Round(i / NoOfRecords * 10000, 1));
                GenerateTreeForItemLocal(ParentItem, BOMBuffer, DemandDate, TreeType);
            until ParentItem.Next() = 0;

        ParentItem.Copy(ItemFilter);

        if not HideDialog and GuiAllowed() then
            Window.Close();
    end;

#if not CLEAN27
    [Obsolete('Replaced by procedure GenerateTreeForOneItem()', '27.0')]
    procedure GenerateTreeForItem(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; DemandDate: Date; TreeType: Option)
    begin
        GenerateTreeForOneItem(ParentItem, BOMBuffer, DemandDate, "BOM Tree Type".FromInteger(TreeType));
    end;
#endif

    procedure GenerateTreeForOneItem(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; DemandDate: Date; TreeType: Enum "BOM Tree Type")
    begin
        ItemFilter.Copy(ParentItem);

        ParentItem.Get(ParentItem."No.");
        InitBOMBuffer(BOMBuffer);
        InitTreeType(TreeType);
        GenerateTreeForItemLocal(ParentItem, BOMBuffer, DemandDate, TreeType);
        ParentItem.Copy(ItemFilter);
    end;

    local procedure GenerateTreeForItemLocal(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; DemandDate: Date; TreeType: Enum "BOM Tree Type")
    var
        TreeTypeOption: Option;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeGenerateTreeForItemLocal(ParentItem, DemandDate, TreeType.AsInteger(), BOMBuffer, IsHandled);
        if IsHandled then
            exit;

        InitVars();

        if ParentItem.HasBOM() or (ParentItem.HasRoutingNo()) then begin
            IsHandled := false;
            OnBeforeFilterBOMBuffer(ParentItem, BOMBuffer, DemandDate, TreeType.AsInteger(), IsHandled);
            if not IsHandled then begin
                BOMBuffer.SetLocationVariantFiltersFrom(ItemFilter);
                BOMBuffer.TransferFromItem(EntryNo, ParentItem, DemandDate);
                GenerateItemSubTree(ParentItem."No.", BOMBuffer);
                TreeTypeOption := TreeType.AsInteger();
                OnGenerateTreeForItemLocalOnBeforeCalculateTreeType(ParentItem, BOMBuffer, TreeTypeOption, EntryNo);
                TreeType := "BOM Tree Type".FromInteger(TreeTypeOption);
                CalculateTreeType(BOMBuffer, ShowTotalAvailability, TreeType);
                OnAfterFilterBOMBuffer(ParentItem, BOMBuffer, DemandDate, TreeType.AsInteger());
            end;
        end;
    end;

    procedure GenerateTreeForSource(SourceRecordVar: Variant; var BOMBuffer: Record "BOM Buffer"; BOMTreeType: Enum "BOM Tree Type"; ShowBy: Enum "BOM Structure Show By"; DemandDate: Date)
    begin
        OnGenerateTreeForSource(SourceRecordVar, BOMBuffer, BOMTreeType, ShowBy, DemandDate, ItemFilter, EntryNo);
    end;

#if not CLEAN27
    [Obsolete('Replaced by procedure GenerateTreeForAssemblyHeader()', '27.0')]
    procedure GenerateTreeForAsm(AsmHeader: Record Microsoft.Assembly.Document."Assembly Header"; var BOMBuffer: Record "BOM Buffer"; TreeType: Option)
    begin
    end;
#endif

#if not CLEAN27
    [Obsolete('Moved to codeunit AsmCalculateBOMTree', '27.0')]
    procedure GenerateTreeForAssemblyHeader(AsmHeader: Record Microsoft.Assembly.Document."Assembly Header"; var BOMBuffer: Record "BOM Buffer"; TreeType: Enum "BOM Tree Type")
    begin
    end;
#endif

#if not CLEAN27
    [Obsolete('Replaced by procedure GenerateTreeForProdOrderLine', '27.0')]
    procedure GenerateTreeForProdLine(ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line"; var BOMBuffer: Record "BOM Buffer"; TreeType: Option)
    begin
        GenerateTreeForProdOrderLine(ProdOrderLine, BOMBuffer, "BOM Tree Type".FromInteger(TreeType));
    end;
#endif

#if not CLEAN27
    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    procedure GenerateTreeForProdOrderLine(ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line"; var BOMBuffer: Record "BOM Buffer"; TreeType: Enum "BOM Tree Type")
    begin
    end;
#endif

    procedure CalculateTreeType(var BOMBuffer: Record "BOM Buffer"; ShowTotalAvailability2: Boolean; TreeType: Enum "BOM Tree Type")
    begin
        case TreeType of
            "BOM Tree Type"::Availability:
                UpdateAvailability(BOMBuffer, ShowTotalAvailability2);
            "BOM Tree Type"::Cost:
                UpdateCost(BOMBuffer);
        end;
    end;

    procedure GenerateItemSubTree(ItemNo: Code[20]; var BOMBuffer: Record "BOM Buffer"): Boolean
    var
        ParentItem: Record Item;
    begin
        ParentItem.Get(ItemNo);
        OnGenerateItemSubTreeOnAfterParentItemGet(ParentItem);
        if TempItem.Get(ItemNo) then begin
            BOMBuffer."Is Leaf" := false;
            BOMBuffer.Modify(true);
            exit(false);
        end;
        TempItem := ParentItem;
        TempItem.Insert();

        if ParentItem.IsMfgItem() then begin
            OnGenerateItemSubTreeOnSetIsLeaf(ParentItem, BOMBuffer, ItemFilter, EntryNo);
            if BOMBuffer."Is Leaf" then
                BOMBuffer."Is Leaf" := not GenerateBOMCompSubTree(ParentItem, BOMBuffer);
        end else begin
            BOMBuffer."Is Leaf" := not GenerateBOMCompSubTree(ParentItem, BOMBuffer);
            if BOMBuffer."Is Leaf" then
                OnGenerateItemSubTreeOnSetIsLeaf(ParentItem, BOMBuffer, ItemFilter, EntryNo);
        end;
        BOMBuffer.Modify(true);

        TempItem.Get(ItemNo);
        TempItem.Delete();
        exit(not BOMBuffer."Is Leaf");
    end;

    local procedure GenerateBOMCompSubTree(ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"): Boolean
    var
        BOMComp: Record "BOM Component";
        ParentBOMBuffer: Record "BOM Buffer";
        IsHandled: Boolean;
    begin
        ParentBOMBuffer := BOMBuffer;
        BOMComp.SetRange("Parent Item No.", ParentItem."No.");
        if BOMComp.FindSet() then begin
            if not ParentItem.IsAssemblyItem() then
                exit(true);

            IsHandled := false;
            OnGenerateBOMCompSubTreeOnBeforeLoopBOMComponents(ParentItem, IsHandled);
            if IsHandled then
                exit(true);
            repeat
                if (BOMComp."No." <> '') and ((BOMComp.Type = BOMComp.Type::Item) or (GlobalTreeType in [GlobalTreeType::" ", GlobalTreeType::Cost])) then begin
                    BOMBuffer.SetLocationVariantFiltersFrom(ItemFilter);
                    BOMBuffer.TransferFromBOMComp(
                      EntryNo, BOMComp, ParentBOMBuffer.Indentation + 1,
                      Round(
                        ParentBOMBuffer."Qty. per Top Item" *
                        UOMMgt.GetQtyPerUnitOfMeasure(ParentItem, ParentBOMBuffer."Unit of Measure Code"), UOMMgt.QtyRndPrecision()),
                      Round(
                        ParentBOMBuffer."Scrap Qty. per Top Item" *
                        UOMMgt.GetQtyPerUnitOfMeasure(ParentItem, ParentBOMBuffer."Unit of Measure Code"), UOMMgt.QtyRndPrecision()),
                      CalcCompDueDate(ParentBOMBuffer."Needed by Date", ParentItem, BOMComp."Lead-Time Offset"),
                      ParentBOMBuffer."Location Code");
                    if BOMComp.Type = BOMComp.Type::Item then
                        GenerateItemSubTree(BOMComp."No.", BOMBuffer);
                end;
            until BOMComp.Next() = 0;
            BOMBuffer := ParentBOMBuffer;
            exit(true);
        end;
    end;

    local procedure UpdateMinAbleToMake(var BOMBuffer: Record "BOM Buffer"; AvailToUse2: Option UpdatedQtyOnItemAvail,QtyOnItemAvail,QtyAvail): Decimal
    var
        AvailQty: Decimal;
    begin
        TempItemAvailByDate.SetRange("Item No.", BOMBuffer."No.");
        TempItemAvailByDate.SetRange("Variant Code", BOMBuffer."Variant Code");
        if LocationSpecific then
            TempItemAvailByDate.SetRange("Location Code", BOMBuffer."Location Code");
        TempItemAvailByDate.SetRange(Date, BOMBuffer."Needed by Date");
        TempItemAvailByDate.FindFirst();

        case AvailToUse2 of
            AvailToUse2::UpdatedQtyOnItemAvail:
                AvailQty := TempItemAvailByDate."Updated Available Qty";
            AvailToUse2::QtyOnItemAvail:
                AvailQty := TempItemAvailByDate."Available Qty";
            AvailToUse2::QtyAvail:
                AvailQty := BOMBuffer."Available Quantity";
        end;

        if BOMBuffer."Calculation Formula" = BOMBuffer."Calculation Formula"::"Fixed Quantity" then
            exit(MinAbleToMakeWithFixedQuantity(BOMBuffer, AvailQty))
        else begin
            BOMBuffer.UpdateAbleToMake(AvailQty);
            BOMBuffer.Modify();
            exit(BOMBuffer."Able to Make Top Item");
        end;
    end;

    local procedure MinAbleToMakeWithFixedQuantity(var BOMBuffer: Record "BOM Buffer"; AvailableQty: Decimal): Decimal
    begin
        if BOMBuffer."Calculation Formula" = BOMBuffer."Calculation Formula"::"Fixed Quantity" then begin
            UpdateAvailabilityForFixedQty(BOMBuffer, AvailableQty);
            if AvailableQty < BOMBuffer."Qty. per Parent" then
                exit(0)
            else
                exit(999999999);
        end;
    end;

    local procedure UpdateAvailabilityForFixedQty(var BOMBuffer: Record "BOM Buffer"; AvailableQty: Decimal)
    begin
        if BOMBuffer."Calculation Formula" = BOMBuffer."Calculation Formula"::"Fixed Quantity" then begin
            BOMBuffer."Available Quantity" := AvailableQty;
            BOMBuffer.Modify();
        end;
    end;

    local procedure CalcMinAbleToMake(IsFirst: Boolean; OldMin: Decimal; NewMin: Decimal): Decimal
    begin
        if NewMin <= 0 then
            exit(0);
        if IsFirst then
            exit(NewMin);
        if NewMin < OldMin then
            exit(NewMin);
        exit(OldMin);
    end;

    local procedure InitItemAvailDates(var BOMBuffer: Record "BOM Buffer")
    var
        BOMItem: Record Item;
        ParentBOMBuffer: Record "BOM Buffer";
        ZeroDF: DateFormula;
    begin
        ParentBOMBuffer := BOMBuffer;
        TempItemAvailByDate.Reset();
        TempItemAvailByDate.DeleteAll();
        Evaluate(ZeroDF, '<0D>');

        repeat
            if not AvailByDateExists(BOMBuffer) then begin
                BOMItem.CopyFilters(ItemFilter);
                BOMItem.Get(BOMBuffer."No.");
                BOMItem.SetRange("Date Filter", 0D, BOMBuffer."Needed by Date");
                if BOMBuffer.Indentation = 0 then begin
                    BOMItem.SetFilter("Variant Filter", ItemFilter.GetFilter("Variant Filter"));
                    BOMItem.SetFilter("Location Filter", ItemFilter.GetFilter("Location Filter"));
                end else
                    BOMItem.SetRange("Variant Filter", BOMBuffer."Variant Code");

                TempItemAvailByDate.Init();
                TempItemAvailByDate."Item No." := BOMBuffer."No.";
                TempItemAvailByDate.Date := BOMBuffer."Needed by Date";
                TempItemAvailByDate."Variant Code" := BOMBuffer."Variant Code";
                if LocationSpecific then
                    TempItemAvailByDate."Location Code" := BOMBuffer."Location Code";

                Clear(AvailableToPromise);
                OnInitItemAvailDatesOnBeforeCalcAvailableQty(BOMItem);
                TempItemAvailByDate."Available Qty" :=
                  AvailableToPromise.CalcQtyAvailabletoPromise(
                      BOMItem, BOMBuffer."Gross Requirement", BOMBuffer."Scheduled Receipts", BOMBuffer."Needed by Date", "Analysis Period Type"::Day, ZeroDF);
                TempItemAvailByDate."Updated Available Qty" := TempItemAvailByDate."Available Qty";
                TempItemAvailByDate.Insert();

                BOMBuffer.Modify();
            end;
        until (BOMBuffer.Next() = 0) or (BOMBuffer.Indentation <= ParentBOMBuffer.Indentation);
        BOMBuffer := ParentBOMBuffer;
        BOMBuffer.Find();
    end;

    local procedure UpdateAvailability(var BOMBuffer: Record "BOM Buffer"; ShowTotalAvailability2: Boolean)
    var
        CopyOfBOMBuffer: Record "BOM Buffer";
        SubOptimalQty: Decimal;
        OptimalQty: Decimal;
    begin
        CopyOfBOMBuffer.Copy(BOMBuffer);
        BOMBuffer.SetRange("Inventoriable", true);
        if BOMBuffer.Find() then
            repeat
                if BOMBuffer.Indentation = 0 then begin
                    InitItemAvailDates(BOMBuffer);
                    SubOptimalQty := TraverseTree(BOMBuffer, AvailToUse::QtyOnItemAvail);
                    TempMemoizedResult.DeleteAll();
                    OptimalQty := BinarySearchOptimal(BOMBuffer, UOMMgt.QtyRndPrecision(), SubOptimalQty);
                    MarkBottlenecks(BOMBuffer, OptimalQty);
                    CalcAvailability(BOMBuffer, OptimalQty, false);
                    if ShowTotalAvailability2 then
                        DistributeRemainingAvail(BOMBuffer);
                    TraverseTree(BOMBuffer, AvailToUse::QtyAvail);
                end;
            until BOMBuffer.Next() = 0;
        BOMBuffer.SetRange("Inventoriable");
        BOMBuffer.Copy(CopyOfBOMBuffer);
    end;

    local procedure TraverseTree(var BOMBuffer: Record "BOM Buffer"; AvailToUse2: Option UpdatedQtyOnItemAvail,QtyOnItemAvail,QtyAvail): Decimal
    var
        ParentBOMBuffer: Record "BOM Buffer";
        IsFirst: Boolean;
        MinAbleToMakeQty: Decimal;
        MinAbleToMakeTopItem: Decimal;
        IsHandled: Boolean;
    begin
        ParentBOMBuffer := BOMBuffer;
        IsFirst := true;
        while (BOMBuffer.Next() <> 0) and (ParentBOMBuffer.Indentation < BOMBuffer.Indentation) do
            if ParentBOMBuffer.Indentation + 1 = BOMBuffer.Indentation then begin
                if not BOMBuffer."Is Leaf" then
                    TraverseTree(BOMBuffer, AvailToUse2)
                else begin
                    MinAbleToMakeQty := UpdateMinAbleToMake(BOMBuffer, AvailToUse2);
                    MinAbleToMakeTopItem := CalcMinAbleToMake(IsFirst, MinAbleToMakeTopItem, MinAbleToMakeQty);
                end;

                IsHandled := false;
                OnTraverseTreeOnBeforeCalcAbleToMakeParentAndTopItem(BOMBuffer, ParentBOMBuffer, IsHandled);
                if not IsHandled then
                    if BOMBuffer."Calculation Formula" = BOMBuffer."Calculation Formula"::"Fixed Quantity" then begin
                        ParentBOMBuffer."Able to Make Parent" := CalcMinAbleToMake(IsFirst, ParentBOMBuffer."Able to Make Parent", MinAbleToMakeTopItem);
                        ParentBOMBuffer."Able to Make Top Item" := CalcMinAbleToMake(IsFirst, ParentBOMBuffer."Able to Make Top Item", MinAbleToMakeTopItem);
                    end
                    else begin
                        ParentBOMBuffer."Able to Make Parent" := CalcMinAbleToMake(IsFirst, ParentBOMBuffer."Able to Make Parent", BOMBuffer."Able to Make Parent");
                        MinAbleToMakeTopItem := CalcMinAbleToMake(IsFirst, ParentBOMBuffer."Able to Make Top Item", BOMBuffer."Able to Make Top Item");
                        ParentBOMBuffer."Able to Make Top Item" := MinAbleToMakeTopItem;
                    end;
                IsFirst := false;
            end;
        if IsFirst then begin
            ParentBOMBuffer."Able to Make Parent" := 0;
            ParentBOMBuffer."Able to Make Top Item" := 0;
        end;

        BOMBuffer := ParentBOMBuffer;
        UpdateMinAbleToMake(BOMBuffer, AvailToUse2);
        exit(MinAbleToMakeTopItem);
    end;

    local procedure UpdateCost(var BOMBuffer: Record "BOM Buffer")
    var
        CopyOfBOMBuffer: Record "BOM Buffer";
    begin
        CopyOfBOMBuffer.Copy(BOMBuffer);
        if BOMBuffer.Find() then
            repeat
                if BOMBuffer.Indentation = 0 then
                    TraverseCostTree(BOMBuffer);
            until BOMBuffer.Next() = 0;
        BOMBuffer.Copy(CopyOfBOMBuffer);
    end;

    local procedure TraverseCostTree(var BOMBuffer: Record "BOM Buffer"): Decimal
    var
        ParentBOMBuffer: Record "BOM Buffer";
    begin
        ParentBOMBuffer := BOMBuffer;
        while (BOMBuffer.Next() <> 0) and (ParentBOMBuffer.Indentation < BOMBuffer.Indentation) do
            if (ParentBOMBuffer.Indentation + 1 = BOMBuffer.Indentation) and
               ((BOMBuffer."Qty. per Top Item" <> 0) or (BOMBuffer.Type in [BOMBuffer.Type::"Machine Center", BOMBuffer.Type::"Work Center"]))
            then begin
                OnTraverseCostTreeOnBeforeAddCosts(BOMBuffer, ParentBOMBuffer);
                if not BOMBuffer."Is Leaf" then
                    TraverseCostTree(BOMBuffer)
                else
                    if (BOMBuffer.Type = BOMBuffer.Type::Resource) and (BOMBuffer."Resource Usage Type" = BOMBuffer."Resource Usage Type"::Fixed) then
                        UpdateNodeCosts(BOMBuffer, ParentBOMBuffer."Lot Size" / ParentBOMBuffer."Qty. per Top Item")
                    else
                        UpdateNodeCosts(BOMBuffer, 1);

                if BOMBuffer."Is Leaf" then begin
                    ParentBOMBuffer.AddMaterialCost(BOMBuffer."Single-Level Material Cost", BOMBuffer."Rolled-up Material Cost");
                    ParentBOMBuffer.AddNonInvMaterialCost(BOMBuffer."Single-Lvl Mat. Non-Invt. Cost", BOMBuffer."Rolled-up Mat. Non-Invt. Cost");
                    ParentBOMBuffer.AddCapacityCost(BOMBuffer."Single-Level Capacity Cost", BOMBuffer."Rolled-up Capacity Cost");
                    ParentBOMBuffer.AddSubcontrdCost(BOMBuffer."Single-Level Subcontrd. Cost", BOMBuffer."Rolled-up Subcontracted Cost");
                    ParentBOMBuffer.AddCapOvhdCost(BOMBuffer."Single-Level Cap. Ovhd Cost", BOMBuffer."Rolled-up Capacity Ovhd. Cost");
                    ParentBOMBuffer.AddMfgOvhdCost(BOMBuffer."Single-Level Mfg. Ovhd Cost", BOMBuffer."Rolled-up Mfg. Ovhd Cost");
                    ParentBOMBuffer.AddScrapCost(BOMBuffer."Single-Level Scrap Cost", BOMBuffer."Rolled-up Scrap Cost");
                end else begin
                    ParentBOMBuffer.AddMaterialCost(
                      BOMBuffer."Single-Level Material Cost" +
                      BOMBuffer."Single-Lvl Mat. Non-Invt. Cost" +
                      BOMBuffer."Single-Level Capacity Cost" +
                      BOMBuffer."Single-Level Subcontrd. Cost" +
                      BOMBuffer."Single-Level Cap. Ovhd Cost" +
                      BOMBuffer."Single-Level Mfg. Ovhd Cost",
                      BOMBuffer."Rolled-up Material Cost");
                    ParentBOMBuffer.AddNonInvMaterialCost(0, BOMBuffer."Rolled-up Mat. Non-Invt. Cost");
                    ParentBOMBuffer.AddCapacityCost(0, BOMBuffer."Rolled-up Capacity Cost");
                    ParentBOMBuffer.AddSubcontrdCost(0, BOMBuffer."Rolled-up Subcontracted Cost");
                    ParentBOMBuffer.AddCapOvhdCost(0, BOMBuffer."Rolled-up Capacity Ovhd. Cost");
                    ParentBOMBuffer.AddMfgOvhdCost(0, BOMBuffer."Rolled-up Mfg. Ovhd Cost");
                    ParentBOMBuffer.AddScrapCost(0, BOMBuffer."Rolled-up Scrap Cost");
                end;
                OnTraverseCostTreeOnAfterAddCosts(ParentBOMBuffer, BOMBuffer);
            end;

        BOMBuffer := ParentBOMBuffer;
        OnTraverseCostTreeOnBeforeLastUpdateNodeCosts(BOMBuffer, ParentBOMBuffer);
        UpdateNodeCosts(BOMBuffer, ParentBOMBuffer."Lot Size");
        exit(BOMBuffer."Able to Make Top Item");
    end;

    local procedure UpdateNodeCosts(var BOMBuffer: Record "BOM Buffer"; LotSize: Decimal)
    begin
        if LotSize = 0 then
            LotSize := 1;
        BOMBuffer.RoundCosts(LotSize);

        if BOMBuffer."Is Leaf" then begin
            case BOMBuffer.Type of
                BOMBuffer.Type::Item:
                    BOMBuffer.GetItemCosts();
                BOMBuffer.Type::Resource:
                    BOMBuffer.GetResCosts();
            end;
            BOMBuffer.RoundCosts(1 / LotSize);
        end else
            if IsProductionOrAssemblyItem(BOMBuffer."No.") then begin
                BOMBuffer.CalcOvhdCost();
                BOMBuffer.RoundCosts(1 / LotSize);
                if not HasBomStructure(BOMBuffer."No.") then
                    BOMBuffer.GetItemUnitCost();
            end else
                if BOMBuffer.Type = BOMBuffer.Type::Item then begin
                    BOMBuffer.RoundCosts(1 / LotSize);
                    BOMBuffer.GetItemCosts();
                end;

        BOMBuffer.CalcUnitCost();
        BOMBuffer.Modify();
    end;

    local procedure BinarySearchOptimal(var BOMBuffer: Record "BOM Buffer"; InputLow: Decimal; InputHigh: Decimal): Decimal
    var
        InputMid: Decimal;
    begin
        if InputHigh <= 0 then
            exit(0);
        if CalcAvailability(BOMBuffer, InputHigh, true) then begin
            TempMemoizedResult.DeleteAll();
            exit(InputHigh);
        end;
        if InputHigh - InputLow = UOMMgt.QtyRndPrecision() then begin
            TempMemoizedResult.DeleteAll();
            exit(InputLow);
        end;
        InputMid := Round((InputLow + InputHigh) / 2, UOMMgt.QtyRndPrecision());
        if not CalcAvailability(BOMBuffer, InputMid, true) then
            exit(BinarySearchOptimal(BOMBuffer, InputLow, InputMid));
        exit(BinarySearchOptimal(BOMBuffer, InputMid, InputHigh));
    end;

    local procedure CalcAvailability(var BOMBuffer: Record "BOM Buffer"; Input: Decimal; IsTest: Boolean): Boolean
    var
        ParentBOMBuffer: Record "BOM Buffer";
        ExpectedQty: Decimal;
        AvailQty: Decimal;
        MaxTime: Integer;
        AvailableVsExpectedCondition: Boolean;
    begin
        if BOMBuffer.Indentation = 0 then begin
            if IsTest then
                if TempMemoizedResult.Get(Input) then
                    exit(TempMemoizedResult.Output);

            ResetUpdatedAvailability();
        end;

        MaxTime := 0;
        ParentBOMBuffer := BOMBuffer;
        while (BOMBuffer.Next() <> 0) and (ParentBOMBuffer.Indentation < BOMBuffer.Indentation) do
            if ParentBOMBuffer.Indentation + 1 = BOMBuffer.Indentation then begin
                TempItemAvailByDate.SetRange("Item No.", BOMBuffer."No.");
                TempItemAvailByDate.SetRange(Date, BOMBuffer."Needed by Date");
                TempItemAvailByDate.SetRange("Variant Code", BOMBuffer."Variant Code");
                if LocationSpecific then
                    TempItemAvailByDate.SetRange("Location Code", BOMBuffer."Location Code");
                TempItemAvailByDate.FindFirst();
                if BOMBuffer."Calculation Formula" = BOMBuffer."Calculation Formula"::"Fixed Quantity" then begin
                    ExpectedQty := Round(BOMBuffer."Qty. per Parent", UOMMgt.QtyRndPrecision());
                    AvailQty := TempItemAvailByDate."Available Qty"
                end
                else begin
                    ExpectedQty := Round(BOMBuffer."Qty. per Parent" * Input, UOMMgt.QtyRndPrecision());
                    AvailQty := TempItemAvailByDate."Updated Available Qty";
                end;

                AvailableVsExpectedCondition := AvailQty < ExpectedQty;
                OnCalcAvailabilityOnBeforeUpdateAvailableQty(BOMBuffer, ExpectedQty, AvailQty, AvailableVsExpectedCondition);
                if AvailableVsExpectedCondition then begin
                    if BOMBuffer."Is Leaf" then begin
                        if MarkBottleneck then begin
                            BOMBuffer.Bottleneck := true;
                            BOMBuffer.Modify(true);
                        end;
                        BOMBuffer := ParentBOMBuffer;
                        if (BOMBuffer.Indentation = 0) and IsTest then
                            AddMemoizedResult(Input, false);
                        exit(false);
                    end;
                    if AvailQty <> 0 then
                        ReduceAvailability(BOMBuffer."No.", BOMBuffer."Variant Code", BOMBuffer."Location Code", BOMBuffer."Needed by Date", AvailQty, BOMBuffer."Calculation Formula");
                    if not IsTest then begin
                        BOMBuffer."Available Quantity" := AvailQty;
                        BOMBuffer.Modify();
                    end;
                    if not CalcAvailability(BOMBuffer, ExpectedQty - AvailQty, IsTest) then begin
                        if MarkBottleneck then begin
                            BOMBuffer.Bottleneck := true;
                            BOMBuffer.Modify(true);
                        end;
                        BOMBuffer := ParentBOMBuffer;
                        if (BOMBuffer.Indentation = 0) and IsTest then
                            AddMemoizedResult(Input, false);
                        exit(false);
                    end;
                    if not IsTest then
                        if MaxTime < (ParentBOMBuffer."Needed by Date" - BOMBuffer."Needed by Date") + BOMBuffer."Rolled-up Lead-Time Offset" then
                            MaxTime := (ParentBOMBuffer."Needed by Date" - BOMBuffer."Needed by Date") + BOMBuffer."Rolled-up Lead-Time Offset";
                end else begin
                    if not IsTest then begin
                        if BOMBuffer."Calculation Formula" <> BOMBuffer."Calculation Formula"::"Fixed Quantity" then begin
                            BOMBuffer."Available Quantity" := ExpectedQty;
                            BOMBuffer.Modify();
                        end;
                        if MaxTime < (ParentBOMBuffer."Needed by Date" - BOMBuffer."Needed by Date") + BOMBuffer."Rolled-up Lead-Time Offset" then
                            MaxTime := (ParentBOMBuffer."Needed by Date" - BOMBuffer."Needed by Date") + BOMBuffer."Rolled-up Lead-Time Offset";
                    end;
                    ReduceAvailability(BOMBuffer."No.", BOMBuffer."Variant Code", BOMBuffer."Location Code", BOMBuffer."Needed by Date", ExpectedQty, BOMBuffer."Calculation Formula");
                end;
            end;
        BOMBuffer := ParentBOMBuffer;
        BOMBuffer."Rolled-up Lead-Time Offset" := MaxTime;
        BOMBuffer.Modify(true);
        if (BOMBuffer.Indentation = 0) and IsTest then
            AddMemoizedResult(Input, true);
        exit(true);
    end;

    local procedure AddMemoizedResult(NewInput: Decimal; NewOutput: Boolean)
    begin
        TempMemoizedResult.Input := NewInput;
        TempMemoizedResult.Output := NewOutput;
        TempMemoizedResult.Insert();
    end;

    local procedure ResetUpdatedAvailability()
    begin
        TempItemAvailByDate.Reset();
        if TempItemAvailByDate.Find('-') then
            repeat
                if TempItemAvailByDate."Updated Available Qty" <> TempItemAvailByDate."Available Qty" then begin
                    TempItemAvailByDate."Updated Available Qty" := TempItemAvailByDate."Available Qty";
                    TempItemAvailByDate.Modify();
                end;
            until TempItemAvailByDate.Next() = 0;
    end;

    local procedure ReduceAvailability(ItemNo: Code[20]; VariantCode: Code[10]; LocationCode: Code[10]; ToDate: Date; Qty: Decimal; BOMLineCalcFormula: Enum "Quantity Calculation Formula")
    begin
        if BOMLineCalcFormula = BOMLineCalcFormula::"Fixed Quantity" then
            exit;
        TempItemAvailByDate.Reset();
        TempItemAvailByDate.SetRange("Item No.", ItemNo);
        TempItemAvailByDate.SetRange("Variant Code", VariantCode);
        if LocationSpecific then
            TempItemAvailByDate.SetRange("Location Code", LocationCode);
        TempItemAvailByDate.SetRange(Date, 0D, ToDate);
        if TempItemAvailByDate.FindSet() then
            repeat
                if TempItemAvailByDate."Updated Available Qty" <> 0 then begin
                    if TempItemAvailByDate."Updated Available Qty" > Qty then
                        TempItemAvailByDate."Updated Available Qty" := TempItemAvailByDate."Updated Available Qty" - Qty
                    else
                        TempItemAvailByDate."Updated Available Qty" := 0;
                    TempItemAvailByDate.Modify();
                end;
            until TempItemAvailByDate.Next() = 0;
        TempItemAvailByDate.SetRange("Item No.");
        TempItemAvailByDate.SetRange("Variant Code");
        TempItemAvailByDate.SetRange("Location Code");
        TempItemAvailByDate.SetRange(Date);
    end;

    local procedure DistributeRemainingAvail(var BOMBuffer: Record "BOM Buffer")
    var
        CurrItemAvailByDate: Record "Item Availability by Date";
        CopyOfBOMBuffer: Record "BOM Buffer";
    begin
        CopyOfBOMBuffer.Copy(BOMBuffer);
        BOMBuffer.Reset();
        BOMBuffer.SetCurrentKey(Type, "No.", Indentation);
        BOMBuffer.SetFilter("Entry No.", '>=%1', BOMBuffer."Entry No.");
        BOMBuffer.SetFilter("Calculation Formula", '<>%1', BOMBuffer."Calculation Formula"::"Fixed Quantity");
        TempItemAvailByDate.Reset();
        if TempItemAvailByDate.FindSet() then
            repeat
                if TempItemAvailByDate."Updated Available Qty" <> 0 then begin
                    CurrItemAvailByDate := TempItemAvailByDate;

                    BOMBuffer.SetRange(Type, BOMBuffer.Type);
                    BOMBuffer.SetRange("No.", TempItemAvailByDate."Item No.");
                    BOMBuffer.SetRange("Variant Code", TempItemAvailByDate."Variant Code");
                    if LocationSpecific then
                        BOMBuffer.SetRange("Location Code", TempItemAvailByDate."Location Code");
                    BOMBuffer.SetRange("Needed by Date", TempItemAvailByDate.Date);
                    if BOMBuffer.FindFirst() then begin
                        BOMBuffer."Available Quantity" += TempItemAvailByDate."Updated Available Qty";
                        BOMBuffer."Unused Quantity" += TempItemAvailByDate."Updated Available Qty";
                        BOMBuffer.Modify();

                        ReduceAvailability(BOMBuffer."No.", BOMBuffer."Variant Code", BOMBuffer."Location Code", BOMBuffer."Needed by Date", TempItemAvailByDate."Updated Available Qty", BOMBuffer."Calculation Formula");
                    end;

                    TempItemAvailByDate := CurrItemAvailByDate;
                end;
            until TempItemAvailByDate.Next() = 0;
        BOMBuffer.Copy(CopyOfBOMBuffer);
        BOMBuffer.Find();
    end;

    local procedure MarkBottlenecks(var BOMBuffer: Record "BOM Buffer"; Input: Decimal)
    begin
        MarkBottleneck := true;
        CalcAvailability(BOMBuffer, Input + UOMMgt.QtyRndPrecision(), true);
        MarkBottleneck := false;
    end;

    procedure CalcCompDueDate(DemandDate: Date; ParentItem: Record Item; LeadTimeOffset: DateFormula) DueDate: Date
    var
        InventorySetup: Record "Inventory Setup";
        EndDate: Date;
        StartDate: Date;
    begin
        if DemandDate = 0D then
            exit;

        EndDate := DemandDate;
        if Format(ParentItem."Safety Lead Time") <> '' then
            EndDate := DemandDate - (CalcDate(ParentItem."Safety Lead Time", DemandDate) - DemandDate)
        else
            if InventorySetup.Get() and (Format(InventorySetup."Default Safety Lead Time") <> '') then
                EndDate := DemandDate - (CalcDate(InventorySetup."Default Safety Lead Time", DemandDate) - DemandDate);

        if Format(ParentItem."Lead Time Calculation") = '' then
            StartDate := EndDate
        else
            StartDate := EndDate - (CalcDate(ParentItem."Lead Time Calculation", EndDate) - EndDate);

        if Format(LeadTimeOffset) = '' then
            DueDate := StartDate
        else
            DueDate := StartDate - (CalcDate(LeadTimeOffset, StartDate) - StartDate);

        OnAfterCalcCompDueDate(DueDate, DemandDate, ParentItem, LeadTimeOffset);
    end;

    local procedure AvailByDateExists(BOMBuffer: Record "BOM Buffer"): Boolean
    begin
        if LocationSpecific then
            exit(TempItemAvailByDate.Get(BOMBuffer."No.", BOMBuffer."Variant Code", BOMBuffer."Location Code", BOMBuffer."Needed by Date"));
        exit(TempItemAvailByDate.Get(BOMBuffer."No.", BOMBuffer."Variant Code", '', BOMBuffer."Needed by Date"));
    end;

    procedure SetShowTotalAvailability(NewShowTotalAvailability: Boolean)
    begin
        ShowTotalAvailability := NewShowTotalAvailability;
    end;

    local procedure HasBomStructure(ItemNo: Code[20]) Result: Boolean
    var
        Item: Record Item;
    begin
        Item.Get(ItemNo);
        case Item."Replenishment System" of
            Item."Replenishment System"::Assembly:
                begin
                    Item.CalcFields("Assembly BOM");
                    if Item."Assembly BOM" then
                        exit(true);
                end;
            Item."Replenishment System"::"Prod. Order":
                if Item.IsProductionBOM() then
                    exit(true);
        end;

        OnHasBOMStructure(Item, Result);
    end;

    local procedure IsProductionOrAssemblyItem(ItemNo: Code[20]): Boolean
    var
        Item: Record Item;
    begin
        if not Item.Get(ItemNo) then
            exit(false);

        exit(Item.IsMfgItem() or Item.IsAssemblyItem());
    end;

    procedure SetItemFilter(var Item: Record Item)
    begin
        ItemFilter.CopyFilters(Item);
    end;

    procedure SetLocationSpecific(NewLocationSpecific: Boolean)
    begin
        LocationSpecific := NewLocationSpecific;
    end;

    procedure GetShowTotalAvailability(): Boolean
    begin
        exit(ShowTotalAvailability);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterFilterBOMBuffer(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; DemandDate: Date; TreeType: Option)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterCalcCompDueDate(var DueDate: Date; DemandDate: Date; ParentItem: Record Item; LeadTimeOffset: DateFormula)
    begin
    end;

#if not CLEAN27
    internal procedure RunOnAfterGenerateProdCompSubTree(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer")
    begin
        OnAfterGenerateProdCompSubTree(ParentItem, BOMBuffer, ParentBOMBuffer);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnAfterGenerateProdCompSubTree(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer")
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnAfterTransferFromProdItem(var BOMBuffer: Record "BOM Buffer"; ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var EntryNo: Integer)
    begin
        OnAfterTransferFromProdItem(BOMBuffer, ProdBOMLine, EntryNo);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnAfterTransferFromProdItem(var BOMBuffer: Record "BOM Buffer"; ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var EntryNo: Integer)
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnAfterTransferFromProdBOM(var BOMBuffer: Record "BOM Buffer"; ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line")
    begin
        OnAfterTransferFromProdBOM(BOMBuffer, ProdBOMLine);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnAfterTransferFromProdBOM(var BOMBuffer: Record "BOM Buffer"; ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line")
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnAfterTransferFromProdRouting(var BOMBuffer: Record "BOM Buffer"; var RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line")
    begin
        OnAfterTransferFromProdRouting(BOMBuffer, RoutingLine);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnAfterTransferFromProdRouting(var BOMBuffer: Record "BOM Buffer"; var RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line")
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnBeforeCalcRoutingLineCosts(var RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; var LotSize: Decimal; var ScrapPct: Decimal; ParentItem: Record Item)
    begin
        OnBeforeCalcRoutingLineCosts(RoutingLine, LotSize, ScrapPct, ParentItem);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCalcRoutingLineCosts(var RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; var LotSize: Decimal; var ScrapPct: Decimal; ParentItem: Record Item)
    begin
    end;
#endif

    [IntegrationEvent(false, false)]
    local procedure OnBeforeFilterBOMBuffer(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; DemandDate: Date; TreeType: Option; var IsHandled: Boolean)
    begin
    end;

#if not CLEAN27
    internal procedure RunOnBeforeFilterByQuantityPer(var ProductionBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var IsHandled: Boolean; BOMBuffer: Record "BOM Buffer")
    begin
        OnBeforeFilterByQuantityPer(ProductionBOMLine, IsHandled, BOMBuffer);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnBeforeFilterByQuantityPer(var ProductionBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var IsHandled: Boolean; BOMBuffer: Record "BOM Buffer")
    begin
    end;
#endif

    [IntegrationEvent(false, false)]
    local procedure OnBeforeGenerateTreeForItems(var HideDialog: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeGenerateTreeForItemLocal(var ParentItem: Record Item; DemandDate: Date; TreeType: Option; var BOMBuffer: Record "BOM Buffer"; var IsHandled: Boolean)
    begin
    end;

#if not CLEAN27
    internal procedure RunOnBeforeTransferFromProdBOM(var BOMBuffer: Record "BOM Buffer"; var ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var ParentItem: Record Item; var ParentBOMBuffer: Record "BOM Buffer"; var EntryNo2: Integer; TreeType: Option " ",Availability,Cost)
    begin
        OnBeforeTransferFromProdBOM(BOMBuffer, ProdBOMLine, ParentItem, ParentBOMBuffer, EntryNo2, TreeType);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferFromProdBOM(var BOMBuffer: Record "BOM Buffer"; var ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var ParentItem: Record Item; var ParentBOMBuffer: Record "BOM Buffer"; var EntryNo: Integer; TreeType: Option " ",Availability,Cost)
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnBeforeTransferProdBOMLine(var BOMBuffer: Record "BOM Buffer"; var ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var ParentItem: Record Item; var ParentBOMBuffer: Record "BOM Buffer"; var EntryNo2: Integer; TreeType: Option " ",Availability,Cost; var IsHandled: Boolean)
    begin
        OnBeforeTransferProdBOMLine(BOMBuffer, ProdBOMLine, ParentItem, ParentBOMBuffer, EntryNo2, TreeType, IsHandled);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferProdBOMLine(var BOMBuffer: Record "BOM Buffer"; var ProdBOMLine: Record Microsoft.Manufacturing.ProductionBOM."Production BOM Line"; var ParentItem: Record Item; var ParentBOMBuffer: Record "BOM Buffer"; var EntryNo: Integer; TreeType: Option " ",Availability,Cost; var IsHandled: Boolean)
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnGenerateAsmHeaderSubTreeOnAfterAsmLineLoop(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
        OnGenerateAsmHeaderSubTreeOnAfterAsmLineLoop(ParentBOMBuffer, BOMBuffer);
    end;

    [Obsolete('Moved to codeunit AsmCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateAsmHeaderSubTreeOnAfterAsmLineLoop(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnGenerateProdCompSubTreeOnBeforeExitForNonProdOrder(ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; var FoundSubTree: Boolean)
    begin
        OnGenerateProdCompSubTreeOnBeforeExitForNonProdOrder(ParentItem, BOMBuffer, FoundSubTree);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateProdCompSubTreeOnBeforeExitForNonProdOrder(ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; var FoundSubTree: Boolean)
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnGenerateProdCompSubTreeOnAfterGenerateItemSubTree(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
        OnGenerateProdCompSubTreeOnAfterGenerateItemSubTree(ParentBOMBuffer, BOMBuffer);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateProdCompSubTreeOnAfterGenerateItemSubTree(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnGenerateProdCompSubTreeOnAfterProdBOMLineLoop(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
        OnGenerateProdCompSubTreeOnAfterProdBOMLineLoop(ParentBOMBuffer, BOMBuffer);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateProdCompSubTreeOnAfterProdBOMLineLoop(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
    end;
#endif

    [IntegrationEvent(false, false)]
    local procedure OnGenerateItemSubTreeOnAfterParentItemGet(var ParentItem: Record Item)
    begin
    end;

#if not CLEAN27
    internal procedure RunOnGenerateProdCompSubTreeOnBeforeBOMBufferModify(var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer"; ParentItem: Record Item)
    begin
        OnGenerateProdCompSubTreeOnBeforeBOMBufferModify(BOMBuffer, ParentBOMBuffer, ParentItem);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateProdCompSubTreeOnBeforeBOMBufferModify(var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer"; ParentItem: Record Item)
    begin
    end;
#endif

    [IntegrationEvent(false, false)]
    local procedure OnTraverseCostTreeOnAfterAddCosts(var ParentBOMBuffer: Record "BOM Buffer"; var BOMBuffer: Record "BOM Buffer")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnTraverseCostTreeOnBeforeAddCosts(var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnTraverseCostTreeOnBeforeLastUpdateNodeCosts(var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnTraverseTreeOnBeforeCalcAbleToMakeParentAndTopItem(var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer"; var IsHandled: Boolean)
    begin
    end;

#if not CLEAN27
    internal procedure RunOnCalcRoutingLineCostsOnBeforeBOMBufferAdd(RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; LotSize: Decimal; ScrapPct: Decimal; var CapCost: Decimal; var SubcontractedCapCost: Decimal; var CapOverhead: Decimal; var BOMBuffer: Record "BOM Buffer");
    begin
        OnCalcRoutingLineCostsOnBeforeBOMBufferAdd(RoutingLine, LotSize, ScrapPct, CapCost, SubcontractedCapCost, CapOverhead, BOMBuffer);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnCalcRoutingLineCostsOnBeforeBOMBufferAdd(RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; LotSize: Decimal; ScrapPct: Decimal; var CapCost: Decimal; var SubcontractedCapCost: Decimal; var CapOverhead: Decimal; var BOMBuffer: Record "BOM Buffer");
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnGenerateProdCompSubTreeOnAfterBOMBufferModify(var BOMBuffer: Record "BOM Buffer"; RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; LotSize: Decimal; ParentItem: Record Item; ParentBOMBuffer: Record "BOM Buffer"; TreeType: Option)
    begin
        OnGenerateProdCompSubTreeOnAfterBOMBufferModify(BOMBuffer, RoutingLine, LotSize, ParentItem, ParentBOMBuffer, TreeType);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateProdCompSubTreeOnAfterBOMBufferModify(var BOMBuffer: Record "BOM Buffer"; RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; LotSize: Decimal; ParentItem: Record Item; ParentBOMBuffer: Record "BOM Buffer"; TreeType: Option)
    begin
    end;
#endif

#if not CLEAN27
    internal procedure RunOnGenerateProdCompSubTreeOnBeforeRoutingLineLoop(var RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; var BOMBuffer: Record "BOM Buffer"; var RunIteration: Boolean)
    begin
        OnGenerateProdCompSubTreeOnBeforeRoutingLineLoop(RoutingLine, BOMBuffer, RunIteration);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnGenerateProdCompSubTreeOnBeforeRoutingLineLoop(var RoutingLine: Record Microsoft.Manufacturing.Routing."Routing Line"; var BOMBuffer: Record "BOM Buffer"; var RunIteration: Boolean)
    begin
    end;
#endif

    [IntegrationEvent(false, false)]
    local procedure OnGenerateTreeForItemLocalOnBeforeCalculateTreeType(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; var TreeType: Option; var EntryNo: Integer)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnInitItemAvailDatesOnBeforeCalcAvailableQty(var BOMItem: Record Item)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGenerateBOMCompSubTreeOnBeforeLoopBOMComponents(ParentItem: Record Item; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeInitBOMBuffer(var BOMBuffer: Record "BOM Buffer"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnCalcAvailabilityOnBeforeUpdateAvailableQty(var BOMBuffer: Record "BOM Buffer"; ExpectedQty: Decimal; AvailQty: Decimal; var AvailableVsExpectedCondition: Boolean)
    begin
    end;

#if not CLEAN27
    internal procedure RunOnBeforeGenerateProdOrderLineSubTree(ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line"; var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer"; var Result: Boolean; var IsHandled: Boolean)
    begin
        OnBeforeGenerateProdOrderLineSubTree(ProdOrderLine, BOMBuffer, ParentBOMBuffer, Result, IsHandled);
    end;

    [Obsolete('Moved to codeunit MfgCalculateBOMTree', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnBeforeGenerateProdOrderLineSubTree(ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line"; var BOMBuffer: Record "BOM Buffer"; var ParentBOMBuffer: Record "BOM Buffer"; var Result: Boolean; var IsHandled: Boolean)
    begin
    end;
#endif

    [IntegrationEvent(true, false)]
    procedure OnGenerateTreeForSource(SourceRecordVar: Variant; var BOMBuffer: Record "BOM Buffer"; BOMTreeType: Enum "BOM Tree Type"; ShowBy: Enum "BOM Structure Show By"; DemandDate: Date; var ItemFilter: Record Item; var EntryNo: Integer)
    begin
    end;

    [IntegrationEvent(true, false)]
    local procedure OnGenerateItemSubTreeOnSetIsLeaf(var ParentItem: Record Item; var BOMBuffer: Record "BOM Buffer"; var ItemFilter: Record Item; var EntryNo: Integer)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnHasBOMStructure(var Item: Record Item; var Result: Boolean)
    begin
    end;
}