From c6abd64c95b09fb52fe49b4ac4dbc40f2122f1a8 Mon Sep 17 00:00:00 2001 From: MarkoAleksandric Date: Fri, 12 Jun 2026 09:41:46 +0200 Subject: [PATCH 1/3] set load fields --- .../Extensions/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al | 1 + .../src/Process/Codeunits/SubcTransferManagement.Codeunit.al | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al index 5d67c498be..a2cc9a826b 100644 --- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al @@ -79,6 +79,7 @@ codeunit 99001516 "Subc. Req. Wksh. Make Ord." ProdOrderComponent.SetRange("Prod. Order Line No.", RequisitionLine."Prod. Order Line No."); ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code"); ProdOrderComponent.SetRange("Component Supply Method", "Component Supply Method"::"Vendor-Supplied"); + ProdOrderComponent.SetLoadFields("Item No.", "Variant Code", "Remaining Quantity"); if ProdOrderComponent.FindSet() then repeat PurchaseLineComp.SetRange("Document Type", PurchaseLine."Document Type"); diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/SubcTransferManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/SubcTransferManagement.Codeunit.al index b90635c06d..d9b44afb20 100644 --- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/SubcTransferManagement.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/SubcTransferManagement.Codeunit.al @@ -336,6 +336,7 @@ codeunit 99001504 "Subc. Transfer Management" ProdOrderComponent.SetRange("Subc. Purchase Order Filter", PurchaseLine."Document No."); ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code"); ProdOrderComponent.SetRange("Component Supply Method", ProdOrderComponent."Component Supply Method"::"Transfer to Vendor"); + ProdOrderComponent.SetLoadFields("Subc. Qty. transf. to Subcontr", "Location Code"); ProdOrderComponent.SetAutoCalcFields("Subc. Qty. transf. to Subcontr"); if ProdOrderComponent.FindSet() then repeat @@ -354,7 +355,6 @@ codeunit 99001504 "Subc. Transfer Management" SubcWIPLedgerEntry.SetRange("Routing Reference No.", PurchaseLine."Routing Reference No."); SubcWIPLedgerEntry.SetRange("Routing No.", PurchaseLine."Routing No."); SubcWIPLedgerEntry.SetRange("Operation No.", PurchaseLine."Operation No."); - SubcWIPLedgerEntry.SetRange("In Transit", false); SubcWIPLedgerEntry.CalcSums("Quantity (Base)"); if SubcWIPLedgerEntry."Quantity (Base)" <> 0 then @@ -374,6 +374,7 @@ codeunit 99001504 "Subc. Transfer Management" ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Consumption); ItemLedgerEntry.SetRange("Prod. Order Comp. Line No.", ProdOrderComponent."Line No."); ItemLedgerEntry.SetRange("Location Code", ProdOrderComponent."Location Code"); + ItemLedgerEntry.SetLoadFields(Quantity); ItemLedgerEntry.CalcSums(Quantity); exit(-ItemLedgerEntry.Quantity); end; From 6c87aab8cea4b0822e8147f2ff4ded984211e8d0 Mon Sep 17 00:00:00 2001 From: MarkoAleksandric Date: Fri, 12 Jun 2026 09:49:23 +0200 Subject: [PATCH 2/3] trigger check fix --- .../Purchase/SubcPurchaseLineExt.Codeunit.al | 12 ++++++------ .../Transfer/SubcTransferLineExt.Codeunit.al | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) 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 f175c6c1b3..21e21e7347 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 @@ -93,7 +93,7 @@ codeunit 99001534 "Subc. Purchase Line Ext" if Rec."Prod. Order No." = '' then exit; - if CurrFieldNo <> Rec.FieldNo(Quantity) then + if CurrFieldNo = 0 then exit; if Rec.Quantity = xRec.Quantity then @@ -113,7 +113,7 @@ codeunit 99001534 "Subc. Purchase Line Ext" if Rec."Prod. Order No." = '' then exit; - if CurrFieldNo <> Rec.FieldNo("No.") then + if CurrFieldNo = 0 then exit; if Rec."No." = xRec."No." then @@ -133,7 +133,7 @@ codeunit 99001534 "Subc. Purchase Line Ext" if Rec."Prod. Order No." = '' then exit; - if CurrFieldNo <> Rec.FieldNo("Location Code") then + if CurrFieldNo = 0 then exit; if Rec."Location Code" = xRec."Location Code" then @@ -153,7 +153,7 @@ codeunit 99001534 "Subc. Purchase Line Ext" if Rec."Prod. Order No." = '' then exit; - if CurrFieldNo <> Rec.FieldNo("Bin Code") then + if CurrFieldNo = 0 then exit; if Rec."Bin Code" = xRec."Bin Code" then @@ -173,7 +173,7 @@ codeunit 99001534 "Subc. Purchase Line Ext" if Rec."Prod. Order No." = '' then exit; - if CurrFieldNo <> Rec.FieldNo("Variant Code") then + if CurrFieldNo = 0 then exit; if Rec."Variant Code" = xRec."Variant Code" then @@ -193,7 +193,7 @@ codeunit 99001534 "Subc. Purchase Line Ext" if Rec."Prod. Order No." = '' then exit; - if CurrFieldNo <> Rec.FieldNo("Unit of Measure Code") then + if CurrFieldNo = 0 then exit; if Rec."Unit of Measure Code" = xRec."Unit of Measure Code" then diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Transfer/SubcTransferLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Transfer/SubcTransferLineExt.Codeunit.al index 50d6316900..4eb13a9a2f 100644 --- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Transfer/SubcTransferLineExt.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Transfer/SubcTransferLineExt.Codeunit.al @@ -36,7 +36,7 @@ codeunit 99001544 "Subc. Transfer Line Ext." if Rec.IsTemporary() then exit; - if CurrFieldNo <> Rec.FieldNo("Item No.") then + if CurrFieldNo = 0 then exit; if Rec."Item No." = xRec."Item No." then @@ -53,7 +53,7 @@ codeunit 99001544 "Subc. Transfer Line Ext." if Rec.IsTemporary() then exit; - if CurrFieldNo <> Rec.FieldNo("Variant Code") then + if CurrFieldNo = 0 then exit; if Rec."Variant Code" = xRec."Variant Code" then @@ -70,7 +70,7 @@ codeunit 99001544 "Subc. Transfer Line Ext." if Rec.IsTemporary() then exit; - if CurrFieldNo <> Rec.FieldNo(Quantity) then + if CurrFieldNo = 0 then exit; if Rec.Quantity = xRec.Quantity then From 5ab33c31660179d4d3e85b551e3a22ec04f5ede3 Mon Sep 17 00:00:00 2001 From: MarkoAleksandric Date: Fri, 12 Jun 2026 17:01:50 +0200 Subject: [PATCH 3/3] blocking change routing and components --- .../SubcProdOrderCompExt.Codeunit.al | 146 ++++++++++++++++++ .../SubcProdOrderRtngExt.Codeunit.al | 136 ++++++++++++++++ .../Tests/SubcPurchSubcontTest.Codeunit.al | 112 ++++++++++++++ .../Tests/SubcTransOrdReservTest.Codeunit.al | 52 +++++++ 4 files changed, 446 insertions(+) diff --git a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderCompExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderCompExt.Codeunit.al index 1b8d358748..35e00e9ec3 100644 --- a/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderCompExt.Codeunit.al +++ b/src/Apps/W1/Subcontracting/App/src/Process/Codeunits/Extensions/Manufacturing/SubcProdOrderCompExt.Codeunit.al @@ -20,6 +20,8 @@ codeunit 99001524 "Subc. Prod. Order Comp. Ext." ExistingPurchLineErr: Label 'You cannot change this field because the component is already assigned to subcontracting purchase order %1.\\Updating the quantity is only allowed through the purchase order.', Comment = '%1=Document No'; ExistingTransferLineQst: Label 'The component has already been assigned to the subcontracting transfer order %1.\\The quantity may only be updated via the purchase order and processing of the stock transfer.', Comment = '%1=Transfer Order No'; ExistingTransferLineErr: Label 'You cannot open Tracking Specification because this component is already specified in Transfer Order %1.', Comment = '%1=Document No.'; + CannotModifyCompTransferExistsErr: Label 'You cannot change this component because transfer orders exist for the linked production order %1, purchase order %2.', Comment = '%1=Production Order No., %2=Purchase Order No.'; + CannotModifyCompStockAtSubcErr: Label 'You cannot change this component because there are remaining components or WIP items transferred to the subcontractor for production order %1, purchase order %2.', Comment = '%1=Production Order No., %2=Purchase Order No.'; [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Comp.-Reserve", OnAfterInitFromProdOrderComp, '', false, false)] local procedure OnAfterInitFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component") @@ -57,6 +59,47 @@ codeunit 99001524 "Subc. Prod. Order Comp. Ext." if Rec.IsTemporary then exit; CheckExistingSubcontractingTransferOrder(Rec, xRec, CurrFieldNo); + + if CurrFieldNo <> 0 then + if Rec."Location Code" <> xRec."Location Code" then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Bin Code", false, false)] + local procedure OnBeforeValidateBinCode(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if Rec."Bin Code" <> xRec."Bin Code" then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Item No.", false, false)] + local procedure OnBeforeValidateItemNo(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if Rec."Item No." <> xRec."Item No." then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Variant Code", false, false)] + local procedure OnBeforeValidateVariantCode(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if Rec."Variant Code" <> xRec."Variant Code" then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); end; [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Quantity per", false, false)] @@ -65,6 +108,47 @@ codeunit 99001524 "Subc. Prod. Order Comp. Ext." if Rec.IsTemporary then exit; CheckExistingDocumentsForSubcontracting(Rec, xRec, CurrFieldNo); + + if CurrFieldNo <> 0 then + if Rec."Quantity per" <> xRec."Quantity per" then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Expected Quantity", false, false)] + local procedure OnBeforeValidateExpectedQuantity(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if Rec."Expected Quantity" <> xRec."Expected Quantity" then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Component Supply Method", false, false)] + local procedure OnBeforeValidateComponentSupplyMethod(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if Rec."Component Supply Method" <> xRec."Component Supply Method" then + if xRec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeDeleteEvent, '', false, false)] + local procedure OnBeforeDeleteProdOrderComponent(var Rec: Record "Prod. Order Component"; RunTrigger: Boolean) + begin + if Rec.IsTemporary then + exit; + if not RunTrigger then + exit; + + if Rec."Component Supply Method" = Rec."Component Supply Method"::"Transfer to Vendor" then + CheckUncompletedSubcontractingDocumentsExist(Rec); end; local procedure CheckExistingPostedSubcontractingTransferOrder(ProdOrderComponent: Record "Prod. Order Component"): Boolean @@ -323,4 +407,66 @@ codeunit 99001524 "Subc. Prod. Order Comp. Ext." CheckExistingSubcontractingPurchaseOrder(ProdOrderComponent); end; end; + + local procedure CheckUncompletedSubcontractingDocumentsExist(ProdOrderComponent: Record "Prod. Order Component") + var + ProdOrderLine: Record "Prod. Order Line"; + ProdOrderRoutingLine: Record "Prod. Order Routing Line"; + PurchaseLine: Record "Purchase Line"; + begin + ProdOrderLine.SetLoadFields("Routing Reference No.", "Routing No."); + if not ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.") then + exit; + + PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.", "Prod. Order Line No."); + PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order); + PurchaseLine.SetRange("Prod. Order No.", ProdOrderComponent."Prod. Order No."); + PurchaseLine.SetRange("Prod. Order Line No.", ProdOrderComponent."Prod. Order Line No."); + + if ProdOrderComponent."Routing Link Code" <> '' then begin + ProdOrderRoutingLine.SetRange(Status, ProdOrderLine.Status); + ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No."); + ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLine."Routing Reference No."); + ProdOrderRoutingLine.SetRange("Routing Link Code", ProdOrderComponent."Routing Link Code"); + ProdOrderRoutingLine.SetLoadFields("Operation No."); + if ProdOrderRoutingLine.FindFirst() then + PurchaseLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No."); + end; + + if PurchaseLine.FindSet() then + repeat + if HasSubcTransferForPurchLine(PurchaseLine, ProdOrderComponent) then + Error(CannotModifyCompTransferExistsErr, PurchaseLine."Prod. Order No.", PurchaseLine."Document No."); + + ProdOrderComponent.SetRange("Subc. Purchase Order Filter", PurchaseLine."Document No."); + if HasStockAtSubcLocationForComponentForPurchLine(ProdOrderComponent) then + Error(CannotModifyCompStockAtSubcErr, PurchaseLine."Prod. Order No.", PurchaseLine."Document No."); + until PurchaseLine.Next() = 0; + end; + + local procedure HasSubcTransferForPurchLine(PurchaseLine: Record "Purchase Line"; ProdOrderComponent: Record "Prod. Order Component"): Boolean + var + TransferLine: Record "Transfer Line"; + begin + TransferLine.SetCurrentKey("Subc. Purch. Order No.", "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Operation No."); + TransferLine.SetRange("Subc. Purch. Order No.", PurchaseLine."Document No."); + TransferLine.SetRange("Subc. Prod. Order No.", ProdOrderComponent."Prod. Order No."); + TransferLine.SetRange("Subc. Prod. Order Line No.", ProdOrderComponent."Prod. Order Line No."); + TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComponent."Line No."); + exit(not TransferLine.IsEmpty()); + end; + + local procedure HasStockAtSubcLocationForComponentForPurchLine(ProdOrderComponent: Record "Prod. Order Component"): Boolean + var + SubcTransferManagement: Codeunit "Subc. Transfer Management"; + NetStockAtSubcLocation: Decimal; + begin + ProdOrderComponent.CalcFields("Subc. Qty. transf. to Subcontr"); + if ProdOrderComponent."Subc. Qty. transf. to Subcontr" = 0 then + exit(false); + + NetStockAtSubcLocation := ProdOrderComponent."Subc. Qty. transf. to Subcontr"; + NetStockAtSubcLocation -= SubcTransferManagement.CalcConsumedQtyAtSubcLocation(ProdOrderComponent); + exit(NetStockAtSubcLocation > 0); + end; } 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 e4a8723910..43231d175e 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 @@ -4,12 +4,31 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.Manufacturing.Subcontracting; +using Microsoft.Inventory.Transfer; using Microsoft.Manufacturing.Document; using Microsoft.Manufacturing.Routing; using Microsoft.Manufacturing.WorkCenter; +using Microsoft.Purchases.Document; codeunit 99001520 "Subc. Prod. Order Rtng. Ext." { + var + CannotModifyRtngLineTransferExistsErr: Label 'You cannot change this routing line because transfer orders exist for the linked production order %1, purchase order %2.', Comment = '%1=Production Order No., %2=Purchase Order No.'; + CannotModifyRtngLineStockAtSubcErr: Label 'You cannot change this routing line because there are remaining components or WIP items transferred to the subcontractor for production order %1, purchase order %2.', Comment = '%1=Production Order No., %2=Purchase Order No.'; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnBeforeDeleteEvent, '', false, false)] + local procedure OnBeforeDeleteProdOrderRtngLine(var Rec: Record "Prod. Order Routing Line"; RunTrigger: Boolean) + begin + if Rec.IsTemporary then + exit; + + if not RunTrigger then + exit; + + if Rec."Transfer WIP Item" then + CheckSubcRtngLineDocumentsExist(Rec); + end; + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnAfterDeleteEvent, '', false, false)] local procedure OnAfterDeleteProdOrderRtngLine(var Rec: Record "Prod. Order Routing Line"; RunTrigger: Boolean) begin @@ -30,10 +49,58 @@ codeunit 99001520 "Subc. Prod. Order Rtng. Ext." if Rec.IsTemporary then exit; + if CurrFieldNo <> 0 then + if (xRec."No." <> Rec."No.") and xRec."Transfer WIP Item" then + CheckSubcRtngLineDocumentsExist(xRec); + if (xRec."No." <> Rec."No.") and (Rec."Routing Link Code" <> '') then SubcontractingManagement.UpdLinkedComponents(Rec, true); end; + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnBeforeValidateEvent, "Operation No.", false, false)] + local procedure OnBeforeValidateOperationNo(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if (xRec."Operation No." <> Rec."Operation No.") and xRec."Transfer WIP Item" then + CheckSubcRtngLineDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnBeforeValidateEvent, "Routing Link Code", false, false)] + local procedure OnBeforeValidateRoutingLinkCode(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if (xRec."Routing Link Code" <> Rec."Routing Link Code") and xRec."Transfer WIP Item" then + CheckSubcRtngLineDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnBeforeValidateEvent, "Type", false, false)] + local procedure OnBeforeValidateType(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if (xRec.Type <> Rec.Type) and xRec."Transfer WIP Item" then + CheckSubcRtngLineDocumentsExist(xRec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnBeforeValidateEvent, "Transfer WIP Item", false, false)] + local procedure OnBeforeValidateTransferWIPItem(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer) + begin + if Rec.IsTemporary then + exit; + + if CurrFieldNo <> 0 then + if (xRec."Transfer WIP Item" <> Rec."Transfer WIP Item") and xRec."Transfer WIP Item" then + CheckSubcRtngLineDocumentsExist(xRec); + end; + [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnAfterValidateEvent, "Routing Link Code", false, false)] local procedure OnAfterValidateRoutingLinkCode(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer) begin @@ -101,4 +168,73 @@ codeunit 99001520 "Subc. Prod. Order Rtng. Ext." SubcontractingManagement.DelLocationLinkedComponents(ProdOrderRoutingLine, false); end; end; + + local procedure CheckSubcRtngLineDocumentsExist(ProdOrderRoutingLine: Record "Prod. Order Routing Line") + var + PurchaseLine: Record "Purchase Line"; + begin + PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.", "Prod. Order Line No."); + PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order); + PurchaseLine.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No."); + PurchaseLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No."); + PurchaseLine.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No."); + PurchaseLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No."); + if PurchaseLine.FindSet() then + repeat + if HasSubcTransferForPurchLine(PurchaseLine) then + Error(CannotModifyRtngLineTransferExistsErr, PurchaseLine."Prod. Order No.", PurchaseLine."Document No."); + if HasStockAtSubcLocation(PurchaseLine, ProdOrderRoutingLine) then + Error(CannotModifyRtngLineStockAtSubcErr, PurchaseLine."Prod. Order No.", PurchaseLine."Document No."); + until PurchaseLine.Next() = 0; + end; + + local procedure HasSubcTransferForPurchLine(PurchaseLine: Record "Purchase Line"): Boolean + var + TransferLine: Record "Transfer Line"; + begin + TransferLine.SetRange("Subc. Purch. Order No.", PurchaseLine."Document No."); + TransferLine.SetRange("Subc. Purch. Order Line No.", PurchaseLine."Line No."); + TransferLine.SetRange("Subc. Prod. Order No.", PurchaseLine."Prod. Order No."); + exit(not TransferLine.IsEmpty()); + end; + + local procedure HasStockAtSubcLocation(PurchaseLine: Record "Purchase Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Boolean + var + ProdOrderComponent: Record "Prod. Order Component"; + SubcWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; + SubcTransferManagement: Codeunit "Subc. Transfer Management"; + NetStockAtSubcLocation: Decimal; + begin + ProdOrderComponent.SetCurrentKey(Status, "Prod. Order No.", "Routing Link Code"); + ProdOrderComponent.SetRange(Status, "Production Order Status"::Released); + ProdOrderComponent.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No."); + ProdOrderComponent.SetRange("Prod. Order Line No.", PurchaseLine."Prod. Order Line No."); + ProdOrderComponent.SetRange("Component Supply Method", ProdOrderComponent."Component Supply Method"::"Transfer to Vendor"); + ProdOrderComponent.SetRange("Subc. Purchase Order Filter", PurchaseLine."Document No."); + ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code"); + ProdOrderComponent.SetAutoCalcFields("Subc. Qty. transf. to Subcontr"); + if ProdOrderComponent.FindSet() then + repeat + if ProdOrderComponent."Subc. Qty. transf. to Subcontr" <> 0 then begin + NetStockAtSubcLocation := ProdOrderComponent."Subc. Qty. transf. to Subcontr"; + NetStockAtSubcLocation -= SubcTransferManagement.CalcConsumedQtyAtSubcLocation(ProdOrderComponent); + if NetStockAtSubcLocation > 0 then + exit(true); + end; + until ProdOrderComponent.Next() = 0; + + SubcWIPLedgerEntry.SetCurrentKey("Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code"); + SubcWIPLedgerEntry.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No."); + SubcWIPLedgerEntry.SetRange("Prod. Order Status", "Production Order Status"::Released); + SubcWIPLedgerEntry.SetRange("Prod. Order Line No.", PurchaseLine."Prod. Order Line No."); + SubcWIPLedgerEntry.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No."); + SubcWIPLedgerEntry.SetRange("Routing No.", ProdOrderRoutingLine."Routing No."); + SubcWIPLedgerEntry.SetRange("Operation No.", ProdOrderRoutingLine."Operation No."); + SubcWIPLedgerEntry.SetRange("In Transit", false); + SubcWIPLedgerEntry.CalcSums("Quantity (Base)"); + if SubcWIPLedgerEntry."Quantity (Base)" <> 0 then + exit(true); + + exit(false); + end; } \ No newline at end of file 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 f07c0afb34..0f6b7901b3 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 @@ -510,6 +510,118 @@ codeunit 139991 "Subc. Purch. Subcont. Test" PostDirectTransferOrder(ReturnTransferHeader); end; + [Test] + [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder')] + procedure CannotModifyOrDeleteRoutingLineWhenTransferOrderExistsWithTransferToVendor() + var + Item: Record Item; + HomeLocation: Record Location; + ProdOrderRoutingLine: Record "Prod. Order Routing Line"; + ProductionOrder: Record "Production Order"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + WorkCenter: array[2] of Record "Work Center"; + MachineCenter: array[2] of Record "Machine Center"; + ProdOrderRtng: TestPage "Prod. Order Routing"; + begin + // [SCENARIO] Modifying key fields or deleting a Prod. Order Routing Line must be blocked + // when subcontracting transfer orders exist for Transfer to Vendor components. + Initialize(); + + // [GIVEN] A subcontracting purchase order with a linked transfer order + SetupSubContractingProdOrder(Item, HomeLocation, WorkCenter, MachineCenter, ProductionOrder, "Component Supply Method"::"Transfer to Vendor", LibraryRandom.RandIntInRange(1, 10)); + CreateSubcontractingPurchaseOrderForProdOrder(PurchaseHeader, PurchaseLine, Item, WorkCenter, ProductionOrder); + CreateTransferOrderForPurchaseOrder(PurchaseHeader); + + // [GIVEN] Find routing line for the subcontracting work center + ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No."); +#pragma warning disable AA0210 + ProdOrderRoutingLine.SetRange("Transfer WIP Item", true); +#pragma warning restore AA0210 + ProdOrderRoutingLine.FindFirst(); + + // [THEN] Changing No. is blocked + ProdOrderRtng.OpenEdit(); + ProdOrderRtng.GoToRecord(ProdOrderRoutingLine); + asserterror ProdOrderRtng."No.".SetValue(WorkCenter[1]."No."); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + ProdOrderRtng.Close(); + + // [THEN] Changing Type is blocked + ProdOrderRtng.OpenEdit(); + ProdOrderRtng.GoToRecord(ProdOrderRoutingLine); + asserterror ProdOrderRtng.Type.SetValue(ProdOrderRoutingLine.Type::"Machine Center"); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + ProdOrderRtng.Close(); + + // [THEN] Changing Routing Link Code is blocked + ProdOrderRtng.OpenEdit(); + ProdOrderRtng.GoToRecord(ProdOrderRoutingLine); + asserterror ProdOrderRtng."Routing Link Code".SetValue(''); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + ProdOrderRtng.Close(); + + // [THEN] Deleting the routing line is blocked + asserterror ProdOrderRoutingLine.Delete(true); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + end; + + [Test] + [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder')] + procedure CannotModifyOrDeleteRoutingLineWhenTransferOrderExistsWithVendorSupplied() + var + Item: Record Item; + HomeLocation: Record Location; + ProdOrderRoutingLine: Record "Prod. Order Routing Line"; + ProductionOrder: Record "Production Order"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + WorkCenter: array[2] of Record "Work Center"; + MachineCenter: array[2] of Record "Machine Center"; + ProdOrderRtng: TestPage "Prod. Order Routing"; + begin + // [SCENARIO] Modifying key fields or deleting a Prod. Order Routing Line must be blocked + // when subcontracting transfer orders exist for Transfer to Vendor components. + Initialize(); + + // [GIVEN] A subcontracting purchase order with a linked transfer order + SetupSubContractingProdOrder(Item, HomeLocation, WorkCenter, MachineCenter, ProductionOrder, "Component Supply Method"::"Vendor-Supplied", LibraryRandom.RandIntInRange(1, 10)); + CreateSubcontractingPurchaseOrderForProdOrder(PurchaseHeader, PurchaseLine, Item, WorkCenter, ProductionOrder); + CreateTransferOrderForPurchaseOrder(PurchaseHeader); + + // [GIVEN] Find routing line for the subcontracting work center + ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No."); +#pragma warning disable AA0210 + ProdOrderRoutingLine.SetRange("Transfer WIP Item", true); +#pragma warning restore AA0210 + ProdOrderRoutingLine.FindFirst(); + + // [THEN] Changing No. is blocked + ProdOrderRtng.OpenEdit(); + ProdOrderRtng.GoToRecord(ProdOrderRoutingLine); + asserterror ProdOrderRtng."No.".SetValue(WorkCenter[1]."No."); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + ProdOrderRtng.Close(); + + // [THEN] Changing Type is blocked + ProdOrderRtng.OpenEdit(); + ProdOrderRtng.GoToRecord(ProdOrderRoutingLine); + asserterror ProdOrderRtng.Type.SetValue(ProdOrderRoutingLine.Type::"Machine Center"); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + ProdOrderRtng.Close(); + + // [THEN] Changing Routing Link Code is blocked + ProdOrderRtng.OpenEdit(); + ProdOrderRtng.GoToRecord(ProdOrderRoutingLine); + asserterror ProdOrderRtng."Routing Link Code".SetValue(''); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + ProdOrderRtng.Close(); + + // [THEN] Deleting the routing line is blocked + asserterror ProdOrderRoutingLine.Delete(true); + Assert.ExpectedError('You cannot change this routing line because transfer orders exist'); + end; + [ModalPageHandler] procedure ItemTrackingLinesSimpleHandler(var ItemTrackingLines: TestPage "Item Tracking Lines") begin diff --git a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcTransOrdReservTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcTransOrdReservTest.Codeunit.al index ca4426c340..79a2777217 100644 --- a/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcTransOrdReservTest.Codeunit.al +++ b/src/Apps/W1/Subcontracting/Test/src/Codeunits/Tests/SubcTransOrdReservTest.Codeunit.al @@ -341,6 +341,58 @@ codeunit 149915 "Subc. TransOrd. Reserv. Test" Assert.AreEqual(30, TransferLine."Quantity (Base)", 'Recreated transfer line must have the full component quantity'); end; + [Test] + [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')] + procedure CannotModifyOrDeleteProdOrderComponentWhenTransferOrderExists() + var + Item: Record Item; + NewItem: Record Item; + ProdOrderComponent: Record "Prod. Order Component"; + ProductionOrder: Record "Production Order"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + WorkCenter: array[2] of Record "Work Center"; + MachineCenter: array[2] of Record "Machine Center"; + ProdOrderCompPage: TestPage "Prod. Order Components"; + begin + // [SCENARIO] Modifying key fields or deleting a Prod. Order Component with Component Supply Method = Transfer to Vendor + // must be blocked when a subcontracting transfer order exists. + Initialize(); + + // [GIVEN] A subcontracting transfer order linked to a production order with Transfer to Vendor components + SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 3, false); + CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 0); + CreateTransferOrder(PurchaseHeader); + + // [GIVEN] Create another item to use for Item No. change + LibraryInventory.CreateItem(NewItem); + + // [THEN] Changing Item No. is blocked + ProdOrderCompPage.OpenEdit(); + ProdOrderCompPage.GoToRecord(ProdOrderComponent); + asserterror ProdOrderCompPage."Item No.".SetValue(NewItem."No."); + Assert.ExpectedError('You cannot change this component because transfer orders exist'); + ProdOrderCompPage.Close(); + + // [THEN] Changing Quantity per is blocked + ProdOrderCompPage.OpenEdit(); + ProdOrderCompPage.GoToRecord(ProdOrderComponent); + asserterror ProdOrderCompPage."Quantity per".SetValue(ProdOrderComponent."Quantity per" + 1); + Assert.ExpectedError('You cannot change this component because transfer orders exist'); + ProdOrderCompPage.Close(); + + // [THEN] Changing Component Supply Method is blocked + ProdOrderCompPage.OpenEdit(); + ProdOrderCompPage.GoToRecord(ProdOrderComponent); + asserterror ProdOrderCompPage."Component Supply Method".SetValue("Component Supply Method"::Empty); + Assert.ExpectedError('You cannot change this component because transfer orders exist'); + ProdOrderCompPage.Close(); + + // [THEN] Deleting the component is blocked + asserterror ProdOrderComponent.Delete(true); + Assert.ExpectedError('You cannot change this component because transfer orders exist'); + end; + local procedure Initialize() begin LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. TransOrd. Reserv. Test");