diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderRtngExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderRtngExt.Codeunit.al
index db143b2bb3..27c51e83f5 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderRtngExt.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderRtngExt.Codeunit.al
@@ -60,6 +60,12 @@ codeunit 99001520 "Subc. Prod. Order Rtng. Ext."
SubcPriceManagement.GetSubcPriceList(ProdOrderRoutingLine);
end;
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Route Management", OnCalculateOnBeforeProdOrderRtngLineLoopIteration, '', false, false)]
+ local procedure CheckSubcontractingOnCalculateOnBeforeProdOrderRtngLineLoopIteration(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var ProdOrderLine: Record "Prod. Order Line"; var IsHandled: Boolean)
+ begin
+ ProdOrderRoutingLine.CheckForSubcontractingPurchaseLineTypeMismatch();
+ end;
+
local procedure HandleRoutingLinkCodeValidation(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var xProdOrderRoutingLine: Record "Prod. Order Routing Line")
var
ProdOrderRoutingLine2: Record "Prod. Order Routing Line";
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchPostExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchPostExt.Codeunit.al
index 4e9cbe6eb0..7c948a81fb 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchPostExt.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchPostExt.Codeunit.al
@@ -9,6 +9,8 @@ using Microsoft.Foundation.UOM;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Journal;
using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Posting;
+using Microsoft.Inventory.Tracking;
using Microsoft.Manufacturing.Capacity;
using Microsoft.Purchases.Document;
using Microsoft.Purchases.History;
@@ -36,11 +38,14 @@ codeunit 99001535 "Subc. Purch. Post Ext"
begin
if not SubcManagementSetup.ItemChargeToRcptSubReferenceEnabled() then
exit;
+ if ItemJournalLine."Item Charge No." = '' then
+ exit;
+ if not PurchRcptLine.Get(TempItemChargeAssignmentPurch."Applies-to Doc. No.", TempItemChargeAssignmentPurch."Applies-to Doc. Line No.") then
+ exit;
+ if not PurchRcptLineHasProdOrder(PurchRcptLine) then
+ exit;
- if ItemJournalLine."Item Charge No." <> '' then
- if PurchRcptLine.Get(TempItemChargeAssignmentPurch."Applies-to Doc. No.", TempItemChargeAssignmentPurch."Applies-to Doc. Line No.") then
- if PurchRcptLineHasProdOrder(PurchRcptLine) then
- CopySubcontractingProdOrderFieldsToItemJnlLine(ItemJournalLine, PurchRcptLine);
+ CopySubcontractingProdOrderFieldsToItemJnlLine(ItemJournalLine, PurchRcptLine);
end;
local procedure SetQuantityBaseOnSubcontractingServiceLine(PurchaseLine: Record "Purchase Line"; var PurchRcptLine: Record "Purch. Rcpt. Line")
@@ -85,4 +90,29 @@ codeunit 99001535 "Subc. Purch. Post Ext"
ItemJournalLine."Inventory Posting Group" := Item."Inventory Posting Group";
ItemJournalLine."Item Charge Sub. Assign." := true;
end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnPostItemJnlLineOnAfterPostItemJnlLineJobConsumption, '', false, false)]
+ local procedure ProcessLastOperationWarehouseTracking_OnPostItemJnlLineOnAfterPostItemJnlLineJobConsumption(var ItemJournalLine: Record "Item Journal Line"; PurchaseHeader: Record "Purchase Header"; PurchaseLine: Record "Purchase Line"; OriginalItemJnlLine: Record "Item Journal Line"; var TempReservationEntry: Record "Reservation Entry" temporary; var TrackingSpecification: Record "Tracking Specification" temporary; QtyToBeInvoiced: Decimal; QtyToBeReceived: Decimal; var PostJobConsumptionBeforePurch: Boolean; var ItemJnlPostLine: Codeunit "Item Jnl.-Post Line"; var TempWhseTrackingSpecification: Record "Tracking Specification" temporary)
+ begin
+ if PurchaseLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::LastOperation then
+ CreateTempWhseSplitSpecificationForLastOperationSubcontracting(PurchaseLine, ItemJnlPostLine, TrackingSpecification, TempWhseTrackingSpecification);
+ end;
+
+ local procedure CreateTempWhseSplitSpecificationForLastOperationSubcontracting(PurchLine: Record "Purchase Line"; var ItemJnlPostLine: Codeunit "Item Jnl.-Post Line"; var TempHandlingSpecification: Record "Tracking Specification" temporary; var TempWhseSplitSpecification: Record "Tracking Specification" temporary)
+ begin
+ if ItemJnlPostLine.CollectTrackingSpecification(TempHandlingSpecification) then begin
+ TempWhseSplitSpecification.Reset();
+ TempWhseSplitSpecification.DeleteAll();
+ if TempHandlingSpecification.FindSet() then
+ repeat
+ TempWhseSplitSpecification := TempHandlingSpecification;
+ TempWhseSplitSpecification."Source Type" := DATABASE::"Purchase Line";
+ TempWhseSplitSpecification."Source Subtype" := PurchLine."Document Type".AsInteger();
+ TempWhseSplitSpecification."Source ID" := PurchLine."Document No.";
+ TempWhseSplitSpecification."Source Ref. No." := PurchLine."Line No.";
+ TempWhseSplitSpecification.Insert();
+ until TempHandlingSpecification.Next() = 0;
+ end;
+ end;
+
}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchaseLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchaseLineExt.Codeunit.al
index 4d57faea89..6945256485 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchaseLineExt.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Purchase/SubcPurchaseLineExt.Codeunit.al
@@ -4,12 +4,22 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Document;
using Microsoft.Purchases.Document;
+using Microsoft.Utilities;
+using Microsoft.Warehouse.Document;
codeunit 99001534 "Subc. Purchase Line Ext"
{
var
SubcSynchronizeManagement: Codeunit "Subc. Synchronize Management";
+ QtyMismatchTitleLbl: Label 'Quantity Mismatch';
+ QtyMismatchErr: Label 'The quantity (%1) in %2 is greater than the specified quantity (%3) in %4. In order to open item tracking lines, first adjust the quantity on %2 to at least match the quantity on %4. You can adjust the quantity from %5 to %6 by using the action below.',
+ Comment = '%1 = PurchaseLine Outstanding Qty, %2 = Tablecaption PurchaseLine, %3 = ProdOrderLine Remaining Qty, %4 = Tablecaption ProdOrderLine, %5 = Current ProdOrderLine Qty, %6 = New PurchaseLine Qty';
+ NotLastOperationLineErr: Label 'Item tracking lines can only be viewed for subcontracting purchase lines which are linked to a routing line which is the last operation.';
+ CannotOpenProductionOrderErr: Label 'Cannot open Production Order %1.', Comment = '%1=Production Order No.';
[EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnAfterDeleteEvent, '', false, false)]
local procedure OnAfterDeleteEvent(var Rec: Record "Purchase Line"; RunTrigger: Boolean)
@@ -81,4 +91,127 @@ codeunit 99001534 "Subc. Purchase Line Ext"
if (PurchaseLine.Type = PurchaseLine.Type::Item) and (PurchaseLine."No." <> '') and (PurchaseLine."Prod. Order No." <> '') and (PurchaseLine."Operation No." <> '') then
SubcPriceManagement.GetSubcPriceForPurchLine(PurchaseLine);
end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeOpenItemTrackingLines, '', false, false)]
+ local procedure OpenProdOrderLineItemTrackingOnBeforeOpenItemTrackingLines(PurchaseLine: Record "Purchase Line"; var IsHandled: Boolean)
+ begin
+ OpenItemTrackingOfProdOrderLine(PurchaseLine, false);
+ IsHandled := true;
+ end;
+
+ local procedure CheckItem(PurchaseLine: Record "Purchase Line")
+ var
+ Item: Record Item;
+ ItemTrackingCode: Record "Item Tracking Code";
+ begin
+ PurchaseLine.TestField(Type, "Purchase Line Type"::Item);
+ PurchaseLine.TestField("No.");
+ Item.SetLoadFields("Item Tracking Code");
+ Item.Get(PurchaseLine."No.");
+ Item.TestField("Item Tracking Code");
+ ItemTrackingCode.Get(Item."Item Tracking Code");
+ end;
+
+ local procedure CheckOverDeliveryQty(PurchaseLine: Record "Purchase Line"; ProdOrderLine: Record "Prod. Order Line")
+ var
+ ShowProductionOrderActionLbl: Label 'Show Prod. Order';
+ AdjustQtyActionLbl: Label 'Adjust Quantity';
+ OpenItemTrackingAnywayActionLbl: Label 'Open anyway';
+ CannotInvoiceErrorInfo: ErrorInfo;
+ begin
+ if PurchaseLine.Quantity > ProdOrderLine.Quantity then begin
+ CannotInvoiceErrorInfo.Title := QtyMismatchTitleLbl;
+ CannotInvoiceErrorInfo.Message := StrSubstNo(QtyMismatchErr, PurchaseLine."Outstanding Quantity", PurchaseLine.TableCaption(), ProdOrderLine."Remaining Quantity", ProdOrderLine.TableCaption(), ProdOrderLine.Quantity, PurchaseLine.Quantity);
+
+ CannotInvoiceErrorInfo.RecordId := PurchaseLine.RecordId;
+ CannotInvoiceErrorInfo.AddAction(
+ AdjustQtyActionLbl,
+ Codeunit::"Subc. Purchase Line Ext",
+ 'AdjustProdOrderLineQuantity'
+ );
+ CannotInvoiceErrorInfo.AddAction(
+ ShowProductionOrderActionLbl,
+ Codeunit::"Subc. Purchase Line Ext",
+ 'ShowProductionOrder'
+ );
+ CannotInvoiceErrorInfo.AddAction(
+ OpenItemTrackingAnywayActionLbl,
+ Codeunit::"Subc. Purchase Line Ext",
+ 'OpenItemTrackingWithoutAdjustment'
+ );
+ Error(CannotInvoiceErrorInfo);
+ end;
+ end;
+
+ local procedure OpenItemTrackingOfProdOrderLine(var PurchaseLine: Record "Purchase Line"; SkipOverDeliveryCheck: Boolean)
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ TrackingSpecification: Record "Tracking Specification";
+ ProdOrderLineReserve: Codeunit "Prod. Order Line-Reserve";
+ ItemTrackingLines: Page "Item Tracking Lines";
+ SecondSourceQtyArray: array[3] of Decimal;
+ begin
+ if PurchaseLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::None then
+ exit;
+ CheckItem(PurchaseLine);
+ if PurchaseLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation then
+ Error(NotLastOperationLineErr);
+ if PurchaseLine."Subc. Purchase Line Type" <> "Subc. Purchase Line Type"::LastOperation then
+ exit;
+ if not PurchaseLine.IsSubcontractingLineWithLastOperation(ProdOrderLine) then
+ exit;
+
+ SecondSourceQtyArray[1] := Database::"Warehouse Receipt Line";
+ SecondSourceQtyArray[2] := PurchaseLine.CalcBaseQtyFromQuantity(PurchaseLine."Qty. to Receive", PurchaseLine.FieldCaption("Qty. Rounding Precision"), PurchaseLine.FieldCaption("Qty. to Receive"), PurchaseLine.FieldCaption("Qty. to Receive (Base)"));
+ SecondSourceQtyArray[3] := 0;
+
+ if not SkipOverDeliveryCheck then
+ CheckOverDeliveryQty(PurchaseLine, ProdOrderLine);
+
+ ProdOrderLineReserve.InitFromProdOrderLine(TrackingSpecification, ProdOrderLine);
+ ItemTrackingLines.SetSourceSpec(TrackingSpecification, ProdOrderLine."Due Date");
+ ItemTrackingLines.SetSecondSourceQuantity(SecondSourceQtyArray);
+ ItemTrackingLines.RunModal();
+ end;
+
+ internal procedure ShowProductionOrder(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ PageManagement: Codeunit "Page Management";
+ begin
+ PurchaseLine.SetLoadFields("Prod. Order No.");
+ PurchaseLine.Get(OverDeliveryErrorInfo.RecordId);
+ ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.");
+ if not PageManagement.PageRun(ProductionOrder) then
+ Error(CannotOpenProductionOrderErr, ProductionOrder."No.");
+ end;
+
+ internal procedure AdjustProdOrderLineQuantity(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ PurchaseLine: Record "Purchase Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ PurchaseLine.SetLoadFields(Type, "No.", "Prod. Order No.", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", Quantity, "Qty. to Receive", "Qty. to Receive (Base)", "Qty. Rounding Precision", "Outstanding Quantity");
+ PurchaseLine.Get(OverDeliveryErrorInfo.RecordId);
+ ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.");
+ if PurchaseLine.Quantity > ProdOrderLine.Quantity then begin
+ ProdOrderLine.Validate(Quantity, PurchaseLine.Quantity);
+ ProdOrderLine.Modify();
+ Commit();
+ end;
+ OpenItemTrackingOfProdOrderLine(PurchaseLine, true);
+ end;
+
+ internal procedure OpenItemTrackingWithoutAdjustment(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ PurchaseLine: Record "Purchase Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ PurchaseLine.SetLoadFields(Type, "No.", "Prod. Order No.", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Qty. to Receive", "Qty. to Receive (Base)", "Qty. Rounding Precision", "Outstanding Quantity");
+ PurchaseLine.Get(OverDeliveryErrorInfo.RecordId);
+ ProdOrderLine.SetLoadFields(SystemId);
+ ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.");
+ OpenItemTrackingOfProdOrderLine(PurchaseLine, true);
+ end;
}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Warehouse/SubcWhsePostReceiptExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Warehouse/SubcWhsePostReceiptExt.Codeunit.al
index 10dfc27d51..8524f2990a 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Warehouse/SubcWhsePostReceiptExt.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Warehouse/SubcWhsePostReceiptExt.Codeunit.al
@@ -4,10 +4,40 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Tracking;
+
using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Utilities;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Journal;
codeunit 99001551 "Subc. WhsePostReceipt Ext"
{
+ var
+ NotLastOperationLineErr: Label 'Item tracking lines can only be viewed for subcontracting purchase lines which are linked to a routing line which is the last operation.';
+ QtyMismatchTitleLbl: Label 'Quantity Mismatch';
+ QtyMismatchErr: Label 'The quantity (%1) in %2 is greater than the remaining quantity (%3) in %4. In order to open item tracking lines, first adjust the quantity on %4 to at least match the quantity on %2. You can adjust the quantity from %5 to %6 by using the action below.',
+ Comment = '%1 = Warehouse Receipt Line Quantity, %2 = Tablecaption WarehouseReceiptLine, %3 = ProdOrderLine Remaining Qty, %4 = Tablecaption ProdOrderLine, %5 = Current ProdOrderLine Quantity, %6 = WarehouseReceiptLine Quantity';
+ ShowProductionOrderActionLbl: Label 'Show Prod. Order';
+ AdjustQtyActionLbl: Label 'Adjust Quantity';
+ OpenItemTrackingAnywayActionLbl: Label 'Open anyway';
+ CannotOpenProductionOrderErr: Label 'Cannot open Production Order %1.', Comment = '%1=Production Order No.';
+ WarehouseReceiptLineSystemIdCustomDimensionTok: Label 'WarehouseReceiptLineSystemId', Locked = true;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Receipt Line", OnBeforeOpenItemTrackingLines, '', false, false)]
+ local procedure CheckOverDeliveryOnBeforeOpenItemTrackingLines(var WarehouseReceiptLine: Record "Warehouse Receipt Line"; var IsHandled: Boolean; CallingFieldNo: Integer)
+ begin
+ if WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::None then
+ exit;
+ if WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation then
+ Error(NotLastOperationLineErr);
+ CheckOverDelivery(WarehouseReceiptLine);
+ end;
+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Receipt", OnAfterTransRcptLineModify, '', false, false)]
local procedure OnAfterTransRcptLineModify(var TransferReceiptLine: Record "Transfer Receipt Line"; TransferLine: Record "Transfer Line"; CommitIsSuppressed: Boolean)
var
@@ -15,4 +45,257 @@ codeunit 99001551 "Subc. WhsePostReceipt Ext"
begin
SubcontractingManagement.TransferReservationEntryFromPstTransferLineToProdOrderComp(TransferReceiptLine);
end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purchases Warehouse Mgt.", OnAfterGetQuantityRelatedParameter, '', false, false)]
+ local procedure CalculateSubcontractingLastOperationQuantity_OnAfterGetQuantityRelatedParameter(PurchaseLine: Record Microsoft.Purchases.Document."Purchase Line"; var QtyPerUoM: Decimal; var QtyBasePurchaseLine: Decimal)
+ var
+ Item: Record Microsoft.Inventory.Item.Item;
+ UnitOfMeasureManagement: Codeunit "Unit of Measure Management";
+ begin
+ if PurchaseLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::LastOperation then begin
+ Item.SetLoadFields("No.", "Base Unit of Measure");
+ Item.Get(PurchaseLine."No.");
+ QtyPerUoM := UnitOfMeasureManagement.GetQtyPerUnitOfMeasure(Item, PurchaseLine."Unit of Measure Code");
+ QtyBasePurchaseLine := PurchaseLine.CalcBaseQtyFromQuantity(PurchaseLine.Quantity, PurchaseLine.FieldCaption("Qty. Rounding Precision"), PurchaseLine.FieldCaption("Quantity"), PurchaseLine.FieldCaption("Quantity (Base)"));
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purchases Warehouse Mgt.", OnPurchLine2ReceiptLineOnAfterInitNewLine, '', false, false)]
+ local procedure SetSubcPurchaseLineTypeOnReceiptLine_OnPurchLine2ReceiptLineOnAfterInitNewLine(var WarehouseReceiptLine: Record "Warehouse Receipt Line"; WarehouseReceiptHeader: Record "Warehouse Receipt Header"; PurchaseLine: Record "Purchase Line"; var IsHandled: Boolean)
+ begin
+ WarehouseReceiptLine."Subc. Purchase Line Type" := PurchaseLine."Subc. Purchase Line Type";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purchases Warehouse Mgt.", OnBeforeCheckIfPurchLine2ReceiptLine, '', false, false)]
+ local procedure CheckOutstandingBaseQtyForSubcontracting_OnBeforeCheckIfPurchLine2ReceiptLine(var PurchaseLine: Record "Purchase Line"; var ReturnValue: Boolean; var IsHandled: Boolean)
+ var
+ OutstandingQtyBase: Decimal;
+ WhseOutstandingQtyBase: Decimal;
+ begin
+ case PurchaseLine."Subc. Purchase Line Type" of
+ "Subc. Purchase Line Type"::None:
+ exit;
+ "Subc. Purchase Line Type"::LastOperation,
+ "Subc. Purchase Line Type"::NotLastOperation:
+ begin
+ PurchaseLine.CalcFields("Whse. Outstanding Quantity");
+ OutstandingQtyBase := PurchaseLine.CalcBaseQtyFromQuantity(PurchaseLine."Outstanding Quantity", PurchaseLine.FieldCaption("Qty. Rounding Precision"), PurchaseLine.FieldCaption("Outstanding Quantity"), PurchaseLine.FieldCaption("Outstanding Qty. (Base)"));
+ WhseOutstandingQtyBase := PurchaseLine.CalcBaseQtyFromQuantity(PurchaseLine."Whse. Outstanding Quantity", PurchaseLine.FieldCaption("Qty. Rounding Precision"), PurchaseLine.FieldCaption("Whse. Outstanding Quantity"), PurchaseLine.FieldCaption("Whse. Outstanding Qty. (Base)"));
+ ReturnValue := (Abs(OutstandingQtyBase) > Abs(WhseOutstandingQtyBase));
+ IsHandled := true;
+ end;
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Purch. Release", OnReleaseOnBeforeCreateWhseRequest, '', false, false)]
+ local procedure CreateWhseRequestForInventoriableItem_OnReleaseOnBeforeCreateWhseRequest(var PurchaseLine: Record "Purchase Line"; var DoCreateWhseRequest: Boolean)
+ begin
+ DoCreateWhseRequest := DoCreateWhseRequest or PurchaseLine.IsInventoriableItem();
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Receipt Line", OnBeforeCalcBaseQty, '', false, false)]
+ local procedure SuppressQtyPerUoMTestfieldForSubcontracting_OnBeforeCalcBaseQty(var WarehouseReceiptLine: Record "Warehouse Receipt Line"; var Qty: Decimal; FromFieldName: Text; ToFieldName: Text; var SuppressQtyPerUoMTestfield: Boolean)
+ begin
+ SuppressQtyPerUoMTestfield := WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Receipt Line", OnValidateQtyToReceiveOnBeforeUOMMgtValidateQtyIsBalanced, '', false, false)]
+ local procedure SkipValidateQtyBalancedForSubcontracting_OnValidateQtyToReceiveOnBeforeUOMMgtValidateQtyIsBalanced(var WarehouseReceiptLine: Record "Warehouse Receipt Line"; xWarehouseReceiptLine: Record "Warehouse Receipt Line"; var IsHandled: Boolean)
+ begin
+ if (WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation) then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Post Receipt", OnBeforePostWhseJnlLine, '', false, false)]
+ local procedure SkipPostWhseJnlLineForSubcontracting_OnBeforePostWhseJnlLine(var PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header"; var PostedWhseReceiptLine: Record "Posted Whse. Receipt Line"; var WhseReceiptLine: Record "Warehouse Receipt Line"; var TempTrackingSpecification: Record "Tracking Specification" temporary; var IsHandled: Boolean)
+ begin
+ if PostedWhseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Post Receipt", OnPostWhseJnlLineOnAfterInsertWhseItemEntryRelation, '', false, false)]
+ local procedure SkipWhseItemEntryRelationForSubcontracting_OnPostWhseJnlLineOnAfterInsertWhseItemEntryRelation(var PostedWhseRcptHeader: Record "Posted Whse. Receipt Header"; var PostedWhseRcptLine: Record "Posted Whse. Receipt Line"; var TempWhseSplitSpecification: Record "Tracking Specification" temporary; var IsHandled: Boolean; ReceivingNo: Code[20]; PostingDate: Date; var TempWhseJnlLine: Record "Warehouse Journal Line" temporary)
+ begin
+ if PostedWhseRcptLine."Subc. Purchase Line Type" <> "Subc. Purchase Line Type"::None then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Receipt Line", OnBeforeOpenItemTrackingLineForPurchLine, '', false, false)]
+ local procedure OpenItemTrackingForSubcontracting_OnBeforeOpenItemTrackingLineForPurchLine(PurchaseLine: Record "Purchase Line"; SecondSourceQtyArray: array[3] of Decimal; var SkipCallItemTracking: Boolean)
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ if PurchaseLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::LastOperation then
+ if PurchaseLine.IsSubcontractingLineWithLastOperation(ProdOrderLine) then begin
+ OpenItemTrackingOfProdOrderLine(SecondSourceQtyArray, ProdOrderLine);
+ SkipCallItemTracking := true;
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Post Receipt", OnCreatePostedRcptLineOnBeforePutAwayProcessing, '', false, false)]
+ local procedure SkipPutAwayForSubcontracting_OnCreatePostedRcptLineOnBeforePutAwayProcessing(var PostedWhseReceiptLine: Record "Posted Whse. Receipt Line"; var SkipPutAwayProcessing: Boolean)
+ begin
+ if SkipPutAwayProcessing then
+ exit;
+ SkipPutAwayProcessing := PostedWhseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Post Receipt", OnBeforeCreatePutAwayLine, '', false, false)]
+ local procedure SkipPutAwayCreationForSubcontracting_OnBeforeCreatePutAwayLine(PostedWhseReceiptLine: Record "Posted Whse. Receipt Line"; var SkipPutAwayCreationForLine: Boolean)
+ begin
+ if PostedWhseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation then
+ SkipPutAwayCreationForLine := true;
+ end;
+
+ local procedure OpenItemTrackingOfProdOrderLine(var SecondSourceQtyArray: array[3] of Decimal; var ProdOrderLine: Record "Prod. Order Line")
+ var
+ TrackingSpecification: Record "Tracking Specification";
+ ProdOrderLineReserve: Codeunit "Prod. Order Line-Reserve";
+ ItemTrackingLines: Page "Item Tracking Lines";
+ begin
+ ProdOrderLineReserve.InitFromProdOrderLine(TrackingSpecification, ProdOrderLine);
+ ItemTrackingLines.SetSourceSpec(TrackingSpecification, ProdOrderLine."Due Date");
+ ItemTrackingLines.SetSecondSourceQuantity(SecondSourceQtyArray);
+ ItemTrackingLines.RunModal();
+ end;
+
+ local procedure CheckOverDelivery(var WarehouseReceiptLine: Record "Warehouse Receipt Line")
+ var
+ PurchaseLine: Record "Purchase Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ CannotInvoiceErrorInfo: ErrorInfo;
+ CustomDimensions: Dictionary of [Text, Text];
+ begin
+ PurchaseLine.SetLoadFields("Subc. Purchase Line Type", "Prod. Order No.", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.");
+ if not PurchaseLine.Get(WarehouseReceiptLine."Source Subtype", WarehouseReceiptLine."Source No.", WarehouseReceiptLine."Source Line No.") then
+ exit;
+ if PurchaseLine."Subc. Purchase Line Type" <> "Subc. Purchase Line Type"::LastOperation then
+ exit;
+ if not PurchaseLine.IsSubcontractingLineWithLastOperation(ProdOrderLine) then
+ exit;
+ if ProdOrderLine.Quantity < WarehouseReceiptLine.Quantity then begin
+ CannotInvoiceErrorInfo.Title := QtyMismatchTitleLbl;
+ CannotInvoiceErrorInfo.Message := StrSubstNo(QtyMismatchErr, WarehouseReceiptLine.Quantity, WarehouseReceiptLine.TableCaption(), ProdOrderLine."Remaining Quantity", ProdOrderLine.TableCaption(), ProdOrderLine.Quantity, WarehouseReceiptLine.Quantity);
+
+ CannotInvoiceErrorInfo.RecordId := PurchaseLine.RecordId;
+ CustomDimensions.Add(GetWarehouseReceiptLineSystemIdCustomDimensionLbl(), WarehouseReceiptLine.SystemId);
+ CannotInvoiceErrorInfo.CustomDimensions(CustomDimensions);
+ CannotInvoiceErrorInfo.AddAction(
+ AdjustQtyActionLbl,
+ Codeunit::"Subc. WhsePostReceipt Ext",
+ 'AdjustProdOrderLineQuantity'
+ );
+ CannotInvoiceErrorInfo.AddAction(
+ ShowProductionOrderActionLbl,
+ Codeunit::"Subc. WhsePostReceipt Ext",
+ 'ShowProductionOrder'
+ );
+ CannotInvoiceErrorInfo.AddAction(
+ OpenItemTrackingAnywayActionLbl,
+ Codeunit::"Subc. Purchase Line Ext",
+ 'OpenItemTrackingWithoutAdjustment'
+ );
+ Error(CannotInvoiceErrorInfo);
+ end;
+ end;
+
+ ///
+ /// Opens the Production Order linked to the subcontracting purchase line in order for the user to review the details of the Production Order, such as the remaining quantity on the Production Order Line, before deciding whether to adjust the quantity on the Production Order Line or open the item tracking lines without adjustment.
+ ///
+ /// ErrorInfo if quantities does not match before. This will hold the reference of the source of the error.
+ internal procedure ShowProductionOrder(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ PageManagement: Codeunit "Page Management";
+ begin
+ PurchaseLine.SetLoadFields("Prod. Order No.");
+ PurchaseLine.Get(OverDeliveryErrorInfo.RecordId);
+ ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.");
+ if not PageManagement.PageRun(ProductionOrder) then
+ Error(CannotOpenProductionOrderErr, ProductionOrder."No.");
+ end;
+
+ ///
+ /// Adjusts the Quantity of of the Production Order Line to at least match the quantity on the Warehouse Receipt Line,
+ /// so that the user can then open the item tracking lines for the Production Order Line from the Warehouse Receipt Line.
+ /// This action is added to an error message that is thrown when the user tries to open item tracking lines from a Warehouse Receipt Line
+ /// which is linked to a subcontracting purchase line with last operation type, and the quantity on the Warehouse Receipt Line
+ /// is greater than the remaining quantity on the linked Production Order Line.
+ /// The action will adjust the quantity on the Production Order Line to match the quantity on the Warehouse Receipt Line,
+ /// and then open the item tracking lines for the Production Order Line.
+ ///
+ /// ErrorInfo if quantities does not match before. This will hold the reference of the source of the error.
+ internal procedure AdjustProdOrderLineQuantity(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ PurchaseLine: Record "Purchase Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ SecondSourceQtyArray: array[3] of Decimal;
+ CustomDimensions: Dictionary of [Text, Text];
+ WarehouseReceiptLineSystemId: Guid;
+ begin
+ CustomDimensions := OverDeliveryErrorInfo.CustomDimensions();
+ if CustomDimensions.ContainsKey(GetWarehouseReceiptLineSystemIdCustomDimensionLbl()) then
+ if not Evaluate(WarehouseReceiptLineSystemId, CustomDimensions.Get(GetWarehouseReceiptLineSystemIdCustomDimensionLbl())) then
+ exit;
+ WarehouseReceiptLine.SetLoadFields(Quantity, "Qty. to Receive (Base)");
+ if not WarehouseReceiptLine.GetBySystemId(WarehouseReceiptLineSystemId) then
+ exit;
+ PurchaseLine.SetLoadFields("Prod. Order No.", "Prod. Order Line No.");
+ PurchaseLine.Get(OverDeliveryErrorInfo.RecordId);
+ ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.");
+ if WarehouseReceiptLine.Quantity > ProdOrderLine.Quantity then begin
+ ProdOrderLine.Validate(Quantity, WarehouseReceiptLine.Quantity);
+ ProdOrderLine.Modify();
+ Commit();
+ end;
+ SecondSourceQtyArray[1] := Database::"Warehouse Receipt Line";
+ SecondSourceQtyArray[2] := WarehouseReceiptLine."Qty. to Receive (Base)";
+ SecondSourceQtyArray[3] := 0;
+
+ OpenItemTrackingOfProdOrderLine(SecondSourceQtyArray, ProdOrderLine);
+ end;
+
+ ///
+ /// Opens the item tracking lines for the Production Order Line without adjusting the quantity,
+ /// even if the quantity on the Warehouse Receipt Line is greater than the remaining quantity on the linked Production Order Line.
+ ///
+ /// ErrorInfo if quantities does not match before. This will hold the reference of the source of the error.
+ internal procedure OpenItemTrackingWithoutAdjustment(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ PurchaseLine: Record "Purchase Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ SecondSourceQtyArray: array[3] of Decimal;
+ CustomDimensions: Dictionary of [Text, Text];
+ WarehouseReceiptLineSystemId: Guid;
+ begin
+ CustomDimensions := OverDeliveryErrorInfo.CustomDimensions();
+ if CustomDimensions.ContainsKey(GetWarehouseReceiptLineSystemIdCustomDimensionLbl()) then
+ if not Evaluate(WarehouseReceiptLineSystemId, CustomDimensions.Get(GetWarehouseReceiptLineSystemIdCustomDimensionLbl())) then
+ exit;
+ WarehouseReceiptLine.SetLoadFields("Qty. to Receive (Base)");
+ if not WarehouseReceiptLine.GetBySystemId(WarehouseReceiptLineSystemId) then
+ exit;
+ PurchaseLine.SetLoadFields("Prod. Order No.", "Prod. Order Line No.");
+ PurchaseLine.Get(OverDeliveryErrorInfo.RecordId);
+ ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.");
+
+ SecondSourceQtyArray[1] := Database::"Warehouse Receipt Line";
+ SecondSourceQtyArray[2] := WarehouseReceiptLine."Qty. to Receive (Base)";
+ SecondSourceQtyArray[3] := 0;
+
+ OpenItemTrackingOfProdOrderLine(SecondSourceQtyArray, ProdOrderLine);
+ end;
+
+ ///
+ /// Retrieves the value of WarehouseReceiptLineSystemIdCustomDimensionTok,
+ /// which is the name of the custom dimension used to store the SystemId of the Warehouse Receipt Line in the error info when there is a quantity mismatch.
+ ///
+ ///
+ procedure GetWarehouseReceiptLineSystemIdCustomDimensionLbl(): Text
+ begin
+ exit(WarehouseReceiptLineSystemIdCustomDimensionTok);
+ end;
}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Enumerations/SubcPurchaseLineType.Enum.al b/src/Apps/W1/Subcontracting/App/src/Process/Enumerations/SubcPurchaseLineType.Enum.al
new file mode 100644
index 0000000000..70ba5b7ed7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Enumerations/SubcPurchaseLineType.Enum.al
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting;
+enum 99001507 "Subc. Purchase Line Type"
+{
+ Extensible = true;
+
+ value(0; None)
+ {
+ Caption = ' ';
+ }
+ value(1; LastOperation)
+ {
+ Caption = 'Last Operation';
+ }
+ value(2; NotLastOperation)
+ {
+ Caption = 'Not Last Operation';
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Reports/Rep99001500.SubcDetailedCalculation.rdl b/src/Apps/W1/Subcontracting/App/src/Process/Reports/Rep99001500.SubcDetailedCalculation.rdl
index f941688ff9..f0ad93b76b 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Reports/Rep99001500.SubcDetailedCalculation.rdl
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Reports/Rep99001500.SubcDetailedCalculation.rdl
@@ -1,5957 +1,5957 @@
-
-
- 0
-
-
-
- SQL
-
-
- None
- 76c8b53c-5c88-4136-be7b-4e803ebab26d
-
-
-
-
-
-
-
-
-
-
- 1.16906cm
-
-
- 1.40236cm
-
-
- 2.22494cm
-
-
- 1.45001cm
-
-
- 1.5873cm
-
-
- 1.12222cm
-
-
- 1.65001cm
-
-
- 1.95001cm
-
-
- 1.7873cm
-
-
- 1.5873cm
-
-
- 0.95237cm
-
-
- 1.26985cm
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =First(Fields!ItemFilterCaption.Value)
-
-
-
-
-
-
- Textbox1
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox2
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox4
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox5
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox6
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox7
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox8
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox9
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox10
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox11
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox12
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox13
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!No_ItemCaption.Value
-
-
-
-
-
-
- Textbox14
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox15
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!No_Item.Value
-
-
-
-
-
-
- Textbox16
-
-
- Bottom
- 5pt
- 5pt
-
-
- 5
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox17
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox18
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox19
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox20
-
-
-
-
-
-
-
- 0.4064cm
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!Description_ItemCaption.Value
-
-
-
-
-
-
- Textbox21
-
-
- 3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!Description_Item.Value
-
-
-
-
-
-
- Textbox22
-
-
- 6
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox23
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox24
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox25
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!ProductionBOMNo_ItemCaption.Value
-
-
-
-
-
-
- Textbox26
-
-
- 3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProductionBOMNo_Item.Value
-
-
-
-
-
-
- Textbox27
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!PBOMVersionCode1.Value
-
-
-
-
-
-
- Textbox28
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox29
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox30
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox32
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox34
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox35
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!LotSize_ItemCaption.Value
-
-
-
-
-
-
- Textbox36
-
-
- 3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!LotSize_Item.Value
-
-
-
-
-
-
- Textbox37
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!BaseUnitOfMeasure_Item.Value
-
-
-
-
-
-
- Textbox38
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox39
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox40
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox41
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox42
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox43
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!RoutingNo_ItemCaption.Value
-
-
-
-
-
-
- Textbox44
-
-
- 3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!RoutingNo_Item.Value
-
-
-
-
-
-
- Textbox45
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!RtngVersionCode.Value
-
-
-
-
-
-
- Textbox46
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox47
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox48
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox49
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox50
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox51
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox52
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!OperationNo_RtngLineCaption.Value
-
-
-
-
-
-
- Textbox53
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!Type_RtngLineCaption.Value
-
-
-
-
-
-
- Textbox54
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!No_RtngLineCaption.Value
-
-
-
-
-
-
- Textbox55
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!Description_ItemCaption.Value
-
-
-
-
-
-
- Textbox56
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!SetupTime_RtngLineCaption.Value
-
-
-
-
-
-
- Textbox57
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Parameters!RunTime_RtngLineCaption.Value
-
-
-
-
-
-
- Textbox58
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!CostTimeCaption.Value
-
-
-
-
-
-
- Textbox59
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!UnitCostCaption.Value
-
-
-
-
-
-
- Textbox60
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!TotalCostCaption.Value
-
-
-
-
-
-
- Textbox62
-
-
- 2
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox63
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox64
-
-
-
-
-
- Bottom
- 5pt
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Fields!TypeCaption.Value
-
-
-
-
-
-
- Textbox65
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!NoCaption.Value
-
-
-
-
-
-
- Textbox66
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!DescriptionCaption.Value
-
-
-
-
-
-
- Textbox67
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!QuantityCaption.Value
-
-
-
-
-
-
- Textbox68
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!BaseUnitOfMeasureCaption.Value
-
-
-
-
-
-
- Textbox69
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!UnitCostCaption.Value
-
-
-
-
-
-
- Textbox70
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!TotalCost1Caption.Value
-
-
-
-
-
-
- Textbox71
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox72
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox73
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox74
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox75
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox77
-
-
-
-
-
- Bottom
- 5pt
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Fields!OperationNo_RtngLine.Value
-
-
-
-
-
-
- Textbox78
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!Type_RtngLine.Value
-
-
-
-
-
-
- Textbox79
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!No_RtngLine.Value
-
-
-
-
-
-
- Textbox80
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!Description_RtngLine.Value
-
-
-
-
-
-
- Textbox81
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!SetupTime_RtngLine.Value
-
-
-
-
-
-
- Textbox82
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!RunTime_RtngLine.Value
-
-
-
-
-
-
- Textbox83
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!CostTime.Value
-
-
-
-
-
-
- Textbox84
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProdUnitCost.Value
-
-
-
-
-
-
- Textbox85
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProdTotalCost.Value
-
-
-
-
-
-
- Textbox86
-
-
- 2
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProdBOMLineLevelType.Value
-
-
-
-
-
-
- Textbox87
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProdBOMLineLevelNo.Value
-
-
-
-
-
-
- Textbox88
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProdBOMLineLevelDesc.Value
-
-
-
-
-
-
- Textbox89
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!ProdBOMLineLevelQuantity.Value
-
-
-
-
-
-
- Textbox90
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!CompItemBaseUOM.Value
-
-
-
-
-
-
- Textbox91
-
-
- 2
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!CompItemUnitCost.Value
-
-
-
-
-
-
- Textbox92
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!CostTotal.Value
-
-
-
-
-
-
- Textbox93
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox94
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox95
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox96
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox97
-
-
- 12
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox98
-
-
- 8
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox99
-
-
-
-
-
- Top
- 5pt
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox100
-
-
- 3
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox101
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox102
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox103
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox104
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox105
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Fields!TotalCost1Caption.Value
-
-
-
-
-
-
- Textbox106
-
-
- 3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Sum(Fields!CostTotal.Value)
-
-
-
-
-
-
- Textbox107
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox108
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox109
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox110
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox111
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox112
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox113
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox114
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox115
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox116
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox117
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox118
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox119
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox120
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox121
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox122
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox123
-
-
- 11
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox124
-
-
-
-
-
- Top
- 5pt
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox125
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =First(Fields!TotalCostCaption.Value)
-
-
-
-
-
-
- Textbox126
-
-
- 4
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Sum(Fields!ProdTotalCost.Value)
-
-
-
-
-
-
- Textbox127
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox128
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox129
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox130
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox131
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox132
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox133
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox134
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Last(Fields!CostOfProductionCaption.Value)
-
-
-
-
-
-
- Textbox135
-
-
- 4
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Sum(Fields!FooterProdTotalCost.Value)
-
-
-
-
-
-
- Textbox136
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox137
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Last(Fields!CostOfComponentsCaption.Value)
-
-
-
-
-
-
- Textbox138
-
-
- 4
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Sum(Fields!FooterCostTotal.Value)
-
-
-
-
-
-
- Textbox139
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox140
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Last(Fields!SingleLevelMfgOverheadCostCaption.Value)
-
-
-
-
-
-
- Textbox141
-
-
- 4
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Sum(Fields!SingleLevelMfgOvhd.Value)
-
-
-
-
-
-
- Textbox142
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox143
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox144
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox145
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox146
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox147
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox148
-
-
-
-
-
-
-
- 0.17638cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox149
-
-
- 11
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox150
-
-
-
-
-
- Top
- 5pt
-
-
-
-
-
-
-
- 0.35278cm
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox151
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
- Textbox152
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Last(Fields!UnitCostCaption.Value)
-
-
-
-
-
-
- Textbox153
-
-
- 3
-
-
-
-
-
-
-
- true
- true
-
-
-
-
- =Sum(Fields!UnitCost_Item.Value)
-
-
-
-
-
-
- Textbox154
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- =iif(Fields!ItemFilter.Value = "",true,false)
-
- After
- true
-
-
-
- =iif(Fields!ItemFilter.Value = "",true,false)
-
- After
- true
-
-
-
-
- =Fields!No_Item.Value
-
-
- End
-
-
-
-
- After
- true
-
-
- After
- true
-
-
- After
- true
-
-
- After
- true
-
-
- After
- true
-
-
- After
- true
-
-
-
-
- =Fields!InRouting.Value
-
-
-
-
-
- =iif(Fields!InRouting.Value,false,true)
-
- After
- true
-
-
-
- =iif(Fields!InRouting.Value,false,true)
-
- After
- true
-
-
-
- =iif(Fields!InRouting.Value,false,true)
-
- After
-
-
-
-
- =Fields!InBOM.Value
-
-
-
-
-
- =iif(Fields!InBOM.Value,false,true)
-
- After
- true
-
-
-
- =iif(Fields!InBOM.Value,false,true)
-
- After
- true
-
-
-
- =iif(Fields!InBOM.Value,false,true)
-
- After
-
-
-
- Detail
-
-
-
-
- =iif(Fields!OperationNo_RtngLine.Value = "",true,false)
-
-
-
-
- =iif(Fields!ShowLine.Value,false,true)
-
-
-
- Detail_Collection
- Output
- true
-
-
-
- =iif(Fields!TotalCost1Caption.Value = "",true,false)
-
- Before
-
-
-
- =iif(Fields!TotalCost1Caption.Value = "",true,false)
-
- Before
-
-
-
- =iif(Fields!TotalCost1Caption.Value = "",true,false)
-
- Before
- true
-
-
-
- =iif(Fields!TotalCostCaption.Value = "",true,false)
-
- Before
- true
-
-
-
- =iif(Fields!TotalCostCaption.Value = "",true,false)
-
- Before
-
-
-
-
-
- =iif(First(Fields!TotalCostCaption.Value) = "",true,false)
-
- Before
- true
-
-
-
- =iif(First(Fields!TotalCostCaption.Value) = "",true,false)
-
- Before
- true
-
-
-
-
- Before
- true
-
-
- Before
- true
-
-
- Before
- true
-
-
- Before
- true
-
-
- Before
-
-
- Before
- true
-
-
-
-
-
- DataSet_Result
- 8.52024cm
- 18.15273cm
-
-
-
- 8.52024cm
-
-
- 18.15273cm
-
-
- 1.52931cm
- true
- true
-
-
- true
-
-
-
-
- =Fields!DetailedCalculationCaption.Value
-
-
-
-
-
-
- 20pt
- 12.50762cm
-
-
-
- true
-
-
-
-
- =Fields!PageNoCaption.Value
-
-
-
-
-
-
- 0.3595cm
- 12.57817cm
- 11pt
- 5.10758cm
- 1
-
-
-
- true
-
-
-
-
- =Fields!CalculateDate.Value
-
-
-
-
-
-
- 0.75cm
- 11pt
- 14.34321cm
- 2
-
-
-
- true
-
-
-
-
- =Fields!CompanyName.Value
-
-
-
-
-
-
- 1.14125cm
- 11pt
- 18.15272cm
- 3
-
-
-
- true
-
-
-
-
- =Fields!TodayFormatted.Value
-
-
-
-
-
-
- 12.50762cm
- 11pt
- 5.64511cm
- 4
-
-
-
- true
- true
-
-
-
-
- =User!UserID
-
-
-
-
-
-
- 0.75075cm
- 14.41376cm
- 11pt
- 3.73896cm
- 5
-
- =iif(Fields!DetailedCalculationCaption.Value = "",true,false)
-
- NoOutput
-
-
-
-
-
-
-
- 29.7cm
- 21cm
- 1.76389cm
- 1.05833cm
- 1.05833cm
- 1.48167cm
- 1.27cm
-
-
-
-
-
-
- String
-
-
- Description_ItemCaption
-
-
- Description_ItemCaption
-
-
- String
-
-
- LotSize_ItemCaption
-
-
- LotSize_ItemCaption
-
-
- String
-
-
- No_ItemCaption
-
-
- No_ItemCaption
-
-
- String
-
-
- ProductionBOMNo_ItemCaption
-
-
- ProductionBOMNo_ItemCaption
-
-
- String
-
-
- RoutingNo_ItemCaption
-
-
- RoutingNo_ItemCaption
-
-
- String
-
-
- Description_RtngLineCaption
-
-
- Description_RtngLineCaption
-
-
- String
-
-
- No_RtngLineCaption
-
-
- No_RtngLineCaption
-
-
- String
-
-
- OperationNo_RtngLineCaption
-
-
- OperationNo_RtngLineCaption
-
-
- String
-
-
- RunTime_RtngLineCaption
-
-
- RunTime_RtngLineCaption
-
-
- String
-
-
- SetupTime_RtngLineCaption
-
-
- SetupTime_RtngLineCaption
-
-
- String
-
-
- Type_RtngLineCaption
-
-
- Type_RtngLineCaption
-
-
-
-
- 1
- 11
-
-
- 0
- 0
- Description_ItemCaption
-
-
- 0
- 1
- LotSize_ItemCaption
-
-
- 0
- 2
- No_ItemCaption
-
-
- 0
- 3
- ProductionBOMNo_ItemCaption
-
-
- 0
- 4
- RoutingNo_ItemCaption
-
-
- 0
- 5
- Description_RtngLineCaption
-
-
- 0
- 6
- No_RtngLineCaption
-
-
- 0
- 7
- OperationNo_RtngLineCaption
-
-
- 0
- 8
- RunTime_RtngLineCaption
-
-
- 0
- 9
- SetupTime_RtngLineCaption
-
-
- 0
- 10
- Type_RtngLineCaption
-
-
-
-
- Public Function BlankZero(ByVal Value As Decimal)
- if Value = 0 then
- Return ""
- end if
- Return Value
-End Function
-
-Public Function BlankPos(ByVal Value As Decimal)
- if Value > 0 then
- Return ""
- end if
- Return Value
-End Function
-
-Public Function BlankZeroAndPos(ByVal Value As Decimal)
- if Value >= 0 then
- Return ""
- end if
- Return Value
-End Function
-
-Public Function BlankNeg(ByVal Value As Decimal)
- if Value < 0 then
- Return ""
- end if
- Return Value
-End Function
-
-Public Function BlankNegAndZero(ByVal Value As Decimal)
- if Value <= 0 then
- Return ""
- end if
- Return Value
-End Function
-
-
- =User!Language
- true
- Cm
- 0eeb6585-38ae-40f1-885b-8d50088d51b4
-
-
-
-
- BaseUnitOfMeasure_Item
-
-
- CalculateDate
-
-
- CompanyName
-
-
- Description_Item
-
-
- DetailedCalculationCaption
-
-
- ItemFilter
-
-
- ItemFilterCaption
-
-
- LotSize_Item
-
-
- LotSize_ItemFormat
-
-
- No_Item
-
-
- PageNoCaption
-
-
- PBOMVersionCode1
-
-
- ProductionBOMNo_Item
-
-
- RoutingNo_Item
-
-
- RtngVersionCode
-
-
- TodayFormatted
-
-
- UnitCostCaption
-
-
- CostTime
-
-
- CostTimeFormat
-
-
- CostTimeCaption
-
-
- Description_RtngLine
-
-
- InRouting
-
-
- No_RtngLine
-
-
- OperationNo_RtngLine
-
-
- ProdTotalCost
-
-
- ProdTotalCostFormat
-
-
- ProdUnitCost
-
-
- ProdUnitCostFormat
-
-
- RunTime_RtngLine
-
-
- RunTime_RtngLineFormat
-
-
- SetupTime_RtngLine
-
-
- SetupTime_RtngLineFormat
-
-
- TotalCostCaption
-
-
- Type_RtngLine
-
-
- VersionCode_RtngLine
-
-
- BaseUnitOfMeasureCaption
-
-
- DescriptionCaption
-
-
- InBOM
-
-
- NoCaption
-
-
- QuantityCaption
-
-
- TotalCost1Caption
-
-
- TypeCaption
-
-
- CompItemBaseUOM
-
-
- CompItemUnitCost
-
-
- CompItemUnitCostFormat
-
-
- CostTotal
-
-
- CostTotalFormat
-
-
- ProdBOMLineLevelDesc
-
-
- ProdBOMLineLevelNo
-
-
- ProdBOMLineLevelQuantity
-
-
- ProdBOMLineLevelQuantityFormat
-
-
- ProdBOMLineLevelType
-
-
- ShowLine
-
-
- Number_IntegerLine
-
-
- CostOfComponentsCaption
-
-
- CostOfProductionCaption
-
-
- FooterCostTotal
-
-
- FooterCostTotalFormat
-
-
- FooterProdTotalCost
-
-
- FooterProdTotalCostFormat
-
-
- FormatCostTotal
-
-
- FormatCostTotalFormat
-
-
- SingleLevelMfgOverheadCostCaption
-
-
- SingleLevelMfgOvhd
-
-
- SingleLevelMfgOvhdFormat
-
-
- TotalProdTotalCost
-
-
- TotalProdTotalCostFormat
-
-
- UnitCost_Item
-
-
- UnitCost_ItemFormat
-
-
-
- DataSource
-
-
-
-
+
+
+ 0
+
+
+
+ SQL
+
+
+ None
+ 76c8b53c-5c88-4136-be7b-4e803ebab26d
+
+
+
+
+
+
+
+
+
+
+ 1.16906cm
+
+
+ 1.40236cm
+
+
+ 2.22494cm
+
+
+ 1.45001cm
+
+
+ 1.5873cm
+
+
+ 1.12222cm
+
+
+ 1.65001cm
+
+
+ 1.95001cm
+
+
+ 1.7873cm
+
+
+ 1.5873cm
+
+
+ 0.95237cm
+
+
+ 1.26985cm
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =First(Fields!ItemFilterCaption.Value)
+
+
+
+
+
+
+ Textbox1
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox2
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox4
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox5
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox6
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox7
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox8
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox9
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox10
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox11
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox12
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox13
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!No_ItemCaption.Value
+
+
+
+
+
+
+ Textbox14
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox15
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!No_Item.Value
+
+
+
+
+
+
+ Textbox16
+
+
+ Bottom
+ 5pt
+ 5pt
+
+
+ 5
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox17
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox18
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox19
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox20
+
+
+
+
+
+
+
+ 0.4064cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!Description_ItemCaption.Value
+
+
+
+
+
+
+ Textbox21
+
+
+ 3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!Description_Item.Value
+
+
+
+
+
+
+ Textbox22
+
+
+ 6
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox23
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox24
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox25
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!ProductionBOMNo_ItemCaption.Value
+
+
+
+
+
+
+ Textbox26
+
+
+ 3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProductionBOMNo_Item.Value
+
+
+
+
+
+
+ Textbox27
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!PBOMVersionCode1.Value
+
+
+
+
+
+
+ Textbox28
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox29
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox30
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox32
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox34
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox35
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!LotSize_ItemCaption.Value
+
+
+
+
+
+
+ Textbox36
+
+
+ 3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!LotSize_Item.Value
+
+
+
+
+
+
+ Textbox37
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!BaseUnitOfMeasure_Item.Value
+
+
+
+
+
+
+ Textbox38
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox39
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox40
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox41
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox42
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox43
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!RoutingNo_ItemCaption.Value
+
+
+
+
+
+
+ Textbox44
+
+
+ 3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!RoutingNo_Item.Value
+
+
+
+
+
+
+ Textbox45
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!RtngVersionCode.Value
+
+
+
+
+
+
+ Textbox46
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox47
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox48
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox49
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox50
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox51
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox52
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!OperationNo_RtngLineCaption.Value
+
+
+
+
+
+
+ Textbox53
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!Type_RtngLineCaption.Value
+
+
+
+
+
+
+ Textbox54
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!No_RtngLineCaption.Value
+
+
+
+
+
+
+ Textbox55
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!Description_ItemCaption.Value
+
+
+
+
+
+
+ Textbox56
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!SetupTime_RtngLineCaption.Value
+
+
+
+
+
+
+ Textbox57
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Parameters!RunTime_RtngLineCaption.Value
+
+
+
+
+
+
+ Textbox58
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!CostTimeCaption.Value
+
+
+
+
+
+
+ Textbox59
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!UnitCostCaption.Value
+
+
+
+
+
+
+ Textbox60
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!TotalCostCaption.Value
+
+
+
+
+
+
+ Textbox62
+
+
+ 2
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox63
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox64
+
+
+
+
+
+ Bottom
+ 5pt
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!TypeCaption.Value
+
+
+
+
+
+
+ Textbox65
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!NoCaption.Value
+
+
+
+
+
+
+ Textbox66
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!DescriptionCaption.Value
+
+
+
+
+
+
+ Textbox67
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!QuantityCaption.Value
+
+
+
+
+
+
+ Textbox68
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!BaseUnitOfMeasureCaption.Value
+
+
+
+
+
+
+ Textbox69
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!UnitCostCaption.Value
+
+
+
+
+
+
+ Textbox70
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!TotalCost1Caption.Value
+
+
+
+
+
+
+ Textbox71
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox72
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox73
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox74
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox75
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox77
+
+
+
+
+
+ Bottom
+ 5pt
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!OperationNo_RtngLine.Value
+
+
+
+
+
+
+ Textbox78
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!Type_RtngLine.Value
+
+
+
+
+
+
+ Textbox79
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!No_RtngLine.Value
+
+
+
+
+
+
+ Textbox80
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!Description_RtngLine.Value
+
+
+
+
+
+
+ Textbox81
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!SetupTime_RtngLine.Value
+
+
+
+
+
+
+ Textbox82
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!RunTime_RtngLine.Value
+
+
+
+
+
+
+ Textbox83
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!CostTime.Value
+
+
+
+
+
+
+ Textbox84
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProdUnitCost.Value
+
+
+
+
+
+
+ Textbox85
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProdTotalCost.Value
+
+
+
+
+
+
+ Textbox86
+
+
+ 2
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProdBOMLineLevelType.Value
+
+
+
+
+
+
+ Textbox87
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProdBOMLineLevelNo.Value
+
+
+
+
+
+
+ Textbox88
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProdBOMLineLevelDesc.Value
+
+
+
+
+
+
+ Textbox89
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!ProdBOMLineLevelQuantity.Value
+
+
+
+
+
+
+ Textbox90
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!CompItemBaseUOM.Value
+
+
+
+
+
+
+ Textbox91
+
+
+ 2
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!CompItemUnitCost.Value
+
+
+
+
+
+
+ Textbox92
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!CostTotal.Value
+
+
+
+
+
+
+ Textbox93
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox94
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox95
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox96
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox97
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox98
+
+
+ 8
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox99
+
+
+
+
+
+ Top
+ 5pt
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox100
+
+
+ 3
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox101
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox102
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox103
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox104
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox105
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Fields!TotalCost1Caption.Value
+
+
+
+
+
+
+ Textbox106
+
+
+ 3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Sum(Fields!CostTotal.Value)
+
+
+
+
+
+
+ Textbox107
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox108
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox109
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox110
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox111
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox112
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox113
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox114
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox115
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox116
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox117
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox118
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox119
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox120
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox121
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox122
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox123
+
+
+ 11
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox124
+
+
+
+
+
+ Top
+ 5pt
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox125
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =First(Fields!TotalCostCaption.Value)
+
+
+
+
+
+
+ Textbox126
+
+
+ 4
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Sum(Fields!ProdTotalCost.Value)
+
+
+
+
+
+
+ Textbox127
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox128
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox129
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox130
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox131
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox132
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox133
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox134
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Last(Fields!CostOfProductionCaption.Value)
+
+
+
+
+
+
+ Textbox135
+
+
+ 4
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Sum(Fields!FooterProdTotalCost.Value)
+
+
+
+
+
+
+ Textbox136
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox137
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Last(Fields!CostOfComponentsCaption.Value)
+
+
+
+
+
+
+ Textbox138
+
+
+ 4
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Sum(Fields!FooterCostTotal.Value)
+
+
+
+
+
+
+ Textbox139
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox140
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Last(Fields!SingleLevelMfgOverheadCostCaption.Value)
+
+
+
+
+
+
+ Textbox141
+
+
+ 4
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Sum(Fields!SingleLevelMfgOvhd.Value)
+
+
+
+
+
+
+ Textbox142
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox143
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox144
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox145
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox146
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox147
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox148
+
+
+
+
+
+
+
+ 0.17638cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox149
+
+
+ 11
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox150
+
+
+
+
+
+ Top
+ 5pt
+
+
+
+
+
+
+
+ 0.35278cm
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox151
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Textbox152
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Last(Fields!UnitCostCaption.Value)
+
+
+
+
+
+
+ Textbox153
+
+
+ 3
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+ =Sum(Fields!UnitCost_Item.Value)
+
+
+
+
+
+
+ Textbox154
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ =iif(Fields!ItemFilter.Value = "",true,false)
+
+ After
+ true
+
+
+
+ =iif(Fields!ItemFilter.Value = "",true,false)
+
+ After
+ true
+
+
+
+
+ =Fields!No_Item.Value
+
+
+ End
+
+
+
+
+ After
+ true
+
+
+ After
+ true
+
+
+ After
+ true
+
+
+ After
+ true
+
+
+ After
+ true
+
+
+ After
+ true
+
+
+
+
+ =Fields!InRouting.Value
+
+
+
+
+
+ =iif(Fields!InRouting.Value,false,true)
+
+ After
+ true
+
+
+
+ =iif(Fields!InRouting.Value,false,true)
+
+ After
+ true
+
+
+
+ =iif(Fields!InRouting.Value,false,true)
+
+ After
+
+
+
+
+ =Fields!InBOM.Value
+
+
+
+
+
+ =iif(Fields!InBOM.Value,false,true)
+
+ After
+ true
+
+
+
+ =iif(Fields!InBOM.Value,false,true)
+
+ After
+ true
+
+
+
+ =iif(Fields!InBOM.Value,false,true)
+
+ After
+
+
+
+ Detail
+
+
+
+
+ =iif(Fields!OperationNo_RtngLine.Value = "",true,false)
+
+
+
+
+ =iif(Fields!ShowLine.Value,false,true)
+
+
+
+ Detail_Collection
+ Output
+ true
+
+
+
+ =iif(Fields!TotalCost1Caption.Value = "",true,false)
+
+ Before
+
+
+
+ =iif(Fields!TotalCost1Caption.Value = "",true,false)
+
+ Before
+
+
+
+ =iif(Fields!TotalCost1Caption.Value = "",true,false)
+
+ Before
+ true
+
+
+
+ =iif(Fields!TotalCostCaption.Value = "",true,false)
+
+ Before
+ true
+
+
+
+ =iif(Fields!TotalCostCaption.Value = "",true,false)
+
+ Before
+
+
+
+
+
+ =iif(First(Fields!TotalCostCaption.Value) = "",true,false)
+
+ Before
+ true
+
+
+
+ =iif(First(Fields!TotalCostCaption.Value) = "",true,false)
+
+ Before
+ true
+
+
+
+
+ Before
+ true
+
+
+ Before
+ true
+
+
+ Before
+ true
+
+
+ Before
+ true
+
+
+ Before
+
+
+ Before
+ true
+
+
+
+
+
+ DataSet_Result
+ 8.52024cm
+ 18.15273cm
+
+
+
+ 8.52024cm
+
+
+ 18.15273cm
+
+
+ 1.52931cm
+ true
+ true
+
+
+ true
+
+
+
+
+ =Fields!DetailedCalculationCaption.Value
+
+
+
+
+
+
+ 20pt
+ 12.50762cm
+
+
+
+ true
+
+
+
+
+ =Fields!PageNoCaption.Value
+
+
+
+
+
+
+ 0.3595cm
+ 12.57817cm
+ 11pt
+ 5.10758cm
+ 1
+
+
+
+ true
+
+
+
+
+ =Fields!CalculateDate.Value
+
+
+
+
+
+
+ 0.75cm
+ 11pt
+ 14.34321cm
+ 2
+
+
+
+ true
+
+
+
+
+ =Fields!CompanyName.Value
+
+
+
+
+
+
+ 1.14125cm
+ 11pt
+ 18.15272cm
+ 3
+
+
+
+ true
+
+
+
+
+ =Fields!TodayFormatted.Value
+
+
+
+
+
+
+ 12.50762cm
+ 11pt
+ 5.64511cm
+ 4
+
+
+
+ true
+ true
+
+
+
+
+ =User!UserID
+
+
+
+
+
+
+ 0.75075cm
+ 14.41376cm
+ 11pt
+ 3.73896cm
+ 5
+
+ =iif(Fields!DetailedCalculationCaption.Value = "",true,false)
+
+ NoOutput
+
+
+
+
+
+
+
+ 29.7cm
+ 21cm
+ 1.76389cm
+ 1.05833cm
+ 1.05833cm
+ 1.48167cm
+ 1.27cm
+
+
+
+
+
+
+ String
+
+
+ Description_ItemCaption
+
+
+ Description_ItemCaption
+
+
+ String
+
+
+ LotSize_ItemCaption
+
+
+ LotSize_ItemCaption
+
+
+ String
+
+
+ No_ItemCaption
+
+
+ No_ItemCaption
+
+
+ String
+
+
+ ProductionBOMNo_ItemCaption
+
+
+ ProductionBOMNo_ItemCaption
+
+
+ String
+
+
+ RoutingNo_ItemCaption
+
+
+ RoutingNo_ItemCaption
+
+
+ String
+
+
+ Description_RtngLineCaption
+
+
+ Description_RtngLineCaption
+
+
+ String
+
+
+ No_RtngLineCaption
+
+
+ No_RtngLineCaption
+
+
+ String
+
+
+ OperationNo_RtngLineCaption
+
+
+ OperationNo_RtngLineCaption
+
+
+ String
+
+
+ RunTime_RtngLineCaption
+
+
+ RunTime_RtngLineCaption
+
+
+ String
+
+
+ SetupTime_RtngLineCaption
+
+
+ SetupTime_RtngLineCaption
+
+
+ String
+
+
+ Type_RtngLineCaption
+
+
+ Type_RtngLineCaption
+
+
+
+
+ 1
+ 11
+
+
+ 0
+ 0
+ Description_ItemCaption
+
+
+ 0
+ 1
+ LotSize_ItemCaption
+
+
+ 0
+ 2
+ No_ItemCaption
+
+
+ 0
+ 3
+ ProductionBOMNo_ItemCaption
+
+
+ 0
+ 4
+ RoutingNo_ItemCaption
+
+
+ 0
+ 5
+ Description_RtngLineCaption
+
+
+ 0
+ 6
+ No_RtngLineCaption
+
+
+ 0
+ 7
+ OperationNo_RtngLineCaption
+
+
+ 0
+ 8
+ RunTime_RtngLineCaption
+
+
+ 0
+ 9
+ SetupTime_RtngLineCaption
+
+
+ 0
+ 10
+ Type_RtngLineCaption
+
+
+
+
+ Public Function BlankZero(ByVal Value As Decimal)
+ if Value = 0 then
+ Return ""
+ end if
+ Return Value
+End Function
+
+Public Function BlankPos(ByVal Value As Decimal)
+ if Value > 0 then
+ Return ""
+ end if
+ Return Value
+End Function
+
+Public Function BlankZeroAndPos(ByVal Value As Decimal)
+ if Value >= 0 then
+ Return ""
+ end if
+ Return Value
+End Function
+
+Public Function BlankNeg(ByVal Value As Decimal)
+ if Value < 0 then
+ Return ""
+ end if
+ Return Value
+End Function
+
+Public Function BlankNegAndZero(ByVal Value As Decimal)
+ if Value <= 0 then
+ Return ""
+ end if
+ Return Value
+End Function
+
+
+ =User!Language
+ true
+ Cm
+ 0eeb6585-38ae-40f1-885b-8d50088d51b4
+
+
+
+
+ BaseUnitOfMeasure_Item
+
+
+ CalculateDate
+
+
+ CompanyName
+
+
+ Description_Item
+
+
+ DetailedCalculationCaption
+
+
+ ItemFilter
+
+
+ ItemFilterCaption
+
+
+ LotSize_Item
+
+
+ LotSize_ItemFormat
+
+
+ No_Item
+
+
+ PageNoCaption
+
+
+ PBOMVersionCode1
+
+
+ ProductionBOMNo_Item
+
+
+ RoutingNo_Item
+
+
+ RtngVersionCode
+
+
+ TodayFormatted
+
+
+ UnitCostCaption
+
+
+ CostTime
+
+
+ CostTimeFormat
+
+
+ CostTimeCaption
+
+
+ Description_RtngLine
+
+
+ InRouting
+
+
+ No_RtngLine
+
+
+ OperationNo_RtngLine
+
+
+ ProdTotalCost
+
+
+ ProdTotalCostFormat
+
+
+ ProdUnitCost
+
+
+ ProdUnitCostFormat
+
+
+ RunTime_RtngLine
+
+
+ RunTime_RtngLineFormat
+
+
+ SetupTime_RtngLine
+
+
+ SetupTime_RtngLineFormat
+
+
+ TotalCostCaption
+
+
+ Type_RtngLine
+
+
+ VersionCode_RtngLine
+
+
+ BaseUnitOfMeasureCaption
+
+
+ DescriptionCaption
+
+
+ InBOM
+
+
+ NoCaption
+
+
+ QuantityCaption
+
+
+ TotalCost1Caption
+
+
+ TypeCaption
+
+
+ CompItemBaseUOM
+
+
+ CompItemUnitCost
+
+
+ CompItemUnitCostFormat
+
+
+ CostTotal
+
+
+ CostTotalFormat
+
+
+ ProdBOMLineLevelDesc
+
+
+ ProdBOMLineLevelNo
+
+
+ ProdBOMLineLevelQuantity
+
+
+ ProdBOMLineLevelQuantityFormat
+
+
+ ProdBOMLineLevelType
+
+
+ ShowLine
+
+
+ Number_IntegerLine
+
+
+ CostOfComponentsCaption
+
+
+ CostOfProductionCaption
+
+
+ FooterCostTotal
+
+
+ FooterCostTotalFormat
+
+
+ FooterProdTotalCost
+
+
+ FooterProdTotalCostFormat
+
+
+ FormatCostTotal
+
+
+ FormatCostTotalFormat
+
+
+ SingleLevelMfgOverheadCostCaption
+
+
+ SingleLevelMfgOvhd
+
+
+ SingleLevelMfgOvhdFormat
+
+
+ TotalProdTotalCost
+
+
+ TotalProdTotalCostFormat
+
+
+ UnitCost_Item
+
+
+ UnitCost_ItemFormat
+
+
+
+ DataSource
+
+
+
+
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al
index 2c00d6901f..764cf25af8 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al
@@ -6,6 +6,7 @@ namespace Microsoft.Manufacturing.Subcontracting;
using Microsoft.Manufacturing.Document;
using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
using Microsoft.Purchases.Vendor;
tableextension 99001506 "Subc. ProdOrderRtngLine Ext." extends "Prod. Order Routing Line"
@@ -30,4 +31,92 @@ tableextension 99001506 "Subc. ProdOrderRtngLine Ext." extends "Prod. Order Rout
ToolTip = 'Specifies whether the Work Center Group is set up with a Vendor for Subcontracting.';
}
}
+
+ trigger OnBeforeDelete()
+ begin
+ CheckForSubcontractingPurchaseLineTypeMismatchOnDeleteLine();
+ end;
+
+ var
+ PurchaseLineTypeMismatchErr: Label 'There is at least one Purchase Line (%1) which is linked to Production Order Routing Line (%2). The Purchase Line cannot be of type %3 for this Production Order Routing Line. Please delete the Purchase line first before changing the Production Order Routing Line.',
+ Comment = '%1 = PurchaseLine Record Id, %2 = Production Order Routing Line Record Id, %3 = Purchase Line Type';
+ PurchaseLineTypeMismatchNotLastOperationErr: Label 'There is at least one Purchase Line (%1) which is linked to Production Order Routing Line (%2). Because the Production Order Routing Line is the last operation after delete, the Purchase Line cannot be of type Not Last Operation. Please delete the Purchase line first before changing the Production Order Routing Line.',
+ Comment = '%1 = PurchaseLine Record Id, %2 = Previous Production Order Routing Line Record Id';
+
+ ///
+ /// Checks if the prod. order routing line has a linked purchase order line. In case of mismatching last operation or not last operation on changing
+ /// the prod. order routing line order an error will be thrown if the type does not match with purchase line
+ ///
+ internal procedure CheckForSubcontractingPurchaseLineTypeMismatch()
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ PurchLine: Record "Purchase Line";
+ begin
+ if Status <> "Production Order Status"::Released then
+ exit;
+
+ ProdOrderLine.SetLoadFields(SystemId);
+ ProdOrderLine.SetRange(Status, Status);
+ ProdOrderLine.SetRange("Prod. Order No.", "Prod. Order No.");
+ ProdOrderLine.SetRange("Routing Reference No.", "Routing Reference No.");
+ ProdOrderLine.SetRange("Routing No.", "Routing No.");
+ if ProdOrderLine.Find('-') then
+ repeat
+ PurchLine.SetLoadFields(SystemId);
+ PurchLine.SetCurrentKey(
+ "Document Type", Type, "Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchLine.SetRange("Document Type", PurchLine."Document Type"::Order);
+ PurchLine.SetRange(Type, PurchLine.Type::Item);
+ PurchLine.SetRange("Prod. Order No.", "Prod. Order No.");
+ PurchLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ PurchLine.SetRange("Operation No.", "Operation No.");
+ if "Next Operation No." <> '' then begin
+ PurchLine.SetRange("Subc. Purchase Line Type", "Subc. Purchase Line Type"::LastOperation);
+ if PurchLine.FindFirst() then
+ Error(PurchaseLineTypeMismatchErr, PurchLine.RecordId(), RecordId(), Format("Subc. Purchase Line Type"::LastOperation));
+ end else begin
+ PurchLine.SetRange("Subc. Purchase Line Type", "Subc. Purchase Line Type"::NotLastOperation);
+ if PurchLine.FindFirst() then
+ Error(PurchaseLineTypeMismatchErr, PurchLine.RecordId(), RecordId(), Format("Subc. Purchase Line Type"::NotLastOperation));
+ end;
+ until ProdOrderLine.Next() = 0;
+ end;
+
+ local procedure CheckForSubcontractingPurchaseLineTypeMismatchOnDeleteLine()
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ PurchLine: Record "Purchase Line";
+ PrevProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ if Status <> "Production Order Status"::Released then
+ exit;
+ ProdOrderLine.SetLoadFields(SystemId);
+ ProdOrderLine.SetRange(Status, Status);
+ ProdOrderLine.SetRange("Prod. Order No.", "Prod. Order No.");
+ ProdOrderLine.SetRange("Routing Reference No.", "Routing Reference No.");
+ ProdOrderLine.SetRange("Routing No.", "Routing No.");
+ if ProdOrderLine.Find('-') then
+ repeat
+ PurchLine.SetLoadFields(SystemId);
+ PurchLine.SetCurrentKey(
+ "Document Type", Type, "Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchLine.SetRange("Document Type", PurchLine."Document Type"::Order);
+ PurchLine.SetRange(Type, PurchLine.Type::Item);
+ PurchLine.SetRange("Prod. Order No.", "Prod. Order No.");
+ PurchLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ if "Next Operation No." = '' then begin
+ PrevProdOrderRoutingLine := Rec;
+ PrevProdOrderRoutingLine.SetRecFilter();
+ PrevProdOrderRoutingLine.SetFilter("Operation No.", "Previous Operation No.");
+ PrevProdOrderRoutingLine.SetLoadFields(SystemId);
+ if PrevProdOrderRoutingLine.FindSet() then
+ repeat
+ PurchLine.SetRange("Operation No.", PrevProdOrderRoutingLine."Operation No.");
+ PurchLine.SetRange("Subc. Purchase Line Type", "Subc. Purchase Line Type"::NotLastOperation);
+ if PurchLine.FindFirst() then
+ Error(PurchaseLineTypeMismatchNotLastOperationErr, PurchLine.RecordId(), PrevProdOrderRoutingLine.RecordId());
+ until PrevProdOrderRoutingLine.Next() = 0;
+ end;
+ until ProdOrderLine.Next() = 0;
+ end;
}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Purchase/SubcPurchaseLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Purchase/SubcPurchaseLine.TableExt.al
index 095ab4a70e..4a2001a08f 100644
--- a/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Purchase/SubcPurchaseLine.TableExt.al
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Purchase/SubcPurchaseLine.TableExt.al
@@ -4,16 +4,33 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Foundation.UOM;
using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
using Microsoft.Manufacturing.Document;
using Microsoft.Manufacturing.Routing;
using Microsoft.Manufacturing.WorkCenter;
using Microsoft.Purchases.Document;
+using Microsoft.Warehouse.Document;
tableextension 99001512 "Subc. Purchase Line" extends "Purchase Line"
{
fields
{
+ modify("Operation No.")
+ {
+ trigger OnBeforeValidate()
+ begin
+ SetSubcontractingLineType();
+ end;
+ }
+ modify("Work Center No.")
+ {
+ trigger OnAfterValidate()
+ begin
+ SetSubcontractingLineType();
+ end;
+ }
field(99001543; "Subc. Prod. Order No."; Code[20])
{
Caption = 'Prod. Order No. (Sub)';
@@ -59,11 +76,34 @@ tableextension 99001512 "Subc. Purchase Line" extends "Purchase Line"
Editable = false;
TableRelation = "Work Center";
}
+ field(99001549; "Subc. Purchase Line Type"; Enum "Subc. Purchase Line Type")
+ {
+ Caption = 'Subcontracting Line Type';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001550; "Whse. Outstanding Quantity"; Decimal)
+ {
+ AccessByPermission = TableData Location = R;
+ AutoFormatType = 0;
+ BlankZero = true;
+ CalcFormula = sum("Warehouse Receipt Line"."Qty. Outstanding" where("Source Type" = const(39),
+#pragma warning disable AL0603
+ "Source Subtype" = field("Document Type"),
+#pragma warning restore
+ "Source No." = field("Document No."),
+ "Source Line No." = field("Line No.")));
+ Caption = 'Whse. Outstanding Quantity';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ }
}
procedure GetQuantityPerUOM(): Decimal
var
ItemUnitofMeasure: Record "Item Unit of Measure";
begin
+ ItemUnitofMeasure.SetLoadFields("Qty. per Unit of Measure");
ItemUnitofMeasure.Get("No.", "Unit of Measure Code");
exit(ItemUnitofMeasure."Qty. per Unit of Measure");
end;
@@ -72,7 +112,80 @@ tableextension 99001512 "Subc. Purchase Line" extends "Purchase Line"
var
ItemUnitofMeasure: Record "Item Unit of Measure";
begin
+ ItemUnitofMeasure.SetLoadFields("Qty. per Unit of Measure");
ItemUnitofMeasure.Get("No.", "Unit of Measure Code");
exit(Round(Quantity * ItemUnitofMeasure."Qty. per Unit of Measure", 0.00001));
end;
+
+ ///
+ /// Calculates the base quantity from a quantity
+ ///
+ /// Quantity from which the base quantity is calculated
+ /// Field on which the calculation is based
+ /// Name of the field from which the calculation starts
+ /// Name of the field to which the calculation is applied
+ /// Calculated base quantity
+ internal procedure CalcBaseQtyFromQuantity(SourceQuantity: Decimal; BasedOnField: Text; FromFieldName: Text; ToFieldName: Text) BaseQuantityToReturn: Decimal
+ var
+ Item: Record Item;
+ UOMMgt: Codeunit "Unit of Measure Management";
+ QtyPerUoM: Decimal;
+ begin
+ Testfield(Type, "Purchase Line Type"::Item);
+ Item.Get(Rec."No.");
+ QtyPerUoM := UOMMgt.GetQtyPerUnitOfMeasure(Item, Rec."Unit of Measure Code");
+ BaseQuantityToReturn := UOMMgt.CalcBaseQty(Rec."No.", Rec."Variant Code", Rec."Unit of Measure Code", SourceQuantity, QtyPerUoM, Rec."Qty. Rounding Precision (Base)", BasedOnField, FromFieldName, ToFieldName);
+ end;
+
+ ///
+ /// Determine if the purchase line is a subcontracting line
+ ///
+ /// The production order line to check
+ /// True if the line is a subcontracting line, false otherwise
+ internal procedure IsSubcontractingLine(var ProdOrderLine: Record "Prod. Order Line"): Boolean
+ var
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ IsValidLine: Boolean;
+ begin
+ if Rec."Operation No." = '' then
+ exit(false);
+ ProductionOrder.SetLoadFields("Source Type");
+ ProdOrderRoutingLine.SetLoadFields(SystemId);
+ IsValidLine := ProdOrderLine.Get("Production Order Status"::Released, Rec."Prod. Order No.", Rec."Prod. Order Line No.");
+ IsValidLine := IsValidLine and ProductionOrder.Get("Production Order Status"::Released, Rec."Prod. Order No.");
+ IsValidLine := IsValidLine and ProdOrderRoutingLine.Get("Production Order Status"::Released, Rec."Prod. Order No.", Rec."Routing Reference No.", Rec."Routing No.", Rec."Operation No.");
+ IsValidLine := IsValidLine and (ProductionOrder."Source Type" <> "Prod. Order Source Type"::Family);
+ exit(IsValidLine);
+ end;
+
+ ///
+ /// Determines if the purchase line is a subcontracting line with the last operation
+ ///
+ /// The production order line to check
+ /// True if the line is a subcontracting line with the last operation, false otherwise
+ internal procedure IsSubcontractingLineWithLastOperation(var ProdOrderLine: Record "Prod. Order Line"): Boolean
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ IsValidLine: Boolean;
+ begin
+ ProdOrderRoutingLine.SetLoadFields("Next Operation No.");
+ IsValidLine := ProdOrderRoutingLine.Get("Production Order Status"::Released, Rec."Prod. Order No.", Rec."Routing Reference No.", Rec."Routing No.", Rec."Operation No.");
+ IsValidLine := IsValidLine and (ProdOrderRoutingLine."Next Operation No." = '');
+ exit(IsSubcontractingLine(ProdOrderLine) and IsValidLine);
+ end;
+
+ local procedure SetSubcontractingLineType()
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ case true of
+ not IsSubcontractingLine(ProdOrderLine):
+ Rec."Subc. Purchase Line Type" := Rec."Subc. Purchase Line Type"::None;
+ IsSubcontractingLineWithLastOperation(ProdOrderLine):
+ Rec."Subc. Purchase Line Type" := Rec."Subc. Purchase Line Type"::LastOperation;
+ else
+ Rec."Subc. Purchase Line Type" := Rec."Subc. Purchase Line Type"::NotLastOperation;
+ end;
+ end;
}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Warehouse/SubcPostedWhseReceiptLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Warehouse/SubcPostedWhseReceiptLine.TableExt.al
new file mode 100644
index 0000000000..118d95784c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Warehouse/SubcPostedWhseReceiptLine.TableExt.al
@@ -0,0 +1,21 @@
+
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting;
+
+using Microsoft.Warehouse.History;
+
+tableextension 99001526 "Subc. Posted Whse Receipt Line" extends "Posted Whse. Receipt Line"
+{
+ fields
+ {
+ field(99001549; "Subc. Purchase Line Type"; Enum "Subc. Purchase Line Type")
+ {
+ Caption = 'Subcontracting Line Type';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Warehouse/SubcWarehouseReceiptLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Warehouse/SubcWarehouseReceiptLine.TableExt.al
new file mode 100644
index 0000000000..1fb2c09597
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Process/Tableextensions/Warehouse/SubcWarehouseReceiptLine.TableExt.al
@@ -0,0 +1,21 @@
+
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting;
+
+using Microsoft.Warehouse.Document;
+
+tableextension 99001525 "Subc. Warehouse Receipt Line" extends "Warehouse Receipt Line"
+{
+ fields
+ {
+ field(99001549; "Subc. Purchase Line Type"; Enum "Subc. Purchase Line Type")
+ {
+ Caption = 'Subcontracting Line Type';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/app.json b/src/Apps/W1/Subcontracting/Test/app.json
index 94e1791529..01ef670fa5 100644
--- a/src/Apps/W1/Subcontracting/Test/app.json
+++ b/src/Apps/W1/Subcontracting/Test/app.json
@@ -32,7 +32,11 @@
"idRanges": [
{
"from": 139980,
- "to": 139999
+ "to": 140009
+ },
+ {
+ "from": 149900,
+ "to": 149930
}
],
"features": [
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcProdOrderCheckLib.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcProdOrderCheckLib.Codeunit.al
index af9da174fe..33515efda1 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcProdOrderCheckLib.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcProdOrderCheckLib.Codeunit.al
@@ -18,6 +18,7 @@ codeunit 139987 "Subc. ProdOrderCheckLib"
{
var
Assert: Codeunit Assert;
+ ProdOrderRefreshed: Boolean;
DescriptionMismatchOnOperationLbl: Label 'Description mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
DirectUnitCostMismatchOnOperationLbl: Label 'Direct Unit Cost mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
DueDateMismatchOnLineLbl: Label 'Due Date mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
@@ -41,12 +42,11 @@ codeunit 139987 "Subc. ProdOrderCheckLib"
UnitCostCalculationMismatchOnOperationLbl: Label 'Unit Cost Calculation mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
WorkCenterGroupCodeMismatchOnOperationLbl: Label 'Work Center Group Code mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
WorkCenterNoMismatchOnOperationLbl: Label 'Work Center No. mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
- ProdOrderRefreshed: Boolean;
procedure CreateTempProdOrderComponentFromSetup(var TempProdOrderComponent: Record "Prod. Order Component" temporary; PurchLine: Record "Purchase Line")
var
- SubManagementSetup: Record "Subc. Management Setup";
TempProdOrderComponent2: Record "Prod. Order Component" temporary;
+ SubManagementSetup: Record "Subc. Management Setup";
LineNo: Integer;
begin
// Fill temporary Production Order Component from setup configuration
@@ -288,8 +288,8 @@ codeunit 139987 "Subc. ProdOrderCheckLib"
procedure CreateTempProdOrderComponentFromBOM(var TempProdOrderComponent: Record "Prod. Order Component" temporary; BOMNo: Code[20]; PurchLine: Record "Purchase Line")
var
- SubManagementSetup: Record "Subc. Management Setup";
ProductionBOMLine: Record "Production BOM Line";
+ SubManagementSetup: Record "Subc. Management Setup";
LineNo: Integer;
begin
// Create temporary Production Order Components based on BOM lines
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcWarehouseLibrary.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcWarehouseLibrary.Codeunit.al
new file mode 100644
index 0000000000..f593a5b894
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Libraries/SubcWarehouseLibrary.Codeunit.al
@@ -0,0 +1,870 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.Planning;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Request;
+using Microsoft.Warehouse.Structure;
+using Microsoft.Warehouse.Worksheet;
+
+codeunit 149908 "Subc. Warehouse Library"
+{
+ // [FEATURE] Subcontracting Warehouse Test Library
+ // Consolidated data creation functions for warehouse tests to avoid code duplication
+
+ var
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryItemTracking: Codeunit "Library - Item Tracking";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibraryUtility: Codeunit "Library - Utility";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+
+ // ========================================
+ // MANUFACTURING SETUP FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates and calculates needed work and machine centers.
+ ///
+ /// The array of work centers which will be created
+ /// The array of machine centers which will be created
+ /// Indicates if the work centers are subcontracting work centers
+ procedure CreateAndCalculateNeededWorkAndMachineCenter(var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center"; Subcontracting: Boolean)
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ Location: Record Location;
+ Vendor1: Record Vendor;
+ Vendor2: Record Vendor;
+ WorkCenterNo: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ if Subcontracting then begin
+ LibraryPurchase.CreateSubcontractor(Vendor1);
+ Vendor1."Subcontr. Location Code" := LibraryWarehouse.CreateLocation(Location);
+ Vendor1.Modify(true);
+ LibraryPurchase.CreateSubcontractor(Vendor2);
+ Vendor2."Subcontr. Location Code" := LibraryWarehouse.CreateLocation(Location);
+ Vendor2.Modify(true);
+ end;
+
+ // Create first work center
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter[1], LibraryRandom.RandDec(10, 2));
+ WorkCenterNo := WorkCenter[1]."No.";
+
+ if Subcontracting then begin
+ WorkCenter[1]."Subcontractor No." := Vendor1."No.";
+ WorkCenter[1].Modify(true);
+ end;
+
+ // Create machine centers
+ LibraryManufacturing.CreateMachineCenterWithCalendar(
+ MachineCenter[1], WorkCenterNo, LibraryRandom.RandDec(10, 1));
+
+ LibraryManufacturing.CreateMachineCenterWithCalendar(
+ MachineCenter[2], WorkCenterNo, LibraryRandom.RandDec(10, 1));
+
+ // Create second work center
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter[2], LibraryRandom.RandDec(10, 2));
+
+ if Subcontracting then begin
+ WorkCenter[2]."Subcontractor No." := Vendor2."No.";
+ WorkCenter[2].Modify(true);
+ end;
+ end;
+
+ ///
+ /// Creates and calculates needed work and machine centers for the same vendor
+ ///
+ /// The Work Center which has been created
+ /// The Machine Center which has been created
+ /// Indicates if the work center is a subcontracting work center
+ procedure CreateAndCalculateNeededWorkAndMachineCenterSameVendor(var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center"; Subcontracting: Boolean)
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ Vendor: Record Vendor;
+ WorkCenterNo: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ // Create single vendor for both work centers
+ if Subcontracting then
+ LibraryPurchase.CreateSubcontractor(Vendor);
+
+ // Create first work center
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter[1], LibraryRandom.RandDec(10, 2));
+ WorkCenterNo := WorkCenter[1]."No.";
+
+ if Subcontracting then begin
+ WorkCenter[1]."Subcontractor No." := Vendor."No.";
+ WorkCenter[1].Modify(true);
+ end;
+
+ // Create machine centers for first work center
+ LibraryManufacturing.CreateMachineCenterWithCalendar(
+ MachineCenter[1], WorkCenterNo, LibraryRandom.RandDec(10, 1));
+
+ LibraryManufacturing.CreateMachineCenterWithCalendar(
+ MachineCenter[2], WorkCenterNo, LibraryRandom.RandDec(10, 1));
+
+ // Create second work center with same vendor
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter[2], LibraryRandom.RandDec(10, 2));
+
+ if Subcontracting then begin
+ WorkCenter[2]."Subcontractor No." := Vendor."No.";
+ WorkCenter[2].Modify(true);
+ end;
+ end;
+
+ ///
+ /// Creates an item with a production BOM and routing, where the routing has both in-house and subcontracting operations. The subcontracting operations are linked to the provided work centers and machine centers.
+ /// The item created is a finished good item which can be used for end-to-end testing of the subcontracting flow from production order creation to warehouse receipt.
+ /// This function is used to set up the data for testing the scenario where a production order has both in-house and subcontracting operations, and the impact on warehouse receipts when posting the production order.
+ ///
+ /// The item record which will be created
+ /// The array of work centers which will be linked to the subcontracting operations in the routing
+ /// The array of machine centers which will be linked to the in-house operations in the routing
+ procedure CreateItemForProductionIncludeRoutingAndProdBOM(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ Item2: Record Item;
+ Item3: Record Item;
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMNo: Code[20];
+ RoutingNo: Code[20];
+ begin
+ // Create routing
+ RoutingNo := CreateRouting(MachineCenter, WorkCenter);
+
+ // Create component items
+ LibraryInventory.CreateItem(Item2);
+ LibraryInventory.CreateItem(Item3);
+
+ // Create production BOM
+ ProductionBOMNo := LibraryManufacturing.CreateCertifProdBOMWithTwoComp(
+ ProductionBOMHeader, Item2."No.", Item3."No.", 1);
+
+ // Create finished item
+ LibraryManufacturing.CreateItemManufacturing(
+ Item, "Costing Method"::FIFO, LibraryRandom.RandDec(10, 2),
+ "Reordering Policy"::" ", "Flushing Method"::Backward, RoutingNo, ProductionBOMNo);
+ end;
+
+ ///
+ /// Creates a routing with the specified machine centers and work centers.
+ ///
+ /// The array of machine centers to be used in the routing
+ /// The array of work centers to be used in the routing
+ /// The routing number of the created routing
+ procedure CreateRouting(var MachineCenter: array[2] of Record "Machine Center"; var WorkCenter: array[2] of Record "Work Center"): Code[20]
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+
+ // Create routing lines
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Machine Center", MachineCenter[1]."No.");
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '20', RoutingLine.Type::"Machine Center", MachineCenter[2]."No.");
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '30', RoutingLine.Type::"Work Center", WorkCenter[1]."No.");
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '40', RoutingLine.Type::"Work Center", WorkCenter[2]."No.");
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ exit(RoutingHeader."No.");
+ end;
+
+ ///
+ /// Updates the production BOM and routing with the specified routing link.
+ ///
+ /// The item record which will be updated
+ /// The work center number to be linked to the routing
+ procedure UpdateProdBomAndRoutingWithRoutingLink(Item: Record Item; WorkCenterNo: Code[20])
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ RoutingLink: Record "Routing Link";
+ begin
+ // Create routing link
+ LibraryManufacturing.CreateRoutingLink(RoutingLink);
+
+ // Update routing
+ RoutingHeader.Get(Item."Routing No.");
+ RoutingHeader.Validate(Status, RoutingHeader.Status::New);
+ RoutingHeader.Modify(true);
+
+ RoutingLine.SetRange("Routing No.", RoutingHeader."No.");
+ RoutingLine.SetRange(Type, RoutingLine.Type::"Work Center");
+ RoutingLine.SetRange("No.", WorkCenterNo);
+ if RoutingLine.FindFirst() then begin
+ RoutingLine.Validate("Routing Link Code", RoutingLink.Code);
+ RoutingLine.Modify(true);
+ end;
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ // Update production BOM
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ if ProductionBOMLine.FindLast() then begin
+ ProductionBOMLine.Validate("Routing Link Code", RoutingLink.Code);
+ ProductionBOMLine.Modify(true);
+ end;
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ ///
+ /// Updates the production BOM and routing with the specified routing links for both operations.
+ ///
+ /// The item record which will be updated
+ /// The array of work centers to be linked to the routing
+ procedure UpdateProdBomAndRoutingWithRoutingLinkForBothOperations(Item: Record Item; var WorkCenter: array[2] of Record "Work Center")
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ RoutingLink1: Record "Routing Link";
+ RoutingLink2: Record "Routing Link";
+ begin
+ // Create routing links for both operations
+ LibraryManufacturing.CreateRoutingLink(RoutingLink1);
+ LibraryManufacturing.CreateRoutingLink(RoutingLink2);
+
+ // Update routing
+ RoutingHeader.Get(Item."Routing No.");
+ RoutingHeader.Validate(Status, RoutingHeader.Status::New);
+ RoutingHeader.Modify(true);
+
+ // Update first operation (intermediate)
+ RoutingLine.SetRange("Routing No.", RoutingHeader."No.");
+ RoutingLine.SetRange(Type, RoutingLine.Type::"Work Center");
+ RoutingLine.SetRange("No.", WorkCenter[1]."No.");
+ if RoutingLine.FindFirst() then begin
+ RoutingLine.Validate("Routing Link Code", RoutingLink1.Code);
+ RoutingLine.Modify(true);
+ end;
+
+ // Update second operation (last)
+ RoutingLine.SetRange("No.", WorkCenter[2]."No.");
+ if RoutingLine.FindFirst() then begin
+ RoutingLine.Validate("Routing Link Code", RoutingLink2.Code);
+ RoutingLine.Modify(true);
+ end;
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ // Update production BOM
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ if ProductionBOMLine.FindFirst() then begin
+ ProductionBOMLine.Validate("Routing Link Code", RoutingLink1.Code);
+ ProductionBOMLine.Modify(true);
+ end;
+ if ProductionBOMLine.FindLast() then begin
+ ProductionBOMLine.Validate("Routing Link Code", RoutingLink2.Code);
+ ProductionBOMLine.Modify(true);
+ end;
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ // ========================================
+ // LOCATION & WAREHOUSE SETUP FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates a location with warehouse handling enabled.
+ ///
+ /// The location record which will be created and updated
+ procedure CreateLocationWithWarehouseHandling(var Location: Record Location)
+ begin
+ LibraryWarehouse.CreateLocationWMS(Location, false, true, false, true, false);
+ Location."Require Receive" := true;
+ Location."Require Put-away" := true;
+ Location.Modify(true);
+ LibraryInventory.UpdateInventoryPostingSetup(Location);
+ end;
+
+ ///
+ /// Creates a location with warehouse handling enabled and require receive only (not put-away).
+ /// This is used to test scenarios where the location requires a warehouse receipt but does not require
+ /// a warehouse put-away. The expected behavior in this case is that when receiving into this location, a warehouse receipt will be created,
+ /// but no put-away will be required and the item will be received directly into the location without needing to be put away to another location.
+ /// This allows testing of the system's handling of warehouse receipts when put-away is not required.
+ ///
+ /// The location record which will be created and updated
+ procedure CreateLocationWithRequireReceiveOnly(var Location: Record Location)
+ begin
+ LibraryWarehouse.CreateLocationWMS(Location, false, false, false, false, false);
+ Location."Require Receive" := true;
+ Location."Require Put-away" := false;
+ Location.Modify(true);
+ LibraryInventory.UpdateInventoryPostingSetup(Location);
+ end;
+
+ ///
+ /// Creates a location with bin mandatory enabled only.
+ ///
+ /// The location record which will be created and updated
+ procedure CreateLocationWithBinMandatoryOnly(var Location: Record Location)
+ begin
+ LibraryWarehouse.CreateLocationWMS(Location, true, false, false, false, false);
+ Location."Require Receive" := false;
+ Location."Require Put-away" := false;
+ Location.Modify(true);
+ LibraryInventory.UpdateInventoryPostingSetup(Location);
+ end;
+
+ ///
+ /// Creates a location with warehouse handling enabled and bin mandatory. This is used to test scenarios where the location requires a warehouse receipt and put-away,
+ /// and also requires that items be placed in bins within the location.
+ ///
+ /// The location record which will be created and updated
+ procedure CreateLocationWithWarehouseHandlingAndBinMandatory(var Location: Record Location)
+ begin
+ // Creates location with Bin Mandatory = true, Require Receive = true, Require Put-away = true
+ // This creates Take/Place warehouse activity lines with Bin Code
+ LibraryWarehouse.CreateLocationWMS(Location, true, true, false, true, false);
+ Location."Require Receive" := true;
+ Location."Require Put-away" := true;
+ Location.Modify(true);
+ LibraryInventory.UpdateInventoryPostingSetup(Location);
+ end;
+
+ ///
+ /// Creates a location with warehouse handling enabled and bins for both receiving and put-away.
+ ///
+ /// The location record which will be created and updated
+ /// The bin record which will be created and updated for receiving
+ /// The bin record which will be created and updated for put-away
+ procedure CreateLocationWithWarehouseHandlingAndBins(var Location: Record Location; var ReceiveBin: Record Bin; var PutAwayBin: Record Bin)
+ begin
+ // Creates location with Bin Mandatory = true, Require Receive = true, Require Put-away = true
+ // Sets up both Receive Bin (for warehouse receipt) and Default Bin (for put-away destination)
+ CreateLocationWithWarehouseHandlingAndBinMandatory(Location);
+
+ // Create receive bin - used when posting warehouse receipt
+ LibraryWarehouse.CreateBin(ReceiveBin, Location.Code, 'RECEIVE', '', '');
+ Location.Validate("Receipt Bin Code", ReceiveBin.Code);
+
+ // Create put-away bin - destination for put-away Place line
+ LibraryWarehouse.CreateBin(PutAwayBin, Location.Code, 'PUTAWAY', '', '');
+ Location.Validate("Default Bin Code", PutAwayBin.Code);
+
+ Location.Modify(true);
+ end;
+
+ // ========================================
+ // PRODUCTION ORDER FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates and refreshes a production order with the specified parameters. This function is used to set up production orders for testing scenarios that involve production orders and their impact on warehouse receipts.
+ ///
+ /// The production order record which will be created and updated
+ /// The status of the production order
+ /// The source type of the production order
+ /// The source number of the production order
+ /// The quantity of the production order
+ /// The location code of the production order
+ procedure CreateAndRefreshProductionOrder(var ProductionOrder: Record "Production Order"; Status: Enum "Production Order Status"; SourceType: Enum "Prod. Order Source Type"; SourceNo: Code[20]; Quantity: Decimal; LocationCode: Code[10])
+ begin
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, Status, SourceType, SourceNo, Quantity);
+ ProductionOrder.Validate("Location Code", LocationCode);
+ ProductionOrder.Modify(true);
+ LibraryManufacturing.RefreshProdOrder(ProductionOrder, false, true, true, true, false);
+ end;
+
+ ///
+ /// Updates the subcontracting management setup with a labor requirement worksheet template and name. This is used to set up the subcontracting management parameters for testing scenarios that involve subcontracting and the use of labor requirement worksheets in the subcontracting process.
+ ///
+ procedure UpdateSubMgmtSetupWithReqWkshTemplate()
+ begin
+ SubcLibraryMfgManagement.CreateLaborReqWkshTemplateAndNameAndUpdateSetup();
+ end;
+
+ // ========================================
+ // PURCHASE ORDER FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates a subcontracting purchase order from a production order routing line with the specified routing number and work center number, and finds the created purchase line.
+ /// This function is used to set up subcontracting purchase orders for testing scenarios that involve the creation of subcontracting purchase orders from production order routings and
+ /// their impact on warehouse receipts.
+ ///
+ /// The routing number of the production order
+ /// The work center number of the production order
+ /// The purchase line record which will be created and updated
+ procedure CreateSubcontractingOrderFromProdOrderRouting(RoutingNo: Code[20]; WorkCenterNo: Code[20]; var PurchaseLine: Record "Purchase Line")
+ var
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ begin
+ ProdOrderRtngLine.SetRange("Routing No.", RoutingNo);
+ ProdOrderRtngLine.SetRange(Type, ProdOrderRtngLine.Type::"Work Center");
+ ProdOrderRtngLine.SetRange("Work Center No.", WorkCenterNo);
+ ProdOrderRtngLine.FindFirst();
+
+ SubcPurchaseOrderCreator.CreateSubcontractingPurchaseOrderFromRoutingLine(ProdOrderRtngLine);
+
+ // Find the created purchase line
+ PurchaseLine.SetRange("Routing No.", RoutingNo);
+ PurchaseLine.SetRange("Work Center No.", WorkCenterNo);
+ PurchaseLine.FindFirst();
+ end;
+
+ ///
+ /// Creates subcontracting purchase orders from worksheet lines for the specified production order.
+ ///
+ /// The production order number used to filter worksheet and resulting purchase lines
+ /// The purchase header record which will be found for the created purchase order
+ procedure CreateSubcontractingOrdersViaWorksheet(ProductionOrderNo: Code[20]; var PurchaseHeader: Record "Purchase Header")
+ var
+ PurchaseLine: Record "Purchase Line";
+ RequisitionLine: Record "Requisition Line";
+ SubMgmtSetup: Record "Subc. Management Setup";
+ CalculateSubContract: Report "Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ begin
+ // Get worksheet template and batch from setup
+ SubMgmtSetup.Get();
+
+ // Initialize requisition line for the Calculate Subcontracts report
+ RequisitionLine."Worksheet Template Name" := SubMgmtSetup."Subcontracting Template Name";
+ RequisitionLine."Journal Batch Name" := SubMgmtSetup."Subcontracting Batch Name";
+
+ // Calculate subcontracting lines to fill the worksheet
+ CalculateSubContract.SetWkShLine(RequisitionLine);
+ CalculateSubContract.UseRequestPage(false);
+ CalculateSubContract.RunModal();
+
+ // Find requisition lines for the production order
+ RequisitionLine.SetRange("Worksheet Template Name", SubMgmtSetup."Subcontracting Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", SubMgmtSetup."Subcontracting Batch Name");
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrderNo);
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+
+ // Create purchase orders from the worksheet - combines lines for same vendor into one PO
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+
+ // Find the created purchase header
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrderNo);
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ end;
+
+ // ========================================
+ // WAREHOUSE DOCUMENT FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates a warehouse receipt from a released purchase order.
+ ///
+ /// The purchase header record that is released and used as source
+ /// The warehouse receipt header record that will be found after creation
+ procedure CreateWarehouseReceiptFromPurchaseOrder(var PurchaseHeader: Record "Purchase Header"; var WarehouseReceiptHeader: Record "Warehouse Receipt Header")
+ var
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ begin
+ LibraryPurchase.ReleasePurchaseDocument(PurchaseHeader);
+ LibraryWarehouse.CreateWhseReceiptFromPO(PurchaseHeader);
+
+ WarehouseReceiptLine.SetRange("Source Document", WarehouseReceiptLine."Source Document"::"Purchase Order");
+ WarehouseReceiptLine.SetRange("Source No.", PurchaseHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ WarehouseReceiptHeader.Get(WarehouseReceiptLine."No.");
+ end;
+
+ ///
+ /// Creates a warehouse receipt header and populates it using get source documents for the given location.
+ ///
+ /// The warehouse receipt header record which will be created and populated
+ /// The location code used to retrieve source documents
+ procedure CreateWarehouseReceiptUsingGetSourceDocuments(var WarehouseReceiptHeader: Record "Warehouse Receipt Header"; LocationCode: Code[10])
+ var
+ WarehouseSourceFilter: Record "Warehouse Source Filter";
+ begin
+ LibraryWarehouse.CreateWarehouseReceiptHeader(WarehouseReceiptHeader);
+
+ LibraryWarehouse.GetSourceDocumentsReceipt(WarehouseReceiptHeader, WarehouseSourceFilter, LocationCode);
+ end;
+
+ ///
+ /// Posts a warehouse receipt and finds the resulting posted warehouse receipt header.
+ ///
+ /// The warehouse receipt header to post
+ /// The posted warehouse receipt header found after posting
+ procedure PostWarehouseReceipt(WarehouseReceiptHeader: Record "Warehouse Receipt Header"; var PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header")
+ begin
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ PostedWhseReceiptHeader.SetRange("Whse. Receipt No.", WarehouseReceiptHeader."No.");
+ PostedWhseReceiptHeader.FindFirst();
+ end;
+
+ ///
+ /// Posts a partial warehouse receipt for the specified quantity and finds the latest posted warehouse receipt header.
+ ///
+ /// The warehouse receipt header to post partially
+ /// The quantity to receive on the warehouse receipt line
+ /// The posted warehouse receipt header found after posting
+ procedure PostPartialWarehouseReceipt(WarehouseReceiptHeader: Record "Warehouse Receipt Header"; PartialQuantity: Decimal; var PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header")
+ var
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ begin
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ WarehouseReceiptLine.Validate("Qty. to Receive", PartialQuantity);
+ WarehouseReceiptLine.Modify(true);
+
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ PostedWhseReceiptHeader.SetRange("Whse. Receipt No.", WarehouseReceiptHeader."No.");
+ PostedWhseReceiptHeader.FindLast();
+ end;
+
+ // ========================================
+ // PUT-AWAY FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates a put-away document from a posted warehouse receipt if none exists and returns the latest put-away header.
+ ///
+ /// The posted warehouse receipt header used as source for put-away creation
+ /// The warehouse activity header for the created or existing put-away
+ procedure CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header"; var WarehouseActivityHeader: Record "Warehouse Activity Header")
+ var
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ begin
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.FindFirst();
+
+ WarehouseActivityLine.SetRange("Location Code", PostedWhseReceiptHeader."Location Code");
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Source Type", PostedWhseReceiptLine."Source Type");
+ WarehouseActivityLine.SetRange("Source No.", PostedWhseReceiptLine."Source No.");
+
+ if WarehouseActivityLine.IsEmpty() then begin
+ PostedWhseReceiptLine.SetHideValidationDialog(true);
+ PostedWhseReceiptLine.CreatePutAwayDoc(PostedWhseReceiptLine, PostedWhseReceiptHeader."Assigned User ID");
+ end;
+
+ if WarehouseActivityLine.FindLast() then
+ WarehouseActivityHeader.Get(WarehouseActivityLine."Activity Type", WarehouseActivityLine."No.");
+ end;
+
+ ///
+ /// Posts a partial put-away by setting quantity to handle on all lines and registering the warehouse activity.
+ ///
+ /// The warehouse activity header for the put-away to register
+ /// The quantity to handle assigned to each warehouse activity line
+ procedure PostPartialPutAway(var WarehouseActivityHeader: Record "Warehouse Activity Header"; PartialQuantity: Decimal)
+ var
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ begin
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ if WarehouseActivityLine.FindSet() then
+ repeat
+ WarehouseActivityLine.Validate("Qty. to Handle", PartialQuantity);
+ WarehouseActivityLine.Modify(true);
+ until WarehouseActivityLine.Next() = 0;
+
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+ end;
+
+ // ========================================
+ // PUT-AWAY WORKSHEET FUNCTIONS
+ // ========================================
+
+ ///
+ /// Creates a put-away worksheet name for the specified location, ensuring a put-away worksheet template exists.
+ ///
+ /// The worksheet template record that is found or created
+ /// The worksheet name record that is created
+ /// The location code assigned to the worksheet name
+ procedure CreatePutAwayWorksheet(var WhseWorksheetTemplate: Record "Whse. Worksheet Template"; var WhseWorksheetName: Record "Whse. Worksheet Name"; LocationCode: Code[10])
+ begin
+ EnsurePutAwayWorksheetTemplate(WhseWorksheetTemplate);
+ LibraryWarehouse.CreateWhseWorksheetName(WhseWorksheetName, WhseWorksheetTemplate.Name, LocationCode);
+ end;
+
+ ///
+ /// Ensures that a put-away worksheet template exists by finding an existing one or creating a new one.
+ ///
+ /// The worksheet template record that is found or created
+ local procedure EnsurePutAwayWorksheetTemplate(var WhseWorksheetTemplate: Record "Whse. Worksheet Template")
+ begin
+ // Try to find existing put-away template
+ WhseWorksheetTemplate.SetRange(Type, WhseWorksheetTemplate.Type::"Put-away");
+ if WhseWorksheetTemplate.FindFirst() then
+ exit;
+
+ // No template exists, create one
+ WhseWorksheetTemplate.Init();
+ WhseWorksheetTemplate.Validate(Name,
+ CopyStr(LibraryUtility.GenerateRandomCode(WhseWorksheetTemplate.FieldNo(Name), Database::"Whse. Worksheet Template"),
+ 1, MaxStrLen(WhseWorksheetTemplate.Name)));
+ WhseWorksheetTemplate.Validate(Type, WhseWorksheetTemplate.Type::"Put-away");
+ WhseWorksheetTemplate.Validate(Description, 'Put-away Worksheet');
+ WhseWorksheetTemplate.Validate("Page ID", Page::"Put-away Worksheet");
+ WhseWorksheetTemplate.Insert(true);
+ end;
+
+ ///
+ /// Retrieves inbound source documents for the put-away worksheet at the specified location.
+ ///
+ /// The worksheet template name used by the worksheet context
+ /// The worksheet name record used to populate worksheet lines
+ /// The location code used to filter put-away requests
+ procedure GetWarehouseDocumentsForPutAwayWorksheet(WhseWorksheetTemplateName: Code[10]; WhseWorksheetName: Record "Whse. Worksheet Name"; LocationCode: Code[10])
+ var
+ WhsePutAwayRequest: Record "Whse. Put-away Request";
+ begin
+ WhsePutAwayRequest.SetRange("Completely Put Away", false);
+ WhsePutAwayRequest.SetRange("Location Code", LocationCode);
+ LibraryWarehouse.GetInboundSourceDocuments(WhsePutAwayRequest, WhseWorksheetName, LocationCode);
+ end;
+
+ ///
+ /// Creates a put-away document from worksheet lines and returns the latest put-away warehouse activity header.
+ ///
+ /// The worksheet name containing the worksheet lines to process
+ /// The warehouse activity header found after document creation
+ procedure CreatePutAwayFromWorksheet(WhseWorksheetName: Record "Whse. Worksheet Name"; var WarehouseActivityHeader: Record "Warehouse Activity Header")
+ var
+ WhseWorksheetLine: Record "Whse. Worksheet Line";
+ begin
+ WhseWorksheetLine.SetRange("Worksheet Template Name", WhseWorksheetName."Worksheet Template Name");
+ WhseWorksheetLine.SetRange(Name, WhseWorksheetName.Name);
+ WhseWorksheetLine.SetRange("Location Code", WhseWorksheetName."Location Code");
+ WhseWorksheetLine.FindFirst();
+
+ // Create put-away from worksheet lines using correct function
+ LibraryWarehouse.WhseSourceCreateDocument(
+ WhseWorksheetLine,
+ "Whse. Activity Sorting Method"::None,
+ false,
+ false,
+ false);
+
+ WarehouseActivityHeader.SetRange("Location Code", WhseWorksheetName."Location Code");
+ WarehouseActivityHeader.SetRange(Type, WarehouseActivityHeader.Type::"Put-away");
+ WarehouseActivityHeader.FindLast();
+ end;
+
+ // ========================================
+ // VERIFICATION FUNCTIONS
+ // ========================================
+
+ ///
+ /// Verifies that output item ledger entries exist for the item and location and match the expected quantity.
+ ///
+ /// The item number to verify
+ /// The expected summed output quantity
+ /// The location code to verify
+ procedure VerifyItemLedgerEntry(ItemNo: Code[20]; ExpectedQuantity: Decimal; LocationCode: Code[10])
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Assert: Codeunit Assert;
+ begin
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(ExpectedQuantity, ItemLedgerEntry.Quantity,
+ 'Item Ledger Entry should have correct output quantity');
+ end;
+
+ ///
+ /// Verifies that capacity ledger entries exist for the work center and match the expected output quantity.
+ ///
+ /// The work center number to verify
+ /// The expected summed output quantity
+ procedure VerifyCapacityLedgerEntry(WorkCenterNo: Code[20]; ExpectedQuantity: Decimal)
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ Assert: Codeunit Assert;
+ begin
+ CapacityLedgerEntry.SetRange(Type, CapacityLedgerEntry.Type::"Work Center");
+ CapacityLedgerEntry.SetRange("No.", WorkCenterNo);
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+ Assert.AreEqual(ExpectedQuantity, CapacityLedgerEntry."Output Quantity",
+ 'Capacity Ledger Entry should have correct output quantity');
+ end;
+
+ ///
+ /// Verifies that bin content exists for the given location, bin, and item and matches the expected quantity.
+ ///
+ /// The location code to verify
+ /// The bin code to verify
+ /// The item number to verify
+ /// The expected bin content quantity
+ procedure VerifyBinContents(LocationCode: Code[10]; BinCode: Code[20]; ItemNo: Code[20]; ExpectedQuantity: Decimal)
+ var
+ BinContent: Record "Bin Content";
+ Assert: Codeunit Assert;
+ begin
+ BinContent.SetRange("Location Code", LocationCode);
+ BinContent.SetRange("Bin Code", BinCode);
+ BinContent.SetRange("Item No.", ItemNo);
+ Assert.RecordIsNotEmpty(BinContent);
+
+ BinContent.FindFirst();
+ BinContent.CalcFields(Quantity);
+ Assert.AreEqual(ExpectedQuantity, BinContent.Quantity,
+ 'Bin contents should show correct quantity after put-away posting');
+ end;
+
+ // ========================================
+ // COMPLETE SCENARIO SETUP FUNCTIONS
+ // ========================================
+
+ ///
+ /// Sets up a complete subcontracting warehouse scenario including item, location, production order, and purchase order.
+ ///
+ /// The item record that is created and configured for the scenario
+ /// The location record that is created and configured for warehouse handling
+ /// The production order record that is created and refreshed
+ /// The purchase header record found for the created subcontracting purchase order
+ /// The production order quantity used in the setup
+ procedure SetupCompleteSubcontractingWarehouseScenario(var Item: Record Item; var Location: Record Location; var ProductionOrder: Record "Production Order"; var PurchaseHeader: Record "Purchase Header"; Quantity: Decimal)
+ var
+ MachineCenter: array[2] of Record "Machine Center";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // Complete setup for most common warehouse scenarios
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ CreateLocationWithWarehouseHandling(Location);
+
+ // Configure vendor with location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // Create production order
+ CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // Setup subcontracting
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // Create purchase order
+ CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ end;
+
+ ///
+ /// Creates a production item with lot tracking setup using generated number series and routing/BOM configuration.
+ ///
+ /// The item record that is created and updated with lot tracking setup
+ /// The work centers used when creating routing data for the item
+ /// The machine centers used when creating routing data for the item
+ procedure CreateLotTrackedItemForProductionWithSetup(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ ItemTrackingCode: Record "Item Tracking Code";
+ LotNoSeries: Record "No. Series";
+ LotNoSeriesLine: Record "No. Series Line";
+ begin
+ // Implemented by Copilot - Create lot tracking components internally
+ LibraryUtility.CreateNoSeries(LotNoSeries, true, true, false);
+ LibraryUtility.CreateNoSeriesLine(LotNoSeriesLine, LotNoSeries.Code,
+ PadStr(Format(CurrentDateTime(), 0, 'L'), 19, '0'),
+ PadStr(Format(CurrentDateTime(), 0, 'L'), 19, '9'));
+ LibraryItemTracking.CreateItemTrackingCode(ItemTrackingCode, false, true, false);
+
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ Item.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ Item.Validate("Lot Nos.", LotNoSeries.Code);
+ Item.Modify(true);
+ end;
+
+ ///
+ /// Creates a production item with serial tracking setup using generated number series and routing/BOM configuration.
+ ///
+ /// The item record that is created and updated with serial tracking setup
+ /// The work centers used when creating routing data for the item
+ /// The machine centers used when creating routing data for the item
+ procedure CreateSerialTrackedItemForProductionWithSetup(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ ItemTrackingCode: Record "Item Tracking Code";
+ SerialNoSeries: Record "No. Series";
+ SerialNoSeriesLine: Record "No. Series Line";
+ begin
+ // Create serial tracking components internally
+ LibraryUtility.CreateNoSeries(SerialNoSeries, true, true, false);
+ LibraryUtility.CreateNoSeriesLine(SerialNoSeriesLine, SerialNoSeries.Code,
+ PadStr(Format(CurrentDateTime(), 0, 'S'), 19, '0'),
+ PadStr(Format(CurrentDateTime(), 0, 'S'), 19, '9'));
+ LibraryItemTracking.CreateItemTrackingCode(ItemTrackingCode, true, false, false);
+
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ Item.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ Item.Validate("Serial Nos.", SerialNoSeries.Code);
+ Item.Modify(true);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcLocationHandlerTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcLocationHandlerTest.Codeunit.al
index b49be8feb4..e2397bf62a 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcLocationHandlerTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcLocationHandlerTest.Codeunit.al
@@ -29,18 +29,18 @@ codeunit 139981 "Subc. Location Handler Test"
var
Assert: Codeunit Assert;
- LibraryPurchase: Codeunit "Library - Purchase";
- LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibraryInventory: Codeunit "Library - Inventory";
- LibraryWarehouse: Codeunit "Library - Warehouse";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryPurchase: Codeunit "Library - Purchase";
LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
SubCreateProdOrdWizLibrary: Codeunit "Subc. CreateProdOrdWizLibrary";
LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibrarySetupStorage: Codeunit "Library - Setup Storage";
- LibraryTestInitialize: Codeunit "Library - Test Initialize";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
WizardFinishedSuccessfully: Boolean;
@@ -68,10 +68,10 @@ codeunit 139981 "Subc. Location Handler Test"
[Test]
procedure TestGetComponentsLocationCode_Purchase()
var
- PurchaseLine: Record "Purchase Line";
+ Location: Record Location;
PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
SubManagementSetup: Record "Subc. Management Setup";
- Location: Record Location;
SubcontractingMgmt: Codeunit "Subcontracting Management";
CompLocationCode: Code[10];
begin
@@ -98,9 +98,9 @@ codeunit 139981 "Subc. Location Handler Test"
[Test]
procedure TestGetComponentsLocationCode_Company()
var
+ Location: Record Location;
PurchaseLine: Record "Purchase Line";
SubManagementSetup: Record "Subc. Management Setup";
- Location: Record Location;
SubcontractingMgmt: Codeunit "Subcontracting Management";
CompLocationCode: Code[10];
begin
@@ -124,9 +124,9 @@ codeunit 139981 "Subc. Location Handler Test"
[Test]
procedure TestGetComponentsLocationCode_Manufacturing()
var
+ Location: Record Location;
PurchaseLine: Record "Purchase Line";
SubManagementSetup: Record "Subc. Management Setup";
- Location: Record Location;
SubcontractingMgmt: Codeunit "Subcontracting Management";
CompLocationCode: Code[10];
begin
@@ -154,17 +154,17 @@ codeunit 139981 "Subc. Location Handler Test"
[HandlerFunctions('HandleTransferOrder')]
procedure TestTransferOrderCreation_SameLocation()
var
- PurchaseHeader: Record "Purchase Header";
- PurchaseLine: Record "Purchase Line";
- ProdOrder: Record "Production Order";
- ProdOrderLine: Record "Prod. Order Line";
+ Item: Record Item;
+ LocationOrig: Record Location;
+ LocationSub: Record Location;
ProdOrderComp: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
ProdOrderRtngLine: Record "Prod. Order Routing Line";
- Vendor: Record Vendor;
- LocationSub: Record Location;
- LocationOrig: Record Location;
- Item: Record Item;
+ ProdOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
TransferHeader: Record "Transfer Header";
+ Vendor: Record Vendor;
CreateSubCTransfOrder: Report "Subc. Create Transf. Order";
begin
// [SCENARIO] Transfer Order creation uses Origin Location if Component Location equals Subcontractor Location
@@ -197,23 +197,23 @@ codeunit 139981 "Subc. Location Handler Test"
[HandlerFunctions('HandleTransferOrder')]
procedure TestTransferOrderCreation_PostAndRecreate()
var
- PurchaseHeader: Record "Purchase Header";
- PurchaseLine: Record "Purchase Line";
- ProdOrder: Record "Production Order";
- ProdOrderLine: Record "Prod. Order Line";
+ Item: Record Item;
+ ItemJournalLine: Record "Item Journal Line";
+ LocationOrig: Record Location;
+ LocationSub: Record Location;
ProdOrderComp: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
ProdOrderRtngLine: Record "Prod. Order Routing Line";
- Vendor: Record Vendor;
- LocationSub: Record Location;
- LocationOrig: Record Location;
- Item: Record Item;
+ ProdOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
TransferHeader: Record "Transfer Header";
TransferLine: Record "Transfer Line";
- ItemJournalLine: Record "Item Journal Line";
+ Vendor: Record Vendor;
CreateSubCTransfOrder: Report "Subc. Create Transf. Order";
- QtyTotal: Decimal;
QtyFirstTransfer: Decimal;
QtyRemaining: Decimal;
+ QtyTotal: Decimal;
begin
// [SCENARIO] Create Transfer Order, reduce quantity, post, and create new Transfer Order for remaining
Initialize();
@@ -280,12 +280,12 @@ codeunit 139981 "Subc. Location Handler Test"
[HandlerFunctions('HandlePurchProvisionWizard')]
procedure TestProdOrderLocationFromMfgSetup_PurchaseLocationMustBeDifferent()
var
- PurchLine: Record "Purchase Line";
- ProdOrder: Record "Production Order";
+ LocationMfg: Record Location;
+ ManufacturingSetup: Record "Manufacturing Setup";
ProdOrderLine: Record "Prod. Order Line";
+ ProdOrder: Record "Production Order";
+ PurchLine: Record "Purchase Line";
SubManagementSetup: Record "Subc. Management Setup";
- ManufacturingSetup: Record "Manufacturing Setup";
- LocationMfg: Record Location;
CreateProdOrdOpt: Codeunit "Subc. Create Prod. Ord. Opt.";
ItemNo: Code[20];
begin
@@ -452,5 +452,4 @@ codeunit 139981 "Subc. Location Handler Test"
procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
begin
end;
-
}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcPurchSubcontTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcPurchSubcontTest.Codeunit.al
index 803436e805..f0522b46a6 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcPurchSubcontTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcPurchSubcontTest.Codeunit.al
@@ -33,6 +33,7 @@ codeunit 139991 "Subc. Purch. Subcont. Test"
var
Assert: Codeunit Assert;
LibraryERM: Codeunit "Library - ERM";
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibraryInventory: Codeunit "Library - Inventory";
LibraryManufacturing: Codeunit "Library - Manufacturing";
LibraryPurchase: Codeunit "Library - Purchase";
@@ -43,7 +44,6 @@ codeunit 139991 "Subc. Purch. Subcont. Test"
LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
ErrorCounter: Integer;
ErrorMessageDescriptionList: List of [Text];
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingSyncTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingSyncTest.Codeunit.al
index a829bc459e..fa6f9ec542 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingSyncTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingSyncTest.Codeunit.al
@@ -470,6 +470,7 @@ codeunit 139992 "Subc. Subcontracting Sync Test"
var
Assert: Codeunit Assert;
LibraryERM: Codeunit "Library - ERM";
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibraryInventory: Codeunit "Library - Inventory";
LibraryManufacturing: Codeunit "Library - Manufacturing";
LibraryPlanning: Codeunit "Library - Planning";
@@ -481,7 +482,6 @@ codeunit 139992 "Subc. Subcontracting Sync Test"
LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
Subcontracting: Boolean;
UnitCostCalculation: Option Time,Units;
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingTest.Codeunit.al
index 34dedb8bd1..e7e425044d 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingTest.Codeunit.al
@@ -1826,7 +1826,7 @@ Comment = '|%1 = Transfer Order No.';
ProdOrderComp: Record "Prod. Order Component";
begin
ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
-#pragma warning disable AA0210
+#pragma warning disable AA0210
ProdOrderComp.SetRange("Subcontracting Type", ProdOrderComp."Subcontracting Type"::Transfer);
#pragma warning restore AA0210
ProdOrderComp.FindFirst();
@@ -1842,9 +1842,9 @@ Comment = '|%1 = Transfer Order No.';
ProdOrderComp: Record "Prod. Order Component";
begin
ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
-#pragma warning disable AA0210
+#pragma warning disable AA0210
ProdOrderComp.SetRange("Subcontracting Type", ProdOrderComp."Subcontracting Type"::Transfer);
-#pragma warning restore AA0210
+#pragma warning restore AA0210
ProdOrderComp.FindFirst();
LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
Location."Bin Mandatory" := true;
@@ -2187,6 +2187,7 @@ Comment = '|%1 = Transfer Order No.';
WorkCenter2: Record "Work Center";
Assert: Codeunit Assert;
LibraryERM: Codeunit "Library - ERM";
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibraryInventory: Codeunit "Library - Inventory";
LibraryManufacturing: Codeunit "Library - Manufacturing";
LibraryPlanning: Codeunit "Library - Planning";
@@ -2199,7 +2200,6 @@ Comment = '|%1 = Transfer Order No.';
LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
Subcontracting: Boolean;
UnitCostCalculation: Option Time,Units;
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingUITest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingUITest.Codeunit.al
index d211d238e6..2757333438 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingUITest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcSubcontractingUITest.Codeunit.al
@@ -324,12 +324,12 @@ codeunit 139990 "Subc. Subcontracting UI Test"
var
Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibrarySetupStorage: Codeunit "Library - Setup Storage";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
ControlNotExistMsg: Label 'Control %1 does not exist.', Comment = '%1 = field caption';
}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseCombinedScenarios.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseCombinedScenarios.Codeunit.al
new file mode 100644
index 0000000000..b3dc7ce5d8
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseCombinedScenarios.Codeunit.al
@@ -0,0 +1,622 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Ledger;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+
+codeunit 149906 "Subc. Whse Combined Scenarios"
+{
+ // [FEATURE] Subcontracting Warehouse Combined Scenarios Tests
+ Subtype = Test;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ HandlingLotNo: Code[50];
+ HandlingSerialNo: Code[50];
+ HandlingQty: Decimal;
+ HandlingSourceType: Integer;
+ HandlingMode: Option Verify,Insert;
+
+ local procedure Initialize()
+ begin
+ HandlingSerialNo := '';
+ HandlingLotNo := '';
+ HandlingQty := 0;
+ HandlingMode := HandlingMode::Verify;
+ HandlingSourceType := 0;
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Combined Scenarios");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Combined Scenarios");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Combined Scenarios");
+ end;
+
+ [Test]
+ procedure ProdOrderWithLastAndIntermediateOperationsSameVendor()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Prod. Order with Last and Intermediate Operations (Same Vendor)
+ // [FEATURE] Subcontracting Warehouse Combined Scenarios
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(10, 20);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting - both with same vendor
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenterSameVendor(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Links for both operations
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLinkForBothOperations(Item, WorkCenter);
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Create Warehouse Employee for the location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, true);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Orders via Subcontracting Worksheet
+ // The worksheet approach combines all lines for the same vendor into one Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrdersViaWorksheet(ProductionOrder."No.", PurchaseHeader);
+
+ // [THEN] Verify Data Consistency: Both operations should be on the same PO (same vendor)
+ PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ Assert.RecordCount(PurchaseLine, 2);
+
+ // [THEN] Verify Purchase Line links to Production Order
+ PurchaseLine.FindSet();
+ repeat
+ Assert.AreEqual(ProductionOrder."No.", PurchaseLine."Prod. Order No.", 'Purchase Line should link to Production Order');
+ Assert.AreEqual(Item."Routing No.", PurchaseLine."Routing No.", 'Purchase Line should have correct Routing No.');
+ until PurchaseLine.Next() = 0;
+
+ // [WHEN] Create single Warehouse Receipt using "Get Source Documents" to include both lines
+ LibraryPurchase.ReleasePurchaseDocument(PurchaseHeader);
+ SubcWarehouseLibrary.CreateWarehouseReceiptUsingGetSourceDocuments(WarehouseReceiptHeader, Location.Code);
+
+ // [GIVEN] Set Bin Code on warehouse receipt lines (Get Source Documents doesn't auto-fill like CreateWhseReceiptFromPO)
+ SetBinCodeOnWarehouseReceiptLines(WarehouseReceiptHeader, ReceiveBin.Code);
+
+ // [THEN] Verify Data Consistency: Single warehouse receipt created for both lines from same PO
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.SetRange("Source Document", WarehouseReceiptLine."Source Document"::"Purchase Order");
+ WarehouseReceiptLine.SetRange("Source No.", PurchaseHeader."No.");
+ Assert.RecordCount(WarehouseReceiptLine, 2);
+
+ // [THEN] Verify Data Consistency: Identify intermediate and last operation lines
+#pragma warning disable AA0210
+ WarehouseReceiptLine.SetRange("Subc. Purchase Line Type", WarehouseReceiptLine."Subc. Purchase Line Type"::NotLastOperation);
+#pragma warning restore AA0210
+ Assert.RecordCount(WarehouseReceiptLine, 1);
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(Item."No.", WarehouseReceiptLine."Item No.", 'Intermediate operation line should have correct item');
+
+ // [THEN] Verify NotLastOperation has zero base quantities (no inventory movement)
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. (Base)", 'NotLastOperation should have zero Qty. (Base)');
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. per Unit of Measure", 'NotLastOperation should have zero Qty. per UoM');
+
+#pragma warning disable AA0210
+ WarehouseReceiptLine.SetRange("Subc. Purchase Line Type", "Subc. Purchase Line Type"::LastOperation);
+#pragma warning restore AA0210
+ Assert.RecordCount(WarehouseReceiptLine, 1);
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(Item."No.", WarehouseReceiptLine."Item No.", 'Last operation line should have correct item');
+
+ // [THEN] Verify LastOperation has populated base quantities
+ Assert.AreEqual(Quantity, WarehouseReceiptLine."Qty. (Base)", 'LastOperation should have correct Qty. (Base)');
+ Assert.IsTrue(WarehouseReceiptLine."Qty. per Unit of Measure" > 0, 'LastOperation should have Qty. per UoM > 0');
+
+ // [WHEN] Post warehouse receipt for both lines
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Posted Entries: Posted warehouse receipt created
+ Assert.AreNotEqual('', PostedWhseReceiptHeader."No.", 'Posted warehouse receipt should be created');
+
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ Assert.RecordCount(PostedWhseReceiptLine, 2);
+
+ // [THEN] Verify Bin Management: Put-away can only be created for last operation line (Take and Place)
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Location Code", Location.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordCount(WarehouseActivityLine, 2);
+
+ // [THEN] Verify Data Consistency: Take line exists with correct bin
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ Assert.RecordCount(WarehouseActivityLine, 1);
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(ReceiveBin.Code, WarehouseActivityLine."Bin Code", 'Take line should use receive bin');
+ Assert.AreEqual(Quantity, WarehouseActivityLine."Qty. (Base)", 'Take line should have correct quantity');
+
+ // [THEN] Verify Data Consistency: Place line exists with correct bin
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ Assert.RecordCount(WarehouseActivityLine, 1);
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(PutAwayBin.Code, WarehouseActivityLine."Bin Code", 'Place line should use put-away bin');
+ Assert.AreEqual(Quantity, WarehouseActivityLine."Qty. (Base)", 'Place line should have correct quantity');
+
+ // [WHEN] Post the put-away
+ WarehouseActivityHeader.Get(WarehouseActivityLine."Activity Type", WarehouseActivityLine."No.");
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify Posted Entries: All ledger entries correct for both operations
+ VerifyLedgerEntriesForCombinedScenario(Item."No.", Quantity, Location.Code);
+ end;
+
+ [Test]
+ procedure ProdOrderWithMultipleOperationsDifferentVendors()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader1: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptHeader2: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader1: Record "Purchase Header";
+ PurchaseHeader2: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor1: Record Vendor;
+ Vendor2: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader1: Record "Warehouse Receipt Header";
+ WarehouseReceiptHeader2: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Prod. Order with Multiple Operations (Different Vendors)
+ // [FEATURE] Subcontracting Warehouse Combined Scenarios
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(15, 25);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting - different vendors
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Links for both operations
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLinkForBothOperations(Item, WorkCenter);
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Create Warehouse Employee for the location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, true);
+
+ // [GIVEN] Configure Vendors with Subcontracting Location
+ Vendor1.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor1."Subcontr. Location Code" := Location.Code;
+ Vendor1."Location Code" := Location.Code;
+ Vendor1.Modify();
+
+ Vendor2.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor2."Subcontr. Location Code" := Location.Code;
+ Vendor2."Location Code" := Location.Code;
+ Vendor2.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Orders via Subcontracting Worksheet
+ // The worksheet creates one PO per vendor
+ SubcWarehouseLibrary.CreateSubcontractingOrdersViaWorksheet(ProductionOrder."No.", PurchaseHeader1);
+
+ // [THEN] Find both Purchase Headers - different vendors will have separate POs
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ PurchaseLine.SetRange("Buy-from Vendor No.", Vendor1."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader1.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseLine.SetRange("Buy-from Vendor No.", Vendor2."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader2.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [THEN] Verify Data Consistency: Separate POs for different vendors
+ Assert.AreNotEqual(PurchaseHeader1."No.", PurchaseHeader2."No.", 'Different vendors should have separate Purchase Orders');
+
+ Assert.AreNotEqual(PurchaseHeader1."Buy-from Vendor No.", PurchaseHeader2."Buy-from Vendor No.", 'Purchase Orders should have different vendors');
+
+ // [WHEN] Create separate Warehouse Receipts for each PO
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader1, WarehouseReceiptHeader1);
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader2, WarehouseReceiptHeader2);
+
+ // [THEN] Verify Data Consistency: Separate warehouse documents for each vendor
+ Assert.AreNotEqual(WarehouseReceiptHeader1."No.", WarehouseReceiptHeader2."No.", 'Separate warehouse receipts should be created for different vendors');
+
+ // [THEN] Verify Data Consistency: Each warehouse receipt has correct vendor info
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader1."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(PurchaseHeader1."No.", WarehouseReceiptLine."Source No.", 'First warehouse receipt should link to first PO');
+
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader2."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(PurchaseHeader2."No.", WarehouseReceiptLine."Source No.", 'Second warehouse receipt should link to second PO');
+
+ // [WHEN] Post both warehouse receipts independently
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader1, PostedWhseReceiptHeader1);
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader2, PostedWhseReceiptHeader2);
+
+ // [THEN] Verify Posted Entries: All documents processed correctly and independently
+ Assert.AreNotEqual('', PostedWhseReceiptHeader1."No.", 'First posted warehouse receipt should be created');
+ Assert.AreNotEqual('', PostedWhseReceiptHeader2."No.", 'Second posted warehouse receipt should be created');
+
+ // [THEN] Verify Bin Management: Put-away created only for last operation (Take and Place lines)
+ // Only the last operation creates physical inventory movement and warehouse activity lines
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Location Code", Location.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ // Should have Take and Place lines for last operation only (1 vendor x 2 lines = 2 total)
+ Assert.RecordCount(WarehouseActivityLine, 2);
+
+ // [THEN] Verify Data Consistency: Take line exists
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ Assert.RecordCount(WarehouseActivityLine, 1);
+
+ // [THEN] Verify Data Consistency: Place line exists
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ Assert.RecordCount(WarehouseActivityLine, 1);
+
+ // [WHEN] Post both put-aways (get distinct warehouse activity headers)
+ WarehouseActivityLine.SetRange("Action Type");
+ if WarehouseActivityLine.FindFirst() then begin
+ WarehouseActivityHeader.Get(WarehouseActivityLine."Activity Type", WarehouseActivityLine."No.");
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+ end;
+
+ // Find second put-away if it exists (different warehouse activity number)
+ WarehouseActivityLine.SetFilter("No.", '<>%1', WarehouseActivityHeader."No.");
+ if WarehouseActivityLine.FindFirst() then begin
+ WarehouseActivityHeader.Get(WarehouseActivityLine."Activity Type", WarehouseActivityLine."No.");
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+ end;
+
+ // [THEN] Verify Data Consistency: All ledger entries correct for both vendors
+ VerifyLedgerEntriesForMultiVendorScenario(Item."No.", Quantity, Location.Code);
+ end;
+
+ [Test]
+ procedure WhseReceiptCreationWithGetSourceDocumentsMultipleProdOrders()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder1: Record "Production Order";
+ ProductionOrder2: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity1: Decimal;
+ Quantity2: Decimal;
+ begin
+ // [SCENARIO] WH Receipt Creation with "Get Source Documents"
+ // [FEATURE] Subcontracting Warehouse Combined Scenarios
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity1 := LibraryRandom.RandIntInRange(10, 15);
+ Quantity2 := LibraryRandom.RandIntInRange(15, 20);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting - same vendor
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenterSameVendor(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link for last operation
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Create Warehouse Employee for the location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, true);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create multiple Production Orders
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder1, "Production Order Status"::Released,
+ ProductionOrder1."Source Type"::Item, Item."No.", Quantity1, Location.Code);
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder2, "Production Order Status"::Released,
+ ProductionOrder2."Source Type"::Item, Item."No.", Quantity2, Location.Code);
+
+ // [WHEN] Create Subcontracting Purchase Orders via Subcontracting Worksheet
+ // The worksheet combines all lines for the same vendor into one PO
+ SubcWarehouseLibrary.CreateSubcontractingOrdersViaWorksheet(ProductionOrder1."No.", PurchaseHeader);
+
+ // [THEN] Verify Data Consistency: Both prod orders should create lines on the same PO (same vendor)
+ PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ Assert.RecordCount(PurchaseLine, 2);
+
+ // [WHEN] Use "Get Source Documents" function to create warehouse receipt
+ LibraryPurchase.ReleasePurchaseDocument(PurchaseHeader);
+ SubcWarehouseLibrary.CreateWarehouseReceiptUsingGetSourceDocuments(WarehouseReceiptHeader, Location.Code);
+
+ // [GIVEN] Set Bin Code on warehouse receipt lines (Get Source Documents doesn't auto-fill like CreateWhseReceiptFromPO)
+ SetBinCodeOnWarehouseReceiptLines(WarehouseReceiptHeader, ReceiveBin.Code);
+
+ // [THEN] Verify Data Consistency: Warehouse receipt created with lines from the PO
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.SetRange("Item No.", Item."No.");
+ Assert.RecordCount(WarehouseReceiptLine, 2);
+
+ // [THEN] Verify Data Consistency: Lines from the combined PO
+ WarehouseReceiptLine.SetRange("Source No.", PurchaseHeader."No.");
+ Assert.RecordCount(WarehouseReceiptLine, 2);
+
+ // [THEN] Verify Data Consistency: Each line has correct data reconciled with original source
+ WarehouseReceiptLine.SetRange("Source No.");
+ WarehouseReceiptLine.FindSet();
+ repeat
+ VerifyWarehouseReceiptLineDetails(WarehouseReceiptLine, Item, PurchaseHeader."No.");
+ until WarehouseReceiptLine.Next() = 0;
+
+ // [WHEN] Post warehouse receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Posted Entries: Subsequent processing correct for each line
+ Assert.AreNotEqual('', PostedWhseReceiptHeader."No.", 'Posted warehouse receipt should be created');
+
+ // [THEN] Verify Data Consistency: All ledger entries correct
+ VerifyLedgerEntriesForGetSourceDocuments(Item."No.", Location.Code);
+ end;
+
+ local procedure VerifyWarehouseReceiptLineDetails(WarehouseReceiptLine: Record "Warehouse Receipt Line"; Item: Record Item; PurchaseHeaderNo: Code[20])
+ begin
+ Assert.AreEqual(Item."No.", WarehouseReceiptLine."Item No.", 'Warehouse Receipt Line should have correct item');
+ Assert.IsTrue(WarehouseReceiptLine.Quantity > 0, 'Warehouse Receipt Line should have positive quantity');
+
+ // Verify Source Type and Source No.
+ Assert.AreEqual(Database::"Purchase Line", WarehouseReceiptLine."Source Type", 'Source Type should be Purchase Line');
+ Assert.AreEqual(PurchaseHeaderNo, WarehouseReceiptLine."Source No.", 'Source No. should match Purchase Header No.');
+
+ // Verify Qty. (Base) and Qty. per Unit of Measure based on operation type
+ if WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::LastOperation then begin
+ Assert.IsTrue(WarehouseReceiptLine."Qty. (Base)" > 0, 'LastOperation should have Qty. (Base) > 0');
+ Assert.IsTrue(WarehouseReceiptLine."Qty. per Unit of Measure" > 0, 'LastOperation should have Qty. per UoM > 0');
+ end else begin
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. (Base)", 'NotLastOperation should have zero Qty. (Base)');
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. per Unit of Measure", 'NotLastOperation should have zero Qty. per UoM');
+ end;
+ end;
+
+ local procedure VerifyLedgerEntriesForCombinedScenario(ItemNo: Code[20]; Quantity: Decimal; LocationCode: Code[10])
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ WarehouseEntry: Record Microsoft.Warehouse.Ledger."Warehouse Entry";
+ begin
+ // Verify Item Ledger Entries and Quantity (Base)
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry total Quantity should equal expected quantity');
+
+ // Verify Capacity Ledger Entries and Output Quantity
+ CapacityLedgerEntry.SetRange("Item No.", ItemNo);
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+ CapacityLedgerEntry.FindFirst();
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+ Assert.AreEqual(2 * Quantity, CapacityLedgerEntry."Output Quantity" / CapacityLedgerEntry."Qty. per Unit of Measure", 'Capacity Ledger Entry Output Quantity should equal expected quantity');
+
+ // Verify Warehouse Entries exist (for Put-away scenarios - only for last operation lines)
+ WarehouseEntry.SetRange("Item No.", ItemNo);
+ WarehouseEntry.SetRange("Location Code", LocationCode);
+ Assert.RecordIsNotEmpty(WarehouseEntry);
+ end;
+
+ local procedure VerifyLedgerEntriesForMultiVendorScenario(ItemNo: Code[20]; Quantity: Decimal; LocationCode: Code[10])
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ WarehouseEntry: Record "Warehouse Entry";
+ begin
+ // Verify Item Ledger Entries and Quantity (Base)
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry total Quantity should equal expected quantity');
+
+ // Verify Capacity Ledger Entries and Output Quantity
+ CapacityLedgerEntry.SetRange("Item No.", ItemNo);
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+ Assert.AreEqual(2 * Quantity, CapacityLedgerEntry."Output Quantity", 'Capacity Ledger Entry Output Quantity should equal expected quantity');
+
+ // Verify Warehouse Entries exist (for Put-away scenarios - only for last operation lines)
+ WarehouseEntry.SetRange("Item No.", ItemNo);
+ WarehouseEntry.SetRange("Location Code", LocationCode);
+ Assert.RecordIsNotEmpty(WarehouseEntry);
+ end;
+
+ local procedure VerifyLedgerEntriesForGetSourceDocuments(ItemNo: Code[20]; LocationCode: Code[10])
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ // Verify Item Ledger Entries
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ end;
+
+ [ModalPageHandler]
+ procedure ItemTrackingLinesPageHandler(var ItemTrackingLines: TestPage "Item Tracking Lines")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ case HandlingMode of
+ HandlingMode::Verify:
+ begin
+ ItemTrackingLines.First();
+ if HandlingSerialNo <> '' then
+ Assert.AreEqual(HandlingSerialNo, Format(ItemTrackingLines."Serial No.".Value), 'Serial No. mismatch');
+ if HandlingLotNo <> '' then
+ Assert.AreEqual(HandlingLotNo, Format(ItemTrackingLines."Lot No.".Value), 'Lot No. mismatch');
+
+ Assert.AreEqual(HandlingQty, ItemTrackingLines."Quantity (Base)".AsDecimal(), 'Quantity mismatch');
+
+ if HandlingSourceType <> 0 then begin
+ ReservationEntry.SetRange("Serial No.", Format(ItemTrackingLines."Serial No.".Value));
+ ReservationEntry.SetRange("Lot No.", Format(ItemTrackingLines."Lot No.".Value));
+ ReservationEntry.FindFirst();
+ Assert.AreEqual(HandlingSourceType, ReservationEntry."Source Type",
+ 'Reservation Entry Source Type should be Prod. Order Line');
+ end;
+ end;
+ HandlingMode::Insert:
+ begin
+ ItemTrackingLines.New();
+ if HandlingSerialNo <> '' then
+ ItemTrackingLines."Serial No.".SetValue(HandlingSerialNo);
+ if HandlingLotNo <> '' then
+ ItemTrackingLines."Lot No.".SetValue(HandlingLotNo);
+
+ ItemTrackingLines."Quantity (Base)".SetValue(HandlingQty);
+ end;
+ end;
+ ItemTrackingLines.OK().Invoke();
+ end;
+
+ local procedure SetBinCodeOnWarehouseReceiptLines(WarehouseReceiptHeader: Record "Warehouse Receipt Header"; BinCode: Code[20])
+ var
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ begin
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ if WarehouseReceiptLine.FindSet() then
+ repeat
+ if WarehouseReceiptLine."Qty. (Base)" > 0 then begin
+ WarehouseReceiptLine.Validate("Bin Code", BinCode);
+ WarehouseReceiptLine.Modify(true);
+ end;
+ until WarehouseReceiptLine.Next() = 0;
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseDataIntegrity.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseDataIntegrity.Codeunit.al
new file mode 100644
index 0000000000..7604caa348
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseDataIntegrity.Codeunit.al
@@ -0,0 +1,416 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Setup;
+
+codeunit 149909 "Subc. Whse Data Integrity"
+{
+ // [FEATURE] Subcontracting Data Integrity and Validation Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ HandlingMode: Option Verify,Insert;
+
+ local procedure Initialize()
+ begin
+ HandlingMode := HandlingMode::Verify;
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Data Integrity");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Data Integrity");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Data Integrity");
+ end;
+
+ [Test]
+ procedure VerifyCannotDeleteLastRoutingOperationWhenPurchaseOrderExists()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] System prevents deletion of last routing operation when purchase orders exist
+ // [FEATURE] Subcontracting Data Integrity - Prevention of last routing operation deletion
+
+ // [GIVEN] Complete setup with subcontracting infrastructure
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[1]."No.");
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for the last routing operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+
+ // [GIVEN] Find the last routing operation
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.RecordIsNotEmpty(ProdOrderRoutingLine);
+ ProdOrderRoutingLine.FindFirst();
+
+ // [WHEN] Attempt to delete the last routing operation that has associated purchase order
+ asserterror ProdOrderRoutingLine.Delete(true);
+
+ // [THEN] The deletion should be prevented - Error message expected
+ Assert.ExpectedError('Because the Production Order Routing Line is the last operation after delete, the Purchase Line cannot be of type Not Last Operation. Please delete the Purchase line first before changing the Production Order Routing Line.');
+ end;
+
+ [Test]
+ procedure VerifyCannotAddRoutingOperationAfterLastWhenPurchaseOrderExists()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ NewRoutingLine: Record "Prod. Order Routing Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] System prevents adding routing operation after last operation when purchase orders exist
+ // [FEATURE] Subcontracting Data Integrity - Prevention of adding operations after last when PO exists
+
+ // [GIVEN] Complete setup
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for last operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+
+ // [GIVEN] Find the last routing operation with purchase order
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [WHEN] Attempt to add a new routing operation after the last operation
+ NewRoutingLine.Init();
+ NewRoutingLine.Status := ProdOrderRoutingLine.Status;
+ NewRoutingLine."Prod. Order No." := ProdOrderRoutingLine."Prod. Order No.";
+ NewRoutingLine."Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ NewRoutingLine."Routing No." := ProdOrderRoutingLine."Routing No.";
+ NewRoutingLine."Operation No." := '9999';
+ NewRoutingLine.Insert(true);
+ NewRoutingLine.Validate(Type, ProdOrderRoutingLine.Type::"Work Center");
+ asserterror NewRoutingLine.Validate("No.", WorkCenter[1]."No.");
+
+ Assert.ExpectedError('The Purchase Line cannot be of type Last Operation for this Production Order Routing Line. Please delete the Purchase line first before changing the Production Order Routing Line.');
+ end;
+
+ [Test]
+ procedure VerifyCannotChangeOperationNoWhenPurchaseLineExists()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] System prevents changing Operation No. on routing operation when purchase line exists
+ // [FEATURE] Subcontracting Data Integrity - Prevention of critical field changes when PO exists
+
+ // [GIVEN] Complete setup with subcontracting infrastructure
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for the routing operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+
+ // [GIVEN] Find the routing operation with purchase order
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [WHEN] Attempt to change Operation No. on the routing operation (by renaming)
+ asserterror ProdOrderRoutingLine.Rename(
+ ProdOrderRoutingLine.Status,
+ ProdOrderRoutingLine."Prod. Order No.",
+ ProdOrderRoutingLine."Routing Reference No.",
+ ProdOrderRoutingLine."Routing No.",
+ '9999'); // New operation no.
+
+ // [THEN] The change should be prevented because a purchase line exists
+ // Error expected: Cannot rename routing line when purchase line exists
+ end;
+
+ [Test]
+ procedure VerifyDataIntegrityWhenModifyingLastOperationWithPO()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ OriginalSetupTime: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Verify data integrity when modifying last routing operation with associated purchase order
+ // [FEATURE] Subcontracting Data Integrity - Modification validation
+
+ // [GIVEN] Complete setup
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Purchase Order for last operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+
+ // [GIVEN] Find last routing operation
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ OriginalSetupTime := ProdOrderRoutingLine."Setup Time";
+
+ // [WHEN] Attempt to modify fields on the routing operation with associated PO
+ // This should maintain referential integrity
+ ProdOrderRoutingLine.Validate("Setup Time", OriginalSetupTime + 10);
+ ProdOrderRoutingLine.Modify(true);
+
+ // [THEN] Verify the modification was allowed for non-critical fields
+ ProdOrderRoutingLine.Get(ProdOrderRoutingLine.Status, ProdOrderRoutingLine."Prod. Order No.",
+ ProdOrderRoutingLine."Routing Reference No.", ProdOrderRoutingLine."Routing No.",
+ ProdOrderRoutingLine."Operation No.");
+ Assert.AreEqual(OriginalSetupTime + 10, ProdOrderRoutingLine."Setup Time",
+ 'Setup time should be modifiable');
+
+ // [THEN] Verify purchase order link remains intact
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ Assert.AreEqual(ProdOrderRoutingLine."Operation No.", PurchaseLine."Operation No.",
+ 'Purchase Order link must remain intact after modification');
+ end;
+
+ [Test]
+ procedure VerifyQuantityReconciliationAfterMultiplePartialReceipts()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ FirstReceiptQty: Decimal;
+ SecondReceiptQty: Decimal;
+ ThirdReceiptQty: Decimal;
+ TotalPostedQty: Decimal;
+ TotalQuantity: Decimal;
+ begin
+ // [SCENARIO] Verify quantity reconciliation is maintained after multiple partial warehouse receipts
+ // [FEATURE] Subcontracting Data Integrity - Quantity Reconciliation
+
+ // [GIVEN] Complete setup with quantity that allows multiple partial receipts
+ Initialize();
+ TotalQuantity := 30;
+ FirstReceiptQty := 10;
+ SecondReceiptQty := 12;
+ ThirdReceiptQty := TotalQuantity - FirstReceiptQty - SecondReceiptQty; // Remaining 8
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create Warehouse Employee for the location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create Production Order and Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", TotalQuantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Post First Partial Receipt
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, FirstReceiptQty, PostedWhseReceiptHeader);
+
+ // [THEN] Verify first receipt quantities
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.FindFirst();
+ Assert.AreEqual(FirstReceiptQty, PostedWhseReceiptLine.Quantity,
+ 'First posted receipt should have correct quantity');
+
+ // [THEN] Verify base quantity on first posted receipt
+ Assert.AreEqual(FirstReceiptQty * PostedWhseReceiptLine."Qty. per Unit of Measure", PostedWhseReceiptLine."Qty. (Base)", 'First posted receipt should have correct Qty. (Base)');
+
+ // [THEN] Verify remaining quantity on warehouse receipt
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(TotalQuantity - FirstReceiptQty, WarehouseReceiptLine."Qty. Outstanding",
+ 'Outstanding quantity should be correctly reduced after first receipt');
+
+ // [THEN] Verify base quantity outstanding after first receipt
+ Assert.AreEqual((TotalQuantity - FirstReceiptQty) * WarehouseReceiptLine."Qty. per Unit of Measure",
+ WarehouseReceiptLine."Qty. Outstanding (Base)",
+ 'Qty. Outstanding (Base) should be correctly calculated after first receipt');
+
+ // [WHEN] Post Second Partial Receipt
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, SecondReceiptQty, PostedWhseReceiptHeader);
+
+ // [THEN] Verify remaining quantity after second receipt
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(ThirdReceiptQty, WarehouseReceiptLine."Qty. Outstanding",
+ 'Outstanding quantity should be correctly reduced after second receipt');
+
+ // [THEN] Verify base quantity outstanding after second receipt
+ Assert.AreEqual(ThirdReceiptQty * WarehouseReceiptLine."Qty. per Unit of Measure",
+ WarehouseReceiptLine."Qty. Outstanding (Base)",
+ 'Qty. Outstanding (Base) should be correctly calculated after second receipt');
+
+ // [WHEN] Post Final Receipt (remaining quantity)
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, ThirdReceiptQty, PostedWhseReceiptHeader);
+
+ // [THEN] Verify total posted quantity across all receipts matches original PO quantity
+ TotalPostedQty := 0;
+ PostedWhseReceiptLine.Reset();
+ PostedWhseReceiptLine.SetRange("Whse. Receipt No.", WarehouseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ if PostedWhseReceiptLine.FindSet() then
+ repeat
+ TotalPostedQty += PostedWhseReceiptLine.Quantity;
+ until PostedWhseReceiptLine.Next() = 0;
+
+ Assert.AreEqual(TotalQuantity, TotalPostedQty,
+ 'Total posted quantity across all receipts must equal original PO quantity');
+
+ // [THEN] Verify purchase line outstanding quantity is zero (fully received)
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ Assert.AreEqual(0, PurchaseLine."Outstanding Quantity",
+ 'Purchase Line outstanding quantity should be zero after full receipt');
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseItemTracking.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseItemTracking.Codeunit.al
new file mode 100644
index 0000000000..d552867807
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseItemTracking.Codeunit.al
@@ -0,0 +1,598 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Setup;
+
+codeunit 149905 "Subc. Whse Item Tracking"
+{
+ // [FEATURE] Subcontracting Item Tracking Integration Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ HandlingLotNo: Code[50];
+ HandlingSerialNo: Code[50];
+ HandlingQty: Decimal;
+ HandlingSourceType: Integer;
+ HandlingMode: Option Verify,Insert;
+
+ local procedure Initialize()
+ begin
+ HandlingSerialNo := '';
+ HandlingLotNo := '';
+ HandlingQty := 0;
+ HandlingMode := HandlingMode::Verify;
+ HandlingSourceType := 0;
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Item Tracking");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Item Tracking");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Item Tracking");
+ end;
+
+ [Test]
+ [HandlerFunctions('ItemTrackingLinesPageHandler')]
+ procedure FullProcessWithSerialTrackingFromProdOrderLine()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ ReservationEntry: Record "Reservation Entry";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ NoSeriesCodeunit: Codeunit "No. Series";
+ SerialNo: Code[50];
+ Quantity: Decimal;
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ begin
+ // [SCENARIO] Full Process with Serial Tracking from Production Order Line
+ // [FEATURE] Subcontracting Item Tracking - Last Operation with Serial Numbers
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Serial-tracked Item
+ Initialize();
+ Quantity := 1; // Serial tracking requires quantity of 1
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Serial-tracked Item for Production include Routing and Prod. BOM
+ SubcWarehouseLibrary.CreateSerialTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Update Vendor with Subcontracting Location Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Assign Serial Number to Production Order Line
+ ProdOrderLine.SetRange(Status, ProductionOrder.Status);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ SerialNo := NoSeriesCodeunit.GetNextNo(Item."Serial Nos.");
+ LibraryManufacturing.CreateProdOrderItemTracking(ReservationEntry, ProdOrderLine, SerialNo, '', Quantity);
+
+ // [GIVEN] Update Subcontracting Management Setup with Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Verify Data Consistency: Item tracking is propagated to Warehouse Receipt
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ Assert.AreEqual(Item."No.", WarehouseReceiptLine."Item No.",
+ 'Item No. should match on Warehouse Receipt Line');
+
+ // [THEN] Verify Data Consistency: Reservation entries exist for warehouse receipt
+ HandlingSerialNo := SerialNo;
+ HandlingLotNo := '';
+ HandlingQty := Quantity;
+
+ // [GIVEN] Create Warehouse Employee for Location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ WarehouseReceiptPage.OpenView();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [WHEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify Data Consistency: Item tracking is propagated to Put-away
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(Item."No.", WarehouseActivityLine."Item No.", 'Item No. should match on Put-away Line');
+ Assert.AreEqual(SerialNo, WarehouseActivityLine."Serial No.", 'Serial No. should be propagated to Put-away Line');
+
+ // [WHEN] Post Put-away
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify Posted Entries: Item Ledger Entry contains correct serial number
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Serial No.", SerialNo);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.FindFirst();
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry Quantity should match');
+ Assert.AreEqual(Location.Code, ItemLedgerEntry."Location Code", 'Item Ledger Entry Location Code should match');
+ end;
+
+ [Test]
+ [HandlerFunctions('ItemTrackingLinesPageHandler')]
+ procedure FullProcessWithLotTrackingFromProdOrderLine()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ ReservationEntry: Record "Reservation Entry";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ NoSeriesCodeunit: Codeunit "No. Series";
+ LotNo: Code[50];
+ Quantity: Decimal;
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ begin
+ // [SCENARIO] Full Process with Lot Tracking from Production Order Line
+ // [FEATURE] Subcontracting Item Tracking - Last Operation with Lot Numbers
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Lot-tracked Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Lot-tracked Item for Production include Routing and Prod. BOM
+ SubcWarehouseLibrary.CreateLotTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Update Vendor with Subcontracting Location Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Assign Lot Number to Production Order Line
+ ProdOrderLine.SetRange(Status, ProductionOrder.Status);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ LotNo := NoSeriesCodeunit.GetNextNo(Item."Lot Nos.");
+ LibraryManufacturing.CreateProdOrderItemTracking(ReservationEntry, ProdOrderLine, '', LotNo, Quantity);
+
+ // [GIVEN] Update Subcontracting Management Setup with Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Verify Data Consistency: Item tracking information is consistent across all documents
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ // [THEN] Verify Data Consistency: Reservation entries exist for warehouse receipt with lot number
+ HandlingSerialNo := '';
+ HandlingLotNo := LotNo;
+ HandlingQty := Quantity;
+
+ // [GIVEN] Create Warehouse Employee for Location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ WarehouseReceiptPage.OpenView();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [WHEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify Data Consistency: Item tracking is correctly passed to the put-away
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(LotNo, WarehouseActivityLine."Lot No.",
+ 'Lot No. should be propagated to Put-away Line');
+
+ // [WHEN] Post Put-away
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify Posted Entries: All posted entries correctly reflect assigned item tracking
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Lot No.", LotNo);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.FindFirst();
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry Quantity should match');
+ Assert.AreEqual(Location.Code, ItemLedgerEntry."Location Code", 'Item Ledger Entry Location Code should match');
+ end;
+
+ [Test]
+ [HandlerFunctions('ItemTrackingLinesPageHandler')]
+ procedure FullProcessWithLotTrackingFromWhseReceiptLine()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ NoSeriesCodeunit: Codeunit "No. Series";
+ LotNo: Code[50];
+ Quantity: Decimal;
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ begin
+ // [SCENARIO] Full Process with Lot Tracking from Warehouse Receipt Line
+ // [FEATURE] Subcontracting Item Tracking - Assign tracking at warehouse receipt stage
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Lot-tracked Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Lot-tracked Item for Production include Routing and Prod. BOM
+ SubcWarehouseLibrary.CreateLotTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Update Vendor with Subcontracting Location Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Update Subcontracting Management Setup with Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Assign Lot Number at Warehouse Receipt Line stage using Item Tracking Lines page
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ LotNo := NoSeriesCodeunit.GetNextNo(Item."Lot Nos.");
+
+ // [GIVEN] Create Warehouse Employee for Location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [WHEN] Insert item tracking via page
+ HandlingMode := HandlingMode::Insert;
+ HandlingSerialNo := '';
+ HandlingLotNo := LotNo;
+ HandlingQty := Quantity;
+
+ WarehouseReceiptPage.OpenEdit();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [THEN] Verify item tracking is correctly assigned and source type is Prod. Order Line
+ HandlingMode := HandlingMode::Verify;
+ HandlingSourceType := Database::"Prod. Order Line";
+
+ WarehouseReceiptPage.OpenView();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [WHEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify Data Consistency: Item tracking is correctly passed to put-away
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(LotNo, WarehouseActivityLine."Lot No.",
+ 'Lot No. should be propagated to Put-away Line');
+
+ // [WHEN] Post Put-away
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify Posted Entries: Posted entries correctly reflect assigned item tracking
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Lot No.", LotNo);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.FindFirst();
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry Quantity should match');
+ Assert.AreEqual(LotNo, ItemLedgerEntry."Lot No.", 'Item Ledger Entry Lot No. should match');
+ end;
+
+ [Test]
+ [HandlerFunctions('ItemTrackingLinesPageHandler')]
+ procedure ItemTrackingForNonLastOperations()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ ReservationEntry: Record "Reservation Entry";
+ Vendor: Record Vendor;
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ NoSeriesCodeunit: Codeunit "No. Series";
+ LotNo: Code[50];
+ Quantity: Decimal;
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ begin
+ // [SCENARIO] Item Tracking for Non-Last Operations
+ // [FEATURE] Subcontracting Item Tracking - Intermediate Operations with Lot Numbers
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Lot-tracked Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Lot-tracked Item for Production include Routing and Prod. BOM
+ SubcWarehouseLibrary.CreateLotTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Location with Warehouse Handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create Warehouse Employee for Location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Update Vendor with Subcontracting Location Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Assign Lot Number to Production Order Line
+ ProdOrderLine.SetRange(Status, ProductionOrder.Status);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ LotNo := NoSeriesCodeunit.GetNextNo(Item."Lot Nos.");
+ LibraryManufacturing.CreateProdOrderItemTracking(ReservationEntry, ProdOrderLine, '', LotNo, Quantity);
+
+ // [GIVEN] Update Subcontracting Management Setup with Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order for intermediate operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Verify Data Consistency: Item tracking is correctly handled on warehouse receipt
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ Assert.AreEqual(WarehouseReceiptLine."Subc. Purchase Line Type"::LastOperation,
+ WarehouseReceiptLine."Subc. Purchase Line Type",
+ 'Warehouse Receipt Line should be marked as Intermediate Operation');
+
+ // [THEN] Verify Data Consistency: Reservation entries exist for non-last operation
+ HandlingSerialNo := '';
+ HandlingLotNo := LotNo;
+ HandlingQty := Quantity;
+
+ WarehouseReceiptPage.OpenView();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Posted Entries: Posted entries reflect correct item tracking
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ PostedWhseReceiptLine.FindFirst();
+
+ PostedWhseReceiptHeader.Get(PostedWhseReceiptLine."No.");
+
+ // [THEN] Verify Posted Entries: Item ledger entries contain correct lot number
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Lot No.", LotNo);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.FindFirst();
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry Quantity should match for non-last operation');
+ Assert.AreEqual(LotNo, ItemLedgerEntry."Lot No.", 'Item Ledger Entry Lot No. should match for non-last operation');
+ end;
+
+ [ModalPageHandler]
+ procedure ItemTrackingLinesPageHandler(var ItemTrackingLines: TestPage "Item Tracking Lines")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ case HandlingMode of
+ HandlingMode::Verify:
+ begin
+ ItemTrackingLines.First();
+ if HandlingSerialNo <> '' then
+ Assert.AreEqual(HandlingSerialNo, Format(ItemTrackingLines."Serial No.".Value), 'Serial No. mismatch');
+ if HandlingLotNo <> '' then
+ Assert.AreEqual(HandlingLotNo, Format(ItemTrackingLines."Lot No.".Value), 'Lot No. mismatch');
+
+ Assert.AreEqual(HandlingQty, ItemTrackingLines."Quantity (Base)".AsDecimal(), 'Quantity mismatch');
+
+ if HandlingSourceType <> 0 then begin
+ ReservationEntry.SetRange("Serial No.", Format(ItemTrackingLines."Serial No.".Value));
+ ReservationEntry.SetRange("Lot No.", Format(ItemTrackingLines."Lot No.".Value));
+ ReservationEntry.FindFirst();
+ Assert.AreEqual(HandlingSourceType, ReservationEntry."Source Type",
+ 'Reservation Entry Source Type should be Prod. Order Line');
+ end;
+ end;
+ HandlingMode::Insert:
+ begin
+ ItemTrackingLines.New();
+ if HandlingSerialNo <> '' then
+ ItemTrackingLines."Serial No.".SetValue(HandlingSerialNo);
+ if HandlingLotNo <> '' then
+ ItemTrackingLines."Lot No.".SetValue(HandlingLotNo);
+
+ ItemTrackingLines."Quantity (Base)".SetValue(HandlingQty);
+ end;
+ end;
+ ItemTrackingLines.OK().Invoke();
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseLocationConfig.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseLocationConfig.Codeunit.al
new file mode 100644
index 0000000000..6b868b25e3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseLocationConfig.Codeunit.al
@@ -0,0 +1,460 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Request;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+
+codeunit 149907 "Subc. Whse Location Config"
+{
+ // [FEATURE] Subcontracting - Location Configuration Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryUtility: Codeunit "Library - Utility";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Location Config");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Location Config");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Location Config");
+ end;
+
+ [Test]
+ procedure LocationWithRequirePutawayDisabled_DirectInventoryUpdateAndLedgerVerification()
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ TotalItemLedgerQty: Decimal;
+ begin
+ // [SCENARIO] Process subcontracting receipt in location where put-away is not required and verify all ledger entries are correct
+ // [FEATURE] Subcontracting - Location Configuration
+
+ // [GIVEN] Complete Setup of Manufacturing
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 15);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Production BOM and Routing
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Require Receive enabled but Require Put-away disabled
+ SubcWarehouseLibrary.CreateLocationWithRequireReceiveOnly(Location);
+
+ // [GIVEN] Create Warehouse Employee for the location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor with Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Post Warehouse Receipt
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ // [THEN] Verify: Posting warehouse receipt directly updates inventory
+ PostedWhseReceiptHeader.SetRange("Whse. Receipt No.", WarehouseReceiptHeader."No.");
+ Assert.RecordIsNotEmpty(PostedWhseReceiptHeader);
+ PostedWhseReceiptHeader.FindFirst();
+
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsNotEmpty(PostedWhseReceiptLine);
+ PostedWhseReceiptLine.FindFirst();
+
+ // [THEN] Verify: No put-away document created
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ WarehouseActivityLine.SetRange("Location Code", Location.Code);
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+
+ // [THEN] Verify: Item Ledger Entry is created with correct quantity
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.FindFirst();
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity,
+ 'Item Ledger Entry Quantity should match the posted quantity');
+
+ // [THEN] Verify: Capacity Ledger Entry exists
+ CapacityLedgerEntry.SetRange("Order Type", CapacityLedgerEntry."Order Type"::Production);
+ CapacityLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+
+ // [THEN] Verify: Output Quantity in Capacity Ledger Entry
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+ Assert.AreEqual(Quantity, CapacityLedgerEntry."Output Quantity", 'Capacity Ledger Entry Output Quantity should match the expected quantity');
+
+ // [THEN] Verify: Quantity Reconciliation - all quantities posted correctly
+ Assert.AreEqual(Quantity, PostedWhseReceiptLine.Quantity,
+ 'Posted Warehouse Receipt Line should have the full quantity');
+ Assert.AreEqual(PostedWhseReceiptLine.Quantity * PostedWhseReceiptLine."Qty. per Unit of Measure", PostedWhseReceiptLine."Qty. (Base)",
+ 'Qty. (Base) should equal Quantity * Qty. per Unit of Measure');
+
+ // [THEN] Verify: Item Ledger Entries are correct with production order linkage
+ ItemLedgerEntry.Reset();
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Order Type", ItemLedgerEntry."Order Type"::Production);
+ ItemLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ TotalItemLedgerQty := 0;
+ if ItemLedgerEntry.FindSet() then
+ repeat
+ TotalItemLedgerQty += ItemLedgerEntry.Quantity;
+ until ItemLedgerEntry.Next() = 0;
+
+ Assert.AreEqual(Quantity, TotalItemLedgerQty,
+ 'Total Item Ledger Entry Quantity should match posted quantity');
+
+ // [THEN] Verify: Capacity Ledger Entries exist and are correct
+ SubcWarehouseLibrary.VerifyCapacityLedgerEntry(WorkCenter[2]."No.", Quantity);
+ end;
+
+ [Test]
+ procedure LocationWithRequirePutawayDisabled_NonLastOperation()
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Process subcontracting receipt for non-last operation in location where put-away is not required
+ // [FEATURE] Subcontracting - Location Configuration - Non-Last Operation
+
+ // [GIVEN] Complete Setup of Manufacturing
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 15);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Production BOM and Routing (non-last operation)
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLinkForBothOperations(Item, WorkCenter);
+
+ // [GIVEN] Create Location with Require Receive enabled but Require Put-away disabled
+ SubcWarehouseLibrary.CreateLocationWithRequireReceiveOnly(Location);
+
+ // [GIVEN] Create Warehouse Employee for the location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor with Location (using first work center for non-last operation)
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for non-last operation (WorkCenter[1])
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Post Warehouse Receipt
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ // [THEN] Verify: Posted warehouse receipt exists
+ PostedWhseReceiptHeader.SetRange("Whse. Receipt No.", WarehouseReceiptHeader."No.");
+ Assert.RecordIsNotEmpty(PostedWhseReceiptHeader);
+ PostedWhseReceiptHeader.FindFirst();
+
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsNotEmpty(PostedWhseReceiptLine);
+ PostedWhseReceiptLine.FindFirst();
+
+ // [THEN] Verify: No put-away document created for non-last operation
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ WarehouseActivityLine.SetRange("Location Code", Location.Code);
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+
+ // [THEN] Verify: NO Item Ledger Entry is created for non-last operation
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsEmpty(ItemLedgerEntry);
+
+ // [THEN] Verify: Capacity Ledger Entry exists for non-last operation
+ CapacityLedgerEntry.SetRange("Order Type", CapacityLedgerEntry."Order Type"::Production);
+ CapacityLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+
+ // [THEN] Verify: Quantity Reconciliation
+ Assert.AreEqual(Quantity, PostedWhseReceiptLine.Quantity,
+ 'Posted Warehouse Receipt Line should have the full quantity');
+ Assert.AreEqual(PostedWhseReceiptLine.Quantity * PostedWhseReceiptLine."Qty. per Unit of Measure", PostedWhseReceiptLine."Qty. (Base)",
+ 'Qty. (Base) should equal Quantity * Qty. per Unit of Measure');
+ end;
+
+ [Test]
+ procedure LocationWithBinMandatoryOnly_StandardPostingProcess()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Verify standard posting process and bin handling for Bin Mandatory Only location
+ // [FEATURE] Subcontracting - Location Configuration
+
+ // [GIVEN] Complete Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create Manufacturing Setup
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Bin Mandatory only
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(Location);
+
+ // [GIVEN] Create a bin for the location
+ LibraryWarehouse.CreateBin(Bin, Location.Code,
+ LibraryUtility.GenerateRandomCode(Bin.FieldNo(Code), Database::Bin), '', '');
+
+ // [GIVEN] Configure Vendor
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Update Purchase Line with Bin Code (simulating user input)
+ PurchaseLine.Validate("Bin Code", Bin.Code);
+ PurchaseLine.Modify(true);
+
+ // [GIVEN] Release Purchase Document
+ LibraryPurchase.ReleasePurchaseDocument(PurchaseHeader);
+
+ // [THEN] Verify: No Warehouse Receipt can be created (location has Bin Mandatory but not Require Receive)
+ VerifyNoWarehouseReceiptCreated(PurchaseHeader);
+
+ // [WHEN] Post Purchase Order directly (standard posting)
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [THEN] Verify: Item Ledger Entry created with correct bin
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.FindFirst();
+
+ // [THEN] Verify: Inventory updated in correct bin
+ Assert.AreEqual(Location.Code, ItemLedgerEntry."Location Code",
+ 'Item Ledger Entry should have correct location');
+ Assert.AreEqual(Quantity, ItemLedgerEntry.Quantity,
+ 'Item Ledger Entry should have correct quantity');
+
+ // [THEN] Verify: Capacity Ledger Entry created
+ SubcWarehouseLibrary.VerifyCapacityLedgerEntry(WorkCenter[2]."No.", Quantity);
+ end;
+
+ [Test]
+ procedure LocationWithBinMandatoryOnly_NonLastOperation()
+ var
+ Bin: Record Bin;
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Verify standard posting process for non-last operation with Bin Mandatory Only location
+ // [FEATURE] Subcontracting - Location Configuration - Non-Last Operation
+
+ // [GIVEN] Complete Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create Manufacturing Setup
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLinkForBothOperations(Item, WorkCenter);
+
+ // [GIVEN] Create Location with Bin Mandatory only
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(Location);
+
+ // [GIVEN] Create a bin for the location
+ LibraryWarehouse.CreateBin(Bin, Location.Code,
+ LibraryUtility.GenerateRandomCode(Bin.FieldNo(Code), Database::Bin), '', '');
+
+ // [GIVEN] Configure Vendor (using first work center for non-last operation)
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for non-last operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Update Purchase Line with Bin Code (simulating user input)
+ PurchaseLine.Validate("Bin Code", Bin.Code);
+ PurchaseLine.Modify(true);
+
+ // [WHEN] Post Purchase Order directly (standard posting)
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [THEN] Verify: NO Item Ledger Entry created for non-last operation
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsEmpty(ItemLedgerEntry);
+
+ // [THEN] Verify: Capacity Ledger Entry created for non-last operation
+ CapacityLedgerEntry.SetRange("Order Type", CapacityLedgerEntry."Order Type"::Production);
+ CapacityLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+ SubcWarehouseLibrary.VerifyCapacityLedgerEntry(WorkCenter[1]."No.", Quantity);
+ end;
+
+ local procedure VerifyNoWarehouseReceiptCreated(PurchaseHeader: Record "Purchase Header")
+ var
+ WarehouseRequest: Record "Warehouse Request";
+ begin
+ // Verify that no warehouse request exists for this purchase order
+ WarehouseRequest.SetRange("Source Type", Database::"Purchase Line");
+ WarehouseRequest.SetRange("Source Subtype", PurchaseHeader."Document Type".AsInteger());
+ WarehouseRequest.SetRange("Source No.", PurchaseHeader."No.");
+ Assert.RecordIsEmpty(WarehouseRequest);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseNonLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseNonLastOp.Codeunit.al
new file mode 100644
index 0000000000..f9bb144670
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseNonLastOp.Codeunit.al
@@ -0,0 +1,652 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+using Microsoft.Warehouse.Worksheet;
+
+codeunit 149903 "Subc. Whse Non-Last Op."
+{
+ // [FEATURE] Subcontracting Warehouse Non-Last Operation Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ HandlingLotNo: Code[50];
+ HandlingSerialNo: Code[50];
+ HandlingQty: Decimal;
+ HandlingSourceType: Integer;
+ HandlingMode: Option Verify,Insert;
+
+ local procedure Initialize()
+ begin
+ HandlingSerialNo := '';
+ HandlingLotNo := '';
+ HandlingQty := 0;
+ HandlingMode := HandlingMode::Verify;
+ HandlingSourceType := 0;
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Non-Last Op.");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Non-Last Op.");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Non-Last Op.");
+ end;
+
+ [Test]
+ procedure CreateAndPostWhseReceiptForNonLastOperationFullQuantity()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Create and Post WH Receipt for Non-Last Operation (Full Quantity)
+ // [FEATURE] Subcontracting Warehouse Receipt - Non-Last Operation
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(10, 20);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing (with non-last operation) and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link for first operation (non-last)
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[1]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling (Require Receive and Put-away)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create default bin for location
+ LibraryWarehouse.CreateBin(Bin, Location.Code, 'DEFAULT', '', '');
+ Location.Validate("Default Bin Code", Bin.Code);
+ Location.Modify(true);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order for first operation (non-last)
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Verify warehouse receipt line is created with correct data
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.SetRange("Source Document", WarehouseReceiptLine."Source Document"::"Purchase Order");
+ WarehouseReceiptLine.SetRange("Source No.", PurchaseHeader."No.");
+ Assert.RecordIsNotEmpty(WarehouseReceiptLine);
+
+ WarehouseReceiptLine.FindFirst();
+
+ // [THEN] Verify Data Consistency: Warehouse receipt line has correct item and quantity
+ Assert.AreEqual(Item."No.", WarehouseReceiptLine."Item No.",
+ 'Warehouse Receipt Line Item No. should match the Production Order Item');
+ Assert.AreEqual(Quantity, WarehouseReceiptLine.Quantity,
+ 'Warehouse Receipt Line Quantity should match the Purchase Order Quantity');
+
+ // [THEN] Verify Subcontracting Line Type is set to Not Last Operation
+ Assert.AreEqual("Subc. Purchase Line Type"::NotLastOperation,
+ WarehouseReceiptLine."Subc. Purchase Line Type",
+ 'Warehouse Receipt Line should be marked as Not Last Operation');
+
+ // [THEN] Verify NotLastOperation has zero base quantities (no inventory movement)
+ // CRITICAL: For NotLastOperation, base quantities should be zero as there is no physical inventory movement
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. (Base)", 'NotLastOperation should have zero Qty. (Base)');
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. per Unit of Measure", 'NotLastOperation should have zero Qty. per UoM');
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Posted Entries: Posted warehouse receipt created
+ Assert.AreNotEqual('', PostedWhseReceiptHeader."No.",
+ 'Posted warehouse receipt should be created');
+
+ // [THEN] Verify Posted Entries: Posted warehouse receipt has correct quantity
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsNotEmpty(PostedWhseReceiptLine);
+ PostedWhseReceiptLine.FindFirst();
+
+ Assert.AreEqual(Quantity, PostedWhseReceiptLine.Quantity,
+ 'Posted warehouse receipt line should have correct quantity');
+ Assert.AreEqual(0, PostedWhseReceiptLine."Qty. (Base)",
+ 'Posted warehouse receipt line for NotLastOperation should have zero Qty. (Base)');
+
+ //Test Base Quantity for NotLastOperation is zero
+ // [THEN] Verify Quantity Reconciliation: Quantities reconciled between PO and posted receipt
+ Assert.AreEqual(PurchaseLine.Quantity, PostedWhseReceiptLine.Quantity,
+ 'Posted receipt quantity should match purchase order quantity');
+
+ // [THEN] Verify Ledger Entries: Capacity ledger entries created for non-last operation with zero output
+ VerifyCapacityLedgerEntriesOutputQuantity(ProductionOrder."No.", WorkCenter[1]."No.", Quantity);
+
+ // [THEN] Verify Ledger Entries: Item ledger entries NOT created for non-last operation
+ VerifyItemLedgerEntriesDoNotExist(Item."No.", Location.Code);
+ end;
+
+ [Test]
+ procedure CreateAndPostWhseReceiptForNonLastOperationPartialQuantity()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PartialQuantity: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Create and Post WH Receipt for Non-Last Operation (Partial Quantity)
+ // [FEATURE] Subcontracting Warehouse Receipt - Non-Last Operation Partial Posting
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(20, 40);
+ PartialQuantity := Round(Quantity / 2, 1);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link for first operation (non-last)
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[1]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create default bin for location
+ LibraryWarehouse.CreateBin(Bin, Location.Code, 'PARTIAL', '', '');
+ Location.Validate("Default Bin Code", Bin.Code);
+ Location.Modify(true);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for non-last operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Post Partial Warehouse Receipt
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, PartialQuantity, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Posted Entries: Posted warehouse receipt created for partial quantity
+ Assert.AreNotEqual('', PostedWhseReceiptHeader."No.",
+ 'Posted warehouse receipt should be created');
+
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsNotEmpty(PostedWhseReceiptLine);
+ PostedWhseReceiptLine.FindFirst();
+
+ Assert.AreEqual(PartialQuantity, PostedWhseReceiptLine.Quantity,
+ 'Posted warehouse receipt line should have correct partial quantity');
+
+ // [THEN] Verify Quantity Reconciliation: Remaining quantity is correct on warehouse receipt
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(Quantity - PartialQuantity, WarehouseReceiptLine."Qty. Outstanding",
+ 'Warehouse receipt line should have correct outstanding quantity after partial posting');
+
+ // [THEN] Verify Quantity Reconciliation: Original and posted quantities reconciled
+ Assert.AreEqual(Quantity, WarehouseReceiptLine.Quantity,
+ 'Original warehouse receipt quantity should remain unchanged');
+
+ // [THEN] Verify Ledger Entries: Capacity ledger entries created for non-last operation
+ VerifyCapacityLedgerEntriesExist(ProductionOrder."No.", WorkCenter[1]."No.");
+
+ // [THEN] Verify Ledger Entries: Item ledger entries NOT created for non-last operation
+ VerifyItemLedgerEntriesDoNotExist(Item."No.", Location.Code);
+ end;
+
+ [Test]
+ procedure PreventPutAwayCreationForNonLastOperation_AllMethods()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WhseWorksheetLine: Record "Whse. Worksheet Line";
+ WhseWorksheetName: Record "Whse. Worksheet Name";
+ WhseWorksheetTemplate: Record "Whse. Worksheet Template";
+ WorkCenter: array[2] of Record "Work Center";
+ DirectPutAwayPrevented: Boolean;
+ WorksheetPutAwayPrevented: Boolean;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Prevent Put-away Creation for Non-Last Operation via All Methods
+ // [FEATURE] Subcontracting Warehouse Receipt - Put-away Prevention (Combined Test)
+ // Tests prevention of put-away creation from both:
+ // 1. Direct creation from Posted Warehouse Receipt
+ // 2. Put-away Worksheet
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(10, 20);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link for first operation (non-last)
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[1]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Put-away Worksheet enabled
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create default bin for location
+ LibraryWarehouse.CreateBin(Bin, Location.Code, 'PREVENT-ALL', '', '');
+ Location.Validate("Default Bin Code", Bin.Code);
+ Location."Use Put-away Worksheet" := true;
+ Location.Modify(true);
+
+ // [GIVEN] Create Warehouse Employee for the location (required for put-away worksheet)
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for non-last operation
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create and Post Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // ============================================================
+ // METHOD 1: Test Direct Put-away Creation from Posted Whse Receipt
+ // ============================================================
+
+ // [WHEN] Attempt to create put-away from posted warehouse receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify put-away creation is prevented (direct method)
+ DirectPutAwayPrevented := (WarehouseActivityHeader."No." = '');
+ Assert.IsTrue(DirectPutAwayPrevented,
+ 'Put-away should not be created for non-last operation via direct creation from Posted Whse Receipt');
+
+ // [THEN] Verify no put-away documents exist for this location
+ WarehouseActivityHeader.Reset();
+ WarehouseActivityHeader.SetRange("Location Code", Location.Code);
+ WarehouseActivityHeader.SetRange(Type, WarehouseActivityHeader.Type::"Put-away");
+ Assert.RecordIsEmpty(WarehouseActivityHeader, CompanyName);
+
+ // ============================================================
+ // METHOD 2: Test Put-away Creation from Put-away Worksheet
+ // ============================================================
+
+ // [WHEN] Create Put-away Worksheet
+ SubcWarehouseLibrary.CreatePutAwayWorksheet(WhseWorksheetTemplate, WhseWorksheetName, Location.Code);
+
+ // [WHEN] Get Warehouse Documents for Put-away Worksheet
+ WorksheetPutAwayPrevented := not TryGetWarehouseDocumentsForPutAwayWorksheet(
+ WhseWorksheetTemplate.Name, WhseWorksheetName, Location.Code);
+
+ // [THEN] Verify put-away creation is prevented (worksheet method) - either errors or no lines created
+ Assert.IsTrue(WorksheetPutAwayPrevented, 'Put-away should not be created for non-last operation via Put-away Worksheet');
+ WhseWorksheetLine.SetRange("Worksheet Template Name", WhseWorksheetName."Worksheet Template Name");
+ WhseWorksheetLine.SetRange(Name, WhseWorksheetName.Name);
+ WhseWorksheetLine.SetRange("Location Code", Location.Code);
+ WhseWorksheetLine.SetRange("Item No.", Item."No.");
+
+ // [THEN] No worksheet lines should be created for non-last operation receipts
+ Assert.RecordIsEmpty(WhseWorksheetLine, CompanyName);
+
+ // [THEN] Final verification: Ensure both methods prevented put-away creation
+ Assert.IsTrue(DirectPutAwayPrevented,
+ 'Direct put-away creation from Posted Whse Receipt must be prevented for non-last operation');
+
+ // Note: WorksheetPutAwayPrevented may be true (error) or false (success but no lines created)
+ // The key verification is that no worksheet lines exist for the non-last operation item
+ end;
+
+ [TryFunction]
+ local procedure TryGetWarehouseDocumentsForPutAwayWorksheet(WorksheetTemplateName: Code[10]; WhseWorksheetName: Record "Whse. Worksheet Name"; LocationCode: Code[10])
+ begin
+ SubcWarehouseLibrary.GetWarehouseDocumentsForPutAwayWorksheet(WorksheetTemplateName, WhseWorksheetName, LocationCode);
+ end;
+
+ local procedure VerifyCapacityLedgerEntriesExist(ProdOrderNo: Code[20]; WorkCenterNo: Code[20])
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ begin
+ // Verify that capacity ledger entries were created for the production order and work center
+ CapacityLedgerEntry.SetRange("Order No.", ProdOrderNo);
+ CapacityLedgerEntry.SetRange("Work Center No.", WorkCenterNo);
+ Assert.RecordCount(CapacityLedgerEntry, 1);
+ end;
+
+ local procedure VerifyCapacityLedgerEntriesOutputQuantity(ProdOrderNo: Code[20]; WorkCenterNo: Code[20]; ExpectedOutputQuantity: Decimal)
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ begin
+ // Verify that capacity ledger entries were created for the production order and work center
+ CapacityLedgerEntry.SetRange("Order No.", ProdOrderNo);
+ CapacityLedgerEntry.SetRange("Work Center No.", WorkCenterNo);
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+
+ // Verify the output quantity matches the expected value
+ CapacityLedgerEntry.FindFirst();
+ Assert.AreEqual(ExpectedOutputQuantity, CapacityLedgerEntry."Output Quantity" / CapacityLedgerEntry."Qty. per Unit of Measure",
+ 'Capacity Ledger Entry should have correct output quantity');
+ end;
+
+ local procedure VerifyItemLedgerEntriesDoNotExist(ItemNo: Code[20]; LocationCode: Code[10])
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ // Verify that NO item ledger entries were created for non-last operation
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsEmpty(ItemLedgerEntry, CompanyName);
+ end;
+
+ [Test]
+ procedure ItemTrackingNotAllowedForNotLastOperationPurchaseLine()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ PurchaseOrderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Item tracking is not available for non-last operation purchase lines
+ // [FEATURE] Subcontracting Item Tracking - Error when opening item tracking for non-last operation
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Lot-tracked Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateLotTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link for FIRST operation (non-last)
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[1]."No.");
+
+ // [GIVEN] Create simple Location without Warehouse Handling (so item tracking can be opened directly from purchase line)
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for NON-LAST operation (WorkCenter[1])
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [THEN] Verify: Purchase Line is marked as NotLastOperation
+ Assert.AreEqual("Subc. Purchase Line Type"::NotLastOperation, PurchaseLine."Subc. Purchase Line Type",
+ 'Purchase Line should be marked as NotLastOperation');
+
+ // [THEN] Verify: Base quantities are zero for non-last operation (no physical inventory movement)
+ Assert.AreEqual(0, PurchaseLine."Quantity (Base)",
+ 'NotLastOperation Purchase Line should have zero Quantity (Base)');
+
+ // [WHEN] Try to open Item Tracking Lines from Purchase Order Page
+ // [THEN] An error should be raised because item tracking is not allowed for non-last operations
+ PurchaseOrderPage.OpenEdit();
+ PurchaseOrderPage.GoToRecord(PurchaseHeader);
+ PurchaseOrderPage.PurchLines.GoToRecord(PurchaseLine);
+ asserterror PurchaseOrderPage.PurchLines."Item Tracking Lines".Invoke();
+
+ // [THEN] Verify error message indicates item tracking is not available for non-last operation
+ Assert.ExpectedError('Item tracking lines can only be viewed for subcontracting purchase lines which are linked to a routing line which is the last operation.');
+ end;
+
+ [Test]
+ procedure ItemTrackingNotAllowedForNotLastOperationWarehouseReceiptLine()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ begin
+ // [SCENARIO] Item tracking is not available for non-last operation warehouse receipt lines
+ // [FEATURE] Subcontracting Item Tracking - Error when opening item tracking for non-last operation from Warehouse Receipt
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Lot-tracked Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateLotTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link for FIRST operation (non-last)
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[1]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling (Require Receive)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create Warehouse Employee for Location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order for NON-LAST operation (WorkCenter[1])
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[1]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Verify: Warehouse Receipt Line is marked as NotLastOperation
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual("Subc. Purchase Line Type"::NotLastOperation, WarehouseReceiptLine."Subc. Purchase Line Type",
+ 'Warehouse Receipt Line should be marked as NotLastOperation');
+
+ // [THEN] Verify: Base quantities are zero for non-last operation
+ Assert.AreEqual(0, WarehouseReceiptLine."Qty. (Base)",
+ 'NotLastOperation Warehouse Receipt Line should have zero Qty. (Base)');
+
+ // [WHEN] Try to open Item Tracking Lines from Warehouse Receipt Page
+ // [THEN] An error should be raised because item tracking is not allowed for non-last operations
+ WarehouseReceiptPage.OpenEdit();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ asserterror WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+
+ // [THEN] Verify error message indicates item tracking is not available for non-last operation
+ Assert.ExpectedError('Item tracking lines can only be viewed for subcontracting purchase lines which are linked to a routing line which is the last operation.');
+ end;
+
+ [ModalPageHandler]
+ procedure ItemTrackingLinesPageHandler(var ItemTrackingLines: TestPage "Item Tracking Lines")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ case HandlingMode of
+ HandlingMode::Verify:
+ begin
+ ItemTrackingLines.First();
+ if HandlingSerialNo <> '' then
+ Assert.AreEqual(HandlingSerialNo, Format(ItemTrackingLines."Serial No.".Value), 'Serial No. mismatch');
+ if HandlingLotNo <> '' then
+ Assert.AreEqual(HandlingLotNo, Format(ItemTrackingLines."Lot No.".Value), 'Lot No. mismatch');
+
+ Assert.AreEqual(HandlingQty, ItemTrackingLines."Quantity (Base)".AsDecimal(), 'Quantity mismatch');
+
+ if HandlingSourceType <> 0 then begin
+ ReservationEntry.SetRange("Serial No.", Format(ItemTrackingLines."Serial No.".Value));
+ ReservationEntry.SetRange("Lot No.", Format(ItemTrackingLines."Lot No.".Value));
+ ReservationEntry.FindFirst();
+ Assert.AreEqual(HandlingSourceType, ReservationEntry."Source Type",
+ 'Reservation Entry Source Type should be Prod. Order Line');
+ end;
+ end;
+ HandlingMode::Insert:
+ begin
+ ItemTrackingLines.New();
+ if HandlingSerialNo <> '' then
+ ItemTrackingLines."Serial No.".SetValue(HandlingSerialNo);
+ if HandlingLotNo <> '' then
+ ItemTrackingLines."Lot No.".SetValue(HandlingLotNo);
+
+ ItemTrackingLines."Quantity (Base)".SetValue(HandlingQty);
+ end;
+ end;
+ ItemTrackingLines.OK().Invoke();
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePartialLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePartialLastOp.Codeunit.al
new file mode 100644
index 0000000000..ec111eb910
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePartialLastOp.Codeunit.al
@@ -0,0 +1,674 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+
+codeunit 149902 "Subc. Whse Partial Last Op"
+{
+ // [FEATURE] Subcontracting Warehouse Partial Posting - Last Operation Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ HandlingLotNo: Code[50];
+ HandlingSerialNo: Code[50];
+ HandlingQty: Decimal;
+ HandlingSourceType: Integer;
+ HandlingMode: Option Verify,Insert;
+
+ local procedure Initialize()
+ begin
+ HandlingSerialNo := '';
+ HandlingLotNo := '';
+ HandlingQty := 0;
+ HandlingMode := HandlingMode::Verify;
+ HandlingSourceType := 0;
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Partial Last Op");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Partial Last Op");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Partial Last Op");
+ end;
+
+ [Test]
+ procedure PartialWhseReceiptPostingForLastOperation()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PartialQuantity: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Post partial quantity of warehouse receipt for Last Operation
+ // [FEATURE] Subcontracting Warehouse Partial Posting - Last Operation
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(10, 20);
+ PartialQuantity := Round(Quantity / 2, 1);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Post Partial Warehouse Receipt
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, PartialQuantity, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Posted Entries: Posted warehouse receipt created for partial quantity
+ Assert.AreNotEqual('', PostedWhseReceiptHeader."No.",
+ 'Posted warehouse receipt should be created');
+
+ // [THEN] Verify Quantity Reconciliation: Posted warehouse receipt has correct partial quantity
+ VerifyPostedWhseReceiptQuantity(PostedWhseReceiptHeader, Item."No.", PartialQuantity);
+
+ // [THEN] Verify Quantity Reconciliation: Remaining quantity is correct on warehouse receipt
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(Quantity - PartialQuantity, WarehouseReceiptLine."Qty. Outstanding",
+ 'Warehouse receipt line should have correct outstanding quantity after partial posting');
+
+ // [THEN] Verify base quantity is correctly calculated from quantity and UoM
+ Assert.AreEqual(Quantity * WarehouseReceiptLine."Qty. per Unit of Measure",
+ WarehouseReceiptLine."Qty. (Base)",
+ 'Qty. (Base) should equal Quantity * Qty. per Unit of Measure');
+
+ // [THEN] Verify base quantity outstanding is correctly calculated after partial posting
+ Assert.AreEqual((Quantity - PartialQuantity) * WarehouseReceiptLine."Qty. per Unit of Measure",
+ WarehouseReceiptLine."Qty. Outstanding (Base)",
+ 'Qty. Outstanding (Base) should be correctly calculated after partial posting');
+ end;
+
+ [Test]
+ procedure PartialPutAwayPostingForLastOperation()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ PartialPutAwayQty: Decimal;
+ PartialReceiptQty: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Post partial quantity of put-away created from partially received warehouse receipt for Last Operation
+ // [FEATURE] Subcontracting Warehouse Partial Posting - Last Operation
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(20, 40);
+ PartialReceiptQty := Round(Quantity / 2, 1);
+ PartialPutAwayQty := Round(PartialReceiptQty / 2, 1);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create and Post Partial Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, PartialReceiptQty, PostedWhseReceiptHeader);
+
+ // [GIVEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [WHEN] Post Partial Put-away
+ SubcWarehouseLibrary.PostPartialPutAway(WarehouseActivityHeader, PartialPutAwayQty);
+
+ // [THEN] Verify Posted Entries: Item ledger entry is created for partial quantity
+ VerifyItemLedgerEntry(Item."No.", PartialReceiptQty, Location.Code);
+
+ // [THEN] Verify Posted Entries: Capacity ledger entry is created for partial quantity
+ VerifyCapacityLedgerEntry(WorkCenter[2]."No.", PartialReceiptQty);
+
+ // [THEN] Verify Bin Management: Inventory updated for partial quantity
+ VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.", PartialPutAwayQty);
+
+ // [THEN] Verify Quantity Reconciliation: Put-away has correct outstanding quantity
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ if WarehouseActivityLine.FindFirst() then
+ Assert.AreEqual(PartialReceiptQty - PartialPutAwayQty,
+ WarehouseActivityLine."Qty. Outstanding",
+ 'Put-away line should have correct outstanding quantity after partial posting');
+ end;
+
+ [Test]
+ procedure MultiStepPartialPostingForLastOperation()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptHeader2: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityHeader2: Record "Warehouse Activity Header";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ FirstPutAwayQty: Decimal;
+ FirstReceiptQty: Decimal;
+ SecondPutAwayQty: Decimal;
+ SecondReceiptQty: Decimal;
+ TotalQuantity: Decimal;
+ begin
+ // [SCENARIO] Post single order in multiple partial steps until full quantity processed for Last Operation
+ // [FEATURE] Subcontracting Warehouse Multi-step Partial Posting - Last Operation
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ TotalQuantity := LibraryRandom.RandIntInRange(30, 60);
+ FirstReceiptQty := Round(TotalQuantity * 0.3, 1);
+ SecondReceiptQty := Round(TotalQuantity * 0.4, 1);
+
+ FirstPutAwayQty := Round(FirstReceiptQty * 0.5, 1);
+ SecondPutAwayQty := FirstReceiptQty - FirstPutAwayQty;
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", TotalQuantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Step 1: Post first partial warehouse receipt
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, FirstReceiptQty, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Quantity Reconciliation: First receipt quantity is correct
+ VerifyPostedWhseReceiptQuantity(PostedWhseReceiptHeader, Item."No.", FirstReceiptQty);
+
+ // [WHEN] Step 2: Create and post first partial put-away
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+ SubcWarehouseLibrary.PostPartialPutAway(WarehouseActivityHeader, FirstPutAwayQty);
+
+ // [THEN] Verify Quantity Reconciliation: First put-away quantity is correct
+ VerifyItemLedgerEntry(Item."No.", FirstReceiptQty, Location.Code);
+ VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.", FirstPutAwayQty);
+
+ // [WHEN] Step 3: Post remaining quantity from first put-away
+ SubcWarehouseLibrary.PostPartialPutAway(WarehouseActivityHeader, SecondPutAwayQty);
+
+ // [THEN] Verify Quantity Reconciliation: Cumulative quantity is correct
+ VerifyItemLedgerEntry(Item."No.", FirstReceiptQty, Location.Code);
+ VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.", FirstPutAwayQty + SecondPutAwayQty);
+
+ // [WHEN] Step 4: Post second partial warehouse receipt
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, SecondReceiptQty, PostedWhseReceiptHeader2);
+
+ // [THEN] Verify Quantity Reconciliation: Second receipt quantity is correct
+ VerifyPostedWhseReceiptQuantity(PostedWhseReceiptHeader2, Item."No.", SecondReceiptQty);
+
+ // [WHEN] Step 5: Create and post second put-away (full quantity)
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader2, WarehouseActivityHeader2);
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader2);
+
+ // [THEN] Verify Quantity Reconciliation: Total posted quantity through all steps
+ VerifyItemLedgerEntry(Item."No.",
+ FirstReceiptQty + SecondReceiptQty, Location.Code);
+ VerifyCapacityLedgerEntry(WorkCenter[2]."No.",
+ FirstReceiptQty + SecondReceiptQty);
+ VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.",
+ FirstReceiptQty + SecondReceiptQty);
+ // [WHEN] Step 6: Post remaining warehouse receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [WHEN] Step 7: Create and post final put-away
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify Data Consistency: Final quantities match original order quantity
+ VerifyItemLedgerEntry(Item."No.", TotalQuantity, Location.Code);
+ VerifyCapacityLedgerEntry(WorkCenter[2]."No.", TotalQuantity);
+ VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.", TotalQuantity);
+
+ // [THEN] Verify UoM: Base quantity calculations are correct across all documents
+ VerifyUoMBaseQuantityCalculations(Item."No.", TotalQuantity, Location.Code);
+ end;
+
+ [Test]
+ [HandlerFunctions('ItemTrackingLinesPageHandler')]
+ procedure PartialLotPostingWithItemTrackingAndPutAwayRecreation()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ NoSeriesCodeunit: Codeunit "No. Series";
+ LotNo1: Code[50];
+ LotNo2: Code[50];
+ PartialQtyLot1: Decimal;
+ PartialQtyLot2: Decimal;
+ TotalQuantity: Decimal;
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ begin
+ // [SCENARIO] Comprehensive item tracking test: partial lot posting with put-away recreation and quantity matching validation
+ // [FEATURE] Subcontracting Item Tracking - Multiple lots with partial posting, put-away deletion/recreation, and quantity validation
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ TotalQuantity := LibraryRandom.RandIntInRange(30, 60);
+ PartialQtyLot1 := Round(TotalQuantity * 0.3, 1);
+ PartialQtyLot2 := Round(TotalQuantity * 0.3, 1);
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Lot-tracked Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateLotTrackedItemForProductionWithSetup(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bins
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Create Warehouse Employee for Location
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", TotalQuantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ // [GIVEN] Generate lot numbers for multiple lot tracking
+ LotNo1 := NoSeriesCodeunit.GetNextNo(Item."Lot Nos.");
+ LotNo2 := NoSeriesCodeunit.GetNextNo(Item."Lot Nos.");
+
+ // [WHEN] Insert first Lot Number with partial quantity
+ HandlingMode := HandlingMode::Insert;
+ HandlingLotNo := LotNo1;
+ HandlingQty := PartialQtyLot1;
+
+ WarehouseReceiptPage.OpenEdit();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Insert second Lot Number with partial quantity
+ HandlingLotNo := LotNo2;
+ HandlingQty := PartialQtyLot2;
+
+ WarehouseReceiptPage.OpenEdit();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Invoke();
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Post partial warehouse receipt with both lots
+ SubcWarehouseLibrary.PostPartialWarehouseReceipt(WarehouseReceiptHeader, PartialQtyLot1 + PartialQtyLot2, PostedWhseReceiptHeader);
+
+ // [THEN] Verify: Posted warehouse receipt has correct partial quantity
+ VerifyPostedWhseReceiptQuantity(PostedWhseReceiptHeader, Item."No.", PartialQtyLot1 + PartialQtyLot2);
+
+ // [WHEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify: Put-away lines exist with correct lot numbers
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ Assert.AreEqual(2, WarehouseActivityLine.Count(), 'Should have 2 Place lines for 2 lots');
+
+ VerifyWarehouseActivityLineForLot(WarehouseActivityHeader, LotNo1, PartialQtyLot1);
+ VerifyWarehouseActivityLineForLot(WarehouseActivityHeader, LotNo2, PartialQtyLot2);
+
+ // [WHEN] Delete the Put-away to test recreation functionality
+ WarehouseActivityHeader.Delete(true);
+
+ // [WHEN] Recreate Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify: Recreated Put-away has same lot tracking information
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ Assert.AreEqual(2, WarehouseActivityLine.Count(), 'Recreated Put-away should have 2 Place lines for 2 lots');
+
+ VerifyWarehouseActivityLineForLot(WarehouseActivityHeader, LotNo1, PartialQtyLot1);
+ VerifyWarehouseActivityLineForLot(WarehouseActivityHeader, LotNo2, PartialQtyLot2);
+
+ // [THEN] Verify: Remaining quantity on Warehouse Receipt is correct
+ WarehouseReceiptHeader.Get(WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ Assert.AreEqual(TotalQuantity - PartialQtyLot1 - PartialQtyLot2, WarehouseReceiptLine."Qty. Outstanding",
+ 'Remaining quantity on Warehouse Receipt Line should be correct');
+
+ // [WHEN] Post the recreated Put-away
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify: Bin contents are correct for both lots
+ VerifyBinContentsForLot(Location.Code, PutAwayBin.Code, Item."No.", LotNo1, PartialQtyLot1);
+ VerifyBinContentsForLot(Location.Code, PutAwayBin.Code, Item."No.", LotNo2, PartialQtyLot2);
+ end;
+
+ local procedure VerifyItemLedgerEntry(ItemNo: Code[20]; ExpectedQuantity: Decimal; LocationCode: Code[10])
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(ExpectedQuantity, ItemLedgerEntry.Quantity,
+ 'Item Ledger Entry should have correct output quantity');
+ end;
+
+ local procedure VerifyCapacityLedgerEntry(WorkCenterNo: Code[20]; ExpectedQuantity: Decimal)
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ begin
+ CapacityLedgerEntry.SetRange(Type, CapacityLedgerEntry.Type::"Work Center");
+ CapacityLedgerEntry.SetRange("No.", WorkCenterNo);
+ Assert.RecordIsNotEmpty(CapacityLedgerEntry);
+
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+ Assert.AreEqual(ExpectedQuantity, CapacityLedgerEntry."Output Quantity",
+ 'Capacity Ledger Entry should have correct output quantity');
+ end;
+
+ local procedure VerifyBinContents(LocationCode: Code[10]; BinCode: Code[20]; ItemNo: Code[20]; ExpectedQuantity: Decimal)
+ var
+ BinContent: Record "Bin Content";
+ begin
+ BinContent.SetRange("Location Code", LocationCode);
+ BinContent.SetRange("Bin Code", BinCode);
+ BinContent.SetRange("Item No.", ItemNo);
+ Assert.RecordIsNotEmpty(BinContent);
+
+ BinContent.FindFirst();
+ BinContent.CalcFields(Quantity);
+ Assert.AreEqual(ExpectedQuantity, BinContent.Quantity,
+ 'Bin contents should show correct quantity after put-away posting');
+ end;
+
+ local procedure VerifyUoMBaseQuantityCalculations(ItemNo: Code[20]; ExpectedQuantity: Decimal; LocationCode: Code[10])
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(ExpectedQuantity, ItemLedgerEntry.Quantity,
+ 'UoM base quantity calculations should be correct across all documents');
+ end;
+
+ local procedure VerifyWarehouseActivityLineForLot(WarehouseActivityHeader: Record "Warehouse Activity Header"; LotNo: Code[50]; ExpectedQuantity: Decimal)
+ var
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ begin
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityHeader.Type);
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ WarehouseActivityLine.SetRange("Lot No.", LotNo);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(ExpectedQuantity, WarehouseActivityLine.Quantity,
+ 'Warehouse Activity Line should have correct quantity for lot ' + LotNo);
+ end;
+
+ local procedure VerifyPostedWhseReceiptQuantity(var PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header"; ItemNo: Code[20]; ExpectedQuantity: Decimal)
+ var
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ begin
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", ItemNo);
+ Assert.RecordIsNotEmpty(PostedWhseReceiptLine);
+
+ PostedWhseReceiptLine.FindFirst();
+ Assert.AreEqual(ExpectedQuantity, PostedWhseReceiptLine.Quantity,
+ 'Posted warehouse receipt line should have correct quantity');
+ end;
+
+ local procedure VerifyBinContentsForLot(LocationCode: Code[10]; BinCode: Code[20]; ItemNo: Code[20]; LotNo: Code[50]; ExpectedQuantity: Decimal)
+ var
+ BinContent: Record "Bin Content";
+ begin
+ BinContent.SetRange("Location Code", LocationCode);
+ BinContent.SetRange("Bin Code", BinCode);
+ BinContent.SetRange("Item No.", ItemNo);
+#pragma warning disable AA0210
+ BinContent.SetRange("Lot No. Filter", LotNo);
+#pragma warning restore AA0210
+ Assert.RecordIsNotEmpty(BinContent);
+
+ BinContent.FindFirst();
+ BinContent.CalcFields(Quantity);
+ Assert.AreEqual(ExpectedQuantity, BinContent.Quantity,
+ 'Bin contents should show correct quantity for lot ' + LotNo + ' after put-away posting');
+ end;
+
+ [ModalPageHandler]
+ procedure ItemTrackingLinesPageHandler(var ItemTrackingLines: TestPage "Item Tracking Lines")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ case HandlingMode of
+ HandlingMode::Verify:
+ begin
+ ItemTrackingLines.First();
+ if HandlingSerialNo <> '' then
+ Assert.AreEqual(HandlingSerialNo, Format(ItemTrackingLines."Serial No.".Value), 'Serial No. mismatch');
+ if HandlingLotNo <> '' then
+ Assert.AreEqual(HandlingLotNo, Format(ItemTrackingLines."Lot No.".Value), 'Lot No. mismatch');
+
+ Assert.AreEqual(HandlingQty, ItemTrackingLines."Quantity (Base)".AsDecimal(), 'Quantity mismatch');
+
+ if HandlingSourceType <> 0 then begin
+ ReservationEntry.SetRange("Serial No.", Format(ItemTrackingLines."Serial No.".Value));
+ ReservationEntry.SetRange("Lot No.", Format(ItemTrackingLines."Lot No.".Value));
+ ReservationEntry.FindFirst();
+ Assert.AreEqual(HandlingSourceType, ReservationEntry."Source Type",
+ 'Reservation Entry Source Type should be Prod. Order Line');
+ end;
+ end;
+ HandlingMode::Insert:
+ begin
+ ItemTrackingLines.New();
+ if HandlingSerialNo <> '' then
+ ItemTrackingLines."Serial No.".SetValue(HandlingSerialNo);
+ if HandlingLotNo <> '' then
+ ItemTrackingLines."Lot No.".SetValue(HandlingLotNo);
+
+ ItemTrackingLines."Quantity (Base)".SetValue(HandlingQty);
+ end;
+ end;
+ ItemTrackingLines.OK().Invoke();
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePutAwayLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePutAwayLastOp.Codeunit.al
new file mode 100644
index 0000000000..66d0f397f9
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePutAwayLastOp.Codeunit.al
@@ -0,0 +1,276 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+using Microsoft.Warehouse.Worksheet;
+
+codeunit 149901 "Subc. Whse Put-Away LastOp."
+{
+ // [FEATURE] Subcontracting Warehouse Put-away - Last Operation Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Put-Away LastOp.");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Put-Away LastOp.");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Put-Away LastOp.");
+ end;
+
+ [Test]
+ procedure RecreatePutAwayFromPostedWhseReceipt()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ VendorLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityHeader2: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ FirstPutAwayNo: Code[20];
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Recreate Put-away from Posted WH Receipt
+ // [FEATURE] Subcontracting Warehouse Put-away - Recreate Put-away
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 15);
+
+ // [GIVEN] Create Work Centers and Manufacturing Setup
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Production BOM and Routing
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Warehouse Location with bins (Bin Mandatory enabled for Take/Place lines)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := LibraryWarehouse.CreateLocationWithInventoryPostingSetup(VendorLocation);
+ Vendor.Modify();
+
+ // [GIVEN] Create Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order and Warehouse Receipt
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [GIVEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [GIVEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+ FirstPutAwayNo := WarehouseActivityHeader."No.";
+
+ // [WHEN] Delete the created put-away
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("No.", FirstPutAwayNo);
+ WarehouseActivityLine.DeleteAll(true);
+ WarehouseActivityHeader.Delete(true);
+
+ // [WHEN] Recreate put-away from posted warehouse receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader2);
+
+ // [THEN] Verify new put-away creation succeeds
+ Assert.AreNotEqual('', WarehouseActivityHeader2."No.",
+ 'New put-away document should be created');
+ Assert.AreNotEqual(FirstPutAwayNo, WarehouseActivityHeader2."No.",
+ 'New put-away should have different number than deleted one');
+
+ // [THEN] Verify Data Consistency: New put-away has correct data
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader2."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(Item."No.", WarehouseActivityLine."Item No.",
+ 'Recreated put-away should have correct item');
+ Assert.AreEqual(Quantity, WarehouseActivityLine.Quantity,
+ 'Recreated put-away should have correct quantity');
+ Assert.AreEqual(Item."Base Unit of Measure", WarehouseActivityLine."Unit of Measure Code",
+ 'Recreated put-away should have correct UoM');
+ end;
+
+ [Test]
+ procedure CreatePutAwayFromPutAwayWorksheetForLastOperation()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ VendorLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WhseWorksheetLine: Record "Whse. Worksheet Line";
+ WhseWorksheetName: Record "Whse. Worksheet Name";
+ WhseWorksheetTemplate: Record "Whse. Worksheet Template";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Create Put-away from Put-away Worksheet for Last Operation
+ // [FEATURE] Subcontracting Warehouse Put-away - Put-away Worksheet
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(10, 20);
+
+ // [GIVEN] Create Work Centers and Manufacturing Setup
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Production BOM and Routing
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Warehouse Location with bins (Bin Mandatory enabled for Take/Place lines)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Enable Put-away Worksheet - required to use worksheet flow instead of auto-creating put-away
+ Location."Use Put-away Worksheet" := true;
+ Location.Modify(true);
+
+ // [GIVEN] Create Warehouse Employee for the location (required for put-away worksheet)
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := LibraryWarehouse.CreateLocationWithInventoryPostingSetup(VendorLocation);
+ Vendor.Modify();
+
+ // [GIVEN] Create Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order and Warehouse Receipt
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [GIVEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [WHEN] Create Put-away Worksheet
+ SubcWarehouseLibrary.CreatePutAwayWorksheet(WhseWorksheetTemplate, WhseWorksheetName, Location.Code);
+
+ // [WHEN] Get Warehouse Documents into Put-away Worksheet
+ SubcWarehouseLibrary.GetWarehouseDocumentsForPutAwayWorksheet(WhseWorksheetTemplate.Name, WhseWorksheetName, Location.Code);
+
+ // [THEN] Verify worksheet identifies source correctly
+ WhseWorksheetLine.SetRange("Worksheet Template Name", WhseWorksheetName."Worksheet Template Name");
+ WhseWorksheetLine.SetRange(Name, WhseWorksheetName.Name);
+ WhseWorksheetLine.SetRange("Location Code", Location.Code);
+ Assert.RecordIsNotEmpty(WhseWorksheetLine);
+
+ WhseWorksheetLine.FindFirst();
+ Assert.AreEqual(Item."No.", WhseWorksheetLine."Item No.",
+ 'Worksheet line should identify correct item from posted receipt');
+ Assert.AreEqual(Quantity, WhseWorksheetLine.Quantity,
+ 'Worksheet line should show correct quantity');
+
+ // [WHEN] Create Put-away from worksheet
+ SubcWarehouseLibrary.CreatePutAwayFromWorksheet(WhseWorksheetName, WarehouseActivityHeader);
+
+ // [THEN] Verify created put-away is accurate
+ Assert.AreNotEqual('', WarehouseActivityHeader."No.",
+ 'Put-away should be created from worksheet');
+
+ // [THEN] Verify Data Consistency: Put-away has correct data
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(Item."No.", WarehouseActivityLine."Item No.",
+ 'Put-away created from worksheet should have correct item');
+ Assert.AreEqual(Quantity, WarehouseActivityLine.Quantity,
+ 'Put-away created from worksheet should have correct quantity');
+
+ // [THEN] Verify all quantities are reconciled using Qty. (Base) = Quantity * Qty. per Unit of Measure
+ Assert.AreEqual(WarehouseActivityLine.Quantity * WarehouseActivityLine."Qty. per Unit of Measure", WarehouseActivityLine."Qty. (Base)",
+ 'Qty. (Base) should equal Quantity * Qty. per Unit of Measure');
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePutAwayWorksheet.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePutAwayWorksheet.Codeunit.al
new file mode 100644
index 0000000000..7b6add4eae
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhsePutAwayWorksheet.Codeunit.al
@@ -0,0 +1,222 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+using Microsoft.Warehouse.Worksheet;
+
+codeunit 149904 "Subc. Whse Put-Away Worksheet"
+{
+ // [FEATURE] Subcontracting Warehouse Put-away Worksheet Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Put-Away Worksheet");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Put-Away Worksheet");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Put-Away Worksheet");
+ end;
+
+ [Test]
+ procedure CreateSinglePutAwayFromMultiplePostedWhseReceipts()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: array[2] of Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: array[2] of Record "Posted Whse. Receipt Header";
+ ProductionOrder: array[2] of Record "Production Order";
+ PurchaseHeader: array[2] of Record "Purchase Header";
+ PurchaseLine: array[2] of Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseReceiptHeader: array[2] of Record "Warehouse Receipt Header";
+ WhseWorksheetLine: Record "Whse. Worksheet Line";
+ WhseWorksheetName: Record "Whse. Worksheet Name";
+ WhseWorksheetTemplate: Record "Whse. Worksheet Template";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: array[2] of Decimal;
+ TotalQuantity: Decimal;
+ begin
+ // [SCENARIO] Create Single Put-away from Multiple Posted WH Receipts
+ // [FEATURE] Subcontracting Warehouse Put-away Worksheet
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Items
+ Initialize();
+ Quantity[1] := LibraryRandom.RandIntInRange(5, 10);
+ Quantity[2] := LibraryRandom.RandIntInRange(5, 10);
+ TotalQuantity := Quantity[1] + Quantity[2];
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create First Item with its own Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item[1], WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item[1], WorkCenter[2]."No.");
+
+ // [GIVEN] Create Second Item with its own Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item[2], WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item[2], WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ // Creates both Receive Bin (for warehouse receipt) and Put-away Bin (for put-away destination)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Enable Put-away Worksheet - required to use worksheet flow instead of auto-creating put-away
+ Location."Use Put-away Worksheet" := true;
+ Location.Modify(true);
+
+ // [GIVEN] Create Warehouse Employee for the location (required for put-away worksheet)
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, Location.Code, false);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create First Production Order and Subcontracting Purchase Order for Item[1]
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder[1], "Production Order Status"::Released,
+ ProductionOrder[1]."Source Type"::Item, Item[1]."No.", Quantity[1], Location.Code);
+
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item[1]."Routing No.", WorkCenter[2]."No.", PurchaseLine[1]);
+ PurchaseHeader[1].Get(PurchaseLine[1]."Document Type", PurchaseLine[1]."Document No.");
+
+ // [GIVEN] Create Second Production Order and Subcontracting Purchase Order for Item[2]
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder[2], "Production Order Status"::Released,
+ ProductionOrder[2]."Source Type"::Item, Item[2]."No.", Quantity[2], Location.Code);
+
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item[2]."Routing No.", WorkCenter[2]."No.", PurchaseLine[2]);
+ PurchaseHeader[2].Get(PurchaseLine[2]."Document Type", PurchaseLine[2]."Document No.");
+
+ // [GIVEN] Create and Post First Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader[1], WarehouseReceiptHeader[1]);
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader[1], PostedWhseReceiptHeader[1]);
+
+ // [GIVEN] Create and Post Second Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader[2], WarehouseReceiptHeader[2]);
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader[2], PostedWhseReceiptHeader[2]);
+
+ // [WHEN] Create Put-away Worksheet
+ SubcWarehouseLibrary.CreatePutAwayWorksheet(WhseWorksheetTemplate, WhseWorksheetName, Location.Code);
+
+ // [WHEN] Get Warehouse Documents into Put-away Worksheet
+ SubcWarehouseLibrary.GetWarehouseDocumentsForPutAwayWorksheet(WhseWorksheetTemplate.Name, WhseWorksheetName, Location.Code);
+
+ // [THEN] Verify worksheet correctly consolidates lines from multiple receipts
+ WhseWorksheetLine.SetRange("Worksheet Template Name", WhseWorksheetName."Worksheet Template Name");
+ WhseWorksheetLine.SetRange(Name, WhseWorksheetName.Name);
+ WhseWorksheetLine.SetRange("Location Code", Location.Code);
+ WhseWorksheetLine.SetFilter("Item No.", '%1|%2', Item[1]."No.", Item[2]."No.");
+ Assert.RecordIsNotEmpty(WhseWorksheetLine);
+
+ // [THEN] Verify worksheet shows aggregated quantities
+ WhseWorksheetLine.CalcSums(Quantity);
+ Assert.AreEqual(TotalQuantity, WhseWorksheetLine.Quantity,
+ 'Worksheet should consolidate quantities from multiple posted receipts');
+
+ // [WHEN] Create Put-away from worksheet
+ SubcWarehouseLibrary.CreatePutAwayFromWorksheet(WhseWorksheetName, WarehouseActivityHeader);
+
+ // [THEN] Verify Quantity Reconciliation: Single put-away document created with aggregated quantities
+ Assert.AreNotEqual('', WarehouseActivityHeader."No.",
+ 'Put-away document should be created from worksheet');
+ Assert.AreEqual(WarehouseActivityHeader.Type::"Put-away", WarehouseActivityHeader.Type,
+ 'Activity type should be Put-away');
+
+ // [THEN] Verify put-away lines have correct aggregated quantities
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetFilter("Item No.", '%1|%2', Item[1]."No.", Item[2]."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+
+ WarehouseActivityLine.CalcSums(Quantity);
+ Assert.AreEqual(TotalQuantity, WarehouseActivityLine.Quantity,
+ 'Put-away should have correctly aggregated quantities from multiple receipts');
+
+ // [THEN] Verify Bin Management: Bin assignment logic correctly applied
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(PutAwayBin.Code, WarehouseActivityLine."Bin Code",
+ 'Put-away should assign correct default bin');
+
+ // [THEN] Verify Data Consistency: Both items are present in put-away lines
+ WarehouseActivityLine.SetRange("Action Type");
+ WarehouseActivityLine.SetRange("Item No.", Item[1]."No.");
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(Item[1]."Base Unit of Measure", WarehouseActivityLine."Unit of Measure Code",
+ 'Put-away should have correct unit of measure for Item[1]');
+
+ WarehouseActivityLine.SetRange("Item No.", Item[2]."No.");
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(Item[2]."Base Unit of Measure", WarehouseActivityLine."Unit of Measure Code",
+ 'Put-away should have correct unit of measure for Item[2]');
+
+ // [THEN] Verify Data Consistency: Location is consistent
+ Assert.AreEqual(Location.Code, WarehouseActivityHeader."Location Code",
+ 'Put-away location should match source documents');
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseReceiptLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseReceiptLastOp.Codeunit.al
new file mode 100644
index 0000000000..592df9bcae
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWhseReceiptLastOp.Codeunit.al
@@ -0,0 +1,659 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Manufacturing.Subcontracting.Test;
+
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Structure;
+
+codeunit 149900 "Subc. Whse Receipt Last Op."
+{
+ // [FEATURE] Subcontracting Warehouse Receipt - Last Operation Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcLibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Whse Receipt Last Op.");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Whse Receipt Last Op.");
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcLibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibrarySetupStorage.Save(Database::"General Ledger Setup");
+
+ IsInitialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Whse Receipt Last Op.");
+ end;
+
+ [Test]
+ procedure CreateAndVerifyWhseReceiptFromSubcontractingPOForLastOperation()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Create and verify warehouse receipt from subcontracting purchase order for last operation
+ // [FEATURE] Subcontracting Warehouse Receipt - Unit-level validation of receipt creation and field mapping
+ // Note: This test focuses on receipt CREATION only. For full flow testing (receipt + put-away), see FullWarehouseFlowForLastOperation_ReceiptToPutAway
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandling(Location);
+
+ // [GIVEN] Create default bin for location
+ LibraryWarehouse.CreateBin(Bin, Location.Code, 'DEFAULT', '', '');
+ Location.Validate("Default Bin Code", Bin.Code);
+ Location.Modify(true);
+
+ // [GIVEN] Update Vendor with Subcontracting Location Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Update Subcontracting Management Setup with Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+
+ // [WHEN] Get Purchase Header
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Verify warehouse receipt line is created
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.SetRange("Source Document", WarehouseReceiptLine."Source Document"::"Purchase Order");
+ WarehouseReceiptLine.SetRange("Source No.", PurchaseHeader."No.");
+ Assert.RecordIsNotEmpty(WarehouseReceiptLine);
+ WarehouseReceiptLine.FindFirst();
+
+ // [THEN] Verify field mapping from Purchase Line to Warehouse Receipt Line
+ Assert.AreEqual(PurchaseLine."No.", WarehouseReceiptLine."Item No.",
+ 'Item No. should match between Purchase Line and Warehouse Receipt Line');
+ Assert.AreEqual(PurchaseLine.Quantity, WarehouseReceiptLine.Quantity,
+ 'Quantity should match between Purchase Line and Warehouse Receipt Line');
+ Assert.AreEqual(PurchaseLine."Unit of Measure Code", WarehouseReceiptLine."Unit of Measure Code",
+ 'Unit of Measure Code should match between Purchase Line and Warehouse Receipt Line');
+ Assert.AreEqual(PurchaseLine."Location Code", WarehouseReceiptLine."Location Code",
+ 'Location Code should match between Purchase Line and Warehouse Receipt Line');
+ Assert.AreEqual(PurchaseLine."Subc. Purchase Line Type", WarehouseReceiptLine."Subc. Purchase Line Type",
+ 'Subcontracting Purchase Line Type should match between Purchase Line and Warehouse Receipt Line');
+
+ // [THEN] Verify source document references
+ Assert.AreEqual(PurchaseHeader."No.", WarehouseReceiptLine."Source No.",
+ 'Source No. should reference the Purchase Order No.');
+ Assert.AreEqual(PurchaseLine."Line No.", WarehouseReceiptLine."Source Line No.",
+ 'Source Line No. should reference the Purchase Line No.');
+ Assert.AreEqual(Database::"Purchase Line", WarehouseReceiptLine."Source Type",
+ 'Source Type should be Purchase Line');
+ Assert.AreEqual(WarehouseReceiptLine."Source Document"::"Purchase Order",
+ WarehouseReceiptLine."Source Document",
+ 'Source Document should be Purchase Order');
+
+ // [THEN] Verify base quantity calculations using Qty. (Base) = Quantity * Qty. per Unit of Measure
+ Assert.AreEqual(WarehouseReceiptLine.Quantity * WarehouseReceiptLine."Qty. per Unit of Measure", WarehouseReceiptLine."Qty. (Base)",
+ 'Qty. (Base) should equal Quantity * Qty. per Unit of Measure');
+ Assert.AreEqual(WarehouseReceiptLine."Qty. Outstanding" * WarehouseReceiptLine."Qty. per Unit of Measure", WarehouseReceiptLine."Qty. Outstanding (Base)",
+ 'Qty. Outstanding (Base) should equal Qty. Outstanding * Qty. per Unit of Measure');
+
+ // [THEN] Verify subcontracting line type is set to Last Operation
+ Assert.AreEqual("Subc. Purchase Line Type"::LastOperation,
+ WarehouseReceiptLine."Subc. Purchase Line Type",
+ 'Warehouse Receipt Line should be marked as Last Operation');
+ end;
+
+ [Test]
+ procedure FullWarehouseFlowForLastOperation_ReceiptToPutAway()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ VendorLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Complete warehouse flow from receipt creation through put-away completion for last operation
+ // [FEATURE] Subcontracting Warehouse - Full Integration Test covering Receipt + Put-away Flow
+ // This test combines receipt creation, posting, put-away creation, and put-away registration
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling AND Bin Mandatory (Require Receive, Put-away, Bin Mandatory)
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := LibraryWarehouse.CreateLocationWithInventoryPostingSetup(VendorLocation);
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt from Purchase Order
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [THEN] Verify Ledger Entries: Item ledger entries created for last operation
+ VerifyItemLedgerEntriesExist(Item."No.", Location.Code, Quantity);
+
+ // [THEN] Verify Ledger Entries: Capacity ledger entries created for last operation
+ VerifyCapacityLedgerEntriesExist(ProductionOrder."No.", WorkCenter[2]."No.", Quantity);
+
+ // [WHEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Verify Put-away document is created
+ Assert.AreNotEqual('', WarehouseActivityHeader."No.",
+ 'Put-away document should be created');
+ Assert.AreEqual(WarehouseActivityHeader.Type::"Put-away", WarehouseActivityHeader.Type,
+ 'Activity document should be of type Put-away');
+
+ // [THEN] Verify Put-away Take line has correct item and quantity
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(Item."No.", WarehouseActivityLine."Item No.",
+ 'Put-away Take line should have correct item');
+ Assert.AreEqual(Quantity, WarehouseActivityLine.Quantity,
+ 'Put-away Take line should have correct quantity');
+
+ // [THEN] Verify Put-away Place line has correct bin assignment
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ Assert.RecordIsNotEmpty(WarehouseActivityLine);
+ WarehouseActivityLine.FindFirst();
+ Assert.AreEqual(PutAwayBin.Code, WarehouseActivityLine."Bin Code",
+ 'Put-away Place line should use default bin from location setup');
+
+ // [WHEN] Register Put-away
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Verify Bin Contents are correctly updated after put-away registration
+ SubcWarehouseLibrary.VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.", Quantity);
+
+ // [THEN] Verify complete flow succeeded - Item Ledger Entry exists with correct quantity
+ SubcWarehouseLibrary.VerifyItemLedgerEntry(Item."No.", Quantity, Location.Code);
+
+ // [THEN] Verify complete flow succeeded - Capacity Ledger Entry exists
+ SubcWarehouseLibrary.VerifyCapacityLedgerEntry(WorkCenter[2]."No.", Quantity);
+ end;
+
+ [Test]
+ procedure VerifyEndToEndUoMFlowWithAlternativeUoM()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ItemUnitOfMeasure: Record "Item Unit of Measure";
+ Location: Record Location;
+ VendorLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ PostedWhseReceiptLine: Record "Posted Whse. Receipt Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ExpectedBaseQty: Decimal;
+ QtyPerUoM: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Verify end-to-end flow with alternative UoM - Purchase Line to Item Ledger Entry
+ // [FEATURE] Subcontracting Warehouse - Complete UoM Flow Verification
+ // [PRIORITY] Critical - Ensures Qty. (Base) flows correctly through all warehouse documents
+
+ // [GIVEN] Complete Setup with alternative Unit of Measure (Box = 12 base units)
+ Initialize();
+ Quantity := LibraryRandom.RandIntInRange(5, 10); // Number of Boxes
+ QtyPerUoM := 12; // 12 units per Box
+ ExpectedBaseQty := Quantity * QtyPerUoM; // Total base units
+
+ // [GIVEN] Create Work Centers and Manufacturing Setup
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with alternative Unit of Measure (Box)
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ LibraryInventory.CreateItemUnitOfMeasureCode(ItemUnitOfMeasure, Item."No.", QtyPerUoM);
+
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling and Bins
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := LibraryWarehouse.CreateLocationWithInventoryPostingSetup(VendorLocation);
+ Vendor.Modify();
+
+ // [GIVEN] Create Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order with alternative UoM (Box)
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseLine.Validate("Unit of Measure Code", ItemUnitOfMeasure.Code);
+ PurchaseLine.Modify(true);
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+
+ // [THEN] Step 2: Verify Warehouse Receipt Line has correct base quantity and UoM fields
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ Assert.AreEqual(ExpectedBaseQty, WarehouseReceiptLine."Qty. (Base)",
+ 'Warehouse Receipt Line Qty. (Base) should equal Quantity * Qty per UoM');
+ Assert.AreEqual(Quantity, WarehouseReceiptLine.Quantity,
+ 'Warehouse Receipt Line Quantity should remain in alternative UoM');
+ Assert.AreEqual(ItemUnitOfMeasure.Code, WarehouseReceiptLine."Unit of Measure Code",
+ 'Warehouse Receipt Line Unit of Measure Code should be the alternative UoM');
+ Assert.AreEqual(QtyPerUoM, WarehouseReceiptLine."Qty. per Unit of Measure",
+ 'Warehouse Receipt Line Qty. per Unit of Measure should match alternative UoM');
+
+ // [WHEN] Post Warehouse Receipt
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [THEN] Step 3: Verify Posted Warehouse Receipt Line has correct base quantity
+ PostedWhseReceiptLine.SetRange("No.", PostedWhseReceiptHeader."No.");
+ PostedWhseReceiptLine.SetRange("Item No.", Item."No.");
+ PostedWhseReceiptLine.FindFirst();
+
+ Assert.AreEqual(ExpectedBaseQty, PostedWhseReceiptLine."Qty. (Base)",
+ 'Posted Warehouse Receipt Line Qty. (Base) should equal Quantity * Qty per UoM');
+ Assert.AreEqual(QtyPerUoM, PostedWhseReceiptLine."Qty. per Unit of Measure",
+ 'Posted Warehouse Receipt Line Qty. per Unit of Measure should match alternative UoM');
+
+ // [WHEN] Create Put-away from Posted Warehouse Receipt
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+
+ // [THEN] Step 4: Verify Put-away Take line has correct UoM fields
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("No.", WarehouseActivityHeader."No.");
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Take);
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(Quantity, WarehouseActivityLine.Quantity,
+ 'Put-away Take line Quantity should be in alternative UoM');
+ Assert.AreEqual(ExpectedBaseQty, WarehouseActivityLine."Qty. (Base)",
+ 'Put-away Take line Qty. (Base) should equal Quantity * Qty per UoM');
+ Assert.AreEqual(QtyPerUoM, WarehouseActivityLine."Qty. per Unit of Measure",
+ 'Put-away Take line Qty. per Unit of Measure should match alternative UoM');
+ Assert.AreEqual(ItemUnitOfMeasure.Code, WarehouseActivityLine."Unit of Measure Code",
+ 'Put-away Take line should use alternative UoM code');
+
+ // [THEN] Step 5: Verify Put-away Place line has correct base quantity
+ WarehouseActivityLine.SetRange("Action Type", WarehouseActivityLine."Action Type"::Place);
+ WarehouseActivityLine.FindFirst();
+
+ Assert.AreEqual(ExpectedBaseQty, WarehouseActivityLine."Qty. (Base)",
+ 'Put-away Place line Qty. (Base) should equal Quantity * Qty per UoM');
+
+ // [WHEN] Post Put-away
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [THEN] Step 6: Verify Item Ledger Entry has correct quantity (in base units)
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.FindFirst();
+
+ Assert.AreEqual(ExpectedBaseQty, ItemLedgerEntry.Quantity,
+ 'Item Ledger Entry Quantity should be in base units (Quantity * Qty per UoM)');
+
+ // [THEN] Step 7: Verify Bin Contents have correct quantity (in base units)
+ SubcWarehouseLibrary.VerifyBinContents(Location.Code, PutAwayBin.Code, Item."No.", ExpectedBaseQty);
+
+ // [THEN] Verify Capacity Ledger Entry created
+ VerifyCapacityLedgerEntriesExist(ProductionOrder."No.", WorkCenter[2]."No.", Quantity);
+ end;
+
+ local procedure VerifyItemLedgerEntriesExist(ItemNo: Code[20]; LocationCode: Code[10]; ExpectedQuantity: Decimal)
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ // Verify that item ledger entries were created for the last operation
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordCount(ItemLedgerEntry, 1);
+ ItemLedgerEntry.FindFirst();
+ Assert.AreEqual(ExpectedQuantity, ItemLedgerEntry.Quantity, 'Item Ledger Entry Quantity mismatch');
+ end;
+
+ local procedure VerifyCapacityLedgerEntriesExist(ProdOrderNo: Code[20]; WorkCenterNo: Code[20]; ExpectedOutputQuantity: Decimal)
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ begin
+ // Verify that capacity ledger entries were created for the last operation
+ CapacityLedgerEntry.SetRange("Order No.", ProdOrderNo);
+ CapacityLedgerEntry.SetRange("Work Center No.", WorkCenterNo);
+ Assert.RecordCount(CapacityLedgerEntry, 1);
+
+ if ExpectedOutputQuantity <> 0 then begin
+ CapacityLedgerEntry.FindFirst();
+ Assert.AreEqual(ExpectedOutputQuantity, CapacityLedgerEntry."Output Quantity" / CapacityLedgerEntry."Qty. per Unit of Measure",
+ 'Capacity Ledger Entry should have correct output quantity');
+ end;
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure UndoPurchaseReceiptForLastOperation()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location: Record Location;
+ VendorLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ OriginalQtyReceived: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Undo purchase receipt for last operation reverses item and capacity ledger entries
+ // [FEATURE] Subcontracting Warehouse Receipt - Undo functionality for last operation
+
+ // [GIVEN] Complete Manufacturing Setup with Work Centers, Machine Centers, and Item
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling AND Bin Mandatory
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+ Location."Use Put-away Worksheet" := true;
+ Location.Modify(true);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := LibraryWarehouse.CreateLocationWithInventoryPostingSetup(VendorLocation);
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create and Post Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [GIVEN] Verify ledger entries were created (precondition for undo)
+ VerifyItemLedgerEntriesExist(Item."No.", Location.Code, Quantity);
+ VerifyCapacityLedgerEntriesExist(ProductionOrder."No.", WorkCenter[2]."No.", Quantity);
+
+ // [GIVEN] Store original purchase line received quantity
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ OriginalQtyReceived := PurchaseLine."Quantity Received";
+ Assert.AreEqual(Quantity, OriginalQtyReceived, 'Purchase Line should have received the full quantity');
+
+ // [WHEN] Undo the Purchase Receipt Line
+ PurchRcptLine.SetRange("Order No.", PurchaseHeader."No.");
+ PurchRcptLine.SetRange("Order Line No.", PurchaseLine."Line No.");
+ PurchRcptLine.FindFirst();
+ Codeunit.Run(Codeunit::"Undo Purchase Receipt Line", PurchRcptLine);
+
+ // [THEN] Verify a correction line was created with negative quantity
+ PurchRcptLine.Reset();
+ PurchRcptLine.SetRange("Order No.", PurchaseHeader."No.");
+#pragma warning disable AA0210
+ PurchRcptLine.SetRange(Correction, true);
+#pragma warning restore AA0210
+ Assert.RecordIsNotEmpty(PurchRcptLine);
+ PurchRcptLine.FindLast();
+ Assert.AreEqual(-Quantity, PurchRcptLine.Quantity,
+ 'Correction line should have negative quantity equal to original');
+
+ // [THEN] Verify Item Ledger Entry has correction entry (net quantity should be zero)
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Location Code", Location.Code);
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(0, ItemLedgerEntry.Quantity,
+ 'Net Item Ledger Entry quantity should be zero after undo');
+
+ // [THEN] Verify Capacity Ledger Entry has correction entry (net output should be zero)
+ CapacityLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ CapacityLedgerEntry.SetRange("Work Center No.", WorkCenter[2]."No.");
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+ Assert.AreEqual(0, CapacityLedgerEntry."Output Quantity",
+ 'Net Capacity Ledger Entry output quantity should be zero after undo');
+
+ // [THEN] Verify Purchase Line quantities are restored
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ Assert.AreEqual(0, PurchaseLine."Quantity Received",
+ 'Purchase Line Quantity Received should be reset to zero after undo');
+ Assert.AreEqual(Quantity, PurchaseLine."Outstanding Quantity",
+ 'Purchase Line Outstanding Quantity should be restored to original quantity');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure UndoPurchaseReceiptFailsWhenPutAwayRegistered()
+ var
+ PutAwayBin: Record Bin;
+ ReceiveBin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ VendorLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PostedWhseReceiptHeader: Record "Posted Whse. Receipt Header";
+ ProductionOrder: Record "Production Order";
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WarehouseActivityHeader: Record "Warehouse Activity Header";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WorkCenter: array[2] of Record "Work Center";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Undo purchase receipt fails when put-away has been registered
+ // [FEATURE] Subcontracting Warehouse Receipt - Undo validation
+
+ // [GIVEN] Complete Manufacturing Setup
+ Initialize();
+ Quantity := LibraryRandom.RandInt(10) + 5;
+
+ // [GIVEN] Create Work Centers and Machine Centers with Subcontracting
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create Item with Routing and Production BOM
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Update BOM and Routing with Routing Link
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Create Location with Warehouse Handling AND Bin Mandatory
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(Location, ReceiveBin, PutAwayBin);
+
+ // [GIVEN] Configure Vendor with Subcontracting Location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor."Subcontr. Location Code" := Location.Code;
+ Vendor."Location Code" := LibraryWarehouse.CreateLocationWithInventoryPostingSetup(VendorLocation);
+ Vendor.Modify();
+
+ // [GIVEN] Create and Refresh Production Order
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", Quantity, Location.Code);
+
+ // [GIVEN] Setup Requisition Worksheet Template
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcWarehouseLibrary.CreateSubcontractingOrderFromProdOrderRouting(Item."Routing No.", WorkCenter[2]."No.", PurchaseLine);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create and Post Warehouse Receipt
+ SubcWarehouseLibrary.CreateWarehouseReceiptFromPurchaseOrder(PurchaseHeader, WarehouseReceiptHeader);
+ SubcWarehouseLibrary.PostWarehouseReceipt(WarehouseReceiptHeader, PostedWhseReceiptHeader);
+
+ // [GIVEN] Create and Register Put-away
+ SubcWarehouseLibrary.CreatePutAwayFromPostedWhseReceipt(PostedWhseReceiptHeader, WarehouseActivityHeader);
+ LibraryWarehouse.RegisterWhseActivity(WarehouseActivityHeader);
+
+ // [WHEN] Try to Undo the Purchase Receipt Line
+ PurchRcptLine.SetRange("Order No.", PurchaseHeader."No.");
+ PurchRcptLine.SetRange("Order Line No.", PurchaseLine."Line No.");
+ PurchRcptLine.FindFirst();
+
+ // [THEN] Error is thrown because put-away is already registered
+ asserterror Codeunit.Run(Codeunit::"Undo Purchase Receipt Line", PurchRcptLine);
+ Assert.ExpectedError('because warehouse activity lines have already been created.');
+ end;
+
+ [ConfirmHandler]
+ procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
+ begin
+ // Always confirm operations
+ Reply := true;
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizConfigTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizConfigTest.Codeunit.al
index ba586d22ce..f26555c246 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizConfigTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizConfigTest.Codeunit.al
@@ -21,13 +21,13 @@ codeunit 139994 "Subc. Wiz. Config Test"
var
Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibrarySetupStorage: Codeunit "Library - Setup Storage";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
SubCreateProdOrdWizLibrary: Codeunit "Subc. CreateProdOrdWizLibrary";
LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
WizardFinishedSuccessfully: Boolean;
WizardWasOpened: Boolean;
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizGeneralTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizGeneralTest.Codeunit.al
index 6d1d33bc9f..339c881ccd 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizGeneralTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizGeneralTest.Codeunit.al
@@ -22,6 +22,7 @@ codeunit 139993 "Subc. Wiz. General Test"
var
Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibrarySetupStorage: Codeunit "Library - Setup Storage";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
SubCreateProdOrdWizLibrary: Codeunit "Subc. CreateProdOrdWizLibrary";
@@ -29,7 +30,6 @@ codeunit 139993 "Subc. Wiz. General Test"
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
ProdOrderCheckLib: Codeunit "Subc. ProdOrderCheckLib";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
WizardFinishedSuccessfully: Boolean;
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizPutAwayTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizPutAwayTest.Codeunit.al
index b5a83a4949..44ceae377d 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizPutAwayTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizPutAwayTest.Codeunit.al
@@ -26,6 +26,7 @@ codeunit 139999 "Subc. Wiz. Put-Away Test"
var
Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
LibrarySetupStorage: Codeunit "Library - Setup Storage";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
LibraryWarehouse: Codeunit "Library - Warehouse";
@@ -34,7 +35,6 @@ codeunit 139999 "Subc. Wiz. Put-Away Test"
SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
ProdOrderCheckLib: Codeunit "Subc. ProdOrderCheckLib";
SubSetupLibrary: Codeunit "Subc. Setup Library";
- LibraryERMCountryData: Codeunit "Library - ERM Country Data";
IsInitialized: Boolean;
// ==================== SCENARIO L: Put-Away Operations ====================
diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizSaveTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizSaveTest.Codeunit.al
index d6541e0d4e..77fc82060f 100644
--- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizSaveTest.Codeunit.al
+++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcWizSaveTest.Codeunit.al
@@ -109,10 +109,10 @@ codeunit 139998 "Subc. Wiz. Save Test"
[HandlerFunctions('HandlePurchProvisionWizardSaveToItem')]
procedure TestH2_SaveToItem_BOMSavedToItem()
var
+ Item: Record Item;
TempProdOrderComponent: Record "Prod. Order Component" temporary;
TempProdOrderRoutingLine: Record "Prod. Order Routing Line" temporary;
ProdOrder: Record "Production Order";
- Item: Record Item;
PurchLine: Record "Purchase Line";
CreateProdOrdOpt: Codeunit "Subc. Create Prod. Ord. Opt.";
ItemNo: Code[20];
@@ -246,10 +246,10 @@ codeunit 139998 "Subc. Wiz. Save Test"
var
TempProdOrderComponent: Record "Prod. Order Component" temporary;
TempProdOrderRoutingLine: Record "Prod. Order Routing Line" temporary;
- RoutingVersion: Record "Routing Version";
- ProdOrder: Record "Production Order";
ProductionBOMVersion: Record "Production BOM Version";
+ ProdOrder: Record "Production Order";
PurchLine: Record "Purchase Line";
+ RoutingVersion: Record "Routing Version";
CreateProdOrdOpt: Codeunit "Subc. Create Prod. Ord. Opt.";
BOMNo: Code[20];
ItemNo: Code[20];