Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions src/Layers/W1/BaseApp/Inventory/Tracking/Reservation.Page.al
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.';
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;

Expand Down
209 changes: 209 additions & 0 deletions src/Layers/W1/Tests/SCM/SCMInventoryDocuments.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -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')]
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
Loading