Codeunit 99000869 Mfg. Invt. Profile Offsetting

App
Base Application
Namespace
Microsoft.Inventory.Tracking
Versions
27-28

Procedures, 2Events, 7

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Manufacturing/Inventory/Tracking/MfgInvtProfileOffsetting.Codeunit.al420 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.Tracking;

using Microsoft.Inventory.Item;
using Microsoft.Inventory.Location;
using Microsoft.Inventory.Planning;
using Microsoft.Inventory.Requisition;
using Microsoft.Inventory.Setup;
using Microsoft.Manufacturing.Document;
using Microsoft.Manufacturing.ProductionBOM;
using Microsoft.Manufacturing.Setup;

codeunit 99000869 "Mfg. Invt. Profile Offsetting"
{
    Permissions = TableData "Reservation Entry" = id,
                  TableData "Prod. Order Capacity Need" = rmd;

#if not CLEAN27
    var
        InventoryProfileOffsetting: Codeunit "Inventory Profile Offsetting";
#endif

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnAfterSupplyToInvProfile', '', true, false)]
    local procedure OnAfterSupplyToInvProfile(var InventoryProfile: Record "Inventory Profile"; var Item: Record Item; var ToDate: Date; var ReservEntry: Record "Reservation Entry" temporary; var NextLineNo: Integer)
    begin
        TransProdOrderToProfile(InventoryProfile, Item, ToDate, NextLineNo, ReservEntry);
    end;

    internal procedure TransProdOrderToProfile(var InventoryProfile: Record "Inventory Profile"; var Item: Record Item; ToDate: Date; var LineNo: Integer; var TempItemTrkgEntry: Record "Reservation Entry" temporary)
    var
        ItemLedgEntry: Record Microsoft.Inventory.Ledger."Item Ledger Entry";
        ProdOrderLine: Record "Prod. Order Line";
        CapLedgEntry: Record Microsoft.Manufacturing.Capacity."Capacity Ledger Entry";
        ProdOrderComp: Record "Prod. Order Component";
        ProdOrderLineReserve: Codeunit "Prod. Order Line-Reserve";
        ShouldProcess: Boolean;
        IsHandled: Boolean;
    begin
        OnBeforeTransProdOrderToProfile(InventoryProfile, Item, ToDate, IsHandled);
#if not CLEAN27
        InventoryProfileOffsetting.RunOnBeforeTransProdOrderToProfile(InventoryProfile, Item, ToDate, IsHandled);
#endif
        if not IsHandled then
            if ProdOrderLine.FindLinesWithItemToPlan(Item, true) then
                repeat
                    ShouldProcess := ProdOrderLine."Due Date" <> 0D;
                    OnTransProdOrderToProfileOnBeforeProcessLine(ProdOrderLine, ShouldProcess);
#if not CLEAN27
                    InventoryProfileOffsetting.RunOnTransProdOrderToProfileOnBeforeProcessLine(ProdOrderLine, ShouldProcess);
#endif
                    if ShouldProcess then begin
                        InventoryProfile.Init();
                        LineNo += 1;
                        InventoryProfile."Line No." := LineNo;
                        ProdOrderLineReserve.TransferInventoryProfileFromProdOrderLine(InventoryProfile, ProdOrderLine, TempItemTrkgEntry);
                        if (ProdOrderLine."Planning Flexibility" = ProdOrderLine."Planning Flexibility"::Unlimited) and
                           (ProdOrderLine.Status = ProdOrderLine.Status::Released)
                        then begin
                            CapLedgEntry.SetCurrentKey("Order Type", "Order No.");
                            CapLedgEntry.SetRange("Order Type", CapLedgEntry."Order Type"::Production);
                            CapLedgEntry.SetRange("Order No.", ProdOrderLine."Prod. Order No.");
                            ItemLedgEntry.Reset();
                            ItemLedgEntry.SetCurrentKey("Order Type", "Order No.");
                            ItemLedgEntry.SetRange("Order Type", ItemLedgEntry."Order Type"::Production);
                            ItemLedgEntry.SetRange("Order No.", ProdOrderLine."Prod. Order No.");
                            if not (CapLedgEntry.IsEmpty() and ItemLedgEntry.IsEmpty) then
                                InventoryProfile."Planning Flexibility" := InventoryProfile."Planning Flexibility"::None
                            else begin
                                ProdOrderComp.SetRange(Status, ProdOrderLine.Status);
                                ProdOrderComp.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
                                ProdOrderComp.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
                                ProdOrderComp.SetFilter("Qty. Picked (Base)", '>0');
                                if not ProdOrderComp.IsEmpty() then
                                    InventoryProfile."Planning Flexibility" := InventoryProfile."Planning Flexibility"::None;
                            end;
                        end;

                        OnTransProdOrderToProfileOnBeforeInsertSupplyInvtProfile(InventoryProfile, ProdOrderLine);
                        InventoryProfile.InsertSupplyInvtProfile(ToDate);
                    end;
                until ProdOrderLine.Next() = 0;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnAfterDisableRelations', '', true, false)]
    local procedure OnAfterDisableRelations(var RequisitionLine: Record "Requisition Line")
    begin
        DisableProdRelations(RequisitionLine);
    end;

    local procedure DisableProdRelations(ReqLine: Record "Requisition Line")
    var
        PlanningRtngLine: Record Microsoft.Manufacturing.Routing."Planning Routing Line";
        ProdOrderCapNeed: Record "Prod. Order Capacity Need";
    begin
        PlanningRtngLine.SetRange("Worksheet Template Name", ReqLine."Worksheet Template Name");
        PlanningRtngLine.SetRange("Worksheet Batch Name", ReqLine."Journal Batch Name");
        PlanningRtngLine.SetRange("Worksheet Line No.", ReqLine."Line No.");
        PlanningRtngLine.DeleteAll();

        ProdOrderCapNeed.SetCurrentKey("Worksheet Template Name", "Worksheet Batch Name", "Worksheet Line No.");
        ProdOrderCapNeed.SetRange("Worksheet Template Name", ReqLine."Worksheet Template Name");
        ProdOrderCapNeed.SetRange("Worksheet Batch Name", ReqLine."Journal Batch Name");
        ProdOrderCapNeed.SetRange("Worksheet Line No.", ReqLine."Line No.");
        ProdOrderCapNeed.DeleteAll();
        ProdOrderCapNeed.Reset();
        ProdOrderCapNeed.SetCurrentKey(Status, "Prod. Order No.", Active);
        ProdOrderCapNeed.SetRange(Status, ReqLine."Ref. Order Status");
        ProdOrderCapNeed.SetRange("Prod. Order No.", ReqLine."Ref. Order No.");
        ProdOrderCapNeed.SetRange(Active, true);
        ProdOrderCapNeed.ModifyAll(Active, false);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnMaintainPlanningLineOnAfterSetSourceForNotNewReqLine', '', true, false)]
    local procedure OnMaintainPlanningLineOnAfterSetSourceForNotNewReqLine(var RequisitionLine: Record "Requisition Line"; var InventoryProfile: Record "Inventory Profile")
    begin
        case InventoryProfile."Source Type" of
            Database::"Prod. Order Line":
                SetProdOrder(RequisitionLine, InventoryProfile);
        end;
    end;

    local procedure SetProdOrder(var ReqLine: Record "Requisition Line"; var InventoryProfile: Record "Inventory Profile")
    var
        ProdOrderLine: Record "Prod. Order Line";
    begin
        ReqLine."Ref. Order Type" := ReqLine."Ref. Order Type"::"Prod. Order";
        ReqLine."Ref. Order Status" := "Production Order Status".FromInteger(InventoryProfile."Source Order Status");
        ReqLine."Ref. Order No." := InventoryProfile."Source ID";
        ReqLine."Ref. Line No." := InventoryProfile."Source Prod. Order Line";
        ProdOrderLine.Get(ReqLine."Ref. Order Status", ReqLine."Ref. Order No.", ReqLine."Ref. Line No.");
        ReqLine.TransferFromProdOrderLine(ProdOrderLine);

        OnAfterSetProdOrder(ReqLine, ProdOrderLine, InventoryProfile);
#if not CLEAN27
        InventoryProfileOffsetting.RunOnAfterSetProdOrder(ReqLine, ProdOrderLine, InventoryProfile);
#endif
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnMaintainPlanningLineOnBeforeValidateNo', '', true, false)]
    local procedure OnMaintainPlanningLineOnBeforeValidateNo(var RequisitionLine: Record "Requisition Line"; InventoryProfile: Record "Inventory Profile"; StockkeepingUnit: Record "Stockkeeping Unit")
    begin
        case StockkeepingUnit."Replenishment System" of
            StockkeepingUnit."Replenishment System"::"Prod. Order":
                SetProdOrderRefOrderType(RequisitionLine, InventoryProfile);
        end;
    end;

    local procedure SetProdOrderRefOrderType(var ReqLine: Record "Requisition Line"; SupplyInvtProfile: Record "Inventory Profile")
    begin
        ReqLine."Ref. Order Type" := ReqLine."Ref. Order Type"::"Prod. Order";
        if ReqLine."Planning Level" > 0 then begin
            ReqLine."Ref. Order Status" := "Production Order Status".FromInteger(SupplyInvtProfile."Primary Order Status");
            ReqLine."Ref. Order No." := SupplyInvtProfile."Primary Order No.";
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnIsProdOrderPlanned', '', true, false)]
    local procedure OnIsProdOrderPlanned(var ReservEntry: Record "Reservation Entry"; var Planned: Boolean)
    begin
        Planned := IsProdOrderPlanned(ReservEntry);
    end;

    local procedure IsProdOrderPlanned(ReservationEntry: Record "Reservation Entry"): Boolean
    var
        ProdOrderComponent: Record "Prod. Order Component";
        RequisitionLine: Record "Requisition Line";
    begin
        ProdOrderComponent.SetLoadFields(Status, "Prod. Order No.", "Prod. Order Line No.");
        if not ProdOrderComponent.Get(
             ReservationEntry."Source Subtype", ReservationEntry."Source ID",
             ReservationEntry."Source Prod. Order Line", ReservationEntry."Source Ref. No.")
        then
            exit(false);

        RequisitionLine.SetRefOrderFilters(
          RequisitionLine."Ref. Order Type"::"Prod. Order", ProdOrderComponent.Status.AsInteger(),
          ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.");
        RequisitionLine.SetRange("Operation No.", '');

        exit(not RequisitionLine.IsEmpty());
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnAfterShouldDeleteReservEntry', '', true, false)]
    local procedure OnAfterShouldDeleteReservEntry(ReservationEntry: Record "Reservation Entry"; ToDate: Date; var DeleteCondition: Boolean; TemplateName: Code[10]; WorksheetName: Code[10])
    var
        SupplyReservationEntry: Record "Reservation Entry";
    begin
        if DeleteCondition then
            exit;
        if not (ReservationEntry."Reservation Status" in
                [ReservationEntry."Reservation Status"::Reservation, ReservationEntry."Reservation Status"::Tracking]) then
            exit;
        if ReservationEntry.Binding <> ReservationEntry.Binding::"Order-to-Order" then
            exit;
        if ReservationEntry."Shipment Date" > ToDate then
            exit;
        if ReservationEntry.Positive then
            exit;
        if not ProdComponentItemOnMultipleLines(ReservationEntry) then
            exit;

        if SupplyReservationEntry.Get(ReservationEntry."Entry No.", true) then
            SupplyReservationEntry.Delete();
        DeleteCondition := true;
    end;

    local procedure ProdComponentItemOnMultipleLines(DemandReservationEntry: Record "Reservation Entry"): Boolean
    var
        ProdOrderComponent: Record "Prod. Order Component";
        SameItemProdOrderComponent: Record "Prod. Order Component";
    begin
        if DemandReservationEntry."Source Type" <> Database::"Prod. Order Component" then
            exit(false);

        ProdOrderComponent.SetLoadFields(Status, "Prod. Order No.", "Item No.", "Line No.");
        if not ProdOrderComponent.Get(
             DemandReservationEntry."Source Subtype", DemandReservationEntry."Source ID",
             DemandReservationEntry."Source Prod. Order Line", DemandReservationEntry."Source Ref. No.")
        then
            exit(false);

        SameItemProdOrderComponent.SetRange(Status, ProdOrderComponent.Status);
        SameItemProdOrderComponent.SetRange("Prod. Order No.", ProdOrderComponent."Prod. Order No.");
        SameItemProdOrderComponent.SetRange("Item No.", ProdOrderComponent."Item No.");
        SameItemProdOrderComponent.SetFilter("Line No.", '<>%1', ProdOrderComponent."Line No.");
        exit(not SameItemProdOrderComponent.IsEmpty());
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnMaintainPlanningLineOnAfterValidateFieldsForNewReqLine', '', true, false)]
    local procedure OnMaintainPlanningLineOnAfterValidateFieldsForNewReqLine(var RequisitionLine: Record "Requisition Line"; InventoryProfile: Record "Inventory Profile"; StockkeepingUnit: Record "Stockkeeping Unit")
    var
        ManufacturingSetup: Record "Manufacturing Setup";
    begin
        ManufacturingSetup.Get();
        RequisitionLine."Starting Time" := ManufacturingSetup."Normal Starting Time";
        RequisitionLine."Ending Time" := ManufacturingSetup."Normal Ending Time";
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnGetComponentsAtLocationCode', '', true, false)]
    local procedure OnGetComponentsAtLocationCode(var LocationCode: Code[10])
    var
        ManufacturingSetup: Record "Manufacturing Setup";
    begin
        ManufacturingSetup.Get();
        LocationCode := ManufacturingSetup."Components at Location";
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnGetComponentsOnNewActionMessage', '', true, false)]
    local procedure OnGetComponentsOnNewActionMessage(var RequisitionLine: Record "Requisition Line")
    var
        VersionManagement: Codeunit VersionManagement;
    begin
        if RequisitionLine."Production BOM No." <> '' then
            RequisitionLine.Validate("Production BOM Version Code",
                VersionManagement.GetBOMVersion(RequisitionLine."Production BOM No.", RequisitionLine."Due Date", true));
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnGetComponentsOnUpdateForActionMessage', '', true, false)]
    local procedure OnGetComponentsOnUpdateForActionMessage(var RequisitionLine: Record "Requisition Line"; sender: Codeunit "Inventory Profile Offsetting")
    var
        PlanningComponent: Record "Planning Component";
        ProdOrderComponent: Record "Prod. Order Component";
    begin
        case RequisitionLine."Ref. Order Type" of
            RequisitionLine."Ref. Order Type"::"Prod. Order":
                begin
                    ProdOrderComponent.SetRange(Status, RequisitionLine."Ref. Order Status");
                    ProdOrderComponent.SetRange("Prod. Order No.", RequisitionLine."Ref. Order No.");
                    ProdOrderComponent.SetRange("Prod. Order Line No.", RequisitionLine."Ref. Line No.");
                    if ProdOrderComponent.Find('-') then
                        repeat
                            PlanningComponent.InitFromRequisitionLine(RequisitionLine);
                            PlanningComponent.TransferFromComponent(ProdOrderComponent);
                            sender.InsertPlanningComponent(PlanningComponent);
                        until ProdOrderComponent.Next() = 0;
                end;
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnGetRouting', '', true, false)]
    local procedure OnGetRouting(var RequisitionLine: Record "Requisition Line"; var sender: Codeunit "Inventory Profile Offsetting")
    begin
        GetRouting(RequisitionLine, sender);
    end;

    procedure GetRouting(var ReqLine: Record "Requisition Line"; var sender: Codeunit "Inventory Profile Offsetting")
    var
        PlanRoutingLine: Record Microsoft.Manufacturing.Routing."Planning Routing Line";
        ProdOrderRoutingLine: Record Microsoft.Manufacturing.Document."Prod. Order Routing Line";
        ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line";
        VersionMgt: Codeunit Microsoft.Manufacturing.ProductionBOM.VersionManagement;
    begin
        if ReqLine.Quantity <= 0 then
            exit;

        if (ReqLine."Action Message" = ReqLine."Action Message"::New) or
           (ReqLine."Ref. Order Type" = ReqLine."Ref. Order Type"::Purchase)
        then begin
            if ReqLine."Routing No." <> '' then
                ReqLine.Validate(ReqLine."Routing Version Code",
                  VersionMgt.GetRtngVersion(ReqLine."Routing No.", ReqLine."Due Date", true));
        end else
            if ReqLine."Ref. Order Type" = ReqLine."Ref. Order Type"::"Prod. Order" then begin
                ProdOrderLine.Get(ReqLine."Ref. Order Status", ReqLine."Ref. Order No.", ReqLine."Ref. Line No.");
                ProdOrderRoutingLine.SetRange(Status, ProdOrderLine.Status);
                ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
                ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLine."Routing Reference No.");
                ProdOrderRoutingLine.SetRange("Routing No.", ProdOrderLine."Routing No.");
                sender.DisableRelations(ReqLine);
                if ProdOrderRoutingLine.Find('-') then
                    repeat
                        PlanRoutingLine.Init();
                        PlanRoutingLine."Worksheet Template Name" := ReqLine."Worksheet Template Name";
                        PlanRoutingLine."Worksheet Batch Name" := ReqLine."Journal Batch Name";
                        PlanRoutingLine."Worksheet Line No." := ReqLine."Line No.";
                        PlanRoutingLine.TransferFromProdOrderRouting(ProdOrderRoutingLine);
                        PlanRoutingLine.Insert();
                    until ProdOrderRoutingLine.Next() = 0;
                OnAfterGetRoutingFromProdOrder(ReqLine);
#if not CLEAN27
                InventoryProfileOffsetting.RunOnAfterGetRoutingFromProdOrder(ReqLine);
#endif
            end;
        OnAfterGetRouting(ReqLine);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnFillTempSKUForLocation', '', true, false)]
    local procedure OnFillTempSKUForLocation(var Item: Record Item; var SupplyInvtProfile: Record "Inventory Profile"; sender: Codeunit "Inventory Profile Offsetting")
    var
        InventorySetup: Record "Inventory Setup";
        ManufacturingSetup: Record "Manufacturing Setup";
        ProdOrderWarehouseMgt: Codeunit "Prod. Order Warehouse Mgt.";
        VersionManagement: Codeunit VersionManagement;
        IsHandled: Boolean;
    begin
        InventorySetup.Get();
        ManufacturingSetup.Get();
        if (not InventorySetup."Location Mandatory") and (ManufacturingSetup."Components at Location" = '') then begin
            IsHandled := false;
            OnFindCombinationOnBeforeCreateTempSKUForLocation(Item, IsHandled);
#if not CLEAN27
            InventoryProfileOffsetting.RunOnFindCombinationOnBeforeCreateTempSKUForLocation(Item, IsHandled);
#endif
            if not IsHandled then
                sender.CreateTempSKUForLocation(
                    Item."No.",
                    ProdOrderWarehouseMgt.GetLastOperationLocationCode(
                        Item."Routing No.", VersionManagement.GetRtngVersion(Item."Routing No.", SupplyInvtProfile."Due Date", true)));
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Inventory Profile Offsetting", 'OnBeforeMatchAttributes', '', true, false)]
    local procedure CheckDemandAndSupplyQuantityAreEqual(var SupplyInvtProfile: Record "Inventory Profile"; var DemandInvtProfile: Record "Inventory Profile"; var SkipMatchingAttributes: Boolean)
    var
        TotalDemandQty: Decimal;
        TotalSupplyQty: Decimal;
    begin
        if (SupplyInvtProfile."Source Type" <> 5406) or // Database::"Prod. Order Line"
           (DemandInvtProfile."Source Type" <> 5407) // Database::"Prod. Order Component"
        then begin
            SkipMatchingAttributes := false;
            exit;
        end;

        DemandInvtProfile.ReadIsolation(IsolationLevel::ReadUncommitted);
        DemandInvtProfile.CalcSums(Quantity);
        TotalDemandQty := DemandInvtProfile.Quantity;

        if (TotalDemandQty <= 0) then begin
            SkipMatchingAttributes := false;
            exit;
        end;

        SupplyInvtProfile.ReadIsolation(IsolationLevel::ReadUncommitted);
        SupplyInvtProfile.CalcSums(Quantity);
        TotalSupplyQty := SupplyInvtProfile.Quantity;

        if TotalSupplyQty = TotalDemandQty then
            SkipMatchingAttributes := true;
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransProdOrderToProfile(var InventoryProfile: Record "Inventory Profile"; var Item: Record Item; ToDate: Date; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnTransProdOrderToProfileOnBeforeProcessLine(ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line"; var ShouldProcess: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterSetProdOrder(var ReqLine: Record "Requisition Line"; var ProdOrderLine: Record Microsoft.Manufacturing.Document."Prod. Order Line"; var InventoryProfile: Record "Inventory Profile")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterGetRoutingFromProdOrder(var RequisitionLine: Record "Requisition Line")
    begin
    end;

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

    [IntegrationEvent(false, false)]
    local procedure OnTransProdOrderToProfileOnBeforeInsertSupplyInvtProfile(var InventoryProfile: Record "Inventory Profile"; ProdOrderLine: Record "Prod. Order Line")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterGetRouting(var RequisitionLine: Record "Requisition Line")
    begin
    end;
}