Report 7390 Whse. Calculate Inventory, source in 29

Source29

src/Layers/W1/BaseApp/Warehouse/Structure/WhseCalculateInventory.Report.al553 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.Warehouse.Structure;

using Microsoft.Foundation.AuditCodes;
using Microsoft.Foundation.NoSeries;
using Microsoft.Foundation.UOM;
using Microsoft.Inventory.Counting.Tracking;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Location;
using Microsoft.Inventory.Tracking;
using Microsoft.Warehouse.Journal;
using Microsoft.Warehouse.Ledger;

report 7390 "Whse. Calculate Inventory"
{
    Caption = 'Whse. Calculate Inventory';
    ProcessingOnly = true;

    dataset
    {
        dataitem("Bin Content"; "Bin Content")
        {
            DataItemTableView = sorting("Location Code", "Bin Code", "Item No.", "Variant Code", "Unit of Measure Code");
            RequestFilterFields = "Zone Code", "Bin Code", "Item No.", "Variant Code", "Unit of Measure Code", "Bin Type Code", "Warehouse Class Code";

            trigger OnAfterGetRecord()
            begin
                if SkipCycleSKU("Location Code", "Item No.", "Variant Code") then
                    CurrReport.Skip();

                if not HideValidationDialog then
                    Window.Update();
                CalcFields("Quantity (Base)");
                if ("Quantity (Base)" <> 0) or ZeroQty then
                    InsertWhseJnlLine("Bin Content");
            end;

            trigger OnPostDataItem()
            begin
                if not HideValidationDialog then
                    Window.Close();
            end;

            trigger OnPreDataItem()
            var
                WhseJnlTemplate: Record "Warehouse Journal Template";
            begin
                if RegisteringDate = 0D then
                    Error(Text001, WhseJnlLine.FieldCaption("Registering Date"));

                SetRange("Location Code", WhseJnlLine."Location Code");

                OnBinContentOnBeforePreDataItem("Bin Content", WhseJnlLine);

                WhseJnlTemplate.Get(WhseJnlLine."Journal Template Name");
                WhseJnlBatch.Get(
                  WhseJnlLine."Journal Template Name",
                  WhseJnlLine."Journal Batch Name", WhseJnlLine."Location Code");
                if NextDocNo = '' then begin
                    if WhseJnlBatch."No. Series" <> '' then begin
                        WhseJnlLine.SetRange("Journal Template Name", WhseJnlLine."Journal Template Name");
                        WhseJnlLine.SetRange("Journal Batch Name", WhseJnlLine."Journal Batch Name");
                        WhseJnlLine.SetRange("Location Code", WhseJnlLine."Location Code");
                        if not WhseJnlLine.FindFirst() then
                            NextDocNo := NoSeriesBatch.GetNextNo(WhseJnlBatch."No. Series", RegisteringDate);
                        WhseJnlLine.Init();
                    end;
                    if NextDocNo = '' then
                        Error(Text001, WhseJnlLine.FieldCaption("Whse. Document No."));
                end;

                NextLineNo := 0;

                if not HideValidationDialog then
                    Window.Open(Text002, "Bin Code");
            end;
        }
        dataitem("Warehouse Entry"; "Warehouse Entry")
        {

            trigger OnAfterGetRecord()
            var
                BinContent: Record "Bin Content";
                SkipRecord: Boolean;
            begin
                this.GetLocation("Location Code");
                SkipRecord := ShouldSkipRecord();
                OnWarehouseEntryOnAfterGetRecordOnAfterCalcSkipRecord("Warehouse Entry", SkipRecord);
                if SkipRecord then
                    CurrReport.Skip();

                BinContent.CopyFilters("Bin Content");
                BinContent.SetRange("Location Code", "Location Code");
                BinContent.SetRange("Item No.", "Item No.");
                BinContent.SetRange("Variant Code", "Variant Code");
                BinContent.SetRange("Unit of Measure Code", "Unit of Measure Code");
                if not BinContent.IsEmpty() then
                    CurrReport.Skip();

                InitBinContent(TempBinContent, "Warehouse Entry");

                if not TempBinContent.Find() then
                    TempBinContent.Insert();
            end;

            trigger OnPostDataItem()
            begin
                TempBinContent.Reset();
                if TempBinContent.FindSet() then
                    repeat
                        InsertWhseJnlLine(TempBinContent);
                    until TempBinContent.Next() = 0;
            end;

            trigger OnPreDataItem()
            begin
                if ("Bin Content".GetFilter("Zone Code") = '') and
                   ("Bin Content".GetFilter("Bin Code") = '')
                then
                    CurrReport.Break();

                "Bin Content".CopyFilter("Location Code", "Location Code");
                "Bin Content".CopyFilter("Zone Code", "Zone Code");
                "Bin Content".CopyFilter("Bin Code", "Bin Code");
                "Bin Content".CopyFilter("Item No.", "Item No.");
                "Bin Content".CopyFilter("Variant Code", "Variant Code");
                "Bin Content".CopyFilter("Unit of Measure Code", "Unit of Measure Code");
                "Bin Content".CopyFilter("Bin Type Code", "Bin Type Code");
                "Bin Content".CopyFilter("Lot No. Filter", "Lot No.");
                "Bin Content".CopyFilter("Serial No. Filter", "Serial No.");
                TempBinContent.Reset();
                TempBinContent.DeleteAll();
            end;
        }
    }

    requestpage
    {
        SaveValues = true;

        layout
        {
            area(content)
            {
                group(Options)
                {
                    Caption = 'Options';
                    field(RegisteringDate; RegisteringDate)
                    {
                        ApplicationArea = Warehouse;
                        Caption = 'Registering Date';
                        ToolTip = 'Specifies the date for registering this batch job. The program automatically enters the work date in this field, but you can change it.';

                        trigger OnValidate()
                        begin
                            ValidateRegisteringDate();
                        end;
                    }
                    field(WhseDocumentNo; NextDocNo)
                    {
                        ApplicationArea = Warehouse;
                        Caption = 'Whse. Document No.';
                        ToolTip = 'Specifies which document number will be entered in the Document No. field on the journal lines created by the batch job.';
                    }
                    field(ZeroQty; ZeroQty)
                    {
                        ApplicationArea = Warehouse;
                        Caption = 'Items Not on Inventory';
                        ToolTip = 'Specifies if journal lines should be created for items that are not on inventory, that is, items where the value in the Qty. (Calculated) field is 0.';
                    }
                }
            }
        }

        actions
        {
        }

        trigger OnOpenPage()
        begin
            if RegisteringDate = 0D then
                RegisteringDate := WorkDate();
            ValidateRegisteringDate();
        end;
    }

    labels
    {
    }

    trigger OnPostReport()
    begin
        OnAfterOnPostReport(WhseJnlLine);
    end;

    var
        SourceCodeSetup: Record "Source Code Setup";
        TempBinContent: Record "Bin Content" temporary;
        NoSeriesBatch: Codeunit "No. Series - Batch";
        UOMMgt: Codeunit "Unit of Measure Management";
        Window: Dialog;
        StockProposal: Boolean;

#pragma warning disable AA0074
#pragma warning disable AA0470
        Text001: Label 'Enter the %1.';
        Text002: Label 'Processing bins    #1##########';
#pragma warning restore AA0470
#pragma warning restore AA0074

    protected var
        Bin: Record Bin;
        Location: Record Location;
        WhseJnlBatch: Record "Warehouse Journal Batch";
        WhseJnlLine: Record "Warehouse Journal Line";
        HideValidationDialog: Boolean;
        NextDocNo: Code[20];
        NextLineNo: Integer;
        RegisteringDate: Date;
        CycleSourceType: Option " ",Item,SKU;
        PhysInvtCountCode: Code[10];
        ZeroQty: Boolean;

    procedure SetWhseJnlLine(var NewWhseJnlLine: Record "Warehouse Journal Line")
    begin
        WhseJnlLine := NewWhseJnlLine;
    end;

    local procedure ValidateRegisteringDate()
    var
        NoSeries: Codeunit "No. Series";
    begin
        WhseJnlBatch.Get(
          WhseJnlLine."Journal Template Name",
          WhseJnlLine."Journal Batch Name", WhseJnlLine."Location Code");
        if WhseJnlBatch."No. Series" = '' then
            NextDocNo := ''
        else
            NextDocNo := NoSeries.PeekNextNo(WhseJnlBatch."No. Series", RegisteringDate);
    end;

    local procedure InitBinContent(var BinContent: Record "Bin Content"; WarehouseEntry: Record "Warehouse Entry")
    var
        BinRec: Record Bin;
    begin
        BinContent.Init();
        BinContent."Location Code" := WarehouseEntry."Location Code";
        BinContent."Item No." := WarehouseEntry."Item No.";
        BinContent."Bin Code" := WarehouseEntry."Bin Code";
        BinContent."Variant Code" := WarehouseEntry."Variant Code";
        BinContent."Unit of Measure Code" := WarehouseEntry."Unit of Measure Code";
        BinContent."Quantity (Base)" := 0;

        // Get current Zone Code from Bin table instead of historical value from Warehouse Entry
        if BinRec.Get(WarehouseEntry."Location Code", WarehouseEntry."Bin Code") then
            BinContent."Zone Code" := BinRec."Zone Code"
        else
            BinContent."Zone Code" := WarehouseEntry."Zone Code";

        OnAfterInitBinContent(BinContent, WarehouseEntry);
    end;

    procedure InsertWhseJnlLine(BinContent: Record "Bin Content")
    var
        WhseEntry: Record "Warehouse Entry";
        ItemUOM: Record "Item Unit of Measure";
        TempItemTrackingSetup: Record "Item Tracking Setup";
        ItemTrackingMgt: Codeunit "Item Tracking Management";
        WhseEntryTrackingDistinct: Query "Whse. Entry Tracking Distinct";
        ProcessedKeys: Dictionary of [Text, Boolean];
        EffectiveKey: Text;
        ExpDate: Date;
        EntriesExist: Boolean;
        IsHandled: Boolean;
        ProcessNext: Boolean;
        ProcessRecord: Boolean;
        SerialNoActive: Boolean;
        LotNoActive: Boolean;
        PackageNoActive: Boolean;
        WhseEntryTrackingDistinctRead: Boolean;
    begin
        if NextLineNo = 0 then begin
            WhseJnlLine.LockTable();
            WhseJnlLine.SetRange("Journal Template Name", WhseJnlLine."Journal Template Name");
            WhseJnlLine.SetRange("Journal Batch Name", WhseJnlLine."Journal Batch Name");
            WhseJnlLine.SetRange("Location Code", WhseJnlLine."Location Code");
            if WhseJnlLine.FindLast() then
                NextLineNo := WhseJnlLine."Line No.";

            SourceCodeSetup.Get();
        end;

        GetLocation(BinContent."Location Code");

        WhseEntry.SetCurrentKey("Item No.", "Bin Code", "Location Code", "Variant Code", "Unit of Measure Code", "Lot No.", "Serial No.");
        WhseEntry.SetRange("Item No.", BinContent."Item No.");
        WhseEntry.SetRange("Bin Code", BinContent."Bin Code");
        WhseEntry.SetRange("Location Code", BinContent."Location Code");
        WhseEntry.SetRange("Variant Code", BinContent."Variant Code");
        WhseEntry.SetRange("Unit of Measure Code", BinContent."Unit of Measure Code");
        WhseEntry.SetLoadFields("Serial No.", "Lot No.", "Package No.", "Warranty Date", "Expiration Date", "Qty. (Base)");
        OnInsertWhseJnlLineOnAfterWhseEntrySetFilters(WhseEntry, "Bin Content");

        // Performance: enumerate distinct tracking combinations via SQL GROUP BY instead of iterating every Warehouse Entry.
        GetSpecificTrackingOptions(BinContent."Item No.", SerialNoActive, LotNoActive, PackageNoActive);
        WhseEntryTrackingDistinct.SetRange(Item_No_, BinContent."Item No.");
        WhseEntryTrackingDistinct.SetRange(Bin_Code, BinContent."Bin Code");
        WhseEntryTrackingDistinct.SetRange(Location_Code, BinContent."Location Code");
        WhseEntryTrackingDistinct.SetRange(Variant_Code, BinContent."Variant Code");
        WhseEntryTrackingDistinct.SetRange(Unit_of_Measure_Code, BinContent."Unit of Measure Code");
        if WhseEntryTrackingDistinct.Open() then begin
            WhseEntryTrackingDistinctRead := WhseEntryTrackingDistinct.Read();
            ProcessNext := WhseEntryTrackingDistinctRead or ZeroQty;
            while ProcessNext do begin
                // Reset tracking filters; only the dimensions enabled for the item will be re-applied below.
                WhseEntry.SetRange("Serial No.");
                WhseEntry.SetRange("Lot No.");
                WhseEntry.SetRange("Package No.");

                ProcessRecord := true;
                if WhseEntryTrackingDistinctRead then begin
                    // Collapse query rows that differ only in inactive tracking dimensions to avoid duplicate journal lines.
                    EffectiveKey := BuildSpecificTrackingKey(WhseEntryTrackingDistinct, SerialNoActive, LotNoActive, PackageNoActive);
                    if ProcessedKeys.ContainsKey(EffectiveKey) then
                        ProcessRecord := false
                    else begin
                        ProcessedKeys.Add(EffectiveKey, true);
                        if SerialNoActive then
                            WhseEntry.SetRange("Serial No.", WhseEntryTrackingDistinct.Serial_No_);
                        if LotNoActive then
                            WhseEntry.SetRange("Lot No.", WhseEntryTrackingDistinct.Lot_No_);
                        if PackageNoActive then
                            WhseEntry.SetRange("Package No.", WhseEntryTrackingDistinct.Package_No_);
                    end;
                end;

                if ProcessRecord then
                    if WhseEntry.FindFirst() or ZeroQty then begin
                        WhseEntry.CalcSums("Qty. (Base)");
                        if (WhseEntry."Qty. (Base)" <> 0) or ZeroQty then begin
                            ItemUOM.Get(BinContent."Item No.", BinContent."Unit of Measure Code");
                            NextLineNo += 10000;
                            WhseJnlLine.Init();
                            WhseJnlLine."Line No." := NextLineNo;
                            WhseJnlLine.Validate("Registering Date", RegisteringDate);
                            WhseJnlLine.Validate("Entry Type", WhseJnlLine."Entry Type"::"Positive Adjmt.");
                            WhseJnlLine.Validate("Whse. Document No.", NextDocNo);
                            WhseJnlLine.Validate("Item No.", BinContent."Item No.");
                            WhseJnlLine.Validate("Variant Code", BinContent."Variant Code");
                            WhseJnlLine.Validate("Location Code", BinContent."Location Code");
                            WhseJnlLine."From Bin Code" := Location."Adjustment Bin Code";
                            WhseJnlLine."From Zone Code" := Bin."Zone Code";
                            WhseJnlLine."From Bin Type Code" := Bin."Bin Type Code";
                            WhseJnlLine.Validate("To Zone Code", BinContent."Zone Code");
                            WhseJnlLine.Validate("To Bin Code", BinContent."Bin Code");
                            WhseJnlLine.Validate("Zone Code", BinContent."Zone Code");
                            WhseJnlLine.SetProposal(StockProposal);
                            WhseJnlLine.Validate("Bin Code", BinContent."Bin Code");
                            WhseJnlLine.Validate("Source Code", SourceCodeSetup."Whse. Phys. Invt. Journal");
                            WhseJnlLine.Validate("Unit of Measure Code", BinContent."Unit of Measure Code");
                            WhseJnlLine.CopyTrackingFromWhseEntry(WhseEntry);
                            WhseJnlLine."Warranty Date" := WhseEntry."Warranty Date";
                            TempItemTrackingSetup.CopyTrackingFromWhseEntry(WhseEntry);
                            ExpDate := ItemTrackingMgt.ExistingExpirationDate(WhseJnlLine."Item No.", WhseJnlLine."Variant Code", TempItemTrackingSetup, false, EntriesExist);
                            if EntriesExist then
                                WhseJnlLine."Expiration Date" := ExpDate
                            else
                                WhseJnlLine."Expiration Date" := WhseEntry."Expiration Date";
                            WhseJnlLine."Phys. Inventory" := true;

                            WhseJnlLine."Qty. (Calculated)" := Round(WhseEntry."Qty. (Base)" / ItemUOM."Qty. per Unit of Measure", UOMMgt.QtyRndPrecision());
                            WhseJnlLine."Qty. (Calculated) (Base)" := WhseEntry."Qty. (Base)";

                            IsHandled := false;
                            OnInsertWhseJnlLineOnBeforeValidateQtyPhysInventory(WhseJnlLine, WhseEntry, IsHandled);
                            if not IsHandled then begin
                                WhseJnlLine.Validate("Qty. (Phys. Inventory)", WhseJnlLine."Qty. (Calculated)");
                                WhseJnlLine.Validate("Qty. (Phys. Inventory) (Base)", WhseEntry."Qty. (Base)");
                            end;

                            if Location."Use ADCS" then
                                WhseJnlLine.Validate("Qty. (Phys. Inventory)", 0);
                            WhseJnlLine."Registering No. Series" := WhseJnlBatch."Registering No. Series";
                            WhseJnlLine."Whse. Document Type" :=
                              WhseJnlLine."Whse. Document Type"::"Whse. Phys. Inventory";
                            if WhseJnlBatch."Reason Code" <> '' then
                                WhseJnlLine."Reason Code" := WhseJnlBatch."Reason Code";
                            WhseJnlLine."Phys Invt Counting Period Code" := PhysInvtCountCode;
                            WhseJnlLine."Phys Invt Counting Period Type" := CycleSourceType;

                            OnBeforeWhseJnlLineInsert(WhseJnlLine, WhseEntry, NextLineNo);
                            WhseJnlLine.Insert(true);
                            OnAfterWhseJnlLineInsert(WhseJnlLine, NextLineNo, "Bin Content");
                        end;
                    end;
                ProcessNext := WhseEntryTrackingDistinct.Read();
            end;
        end;

        WhseEntryTrackingDistinct.Close();
    end;

    procedure InitializeRequest(NewRegisteringDate: Date; WhseDocNo: Code[20]; ItemsNotOnInvt: Boolean)
    begin
        RegisteringDate := NewRegisteringDate;
        NextDocNo := WhseDocNo;
        ZeroQty := ItemsNotOnInvt;
    end;

    procedure InitializePhysInvtCount(PhysInvtCountCode2: Code[10]; CycleSourceType2: Option " ",Item,SKU)
    begin
        PhysInvtCountCode := PhysInvtCountCode2;
        CycleSourceType := CycleSourceType2;
    end;

    procedure SetHideValidationDialog(NewHideValidationDialog: Boolean)
    begin
        HideValidationDialog := NewHideValidationDialog;
    end;

    local procedure SkipCycleSKU(LocationCode: Code[10]; ItemNo: Code[20]; VariantCode: Code[10]): Boolean
    var
        SKU: Record "Stockkeeping Unit";
    begin
        if CycleSourceType = CycleSourceType::Item then
            if SKU.ReadPermission then
                if SKU.Get(LocationCode, ItemNo, VariantCode) then
                    exit(true);
        exit(false);
    end;

    local procedure ShouldSkipRecord(): Boolean
    var
        Item: Record Item;
    begin
        if ("Warehouse Entry"."Bin Code" = Location."Adjustment Bin Code") then
            exit(true);

        Item.SetRange("No.", "Warehouse Entry"."Item No.");
        if Item.IsEmpty() then
            exit(true);

        if SkipCycleSKU("Warehouse Entry"."Location Code", "Warehouse Entry"."Item No.", "Warehouse Entry"."Variant Code") then
            exit(true);

        exit(false);
    end;

    procedure GetLocation(LocationCode: Code[10])
    begin
        if Location.Code <> LocationCode then begin
            Location.Get(LocationCode);
            Location.TestField("Adjustment Bin Code");
            Bin.Get(Location.Code, Location."Adjustment Bin Code");
            Bin.TestField("Zone Code");
        end;
    end;

    procedure SetProposalMode(NewValue: Boolean)
    begin
        StockProposal := NewValue;
    end;

    local procedure GetSpecificTrackingOptions(ItemNo: Code[20]; var SerialNoActive: Boolean; var LotNoActive: Boolean; var PackageNoActive: Boolean)
    var
        Item: Record Item;
        ItemTrackingCode: Record "Item Tracking Code";
        PhysInvTrackingMgt: Codeunit "Phys. Invt. Tracking Mgt.";
    begin
        // Determines which tracking dimensions drive grouping for the item.
        // Defaults to all dimensions active so untracked items still group by blank tracking values.
        SerialNoActive := true;
        LotNoActive := true;
        PackageNoActive := true;

        if not Item.Get(ItemNo) then
            exit;

        if not PhysInvTrackingMgt.GetTrackingNosFromWhse(Item) then
            exit;

        if not ItemTrackingCode.Get(Item."Item Tracking Code") then
            exit;

        SerialNoActive := ItemTrackingCode."SN Specific Tracking" and ItemTrackingCode."SN Warehouse Tracking";
        LotNoActive := ItemTrackingCode."Lot Specific Tracking" and ItemTrackingCode."Lot Warehouse Tracking";
        PackageNoActive := ItemTrackingCode."Package Specific Tracking" and ItemTrackingCode."Package Warehouse Tracking";
    end;

    local procedure BuildSpecificTrackingKey(var WhseEntryTrackingDistinct: Query "Whse. Entry Tracking Distinct"; SerialNoActive: Boolean; LotNoActive: Boolean; PackageNoActive: Boolean): Text
    var
        SerialNo: Code[50];
        LotNo: Code[50];
        PackageNo: Code[50];
        Separator: Char;
    begin
        // Builds a deduplication key using only active dimensions; inactive dimensions contribute blank.
        // Uses a non-printable separator (NUL) to prevent collisions if Code values contain printable delimiters.
        Separator := 0;
        if SerialNoActive then
            SerialNo := WhseEntryTrackingDistinct.Serial_No_;
        if LotNoActive then
            LotNo := WhseEntryTrackingDistinct.Lot_No_;
        if PackageNoActive then
            PackageNo := WhseEntryTrackingDistinct.Package_No_;
        exit(SerialNo + Separator + LotNo + Separator + PackageNo);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterInitBinContent(var BinContent: Record "Bin Content"; WarehouseEntry: Record "Warehouse Entry")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterOnPostReport(var WarehouseJournalLine: Record "Warehouse Journal Line")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterWhseJnlLineInsert(var WarehouseJournalLine: Record "Warehouse Journal Line"; var NextLineNo: Integer; var BinContent: Record "Bin Content")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeWhseJnlLineInsert(var WarehouseJournalLine: Record "Warehouse Journal Line"; var WarehouseEntry: Record "Warehouse Entry"; var NextLineNo: Integer)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBinContentOnBeforePreDataItem(var BinContent: Record "Bin Content"; var WarehouseJournalLine: Record "Warehouse Journal Line")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnInsertWhseJnlLineOnAfterWhseEntrySetFilters(var WhseEntry: Record "Warehouse Entry"; var BinContent: Record "Bin Content")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnWarehouseEntryOnAfterGetRecordOnAfterCalcSkipRecord(var WarehouseEntry: Record "Warehouse Entry"; var SkipRecord: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnInsertWhseJnlLineOnBeforeValidateQtyPhysInventory(var WarehouseJournalLine: Record "Warehouse Journal Line"; var WarehouseEntry: Record "Warehouse Entry"; var IsHandled: Boolean)
    begin
    end;
}