Codeunit 3687 Low-Level Code Calculator

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

Procedures, 3Events, 1

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Manufacturing/ProductionBOM/LowLevelCodeCalculator.Codeunit.al343 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.Inventory.BOM;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Location;
using Microsoft.Manufacturing.ProductionBOM;
using System.Threading;
using System.Utilities;

codeunit 3687 "Low-Level Code Calculator"
{
    var
        LowLevelCodeParam: Codeunit "Low-Level Code Parameter";
        NodeKeysAddedToTree: List of [Text];
        ConfirmQst: Label 'Calculate low-level code?';
        BackgroundJobQst: Label 'Would you like to run the low-level code calculation as a background job?';
        RecordDetailsLbl: Label 'Table %1: %2', Comment = '%1 is the table caption, %2 is the record ID';
        ResetLowLevelCodeLbl: Label 'Reset code';
        TimeTakenForRunTxt: Label 'Time taken to run low level calculation through Low- Level Code Calculator is %1.', Comment = '%1 is the time taken', Locked = true;
        LLCResetToZeroTxt: Label 'Low- Level Codes have been reset to 0 for %1 records in the %2 table.', Comment = '%1 is the count of reset records; %2 is the table in which the reset has been done', Locked = true;

    trigger OnRun()
    begin
        Calculate();
    end;

    procedure Calculate()
    begin
        Calculate(true);
    end;

    procedure Calculate(ShowConfirmation: Boolean)
    var
        BOMStructure: Codeunit "BOM Tree";
        ConfirmManagement: Codeunit "Confirm Management";
        Start: DateTime;
    begin
        // Ask for confirmation
        if ShowConfirmation then
            if not ConfirmManagement.GetResponseOrDefault(ConfirmQst, true) then
                exit;

        Start := CurrentDateTime();
        // Take locks to prevent other sessions from updating entities while the calculation is going on
        LockTables();

        LowLevelCodeParam.Create();

        PopulateBOMTree(BOMStructure);

        // This is a full run- so clean up the Low Level Codes for those nodes that are not part of the BOM tree
        ResetLowLevelCodesForEntitiesNotPartOfBOM();

        // Traverse to calculate the low level codes
        LowLevelCodeParam.SetRunMode("Low-Level Code Run Mode"::Calculate);
        BOMStructure.TraverseDown();

        // Calculation completed. Traverse to modify the database
        LowLevelCodeParam.SetRunMode("Low-Level Code Run Mode"::"Write To Database");
        BOMStructure.TraverseDown();

        LowLevelCodeParam.Close();
        Session.LogMessage('0000CIM', StrSubstNo(TimeTakenForRunTxt, CurrentDateTime() - Start), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'Planning');

        OnAfterCalculate();
    end;

    procedure SuggestToRunAsBackgroundJob()
    var
        JobQueueEntry: Record "Job Queue Entry";
        ConfirmManagement: Codeunit "Confirm Management";
        DummyRecordID: RecordId;
    begin
        // check for existing job
        if JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"Low-Level Code Calculator") then
            exit;

        if not ConfirmManagement.GetResponseOrDefault(BackgroundJobQst, false) then
            exit;

        JobQueueEntry.ScheduleRecurrentJobQueueEntryWithFrequency(JobQueueEntry."Object Type to Run"::Codeunit, Codeunit::"Low-Level Code Calculator", DummyRecordID, 1440); // once daily
    end;

    local procedure PopulateBOMTree(BOMStructure: Codeunit "BOM Tree")
    begin
        // Set nodes on the tree
        PopulateFromItemAndRelatedBOMs(BOMStructure);
        PopulateFromSKUAndRelatedBOMs(BOMStructure);
        PopulateFromProductionBOMAndLines(BOMStructure);
        PopulateFromBOMComponents(BOMStructure);
    end;

    local procedure LockTables()
    var
        Item: Record Item;
        StockkeepingUnit: Record "Stockkeeping Unit";
        ProductionBOMHeader: Record "Production BOM Header";
        ProductionBOMLine: Record "Production BOM Line";
        ProductionBOMVersion: Record "Production BOM Version";
        BOMComponent: Record "BOM Component";
    begin
        Item.LockTable();
        StockkeepingUnit.LockTable();
        ProductionBOMHeader.LockTable();
        ProductionBOMLine.LockTable();
        ProductionBOMVersion.LockTable();
        BOMComponent.LockTable();
    end;

    local procedure ResetLowLevelCodesForEntitiesNotPartOfBOM()
    var
        Item: Record Item;
        ProductionBOMHeader: Record "Production BOM Header";
        BOMNode: Codeunit "BOM Node";
        ItemCounter: Integer;
        TotalItems: Integer;
        ProdBOMCounter: Integer;
        TotalBOMs: Integer;
        Counter: Integer;
    begin
        LowLevelCodeParam.ShowHeading(ResetLowLevelCodeLbl);
        Item.SetFilter("Low-Level Code", '<> %1', 0);
        TotalItems := Item.Count();
        ProductionBOMHeader.SetFilter("Low-Level Code", '<> %1', 0);
        TotalBOMs := ProductionBOMHeader.Count();

        LowLevelCodeParam.ShowHeading(ResetLowLevelCodeLbl);
        if Item.FindSet() then
            repeat
                BOMNode.CreateForItem(Item."No.", Item."Low-Level Code", LowLevelCodeParam);
                ItemCounter += 1;
                LowLevelCodeParam.ShowDetails(StrSubstNo(RecordDetailsLbl, Item.TableCaption(), Item."No."), ItemCounter, TotalItems + TotalBOMs);

                if not NodeKeysAddedToTree.Contains(BOMNode.GetKey()) then begin
                    Item.Mark(true);
                    Counter += 1;
                end;
            until Item.Next() = 0;
        if Counter > 0 then begin
            Item.MarkedOnly(true);
            Item.ModifyAll("Low-Level Code", 0);
            Session.LogMessage('0000CIP', StrSubstNo(LLCResetToZeroTxt, Counter, Item.TableName), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'Planning');
        end;

        Counter := 0;
        if ProductionBOMHeader.FindSet() then
            repeat
                BOMNode.CreateForProdBOM(ProductionBOMHeader."No.", ProductionBOMHeader."Low-Level Code", LowLevelCodeParam);
                ProdBOMCounter += 1;
                LowLevelCodeParam.ShowDetails(StrSubstNo(RecordDetailsLbl, ProductionBOMHeader.TableCaption(), ProductionBOMHeader."No."), TotalItems + ProdBOMCounter, TotalItems + TotalBOMs);

                if not NodeKeysAddedToTree.Contains(BOMNode.GetKey()) then begin
                    ProductionBOMHeader.Mark(true);
                    Counter += 1;
                end;
            until ProductionBOMHeader.Next() = 0;
        if Counter > 0 then begin
            ProductionBOMHeader.MarkedOnly(true);
            ProductionBOMHeader.ModifyAll("Low-Level Code", 0);
            Session.LogMessage('0000CIQ', StrSubstNo(LLCResetToZeroTxt, Counter, ProductionBOMHeader.TableName), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'Planning');
        end;
    end;

    local procedure PopulateFromItemAndRelatedBOMs(BOMStructure: Codeunit "BOM Tree")
    var
        Item: Record Item;
        Parent: Codeunit "BOM Node";
        Child: Codeunit "BOM Node";
        ItemProductionBOMs: Query "Item Production BOMs";
        Progressed: Integer;
        TotalItemCount: Integer;
    begin
        Item.SetFilter("Production BOM No.", '<> %1', '');
        TotalItemCount := Item.Count();

        ItemProductionBOMs.SetFilter(BOMStatus, '<>%1', Enum::"BOM Status"::Closed);
        ItemProductionBOMs.Open();
        while ItemProductionBOMs.Read() do
            if CheckItemProductionBOMIsCertified(ItemProductionBOMs) then begin
                Progressed += 1;
                LowLevelCodeParam.ShowDetails(StrSubstNo(RecordDetailsLbl, Item.TableCaption(), ItemProductionBOMs.No_), Progressed, TotalItemCount);

                Parent.CreateForItem(ItemProductionBOMs.No_, ItemProductionBOMs.Item_Low_Level_Code, LowLevelCodeParam);
                Child.CreateForProdBOM(ItemProductionBOMs.Production_BOM_No_, ItemProductionBOMs.BOM_Low_Level_Code, LowLevelCodeParam);

                AddChildToParent(BOMStructure, Parent, Child);

                // ensure that next nodes created are new instances
                Clear(Parent);
                Clear(Child);
            end;
        ItemProductionBOMs.Close();
    end;

    local procedure PopulateFromProductionBOMAndLines(BOMStructure: Codeunit "BOM Tree")
    var
        ProductionBOMHeader: Record "Production BOM Header";
        Parent: Codeunit "BOM Node";
        Child: Codeunit "BOM Node";
        ProductionBOMLineDetails: Query "Production BOM & Line Details";
        CurrentBOMHeaderNo: Code[20];
        Progressed: Integer;
        TotalProdBOMCount: Integer;
    begin
        TotalProdBOMCount := ProductionBOMHeader.Count();

        CurrentBOMHeaderNo := '';
        ProductionBOMLineDetails.SetFilter(Type, '<> %1', "Production BOM Line Type"::" ");
        ProductionBOMLineDetails.Open();
        while ProductionBOMLineDetails.Read() do begin
            if CurrentBOMHeaderNo <> ProductionBOMLineDetails.No_ then begin
                CurrentBOMHeaderNo := ProductionBOMLineDetails.No_;
                Progressed += 1;
            end;
            LowLevelCodeParam.ShowDetails(StrSubstNo(RecordDetailsLbl, ProductionBOMHeader.TableCaption(), ProductionBOMLineDetails.No_), Progressed, TotalProdBOMCount);

            if (ProductionBOMLineDetails.Status = ProductionBOMLineDetails.Status::Certified) or ((ProductionBOMLineDetails.BOM_Version_Code <> '') and (ProductionBOMLineDetails.BOM_Version_StartingDate <= WorkDate()) and (ProductionBOMLineDetails.BOM_Version_Status = Enum::"BOM Status"::Certified)) then begin
                Parent.CreateForProdBOM(ProductionBOMLineDetails.No_, ProductionBOMLineDetails.Low_Level_Code, LowLevelCodeParam);
                case ProductionBOMLineDetails.Type of
                    "Production BOM Line Type"::Item:
                        if ProductionBOMLineDetails.ChildItem_No_ <> '' then
                            Child.CreateForItem(ProductionBOMLineDetails.ChildItem_No_, ProductionBOMLineDetails.ChildItem_Low_Level_Code, LowLevelCodeParam);
                    "Production BOM Line Type"::"Production BOM":
                        if (ProductionBOMLineDetails.ChildBOM_No_ <> '') and (ProductionBOMLineDetails.ChildBOM_Status = ProductionBOMLineDetails.Status::Certified) then
                            Child.CreateForProdBOM(ProductionBOMLineDetails.ChildBOM_No_, ProductionBOMLineDetails.ChildBOM_Low_Level_Code, LowLevelCodeParam);
                end;

                if Child.IsInitialized() then
                    if not BOMStructure.ChildHasKey(Parent.GetKey(), Child.GetKey()) then
                        AddChildToParent(BOMStructure, Parent, Child);

                // ensure that next nodes created are new instances
                Clear(Parent);
                Clear(Child);
            end;
        end;
        ProductionBOMLineDetails.Close();
    end;

    local procedure PopulateFromBOMComponents(BOMStructure: Codeunit "BOM Tree")
    var
        BOMComponent: Record "BOM Component";
        Parent: Codeunit "BOM Node";
        Child: Codeunit "BOM Node";
        BOMComponentItems: Query "BOM Component Items";
        Progressed: Integer;
        TotalBomComponentCount: Integer;
    begin
        BOMComponent.SetRange(Type, BOMComponent.Type::Item);
        TotalBomComponentCount := BOMComponent.Count();

        BOMComponentItems.SetRange(Type, BOMComponent.Type::Item);
        BOMComponentItems.Open();
        while BOMComponentItems.Read() do begin
            Progressed += 1;
            LowLevelCodeParam.ShowDetails(StrSubstNo(RecordDetailsLbl, BOMComponent.TableCaption(), BOMComponentItems.Parent_Item_No_), Progressed, TotalBomComponentCount);

            Parent.CreateForItem(BOMComponentItems.Parent_Item_No_, BOMComponentItems.Parent_Low_Level_Code, LowLevelCodeParam);
            Child.CreateForItem(BOMComponentItems.No_, BOMComponentItems.Child_Low_Level_Code, LowLevelCodeParam);

            if not BOMStructure.ChildHasKey(Parent.GetKey(), Child.GetKey()) then
                AddChildToParent(BOMStructure, Parent, Child);

            // ensure that next nodes created are new instances
            Clear(Parent);
            Clear(Child);
        end;
        BOMComponentItems.Close();
    end;

    local procedure AddKeyToList(BOMKey: Text)
    begin
        if NodeKeysAddedToTree.Contains(BOMKey) then
            exit;
        NodeKeysAddedToTree.Add(BOMKey);
    end;

    local procedure AddChildToParent(BOMStructure: Codeunit "BOM Tree"; Parent: Codeunit "BOM Node"; Child: Codeunit "BOM Node")
    begin
        // there can be multiple relations with same parent & child combination, e.g. a BOM can have two lines with the same item or another BOM
        if BOMStructure.ChildHasKey(Parent.GetKey(), Child.GetKey()) then
            exit;

        BOMStructure.AddRelation(Parent, Child);
        AddKeyToList(Parent.GetKey());
        AddKeyToList(Child.GetKey());
    end;

    local procedure CheckItemProductionBOMIsCertified(var ItemProductionBOMs: Query "Item Production BOMs"): Boolean
    var
        VersionManagement: Codeunit VersionManagement;
    begin
        if ItemProductionBOMs.BOMStatus = ItemProductionBOMs.BOMStatus::Certified then
            exit(true);

        exit(VersionManagement.GetBOMVersion(ItemProductionBOMs.Production_BOM_No_, WorkDate(), true) <> '');
    end;

    local procedure PopulateFromSKUAndRelatedBOMs(BOMStructure: Codeunit "BOM Tree")
    var
        StockkeepingUnit: Record "Stockkeeping Unit";
        Item: Record Item;
        ProductionBOMHeader: Record "Production BOM Header";
        VersionManagement: Codeunit VersionManagement;
        Parent: Codeunit "BOM Node";
        Child: Codeunit "BOM Node";
        Progressed: Integer;
        TotalSKUCount: Integer;
    begin
        StockkeepingUnit.SetFilter("Production BOM No.", '<>%1', '');
        TotalSKUCount := StockkeepingUnit.Count();
        if TotalSKUCount = 0 then
            exit;

        if StockkeepingUnit.FindSet() then
            repeat
                Progressed += 1;
                LowLevelCodeParam.ShowDetails(StrSubstNo(RecordDetailsLbl, StockkeepingUnit.TableCaption(), StockkeepingUnit."Item No."), Progressed, TotalSKUCount);

                if Item.Get(StockkeepingUnit."Item No.") and ProductionBOMHeader.Get(StockkeepingUnit."Production BOM No.") then
                    if (ProductionBOMHeader.Status = ProductionBOMHeader.Status::Certified) or
                       (VersionManagement.GetBOMVersion(ProductionBOMHeader."No.", WorkDate(), true) <> '')
                    then begin
                        Parent.CreateForItem(Item."No.", Item."Low-Level Code", LowLevelCodeParam);
                        Child.CreateForProdBOM(ProductionBOMHeader."No.", ProductionBOMHeader."Low-Level Code", LowLevelCodeParam);

                        AddChildToParent(BOMStructure, Parent, Child);

                        Clear(Parent);
                        Clear(Child);
                    end;
            until StockkeepingUnit.Next() = 0;
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterCalculate()
    begin
    end;
}