Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -2901,6 +2901,7 @@ codeunit 99000854 "Inventory Profile Offsetting"

local procedure UpdateReqLineQuantity(var SupplyInventoryProfile: Record "Inventory Profile")
var
QtyRoundingPrecisionBase: Decimal;
IsHandled: Boolean;
begin
IsHandled := false;
Expand All @@ -2911,6 +2912,17 @@ codeunit 99000854 "Inventory Profile Offsetting"
ReqLine.Validate(
Quantity,
Round(SupplyInventoryProfile."Remaining Quantity (Base)" / SupplyInventoryProfile."Qty. per Unit of Measure", UOMMgt.QtyRndPrecision()));

QtyRoundingPrecisionBase := ReqLine."Qty. Rounding Precision (Base)";
if QtyRoundingPrecisionBase = 0 then
QtyRoundingPrecisionBase := UOMMgt.QtyRndPrecision();
if Abs(ReqLine."Quantity (Base)" - SupplyInventoryProfile."Remaining Quantity (Base)") <=
(SupplyInventoryProfile."Qty. per Unit of Measure" * UOMMgt.QtyRndPrecision() + QtyRoundingPrecisionBase) / 2
then
if ReqLine."Quantity (Base)" <> SupplyInventoryProfile."Remaining Quantity (Base)" then begin
ReqLine."Remaining Qty. (Base)" += SupplyInventoryProfile."Remaining Quantity (Base)" - ReqLine."Quantity (Base)";
ReqLine."Quantity (Base)" := SupplyInventoryProfile."Remaining Quantity (Base)";
end;
end;

local procedure UpdateReqLineOriginalQuantity(var SupplyInventoryProfile: Record "Inventory Profile")
Expand Down
96 changes: 96 additions & 0 deletions src/Layers/W1/Tests/SCM/SCMPlanningNTFtests.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,62 @@ codeunit 137021 "SCM Planning - NTF tests"
LibraryJob.CreateJobTask(Job, JobTask);
end;

local procedure CreateItemWithNonBasePurchUoM(var Item: Record Item; var ItemUnitOfMeasure: Record "Item Unit of Measure"; QtyPerUoM: Decimal)
begin
ItemSetup(Item, Item."Replenishment System"::Purchase, '<0D>');
Item.Validate("Reordering Policy", Item."Reordering Policy"::"Lot-for-Lot");
Item.Validate("Vendor No.", LibraryPurchase.CreateVendorNo());
LibraryInventory.CreateItemUnitOfMeasureCode(ItemUnitOfMeasure, Item."No.", QtyPerUoM);
Item.Validate("Purch. Unit of Measure", ItemUnitOfMeasure.Code);
Item.Modify(true);
end;

local procedure AcceptAndCarryOutActionMessage(ItemNo: Code[20])
var
RequisitionLine: Record "Requisition Line";
begin
RequisitionLine.SetRange(Type, RequisitionLine.Type::Item);
RequisitionLine.SetRange("No.", ItemNo);
RequisitionLine.FindSet(true);
repeat
RequisitionLine.Validate("Accept Action Message", true);
RequisitionLine.Modify(true);
until RequisitionLine.Next() = 0;
LibraryPlanning.CarryOutActionMsgPlanWksh(RequisitionLine);
end;

local procedure FindPurchaseLineByItemNo(var PurchaseLine: Record "Purchase Line"; ItemNo: Code[20])
begin
PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
PurchaseLine.SetRange("No.", ItemNo);
PurchaseLine.FindFirst();
end;

local procedure VerifyReservEntryQtyBase(SourceType: Integer; SourceSubtype: Integer; SourceID: Code[20]; ExpectedQtyBase: Decimal)
var
ReservationEntry: Record "Reservation Entry";
TotalQtyBase: Decimal;
begin
ReservationEntry.SetRange("Source Type", SourceType);
ReservationEntry.SetRange("Source Subtype", SourceSubtype);
ReservationEntry.SetRange("Source ID", SourceID);
if ReservationEntry.FindSet() then
repeat
TotalQtyBase += Abs(ReservationEntry."Quantity (Base)");
until ReservationEntry.Next() = 0;
Assert.AreEqual(ExpectedQtyBase, TotalQtyBase,
'Reservation Entry Quantity (Base) must match demand.');
end;

local procedure VerifyNoReservEntryExists(ItemNo: Code[20])
var
ReservationEntry: Record "Reservation Entry";
begin
ReservationEntry.SetRange("Item No.", ItemNo);
Assert.RecordIsEmpty(ReservationEntry);
end;

local procedure CreateSaleDocType(var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type"; Item: Record Item; SalesQty: Integer; ShipmentDate: Date; LocationCode: Code[10])
var
SalesLine: Record "Sales Line";
Expand Down Expand Up @@ -3647,6 +3703,46 @@ codeunit 137021 "SCM Planning - NTF tests"
RequisitionLine."Ref. Order Type"::Transfer, LocationTwo.Code, 1);
end;

[Test]
[Scope('OnPrem')]
procedure JobPlanLineNonBaseUoMCarryOutDeletePlanLine()
var
Item: Record Item;
ItemUnitOfMeasure: Record "Item Unit of Measure";
JobTask: Record "Job Task";
JobPlanningLine: Record "Job Planning Line";
PurchaseHeader: Record "Purchase Header";
PurchaseLine: Record "Purchase Line";
QtyPerUoM: Decimal;
JobQuantity: Decimal;
begin
// [SCENARIO 642150] Job Planning Line with non-base Purchase UoM can be deleted after carrying out the planning Action Message, without orphan reservation entries.

Initialize();

// [GIVEN] Item "I" with non-base Purchase UoM "U" with Qty per UoM = 1.5, Reordering Policy = Lot-for-Lot, and Job Planning Line "J" with quantity = 1.
// [GIVEN] Qty per UoM = 1.5 and quantity = 1 reproduce a base-quantity conversion rounding mismatch that also depends on the base rounding step (1/1.5 rounded to 0.00001 precision converts back to 1.00001).
TestSetup();
QtyPerUoM := 1.5;
JobQuantity := 1;
CreateItemWithNonBasePurchUoM(Item, ItemUnitOfMeasure, QtyPerUoM);
CreateJobAndPlanningLine(JobTask, JobPlanningLine, Item."No.", JobQuantity, true);

// [WHEN] Calculate Regenerative Plan is run and Action Message is carried out to create a Purchase Order "P".
LibraryPlanning.CalcRegenPlanForPlanWksh(Item, WorkDate(), CalcDate(PlanningEndDate, WorkDate()));
AcceptAndCarryOutActionMessage(Item."No.");

// [THEN] Reservation entries linked to "P" have correct Quantity (Base) matching "J" quantity, and deleting "P" and then "J" succeeds without leaving orphan reservation entries.
FindPurchaseLineByItemNo(PurchaseLine, Item."No.");
PurchaseHeader.Get(PurchaseHeader."Document Type"::Order, PurchaseLine."Document No.");
VerifyReservEntryQtyBase(
Database::"Purchase Line", PurchaseHeader."Document Type".AsInteger(), PurchaseHeader."No.", JobQuantity);
PurchaseHeader.Delete(true);
JobPlanningLine.Find();
JobPlanningLine.Delete(true);
VerifyNoReservEntryExists(Item."No.");
end;

[ModalPageHandler]
[Scope('OnPrem')]
procedure ItemTrackingPageHandler(var ItemTrackingLines: TestPage "Item Tracking Lines")
Expand Down
Loading