Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -2165,6 +2165,7 @@ codeunit 7312 "Create Pick"
CalcPickableQtyFromWhseEntry.SetRange(Location_Code, LocationCode);
CalcPickableQtyFromWhseEntry.SetRange(Item_No_, ItemNo);
CalcPickableQtyFromWhseEntry.SetRange(Variant_Code, VariantCode);
CalcPickableQtyFromWhseEntry.SetFilter(Bin_Type_Code, GetBinTypeFilter(3));
CalcPickableQtyFromWhseEntry.Open();
if CalcPickableQtyFromWhseEntry.Read() then
QtyInBinNotBlockedNotDedicatedWithoutItemTrackingFilter := CalcPickableQtyFromWhseEntry.TotalPickableQtyBase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -923,11 +923,47 @@ codeunit 7314 "Warehouse Availability Mgt."
if not (Location.Get(LocationCode) and Location."Bin Mandatory" and (Location."Shipment Bin Code" <> '')) then
exit;

QtyOnShipmentBin := CalcQtyOnBin(LocationCode, Location."Shipment Bin Code", ItemNo, VariantCode, ItemTrackingSetup);
QtyOnShipmentBin := CalcQtyOnShipmentBins(LocationCode, ItemNo, VariantCode, ItemTrackingSetup);
if QtyOnShipmentBin = 0 then
QtyPicked := 0
end;

local procedure CalcQtyOnShipmentBins(LocationCode: Code[10]; ItemNo: Code[20]; VariantCode: Code[10]; WhseItemTrackingSetup: Record "Item Tracking Setup"): Decimal
var
WarehouseEntry: Record "Warehouse Entry";
BinType: Record "Bin Type";
Location: Record Location;
Bin: Record Bin;
CreatePick: Codeunit "Create Pick";
ShipBinTypeFilter: Text;
QtyOnShipmentBins: Decimal;
DefaultBinAlreadyCounted: Boolean;
begin
ShipBinTypeFilter := CreatePick.GetBinTypeFilter(1);

if ShipBinTypeFilter <> '' then begin
WarehouseEntry.SetLoadFields("Qty. (Base)");
WarehouseEntry.SetRange("Item No.", ItemNo);
WarehouseEntry.SetRange("Location Code", LocationCode);
WarehouseEntry.SetRange("Variant Code", VariantCode);
WarehouseEntry.SetFilter("Bin Type Code", ShipBinTypeFilter);
WarehouseEntry.SetTrackingFilterFromItemTrackingSetupIfNotBlank(WhseItemTrackingSetup);
WarehouseEntry.CalcSums("Qty. (Base)");
QtyOnShipmentBins := WarehouseEntry."Qty. (Base)";
end;

if Location.Get(LocationCode) and (Location."Shipment Bin Code" <> '') then
if Bin.Get(LocationCode, Location."Shipment Bin Code") then begin
if (ShipBinTypeFilter <> '') and (Bin."Bin Type Code" <> '') then
if BinType.Get(Bin."Bin Type Code") then
DefaultBinAlreadyCounted := BinType.Ship;
if not DefaultBinAlreadyCounted then
QtyOnShipmentBins += CalcQtyOnBin(LocationCode, Location."Shipment Bin Code", ItemNo, VariantCode, WhseItemTrackingSetup);
end;

exit(QtyOnShipmentBins);
end;

[IntegrationEvent(false, false)]
local procedure OnAfterCalcQtyPicked(var Item: Record Item; var QtyPicked: Decimal; Location: Record Location)
begin
Expand Down
255 changes: 255 additions & 0 deletions src/Layers/W1/Tests/SCM-Warehouse/SCMWarehousePick.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -2462,6 +2462,164 @@ codeunit 137055 "SCM Warehouse Pick"
end;
end;

[Test]
[HandlerFunctions('ReservationPageHandler')]
[Scope('OnPrem')]
procedure CreatePickForRemainingQtyWhenPreviousPickPlacedInNonDefaultShipmentBin()
var
Location: Record Location;
WarehouseEmployee: Record "Warehouse Employee";
Item: Record Item;
ShipZone: Record Zone;
PickZone: Record Zone;
DefaultShipmentBin: Record Bin;
NonDefaultShipmentBin: Record Bin;
PickBin: Record Bin;
SalesHeaderFirst: Record "Sales Header";
SalesHeaderSecond: Record "Sales Header";
WarehouseShipmentHeader: Record "Warehouse Shipment Header";
WarehouseActivityHeader: Record "Warehouse Activity Header";
WarehouseActivityLine: Record "Warehouse Activity Line";
TotalQty: Decimal;
FirstShipmentQty: Decimal;
RemainingQty: Decimal;
begin
// [SCENARIO 642378] Create Pick for the remaining quantity succeeds when a previous registered pick placed the
// picked-but-not-shipped quantity into a Ship-type bin other than the location's default Shipment Bin.
// [SCENARIO 642378] Previously the picked quantity was only looked for in the location's default Shipment Bin, so
// when the shipment was directed to a different ship bin the picked quantity was treated as zero. That wrongly
// removed the reservation cover on the picks/shipments, making the remaining inventory look unavailable and
// raising "Nothing to handle. The quantity to be picked is in bin ..., which is not set up for picking.".
Initialize();

// [GIVEN] Directed put-away and pick location with two Ship-type bins in the SHIP zone.
LibraryWarehouse.CreateFullWMSLocation(Location, 2);
LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
LibraryWarehouse.FindZone(ShipZone, Location.Code, LibraryWarehouse.SelectBinType(false, true, false, false), false);
LibraryWarehouse.FindBin(DefaultShipmentBin, Location.Code, ShipZone.Code, 1); // Location's default Shipment Bin.
LibraryWarehouse.FindBin(NonDefaultShipmentBin, Location.Code, ShipZone.Code, 2); // Alternative Ship-type bin.
LibraryWarehouse.FindZone(PickZone, Location.Code, LibraryWarehouse.SelectBinType(false, false, true, true), false);
LibraryWarehouse.FindBin(PickBin, Location.Code, PickZone.Code, 1); // Pick bin the remaining quantity must be sourced from.

// [GIVEN] A random quantity of an item is in a pick bin, split across two shipments.
TotalQty := LibraryRandom.RandIntInRange(100, 200);
FirstShipmentQty := LibraryRandom.RandIntInRange(1, TotalQty - 1);
RemainingQty := TotalQty - FirstShipmentQty;
LibraryInventory.CreateItem(Item);
UpdateInventoryInPickBin(Item, Location.Code, TotalQty);

// [GIVEN] The first sales order, reserved against inventory, is picked into the non-default Ship-type bin, but not shipped.
CreateReleaseAndReserveSalesOrder(SalesHeaderFirst, Location.Code, Item."No.", FirstShipmentQty, true);
LibraryWarehouse.CreateWhseShipmentFromSO(SalesHeaderFirst);
FindWarehouseShipmentHeader(WarehouseShipmentHeader, SalesHeaderFirst."No.");
UpdateShipmentBinOnWhseShipment(WarehouseShipmentHeader, NonDefaultShipmentBin.Code);
LibraryWarehouse.CreatePick(WarehouseShipmentHeader);
FindWarehouseActivityHeader(WarehouseActivityHeader, WarehouseActivityHeader.Type::Pick, Location.Code, SalesHeaderFirst."No.");
LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);

// [WHEN] Create Pick for a second sales order for the remaining quantity.
CreateReleaseAndReserveSalesOrder(SalesHeaderSecond, Location.Code, Item."No.", RemainingQty, false);
LibraryWarehouse.CreateWhseShipmentFromSO(SalesHeaderSecond);
FindWarehouseShipmentHeader(WarehouseShipmentHeader, SalesHeaderSecond."No.");
LibraryWarehouse.CreatePick(WarehouseShipmentHeader);

// [THEN] The pick for the remaining quantity is created (before the fix it failed with "Nothing to handle").
VerifyPick(SalesHeaderSecond."No.", Item."No.", Location.Code, RemainingQty);

// [THEN] The Take line sources the remaining quantity from the pick bin, not from a ship bin.
FindWarehouseActivityLine(
WarehouseActivityLine, WarehouseActivityLine."Activity Type"::Pick, Location.Code, SalesHeaderSecond."No.",
WarehouseActivityLine."Action Type"::Take);
WarehouseActivityLine.TestField("Bin Code", PickBin.Code);
end;

[Test]
[HandlerFunctions('ReservationPageHandler,WhseItemTrackingLinesAssignLotAndExpirationPageHandler')]
[Scope('OnPrem')]
procedure CreatePickForRemainingQtyWithFEFOWhenPreviousPickPlacedInNonDefaultShipmentBin()
var
Location: Record Location;
WarehouseEmployee: Record "Warehouse Employee";
Item: Record Item;
ItemTrackingCode: Record "Item Tracking Code";
ShipZone: Record Zone;
DefaultShipmentBin: Record Bin;
NonDefaultShipmentBin: Record Bin;
SalesHeaderFirst: Record "Sales Header";
SalesHeaderSecond: Record "Sales Header";
WarehouseShipmentHeader: Record "Warehouse Shipment Header";
WarehouseActivityHeader: Record "Warehouse Activity Header";
WarehouseActivityLine: Record "Warehouse Activity Line";
LotNo: Code[50];
ExpirationDate: Date;
TotalQty: Decimal;
FirstShipmentQty: Decimal;
RemainingQty: Decimal;
begin
// [FEATURE] [FEFO] [Item Tracking]
// [SCENARIO 642378] Create Pick for the remaining quantity of a lot-tracked, FEFO-picked item succeeds when a
// previous registered pick placed the picked-but-not-shipped quantity into a Ship-type bin other than the
// location's default Shipment Bin.
// [SCENARIO 642378] This exercises the FEFO branch (ReqFEFOPick): the pickable quantity in the warehouse must be
// measured only across pick bins. Otherwise the quantity already picked into the non-default ship bin is treated
// as still pickable, the reservation cover is wrongly removed and the second pick fails with
// "Nothing to handle. The quantity to be picked is in bin ..., which is not set up for picking.".
Initialize();

// [GIVEN] Directed put-away and pick location with Pick According to FEFO and two Ship-type bins in the SHIP zone.
LibraryWarehouse.CreateFullWMSLocation(Location, 2);
Location.Validate("Pick According to FEFO", true);
Location.Modify(true);
LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
LibraryWarehouse.FindZone(ShipZone, Location.Code, LibraryWarehouse.SelectBinType(false, true, false, false), false);
LibraryWarehouse.FindBin(DefaultShipmentBin, Location.Code, ShipZone.Code, 1); // Location's default Shipment Bin.
LibraryWarehouse.FindBin(NonDefaultShipmentBin, Location.Code, ShipZone.Code, 2); // Alternative Ship-type bin.

// [GIVEN] A single lot (with expiration date) of a lot-tracked item is in a pick bin, split across two shipments.
TotalQty := LibraryRandom.RandIntInRange(100, 200);
FirstShipmentQty := LibraryRandom.RandIntInRange(1, TotalQty - 1);
RemainingQty := TotalQty - FirstShipmentQty;
CreateItemWithLotTrackingAndExpirationDate(Item, ItemTrackingCode);
LotNo := LibraryUtility.GenerateGUID();
// The expiration date is entered through the Whse. Item Tracking Lines page, which enforces the date interval
// allowed by the (evaluation) license. Keep it inside WorkDate's month so it stays within that interval in every
// localization (some, e.g. IN, use a WorkDate where WorkDate + 30D would fall outside the allowed interval).
ExpirationDate := CalcDate('<CM>', WorkDate());
UpdateInventoryInPickBinWithLotAndExpiration(Item, Location.Code, TotalQty, LotNo, ExpirationDate);

// [GIVEN] The first sales order, reserved against inventory, is FEFO-picked into the non-default Ship-type bin, but not shipped.
CreateReleaseAndReserveSalesOrder(SalesHeaderFirst, Location.Code, Item."No.", FirstShipmentQty, true);
LibraryWarehouse.CreateWhseShipmentFromSO(SalesHeaderFirst);
FindWarehouseShipmentHeader(WarehouseShipmentHeader, SalesHeaderFirst."No.");
UpdateShipmentBinOnWhseShipment(WarehouseShipmentHeader, NonDefaultShipmentBin.Code);
LibraryWarehouse.CreatePick(WarehouseShipmentHeader);

// [GIVEN] The first pick places the FEFO-assigned lot into the non-default Ship-type bin (not the default Shipment Bin).
FindWarehouseActivityLine(
WarehouseActivityLine, WarehouseActivityLine."Activity Type"::Pick, Location.Code, SalesHeaderFirst."No.",
WarehouseActivityLine."Action Type"::Place);
WarehouseActivityLine.TestField("Bin Code", NonDefaultShipmentBin.Code);
WarehouseActivityLine.TestField("Lot No.", LotNo);

FindWarehouseActivityHeader(WarehouseActivityHeader, WarehouseActivityHeader.Type::Pick, Location.Code, SalesHeaderFirst."No.");
LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);

// [GIVEN] After registration the picked quantity sits in the non-default Ship-type bin, not the default Shipment Bin.
VerifyQuantityOnBin(Location.Code, NonDefaultShipmentBin.Code, Item."No.", LotNo, FirstShipmentQty);
VerifyQuantityOnBin(Location.Code, DefaultShipmentBin.Code, Item."No.", LotNo, 0);

// [WHEN] Create Pick for a second sales order for the remaining quantity.
CreateReleaseAndReserveSalesOrder(SalesHeaderSecond, Location.Code, Item."No.", RemainingQty, false);
LibraryWarehouse.CreateWhseShipmentFromSO(SalesHeaderSecond);
FindWarehouseShipmentHeader(WarehouseShipmentHeader, SalesHeaderSecond."No.");
LibraryWarehouse.CreatePick(WarehouseShipmentHeader);

// [THEN] The pick for the remaining quantity is created (before the fix it failed with "Nothing to handle").
VerifyPick(SalesHeaderSecond."No.", Item."No.", Location.Code, RemainingQty);

LibraryVariableStorage.AssertEmpty();
end;

local procedure Initialize()
var
WarehouseActivityLine: Record "Warehouse Activity Line";
Expand Down Expand Up @@ -3040,6 +3198,79 @@ codeunit 137055 "SCM Warehouse Pick"
LibraryInventory.PostItemJournalLine(ItemJournalBatch."Journal Template Name", ItemJournalBatch.Name);
end;

local procedure UpdateInventoryInPickBin(Item: Record Item; LocationCode: Code[10]; Quantity: Decimal)
var
Zone: Record Zone;
Bin: Record Bin;
WarehouseJournalLine: Record "Warehouse Journal Line";
begin
EnsureGeneralPostingSetupForItem(Item);
LibraryWarehouse.FindZone(Zone, LocationCode, LibraryWarehouse.SelectBinType(false, false, true, true), false);
LibraryWarehouse.FindBin(Bin, LocationCode, Zone.Code, 1);
LibraryWarehouse.WarehouseJournalSetup(LocationCode, WarehouseJournalTemplate, WarehouseJournalBatch);
LibraryInventory.ClearItemJournal(ItemJournalTemplate, ItemJournalBatch);
LibraryWarehouse.CreateWhseJournalLine(
WarehouseJournalLine, WarehouseJournalBatch."Journal Template Name", WarehouseJournalBatch.Name,
LocationCode, Zone.Code, Bin.Code,
WarehouseJournalLine."Entry Type"::"Positive Adjmt.", Item."No.", Quantity);
CalculateAndPostWhseAdjustment(Item, LocationCode);
end;

local procedure UpdateInventoryInPickBinWithLotAndExpiration(Item: Record Item; LocationCode: Code[10]; Quantity: Decimal; LotNo: Code[50]; ExpirationDate: Date)
var
Zone: Record Zone;
Bin: Record Bin;
WarehouseJournalLine: Record "Warehouse Journal Line";
begin
EnsureGeneralPostingSetupForItem(Item);
LibraryWarehouse.FindZone(Zone, LocationCode, LibraryWarehouse.SelectBinType(false, false, true, true), false);
LibraryWarehouse.FindBin(Bin, LocationCode, Zone.Code, 1);
LibraryWarehouse.WarehouseJournalSetup(LocationCode, WarehouseJournalTemplate, WarehouseJournalBatch);
LibraryInventory.ClearItemJournal(ItemJournalTemplate, ItemJournalBatch);
LibraryWarehouse.CreateWhseJournalLine(
WarehouseJournalLine, WarehouseJournalBatch."Journal Template Name", WarehouseJournalBatch.Name,
LocationCode, Zone.Code, Bin.Code,
WarehouseJournalLine."Entry Type"::"Positive Adjmt.", Item."No.", Quantity);

// Assign the lot and expiration date to the warehouse journal line so the adjustment creates lot-tracked inventory.
LibraryVariableStorage.Enqueue(LotNo);
LibraryVariableStorage.Enqueue(ExpirationDate);
LibraryVariableStorage.Enqueue(Quantity);
WarehouseJournalLine.OpenItemTrackingLines();

CalculateAndPostWhseAdjustment(Item, LocationCode);
end;

local procedure EnsureGeneralPostingSetupForItem(Item: Record Item)
var
GeneralPostingSetup: Record "General Posting Setup";
LibraryERM: Codeunit "Library - ERM";
begin
// The whse adjustment posts with a blank Gen. Bus. Posting Group, so the ('', Item Gen. Prod. Posting Group)
// combination must exist with inventory accounts even though the item picked a group from a "full" setup that
// used a non-blank Gen. Bus. Posting Group.
if not GeneralPostingSetup.Get('', Item."Gen. Prod. Posting Group") then begin
GeneralPostingSetup.Init();
GeneralPostingSetup.Validate("Gen. Bus. Posting Group", '');
GeneralPostingSetup.Validate("Gen. Prod. Posting Group", Item."Gen. Prod. Posting Group");
GeneralPostingSetup.Insert(true);
end;
LibraryERM.SetGeneralPostingSetupInvtAccounts(GeneralPostingSetup);
GeneralPostingSetup.Modify(true);
end;

local procedure UpdateShipmentBinOnWhseShipment(WarehouseShipmentHeader: Record "Warehouse Shipment Header"; BinCode: Code[20])
var
WarehouseShipmentLine: Record "Warehouse Shipment Line";
begin
WarehouseShipmentLine.SetRange("No.", WarehouseShipmentHeader."No.");
WarehouseShipmentLine.FindSet();
repeat
WarehouseShipmentLine.Validate("Bin Code", BinCode);
WarehouseShipmentLine.Modify(true);
until WarehouseShipmentLine.Next() = 0;
end;

local procedure UpdateBinOnActivityLine(var WarehouseActivityLine: Record "Warehouse Activity Line"; SourceNo: Code[20]; BinCode: Code[20]; ActionType: Enum "Warehouse Action Type")
begin
FindWhseActivityLine(WarehouseActivityLine, WarehouseActivityLine."Activity Type"::Pick, ActionType, LocationWhite.Code, SourceNo);
Expand Down Expand Up @@ -3217,6 +3448,20 @@ codeunit 137055 "SCM Warehouse Pick"
WarehouseActivityLine.TestField("Location Code", LocationCode);
end;

local procedure VerifyQuantityOnBin(LocationCode: Code[10]; BinCode: Code[20]; ItemNo: Code[20]; LotNo: Code[50]; ExpectedQty: Decimal)
var
WarehouseEntry: Record "Warehouse Entry";
begin
WarehouseEntry.SetRange("Location Code", LocationCode);
WarehouseEntry.SetRange("Bin Code", BinCode);
WarehouseEntry.SetRange("Item No.", ItemNo);
WarehouseEntry.SetRange("Lot No.", LotNo);
WarehouseEntry.CalcSums("Qty. (Base)");
Assert.AreEqual(
ExpectedQty, WarehouseEntry."Qty. (Base)",
StrSubstNo('Unexpected quantity of lot %1 in bin %2.', LotNo, BinCode));
end;

local procedure VerifyRemainingQunatityOnPick(SourceNo: Code[20]; ItemNo: Code[20]; LocationCode: Code[10]; Quantity: Decimal)
var
WarehouseActivityLine: Record "Warehouse Activity Line";
Expand Down Expand Up @@ -3555,6 +3800,16 @@ codeunit 137055 "SCM Warehouse Pick"
ItemTrackingLines.OK().Invoke();
end;

[ModalPageHandler]
[Scope('OnPrem')]
procedure WhseItemTrackingLinesAssignLotAndExpirationPageHandler(var WhseItemTrackingLines: TestPage "Whse. Item Tracking Lines")
begin
WhseItemTrackingLines."Lot No.".SetValue(LibraryVariableStorage.DequeueText());
WhseItemTrackingLines."Expiration Date".SetValue(LibraryVariableStorage.DequeueDate());
WhseItemTrackingLines.Quantity.SetValue(LibraryVariableStorage.DequeueDecimal());
WhseItemTrackingLines.OK().Invoke();
end;

[ConfirmHandler]
[Scope('OnPrem')]
procedure AvailabilityWarningConfirmHandler(Question: Text[1024]; var Reply: Boolean)
Expand Down
Loading