diff --git a/src/Apps/W1/Quality Management/app/src/Dispositions/PutAway/QltyDispInternalPutAway.Codeunit.al b/src/Apps/W1/Quality Management/app/src/Dispositions/PutAway/QltyDispInternalPutAway.Codeunit.al
index c9b2f02bfc..486929ac99 100644
--- a/src/Apps/W1/Quality Management/app/src/Dispositions/PutAway/QltyDispInternalPutAway.Codeunit.al
+++ b/src/Apps/W1/Quality Management/app/src/Dispositions/PutAway/QltyDispInternalPutAway.Codeunit.al
@@ -72,6 +72,11 @@ codeunit 20447 "Qlty. Disp. Internal Put-away" implements "Qlty. Disposition"
exit;
end;
+ repeat
+ QltyInventoryAvailability.ErrorIfFromBinIsReceiveBin(QltyInspectionHeader, TempQuantityToActQltyDispositionBuffer.GetFromLocationCode(), TempQuantityToActQltyDispositionBuffer.GetFromBinCode());
+ until TempQuantityToActQltyDispositionBuffer.Next() = 0;
+
+ TempQuantityToActQltyDispositionBuffer.FindSet();
repeat
if (WhseInternalPutAwayHeader."Location Code" <> TempQuantityToActQltyDispositionBuffer."Location Filter") or (WhseInternalPutAwayHeader."From Bin Code" <> TempQuantityToActQltyDispositionBuffer."Bin Filter") then begin
CreateInternalPutawayHeader(WhseInternalPutAwayHeader, TempQuantityToActQltyDispositionBuffer.GetFromLocationCode(), TempQuantityToActQltyDispositionBuffer.GetFromBinCode());
diff --git a/src/Apps/W1/Quality Management/app/src/Integration/Inventory/QltyInventoryAvailability.Codeunit.al b/src/Apps/W1/Quality Management/app/src/Integration/Inventory/QltyInventoryAvailability.Codeunit.al
index e8e13f8888..28d0412dde 100644
--- a/src/Apps/W1/Quality Management/app/src/Integration/Inventory/QltyInventoryAvailability.Codeunit.al
+++ b/src/Apps/W1/Quality Management/app/src/Integration/Inventory/QltyInventoryAvailability.Codeunit.al
@@ -24,6 +24,7 @@ codeunit 20445 "Qlty. Inventory Availability"
NoSamplesToMoveErr: Label 'No samples meet the condition specified.', Locked = true;
SerialQuantityGreaterThanOneErr: Label '%1 (%2) cannot be greater than 1 when New Serial No. is requested.', Comment = '%1=quantity behavior, %2=quantity';
ZeroQuantityErr: Label 'Unable to use the disposition %1 on the inspection %2 for the item %3 because the quantity is zero.', Comment = '%1=the inspection, %2=the inspection, %3=the item';
+ InsufficientInventoryToAllocateErr: Label 'Unable to move the entire requested quantity for inspection %1 for item %2. There is not enough inventory available in eligible bins (short by %3). Put the remaining inventory away or reduce the quantity, and then try again.', Comment = '%1=the inspection, %2=the item, %3=the missing quantity';
SupplyFromLocationCodeNameLbl: Label 'Supply-from Location Code', Locked = true;
FromLocationCodeNameLbl: Label 'From Location Code', Locked = true;
LocationCodeNameLbl: Label 'Location Code', Locked = true;
@@ -162,19 +163,20 @@ codeunit 20445 "Qlty. Inventory Availability"
if RecordRefToSearch.FindFirst() then begin
LocationCode := QltyInspectionHeader."Location Code";
GetFromLocationAndBinBasedOnNamingConventions(RecordRefToSearch, LocationCode, BinCode, QuantityBaseValue);
- if LocationCode <> '' then begin
- TempToMoveBinContent.Reset();
- TempToMoveBinContent.SetRange("Location Code", LocationCode);
- if BinCode <> '' then
- TempToMoveBinContent.SetRange("Bin Code", BinCode);
- if not TempToMoveBinContent.FindFirst() then begin
- TempToMoveBinContent.Init();
- TempToMoveBinContent."Location Code" := LocationCode;
- TempToMoveBinContent."Bin Code" := BinCode;
- TempToMoveBinContent."Min. Qty." := QuantityBaseValue;
- TempToMoveBinContent.Insert(false);
+ if LocationCode <> '' then
+ if not ResolveBinsFromBinContent(QltyInspectionHeader, LocationCode, TempToMoveBinContent) then begin
+ TempToMoveBinContent.Reset();
+ TempToMoveBinContent.SetRange("Location Code", LocationCode);
+ if BinCode <> '' then
+ TempToMoveBinContent.SetRange("Bin Code", BinCode);
+ if not TempToMoveBinContent.FindFirst() then begin
+ TempToMoveBinContent.Init();
+ TempToMoveBinContent."Location Code" := LocationCode;
+ TempToMoveBinContent."Bin Code" := BinCode;
+ TempToMoveBinContent."Min. Qty." := QuantityBaseValue;
+ TempToMoveBinContent.Insert(false);
+ end;
end;
- end;
end;
end;
@@ -187,6 +189,48 @@ codeunit 20445 "Qlty. Inventory Availability"
end;
end;
+ local procedure ResolveBinsFromBinContent(QltyInspectionHeader: Record "Qlty. Inspection Header"; LocationCode: Code[10]; var TempToMoveBinContent: Record "Bin Content" temporary): Boolean
+ var
+ Location: Record Location;
+ BinContent: Record "Bin Content";
+ Added: Boolean;
+ begin
+ if (LocationCode = '') or (QltyInspectionHeader."Source Item No." = '') then
+ exit(false);
+ if not Location.Get(LocationCode) then
+ exit(false);
+ if not Location."Bin Mandatory" then
+ exit(false);
+
+ BinContent.SetRange("Location Code", LocationCode);
+ BinContent.SetRange("Item No.", QltyInspectionHeader."Source Item No.");
+ BinContent.SetRange("Variant Code", QltyInspectionHeader."Source Variant Code");
+ if QltyInspectionHeader."Source Lot No." <> '' then
+ BinContent.SetRange("Lot No. Filter", QltyInspectionHeader."Source Lot No.");
+ if QltyInspectionHeader."Source Serial No." <> '' then
+ BinContent.SetRange("Serial No. Filter", QltyInspectionHeader."Source Serial No.");
+ if QltyInspectionHeader."Source Package No." <> '' then
+ BinContent.SetRange("Package No. Filter", QltyInspectionHeader."Source Package No.");
+ if Location."Adjustment Bin Code" <> '' then
+ BinContent.SetFilter("Bin Code", '<>%1', Location."Adjustment Bin Code");
+ BinContent.SetFilter("Quantity (Base)", '>%1', 0);
+ BinContent.SetAutoCalcFields("Quantity (Base)");
+ if BinContent.FindSet() then
+ repeat
+ if not IsReceiveBin(BinContent."Location Code", BinContent."Bin Code") then begin
+ Added := true;
+ if not TempToMoveBinContent.Get(BinContent."Location Code", BinContent."Bin Code", BinContent."Item No.", BinContent."Variant Code", BinContent."Unit of Measure Code") then begin
+ TempToMoveBinContent.Init();
+ TempToMoveBinContent := BinContent;
+ TempToMoveBinContent."Min. Qty." := BinContent."Quantity (Base)";
+ TempToMoveBinContent.Insert(false);
+ end;
+ end;
+ until BinContent.Next() = 0;
+
+ exit(Added);
+ end;
+
local procedure GetFromLocationAndBinBasedOnNamingConventions(var RecordRef: RecordRef; var LocationCode: Code[10]; var BinCode: Code[20]; var QuantityBase: Decimal)
var
Location: Record Location;
@@ -386,7 +430,10 @@ codeunit 20445 "Qlty. Inventory Availability"
MultipleBins: Boolean;
SkipBinContent: Boolean;
IsHandled: Boolean;
+ AllocateAcrossBins: Boolean;
BufferEntryCounter: Integer;
+ RemainingQuantityToAllocate: Decimal;
+ QuantityToHandle: Decimal;
begin
TempQuantityQltyDispositionBuffer.Reset();
TempQuantityQltyDispositionBuffer.DeleteAll();
@@ -417,6 +464,18 @@ codeunit 20445 "Qlty. Inventory Availability"
TempExistingInventoryBinContent.FindSet();
end;
+ AllocateAcrossBins := MultipleBins and (TempInstructionQltyDispositionBuffer."Quantity Behavior" in [
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Specific Quantity",
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Sample Quantity",
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Failed Quantity",
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Passed Quantity"]);
+ if AllocateAcrossBins then
+ RemainingQuantityToAllocate := GetQuantityToHandleFromInspection(
+ QltyInspectionHeader,
+ TempInstructionQltyDispositionBuffer."Quantity Behavior",
+ TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)",
+ TempExistingInventoryBinContent);
+
repeat
SkipBinContent := false;
if TempExistingInventoryBinContent."Location Code" <> '' then
@@ -431,17 +490,30 @@ codeunit 20445 "Qlty. Inventory Availability"
Location."Bin Mandatory");
end;
+ if not SkipBinContent then
+ if AllocateAcrossBins then begin
+ if TempExistingInventoryBinContent."Min. Qty." <= 0 then
+ QuantityToHandle := 0
+ else
+ if TempExistingInventoryBinContent."Min. Qty." < RemainingQuantityToAllocate then
+ QuantityToHandle := TempExistingInventoryBinContent."Min. Qty."
+ else
+ QuantityToHandle := RemainingQuantityToAllocate;
+ SkipBinContent := QuantityToHandle <= 0;
+ end else
+ QuantityToHandle := GetQuantityToHandleFromInspection(
+ QltyInspectionHeader,
+ TempInstructionQltyDispositionBuffer."Quantity Behavior",
+ TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)",
+ TempExistingInventoryBinContent);
+
if not SkipBinContent then begin
BufferEntryCounter += 1;
TempQuantityQltyDispositionBuffer := TempInstructionQltyDispositionBuffer;
TempQuantityQltyDispositionBuffer."Buffer Entry No." := BufferEntryCounter;
TempQuantityQltyDispositionBuffer."Location Filter" := TempExistingInventoryBinContent."Location Code";
TempQuantityQltyDispositionBuffer."Bin Filter" := TempExistingInventoryBinContent."Bin Code";
- TempQuantityQltyDispositionBuffer."Qty. To Handle (Base)" := GetQuantityToHandleFromInspection(
- QltyInspectionHeader,
- TempInstructionQltyDispositionBuffer."Quantity Behavior",
- TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)",
- TempExistingInventoryBinContent);
+ TempQuantityQltyDispositionBuffer."Qty. To Handle (Base)" := QuantityToHandle;
if TempQuantityQltyDispositionBuffer."Qty. To Handle (Base)" = 0 then
Error(ZeroQuantityErr, TempInstructionQltyDispositionBuffer."Disposition Action", QltyInspectionHeader."No.", QltyInspectionHeader."Source Item No.");
@@ -449,9 +521,15 @@ codeunit 20445 "Qlty. Inventory Availability"
if (TempQuantityQltyDispositionBuffer."New Serial No." <> '') and (TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)" > 1) then
Error(SerialQuantityGreaterThanOneErr, TempInstructionQltyDispositionBuffer."Entry Behavior", TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)");
+ if AllocateAcrossBins then
+ RemainingQuantityToAllocate -= TempQuantityQltyDispositionBuffer."Qty. To Handle (Base)";
+
TempQuantityQltyDispositionBuffer.Insert(false);
end;
until TempExistingInventoryBinContent.Next() = 0;
+
+ if AllocateAcrossBins and (RemainingQuantityToAllocate > 0) then
+ Error(InsufficientInventoryToAllocateErr, QltyInspectionHeader."No.", QltyInspectionHeader."Source Item No.", RemainingQuantityToAllocate);
end;
OnAfterPopulateBinContentBuffer(QltyInspectionHeader, TempInstructionQltyDispositionBuffer, TempQuantityQltyDispositionBuffer, TempExistingInventoryBinContent);
@@ -464,18 +542,23 @@ codeunit 20445 "Qlty. Inventory Availability"
/// The location code the inventory is being moved from.
/// The bin code the inventory is being moved from.
internal procedure ErrorIfFromBinIsReceiveBin(QltyInspectionHeader: Record "Qlty. Inspection Header"; FromLocationCode: Code[10]; FromBinCode: Code[20])
+ begin
+ if IsReceiveBin(FromLocationCode, FromBinCode) then
+ Error(CannotMoveFromReceiveBinErr, QltyInspectionHeader.GetFriendlyIdentifier(), FromBinCode, FromLocationCode);
+ end;
+
+ local procedure IsReceiveBin(FromLocationCode: Code[10]; FromBinCode: Code[20]): Boolean
var
FromBin: Record Bin;
BinType: Record "Bin Type";
begin
if (FromLocationCode = '') or (FromBinCode = '') then
- exit;
+ exit(false);
if not FromBin.Get(FromLocationCode, FromBinCode) then
- exit;
+ exit(false);
if not BinType.Get(FromBin."Bin Type Code") then
- exit;
- if BinType.Receive then
- Error(CannotMoveFromReceiveBinErr, QltyInspectionHeader.GetFriendlyIdentifier(), FromBinCode, FromLocationCode);
+ exit(false);
+ exit(BinType.Receive);
end;
[IntegrationEvent(false, false)]
diff --git a/src/Apps/W1/Quality Management/app/src/Utilities/QltyNotificationMgmt.Codeunit.al b/src/Apps/W1/Quality Management/app/src/Utilities/QltyNotificationMgmt.Codeunit.al
index 706110f3e1..fe9d46e200 100644
--- a/src/Apps/W1/Quality Management/app/src/Utilities/QltyNotificationMgmt.Codeunit.al
+++ b/src/Apps/W1/Quality Management/app/src/Utilities/QltyNotificationMgmt.Codeunit.al
@@ -270,7 +270,7 @@ codeunit 20437 "Qlty. Notification Mgmt."
DocumentNotAbleToBeCreatedAnMsg,
DocumentType,
QltyInspectionHeader."No.",
- TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)",
+ GetDisplayQuantityForFailure(QltyInspectionHeader, TempInstructionQltyDispositionBuffer),
GetSourceSummaryText(QltyInspectionHeader),
OptionalAdditionalMessageContext,
Item."Base Unit of Measure")
@@ -279,7 +279,7 @@ codeunit 20437 "Qlty. Notification Mgmt."
DocumentNotAbleToBeCreatedAMsg,
DocumentType,
QltyInspectionHeader."No.",
- TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)",
+ GetDisplayQuantityForFailure(QltyInspectionHeader, TempInstructionQltyDispositionBuffer),
GetSourceSummaryText(QltyInspectionHeader),
OptionalAdditionalMessageContext,
Item."Base Unit of Measure");
@@ -294,6 +294,29 @@ codeunit 20437 "Qlty. Notification Mgmt."
CreateActionNotification(DocumentCreationFailedNotification, CurrentMessage, AvailableOptions);
end;
+ ///
+ /// Gets the quantity to show in the document creation failed message, falling back to the quantity implied by the quantity behavior when the instruction quantity is zero.
+ ///
+ /// The inspection the failed document relates to.
+ /// The attempted instruction.
+ /// The quantity to display.
+ local procedure GetDisplayQuantityForFailure(QltyInspectionHeader: Record "Qlty. Inspection Header"; var TempInstructionQltyDispositionBuffer: Record "Qlty. Disposition Buffer" temporary): Decimal
+ begin
+ if TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)" <> 0 then
+ exit(TempInstructionQltyDispositionBuffer."Qty. To Handle (Base)");
+
+ case TempInstructionQltyDispositionBuffer."Quantity Behavior" of
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Failed Quantity":
+ exit(QltyInspectionHeader."Fail Quantity");
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Passed Quantity":
+ exit(QltyInspectionHeader."Pass Quantity");
+ TempInstructionQltyDispositionBuffer."Quantity Behavior"::"Sample Quantity":
+ exit(QltyInspectionHeader."Sample Size");
+ else
+ exit(QltyInspectionHeader."Source Quantity (Base)");
+ end;
+ end;
+
///
/// Creates a notification that an item tracking change occurred.
///
diff --git a/src/Apps/W1/Quality Management/test/src/QltyTestsDispositions.Codeunit.al b/src/Apps/W1/Quality Management/test/src/QltyTestsDispositions.Codeunit.al
index 866d0afe64..767c0485ab 100644
--- a/src/Apps/W1/Quality Management/test/src/QltyTestsDispositions.Codeunit.al
+++ b/src/Apps/W1/Quality Management/test/src/QltyTestsDispositions.Codeunit.al
@@ -6960,6 +6960,75 @@ codeunit 139960 "Qlty. Tests - Dispositions"
LibraryAssert.AreEqual(WhseInternalPutAwayHeader.Status::Released, WhseInternalPutAwayHeader.Status, 'Should have released internal putaway');
end;
+ [Test]
+ procedure InternalPutaway_FailedQuantity_SourcedFromStorageBinNotReceive()
+ var
+ QltyInspectionTemplateHdr: Record "Qlty. Inspection Template Hdr.";
+ QltyInspectionGenRule: Record "Qlty. Inspection Gen. Rule";
+ Location: Record Location;
+ Item: Record Item;
+ Bin: Record Bin;
+ BinType: Record "Bin Type";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WarehouseEntry: Record "Warehouse Entry";
+ QltyInspectionHeader: Record "Qlty. Inspection Header";
+ WhseInternalPutAwayLine: Record "Whse. Internal Put-away Line";
+ WarehouseSetup: Record "Warehouse Setup";
+ FailedQuantity: Decimal;
+ TotalPutAwayQuantity: Decimal;
+ begin
+ // [SCENARIO] Creating an internal put-away for the failed quantity sources the inventory from the storage bin the goods were put away to, never from the receive bin, and moves exactly the failed quantity
+
+ // [GIVEN] A directed warehouse location with warehouse number series configured
+ QltyInspectionUtility.EnsureSetupExists();
+ QltyInspectionUtility.CreatePrioritizedRule(QltyInspectionTemplateHdr, Database::"Warehouse Entry", QltyInspectionGenRule);
+ LibraryWarehouse.NoSeriesSetup(WarehouseSetup);
+ LibraryWarehouse.CreateFullWMSLocation(Location, 3);
+
+ // [GIVEN] Current user is set as warehouse employee for the location
+ QltyInspectionUtility.SetCurrLocationWhseEmployee(Location.Code);
+
+ // [GIVEN] An untracked item is purchased, received and put away at the directed location
+ LibraryInventory.CreateItem(Item);
+ QltyPurOrderGenerator.CreatePurchaseOrder(100, Location, Item, PurchaseHeader, PurchaseLine);
+ LibraryPurchase.ReleasePurchaseDocument(PurchaseHeader);
+ QltyPurOrderGenerator.ReceivePurchaseOrder(Location, PurchaseHeader, PurchaseLine);
+
+ // [GIVEN] A quality inspection is created for the put away warehouse entry (a storage bin, not the receive bin)
+ WarehouseEntry.SetRange("Entry Type", WarehouseEntry."Entry Type"::Movement);
+ WarehouseEntry.SetRange("Location Code", Location.Code);
+ WarehouseEntry.SetRange("Item No.", Item."No.");
+ WarehouseEntry.SetFilter("Zone Code", '<>%1', 'RECEIVE');
+ WarehouseEntry.FindFirst();
+ QltyInspectionUtility.CreateInspectionWithWarehouseEntry(WarehouseEntry, QltyInspectionHeader);
+
+ // [GIVEN] The inspection has 5 samples with 3 failures and 2 passes
+ FailedQuantity := 3;
+ QltyInspectionHeader."Sample Size" := 5;
+ QltyInspectionHeader."Fail Quantity" := FailedQuantity;
+ QltyInspectionHeader."Pass Quantity" := 2;
+ QltyInspectionHeader.Modify();
+
+ // [WHEN] Perform disposition with internal put-away for the failed quantity
+ LibraryAssert.IsTrue(
+ QltyInspectionUtility.PerformInternalPutAwayDisposition(QltyInspectionHeader, 0, '', '', true, Enum::"Qlty. Quantity Behavior"::"Failed Quantity"),
+ 'Should have created an internal put-away for the failed quantity');
+
+ // [THEN] Every created put-away line is sourced from a non-receive bin
+ WhseInternalPutAwayLine.SetRange("Item No.", Item."No.");
+ LibraryAssert.IsTrue(WhseInternalPutAwayLine.FindSet(), 'Expected internal put-away lines to be created');
+ repeat
+ Bin.Get(WhseInternalPutAwayLine."Location Code", WhseInternalPutAwayLine."From Bin Code");
+ if BinType.Get(Bin."Bin Type Code") then
+ LibraryAssert.IsFalse(BinType.Receive, 'Internal put-away must not be sourced from a receive bin');
+ TotalPutAwayQuantity += WhseInternalPutAwayLine.Quantity;
+ until WhseInternalPutAwayLine.Next() = 0;
+
+ // [THEN] The total put-away quantity equals the failed quantity
+ LibraryAssert.AreEqual(FailedQuantity, TotalPutAwayQuantity, 'Total internal put-away quantity should equal the failed quantity');
+ end;
+
[Test]
procedure WarehousePutaway_PerformDisposition()
var