diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al index ae02ae8c129..bed4270e5ba 100644 --- a/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al @@ -48,13 +48,13 @@ codeunit 99001505 "Subcontracting Management" "Component Supply Method"::"Consignment at Vendor", "Component Supply Method"::"Vendor-Supplied": if (VendorSubcontrLocation <> '') and (ProdOrderComponent."Location Code" <> VendorSubcontrLocation) then - ProdOrderComponent.Validate("Location Code", VendorSubcontrLocation); + ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, VendorSubcontrLocation); "Component Supply Method"::"Transfer to Vendor", "Component Supply Method"::Empty: begin if (ProdOrderComponent."Location Code" <> OriginalLocationCode) and (OriginalLocationCode <> '') then begin - ProdOrderComponent.Validate("Location Code", OriginalLocationCode); + ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, OriginalLocationCode); ProdOrderComponent."Subc. Original Location Code" := ''; end; if (ProdOrderComponent."Bin Code" <> OriginalBinCode) and (OriginalBinCode <> '') then begin @@ -65,6 +65,38 @@ codeunit 99001505 "Subcontracting Management" end; end; + internal procedure ValidateProdOrderCompLocationPreservingFlushingMethod(var ProdOrderComponent: Record "Prod. Order Component"; NewLocationCode: Code[10]) + var + PreservedFlushingMethod: Enum "Flushing Method"; + PreservedLocationCode: Code[10]; + PreservedBinCode: Code[20]; + begin + // Validating "Location Code" re-reads the planning parameters from the item or SKU (Prod. Order Component.GetUpdateFromSKU), + // which silently overwrites a manually chosen "Flushing Method". Subcontracting only shuttles the component between the + // shop floor and the subcontractor location, so a manually set "Flushing Method" must survive that automatic move. + // The "Flushing Method" is restored by direct assignment on purpose: re-validating it errors once consumption has been + // posted at the subcontractor (see the return leg), which is a lifecycle subcontracting must support. + PreservedFlushingMethod := ProdOrderComponent."Flushing Method"; + PreservedLocationCode := ProdOrderComponent."Location Code"; + PreservedBinCode := ProdOrderComponent."Bin Code"; + + ProdOrderComponent.Validate("Location Code", NewLocationCode); + + if ProdOrderComponent."Flushing Method" <> PreservedFlushingMethod then + ProdOrderComponent."Flushing Method" := PreservedFlushingMethod; + + // Restoring the "Flushing Method" by direct assignment does not undo the "Bin Code" that the "Location Code"/temporary + // "Flushing Method" validation already derived (GetDefaultBin/GetUpdateFromSKU -> UpdateBin). Realign the default bin with + // the preserved flushing method so it never lingers on the bin picked for the item/SKU flushing method. + if ProdOrderComponent."Location Code" <> PreservedLocationCode then + // The component actually moved: recompute the default bin, now evaluated against the preserved flushing method. + ProdOrderComponent.GetDefaultBin() + else + // Same location (e.g. the direct-transfer post revalidates the current location to trigger side effects): keep the + // component on the bin it already had instead of the one derived for the temporary flushing method. + ProdOrderComponent."Bin Code" := PreservedBinCode; + end; + procedure ChangeLocationOnPlanningComponent(var PlanningComponent: Record "Planning Component"; VendorSubcontrLocation: Code[10]; OriginalLocationCode: Code[10]; OriginalBinCode: Code[20]) begin #if not CLEAN29 diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al index 4a4fc03fefb..e929e2be7ce 100644 --- a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al @@ -445,7 +445,7 @@ codeunit 99001524 "Subc. Prod. Order Comp. Ext." end else if xProdOrderComponent."Routing Link Code" <> '' then if ProdOrderComponent."Subc. Original Location Code" <> '' then begin - ProdOrderComponent.Validate("Location Code", ProdOrderComponent."Subc. Original Location Code"); + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, ProdOrderComponent."Subc. Original Location Code"); ProdOrderComponent."Subc. Original Location Code" := ''; if ProdOrderComponent."Subc. Orig. Bin Code" <> '' then begin ProdOrderComponent.Validate("Bin Code", ProdOrderComponent."Subc. Orig. Bin Code"); diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al index 048e43d232d..06f1053a0fd 100644 --- a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al +++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al @@ -166,6 +166,7 @@ report 99001502 "Subc. Create SubCReturnOrder" ProdOrderRoutingLine: Record "Prod. Order Routing Line"; MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt."; SubcTransferManagement: Codeunit "Subc. Transfer Management"; + SubcontractingManagement: Codeunit "Subcontracting Management"; UnitofMeasureManagement: Codeunit "Unit of Measure Management"; SubcFromLocationCode: Code[10]; AvailableToReturn: Decimal; @@ -246,10 +247,8 @@ report 99001502 "Subc. Create SubCReturnOrder" if ProdOrderComponent."Subc. Orig. Bin Code" = '' then ProdOrderComponent."Subc. Orig. Bin Code" := ProdOrderComponent."Bin Code"; - if TransferHeader."Transfer-to Code" <> ProdOrderComponent."Location Code" then begin - ProdOrderComponent.Validate("Location Code", TransferHeader."Transfer-to Code"); - ProdOrderComponent.GetDefaultBin(); - end; + if TransferHeader."Transfer-to Code" <> ProdOrderComponent."Location Code" then + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, TransferHeader."Transfer-to Code"); ProdOrderComponent.Modify(); SubcTransferManagement.CreateReservEntryForTransferReceiptToProdOrderComp(TransferLine, ProdOrderComponent); diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al index 570b56f8b64..9f549041142 100644 --- a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al +++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al @@ -163,6 +163,7 @@ report 99001501 "Subc. Create Transf. Order" MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt."; SubcProdOrdCompRes: Codeunit "Subc. Prod. Ord. Comp. Res."; SubcTransferManagement: Codeunit "Subc. Transfer Management"; + SubcontractingManagement: Codeunit "Subcontracting Management"; UnitofMeasureManagement: Codeunit "Unit of Measure Management"; TransferFromLocationCode: Code[10]; QtyPerUom: Decimal; @@ -245,16 +246,14 @@ report 99001501 "Subc. Create Transf. Order" Error(ExcessReservationsErr, TransferLine."Quantity (Base)", SubcTransferManagement.GetComponentReservedQtyBase(ProdOrderComponent), ProdOrderComponent."Item No."); SubcTransferManagement.TransferReservationEntryFromProdOrderCompToTransferOrder(TransferLine, ProdOrderComponent); - if TransferHeader."Transfer-to Code" <> ProdOrderComponent."Location Code" then begin + if TransferHeader."Transfer-to Code" <> ProdOrderComponent."Location Code" then if Item."Order Tracking Policy" = Item."Order Tracking Policy"::None then - ProdOrderComponent.Validate("Location Code", TransferHeader."Transfer-to Code") + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, TransferHeader."Transfer-to Code") else begin BindSubscription(SubcProdOrdCompRes); - ProdOrderComponent.Validate("Location Code", TransferHeader."Transfer-to Code"); + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, TransferHeader."Transfer-to Code"); UnbindSubscription(SubcProdOrdCompRes); end; - ProdOrderComponent.GetDefaultBin(); - end; ProdOrderComponent.Modify(); SubcTransferManagement.CreateReservEntryForTransferReceiptToProdOrderComp(TransferLine, ProdOrderComponent); diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al index c24edf08d14..d608c7df38a 100644 --- a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al @@ -60,6 +60,7 @@ codeunit 99001540 "Subc. TransOrderPostRcpt Ext" local procedure OnCheckTransLine(TransferLine: Record "Transfer Line"; TransferHeader: Record "Transfer Header"; Location: Record Location; WhseReceive: Boolean) var ProdOrderComponent: Record "Prod. Order Component"; + SubcontractingManagement: Codeunit "Subcontracting Management"; begin #if not CLEAN29 #pragma warning disable AL0432 @@ -74,7 +75,7 @@ codeunit 99001540 "Subc. TransOrderPostRcpt Ext" exit; if Location.Code <> ProdOrderComponent."Location Code" then begin - ProdOrderComponent.Validate("Location Code", Location.Code); + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, Location.Code); ProdOrderComponent.Modify(); end; end; diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al index bfc7e3c9a11..e6792cfa4c6 100644 --- a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al @@ -59,6 +59,7 @@ codeunit 99001547 "Subc. TransOrderPostTrans Ext" local procedure OnBeforeInsertDirectTransLine(TransferLine: Record "Transfer Line") var ProdOrderComponent: Record "Prod. Order Component"; + SubcontractingManagement: Codeunit "Subcontracting Management"; begin #if not CLEAN29 #pragma warning disable AL0432 @@ -72,7 +73,7 @@ codeunit 99001547 "Subc. TransOrderPostTrans Ext" if not ProdOrderComponent.Get(ProdOrderComponent.Status::Released, TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Prod. Ord. Comp Line No.") then exit; - ProdOrderComponent.Validate("Location Code"); + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, ProdOrderComponent."Location Code"); ProdOrderComponent.Modify(); end; diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al index b6246cfb5d0..8342aabb659 100644 --- a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al @@ -302,7 +302,7 @@ codeunit 99001504 "Subc. Transfer Management" // during Return TO creation. Revert it back to Transfer-from (subcontractor location) // so the component correctly reflects that items are still at the subcontractor. if (TransferLine."Transfer-from Code" <> '') and (ProdOrderComponent."Location Code" <> TransferLine."Transfer-from Code") then begin - ProdOrderComponent.Validate("Location Code", TransferLine."Transfer-from Code"); + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, TransferLine."Transfer-from Code"); ProdOrderComponent.Modify(); end; exit; diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al index af63038db0f..ee9fd25cc2a 100644 --- a/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al +++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al @@ -22,6 +22,8 @@ using Microsoft.Manufacturing.Subcontracting; using Microsoft.Manufacturing.WorkCenter; using Microsoft.Purchases.Document; using Microsoft.Purchases.Vendor; +using Microsoft.Warehouse.Structure; +using System.TestLibraries.Utilities; codeunit 139991 "Subc. Purch. Subcont. Test" { @@ -45,6 +47,7 @@ codeunit 139991 "Subc. Purch. Subcont. Test" LibraryRandom: Codeunit "Library - Random"; LibrarySetupStorage: Codeunit "Library - Setup Storage"; LibraryTestInitialize: Codeunit "Library - Test Initialize"; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; LibraryWarehouse: Codeunit "Library - Warehouse"; LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management"; SubcontractingMgmtLibrary: Codeunit "Subc. Management Library"; @@ -418,6 +421,223 @@ codeunit 139991 "Subc. Purch. Subcont. Test" SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption(Quantity)); end; + [Test] + [HandlerFunctions('ConfirmViewCreatedDocumentHandler,HandleExpectedSubcTransferOrder,ExpectedPostedTransferMessageHandler')] + procedure SubcTransferPreservesManuallyChangedComponentFlushingMethod() + var + ComponentItem: Record Item; + Item: Record Item; + HomeLocation: Record Location; + MachineCenter: array[2] of Record "Machine Center"; + ProdOrderComponent: Record "Prod. Order Component"; + ProductionOrder: Record "Production Order"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + TransferHeader: Record "Transfer Header"; + Vendor: Record Vendor; + WorkCenter: array[2] of Record "Work Center"; + begin + // [SCENARIO 646576] A manually changed component Flushing Method is preserved when the component is transferred to the subcontractor location. + Initialize(); + + // Harden the UI handlers: assert the exact confirm, transfer order page and posting message that must occur, in order. + LibraryVariableStorage.Enqueue('Do you want to view it?'); // confirm shown after the subcontracting purchase order is created + LibraryVariableStorage.Enqueue(false); // do not open the created purchase order + LibraryVariableStorage.Enqueue('A subcontracting transfer order should be surfaced before posting.'); // transfer order page + LibraryVariableStorage.Enqueue('was successfully posted and is now deleted'); // direct transfer posting message + + // [GIVEN] A subcontracting purchase order with a "Transfer to Vendor" component + SetupSubContractingProdOrder(Item, HomeLocation, WorkCenter, MachineCenter, ProductionOrder, "Component Supply Method"::"Transfer to Vendor", LibraryRandom.RandIntInRange(2, 10)); + CreateSubcontractingPurchaseOrderForProdOrder(PurchaseHeader, PurchaseLine, Item, WorkCenter, ProductionOrder); + + // [GIVEN] The component item's own Flushing Method is "Pick + Manual" + FindTransferProdOrderComponent(ProdOrderComponent, PurchaseLine); + ProdOrderComponent.FindFirst(); + ComponentItem.Get(ProdOrderComponent."Item No."); + ComponentItem.Validate("Flushing Method", "Flushing Method"::"Pick + Manual"); + ComponentItem.Modify(true); + + // [GIVEN] The user manually changes the released component's Flushing Method to "Backward" + ProdOrderComponent.Validate("Flushing Method", "Flushing Method"::Backward); + ProdOrderComponent.Modify(true); + + // [WHEN] The transfer to the subcontractor is created and posted as a direct transfer + CreateTransferOrderForPurchaseOrder(PurchaseHeader); + FindTransferOrderForPurchaseLine(TransferHeader, PurchaseLine); + PostDirectTransferOrder(TransferHeader); + + // [THEN] The component has been moved to the subcontractor location + ProdOrderComponent.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.", ProdOrderComponent."Line No."); + Vendor.Get(WorkCenter[2]."Subcontractor No."); + Assert.AreEqual(Vendor."Subc. Location Code", ProdOrderComponent."Location Code", 'Component should be moved to the subcontractor location.'); + + // [THEN] The manually selected "Backward" flushing method is preserved + Assert.AreEqual("Flushing Method"::Backward, ProdOrderComponent."Flushing Method", 'Manually changed Flushing Method must be preserved after the subcontracting transfer.'); + + // [THEN] Exactly the expected confirm, transfer order page and posting message were handled + LibraryVariableStorage.AssertEmpty(); + end; + + [Test] + [HandlerFunctions('ConfirmViewCreatedDocumentHandler,HandleExpectedSubcTransferOrder,ExpectedPostedTransferMessageHandler')] + procedure SubcReturnTransferPreservesManuallyChangedComponentFlushingMethod() + var + ComponentItem: Record Item; + Item: Record Item; + HomeLocation: Record Location; + MachineCenter: array[2] of Record "Machine Center"; + ProdOrderComponent: Record "Prod. Order Component"; + ProdOrderLine: Record "Prod. Order Line"; + ProductionOrder: Record "Production Order"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + ReturnTransferHeader: Record "Transfer Header"; + ReturnTransferLine: Record "Transfer Line"; + TransferHeader: Record "Transfer Header"; + TransferLine: Record "Transfer Line"; + WorkCenter: array[2] of Record "Work Center"; + PurchaseOrderPage: TestPage "Purchase Order"; + OriginalLocationCode: Code[10]; + TransferredQty: Decimal; + ConsumedQty: Decimal; + begin + // [SCENARIO 646576] A manually changed component Flushing Method is preserved through the full + // subcontracting lifecycle, including the return of the remaining stock from the subcontractor + // (even when consumption has already been posted at the subcontractor location). + Initialize(); + + // Harden the UI handlers: assert the exact confirm, the forward and return transfer order pages and both + // posting messages that must occur, in order. + LibraryVariableStorage.Enqueue('Do you want to view it?'); // confirm shown after the subcontracting purchase order is created + LibraryVariableStorage.Enqueue(false); // do not open the created purchase order + LibraryVariableStorage.Enqueue('A subcontracting transfer order should be surfaced before posting.'); // forward transfer order page + LibraryVariableStorage.Enqueue('was successfully posted and is now deleted'); // forward direct transfer posting message + LibraryVariableStorage.Enqueue('A subcontracting return transfer order should be surfaced before posting.'); // return transfer order page + LibraryVariableStorage.Enqueue('was successfully posted and is now deleted'); // return direct transfer posting message + + // [GIVEN] A subcontracting purchase order with a "Transfer to Vendor" component + SetupSubContractingProdOrder(Item, HomeLocation, WorkCenter, MachineCenter, ProductionOrder, "Component Supply Method"::"Transfer to Vendor", LibraryRandom.RandIntInRange(2, 10)); + CreateSubcontractingPurchaseOrderForProdOrder(PurchaseHeader, PurchaseLine, Item, WorkCenter, ProductionOrder); + + // [GIVEN] The component item's own Flushing Method is "Pick + Manual" while the user manually sets the component to "Backward" + FindTransferProdOrderComponent(ProdOrderComponent, PurchaseLine); + ProdOrderComponent.FindFirst(); + OriginalLocationCode := ProdOrderComponent."Location Code"; + ComponentItem.Get(ProdOrderComponent."Item No."); + ComponentItem.Validate("Flushing Method", "Flushing Method"::"Pick + Manual"); + ComponentItem.Modify(true); + ProdOrderComponent.Validate("Flushing Method", "Flushing Method"::Backward); + ProdOrderComponent.Modify(true); + + // [GIVEN] The component is transferred to the subcontractor and partially consumed there + CreateTransferOrderForPurchaseOrder(PurchaseHeader); + FindTransferOrderForPurchaseLine(TransferHeader, PurchaseLine); + TransferLine.SetRange("Document No.", TransferHeader."No."); + TransferLine.SetRange("Item No.", ProdOrderComponent."Item No."); + TransferLine.FindFirst(); + TransferredQty := TransferLine.Quantity; + ConsumedQty := Round(TransferredQty / 2, 1); + PostDirectTransferOrder(TransferHeader); + + ProdOrderComponent.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.", ProdOrderComponent."Line No."); + ProdOrderLine.Get(ProductionOrder.Status, ProductionOrder."No.", ProdOrderComponent."Prod. Order Line No."); + LibraryMfgManagement.PostConsumptionForComponent(ProdOrderLine, ProdOrderComponent, ComponentItem, ConsumedQty); + + // [WHEN] The remaining stock is returned from the subcontractor and the return transfer is posted + PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No."); + PurchaseOrderPage.OpenView(); + PurchaseOrderPage.GoToRecord(PurchaseHeader); + PurchaseOrderPage.CreateReturnFromSubcontractor.Invoke(); + PurchaseOrderPage.Close(); + + ReturnTransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No."); + ReturnTransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComponent."Line No."); + ReturnTransferLine.SetRange("Item No.", ProdOrderComponent."Item No."); + ReturnTransferLine.SetRange("Subc. Return Order", true); + ReturnTransferLine.FindFirst(); + ReturnTransferHeader.Get(ReturnTransferLine."Document No."); + PostDirectTransferOrder(ReturnTransferHeader); + + // [THEN] The component has been moved back to its original location + ProdOrderComponent.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.", ProdOrderComponent."Line No."); + Assert.AreEqual(OriginalLocationCode, ProdOrderComponent."Location Code", 'Component should be moved back to its original location after the return.'); + + // [THEN] The manually selected "Backward" flushing method is still preserved + Assert.AreEqual("Flushing Method"::Backward, ProdOrderComponent."Flushing Method", 'Manually changed Flushing Method must be preserved after the subcontracting return transfer.'); + + // [THEN] Exactly the expected confirm, both transfer order pages and both posting messages were handled + LibraryVariableStorage.AssertEmpty(); + end; + + [Test] + [HandlerFunctions('ConfirmViewCreatedDocumentHandler,HandleExpectedSubcTransferOrder')] + procedure SubcDirectPostPreservesComponentBinForManualFlushingMethod() + var + ComponentItem: Record Item; + Item: Record Item; + HomeLocation: Record Location; + MachineCenter: array[2] of Record "Machine Center"; + ProdOrderComponent: Record "Prod. Order Component"; + ProductionOrder: Record "Production Order"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + Vendor: Record Vendor; + WorkCenter: array[2] of Record "Work Center"; + SubcLocation: Record Location; + OpenShopFloorBin: Record Bin; + ToProductionBin: Record Bin; + SubcontractingManagement: Codeunit "Subcontracting Management"; + begin + // [SCENARIO 646576] When the subcontracting direct-transfer post revalidates the component against its own + // (unchanged) location, the manually chosen "Backward" flushing method must keep its Open Shop Floor bin instead + // of being switched to the item's "Pick + Manual" To-Production bin. + Initialize(); + + // Harden the UI handlers: assert the exact confirm and transfer order page that must occur, in order. + LibraryVariableStorage.Enqueue('Do you want to view it?'); // confirm shown after the subcontracting purchase order is created + LibraryVariableStorage.Enqueue(false); // do not open the created purchase order + LibraryVariableStorage.Enqueue('A subcontracting transfer order should be surfaced.'); // transfer order page + + // [GIVEN] A subcontracting purchase order with a "Transfer to Vendor" component + SetupSubContractingProdOrder(Item, HomeLocation, WorkCenter, MachineCenter, ProductionOrder, "Component Supply Method"::"Transfer to Vendor", LibraryRandom.RandIntInRange(2, 10)); + CreateSubcontractingPurchaseOrderForProdOrder(PurchaseHeader, PurchaseLine, Item, WorkCenter, ProductionOrder); + + // [GIVEN] The subcontractor location is Bin Mandatory with distinct Open Shop Floor and To-Production bins + Vendor.Get(WorkCenter[2]."Subcontractor No."); + ConfigureSubcontractorLocationBins(SubcLocation, OpenShopFloorBin, ToProductionBin, Vendor."Subc. Location Code"); + + // [GIVEN] The component item's own Flushing Method is "Pick + Manual" (its default bin would be To-Production) + FindTransferProdOrderComponent(ProdOrderComponent, PurchaseLine); + ProdOrderComponent.FindFirst(); + ComponentItem.Get(ProdOrderComponent."Item No."); + ComponentItem.Validate("Flushing Method", "Flushing Method"::"Pick + Manual"); + ComponentItem.Modify(true); + + // [GIVEN] The user manually changes the released component's Flushing Method to "Backward" + ProdOrderComponent.Validate("Flushing Method", "Flushing Method"::Backward); + ProdOrderComponent.Modify(true); + + // [GIVEN] The transfer is created, moving the component to the subcontractor location on its Open Shop Floor bin + CreateTransferOrderForPurchaseOrder(PurchaseHeader); + ProdOrderComponent.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.", ProdOrderComponent."Line No."); + Assert.AreEqual(SubcLocation.Code, ProdOrderComponent."Location Code", 'Precondition: component is moved to the subcontractor location when the transfer is created.'); + Assert.AreEqual(OpenShopFloorBin.Code, ProdOrderComponent."Bin Code", 'Precondition: component starts on the Open Shop Floor bin (matching the Backward flushing method).'); + + // [WHEN] The direct-transfer post revalidates the component against its own (unchanged) location + // (this is exactly what "Subc. TransOrderPostTrans Ext".OnBeforeInsertDirectTransLine does during posting) + SubcontractingManagement.ValidateProdOrderCompLocationPreservingFlushingMethod(ProdOrderComponent, ProdOrderComponent."Location Code"); + ProdOrderComponent.Modify(); + + // [THEN] The manually selected "Backward" flushing method is preserved + Assert.AreEqual("Flushing Method"::Backward, ProdOrderComponent."Flushing Method", 'Manually changed Flushing Method must survive the direct-post revalidation.'); + + // [THEN] The component keeps its Open Shop Floor bin and is not switched to the item's To-Production bin + Assert.AreEqual(OpenShopFloorBin.Code, ProdOrderComponent."Bin Code", 'Component Bin Code must stay consistent with the manually selected Backward flushing method (Open Shop Floor Bin).'); + + // [THEN] Exactly the expected confirm and transfer order page were handled + LibraryVariableStorage.AssertEmpty(); + end; + [Test] [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder,MessageHandler')] procedure SubcTransferPartialConsumptionAndReturnFlow() @@ -901,6 +1121,25 @@ codeunit 139991 "Subc. Purch. Subcont. Test" begin end; + [ConfirmHandler] + procedure ConfirmViewCreatedDocumentHandler(Question: Text[1024]; var Reply: Boolean) + begin + Assert.ExpectedConfirm(LibraryVariableStorage.DequeueText(), Question); + Reply := LibraryVariableStorage.DequeueBoolean(); + end; + + [MessageHandler] + procedure ExpectedPostedTransferMessageHandler(Message: Text[1024]) + begin + Assert.ExpectedMessage(LibraryVariableStorage.DequeueText(), Message); + end; + + [PageHandler] + procedure HandleExpectedSubcTransferOrder(var TransfOrderPage: TestPage "Transfer Order") + begin + Assert.AreNotEqual('', TransfOrderPage."No.".Value(), LibraryVariableStorage.DequeueText()); + end; + local procedure CreateAndCalculateNeededWorkCenter(var WorkCenter: Record "Work Center"; IsSubcontracting: Boolean) var CapacityUnitOfMeasure: Record "Capacity Unit of Measure"; @@ -946,6 +1185,7 @@ codeunit 139991 "Subc. Purch. Subcont. Test" begin LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Purch. Subcont. Test"); LibrarySetupStorage.Restore(); + LibraryVariableStorage.Clear(); if IsInitialized then exit; @@ -1094,6 +1334,21 @@ codeunit 139991 "Subc. Purch. Subcont. Test" TransferOrderPage.Post.Invoke(); end; + local procedure ConfigureSubcontractorLocationBins(var SubcLocation: Record Location; var OpenShopFloorBin: Record Bin; var ToProductionBin: Record Bin; SubcLocationCode: Code[10]) + begin + SubcLocation.Get(SubcLocationCode); + SubcLocation."Bin Mandatory" := true; + SubcLocation.Modify(); + LibraryWarehouse.CreateBin(OpenShopFloorBin, SubcLocation.Code, '', '', ''); + LibraryWarehouse.CreateBin(ToProductionBin, SubcLocation.Code, '', '', ''); + // The default production bins are assigned directly: validating them runs Location.CheckBinCode, which + // requires a Bin Mandatory location. Bin Mandatory itself is likewise set directly because validating the + // subcontractor location through "Subc. Location Code" deliberately rejects Bin Mandatory locations. + SubcLocation."Open Shop Floor Bin Code" := OpenShopFloorBin.Code; + SubcLocation."To-Production Bin Code" := ToProductionBin.Code; + SubcLocation.Modify(); + end; + local procedure CreateInventoryForAllComponents(ProductionOrder: Record "Production Order") var ProdOrderComponent: Record "Prod. Order Component";