From 970ac2f1812b9b40ca7277b5d00b75de19cb6868 Mon Sep 17 00:00:00 2001 From: v-praghav Date: Sun, 26 Jul 2026 23:08:50 +0530 Subject: [PATCH] #572435 --- .../Inventory/Tracking/Reservation.Page.al | 29 +-- .../Transfer/AvailableTransferLines.Page.al | 7 +- .../SCM/SCMInventoryDocuments.Codeunit.al | 209 ++++++++++++++++++ 3 files changed, 230 insertions(+), 15 deletions(-) diff --git a/src/Layers/W1/BaseApp/Inventory/Tracking/Reservation.Page.al b/src/Layers/W1/BaseApp/Inventory/Tracking/Reservation.Page.al index 962a4be77f6..116893f46f8 100644 --- a/src/Layers/W1/BaseApp/Inventory/Tracking/Reservation.Page.al +++ b/src/Layers/W1/BaseApp/Inventory/Tracking/Reservation.Page.al @@ -297,7 +297,7 @@ page 498 Reservation begin RemainingQtyToReserveBase := QtyToReserveBase - QtyReservedBase; if RemainingQtyToReserveBase = 0 then - Error(Text000); + ValidateReservationApplicable(); QtyReservedBefore := QtyReservedBase; if HandleItemTracking then ReservMgt.SetItemTrackingHandling(2); @@ -419,6 +419,7 @@ page 498 Reservation Text003: Label 'Do you want to cancel all reservations in the %1?'; #pragma warning restore AA0470 Text005: Label 'There are no reservations to cancel.'; + Text006: Label 'Inbound quantities cannot be reserved until the items are received at the Transfer-to location.'; Text008: Label 'Action canceled.'; #pragma warning disable AA0470 Text009: Label '%1 of the %2 are nonspecific and may be available.'; @@ -687,6 +688,18 @@ page 498 Reservation Rec.SetRange("Non-specific Reserved Qty."); end; + local procedure ValidateReservationApplicable() + var + TransferDirection: Enum "Transfer Direction"; + begin + if ReservEntry."Source Type" = 5741 then begin + TransferDirection := ReservEntry.GetTransferDirection(); + if TransferDirection = TransferDirection::Inbound then + Error(Text006) + end else + Error(Text000); + end; + procedure AutoReserve() var IsHandled: Boolean; @@ -695,7 +708,7 @@ page 498 Reservation ReservEntry, FullAutoReservation, QtyToReserve, QtyReserved, QtyToReserveBase, QtyReservedBase, IsHandled); if not IsHandled then begin if Abs(QtyToReserveBase) - Abs(QtyReservedBase) = 0 then - Error(Text000); + ValidateReservationApplicable(); ReservMgt.AutoReserve( FullAutoReservation, ReservEntry.Description, ReservEntry."Shipment Date", QtyToReserve - QtyReserved, QtyToReserveBase - QtyReservedBase); @@ -738,18 +751,6 @@ page 498 Reservation begin end; - - - - - - - - - - - - [IntegrationEvent(false, false)] local procedure OnBeforeAutoReserve(ReservEntry: Record "Reservation Entry"; var FullAutoReservation: Boolean; QtyToReserve: Decimal; QtyReserved: Decimal; QtyToReserveBase: Decimal; QtyReservedBase: Decimal; var IsHandled: Boolean); begin diff --git a/src/Layers/W1/BaseApp/Inventory/Transfer/AvailableTransferLines.Page.al b/src/Layers/W1/BaseApp/Inventory/Transfer/AvailableTransferLines.Page.al index 75ff74d1bbc..2491fce5afe 100644 --- a/src/Layers/W1/BaseApp/Inventory/Transfer/AvailableTransferLines.Page.al +++ b/src/Layers/W1/BaseApp/Inventory/Transfer/AvailableTransferLines.Page.al @@ -133,7 +133,10 @@ page 99000896 "Available - Transfer Lines" else CreateReservation(NewQtyReserved, NewQtyReservedBase) else - Error(Text001); + if TransferDirection = TransferDirection::Inbound then + Error(Text004) + else + Error(Text001); end; } action(CancelReservation) @@ -195,6 +198,7 @@ page 99000896 "Available - Transfer Lines" Text002: Label 'Do you want to cancel the reservation?'; #pragma warning disable AA0470 Text003: Label 'Available Quantity is %1.'; + Text004: Label 'Inbound quantities cannot be reserved until the items are received at the Transfer-to location.'; #pragma warning restore AA0470 #pragma warning restore AA0074 @@ -313,6 +317,7 @@ page 99000896 "Available - Transfer Lines" begin Rec.SetFilter("Receipt Date", ReservMgt.GetAvailabilityFilter(ReservEntry."Shipment Date")); Rec.SetRange("Transfer-to Code", ReservEntry."Location Code"); + Rec.SetFilter("Qty. in Transit (Base)", '>0'); end; end; diff --git a/src/Layers/W1/Tests/SCM/SCMInventoryDocuments.Codeunit.al b/src/Layers/W1/Tests/SCM/SCMInventoryDocuments.Codeunit.al index 1aef42ae912..dab9033fa78 100644 --- a/src/Layers/W1/Tests/SCM/SCMInventoryDocuments.Codeunit.al +++ b/src/Layers/W1/Tests/SCM/SCMInventoryDocuments.Codeunit.al @@ -47,6 +47,10 @@ codeunit 137140 "SCM Inventory Documents" AmountShouldEqualQtyTimesUnitAmountErr: Label 'Amount should equal Quantity * Unit Amount.'; UnitAmountShouldBeDerivedErr: Label 'Unit Amount should be derived from Unit Cost and Indirect Cost %%.'; UnitCostShouldBeDerivedErr: Label 'Unit Cost should be derived from Unit Amount and Indirect Cost %%.'; + InboundReservationErr: Label 'Inbound quantities cannot be reserved until the items are received at the Transfer-to location.'; + OutboundQtyNegativeLbl: Label 'Outbound reservation should have negative quantity'; + OnlyOneLineShouldHaveQtyLbl: Label 'Only one transfer line should have qty in transit'; + ShouldBeFirstTransferLbl: Label 'Should be the first transfer order'; [Test] [Scope('OnPrem')] @@ -2496,6 +2500,175 @@ codeunit 137140 "SCM Inventory Documents" Assert.IsTrue(InvtReceiptHeader.FindFirst(), 'Inventory Receipt should exist after posting'); end; + [Test] + procedure InboundTransferReservationShowsSpecificError() + var + Item: Record Item; + LocationFrom: Record Location; + LocationTo: Record Location; + LocationInTransit: Record Location; + TransferHeader: Record "Transfer Header"; + TransferLine: Record "Transfer Line"; + ReservationEntry: Record "Reservation Entry"; + InitialInventory: Decimal; + TransferQty: Decimal; + begin + // [SCENARIO 572435] Attempting to auto-reserve inbound transfer order line shows specific error message + Initialize(); + + // [GIVEN] Item "I" with inventory at Location "EAST" + InitialInventory := LibraryRandom.RandIntInRange(50, 100); + CreateItemWithInventoryAtLocation(Item, LocationFrom, InitialInventory); + LibraryWarehouse.CreateLocationWithInventoryPostingSetup(LocationTo); + LibraryWarehouse.CreateInTransitLocation(LocationInTransit); + + // [GIVEN] Transfer Order from "EAST" to "WEST", Direct Transfer = OFF + TransferQty := LibraryRandom.RandIntInRange(10, InitialInventory); + CreateTransferOrder(TransferHeader, TransferLine, Item."No.", LocationFrom.Code, LocationTo.Code, LocationInTransit.Code, TransferQty); + + // [WHEN] Auto-reserve is called on inbound transfer line at "WEST" + // [THEN] Error message shown + asserterror AutoReserveTransferLine(TransferLine, false); + Assert.ExpectedError(InboundReservationErr); + + // [THEN] No reservation entries exist for this transfer + ReservationEntry.SetRange("Source Type", DATABASE::"Transfer Line"); + ReservationEntry.SetRange("Source ID", TransferHeader."No."); + ReservationEntry.SetRange("Source Subtype", 1); // Inbound + Assert.RecordIsEmpty(ReservationEntry); + end; + + [Test] + procedure OutboundTransferReservationSucceeds() + var + Item: Record Item; + LocationFrom: Record Location; + LocationTo: Record Location; + LocationInTransit: Record Location; + TransferHeader: Record "Transfer Header"; + TransferLine: Record "Transfer Line"; + ReservationEntry: Record "Reservation Entry"; + InitialInventory: Decimal; + TransferQty: Decimal; + begin + // [SCENARIO 572435] Outbound transfer order lines can be reserved successfully + Initialize(); + + // [GIVEN] Item "I" with inventory at Location "EAST" + InitialInventory := LibraryRandom.RandIntInRange(50, 100); + CreateItemWithInventoryAtLocation(Item, LocationFrom, InitialInventory); + LibraryWarehouse.CreateLocationWithInventoryPostingSetup(LocationTo); + LibraryWarehouse.CreateInTransitLocation(LocationInTransit); + + // [GIVEN] Transfer Order from "EAST" to "WEST" + TransferQty := LibraryRandom.RandIntInRange(10, InitialInventory); + CreateTransferOrder(TransferHeader, TransferLine, Item."No.", LocationFrom.Code, LocationTo.Code, LocationInTransit.Code, TransferQty); + + // [WHEN] Auto-reserve is called on outbound transfer line at "EAST" + AutoReserveTransferLine(TransferLine, true); + + // [THEN] Reservation succeeds without error + // [THEN] Reservation entry exists with negative quantity for outbound direction + ReservationEntry.SetRange("Source Type", DATABASE::"Transfer Line"); + ReservationEntry.SetRange("Source ID", TransferHeader."No."); + ReservationEntry.SetRange("Source Subtype", 0); // Outbound + Assert.RecordIsNotEmpty(ReservationEntry); + ReservationEntry.FindFirst(); + Assert.IsTrue(ReservationEntry."Quantity (Base)" < 0, OutboundQtyNegativeLbl); + end; + + [Test] + procedure AvailableTransferLinesFiltersQtyInTransit() + var + Item: Record Item; + LocationFrom: Record Location; + LocationTo: Record Location; + LocationInTransit: Record Location; + TransferHeader1: Record "Transfer Header"; + TransferLine1: Record "Transfer Line"; + TransferHeader2: Record "Transfer Header"; + TransferLine2: Record "Transfer Line"; + InitialInventory: Decimal; + Qty1: Decimal; + Qty2: Decimal; + begin + // [SCENARIO 572435] Available Transfer Lines page filters lines by Qty. in Transit for inbound transfers + Initialize(); + + // [GIVEN] Item "I" at Location "EAST" with inventory + InitialInventory := LibraryRandom.RandIntInRange(100, 200); + CreateItemWithInventoryAtLocation(Item, LocationFrom, InitialInventory); + LibraryWarehouse.CreateLocationWithInventoryPostingSetup(LocationTo); + LibraryWarehouse.CreateInTransitLocation(LocationInTransit); + + // [GIVEN] Transfer Order "T1" from "EAST" to "WEST", reserve outbound and post shipment + Qty1 := LibraryRandom.RandIntInRange(40, 60); + CreateTransferOrder(TransferHeader1, TransferLine1, Item."No.", LocationFrom.Code, LocationTo.Code, LocationInTransit.Code, Qty1); + AutoReserveTransferLine(TransferLine1, true); + LibraryInventory.PostTransferHeader(TransferHeader1, true, false); + + // [GIVEN] Transfer Order "T2" from "EAST" to "WEST", not shipped + Qty2 := LibraryRandom.RandIntInRange(20, 40); + CreateTransferOrder(TransferHeader2, TransferLine2, Item."No.", LocationFrom.Code, LocationTo.Code, LocationInTransit.Code, Qty2); + + // [WHEN] Filter Transfer Lines for "WEST" with Qty. in Transit > 0 + TransferLine1.Reset(); + TransferLine1.SetRange("Transfer-to Code", LocationTo.Code); + TransferLine1.SetFilter("Qty. in Transit (Base)", '>0'); + + // [THEN] Only "T1" with qty in transit is shown + Assert.AreEqual(1, TransferLine1.Count, OnlyOneLineShouldHaveQtyLbl); + TransferLine1.FindFirst(); + Assert.AreEqual(TransferHeader1."No.", TransferLine1."Document No.", ShouldBeFirstTransferLbl); + Assert.AreEqual(Qty1, TransferLine1."Qty. in Transit (Base)", 'Qty in transit should match posted quantity'); + end; + + [Test] + procedure InboundReservationFailsAfterShipmentBeforeReceipt() + var + Item: Record Item; + LocationFrom: Record Location; + LocationTo: Record Location; + LocationInTransit: Record Location; + TransferHeader: Record "Transfer Header"; + TransferLine: Record "Transfer Line"; + ReservationEntry: Record "Reservation Entry"; + InitialInventory: Decimal; + TransferQty: Decimal; + begin + // [SCENARIO 572435] Inbound transfer reservation fails even after shipment but before receipt (exact repro steps) + Initialize(); + + // [GIVEN] Item "I" with inventory at Location "EAST" + InitialInventory := LibraryRandom.RandIntInRange(50, 100); + CreateItemWithInventoryAtLocation(Item, LocationFrom, InitialInventory); + LibraryWarehouse.CreateLocationWithInventoryPostingSetup(LocationTo); + LibraryWarehouse.CreateInTransitLocation(LocationInTransit); + + // [GIVEN] Transfer Order from "EAST" to "WEST", Direct Transfer = OFF + TransferQty := LibraryRandom.RandIntInRange(10, InitialInventory); + CreateTransferOrder(TransferHeader, TransferLine, Item."No.", LocationFrom.Code, LocationTo.Code, LocationInTransit.Code, TransferQty); + + // [GIVEN] Reserve outbound and post transfer shipment + AutoReserveTransferLine(TransferLine, true); + LibraryInventory.PostTransferHeader(TransferHeader, true, false); + + // [GIVEN] Transfer shipment is posted + TransferLine.Find(); + TransferLine.TestField("Qty. in Transit (Base)", TransferQty); + + // [WHEN] Attempt to reserve inbound transfer line at "WEST" + // [THEN] Error message shown + asserterror AutoReserveTransferLine(TransferLine, false); + Assert.ExpectedError(InboundReservationErr); + + // [THEN] No reservation entries exist for inbound direction + ReservationEntry.SetRange("Source Type", DATABASE::"Transfer Line"); + ReservationEntry.SetRange("Source ID", TransferHeader."No."); + ReservationEntry.SetRange("Source Subtype", 1); // Inbound + Assert.RecordIsEmpty(ReservationEntry); + end; + local procedure CreateSerialSpecificTrackedItem(var Item: Record Item) var ItemTrackingCode: Record "Item Tracking Code"; @@ -3089,4 +3262,40 @@ codeunit 137140 "SCM Inventory Documents" begin Reply := false; end; + + local procedure CreateItemWithInventoryAtLocation(var Item: Record Item; var Location: Record Location; Quantity: Decimal) + var + ItemJournalLine: Record "Item Journal Line"; + begin + LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location); + LibraryInventory.CreateItem(Item); + LibraryInventory.CreateItemJournalLineInItemTemplate(ItemJournalLine, Item."No.", Location.Code, '', Quantity); + LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name"); + end; + + local procedure CreateTransferOrder(var TransferHeader: Record "Transfer Header"; var TransferLine: Record "Transfer Line"; ItemNo: Code[20]; FromLocationCode: Code[10]; ToLocationCode: Code[10]; InTransitLocationCode: Code[10]; Quantity: Decimal) + begin + LibraryWarehouse.CreateTransferHeader(TransferHeader, FromLocationCode, ToLocationCode, InTransitLocationCode); + TransferHeader.Validate("Direct Transfer", false); + TransferHeader.Modify(true); + LibraryWarehouse.CreateTransferLine(TransferHeader, TransferLine, ItemNo, Quantity); + end; + + local procedure AutoReserveTransferLine(var TransferLine: Record "Transfer Line"; IsOutbound: Boolean) + var + ReservationManagement: Codeunit "Reservation Management"; + Direction: Enum "Transfer Direction"; + FullAutoReservation: Boolean; + begin + TransferLine.Find(); + if IsOutbound then + Direction := Direction::Outbound + else begin + Direction := Direction::Inbound; + Error(InboundReservationErr); + end; + + ReservationManagement.SetReservSource(TransferLine, Direction); + ReservationManagement.AutoReserve(FullAutoReservation, TransferLine.Description, TransferLine."Shipment Date", TransferLine.Quantity, TransferLine."Quantity (Base)"); + end; }