Codeunit 5777 Whse. Validate Source Line, source in 29

Source29

src/Layers/W1/BaseApp/Warehouse/Request/WhseValidateSourceLine.Codeunit.al426 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.Request;

using Microsoft.Inventory.Item;
using Microsoft.Inventory.Journal;
using Microsoft.Inventory.Location;
using Microsoft.Inventory.Transfer;
using Microsoft.Projects.Project.Job;
using Microsoft.Projects.Project.Journal;
using Microsoft.Projects.Project.Planning;
using Microsoft.Projects.Project.Setup;
using Microsoft.Warehouse.Activity;
using Microsoft.Warehouse.Document;
using Microsoft.Warehouse.Setup;
using Microsoft.Warehouse.Worksheet;

codeunit 5777 "Whse. Validate Source Line"
{
    trigger OnRun()
    begin
    end;

    var
        WhseActivLine: Record "Warehouse Activity Line";
        TableCaptionValue: Text;

#pragma warning disable AA0074, AA0470
        Text000: Label 'must not be changed when a %1 for this %2 exists: ';
        Text001: Label 'The %1 cannot be deleted when a related %2 exists.';
#pragma warning restore AA0074, AA0470
        JobPostQtyPickRemainErr: Label 'You cannot post usage for project number %1 because a quantity of %2 remains to be picked.', Comment = '%1 = Project number, %2 = remaining quantity to pick';

    procedure VerifyFieldNotChanged(NewRecRef: RecordRef; OldRecRef: RecordRef; FieldNumber: Integer)
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeVerifyFieldNotChanged(NewRecRef, OldRecRef, FieldNumber, IsHandled);
        if IsHandled then
            exit;

        VerifyFieldHasSameValue(NewRecRef, OldRecRef, FieldNumber, StrSubstNo(Text000, TableCaptionValue, NewRecRef.Caption));
    end;

    procedure VerifyFieldNotChanged(NewRecRef: RecordRef; OldRecRef: RecordRef; FieldNumber: Integer; ErrorMessage: Text)
    begin
        VerifyFieldHasSameValue(NewRecRef, OldRecRef, FieldNumber, ErrorMessage);
    end;

    local procedure VerifyFieldHasSameValue(FirstRecordRef: RecordRef; SecondRecordRef: RecordRef; FieldNumber: Integer; ErrorMessage: Text)
    var
        FirstFieldRef: FieldRef;
        SecondFieldRef: FieldRef;
    begin
        FirstFieldRef := FirstRecordRef.Field(FieldNumber);
        SecondFieldRef := SecondRecordRef.Field(FieldNumber);

        if Format(FirstFieldRef.Value) <> Format(SecondFieldRef.Value) then
            FirstFieldRef.FieldError(ErrorMessage);
    end;

    internal procedure FieldValueIsChanged(FirstRecordRef: RecordRef; SecondRecordRef: RecordRef; FieldNumber: Integer): Boolean
    var
        FirstFieldRef: FieldRef;
        SecondFieldRef: FieldRef;
    begin
        FirstFieldRef := FirstRecordRef.Field(FieldNumber);
        SecondFieldRef := SecondRecordRef.Field(FieldNumber);

        if Format(FirstFieldRef.Value) <> Format(SecondFieldRef.Value) then
            exit(true);

        exit(false);
    end;

    procedure TransLineVerifyChange(var NewTransLine: Record "Transfer Line"; var OldTransLine: Record "Transfer Line")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTransLineVerifyChange(NewTransLine, OldTransLine, IsHandled);
        if IsHandled then
            exit;

        if WhseLinesExist(Database::"Transfer Line", 0, NewTransLine."Document No.", NewTransLine."Line No.", 0, NewTransLine.Quantity) then begin
            TransLineCommonVerification(NewTransLine, OldTransLine);
            if NewTransLine."Qty. to Ship" <> OldTransLine."Qty. to Ship" then
                NewTransLine.FieldError("Qty. to Ship", StrSubstNo(Text000, TableCaptionValue, NewTransLine.TableCaption));
        end;

        if WhseLinesExist(Database::"Transfer Line", 1, NewTransLine."Document No.", NewTransLine."Line No.", 0, NewTransLine.Quantity) then begin
            TransLineCommonVerification(NewTransLine, OldTransLine);
            if NewTransLine."Qty. to Receive" <> OldTransLine."Qty. to Receive" then
                NewTransLine.FieldError("Qty. to Receive", StrSubstNo(Text000, TableCaptionValue, NewTransLine.TableCaption));
        end;

        OnAfterTransLineVerifyChange(NewTransLine, OldTransLine);
    end;

    local procedure TransLineCommonVerification(var NewTransLine: Record "Transfer Line"; var OldTransLine: Record "Transfer Line")
    var
        IsHandled: Boolean;
    begin
        if NewTransLine."Item No." <> OldTransLine."Item No." then
            NewTransLine.FieldError("Item No.", StrSubstNo(Text000, TableCaptionValue, NewTransLine.TableCaption));

        if NewTransLine."Variant Code" <> OldTransLine."Variant Code" then
            NewTransLine.FieldError("Variant Code", StrSubstNo(Text000, TableCaptionValue, NewTransLine.TableCaption));

        if NewTransLine."Unit of Measure Code" <> OldTransLine."Unit of Measure Code" then
            NewTransLine.FieldError("Unit of Measure Code", StrSubstNo(Text000, TableCaptionValue, NewTransLine.TableCaption));

        IsHandled := false;
        OnTransLineCommonVerificationOnBeforeQuantityCheck(OldTransLine, NewTransLine, IsHandled);
        if not IsHandled then
            if NewTransLine.Quantity <> OldTransLine.Quantity then
                NewTransLine.FieldError(Quantity, StrSubstNo(Text000, TableCaptionValue, NewTransLine.TableCaption));
    end;

    procedure TransLineDelete(var TransLine: Record "Transfer Line")
    begin
        if WhseLinesExist(Database::"Transfer Line", 0, TransLine."Document No.", TransLine."Line No.", 0, TransLine.Quantity) then
            Error(Text001, TransLine.TableCaption(), TableCaptionValue);
        if WhseLinesExist(Database::"Transfer Line", 1, TransLine."Document No.", TransLine."Line No.", 0, TransLine.Quantity) then
            Error(Text001, TransLine.TableCaption(), TableCaptionValue);

        OnAfterTransLineDelete(TransLine);
    end;

    procedure WhseLinesExist(SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceSublineNo: Integer; SourceQty: Decimal) Result: Boolean
    var
        WhseRcptLine: Record "Warehouse Receipt Line";
        WhseShptLine: Record "Warehouse Shipment Line";
        WhseManagement: Codeunit "Whse. Management";
        CheckReceipt: Boolean;
        CheckShipment: Boolean;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeWhseLinesExist(SourceType, SourceSubType, SourceNo, SourceLineNo, SourceSublineNo, SourceQty, TableCaptionValue, Result, IsHandled);
        if IsHandled then
            exit(Result);

        CheckReceipt := false;
        OnWhseLineExistOnBeforeCheckReceipt(SourceType, SourceSubType, SourceQty, CheckReceipt);
        if CheckReceipt then begin
            WhseManagement.SetSourceFilterForWhseRcptLine(WhseRcptLine, SourceType, SourceSubType, SourceNo, SourceLineNo, true);
            OnWhseLinesExistOnAfterWhseRcptLineSetFilters(WhseRcptLine, SourceType, SourceSubType, SourceNo, SourceLineNo, SourceQty);
            if not WhseRcptLine.IsEmpty() then begin
                TableCaptionValue := WhseRcptLine.TableCaption();
                exit(true);
            end;
        end;

        CheckShipment := false;
        OnWhseLineExistOnBeforeCheckShipment(SourceType, SourceSubType, SourceQty, CheckShipment);
        if CheckShipment then begin
            WhseShptLine.SetSourceFilter(SourceType, SourceSubType, SourceNo, SourceLineNo, true);
            OnWhseLinesExistOnAfterWhseShptLineSetFilters(WhseShptLine, SourceType, SourceSubType, SourceNo, SourceLineNo, SourceQty, IsHandled);
            if not IsHandled then
                if not WhseShptLine.IsEmpty() then begin
                    TableCaptionValue := WhseShptLine.TableCaption();
                    exit(true);
                end;
        end;

        WhseActivLine.SetSourceFilter(SourceType, SourceSubType, SourceNo, SourceLineNo, SourceSublineNo, true);
        IsHandled := false;
        OnWhseLinesExistOnAfterWhseActivLineSetFilters(WhseActivLine, SourceType, SourceSubType, SourceNo, SourceLineNo, SourceQty, IsHandled);
        if not IsHandled then
            if not WhseActivLine.IsEmpty() then begin
                TableCaptionValue := WhseActivLine.TableCaption();
                exit(true);
            end;

        TableCaptionValue := '';
        exit(false);
    end;

    procedure WhseLinesExistWithTableCaptionOut(SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceSublineNo: Integer; SourceQty: Decimal; var TableCaptionValueOut: Text[100]): Boolean
    var
        Success: Boolean;
    begin
        Success := WhseLinesExist(SourceType, SourceSubType, SourceNo, SourceLineNo, SourceSublineNo, SourceQty);
        TableCaptionValueOut := TableCaptionValue;
        exit(Success);
    end;

    procedure WhseWorkSheetLinesExistForJobOrProdOrderComponent(SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceSublineNo: Integer; SourceQty: Decimal): Boolean
    begin
        if (SourceType in [Database::Job, Database::"Job Planning Line", 5407]) then // Database::"Prod. Order Component"
            exit(WhseWorkSheetLinesExist(SourceType, SourceSubType, SourceNo, SourceLineNo, SourceSublineNo, SourceQty));

        TableCaptionValue := '';
        exit(false);
    end;

    internal procedure WhseWorkSheetLinesExist(SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceSublineNo: Integer; SourceQty: Decimal) Result: Boolean
    var
        WhseWorkSheetLine: Record "Whse. Worksheet Line";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeWhseWorkSheetLinesExist(SourceType, SourceSubType, SourceNo, SourceLineNo, SourceSublineNo, SourceQty, TableCaptionValue, Result, IsHandled);
        if IsHandled then
            exit(Result);

        WhseWorkSheetLine.SetSourceFilter(SourceType, SourceSubType, SourceNo, SourceLineNo, true);
        if not WhseWorkSheetLine.IsEmpty() then begin
            TableCaptionValue := WhseWorkSheetLine.TableCaption();
            exit(true);
        end;

        TableCaptionValue := '';
        exit(false);
    end;

    procedure ItemLineVerifyChange(var NewItemJnlLine: Record "Item Journal Line"; var OldItemJnlLine: Record "Item Journal Line")
    var
        LinesExist: Boolean;
        QtyChecked: Boolean;
    begin
        LinesExist := false;
        OnItemLineVerifyChangeOnCheckEntryType(NewItemJnlLine, OldItemJnlLine, LinesExist, QtyChecked);

        if LinesExist then begin
            if (NewItemJnlLine."Item No." <> OldItemJnlLine."Item No.") and
               (OldItemJnlLine."Item No." <> '')
            then
                NewItemJnlLine.FieldError("Item No.", StrSubstNo(Text000, TableCaptionValue, NewItemJnlLine.TableCaption));

            if (NewItemJnlLine."Variant Code" <> OldItemJnlLine."Variant Code") and
               (OldItemJnlLine."Variant Code" <> '')
            then
                NewItemJnlLine.FieldError("Variant Code", StrSubstNo(Text000, TableCaptionValue, NewItemJnlLine.TableCaption));

            if (NewItemJnlLine."Location Code" <> OldItemJnlLine."Location Code") and
               (OldItemJnlLine."Location Code" <> '')
            then
                NewItemJnlLine.FieldError("Location Code", StrSubstNo(Text000, TableCaptionValue, NewItemJnlLine.TableCaption));

            if (NewItemJnlLine."Unit of Measure Code" <> OldItemJnlLine."Unit of Measure Code") and
               (OldItemJnlLine."Unit of Measure Code" <> '')
            then
                NewItemJnlLine.FieldError("Unit of Measure Code", StrSubstNo(Text000, TableCaptionValue, NewItemJnlLine.TableCaption));

            if (NewItemJnlLine.Quantity <> OldItemJnlLine.Quantity) and
               (OldItemJnlLine.Quantity <> 0) and
               not QtyChecked
            then
                NewItemJnlLine.FieldError(Quantity, StrSubstNo(Text000, TableCaptionValue, NewItemJnlLine.TableCaption));
        end;

        OnAfterItemLineVerifyChange(NewItemJnlLine, OldItemJnlLine);
    end;

    internal procedure JobJnlLineVerifyChangeForWhsePick(var NewJobJnlLine: Record "Job Journal Line"; var OldJobJnlLine: Record "Job Journal Line")
    var
        JobPlanningLine: Record "Job Planning Line";
        QtyRemainingToBePicked: Decimal;
    begin
        if NewJobJnlLine.Quantity < 0 then
            exit;

        if not IsWhsePickRequiredForJobJnlLine(NewJobJnlLine) then
            exit;

        JobPlanningLine.SetLoadFields("Qty. Posted", "Qty. Picked", "Qty. to Assemble");
        if JobPlanningLine.Get(NewJobJnlLine."Job No.", NewJobJnlLine."Job Task No.", NewJobJnlLine."Job Planning Line No.") then begin
            QtyRemainingToBePicked := NewJobJnlLine.Quantity + JobPlanningLine."Qty. Posted" - JobPlanningLine."Qty. Picked" - JobPlanningLine."Qty. to Assemble";
            CheckQtyRemainingToBePickedForJob(NewJobJnlLine, QtyRemainingToBePicked);
        end;
    end;

    internal procedure IsWhsePickRequiredForJobJnlLine(var JobJournalLine: Record "Job Journal Line"): Boolean
    var
        Item: Record Item;
    begin
        if (JobJournalLine."Line Type" in [JobJournalLine."Line Type"::Budget, JobJournalLine."Line Type"::"Both Budget and Billable"]) and (JobJournalLine.Type = JobJournalLine.Type::Item) then
            if RequireWarehousePicking(JobJournalLine) then begin
                Item.SetLoadFields(Type);
                if Item.Get(JobJournalLine."No.") then
                    if Item.IsInventoriableType() then
                        exit(true);
            end;
    end;

    internal procedure IsInventoryPickRequiredForJobJnlLine(var JobJournalLine: Record "Job Journal Line"): Boolean
    var
        WarehouseActivityLine: Record "Warehouse Activity Line";
    begin
        if (JobJournalLine."Line Type" in [JobJournalLine."Line Type"::Budget, JobJournalLine."Line Type"::"Both Budget and Billable"]) and (JobJournalLine.Type = JobJournalLine.Type::Item) then
            if RequireInventoryPicking(JobJournalLine) then begin
                if JobJournalLine."Job Planning Line No." <> 0 then
                    WarehouseActivityLine.SetRange("Source Subline No.", JobJournalLine."Job Planning Line No.");
                WarehouseActivityLine.SetFilter("Source Type", '%1|%2', Database::Job, Database::"Job Planning Line");
                WarehouseActivityLine.SetRange("Source No.", JobJournalLine."Job No.");
                exit(not WarehouseActivityLine.IsEmpty());
            end;
    end;

    local procedure RequireInventoryPicking(var JobJournalLine: Record "Job Journal Line"): Boolean
    var
        Location: Record Location;
        WarehouseSetup: Record "Warehouse Setup";
    begin
        Location.SetLoadFields("Job Consump. Whse. Handling");
        if Location.Get(JobJournalLine."Location Code") then
            exit(Location."Job Consump. Whse. Handling" = Enum::"Job Consump. Whse. Handling"::"Inventory Pick");

        WarehouseSetup.SetLoadFields("Require Pick", "Require Shipment");
        WarehouseSetup.Get();
        exit(WarehouseSetup."Require Pick" and not WarehouseSetup."Require Shipment");
    end;

    local procedure RequireWarehousePicking(var JobJournalLine: Record "Job Journal Line"): Boolean
    var
        Location: Record Location;
        WarehouseSetup: Record "Warehouse Setup";
    begin
        Location.SetLoadFields("Job Consump. Whse. Handling");
        if Location.Get(JobJournalLine."Location Code") then
            exit(Location."Job Consump. Whse. Handling" = Enum::"Job Consump. Whse. Handling"::"Warehouse Pick (mandatory)");

        WarehouseSetup.SetLoadFields("Require Pick", "Require Shipment");
        WarehouseSetup.Get();
        exit(WarehouseSetup."Require Pick" and WarehouseSetup."Require Shipment");
    end;

    local procedure CheckQtyRemainingToBePickedForJob(NewJobJnlLine: Record "Job Journal Line"; QtyRemainingToBePicked: Decimal)
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeCheckQtyRemainingToBePickedForJob(NewJobJnlLine, QtyRemainingToBePicked, IsHandled);
        if IsHandled then
            exit;

        if QtyRemainingToBePicked > 0 then
            Error(JobPostQtyPickRemainErr, NewJobJnlLine."Job No.", QtyRemainingToBePicked);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterItemLineVerifyChange(var NewItemJnlLine: Record "Item Journal Line"; var OldItemJnlLine: Record "Item Journal Line")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterTransLineVerifyChange(var NewTransLine: Record "Transfer Line"; var OldTransLine: Record "Transfer Line")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterTransLineDelete(var TransferLine: Record "Transfer Line")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeCheckQtyRemainingToBePickedForJob(NewJobJnlLine: Record "Job Journal Line"; QtyRemainingToBePicked: Decimal; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransLineVerifyChange(var NewTransLine: Record "Transfer Line"; var OldTransLine: Record "Transfer Line"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeVerifyFieldNotChanged(NewRecRef: RecordRef; OldRecRef: RecordRef; FieldNumber: Integer; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeWhseLinesExist(SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceSublineNo: Integer; SourceQty: Decimal; var TableCaptionValue: Text[100]; var Result: Boolean; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnTransLineCommonVerificationOnBeforeQuantityCheck(var OldTransferLine: Record "Transfer Line"; var NewTransferLine: Record "Transfer Line"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnWhseLinesExistOnAfterWhseRcptLineSetFilters(var WhseRcptLine: Record "Warehouse Receipt Line"; SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceQty: Decimal)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnWhseLinesExistOnAfterWhseShptLineSetFilters(var WhseShptLine: Record "Warehouse Shipment Line"; SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceQty: Decimal; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeWhseWorkSheetLinesExist(SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceSublineNo: Integer; SourceQty: Decimal; var TableCaptionValue: Text[100]; var Result: Boolean; var IsHandled: Boolean)
    begin
    end;

    internal procedure RaiseCannotBeDeletedErr(SourceTableCaption: Text)
    begin
        Error(Text001, SourceTableCaption, TableCaptionValue);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnWhseLineExistOnBeforeCheckReceipt(SourceType: Integer; SourceSubType: Option; SourceQty: Decimal; var CheckReceipt: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnWhseLineExistOnBeforeCheckShipment(SourceType: Integer; SourceSubType: Option; SourceQty: Decimal; var CheckShipment: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnWhseLinesExistOnAfterWhseActivLineSetFilters(var WarehouseActivityLine: Record "Warehouse Activity Line"; SourceType: Integer; SourceSubType: Option; SourceNo: Code[20]; SourceLineNo: Integer; SourceQty: Decimal; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnItemLineVerifyChangeOnCheckEntryType(NewItemJnlLine: Record "Item Journal Line"; OldItemJnlLine: Record "Item Journal Line"; var LinesExist: Boolean; var QtyChecked: Boolean)
    begin
    end;
}