microsoft / ALAppExtensions

Repository for collaboration on Microsoft AL application add-on and localization extensions for Microsoft Dynamics 365 Business Central.
MIT License
774 stars 609 forks source link

Add integration events to extend the recognition of cyclical loops in item applications (table 339 "Item Application Entry") #27115

Open sd484 opened 1 month ago

sd484 commented 1 month ago

Describe the request

The application currently recognizes circular references in production and assembly. However, further modules cannot be added to this detection. Circular references are identified via functions in table 339 ???Item Application Entry???. These functions are taken into account when adjusting inventory costs as well as when applying inventory.

The following is a suggestion for making the recognition of circular references expandable.

Adding integration events

table 339 "Item Application Entry"

procedure CheckIsCyclicalLoop(CheckItemLedgEntry: Record "Item Ledger Entry"; FromItemLedgEntry: Record "Item Ledger Entry"): Boolean
var
    Result: Boolean; // new variable
begin
    if CheckItemLedgEntry."Entry No." = FromItemLedgEntry."Entry No." then
        exit(true);

    [...]

    if FromItemLedgEntry."Entry Type" = FromItemLedgEntry."Entry Type"::"Assembly Consumption" then
        if CheckCyclicAsmCyclicalLoop(CheckItemLedgEntry, FromItemLedgEntry) then
            exit(true);

    // start
    Result := false;
    OnCheckIsCyclicalLoop(CheckItemLedgEntry, FromItemLedgEntry, MaxValuationDate, Result);
    if Result then
        exit(true);
    // end

    exit(CheckCyclicFwdToAppliedInbnds(CheckItemLedgEntry, FromItemLedgEntry."Entry No."));
end;

local procedure CheckCyclicFwdToAppliedEntries(CheckItemLedgEntry: Record "Item Ledger Entry"; var ItemApplnEntry: Record "Item Application Entry"; FromEntryNo: Integer; IsPositiveToNegativeFlow: Boolean): Boolean
var
    ToEntryNo: Integer;
    IsCyclicalLoop: Boolean; //new variable
begin
    if EntryIsVisited(FromEntryNo) then
        exit(false);

    repeat

        [...]

            if not IsPositiveToNegativeFlow then begin
                if CheckCyclicFwdToAppliedOutbnds(CheckItemLedgEntry, ToEntryNo) then
                    exit(true);
            end else begin
                if CheckCyclicFwdToAppliedInbnds(CheckItemLedgEntry, ToEntryNo) then
                    exit(true);
                if CheckCyclicFwdToProdOutput(CheckItemLedgEntry, ToEntryNo) then
                    exit(true);
                if CheckCyclicFwdToAsmOutput(CheckItemLedgEntry, ToEntryNo) then
                    exit(true);

                // start
                IsCyclicalLoop := false;
                OnCheckCyclicFwdToAppliedEntries(CheckItemLedgEntry, ToEntryNo, IsCyclicalLoop);
                if IsCyclicalLoop then
                    exit(true);
                // end
            end;
        end;
    until ItemApplnEntry.Next() = 0;

    if IsPositiveToNegativeFlow then
        exit(CheckCyclicFwdToInbndTransfers(CheckItemLedgEntry, FromEntryNo));
    exit(false);
end;

Redesign existing functions for production and assembly

table 339 "Item Application Entry"

local procedure CheckCyclicProdCyclicalLoop(CheckItemLedgEntry: Record "Item Ledger Entry"; ItemLedgEntry: Record "Item Ledger Entry"): Boolean
begin
    if not IsItemEverOutput(ItemLedgEntry."Item No.") then
        exit(false);

    [...]

    ItemLedgEntry.SetRange("Entry Type", ItemLedgEntry."Entry Type"::Output);
    // start
    // if MaxValuationDate <> 0D then
    //     ItemLedgEntry.SetRange("Posting Date", 0D, MaxValuationDate);
    // ItemLedgEntry.SetLoadFields(Positive);
    // if ItemLedgEntry.FindSet() then
    //     repeat
    //         if TrackChain then begin
    //             TempItemLedgEntryInChainNo.Number := ItemLedgEntry."Entry No.";
    //             if TempItemLedgEntryInChainNo.Insert() then;

    //             if SearchedItemLedgerEntryFound(ItemLedgEntry) then
    //                 exit(true);
    //         end;

    //         if ItemLedgEntry."Entry No." = CheckItemLedgEntry."Entry No." then
    //             exit(true);

    //         if ItemLedgEntry.Positive then
    //             if CheckCyclicFwdToAppliedOutbnds(CheckItemLedgEntry, ItemLedgEntry."Entry No.") then
    //                 exit(true);
    //     until ItemLedgEntry.Next() = 0;
    // exit(false);
    exit(CheckCyclicLedgerEntryCyclicalLoop(CheckItemLedgEntry, ItemLedgEntry));
    // end
end;

local procedure CheckCyclicAsmCyclicalLoop(CheckItemLedgEntry: Record "Item Ledger Entry"; ItemLedgEntry: Record "Item Ledger Entry"): Boolean
begin
    if ItemLedgEntry."Order Type" <> ItemLedgEntry."Order Type"::Assembly then
        exit(false);

    [...]

    ItemLedgEntry.SetRange("Entry Type", ItemLedgEntry."Entry Type"::"Assembly Output");
    // start
    // if MaxValuationDate <> 0D then
    //     ItemLedgEntry.SetRange("Posting Date", 0D, MaxValuationDate);
    // ItemLedgEntry.SetLoadFields(Positive);
    // if ItemLedgEntry.FindSet() then
    //     repeat
    //         if TrackChain then begin
    //             TempItemLedgEntryInChainNo.Number := ItemLedgEntry."Entry No.";
    //             if TempItemLedgEntryInChainNo.Insert() then;

    //             if SearchedItemLedgerEntryFound(ItemLedgEntry) then
    //                 exit(true);
    //         end;

    //         if ItemLedgEntry."Entry No." = CheckItemLedgEntry."Entry No." then
    //             exit(true);

    //         if ItemLedgEntry.Positive then
    //             if CheckCyclicFwdToAppliedOutbnds(CheckItemLedgEntry, ItemLedgEntry."Entry No.") then
    //                 exit(true);
    //     until ItemLedgEntry.Next() = 0;
    // exit(false);
    exit(CheckCyclicLedgerEntryCyclicalLoop(CheckItemLedgEntry, ItemLedgEntry));
    // end
end;

New function

table 339 "Item Application Entry"

procedure CheckCyclicLedgerEntryCyclicalLoop(CheckItemLedgEntry: Record "Item Ledger Entry"; var ItemLedgEntry: Record "Item Ledger Entry"): Boolean
begin
    if MaxValuationDate <> 0D then
        ItemLedgEntry.SetRange("Posting Date", 0D, MaxValuationDate);
    ItemLedgEntry.SetLoadFields(Positive);
    if ItemLedgEntry.FindSet() then
        repeat
            if TrackChain then begin
                TempItemLedgEntryInChainNo.Number := ItemLedgEntry."Entry No.";
                if TempItemLedgEntryInChainNo.Insert() then;

                if SearchedItemLedgerEntryFound(ItemLedgEntry) then
                    exit(true);
            end;

            if ItemLedgEntry."Entry No." = CheckItemLedgEntry."Entry No." then
                exit(true);

            if ItemLedgEntry.Positive then
                if CheckCyclicFwdToAppliedOutbnds(CheckItemLedgEntry, ItemLedgEntry."Entry No.") then
                    exit(true);
        until ItemLedgEntry.Next() = 0;
    exit(false);        
end;

New integration events

table 339 "Item Application Entry"

[IntegrationEvent(true,false)]
local procedure OnCheckIsCyclicalLoop(CheckItemLedgEntry: Record "Item Ledger Entry"; FromItemLedgEntry: Record "Item Ledger Entry"; var IsCyclicalLoop: Boolean)
begin
end;

[IntegrationEvent(true, false)]
local procedure OnCheckCyclicFwdToAppliedEntries(CheckItemLedgEntry: Record "Item Ledger Entry"; EntryNo: Integer; var IsCyclicalLoop: Boolean)
begin
end;

Important! include sender = true, so function CheckCyclicLedgerEntryCyclicalLoop can access global variables

Additional context

We are expanding Business Central with a module that enables the treatments (mixing and separating) of items. In order to correctly calculate and adjust inventory values, we need to be able to recognize and avoid circular references.

Internal work item: AB#545884

AR101 commented 1 month ago

This sounds, as if it would be beneficial for all partners extending on items.