diff --git a/src/Apps/W1/Subcontracting/App/ExtensionLogo.png b/src/Apps/W1/Subcontracting/App/ExtensionLogo.png
new file mode 100644
index 0000000000..8cce2b2fd0
Binary files /dev/null and b/src/Apps/W1/Subcontracting/App/ExtensionLogo.png differ
diff --git a/src/Apps/W1/Subcontracting/App/app.json b/src/Apps/W1/Subcontracting/App/app.json
new file mode 100644
index 0000000000..b8e06f388f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/app.json
@@ -0,0 +1,40 @@
+{
+ "id": "1f32a50d-0057-4b95-b5df-cc04d7e89470",
+ "name": "Subcontracting",
+ "publisher": "Microsoft",
+ "brief": "Manage subcontracting operations in your manufacturing process.",
+ "description": "Streamline your subcontracting workflow by managing production orders, routing operations, and component transfers to subcontractors. Create purchase orders for subcontracted work, track components sent to and returned from subcontractors, and maintain accurate costing for outsourced manufacturing operations.",
+ "version": "28.3.0.0",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2345592",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2182906",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2345593",
+ "url": "https://go.microsoft.com/fwlink/?linkid=2345594",
+ "logo": "ExtensionLogo.png",
+ "screenshots": [],
+ "dependencies": [],
+ "platform": "28.0.0.0",
+ "application": "28.3.0.0",
+ "idRanges": [
+ {
+ "from": 99001500,
+ "to": 99001600
+ }
+ ],
+ "features": [
+ "NoImplicitWith",
+ "TranslationFile"
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "includeSourceInSymbolFile": true,
+ "allowDownloadingSource": true
+ },
+ "internalsVisibleTo": [
+ {
+ "id": "32b0d4d1-eee6-4a42-9d76-86a026cd2a71",
+ "name": "Subcontracting Test",
+ "publisher": "Microsoft"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcFeatureFlagHandler.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcFeatureFlagHandler.Codeunit.al
new file mode 100644
index 0000000000..c40a473bc0
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcFeatureFlagHandler.Codeunit.al
@@ -0,0 +1,27 @@
+#if not CLEAN28
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Setup;
+
+codeunit 99001569 "Subc. Feature Flag Handler"
+{
+ ObsoleteState = Pending;
+ ObsoleteTag = '28.0';
+ ObsoleteReason = 'Legacy Subcontracting will be discontinued, environments should move to the Subcontracting App so this codeunit will be removed in a future release.';
+
+ [Obsolete('Legacy Subcontracting will be discontinued, environments should move to the Subcontracting App so this codeunit will be removed in a future release.', '28.0')]
+ procedure IsSubcontractingEnabled(): Boolean
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.SetLoadFields("Legacy Subcontracting");
+ if not ManufacturingSetup.Get() then
+ exit(false);
+ exit(not ManufacturingSetup."Legacy Subcontracting");
+ end;
+}
+#endif
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcILEntries.PageExt.al b/src/Apps/W1/Subcontracting/App/src/General/SubcILEntries.PageExt.al
new file mode 100644
index 0000000000..c7565cda6f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcILEntries.PageExt.al
@@ -0,0 +1,122 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+
+pageextension 99001501 "Subc. ILEntries" extends "Item Ledger Entries"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Subc. Purch. Order No."; Rec."Subc. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ Visible = false;
+ }
+ field("Subc. Purch. Order Line No."; Rec."Subc. Purch. Order Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ Visible = false;
+ }
+ field("Prod. Order No."; Rec."Subc. Prod. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production order.';
+ Visible = false;
+ }
+ field("Prod. Order Line No."; Rec."Subc. Prod. Order Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production order line.';
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("&Application")
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+ action("Production Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order';
+ Enabled = Rec."Subc. Prod. Order No." <> '';
+ Image = Production;
+ ToolTip = 'View the related production order.';
+ trigger OnAction()
+ begin
+ ShowProductionOrder(Rec);
+ end;
+ }
+ action("Production Order Routing")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Routing';
+ Enabled = Rec."Subc. Prod. Order No." <> '';
+ Image = Route;
+ ToolTip = 'View the related production order routing.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderRouting(Rec);
+ end;
+ }
+ action("Production Order Components")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Components';
+ Enabled = Rec."Subc. Prod. Order No." <> '';
+ Image = Components;
+ ToolTip = 'View the related production order components.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderComponents(Rec);
+ end;
+ }
+ action("Purchase Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Purchase Order';
+ Enabled = Rec."Subc. Purch. Order No." <> '';
+ Image = Order;
+ ToolTip = 'View the related subcontracting purchase order.';
+ trigger OnAction()
+ begin
+ ShowPurchaseOrder(Rec);
+ end;
+ }
+ }
+ }
+ }
+ var
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowPurchaseOrder(RecRelatedVariant: Variant)
+ begin
+ SubcPurchFactboxMgmt.ShowPurchaseOrder(RecRelatedVariant);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcItemJnlPostLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcItemJnlPostLineExt.Codeunit.al
new file mode 100644
index 0000000000..0e99a2df5a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcItemJnlPostLineExt.Codeunit.al
@@ -0,0 +1,136 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Costing;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Posting;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+
+codeunit 99001515 "Subc. ItemJnlPostLine Ext"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Post Line", OnAfterInitItemLedgEntry, '', false, false)]
+ local procedure OnAfterInitItemLedgEntry(var NewItemLedgEntry: Record "Item Ledger Entry"; ItemJournalLine: Record "Item Journal Line"; var ItemLedgEntryNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+
+ UpdateNewItemLedgerEntry(NewItemLedgEntry, ItemJournalLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Post Line", OnBeforeInsertCapLedgEntry, '', false, false)]
+ local procedure OnBeforeInsertCapLedgEntry(var CapLedgEntry: Record "Capacity Ledger Entry"; ItemJournalLine: Record "Item Journal Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+
+ UpdateCapLedgerEntry(CapLedgEntry, ItemJournalLine);
+ end;
+
+#if not CLEAN27
+#pragma warning disable AL0432
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Post Line", OnAfterPostOutput, '', false, false)]
+#pragma warning restore AL0432
+#else
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Item Jnl.-Post Line", OnAfterPostOutput, '', false, false)]
+#endif
+ local procedure OnAfterPostOutput(var ItemLedgerEntry: Record "Item Ledger Entry"; var ProdOrderLine: Record "Prod. Order Line"; var ItemJournalLine: Record "Item Journal Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+
+ UpdateProdOrderRoutingLine(ProdOrderLine, ItemJournalLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Post Line", OnBeforeInsertCapValueEntry, '', false, false)]
+ local procedure "Item Jnl.-Post Line_OnBeforeInsertCapValueEntry"(var ValueEntry: Record "Value Entry"; ItemJnlLine: Record "Item Journal Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+
+ ClearInvoicedQuantityForItemChargeSubAssign(ValueEntry, ItemJnlLine);
+ CopyItemChargeNoForItemChargeSubAssign(ValueEntry, ItemJnlLine);
+ end;
+
+ local procedure UpdateProdOrderRoutingLine(var ProdOrderLine: Record "Prod. Order Line"; var ItemJournalLine: Record "Item Journal Line")
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ begin
+ if not ItemJournalLine.Subcontracting then
+ exit;
+
+ if not ProductionOrder.Get(ProdOrderLine.Status, ProdOrderLine."Prod. Order No.") then
+ exit;
+
+ if not ProdOrderRoutingLine.Get(ProdOrderLine.Status, ProdOrderLine."Prod. Order No.", ProdOrderLine."Line No.", ProdOrderLine."Routing No.", ItemJournalLine."Operation No.") then
+ exit;
+
+ CapacityLedgerEntry.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ CapacityLedgerEntry.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ CapacityLedgerEntry.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ CapacityLedgerEntry.SetRange("Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ CapacityLedgerEntry.CalcSums("Output Quantity");
+
+ if CapacityLedgerEntry."Output Quantity" >= ProdOrderLine."Quantity (Base)" then begin
+ ProdOrderRoutingLine."Routing Status" := "Prod. Order Routing Status"::Finished;
+ ProdOrderRoutingLine.Modify();
+ end;
+ end;
+
+ local procedure UpdateNewItemLedgerEntry(var NewItemLedgerEntry: Record "Item Ledger Entry"; var ItemJournalLine: Record "Item Journal Line")
+ begin
+ NewItemLedgerEntry."Subc. Prod. Order No." := ItemJournalLine."Subc. Prod. Order No.";
+ NewItemLedgerEntry."Subc. Prod. Order Line No." := ItemJournalLine."Subc. Prod. Order Line No.";
+ NewItemLedgerEntry."Subc. Purch. Order No." := ItemJournalLine."Subc. Purch. Order No.";
+ NewItemLedgerEntry."Subc. Purch. Order Line No." := ItemJournalLine."Subc. Purch. Order Line No.";
+ NewItemLedgerEntry."Subc. Operation No." := ItemJournalLine."Subc. Operation No.";
+ end;
+
+ local procedure UpdateCapLedgerEntry(var CapacityLedgerEntry: Record "Capacity Ledger Entry"; var ItemJournalLine: Record "Item Journal Line")
+ begin
+ CapacityLedgerEntry."Subc. Subcontractor No." := ItemJournalLine."Source No.";
+ CapacityLedgerEntry."Subc. Purch. Order No." := ItemJournalLine."Subc. Purch. Order No.";
+ CapacityLedgerEntry."Subc. Purch. Order Line No." := ItemJournalLine."Subc. Purch. Order Line No.";
+ end;
+
+ local procedure ClearInvoicedQuantityForItemChargeSubAssign(var ValueEntry: Record "Value Entry"; var ItemJournalLine: Record "Item Journal Line")
+ begin
+ if ItemJournalLine."Subc. Item Charge Assign." and (ValueEntry."Entry Type" = "Cost Entry Type"::"Direct Cost") then
+ ValueEntry."Invoiced Quantity" := 0;
+ end;
+
+ local procedure CopyItemChargeNoForItemChargeSubAssign(var ValueEntry: Record "Value Entry"; ItemJournalLine: Record "Item Journal Line")
+ begin
+ if ItemJournalLine."Subc. Item Charge Assign." and (ItemJournalLine."Item Charge No." <> '') then
+ ValueEntry."Item Charge No." := ItemJournalLine."Item Charge No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcItemLedgerEntry.TableExt.al b/src/Apps/W1/Subcontracting/App/src/General/SubcItemLedgerEntry.TableExt.al
new file mode 100644
index 0000000000..852f0a8820
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcItemLedgerEntry.TableExt.al
@@ -0,0 +1,48 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Purchases.Document;
+
+tableextension 99001500 "Subc. Item Ledger Entry" extends "Item Ledger Entry"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001510; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001511; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001512; "Subc. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subc. Purch. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Purchase Header"."No." where("Document Type" = const(Order));
+ }
+ field(99001513; "Subc. Purch. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Purchase Line"."Line No." where("Document Type" = const(Order),
+ "Document No." = field("Subc. Purch. Order No."));
+ }
+ field(99001514; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Subc. Operation No.';
+ DataClassification = CustomerContent;
+ }
+ }
+ keys
+ {
+ key(Key99001500; "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Purch. Order No.", "Subc. Purch. Order Line No.") { }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcNotificationMgmt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcNotificationMgmt.Codeunit.al
new file mode 100644
index 0000000000..89f7e46cf2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcNotificationMgmt.Codeunit.al
@@ -0,0 +1,117 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Environment.Configuration;
+
+codeunit 99001506 "Subc. Notification Mgmt."
+{
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ ProdOrdNotificationDescriptionTxt: Label 'Show a notification if Production Orders were created for Subcontracting.';
+ ProdOrdNotificationNameLbl: Label 'Show Created Production Orders';
+ SubcOrdNotificationDescriptionTxt: Label 'Show a notification if Subcontracting Orders were created for Subcontracting.';
+ SubcOrdNotificationNameLbl: Label 'Show Created Subcontracting Orders';
+
+ procedure ShowCreatedProductionOrderConfirmationMessageCode(): Code[50]
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+#endif
+ exit(UpperCase(GetShowCreatedProductionOrderCode()));
+ end;
+
+ procedure ShowCreatedSubcontractingOrderConfirmationMessageCode(): Code[50]
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+#endif
+ exit(UpperCase(GetShowCreatedSubContPurchOrderCode()));
+ end;
+
+ procedure GetShowCreatedProductionOrderCode(): Code[50]
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+#endif
+ exit('Show Created Production Orders');
+ end;
+
+ procedure GetShowCreatedSubContPurchOrderCode(): Code[50]
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+#endif
+ exit('Show Created Subcontracting Orders');
+ end;
+
+ [EventSubscriber(ObjectType::Page, Page::"My Notifications", OnInitializingNotificationWithDefaultState, '', false, false)]
+ local procedure InitializeSubcontractingNotifications()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ RegisterSubcontrProductionOrderCreatedNotification();
+ RegisterSubcontrPurchOrderCreatedNotification();
+ end;
+
+ local procedure RegisterSubcontrProductionOrderCreatedNotification()
+ var
+ MyNotifications: Record "My Notifications";
+ begin
+ MyNotifications.InsertDefault(GetGuidProductionOrderCreatedNotification(), ProdOrdNotificationNameLbl, ProdOrdNotificationDescriptionTxt, true);
+ end;
+
+ local procedure RegisterSubcontrPurchOrderCreatedNotification()
+ var
+ MyNotifications: Record "My Notifications";
+ begin
+ MyNotifications.InsertDefault(GetGuidSubcontractingPOCreatedNotification(), SubcOrdNotificationNameLbl, SubcOrdNotificationDescriptionTxt, true);
+ end;
+
+ procedure DisableNotification(var NotificationVar: Notification)
+ var
+ MyNotifications: Record "My Notifications";
+ PageMyNotifications: Page "My Notifications";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PageMyNotifications.InitializeNotificationsWithDefaultState();
+ MyNotifications.Disable(NotificationVar.Id());
+ end;
+
+ procedure GetGuidProductionOrderCreatedNotification(): Guid
+ begin
+ exit('{5d564aca-ce60-4345-ba68-e1e50976a346}');
+ end;
+
+ procedure GetGuidSubcontractingPOCreatedNotification(): Guid
+ begin
+ exit('{f7b10c9e-071a-4455-a048-d17b29ef764c}');
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcReportingTriggersExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcReportingTriggersExt.Codeunit.al
new file mode 100644
index 0000000000..218317c77d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcReportingTriggersExt.Codeunit.al
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Reports;
+using System.Environment;
+
+codeunit 99001512 "Subc. Reporting Triggers Ext"
+{
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reporting Triggers", SubstituteReport, '', false, false)]
+ local procedure SubstituteDetailedCalculation(ReportId: Integer; var NewReportId: Integer)
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ReportId = Report::"Detailed Calculation" then
+ NewReportId := Report::"Subc. Detailed Calculation";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcSessionState.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcSessionState.Codeunit.al
new file mode 100644
index 0000000000..d8c5f3b96a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcSessionState.Codeunit.al
@@ -0,0 +1,117 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+codeunit 99001500 "Subc. Session State"
+{
+ SingleInstance = true;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ CodeDictionary: Dictionary of [Text, Code[1024]];
+ DateDictionary: Dictionary of [Text, Date];
+ RecordIDDictionary: Dictionary of [Text, RecordId];
+
+ procedure ClearAllDictionariesForKey(StoredKey: Text)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if CodeDictionary.ContainsKey(StoredKey) then
+ CodeDictionary.Remove(StoredKey);
+
+ if DateDictionary.ContainsKey(StoredKey) then
+ DateDictionary.Remove(StoredKey);
+
+ if RecordIDDictionary.ContainsKey(StoredKey) then
+ RecordIDDictionary.Remove(StoredKey);
+ end;
+
+ procedure SetCode(KeyToStore: Text; CodeToStore: Code[1024])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if CodeDictionary.ContainsKey(KeyToStore) then
+ CodeDictionary.Set(KeyToStore, CodeToStore)
+ else
+ CodeDictionary.Add(KeyToStore, CodeToStore);
+ end;
+
+ procedure SetDate(KeyToStore: Text; DateToStore: Date)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if DateDictionary.ContainsKey(KeyToStore) then
+ DateDictionary.Set(KeyToStore, DateToStore)
+ else
+ DateDictionary.Add(KeyToStore, DateToStore);
+ end;
+
+ procedure SetRecordID(KeyToStore: Text; RecordIDToStore: RecordId)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if RecordIDDictionary.ContainsKey(KeyToStore) then
+ RecordIDDictionary.Set(KeyToStore, RecordIDToStore)
+ else
+ RecordIDDictionary.Add(KeyToStore, RecordIDToStore);
+ end;
+
+ procedure GetCode(StoredKey: Text): Code[1024]
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if CodeDictionary.ContainsKey(StoredKey) then
+ exit(CodeDictionary.Get(StoredKey));
+ end;
+
+ procedure GetDate(StoredKey: Text): Date
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if DateDictionary.ContainsKey(StoredKey) then
+ exit(DateDictionary.Get(StoredKey));
+ end;
+
+ procedure GetRecordID(StoredKey: Text; var ReturnRecordID: RecordId)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ Clear(ReturnRecordID);
+ if RecordIDDictionary.ContainsKey(StoredKey) then
+ ReturnRecordID := RecordIDDictionary.Get(StoredKey);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcSynchronizeManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcSynchronizeManagement.Codeunit.al
new file mode 100644
index 0000000000..4699420854
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcSynchronizeManagement.Codeunit.al
@@ -0,0 +1,281 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Enums;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+codeunit 99001511 "Subc. Synchronize Management"
+{
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ CannotDeleteSubcOrderTitleLbl: Label 'Transfer Order Exists';
+ CannotDeleteSubcOrderWithTransferOrderErr: Label 'You cannot delete Subcontracting Order %1 because Transfer Order %2 is associated with it. Delete or receive the Transfer Order first.', Comment = '%1=Subcontracting Order No., %2=Transfer Order No.';
+ CannotDeleteSubcOrderWithTransferOrdersErr: Label 'You cannot delete Subcontracting Order %1 because Transfer Orders are associated with it. Delete or receive all Transfer Orders first.', Comment = '%1=Subcontracting Order No.';
+ OpenTransferOrderLbl: Label 'Open Transfer Order';
+
+ procedure SynchronizeExpectedReceiptDate(var PurchaseLine: Record "Purchase Line"; xRecPurchaseLine: Record "Purchase Line")
+ var
+ ProductionOrder: Record "Production Order";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not IsSubcontractingLine(PurchaseLine) then
+ exit;
+
+ if PurchaseLine."Expected Receipt Date" = xRecPurchaseLine."Expected Receipt Date" then
+ exit;
+ if PurchaseLine."Qty. Received (Base)" <> 0 then
+ exit;
+
+ if not ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.") then
+ exit;
+
+ if not ProductionOrder."Created from Purch. Order" then
+ exit;
+
+ if ProductionOrder."Due Date" <> PurchaseLine."Expected Receipt Date" then begin
+ ProductionOrder.SetUpdateEndDate();
+ ProductionOrder.Validate("Due Date", PurchaseLine."Expected Receipt Date");
+ ProductionOrder.Modify();
+ end;
+ end;
+
+ procedure SynchronizeQuantity(var PurchaseLine: Record "Purchase Line"; xRecPurchaseLine: Record "Purchase Line")
+ var
+ ItemUnitofMeasure: Record "Item Unit of Measure";
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProductionOrder: Record "Production Order";
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+ PurchLineBaseQuantity: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not IsSubcontractingLine(PurchaseLine) then
+ exit;
+
+ if (PurchaseLine.Quantity = xRecPurchaseLine.Quantity) and (PurchaseLine."Unit of Measure Code" = xRecPurchaseLine."Unit of Measure Code") then
+ exit;
+
+ if PurchaseLine."Qty. Received (Base)" <> 0 then
+ exit;
+
+ if not ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.") then
+ exit;
+
+ if not ProductionOrder."Created from Purch. Order" then
+ exit;
+
+ ItemUnitofMeasure.Get(PurchaseLine."No.", PurchaseLine."Unit of Measure Code");
+ PurchLineBaseQuantity :=
+ UnitofMeasureManagement.CalcBaseQty(PurchaseLine."No.", PurchaseLine."Variant Code", PurchaseLine."Unit of Measure Code", PurchaseLine.Quantity, ItemUnitofMeasure."Qty. per Unit of Measure", ItemUnitofMeasure."Qty. Rounding Precision", PurchaseLine.FieldCaption("Qty. Rounding Precision"), PurchaseLine.FieldCaption(Quantity), PurchaseLine.FieldCaption("Quantity (Base)"));
+
+ if ProductionOrder.Quantity <> PurchLineBaseQuantity then begin
+ ProductionOrder.Quantity := PurchLineBaseQuantity;
+ ProductionOrder.Modify();
+ end;
+
+ if not ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit;
+
+ if ProdOrderLine.Quantity = PurchLineBaseQuantity then
+ exit;
+
+ ProdOrderLine.Validate(Quantity, PurchLineBaseQuantity);
+ ProdOrderLine.Modify();
+
+ ProdOrderComponent.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No.");
+ ProdOrderComponent.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ ProdOrderComponent.SetRange(Status, "Production Order Status"::Released);
+ if ProdOrderComponent.IsEmpty() then
+ exit;
+
+ ProdOrderComponent.FindSet();
+ repeat
+ ProdOrderComponent.Validate("Quantity per");
+ ProdOrderComponent.Modify();
+ until ProdOrderComponent.Next() = 0;
+ end;
+
+ procedure DeleteEnhancedDocumentsByChangeOfVendorNo(var PurchaseHeader: Record "Purchase Header"; var xPurchaseHeader: Record "Purchase Header")
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ ItemLedgerEntry, ItemLedgerEntry2 : Record "Item Ledger Entry";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine, PurchaseLine2, PurchaseLineModify : Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetFilter("No.", '<>%1', '');
+ PurchaseLine.SetFilter("Prod. Order No.", '<>%1', '');
+ PurchaseLine.SetRange("Qty. Received (Base)", 0);
+
+ PurchaseLine2.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine2.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine2.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine2.SetFilter("No.", '<>%1', '');
+ PurchaseLine2.SetRange("Prod. Order No.", '');
+ PurchaseLine2.SetRange("Qty. Received (Base)", 0);
+
+ if not PurchaseLine.IsEmpty() then begin
+ PurchaseLine.FindSet();
+ repeat
+ if ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.") then
+ if ProductionOrder."Created from Purch. Order" then begin
+ ItemLedgerEntry.SetRange("Order Type", "Inventory Order Type"::Production);
+ ItemLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ if ItemLedgerEntry.IsEmpty() then begin
+ CapacityLedgerEntry.SetRange("Order Type", "Inventory Order Type"::Production);
+ CapacityLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ if CapacityLedgerEntry.IsEmpty() then begin
+ ProductionOrder.DeleteProdOrderRelations();
+
+ // Delete References to Production Order to delete
+ PurchaseLineModify.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLineModify.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLineModify.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLineModify.SetFilter("No.", '<>%1', '');
+ PurchaseLineModify.SetRange("Prod. Order No.", ProductionOrder."No.");
+ if not PurchaseLineModify.IsEmpty() then begin
+ PurchaseLineModify.ModifyAll("Prod. Order Line No.", 0);
+ PurchaseLineModify.ModifyAll("Operation No.", '');
+ PurchaseLineModify.ModifyAll("Routing No.", '');
+ PurchaseLineModify.ModifyAll("Routing Reference No.", 0);
+ PurchaseLineModify.ModifyAll("Prod. Order No.", '');
+ end;
+
+ // Delete Subcontracting dependent Purchase Lines
+ PurchaseLine2.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ if not PurchaseLine2.IsEmpty() then
+ PurchaseLine2.DeleteAll(true);
+
+ TransferHeader.SetCurrentKey("Source ID", "Subc. Source Type", "Source Subtype");
+ TransferHeader.SetRange("Source ID", xPurchaseHeader."Buy-from Vendor No.");
+ TransferHeader.SetRange("Subc. Source Type", "Transfer Source Type"::Subcontracting);
+ TransferHeader.SetRange("Source Subtype", TransferHeader."Source Subtype"::"2");
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ if not TransferHeader.IsEmpty() then begin
+ TransferHeader.FindFirst();
+ ItemLedgerEntry2.SetRange("Order Type", "Inventory Order Type"::Production);
+ ItemLedgerEntry2.SetRange("Order No.", ProductionOrder."No.");
+ if ItemLedgerEntry2.IsEmpty() then
+ TransferHeader.Delete(true);
+ end;
+ ProductionOrder.Delete();
+ end;
+ end;
+ end;
+ until PurchaseLine.Next() = 0;
+ end;
+ end;
+
+ procedure CheckTransferOrderExistsForPurchaseHeader(var PurchaseHeader: Record "Purchase Header")
+ var
+ TransferHeader: Record "Transfer Header";
+ TransferOrderErrorInfo: ErrorInfo;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ if TransferHeader.IsEmpty() then
+ exit;
+
+ TransferOrderErrorInfo.Title := CannotDeleteSubcOrderTitleLbl;
+ TransferOrderErrorInfo.RecordId := PurchaseHeader.RecordId;
+ if TransferHeader.Count() = 1 then begin
+ TransferHeader.FindFirst();
+ TransferOrderErrorInfo.Message := StrSubstNo(CannotDeleteSubcOrderWithTransferOrderErr, PurchaseHeader."No.", TransferHeader."No.");
+ end else
+ TransferOrderErrorInfo.Message := StrSubstNo(CannotDeleteSubcOrderWithTransferOrdersErr, PurchaseHeader."No.");
+ TransferOrderErrorInfo.AddAction(OpenTransferOrderLbl, Codeunit::"Subc. Purchase Header Ext", 'ShowTransferOrdersForPurchHeader');
+ Error(TransferOrderErrorInfo);
+ end;
+
+ procedure DeleteEnhancedDocumentsByDeletePurchLine(var PurchaseLine: Record "Purchase Line")
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine2: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not IsSubcontractingLine(PurchaseLine) then
+ exit;
+
+ if PurchaseLine."Qty. Received (Base)" <> 0 then
+ exit;
+
+ if ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.") then begin
+ ItemLedgerEntry.SetRange("Order Type", "Inventory Order Type"::Production);
+ ItemLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ if ItemLedgerEntry.IsEmpty() then begin
+ CapacityLedgerEntry.SetRange("Order Type", "Inventory Order Type"::Production);
+ CapacityLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ if CapacityLedgerEntry.IsEmpty() then
+ if ProductionOrder."Created from Purch. Order" then begin
+ ProductionOrder.DeleteProdOrderRelations();
+
+ // Delete Subcontracting dependent Purchase Lines
+ PurchaseLine2.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ if PurchaseLine2.FindSet() then
+ PurchaseLine2.DeleteAll(true);
+
+ ProductionOrder.Delete();
+ end;
+ end;
+ end;
+ end;
+
+ local procedure IsSubcontractingLine(var PurchaseLine: Record "Purchase Line") IsSubcontracting: Boolean
+ begin
+ if PurchaseLine.Type <> "Purchase Line Type"::Item then
+ exit(IsSubcontracting);
+
+ if PurchaseLine."No." = '' then
+ exit(IsSubcontracting);
+
+ if PurchaseLine."Prod. Order No." = '' then
+ exit(IsSubcontracting);
+
+ IsSubcontracting := true;
+ exit(IsSubcontracting);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al
new file mode 100644
index 0000000000..95709ae407
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/General/SubcontractingManagement.Codeunit.al
@@ -0,0 +1,428 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Company;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Planning;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using System.Utilities;
+
+codeunit 99001505 "Subcontracting Management"
+{
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ RoutingLinkUpdConfQst: Label 'If you change the Work Center, you will also change the default location for components with Routing Link Code=%1.\Do you want to continue anyway?', Comment = '%1=Routing Link Code';
+ SuccessfullyUpdatedMsg: Label 'Successfully updated.';
+ UpdateIsCancelledErr: Label 'The update is cancelled.';
+ WorkCenterVendorDoesntExistErr: Label 'Subcontractor %1 on Work Center %2 does not exist.', Comment = 'Parameter %1 - subcontractor/vendor number, %2 - work center number.';
+ PurchOrderExistErr: Label 'The currently selected component %1 is already used in Purchase Order %2. Therefore, it is not permitted to change the %3 field.', Comment = '%1=Item No, %2=Purchase Order No, %3=Field Caption';
+ HasManufacturingSetup: Boolean;
+
+ procedure ChangeLocationOnProdOrderComponent(var ProdOrderComponent: Record "Prod. Order Component"; VendorSubcontrLocation: Code[10]; OriginalLocationCode: Code[10]; OriginalBinCode: Code[20])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ case ProdOrderComponent."Component Supply Method" of
+ "Component Supply Method"::"Consignment at Vendor",
+ "Component Supply Method"::"Vendor-Supplied":
+ if (VendorSubcontrLocation <> '') and (ProdOrderComponent."Location Code" <> VendorSubcontrLocation) then
+ ProdOrderComponent.Validate("Location Code", 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);
+ ProdOrderComponent."Subc. Original Location Code" := '';
+ end;
+ if (ProdOrderComponent."Bin Code" <> OriginalBinCode) and (OriginalBinCode <> '') then begin
+ ProdOrderComponent.Validate("Bin Code", OriginalBinCode);
+ ProdOrderComponent."Subc. Orig. Bin Code" := '';
+ end;
+ end;
+ end;
+ end;
+
+ procedure ChangeLocationOnPlanningComponent(var PlanningComponent: Record "Planning Component"; VendorSubcontrLocation: Code[10]; OriginalLocationCode: Code[10]; OriginalBinCode: Code[20])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ case PlanningComponent."Component Supply Method" of
+ "Component Supply Method"::"Consignment at Vendor",
+ "Component Supply Method"::"Vendor-Supplied":
+ if (VendorSubcontrLocation <> '') and (PlanningComponent."Location Code" <> VendorSubcontrLocation) then
+ PlanningComponent.Validate("Location Code", VendorSubcontrLocation);
+
+ "Component Supply Method"::"Transfer to Vendor",
+ "Component Supply Method"::Empty:
+ begin
+ if (PlanningComponent."Location Code" <> OriginalLocationCode) and (OriginalLocationCode <> '') then begin
+ PlanningComponent.Validate("Location Code", OriginalLocationCode);
+ PlanningComponent."Orig. Location Code" := '';
+ end;
+ if (PlanningComponent."Bin Code" <> OriginalBinCode) and (OriginalBinCode <> '') then begin
+ PlanningComponent.Validate("Bin Code", OriginalBinCode);
+ PlanningComponent."Orig. Bin Code" := '';
+ end;
+ end;
+ end;
+ end;
+
+ procedure DelLocationLinkedComponents(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ShowMsg: Boolean)
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ StockkeepingUnit: Record "Stockkeeping Unit";
+ ConfirmManagement: Codeunit "Confirm Management";
+ PlanningGetParameters: Codeunit "Planning-Get Parameters";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderComponent.SetRange(Status, ProdOrderRoutingLine.Status);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderComponent.SetRange("Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ if not ProdOrderComponent.IsEmpty() then begin
+ ProdOrderComponent.FindSet();
+ if ShowMsg then
+ if not ConfirmManagement.GetResponseOrDefault(StrSubstNo(RoutingLinkUpdConfQst, ProdOrderRoutingLine."Routing Link Code"), true) then
+ Error(UpdateIsCancelledErr);
+
+ ProdOrderLine.SetLoadFields("Item No.", "Variant Code", "Location Code");
+ ProdOrderLine.Get(ProdOrderRoutingLine.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.");
+ PlanningGetParameters.AtSKU(
+ StockkeepingUnit,
+ ProdOrderLine."Item No.",
+ ProdOrderLine."Variant Code",
+ ProdOrderLine."Location Code");
+ repeat
+ ProdOrderComponent.Validate("Location Code", StockkeepingUnit."Components at Location");
+ ProdOrderComponent.Modify();
+ until ProdOrderComponent.Next() = 0;
+
+ if ShowMsg then
+ Message(SuccessfullyUpdatedMsg);
+ end;
+ end;
+
+ procedure GetSubcontractor(WorkCenterNo: Code[20]; var Vendor: Record Vendor): Boolean
+ var
+ WorkCenter: Record "Work Center";
+ HasSubcontractor, IsHandled : Boolean;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(false);
+
+#endif
+ OnBeforeGetSubcontractor(WorkCenterNo, Vendor, HasSubcontractor, IsHandled);//DO NOT DELETE
+ if IsHandled then
+ exit(HasSubcontractor);
+
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ WorkCenter.Get(WorkCenterNo);
+ if WorkCenter."Subcontractor No." <> '' then begin
+ Vendor.SetLoadFields("Subc. Location Code");
+ if not Vendor.Get(WorkCenter."Subcontractor No.") then
+ Error(WorkCenterVendorDoesntExistErr, WorkCenter."Subcontractor No.", WorkCenter."No.");
+ Vendor.TestField("Subc. Location Code");
+ exit(true);
+ end;
+ exit(false);
+ end;
+
+ procedure UpdateSubcontractorPriceForRequisitionLine(var RequisitionLine: Record "Requisition Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if IsSubcontracting(RequisitionLine."Work Center No.") then
+ RequisitionLine.UpdateSubcontractorPrice();
+ end;
+
+ procedure UpdateLinkedComponentsAfterRoutingTransfer(var ProdOrderLine: Record "Prod. Order Line"; var RoutingLine: Record "Routing Line"; var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ WorkCenter: Record "Work Center";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProdOrderRoutingLine.Type <> "Capacity Type"::"Work Center" then
+ exit;
+
+ if ProdOrderRoutingLine."Routing Link Code" = '' then
+ exit;
+
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ WorkCenter.Get(RoutingLine."Work Center No.");
+ if WorkCenter."Subcontractor No." = '' then
+ exit;
+
+ UpdLinkedComponents(ProdOrderRoutingLine, false);
+ end;
+
+ procedure UpdateComponentSupplyMethodForPlanningComponent(var PlanningComponent: Record "Planning Component")
+ var
+ PlanningRoutingLine: Record "Planning Routing Line";
+ Vendor: Record Vendor;
+ OrigLocationCode, VendorSubcontractingLocationCode : Code[10];
+ OrigBinCode: Code[20];
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PlanningComponent."Routing Link Code" = '' then
+ exit;
+
+ PlanningRoutingLine.SetRange("Worksheet Template Name", PlanningComponent."Worksheet Template Name");
+ PlanningRoutingLine.SetRange("Worksheet Batch Name", PlanningComponent."Worksheet Batch Name");
+ PlanningRoutingLine.SetRange("Worksheet Line No.", PlanningComponent."Worksheet Line No.");
+ PlanningRoutingLine.SetRange("Routing Link Code", PlanningComponent."Routing Link Code");
+ PlanningRoutingLine.SetRange(Type, "Capacity Type"::"Work Center");
+ if not PlanningRoutingLine.IsEmpty() then begin
+ PlanningRoutingLine.SetLoadFields("No.");
+ PlanningRoutingLine.FindFirst();
+
+ if not GetSubcontractor(PlanningRoutingLine."No.", Vendor) then
+ Clear(Vendor);
+ if PlanningComponent."Component Supply Method" in ["Component Supply Method"::"Consignment at Vendor", "Component Supply Method"::"Vendor-Supplied"] then
+ VendorSubcontractingLocationCode := Vendor."Subc. Location Code";
+ OrigLocationCode := PlanningComponent."Orig. Location Code";
+ OrigBinCode := PlanningComponent."Orig. Bin Code";
+
+ ChangeLocationOnPlanningComponent(PlanningComponent, VendorSubcontractingLocationCode, OrigLocationCode, OrigBinCode);
+
+ PlanningComponent.Modify();
+ end;
+ end;
+
+ procedure UpdateComponentSupplyMethodForProdOrderComponent(var ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ PurchaseLine2: Record "Purchase Line";
+ Vendor: Record Vendor;
+ ProdOrderCompFound: Boolean;
+ OrigLocationCode, VendorSubcontractingLocationCode : Code[10];
+ OrigBinCode: Code[20];
+ PurchOrderNo: Code[20];
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProdOrderComponent."Routing Link Code" = '' then
+ exit;
+
+ ProdOrderLine.SetLoadFields("Routing Reference No.", "Routing No.");
+ ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.");
+
+ ProdOrderRoutingLine.SetRange(Status, ProdOrderComponent.Status);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderComponent."Prod. Order No.");
+ ProdOrderRoutingLine.SetRange("Routing No.", ProdOrderLine."Routing No.");
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLine."Routing Reference No.");
+ ProdOrderRoutingLine.SetRange("Routing Link Code", ProdOrderComponent."Routing Link Code");
+ ProdOrderRoutingLine.SetLoadFields("Prod. Order No.", Type, "No.");
+ if ProdOrderRoutingLine.FindFirst() then begin
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetLoadFields(SystemId);
+ if PurchaseLine.FindSet() then
+ repeat
+ if PurchOrderNo <> PurchaseLine."Document No." then begin
+ PurchOrderNo := PurchaseLine."Document No.";
+ PurchaseLine2.SetRange("Document Type", PurchaseLine."Document Type");
+ PurchaseLine2.SetRange("Document No.", PurchaseLine."Document No.");
+ PurchaseLine2.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine2.SetRange("No.", ProdOrderComponent."Item No.");
+ ProdOrderCompFound := not PurchaseLine2.IsEmpty();
+ end;
+ until (PurchaseLine.Next() = 0) or ProdOrderCompFound;
+ if ProdOrderCompFound then
+ Error(PurchOrderExistErr, ProdOrderComponent."Item No.", PurchOrderNo, ProdOrderComponent.FieldCaption(ProdOrderComponent."Component Supply Method"));
+
+ if ProdOrderRoutingLine.Type = "Capacity Type"::"Work Center" then begin
+ if not GetSubcontractor(ProdOrderRoutingLine."No.", Vendor) then
+ Clear(Vendor);
+
+ VendorSubcontractingLocationCode := Vendor."Subc. Location Code";
+ if not (ProdOrderComponent."Component Supply Method" in ["Component Supply Method"::"Consignment at Vendor", "Component Supply Method"::"Vendor-Supplied"]) then
+ Clear(VendorSubcontractingLocationCode);
+ OrigLocationCode := ProdOrderComponent."Subc. Original Location Code";
+ OrigBinCode := ProdOrderComponent."Subc. Orig. Bin Code";
+
+ ChangeLocationOnProdOrderComponent(ProdOrderComponent, VendorSubcontractingLocationCode, OrigLocationCode, OrigBinCode);
+
+ ProdOrderComponent.Modify();
+ end;
+ end;
+ end;
+
+ procedure UpdLinkedComponents(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ShowMsg: Boolean)
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ Vendor: Record Vendor;
+ ConfirmManagement: Codeunit "Confirm Management";
+ Subcontracting: Boolean;
+ OrigLocationCode, VendorSubcontractingLocationCode : Code[10];
+ OrigBinCode: Code[20];
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderComponent.SetRange(Status, ProdOrderRoutingLine.Status);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderComponent.SetRange("Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ if ProdOrderComponent.FindSet() then begin
+ if ProdOrderRoutingLine.Type = "Capacity Type"::"Work Center" then
+ Subcontracting := GetSubcontractor(ProdOrderRoutingLine."No.", Vendor);
+
+ if Subcontracting then begin
+ VendorSubcontractingLocationCode := Vendor."Subc. Location Code";
+ if ShowMsg then
+ if not ConfirmManagement.GetResponseOrDefault(StrSubstNo(RoutingLinkUpdConfQst, ProdOrderRoutingLine."Routing Link Code"), true) then
+ Error(UpdateIsCancelledErr);
+ repeat
+ if not (ProdOrderComponent."Component Supply Method" in ["Component Supply Method"::"Consignment at Vendor", "Component Supply Method"::"Vendor-Supplied"]) then
+ Clear(VendorSubcontractingLocationCode);
+ OrigLocationCode := ProdOrderComponent."Subc. Original Location Code";
+ OrigBinCode := ProdOrderComponent."Subc. Orig. Bin Code";
+
+ ChangeLocationOnProdOrderComponent(ProdOrderComponent, VendorSubcontractingLocationCode, OrigLocationCode, OrigBinCode);
+
+ ProdOrderComponent.Modify();
+ until ProdOrderComponent.Next() = 0;
+
+ if ShowMsg then
+ Message(SuccessfullyUpdatedMsg);
+ end;
+ end;
+ end;
+
+ ///
+ /// Gets the location code for production order components based on the setup field "Subc. Default Comp. Location".
+ /// The location code is retrieved from the purchase line, company information, or manufacturing setup.
+ ///
+ /// The components location code.
+ procedure GetComponentsLocationCode(PurchaseLine: Record "Purchase Line"): Code[10]
+ var
+ CompanyInformation: Record "Company Information";
+ ComponentsLocationCode: Code[10];
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+
+#endif
+ GetManufacturingSetup();
+ ManufacturingSetup.TestField("Subc. Default Comp. Location");
+
+ case ManufacturingSetup."Subc. Default Comp. Location" of
+ "Components at Location"::Purchase:
+ begin
+ PurchaseLine.TestField("Location Code");
+ ComponentsLocationCode := PurchaseLine."Location Code";
+ end;
+ "Components at Location"::Company:
+ begin
+ CompanyInformation.SetLoadFields("Location Code");
+ CompanyInformation.Get();
+ CompanyInformation.TestField("Location Code");
+ ComponentsLocationCode := CompanyInformation."Location Code";
+ end;
+ "Components at Location"::Manufacturing:
+ begin
+ ManufacturingSetup.SetLoadFields("Components at Location");
+ ManufacturingSetup.Get();
+ ManufacturingSetup.TestField("Components at Location");
+ ComponentsLocationCode := ManufacturingSetup."Components at Location";
+ end;
+ end;
+
+ exit(ComponentsLocationCode);
+ end;
+
+ internal procedure IsSubcontractingPurchaseDocument(PurchaseHeader: Record "Purchase Header"): Boolean
+ var
+ PurchaseLine: Record "Purchase Line";
+ begin
+ PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine.SetFilter("Prod. Order No.", '<>%1', '');
+ PurchaseLine.SetFilter("Prod. Order Line No.", '<>%1', 0);
+ exit(not PurchaseLine.IsEmpty());
+ end;
+
+ internal procedure IsSubcontractingPurchaseLine(PurchaseLine: Record "Purchase Line"): Boolean
+ begin
+ exit((PurchaseLine."Prod. Order No." <> '') and (PurchaseLine."Prod. Order Line No." <> 0));
+ end;
+
+ local procedure GetManufacturingSetup()
+ begin
+ if HasManufacturingSetup then
+ exit;
+ HasManufacturingSetup := ManufacturingSetup.Get();
+ end;
+
+ local procedure IsSubcontracting(WorkCenterNo: Code[20]): Boolean
+ var
+ WorkCenter: Record "Work Center";
+ begin
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ if WorkCenter.Get(WorkCenterNo) then
+ exit(WorkCenter."Subcontractor No." <> '')
+ end;
+
+ [InternalEvent(false, false)]
+ local procedure OnBeforeGetSubcontractor(WorkCenterNo: Code[20]; var Vendor: Record Vendor; var HasSubcontractor: Boolean; var IsHandled: Boolean)
+ begin
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Install/SubcBusinessSetupExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Install/SubcBusinessSetupExt.Codeunit.al
new file mode 100644
index 0000000000..a0bb8e73f4
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Install/SubcBusinessSetupExt.Codeunit.al
@@ -0,0 +1,33 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Company;
+using Microsoft.Manufacturing.Setup;
+using System.Environment.Configuration;
+
+codeunit 99001502 "Subc. Business Setup Ext."
+{
+ var
+ SubcontractingDescriptionLbl: Label 'Make manual Subcontracting Setup';
+ SubcontractingKeyWordsLbl: Label 'Subcontracting, Management';
+ SubcontractingLbl: Label 'Subcontracting App';
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Guided Experience", OnRegisterManualSetup, '', false, false)]
+ local procedure OnRegisterManualSetup(sender: Codeunit "Guided Experience")
+ var
+ ManualSetupCategory: Enum "Manual Setup Category";
+ begin
+ sender.InsertManualSetup(SubcontractingLbl, SubcontractingLbl, SubcontractingDescriptionLbl, 0, ObjectType::Page, Page::"Manufacturing Setup", ManualSetupCategory::Uncategorized, SubcontractingKeyWordsLbl);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Company-Initialize", OnCompanyInitialize, '', false, false)]
+ local procedure OnCompanyInitialize()
+ var
+ SubcontractingCompInit: Codeunit "Subcontracting Comp. Init.";
+ begin
+ SubcontractingCompInit.CreateBasicSubcontractingMgtSetup();
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Install/SubcUpgradeTagDefExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Install/SubcUpgradeTagDefExt.Codeunit.al
new file mode 100644
index 0000000000..1fcfc5431b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Install/SubcUpgradeTagDefExt.Codeunit.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 System.Upgrade;
+
+codeunit 99001570 "Subc. Upgrade Tag Def. Ext."
+{
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Upgrade Tag", 'OnGetPerCompanyUpgradeTags', '', false, false)]
+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])
+ begin
+ PerCompanyUpgradeTags.Add(GetSubcontractingUpgradeTag());
+ end;
+
+ internal procedure GetSubcontractingUpgradeTag(): Code[250]
+ begin
+ exit('MS-406123-Subcontracting-20260601');
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Install/SubcontractingCompInit.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Install/SubcontractingCompInit.Codeunit.al
new file mode 100644
index 0000000000..c95d697575
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Install/SubcontractingCompInit.Codeunit.al
@@ -0,0 +1,88 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Manufacturing.Setup;
+
+codeunit 99001503 "Subcontracting Comp. Init."
+{
+ var
+ ReqWkshTempDescLbl: Label 'Subcontracting', MaxLength = 80;
+ ReqWkshTempNameLbl: Label 'SUBCONTR', MaxLength = 10;
+ ReqWkshDescLbl: Label 'Subcontracting', MaxLength = 100;
+ ReqWkshNameLbl: Label 'SUBCONTR', MaxLength = 10;
+ DefaultCompTransferLeadTimeLbl: Label '<1D>', Locked = true;
+
+ procedure CreateBasicSubcontractingMgtSetup()
+ begin
+ InitializeManufacturingSetupDefaults();
+ end;
+
+ local procedure InitializeManufacturingSetupDefaults()
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ if not ManufacturingSetup.Get() then begin
+ ManufacturingSetup.Init();
+ ManufacturingSetup.Insert(true);
+ end;
+
+ if not CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup(ManufacturingSetup) then
+ exit;
+
+ ManufacturingSetup."Create Prod. Order Info Line" := true;
+ Evaluate(ManufacturingSetup."Subc. Comp. Transfer Lead Time", GetDefaultCompTransferLeadTime());
+ ManufacturingSetup.Modify(true);
+ end;
+
+ procedure CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup(var ManufacturingSetup: Record "Manufacturing Setup"): Boolean
+ var
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ begin
+ if not CreateReqWkshTemplate(ReqWkshTemplate, false) then
+ exit(false);
+
+ CreateRequisitionWkshName(RequisitionWkshName, ReqWkshTemplate.Name);
+ ManufacturingSetup."Subcontracting Template Name" := ReqWkshTemplate.Name;
+ ManufacturingSetup."Subcontracting Batch Name" := RequisitionWkshName.Name;
+ exit(true);
+ end;
+
+ procedure CreateReqWkshTemplate(var ReqWkshTemplate: Record "Req. Wksh. Template"; Recurring: Boolean): Boolean
+ begin
+ ReqWkshTemplate.SetRange(Type, ReqWkshTemplate.Type::Subcontracting);
+ if ReqWkshTemplate.FindFirst() then
+ exit(false);
+
+ ReqWkshTemplate.Init();
+ ReqWkshTemplate.Validate(Name, ReqWkshTempNameLbl);
+ ReqWkshTemplate.Validate(Description, ReqWkshTempDescLbl);
+ ReqWkshTemplate.Recurring := Recurring;
+ ReqWkshTemplate.Validate(Type, ReqWkshTemplate.Type::Subcontracting);
+ ReqWkshTemplate.Validate("Page ID", Page::"Subc. Subcontracting Worksheet");
+ ReqWkshTemplate.Insert(true);
+ exit(true);
+ end;
+
+ procedure CreateRequisitionWkshName(var RequisitionWkshName: Record "Requisition Wksh. Name"; WorksheetTemplateName: Text)
+ begin
+ RequisitionWkshName.SetRange("Worksheet Template Name", WorksheetTemplateName);
+ if RequisitionWkshName.FindFirst() then
+ exit;
+
+ RequisitionWkshName.Init();
+ RequisitionWkshName.Validate("Worksheet Template Name", CopyStr(WorksheetTemplateName, 1, MaxStrLen(RequisitionWkshName."Worksheet Template Name")));
+ RequisitionWkshName.Validate(Name, ReqWkshNameLbl);
+ RequisitionWkshName.Description := ReqWkshDescLbl;
+ RequisitionWkshName.Insert(true);
+ end;
+
+ local procedure GetDefaultCompTransferLeadTime(): Text
+ begin
+ exit(DefaultCompTransferLeadTimeLbl);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Install/SubcontractingInstall.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Install/SubcontractingInstall.Codeunit.al
new file mode 100644
index 0000000000..8af3d51e41
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Install/SubcontractingInstall.Codeunit.al
@@ -0,0 +1,73 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Upgrade;
+
+codeunit 99001501 "Subcontracting Install"
+{
+ Subtype = Install;
+
+ trigger OnInstallAppPerCompany()
+ var
+ CurrentAppInfo: ModuleInfo;
+ begin
+ NavApp.GetCurrentModuleInfo(CurrentAppInfo);
+
+ if CurrentAppInfo.DataVersion() = Version.Create(0, 0, 0, 0) then
+ HandleFreshInstallPerCompany()
+ else
+ HandleReinstallPerCompany();
+ end;
+
+ trigger OnInstallAppPerDatabase()
+ var
+ CurrentAppInfo: ModuleInfo;
+ begin
+ NavApp.GetCurrentModuleInfo(CurrentAppInfo);
+ if CurrentAppInfo.DataVersion() = Version.Create(0, 0, 0, 0) then
+ HandleFreshInstallPerDatabase()
+ else
+ HandleReinstallPerDatabase();
+ end;
+
+ local procedure HandleFreshInstallPerCompany()
+ var
+ SubcontractingCompInit: Codeunit "Subcontracting Comp. Init.";
+ begin
+ SubcontractingCompInit.CreateBasicSubcontractingMgtSetup();
+ SetSubcontractingFeatureOnInstall();
+ end;
+
+ local procedure HandleReinstallPerCompany()
+ var
+ SubcontractingCompInit: Codeunit "Subcontracting Comp. Init.";
+ begin
+ SubcontractingCompInit.CreateBasicSubcontractingMgtSetup();
+ SetSubcontractingFeatureOnInstall();
+ end;
+
+ local procedure HandleFreshInstallPerDatabase()
+ begin
+ end;
+
+ local procedure HandleReinstallPerDatabase()
+ begin
+ end;
+
+ local procedure SetSubcontractingFeatureOnInstall()
+ var
+ UpgradeTag: Codeunit "Upgrade Tag";
+ SubcApplicationAreaMgmt: Codeunit "Subc. Application Area Mgmt.";
+ SubcUpgradeTagDefExt: Codeunit "Subc. Upgrade Tag Def. Ext.";
+ begin
+ if UpgradeTag.HasUpgradeTag(SubcUpgradeTagDefExt.GetSubcontractingUpgradeTag()) then
+ exit;
+
+ SubcApplicationAreaMgmt.RefreshExperienceTierCurrentCompany();
+
+ UpgradeTag.SetUpgradeTag(SubcUpgradeTagDefExt.GetSubcontractingUpgradeTag());
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/ComponentSupplyMethod.Enum.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/ComponentSupplyMethod.Enum.al
new file mode 100644
index 0000000000..fed94caf9e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/ComponentSupplyMethod.Enum.al
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------------------------------
+// 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 99001500 "Component Supply Method"
+{
+ Extensible = true;
+ // No supply method is selected.
+ value(0; Empty)
+ {
+ Caption = ' ', Locked = true;
+ }
+ // The subcontractor provides the required component materials.
+ value(1; "Vendor-Supplied")
+ {
+ Caption = 'Vendor-Supplied';
+ }
+ // Your company owns the components and stores them at the subcontractor location (consignment stock).
+ value(2; "Consignment at Vendor")
+ {
+ Caption = 'Consignment at Vendor';
+ }
+ // Components are sent to the subcontractor through a transfer order.
+ value(3; "Transfer to Vendor")
+ {
+ Caption = 'Transfer to Vendor';
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/ComponentsatLocation.Enum.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/ComponentsatLocation.Enum.al
new file mode 100644
index 0000000000..48e008184f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/ComponentsatLocation.Enum.al
@@ -0,0 +1,26 @@
+// ------------------------------------------------------------------------------------------------
+// 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 99001503 "Components at Location"
+{
+ Extensible = true;
+ value(0; Empty)
+ {
+ Caption = ' ', Locked = true;
+ }
+ value(1; Purchase)
+ {
+ Caption = 'Purchase Line';
+ }
+ value(2; Company)
+ {
+ Caption = 'Company Info';
+ }
+ value(3; Manufacturing)
+ {
+ Caption = 'Manufacturing Setup';
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcBOMTreeExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcBOMTreeExt.Codeunit.al
new file mode 100644
index 0000000000..071b7f2f3e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcBOMTreeExt.Codeunit.al
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.BOM.Tree;
+using Microsoft.Inventory.Item;
+using Microsoft.Manufacturing.Routing;
+
+codeunit 99001521 "Subc. Calc BOM Tree Ext."
+{
+#if not CLEAN27
+#pragma warning disable AL0432
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Calculate BOM Tree", OnBeforeCalcRoutingLineCosts, '', false, false)]
+#pragma warning restore AL0432
+#else
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Calculate BOM Tree", OnBeforeCalcRoutingLineCosts, '', false, false)]
+#endif
+ local procedure OnBeforeCalcRoutingLineCosts(var RoutingLine: Record "Routing Line"; var LotSize: Decimal; var ScrapPct: Decimal; ParentItem: Record Item)
+ var
+ SubcSessionState: Codeunit "Subc. Session State";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcSessionState.SetRecordID('OnBeforeCalcRoutingLineCosts', ParentItem.RecordId());
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcProdOrderExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcProdOrderExt.Codeunit.al
new file mode 100644
index 0000000000..8c61e6dee4
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcProdOrderExt.Codeunit.al
@@ -0,0 +1,68 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+
+codeunit 99001517 "Subc. Calc. Prod. Order Ext."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Calculate Prod. Order", OnAfterTransferRoutingLine, '', false, false)]
+ local procedure OnAfterTransferRoutingLine(var ProdOrderLine: Record "Prod. Order Line"; var RoutingLine: Record "Routing Line"; var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcontractingManagement.UpdateLinkedComponentsAfterRoutingTransfer(ProdOrderLine, RoutingLine, ProdOrderRoutingLine);
+
+ SubcPriceManagement.ApplySubcontractorPricingToProdOrderRouting(ProdOrderLine, RoutingLine, ProdOrderRoutingLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Calculate Prod. Order", OnAfterTransferBOMComponent, '', false, false)]
+ local procedure OnAfterTransferBOMComponent(var ProdOrderLine: Record "Prod. Order Line"; var ProductionBOMLine: Record "Production BOM Line"; var ProdOrderComponent: Record "Prod. Order Component"; LineQtyPerUOM: Decimal; ItemQtyPerUOM: Decimal)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferSubcontractingFieldsBOMComponent(ProductionBOMLine, ProdOrderComponent);
+ end;
+
+ local procedure TransferSubcontractingFieldsBOMComponent(var ProductionBOMLine: Record "Production BOM Line"; var ProdOrderComponent: Record "Prod. Order Component")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderComponent."Subc. Original Location Code" := ProdOrderComponent."Location Code";
+ ProdOrderComponent."Subc. Orig. Bin Code" := ProdOrderComponent."Bin Code";
+ ProdOrderComponent."Component Supply Method" := ProductionBOMLine."Component Supply Method";
+
+ OnAfterTransferSubcontractingFieldsBOMComponent(ProductionBOMLine, ProdOrderComponent);
+ end;
+
+ [InternalEvent(false, false)]
+ local procedure OnAfterTransferSubcontractingFieldsBOMComponent(var ProductionBOMLine: Record "Production BOM Line"; var ProdOrderComponent: Record "Prod. Order Component")
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcStandardCostExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcStandardCostExt.Codeunit.al
new file mode 100644
index 0000000000..7645b42d83
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcStandardCostExt.Codeunit.al
@@ -0,0 +1,61 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.StandardCost;
+
+codeunit 99001514 "Subc. Calc.StandardCost Ext."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Calculate Standard Cost", OnAfterCalcRtngLineCost, '', false, false)]
+ local procedure OnAfterCalcRtngLineCost(RoutingLine: Record "Routing Line"; MfgItemQtyBase: Decimal; var SLSub: Decimal)
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcPriceManagement.CalcStandardCostOnAfterCalcRtngLineCost(RoutingLine, MfgItemQtyBase, SLSub);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Calculate Standard Cost", OnCalcMfgItemOnBeforeCalcRtngCost, '', false, false)]
+ local procedure OnCalcMfgItemOnBeforeCalcRtngCost(var Item: Record Item; Level: Integer; var LotSize: Decimal; var MfgItemQtyBase: Decimal)
+ var
+ SubcSessionState: Codeunit "Subc. Session State";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcSessionState.SetRecordID('OnCalcMfgItemOnBeforeCalcRtngCost', Item.RecordId());
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Calculate Standard Cost", OnAfterSetProperties, '', false, false)]
+ local procedure OnAfterSetProperties(var NewCalculationDate: Date; var NewCalcMultiLevel: Boolean; var NewUseAssemblyList: Boolean; var NewLogErrors: Boolean; var NewStdCostWkshName: Text[50]; var NewShowDialog: Boolean)
+ var
+ SubcSessionState: Codeunit "Subc. Session State";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcSessionState.SetDate('OnAfterSetProperties', NewCalculationDate);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcSubcontractsExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcSubcontractsExt.Codeunit.al
new file mode 100644
index 0000000000..4dc2501a0b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCalcSubcontractsExt.Codeunit.al
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.WorkCenter;
+
+codeunit 99001529 "Subc. Calc Subcontracts Ext."
+{
+ [EventSubscriber(ObjectType::Report, Report::"Subc. Calculate Subcontracts", OnAfterTransferProdOrderRoutingLine, '', false, false)]
+ local procedure OnAfterTransferProdOrderRoutingLine(var RequisitionLine: Record "Requisition Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ WorkCenter: Record "Work Center";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if RequisitionLine."Description 2" = '' then begin
+ WorkCenter.SetLoadFields("Name 2");
+ if WorkCenter.Get(ProdOrderRoutingLine."Work Center No.") then
+ RequisitionLine."Description 2" := WorkCenter."Name 2";
+ end;
+
+ RequisitionLine.Validate("Subc. Standard Task Code", ProdOrderRoutingLine."Standard Task Code");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCapLEntries.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCapLEntries.PageExt.al
new file mode 100644
index 0000000000..e9f796b6da
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCapLEntries.PageExt.al
@@ -0,0 +1,90 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Capacity;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Utilities;
+
+pageextension 99001502 "Subc. CapLEntries" extends "Capacity Ledger Entries"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Subc. Purch. Order No."; Rec."Subc. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ Visible = false;
+ }
+ field("Subc. Purch. Order Line No."; Rec."Subc. Purch. Order Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ Visible = false;
+ }
+ field("Subc. Subcontractor No."; Rec."Subc. Subcontractor No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related subcontractor.';
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("Ent&ry")
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+
+ action(ShowDocument)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Show Document';
+ Image = Document;
+ ToolTip = 'View the document related to this capacity ledger entry. Shows the posted purchase receipt or invoice if available, otherwise shows the purchase order.';
+ trigger OnAction()
+ begin
+ ShowRelatedDocument(Rec);
+ end;
+ }
+ }
+ }
+ }
+ local procedure ShowRelatedDocument(CapacityLedgerEntry: Record "Capacity Ledger Entry")
+ var
+ PurchRcptHeader: Record "Purch. Rcpt. Header";
+ PurchInvHeader: Record "Purch. Inv. Header";
+ PurchaseHeader: Record "Purchase Header";
+ PageManagement: Codeunit "Page Management";
+ begin
+ if CapacityLedgerEntry."Document No." <> '' then begin
+ if PurchRcptHeader.Get(CapacityLedgerEntry."Document No.") then begin
+ PageManagement.PageRun(PurchRcptHeader);
+ exit;
+ end;
+
+ if PurchInvHeader.Get(CapacityLedgerEntry."Document No.") then begin
+ PageManagement.PageRun(PurchInvHeader);
+ exit;
+ end;
+ end;
+
+ if CapacityLedgerEntry."Subc. Purch. Order No." <> '' then
+ if PurchaseHeader.Get(PurchaseHeader."Document Type"::Order, CapacityLedgerEntry."Subc. Purch. Order No.") then begin
+ PageManagement.PageRun(PurchaseHeader);
+ exit;
+ end;
+ // No document found
+ Message(NoDocumentFoundMsg);
+ end;
+
+ var
+ NoDocumentFoundMsg: Label 'No related document could be found for this entry.';
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCapLedgerEntryExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCapLedgerEntryExt.TableExt.al
new file mode 100644
index 0000000000..3aeba5d4f1
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCapLedgerEntryExt.TableExt.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Capacity;
+using Microsoft.Purchases.Document;
+
+tableextension 99001504 "Subc. Cap Ledger Entry Ext." extends "Capacity Ledger Entry"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001500; "Subc. Subcontractor No."; Code[20])
+ {
+ Caption = 'Subcontractor No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001512; "Subc. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subc. Purch. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Purchase Header"."No." where("Document Type" = const(Order));
+ }
+ field(99001513; "Subc. Purch. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Purchase Line"."Line No." where("Document Type" = const(Order),
+ "Document No." = field("Subc. Purch. Order No."));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCarryOutActionExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCarryOutActionExt.Codeunit.al
new file mode 100644
index 0000000000..e7813b6919
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCarryOutActionExt.Codeunit.al
@@ -0,0 +1,38 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Planning;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Document;
+
+codeunit 99001523 "Subc. Carry Out Action Ext."
+{
+#if not CLEAN27
+#pragma warning disable AL0432
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Carry Out Action", OnAfterTransferPlanningComp, '', false, false)]
+#pragma warning restore AL0432
+#else
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Carry Out Action", OnAfterTransferPlanningComp, '', false, false)]
+#endif
+ local procedure OnAfterTransferPlanningComp(var PlanningComponent: Record "Planning Component"; var ProdOrderComponent: Record "Prod. Order Component")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderComponent."Component Supply Method" := PlanningComponent."Component Supply Method";
+ ProdOrderComponent."Subc. Original Location Code" := PlanningComponent."Orig. Location Code";
+ ProdOrderComponent."Subc. Orig. Bin Code" := PlanningComponent."Orig. Bin Code";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCompFactboxMgmt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCompFactboxMgmt.Codeunit.al
new file mode 100644
index 0000000000..df2dfe4123
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcCompFactboxMgmt.Codeunit.al
@@ -0,0 +1,227 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+codeunit 99001562 "Subc. Comp. Factbox Mgmt."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ ///
+ /// Returns the total consumption quantity posted for the given production order component via its linked routing operation.
+ ///
+ /// The production order component to sum consumption entries for.
+ /// The absolute sum of consumption item ledger entry quantities for the component.
+ procedure GetConsumptionQtyFromProdOrderComponent(ProdOrderComponent: Record "Prod. Order Component"): Decimal
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ ItemLedgerEntry.SetCurrentKey(ItemLedgerEntry."Order Type", ItemLedgerEntry."Order No.", ItemLedgerEntry."Order Line No.", ItemLedgerEntry."Entry Type", ItemLedgerEntry."Prod. Order Comp. Line No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Order Type", ItemLedgerEntry."Order Type"::Production);
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Order No.", ProdOrderComponent."Prod. Order No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Order Line No.", ProdOrderComponent."Prod. Order Line No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Entry Type", ItemLedgerEntry."Entry Type"::Consumption);
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Prod. Order Comp. Line No.", ProdOrderComponent."Line No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ ItemLedgerEntry.CalcSums(ItemLedgerEntry.Quantity);
+
+ exit(Abs(ItemLedgerEntry.Quantity));
+ end;
+
+ ///
+ /// Opens the Item Ledger Entries page filtered to consumption entries for the given production order component.
+ ///
+ /// The production order component to show consumption entries for.
+ procedure ShowConsumptionQtyFromProdOrderComponent(ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ ItemLedgerEntry.SetCurrentKey(ItemLedgerEntry."Order Type", ItemLedgerEntry."Order No.", ItemLedgerEntry."Order Line No.", ItemLedgerEntry."Entry Type", ItemLedgerEntry."Prod. Order Comp. Line No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Order Type", ItemLedgerEntry."Order Type"::Production);
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Order No.", ProdOrderComponent."Prod. Order No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Order Line No.", ProdOrderComponent."Prod. Order Line No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Entry Type", ItemLedgerEntry."Entry Type"::Consumption);
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Prod. Order Comp. Line No.", ProdOrderComponent."Line No.");
+ ItemLedgerEntry.SetRange(ItemLedgerEntry."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ Page.Run(Page::"Item Ledger Entries", ItemLedgerEntry);
+ end;
+
+ ///
+ /// Returns the total outstanding base quantity on subcontracting purchase lines for the given production order component.
+ ///
+ /// The production order component to calculate outstanding quantity for.
+ /// The sum of Outstanding Qty. (Base) on matching purchase lines, or 0 if not a purchase subcontracting component.
+ procedure GetPurchOrderOutstandingQtyBaseFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"): Decimal
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if ProdOrderComponent."Routing Link Code" = '' then
+ exit(0);
+ if ProdOrderComponent."Component Supply Method" <> ProdOrderComponent."Component Supply Method"::"Vendor-Supplied" then
+ exit(0);
+
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ PurchaseLine.SetRange(PurchaseLine."Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Work Center No.", ProdOrderRoutingLine."Work Center No.");
+ PurchaseLine.SetRange(PurchaseLine."No.", ProdOrderComponent."Item No.");
+ PurchaseLine.CalcSums(PurchaseLine."Outstanding Qty. (Base)");
+ exit(PurchaseLine."Outstanding Qty. (Base)");
+ end;
+
+ ///
+ /// Opens the Purchase Lines page filtered to outstanding subcontracting purchase lines for the given production order component.
+ ///
+ /// The production order component to filter purchase lines by.
+ procedure ShowPurchOrderOutstandingQtyBaseFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProdOrderComponent."Routing Link Code" = '' then
+ exit;
+ if ProdOrderComponent."Component Supply Method" <> ProdOrderComponent."Component Supply Method"::"Vendor-Supplied" then
+ exit;
+
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ PurchaseLine.SetRange(PurchaseLine."Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Work Center No.", ProdOrderRoutingLine."Work Center No.");
+ PurchaseLine.SetRange(PurchaseLine."No.", ProdOrderComponent."Item No.");
+ Page.Run(Page::"Purchase Lines", PurchaseLine);
+ end;
+
+ ///
+ /// Returns the total received base quantity on subcontracting purchase lines for the given production order component.
+ ///
+ /// The production order component to calculate received quantity for.
+ /// The sum of Qty. Received (Base) on matching purchase lines, or 0 if not a purchase subcontracting component.
+ procedure GetPurchOrderQtyReceivedBaseFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"): Decimal
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if ProdOrderComponent."Routing Link Code" = '' then
+ exit(0);
+ if ProdOrderComponent."Component Supply Method" <> ProdOrderComponent."Component Supply Method"::"Vendor-Supplied" then
+ exit(0);
+
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ PurchaseLine.SetRange(PurchaseLine."Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Work Center No.", ProdOrderRoutingLine."Work Center No.");
+ PurchaseLine.SetRange(PurchaseLine."No.", ProdOrderComponent."Item No.");
+ PurchaseLine.CalcSums(PurchaseLine."Qty. Received (Base)");
+ exit(PurchaseLine."Qty. Received (Base)");
+ end;
+
+ ///
+ /// Opens the Purchase Lines page filtered to subcontracting purchase lines for the given production order component to show received quantities.
+ ///
+ /// The production order component to filter purchase lines by.
+ procedure ShowPurchOrderQtyReceivedBaseFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProdOrderComponent."Routing Link Code" = '' then
+ exit;
+ if ProdOrderComponent."Component Supply Method" <> ProdOrderComponent."Component Supply Method"::"Vendor-Supplied" then
+ exit;
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ PurchaseLine.SetRange(PurchaseLine."Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.SetRange(PurchaseLine."Subc. Work Center No.", ProdOrderRoutingLine."Work Center No.");
+ PurchaseLine.SetRange(PurchaseLine."No.", ProdOrderComponent."Item No.");
+ Page.Run(Page::"Purchase Lines", PurchaseLine);
+ end;
+
+ local procedure GetProdOrderRtngLineFromProdOrderComp(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ if not ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.") then
+ exit;
+
+ 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");
+ if ProdOrderRoutingLine.IsEmpty() then
+ exit;
+
+ ProdOrderRoutingLine.FindFirst();
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcDispatchingList.Report.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcDispatchingList.Report.al
new file mode 100644
index 0000000000..98e422586b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcDispatchingList.Report.al
@@ -0,0 +1,2024 @@
+// ------------------------------------------------------------------------------------------------
+// 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.CRM.Contact;
+using Microsoft.CRM.Interaction;
+using Microsoft.CRM.Segment;
+using Microsoft.CRM.Team;
+using Microsoft.Finance.Currency;
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Finance.ReceivablesPayables;
+using Microsoft.Finance.VAT.Calculation;
+using Microsoft.Finance.VAT.Clause;
+using Microsoft.Foundation.Address;
+using Microsoft.Foundation.Company;
+using Microsoft.Foundation.PaymentTerms;
+using Microsoft.Foundation.Shipping;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Posting;
+using Microsoft.Purchases.Setup;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Sales.Customer;
+using Microsoft.Sales.Setup;
+using Microsoft.Utilities;
+using System.Email;
+using System.Globalization;
+using System.Utilities;
+
+report 99001504 "Subc. Dispatching List"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor - Dispatch List';
+ DefaultLayout = Word;
+ PreviewMode = PrintLayout;
+ UsageCategory = Administration;
+ WordLayout = '.\src\Manufacturing\SubcDispatchingList.docx';
+ WordMergeDataItem = "Purchase Header";
+
+ dataset
+ {
+ dataitem("Purchase Header"; "Purchase Header")
+ {
+ DataItemTableView = sorting("Document Type", "Buy-from Vendor No.", "No.") where("Subc. Order" = const(true));
+ RequestFilterFields = "No.", "Buy-from Vendor No.", "No. Printed";
+ RequestFilterHeading = 'Subcontractor - Dispatch List';
+ column(AllowInvoiceDisc_Lbl; AllowInvoiceDiscCaptionLbl)
+ {
+ }
+ column(Amount_Lbl; AmountCaptionLbl)
+ {
+ }
+ column(Buyer_Lbl; BuyerCaptionLbl)
+ {
+ }
+ column(BuyFrmVendNo_PurchHeader; "Buy-from Vendor No.")
+ {
+ }
+ column(BuyFrmVendNo_PurchHeader_Lbl; FieldCaption("Buy-from Vendor No."))
+ {
+ }
+ column(BuyFromAddr1; BuyFromAddr[1])
+ {
+ }
+ column(BuyFromAddr2; BuyFromAddr[2])
+ {
+ }
+ column(BuyFromAddr3; BuyFromAddr[3])
+ {
+ }
+ column(BuyFromAddr4; BuyFromAddr[4])
+ {
+ }
+ column(BuyFromAddr5; BuyFromAddr[5])
+ {
+ }
+ column(BuyFromAddr6; BuyFromAddr[6])
+ {
+ }
+ column(BuyFromAddr7; BuyFromAddr[7])
+ {
+ }
+ column(BuyFromAddr8; BuyFromAddr[8])
+ {
+ }
+ column(BuyFromContactEmail; BuyFromContact."E-Mail")
+ {
+ }
+ column(BuyFromContactEmailLbl; BuyFromContactEmailLbl)
+ {
+ }
+ column(BuyFromContactMobilePhoneNo; BuyFromContact."Mobile Phone No.")
+ {
+ }
+ column(BuyFromContactMobilePhoneNoLbl; BuyFromContactMobilePhoneNoLbl)
+ {
+ }
+ column(BuyFromContactPhoneNo; BuyFromContact."Phone No.")
+ {
+ }
+ column(BuyFromContactPhoneNoLbl; BuyFromContactPhoneNoLbl)
+ {
+ }
+ column(CompanyAddress1; CompanyAddr[1])
+ {
+ }
+ column(CompanyAddress2; CompanyAddr[2])
+ {
+ }
+ column(CompanyAddress3; CompanyAddr[3])
+ {
+ }
+ column(CompanyAddress4; CompanyAddr[4])
+ {
+ }
+ column(CompanyAddress5; CompanyAddr[5])
+ {
+ }
+ column(CompanyAddress6; CompanyAddr[6])
+ {
+ }
+ column(CompanyBankAccountNo; CompanyInformation."Bank Account No.")
+ {
+ }
+ column(CompanyBankAccountNo_Lbl; CompanyInfoBankAccNoCaptionLbl)
+ {
+ }
+ column(CompanyBankBranchNo; CompanyInformation."Bank Branch No.")
+ {
+ }
+ column(CompanyBankBranchNo_Lbl; CompanyInformation.FieldCaption("Bank Branch No."))
+ {
+ }
+ column(CompanyBankName; CompanyInformation."Bank Name")
+ {
+ }
+ column(CompanyBankName_Lbl; CompanyInfoBankNameCaptionLbl)
+ {
+ }
+ column(CompanyCustomGiro; CustomGiroTxt)
+ {
+ }
+ column(CompanyCustomGiro_Lbl; CustomGiroLbl)
+ {
+ }
+ column(CompanyEMail; CompanyInformation."E-Mail")
+ {
+ }
+ column(CompanyEmail_Lbl; EmailIDCaptionLbl)
+ {
+ }
+ column(CompanyGiroNo; CompanyInformation."Giro No.")
+ {
+ }
+ column(CompanyGiroNo_Lbl; CompanyInfoGiroNoCaptionLbl)
+ {
+ }
+ column(CompanyHomePage; CompanyInformation."Home Page")
+ {
+ }
+ column(CompanyHomePage_Lbl; HomePageCaptionLbl)
+ {
+ }
+ column(CompanyIBAN; CompanyInformation.IBAN)
+ {
+ }
+ column(CompanyIBAN_Lbl; CompanyInformation.FieldCaption(IBAN))
+ {
+ }
+ column(CompanyLegalOffice; LegalOfficeTxt)
+ {
+ }
+ column(CompanyLegalOffice_Lbl; LegalOfficeLbl)
+ {
+ }
+ column(CompanyLogoPosition; CompanyLogoPosition)
+ {
+ }
+ column(CompanyPhoneNo; CompanyInformation."Phone No.")
+ {
+ }
+ column(CompanyPhoneNo_Lbl; CompanyInfoPhoneNoCaptionLbl)
+ {
+ }
+ column(CompanyPicture; CompanyInformation.Picture)
+ {
+ }
+ column(CompanyRegistrationNumber; CompanyInformation.GetRegistrationNumber())
+ {
+ }
+ column(CompanyRegistrationNumber_Lbl; CompanyInformation.GetRegistrationNumberLbl())
+ {
+ }
+ column(CompanySWIFT; CompanyInformation."SWIFT Code")
+ {
+ }
+ column(CompanySWIFT_Lbl; CompanyInformation.FieldCaption("SWIFT Code"))
+ {
+ }
+ column(CompanyVATRegistrationNo; CompanyInformation.GetVATRegistrationNumber())
+ {
+ }
+ column(CompanyVATRegistrationNo_Lbl; CompanyInformation.GetVATRegistrationNumberLbl())
+ {
+ }
+ column(CompanyVATRegNo; CompanyInformation.GetVATRegistrationNumber())
+ {
+ }
+ column(CompanyVATRegNo_Lbl; CompanyInformation.GetVATRegistrationNumberLbl())
+ {
+ }
+ column(ConfirmToCaption_Lbl; ConfirmToCaptionLbl)
+ {
+ }
+ column(ContinuedCaptionLbl; ContinuedCaptionLbl)
+ { }
+ column(DimText; DimText)
+ {
+ }
+ column(DocType_PurchHeader; "Document Type")
+ {
+ }
+ column(DocumentDate; Format("Document Date", 0, 4))
+ {
+ }
+ column(DocumentDate_Lbl; DocumentDateCaptionLbl)
+ {
+ }
+ column(DocumentTitle_Lbl; DocumentTitleLbl)
+ {
+ }
+ column(DueDate; Format("Due Date", 0, 4))
+ {
+ }
+ column(EmailID_Lbl; EmailIDCaptionLbl)
+ {
+ }
+ column(ExptRecptDt_PurchaseHeader; Format("Expected Receipt Date", 0, 4))
+ {
+ }
+ column(SubcAddrInfoLine; AddrInfoLine)
+ {
+ }
+ column(SubcAmountCaptionLbl; SubAmountCaptionLbl)
+ {
+ }
+ column(SubcBarcodeBase; TempCompanyInformation.Picture)
+ {
+ }
+ column(SubcCompanyAddress1; CompanyAddress1Footer)
+ {
+ }
+ column(SubcCompanyAddress2; CompanyAddress2Footer)
+ {
+ }
+ column(SubcCompanyAddress3; CompanyAddress3Footer)
+ {
+ }
+ column(SubcCompanyAddress4; CompanyAddress4Footer)
+ {
+ }
+ column(SubcCompanyBankName; CompanyInfoBankNameFooter)
+ {
+ }
+ column(SubcCompanyBankName_Lbl; CompanyInfoBankNameLblFooter)
+ {
+ }
+ column(SubcCompanyEMail; CompanyInfoEmailFooter)
+ {
+ }
+ column(SubcCompanyHomePage; CompanyInfoHomepageFooter)
+ {
+ }
+ column(SubcCompanyIBAN; CompanyInfoIBANFooter)
+ {
+ }
+ column(SubcCompanyIBAN_Lbl; CompanyInfoIBANLblFooter)
+ {
+ }
+ column(SubcCompanyInfo1Picture; CompanyInformation1.Picture)
+ {
+ }
+ column(SubcCompanyInfo2Picture; CompanyInformation2.Picture)
+ {
+ }
+ column(SubcCompanyInfoCourtLocation; CompanyInfoCourtLocationFooter)
+ {
+ }
+ column(SubcCompanyInfoExecutiveDirector1; CompanyInfoExecutiveDirector1Footer)
+ {
+ }
+ column(SubcCompanyInfoExecutiveDirector2; CompanyInfoExecutiveDirector2Footer)
+ {
+ }
+ column(SubcCompanyInfoExecutiveDirector3; CompanyInfoExecutiveDirector3Footer)
+ {
+ }
+ column(SubcCompanyInfoExecutiveDirector4; CompanyInfoExecutiveDirector4Footer)
+ {
+ }
+ column(SubcCompanyInfoFaxLbl; CompanyInfoFaxNoLblFooter)
+ {
+ }
+ column(SubcCompanyInfoFaxNo; CompanyInfoFaxNoFooter)
+ {
+ }
+ column(SubcCompanyInfoPhoneNoLbl; CompanyInfoPhoneNoLblFooter)
+ {
+ }
+ column(SubcCompanyInfoPicture; SubCompanyInformation.Picture)
+ {
+ }
+ column(SubcCompanyInfoRegisterCourtNo; CompanyInfoRegisterCourtNoFooter)
+ {
+ }
+ column(SubcCompanyPhoneNo; CompanyInfoPhoneNoFooter)
+ {
+ }
+ column(SubcCompanyPhoneNo_Lbl; CompanyInfoPhoneNoLblFooter)
+ {
+ }
+ column(SubcCompanySWIFT; CompanyInfoSWIFTFooter)
+ {
+ }
+ column(SubcCompanySWIFT_Lbl; CompanyInfoSWIFTLblFooter)
+ {
+ }
+ column(SubcCompanyVATRegistrationNo; CompanyVATRegNoFooter)
+ {
+ }
+ column(SubcCompanyVATRegistrationNo_Lbl; CompanyVATRegNoLblFooter)
+ {
+ }
+ column(SubcCompanyVATRegNo_Lbl; CompanyVATRegNoLblFooter)
+ {
+ }
+ column(SubcCourtLocationLbl; CourtLocationLblFooter)
+ {
+ }
+ column(SubcDocCaptionLbl; DocCaptionLbl)
+ {
+ }
+ column(SubcDocumentDate; Format("Document Date", 0, HeaderDateFormatExpression))
+ {
+ }
+ column(SubcDocumentDateLbl; DocumentDateLbl)
+ {
+ }
+ column(SubcDueDate; Format("Due Date", 0, HeaderDateFormatExpression))
+ {
+ }
+ column(SubcEMail_Lbl; CompanyInfoEMailAddressLblFooter)
+ {
+ }
+ column(SubcExecutiveDirectorsLbl; ExecutiveDirectorsLblFooter)
+ {
+ }
+ column(SubcExternalDocumentNoCaptionLbl; ExternalDocumentNoCaptionLbl)
+ {
+ }
+ column(SubcFooterMark; FooterMark)
+ {
+ }
+ column(SubcHeaderMark; HeaderMark)
+ {
+ }
+ column(SubcHeaderPhoneNo; HeaderPhoneNo)
+ {
+ }
+ column(SubcHeaderPhoneNoLbl; CompanyInfoPhoneNoCaptionLbl)
+ {
+ }
+ column(SubcHomePage_Lbl; CompanyInfoHomepageLblFooter)
+ {
+ }
+ column(SubcInvDiscAmount; InvDiscAmount)
+ {
+ }
+ column(SubcInvDiscAmtCaptionLbl; InvDiscAmtCaptionLbl)
+ {
+ }
+ column(SubcOfForPageLbl; OfForPageLbl)
+ {
+ }
+ column(SubcOrder_Date; Format("Order Date", 0, HeaderDateFormatExpression))
+ {
+ }
+ column(SubcOrderNoCaptionLbl; SubOrderNoCaptionLbl)
+ {
+ }
+ column(SubcPageLbl; SubcPageLbl)
+ {
+ }
+ column(SubcPaymentTermsDescriptionLbl; PaymentTermsDescriptionTxt)
+ {
+ }
+ column(SubcPrintAddressLine; PrintAddressLine)
+ {
+ }
+ column(SubcPrintBarCode; PrintBarCode)
+ {
+ }
+ column(SubcPrintFooterLine; PrintFooterLine)
+ {
+ }
+ column(SubcPromisedDeliveryDateLbl; PromisedDeliveryDateLbl)
+ {
+ }
+ column(SubcPurchDocNum; "No.")
+ {
+ }
+ column(SubcPurchDocNumLbl; PurchOrderNumCaptionLbl)
+ {
+ }
+ column(SubcRegisterCourtNoLbl; RegisterCourtLblFooter)
+ {
+ }
+ column(SubcSalesLineLineDiscountLbl; SalesLineLineDiscountLbl)
+ {
+ }
+ column(SubcSalesperson; SalespersonPurchaser.Name)
+ {
+ }
+ column(SubcSalesPersonEmail; SalespersonPurchaser."E-Mail")
+ {
+ }
+ column(SubcSalespersonLbl; SalespersonLbl)
+ {
+ }
+ column(SubcSalesPersonPurchaserEmailLbl; SalesPersonPurchaserEmailLbl)
+ {
+ }
+ column(SubcSellToCustomerNoCaptionLbl; SellToCustomerNoCaption)
+ {
+ }
+ column(SubcSellToCustomerNoShipToAddr; SellToCustomerNoShipToAddr)
+ {
+ }
+ column(SubcSeperator2Lbl; Seperator2Lbl)
+ {
+ }
+ column(SubcSeperator3Lbl; Seperator3Lbl)
+ {
+ }
+ column(SubcSeperatorLbl; SeperatorLbl)
+ {
+ }
+ column(SubcShipToAddressCaptionLbl; ShipToAddressCaption)
+ {
+ }
+ column(SubcShptMethodDesc; ShipmentMethod.Description)
+ {
+ }
+ column(SubcShptMethodDescLbl; ShptMethodDescTxt)
+ {
+ }
+ column(SubcTotalAmountInclVAT; Format(TotalAmountInclVAT, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcTotalText; SubTotalText)
+ {
+ }
+ column(SubcUnit_PriceCaptionLbl; Unit_PriceCaptionLbl)
+ {
+ }
+ column(SubcUnitOfMeasureLbl; UnitOfMeasureLbl)
+ {
+ }
+ column(SubcVATAmount; Format(SubVATAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcVATAmountSpecificationCaptionLbl; VATAmountSpecificationCaptionLbl)
+ {
+ }
+ column(SubcVATBaseAmount; Format(SubVATBaseAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcVATClauseCaptionLbl; VATClauseCaptionLbl)
+ {
+ }
+ column(SubcVATDiscountAmount; Format(SubVATDiscountAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcVATNo; "VAT Registration No.")
+ {
+ }
+ column(SubcVATNoLbl; VATNoLbl)
+ {
+ }
+ column(SubcVendorNo; "Buy-from Vendor No.")
+ {
+ }
+ column(SubcVendorNoLbl; VendorNoLbl)
+ {
+ }
+ column(SubcYourReferenceLbl; YourReferenceLbl)
+ {
+ }
+ column(HomePage_Lbl; HomePageCaptionLbl)
+ {
+ }
+ column(ItemDescription_Lbl; ItemDescriptionCaptionLbl)
+ {
+ }
+ column(ItemLineAmount_Lbl; ItemLineAmountCaptionLbl)
+ {
+ }
+ column(ItemNumber_Lbl; ItemNumberCaptionLbl)
+ {
+ }
+ column(ItemQuantity_Lbl; ItemQuantityCaptionLbl)
+ {
+ }
+ column(ItemUnit_Lbl; ItemUnitCaptionLbl)
+ {
+ }
+ column(ItemUnitPrice_Lbl; ItemUnitPriceCaptionLbl)
+ {
+ }
+ column(No_PurchHeader; "No.")
+ {
+ }
+ column(OrderDate_Lbl; OrderDateLbl)
+ {
+ }
+ column(OrderDate_PurchaseHeader; Format("Order Date", 0, 4))
+ {
+ }
+ column(OrderDatenLbl; OrderDatenLbl)
+ { }
+ column(OrderNo_Lbl; OrderNoCaptionLbl)
+ {
+ }
+ column(OutputNo; OutputNo)
+ {
+ }
+ column(OutstandingLbl; OutstandingLbl)
+ { }
+ column(Page_Lbl; PageCaptionLbl)
+ {
+ }
+ column(PageLbl; PageLbl)
+ { }
+ column(PaymentDetails_Lbl; PaymentDetailsCaptionLbl)
+ {
+ }
+ column(PaymentTermsDesc_Lbl; PaymentTermsDescCaptionLbl)
+ {
+ }
+ column(PayToContactEmail; PayToContact."E-Mail")
+ {
+ }
+ column(PayToContactEmailLbl; PayToContactEmailLbl)
+ {
+ }
+ column(PayToContactMobilePhoneNo; PayToContact."Mobile Phone No.")
+ {
+ }
+ column(PayToContactMobilePhoneNoLbl; PayToContactMobilePhoneNoLbl)
+ {
+ }
+ column(PayToContactPhoneNo; PayToContact."Phone No.")
+ {
+ }
+ column(PayToContactPhoneNoLbl; PayToContactPhoneNoLbl)
+ {
+ }
+ column(PayToVendNo_PurchHeader; "Pay-to Vendor No.")
+ {
+ }
+ column(PrepmtPaymentTermsDesc; PrepmtPaymentTerms.Description)
+ {
+ }
+ column(PrepymtTermsDesc_Lbl; PrepymtTermsDescCaptionLbl)
+ {
+ }
+ column(PricesIncludingVAT_Lbl; PricesIncludingVATCaptionLbl)
+ {
+ }
+ column(PricesInclVAT_PurchHeader; "Prices Including VAT")
+ {
+ }
+ column(PricesInclVAT_PurchHeader_Lbl; FieldCaption("Prices Including VAT"))
+ {
+ }
+ column(PricesInclVATtxt; PricesInclVATtxtLbl)
+ {
+ }
+ column(PurchaserText; PurchaserText)
+ {
+ }
+ column(PurchLineInvDiscAmt_Lbl; PurchLineInvDiscAmtCaptionLbl)
+ {
+ }
+ column(PurchOrderCaption_Lbl; PurchOrderCaptionLbl)
+ {
+ }
+ column(PurchOrderDateCaption_Lbl; PurchOrderDateCaptionLbl)
+ {
+ }
+ column(PurchOrderNumCaption_Lbl; PurchOrderNumCaptionLbl)
+ {
+ }
+ column(Receiveby_Lbl; ReceivebyCaptionLbl)
+ {
+ }
+ column(ReferenceText; ReferenceText)
+ {
+ }
+ column(SalesPurchPersonName; SalespersonPurchaser.Name)
+ {
+ }
+ column(SellToCustNo_PurchHeader; "Sell-to Customer No.")
+ {
+ }
+ column(SellToCustNo_PurchHeader_Lbl; FieldCaption("Sell-to Customer No."))
+ {
+ }
+ column(ShipmentMethodDesc; ShipmentMethod.Description)
+ {
+ }
+ column(ShipmentMethodDesc_Lbl; ShipmentMethodDescCaptionLbl)
+ {
+ }
+ column(ShipToAddr1; ShipToAddr[1])
+ {
+ }
+ column(ShipToAddr2; ShipToAddr[2])
+ {
+ }
+ column(ShipToAddr3; ShipToAddr[3])
+ {
+ }
+ column(ShipToAddr4; ShipToAddr[4])
+ {
+ }
+ column(ShipToAddr5; ShipToAddr[5])
+ {
+ }
+ column(ShipToAddr6; ShipToAddr[6])
+ {
+ }
+ column(ShipToAddr7; ShipToAddr[7])
+ {
+ }
+ column(ShipToAddr8; ShipToAddr[8])
+ {
+ }
+ column(ShiptoAddress_Lbl; ShiptoAddressCaptionLbl)
+ {
+ }
+ column(SubcontractorDispatchListLbl; SubcontractorDispatchListLbl)
+ { }
+ column(SubcontractorLbl; SubcontractorLbl)
+ { }
+ column(SubcOrdNoLbl; SubcOrdNoLbl)
+ { }
+ column(Subtotal_Lbl; SubtotalCaptionLbl)
+ {
+ }
+ column(TaxIdentTypeCaption_Lbl; TaxIdentTypeCaptionLbl)
+ {
+ }
+ column(ToCaption_Lbl; ToCaptionLbl)
+ {
+ }
+ column(Total_Lbl; TotalCaptionLbl)
+ {
+ }
+ column(VALVATBaseLCY_Lbl; VALVATBaseLCYCaptionLbl)
+ {
+ }
+ column(VATAmtLineInvDiscBaseAmt_Lbl; VATAmtLineInvDiscBaseAmtCaptionLbl)
+ {
+ }
+ column(VATAmtLineLineAmt_Lbl; VATAmtLineLineAmtCaptionLbl)
+ {
+ }
+ column(VATAmtLineVAT_Lbl; VATAmtLineVATCaptionLbl)
+ {
+ }
+ column(VATAmtLineVATAmt_Lbl; VATAmtLineVATAmtCaptionLbl)
+ {
+ }
+ column(VATAmtSpec_Lbl; VATAmtSpecCaptionLbl)
+ {
+ }
+ column(VATBaseDisc_PurchHeader; "VAT Base Discount %")
+ {
+ }
+ column(VATIdentifier_Lbl; VATIdentifierCaptionLbl)
+ {
+ }
+ column(VATNoText; VATNoText)
+ {
+ }
+ column(VATRegNo_PurchHeader; "VAT Registration No.")
+ {
+ }
+ column(VendAddr1; VendAddr[1])
+ {
+ }
+ column(VendAddr2; VendAddr[2])
+ {
+ }
+ column(VendAddr3; VendAddr[3])
+ {
+ }
+ column(VendAddr4; VendAddr[4])
+ {
+ }
+ column(VendAddr5; VendAddr[5])
+ {
+ }
+ column(VendAddr6; VendAddr[6])
+ {
+ }
+ column(VendAddr7; VendAddr[7])
+ {
+ }
+ column(VendAddr8; VendAddr[8])
+ {
+ }
+ column(VendNo_Lbl; VendNoCaptionLbl)
+ {
+ }
+ column(Vendor__Subcontracting_Location_Code_; Vendor."Subc. Location Code")
+ {
+ }
+ column(Vendor__Subcontracting_Location_Code_Caption; Vendor.FieldCaption("Subc. Location Code"))
+ {
+ }
+ column(VendorIDCaption_Lbl; VendorIDCaptionLbl)
+ {
+ }
+ column(VendorInvoiceNo; "Vendor Invoice No.")
+ {
+ }
+ column(VendorInvoiceNo_Lbl; VendorInvoiceNoLbl)
+ {
+ }
+ column(VendorOrderNo; "Vendor Order No.")
+ {
+ }
+ column(VendorOrderNo_Lbl; VendorOrderNoLbl)
+ {
+ }
+ column(YourRef_PurchHeader; "Your Reference")
+ {
+ }
+ dataitem("Purchase Line"; "Purchase Line")
+ {
+ DataItemLink = "Document Type" = field("Document Type"), "Document No." = field("No.");
+ DataItemTableView = sorting("Document Type", "Document No.", "Line No.") where("Prod. Order No." = filter(<> ''));
+ column(AllowInvDisc_PurchLine; "Allow Invoice Disc.")
+ {
+ }
+ column(AllowInvDisctxt; AllowInvDisctxt)
+ {
+ }
+ column(AmountIncludingVAT; "Amount Including VAT")
+ {
+ }
+ column(Desc_PurchLine; Description)
+ {
+ }
+ column(Desc_PurchLine_Lbl; FieldCaption(Description))
+ {
+ }
+ column(DirectUniCost_Lbl; DirectUniCostCaptionLbl)
+ {
+ }
+ column(DirUnitCost_PurchLine; FormattedDirectUnitCost)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 2;
+ }
+ column(ExpectedReceiptDate; Format("Expected Receipt Date", 0, 4))
+ {
+ }
+ column(ExpectedReceiptDateLbl; ExpectedReceiptDateLbl)
+ {
+ }
+ column(SubcDescriptionCaption; "Purchase Line".FieldCaption(Description))
+ {
+ }
+ column(SubcExpectedReceiptDate; Format("Expected Receipt Date", 0, LineDateFormatExpression))
+ {
+ }
+ column(SubcLineDescription2; "Purchase Line"."Description 2")
+ {
+ }
+ column(SubcLineDisc; LineDiscountPctText)
+ {
+ }
+ column(SubcLineMark; LineMark)
+ {
+ }
+ column(SubcNoCaption; "Purchase Line".FieldCaption("No."))
+ {
+ }
+ column(SubcPosNoText; PosNoText)
+ {
+ }
+ column(SubcPUoM; PricingUoMCode)
+ {
+ }
+ column(SubcPurchQty; "Purchase Line".Quantity)
+ {
+ }
+ column(SubcPUS; PriceUnit)
+ {
+ }
+ column(SubcQuantityCaption; "Purchase Line".FieldCaption(Quantity))
+ {
+ }
+ column(SubcSalesLineLineAmount; LineAmount)
+ {
+ }
+ column(SubcUnitCost; UnitCost)
+ {
+ }
+ column(InvDiscAmt_PurchLine; -"Inv. Discount Amount")
+ {
+ AutoFormatExpression = "Currency Code";
+ AutoFormatType = 1;
+ }
+ column(InvDiscCaption_Lbl; InvDiscCaptionLbl)
+ {
+ }
+ column(ItemNo_PurchLine; "No.")
+ {
+ }
+ column(ItemReferenceNo_PurchLine; "Item Reference No.")
+ {
+ }
+ column(JobNo_PurchLine; "Job No.")
+ {
+ }
+ column(JobNo_PurchLine_Lbl; JobNoLbl)
+ {
+ }
+ column(JobTaskNo_PurchLine; "Job Task No.")
+ {
+ }
+ column(JobTaskNo_PurchLine_Lbl; JobTaskNoLbl)
+ {
+ }
+ column(LineAmt_PurchLine; FormattedLineAmount)
+ {
+ }
+ column(LineDisc_PurchLine; "Line Discount %")
+ {
+ }
+ column(LineNo_PurchLine; "Line No.")
+ {
+ }
+ column(No_PurchLine; ItemNo)
+ {
+ }
+ column(No_PurchLine_Lbl; FieldCaption("No."))
+ {
+ }
+ column(PlannedReceiptDate; Format("Planned Receipt Date", 0, 4))
+ {
+ }
+ column(PlannedReceiptDateLbl; PlannedReceiptDateLbl)
+ {
+ }
+ column(PromisedReceiptDate; Format("Promised Receipt Date", 0, 4))
+ {
+ }
+ column(PromisedReceiptDateLbl; PromisedReceiptDateLbl)
+ {
+ }
+ column(Purchase_Line_Operation_No_; "Purchase Line"."Subc. Operation No.")
+ {
+ }
+ column(Purchase_Line_Outstanding_Qty; "Purchase Line"."Outstanding Quantity")
+ { }
+ column(Purchase_Line_Prod__Order_Line_No_; "Purchase Line"."Subc. Prod. Order Line No.")
+ {
+ }
+ column(Purchase_Line_Prod__Order_No_; "Purchase Line"."Subc. Prod. Order No.")
+ {
+ }
+ column(Purchase_Line_Routing_No_; "Purchase Line"."Subc. Routing No.")
+ {
+ }
+ column(Purchase_Line_Routing_Reference_No_; "Purchase Line"."Subc. Rtng Reference No.")
+ {
+ }
+ column(PurchLineLineDisc_Lbl; PurchLineLineDiscCaptionLbl)
+ {
+ }
+ column(Qty_PurchLine; FormattedQuanitity)
+ {
+ }
+ column(Qty_PurchLine_Lbl; FieldCaption(Quantity))
+ {
+ }
+ column(RequestedReceiptDate; Format("Requested Receipt Date", 0, 4))
+ {
+ }
+ column(RequestedReceiptDateLbl; RequestedReceiptDateLbl)
+ {
+ }
+ column(TotalInclVAT; "Line Amount" - "Inv. Discount Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalPriceCaption_Lbl; TotalPriceCaptionLbl)
+ {
+ }
+ column(Type_PurchLine; Format(Type, 0, 2))
+ {
+ }
+ column(UnitPrice_PurchLine; "Unit Price (LCY)")
+ {
+ }
+ column(UnitPrice_PurchLine_Lbl; UnitPriceLbl)
+ {
+ }
+ column(UOM_PurchLine; "Unit of Measure")
+ {
+ }
+ column(UOM_PurchLine_Lbl; ItemUnitOfMeasureCaptionLbl)
+ {
+ }
+ column(VATDiscountAmount_Lbl; VATDiscountAmountCaptionLbl)
+ {
+ }
+ column(VATIdentifier_PurchLine; "VAT Identifier")
+ {
+ }
+ column(VATIdentifier_PurchLine_Lbl; FieldCaption("VAT Identifier"))
+ {
+ }
+ column(VendorItemNo_PurchLine; "Vendor Item No.")
+ {
+ }
+ dataitem("Prod. Order Line"; "Prod. Order Line")
+ {
+ DataItemLink = "Prod. Order No." = field("Prod. Order No."), "Line No." = field("Prod. Order Line No.");
+ DataItemTableView = sorting(Status, "Prod. Order No.", "Line No.") where(Status = const(Released));
+ }
+ dataitem("Prod. Order Routing Line"; "Prod. Order Routing Line")
+ {
+ DataItemLink = "Prod. Order No." = field("Prod. Order No."), "Routing No." = field("Routing No."), "Routing Reference No." = field("Routing Reference No."), "Operation No." = field("Operation No.");
+ DataItemTableView = sorting(Status, "Prod. Order No.", "Routing Reference No.", "Routing No.", "Operation No.") where(Status = const(Released));
+ column(ComponentsToShipLbl; ComponentsToShipLbl)
+ { }
+ column(EndingDateLbl; EndingDateLbl)
+ { }
+ column(ItemLbl; ItemLbl)
+ { }
+ column(Prod__Order_Line___Item_No__; "Prod. Order Line"."Item No.")
+ {
+ }
+ column(Prod__Order_Line___Quantity__Base__; "Prod. Order Line"."Quantity (Base)")
+ {
+ }
+ column(Prod__Order_Line___Remaining_Quantity_; Format("Prod. Order Line"."Remaining Quantity"))
+ {
+ }
+ column(Prod__Order_Line___Unit_of_Measure_Code_; "Prod. Order Line"."Unit of Measure Code")
+ {
+ }
+ column(Prod__Order_Line__Quantity; Format("Prod. Order Line".Quantity))
+ {
+ }
+ column(Prod__Order_Routing_Line__Ending_Date_; Format("Ending Date", 0, 4))
+ {
+ }
+ column(Prod__Order_Routing_Line__Operation_No__; "Operation No.")
+ {
+ }
+ column(Prod__Order_Routing_Line__Operation_No__Caption; FieldCaption("Operation No."))
+ {
+ }
+ column(Prod__Order_Routing_Line__Prod__Order_No__; "Prod. Order No.")
+ {
+ }
+ column(Prod__Order_Routing_Line__Starting_Date_; Format("Starting Date", 0, 4))
+ {
+ }
+ column(Prod__Order_Routing_Line_Description; Description)
+ {
+ }
+ column(Prod__Order_Routing_Line_Description_Control1130538; Description)
+ {
+ }
+ column(Prod__Order_Routing_Line_Routing_Link_Code; "Routing Link Code")
+ {
+ }
+ column(Prod__Order_Routing_Line_Routing_No_; "Routing No.")
+ {
+ }
+ column(Prod__Order_Routing_Line_Routing_Reference_No_; "Routing Reference No.")
+ {
+ }
+ column(Prod__Order_Routing_Line_Status; Status)
+ {
+ }
+ column(ProdOrderLbl; ProdOrderLbl)
+ { }
+ column(QtyToShipLbl; QtyToShipLbl)
+ { }
+ column(QuantityBaseLbl; QuantityBaseLbl)
+ { }
+ column(QuantityLbl; QuantityLbl)
+ { }
+ column(RemainingQuantityLbl; RemainingQuantityLbl)
+ { }
+ column(StartingDateLbl; StartingDateLbl)
+ { }
+ column(UoMLbl; UoMLbl)
+ { }
+ dataitem("Prod. Order Component"; "Prod. Order Component")
+ {
+ DataItemLink = Status = field(Status), "Prod. Order No." = field("Prod. Order No."), "Prod. Order Line No." = field("Routing Reference No."), "Routing Link Code" = field("Routing Link Code");
+ DataItemTableView = sorting(Status, "Prod. Order No.", "Prod. Order Line No.", "Line No.");
+ column(Expected_Qty___Base______Qty__transf__to_Subcontractor______Qty__in_Transit__Base__; "Expected Qty. (Base)" - "Subc. Qty. transf. to Subcontr" - "Subc. Qty. in Transit (Base)")
+ {
+ DecimalPlaces = 0 : 5;
+ }
+ column(Prod__Order_Component__Expected_Qty___Base__; "Expected Qty. (Base)")
+ {
+ }
+ column(Prod__Order_Component__Expected_Qty___Base__Caption; FieldCaption("Expected Qty. (Base)"))
+ {
+ }
+ column(Prod__Order_Component__Item_No__; "Item No.")
+ {
+ }
+ column(Prod__Order_Component__Qty__in_Transit__Base__; "Subc. Qty. in Transit (Base)")
+ {
+ }
+ column(Prod__Order_Component__Qty__in_Transit__Base__Caption; FieldCaption("Subc. Qty. in Transit (Base)"))
+ {
+ }
+ column(Prod__Order_Component__Qty__transf__to_Subcontractor_; "Subc. Qty. transf. to Subcontr")
+ {
+ }
+ column(Prod__Order_Component__Qty__transf__to_Subcontractor_Caption; FieldCaption("Subc. Qty. transf. to Subcontr"))
+ {
+ }
+ column(Prod__Order_Component_Description; Description)
+ {
+ }
+ column(Prod__Order_Component_Line_No_; "Line No.")
+ {
+ }
+ column(Prod__Order_Component_Prod__Order_Line_No_; "Prod. Order Line No.")
+ {
+ }
+ column(Prod__Order_Component_Prod__Order_No_; "Prod. Order No.")
+ {
+ }
+ column(Prod__Order_Component_Routing_Link_Code; "Routing Link Code")
+ {
+ }
+ column(Prod__Order_Component_Status; Status)
+ {
+ }
+ trigger OnPreDataItem()
+ begin
+ SetRange("Subc. Purchase Order Filter", "Purchase Header"."No.");
+ end;
+ }
+ }
+ trigger OnAfterGetRecord()
+ begin
+ AllowInvDisctxt := Format("Allow Invoice Disc.");
+ TotalSubTotal += "Line Amount";
+ TotalInvoiceDiscountAmount -= "Inv. Discount Amount";
+ TotalAmount += Amount;
+
+ ItemNo := "No.";
+
+ if "Vendor Item No." <> '' then
+ ItemNo := "Vendor Item No.";
+
+ if "Item Reference No." <> '' then
+ ItemNo := "Item Reference No.";
+
+ FormatDocument.SetPurchaseLine("Purchase Line", FormattedQuanitity, FormattedDirectUnitCost, FormattedVATPct, FormattedLineAmount);
+
+ BlankZero("Purchase Line");
+ end;
+ }
+ dataitem(Totals; "Integer")
+ {
+ DataItemTableView = sorting(Number) where(Number = const(1));
+ column(SubcTotalAmountIncludingVAT; TotalAmountIncludingVATTxt)
+ {
+ }
+ column(SubcTotalInvoiceDiscountAmount; Format(VATDiscountAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcTotalNetAmount; Format(TotalAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcTotalSubTotal; Format(TotalSubTotal, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcTotalVATAmount; Format(VATAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(SubcTotalVATBaseLCY; Format(VATBaseAmount, 0, DecimalAmountFormatExpression))
+ {
+ }
+ column(TotalAmount; TotalAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalAmountInclVAT; TotalAmountInclVAT)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalExclVATText; TotalExclVATText)
+ {
+ }
+ column(TotalInclVATText; TotalInclVATText)
+ {
+ }
+ column(TotalInvoiceDiscountAmount; TotalInvoiceDiscountAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalSubTotal; TotalSubTotal)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalText; TotalText)
+ {
+ }
+ column(TotalVATAmount; VATAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalVATBaseAmount; VATBaseAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalVATDiscountAmount; -VATDiscountAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(VATAmountText; TempVATAmountLine.VATAmountText())
+ {
+ }
+ trigger OnAfterGetRecord()
+ var
+ TempPrepmtPurchaseLine: Record "Purchase Line" temporary;
+ begin
+ Clear(TempPurchaseLine);
+ Clear(PurchPost);
+ if not TempPurchaseLine.IsEmpty() then
+ TempPurchaseLine.DeleteAll();
+ if not TempVATAmountLine.IsEmpty() then
+ TempVATAmountLine.DeleteAll();
+ PurchPost.GetPurchLines("Purchase Header", TempPurchaseLine, 0);
+ TempPurchaseLine.CalcVATAmountLines(0, "Purchase Header", TempPurchaseLine, TempVATAmountLine);
+ TempPurchaseLine.UpdateVATOnLines(0, "Purchase Header", TempPurchaseLine, TempVATAmountLine);
+ VATAmount := TempVATAmountLine.GetTotalVATAmount();
+ VATBaseAmount := TempVATAmountLine.GetTotalVATBase();
+ VATDiscountAmount :=
+ TempVATAmountLine.GetTotalVATDiscount("Purchase Header"."Currency Code", "Purchase Header"."Prices Including VAT");
+ TotalAmountInclVAT := TempVATAmountLine.GetTotalAmountInclVAT();
+
+ if not TempPrepaymentInvLineBuffer.IsEmpty() then
+ TempPrepaymentInvLineBuffer.DeleteAll();
+ PurchasePostPrepayments.GetPurchLines("Purchase Header", 0, TempPurchaseLine);
+ if not TempPrepmtPurchaseLine.IsEmpty() then begin
+ PurchasePostPrepayments.GetPurchLinesToDeduct("Purchase Header", TempPurchaseLine);
+ if not TempPurchaseLine.IsEmpty() then
+ PurchasePostPrepayments.CalcVATAmountLines("Purchase Header", TempPurchaseLine, TempPrePmtVATAmountLineDeduct, 1);
+ end;
+ PurchasePostPrepayments.CalcVATAmountLines("Purchase Header", TempPrepmtPurchaseLine, TempPrepmtVATAmountLine, 0);
+ TempPrepmtVATAmountLine.DeductVATAmountLine(TempPrePmtVATAmountLineDeduct);
+ PurchasePostPrepayments.UpdateVATOnLines("Purchase Header", TempPrepmtPurchaseLine, TempPrepmtVATAmountLine, 0);
+ PurchasePostPrepayments.BuildInvLineBuffer("Purchase Header", TempPrepmtPurchaseLine, 0, TempPrepaymentInvLineBuffer);
+ PrepmtVATAmount := TempPrepmtVATAmountLine.GetTotalVATAmount();
+ PrepmtVATBaseAmount := TempPrepmtVATAmountLine.GetTotalVATBase();
+ PrepmtTotalAmountInclVAT := TempPrepmtVATAmountLine.GetTotalAmountInclVAT();
+
+ if TotalAmount <> TotalAmountInclVAT then begin
+ TotalAmountIncludingVATTxt := Format(TotalAmountInclVAT, 0, DecimalAmountFormatExpression);
+ SubFormatDocument.SetTotalLabels("Purchase Header"."Currency Code", TotalText, TotalInclVATText, TotalExclVATText);
+ end else begin
+ TotalAmountIncludingVATTxt := '';
+ TotalInclVATText := '';
+ end;
+ end;
+ }
+ dataitem(VATCounter; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ column(VATAmtLineInvDiscAmt; TempVATAmountLine."Invoice Discount Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(VATAmtLineInvDiscBaseAmt; TempVATAmountLine."Inv. Disc. Base Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(VATAmtLineLineAmt; TempVATAmountLine."Line Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(VATAmtLineVAT; TempVATAmountLine."VAT %")
+ {
+ DecimalPlaces = 0 : 5;
+ }
+ column(VATAmtLineVATAmt; TempVATAmountLine."VAT Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(VATAmtLineVATBase; TempVATAmountLine."VAT Base")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(VATAmtLineVATIdentifier; TempVATAmountLine."VAT Identifier")
+ {
+ }
+ trigger OnAfterGetRecord()
+ begin
+ TempVATAmountLine.GetLine(Number);
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ if VATAmount = 0 then
+ CurrReport.Break();
+ SetRange(Number, 1, TempVATAmountLine.Count);
+ end;
+ }
+ dataitem(VATCounterLCY; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ column(VALExchRate; VALExchRate)
+ {
+ }
+ column(VALSpecLCYHeader; VALSpecLCYHeader)
+ {
+ }
+ column(VALVATAmountLCY; VALVATAmountLCY)
+ {
+ AutoFormatType = 1;
+ }
+ column(VALVATBaseLCY; VALVATBaseLCY)
+ {
+ AutoFormatType = 1;
+ }
+ trigger OnAfterGetRecord()
+ begin
+ TempVATAmountLine.GetLine(Number);
+ VALVATBaseLCY :=
+ TempVATAmountLine.GetBaseLCY(
+ "Purchase Header"."Posting Date", "Purchase Header"."Currency Code", "Purchase Header"."Currency Factor");
+ VALVATAmountLCY :=
+ TempVATAmountLine.GetAmountLCY(
+ "Purchase Header"."Posting Date", "Purchase Header"."Currency Code", "Purchase Header"."Currency Factor");
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ if (not GLSetup."Print VAT specification in LCY") or
+ ("Purchase Header"."Currency Code" = '') or
+ (TempVATAmountLine.GetTotalVATAmount() = 0)
+ then
+ CurrReport.Break();
+
+ SetRange(Number, 1, TempVATAmountLine.Count);
+
+ if GLSetup."LCY Code" = '' then
+ VALSpecLCYHeader := VATAmountSpecificationLbl + LocalCurrencyLbl
+ else
+ VALSpecLCYHeader := VATAmountSpecificationLbl + Format(GLSetup."LCY Code");
+
+ CurrencyExchangeRate.FindCurrency("Purchase Header"."Posting Date", "Purchase Header"."Currency Code", 1);
+ VALExchRate := StrSubstNo(ExchangeRateLbl, CurrencyExchangeRate."Relational Exch. Rate Amount", CurrencyExchangeRate."Exchange Rate Amount");
+ end;
+ }
+ dataitem(PrepmtLoop; "Integer")
+ {
+ DataItemTableView = sorting(Number) where(Number = filter(1 ..));
+ column(PrepaymentSpecCaption; PrepaymentSpecCaptionLbl)
+ {
+ }
+ column(PrepmtInvBuDescCaption; PrepmtInvBuDescCaptionLbl)
+ {
+ }
+ column(PrepmtInvBufAmt; TempPrepaymentInvLineBuffer.Amount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtInvBufDesc; TempPrepaymentInvLineBuffer.Description)
+ {
+ }
+ column(PrepmtInvBufGLAccNo; TempPrepaymentInvLineBuffer."G/L Account No.")
+ {
+ }
+ column(PrepmtInvBufGLAccNoCaption; PrepmtInvBufGLAccNoCaptionLbl)
+ {
+ }
+ column(PrepmtLineAmount; PrepmtLineAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtTotalAmountInclVAT; PrepmtTotalAmountInclVAT)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtVATAmount; PrepmtVATAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtVATAmountText; TempPrepmtVATAmountLine.VATAmountText())
+ {
+ }
+ column(PrepmtVATBaseAmount; PrepmtVATBaseAmount)
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(TotalExclVATText2; TotalExclVATText)
+ {
+ }
+ column(TotalInclVATText2; TotalInclVATText)
+ {
+ }
+ trigger OnAfterGetRecord()
+ begin
+ if Number = 1 then begin
+ if not TempPrepaymentInvLineBuffer.Find('-') then
+ CurrReport.Break();
+ end else
+ if TempPrepaymentInvLineBuffer.Next() = 0 then
+ CurrReport.Break();
+
+ if "Purchase Header"."Prices Including VAT" then
+ PrepmtLineAmount := TempPrepaymentInvLineBuffer."Amount Incl. VAT"
+ else
+ PrepmtLineAmount := TempPrepaymentInvLineBuffer.Amount;
+ end;
+ }
+ dataitem(PrepmtVATCounter; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ column(PrepmtVATAmtLineLineAmt; TempPrepmtVATAmountLine."Line Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtVATAmtLineVAT; TempPrepmtVATAmountLine."VAT %")
+ {
+ DecimalPlaces = 0 : 5;
+ }
+ column(PrepmtVATAmtLineVATAmt; TempPrepmtVATAmountLine."VAT Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtVATAmtLineVATBase; TempPrepmtVATAmountLine."VAT Base")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(PrepmtVATAmtLineVATId; TempPrepmtVATAmountLine."VAT Identifier")
+ {
+ }
+ column(PrepymtVATAmtSpecCaption; PrepymtVATAmtSpecCaptionLbl)
+ {
+ }
+ trigger OnAfterGetRecord()
+ begin
+ TempPrepmtVATAmountLine.GetLine(Number);
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ SetRange(Number, 1, TempPrepmtVATAmountLine.Count);
+ end;
+ }
+ dataitem(LetterText; "Integer")
+ {
+ DataItemTableView = sorting(Number) where(Number = const(1));
+ column(BodyText; BodyLbl)
+ {
+ }
+ column(ClosingText; ClosingLbl)
+ {
+ }
+ column(GreetingText; GreetingLbl)
+ {
+ }
+ }
+ dataitem(SubcVATAmountLine; "VAT Amount Line")
+ {
+ DataItemTableView = sorting("VAT Identifier", "VAT Calculation Type", "Tax Group Code", "Use Tax", Positive);
+ UseTemporary = true;
+ column(SubcInvoiceDiscountAmount_VATAmountLine; "Invoice Discount Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(SubcInvoiceDiscountAmount_VATAmountLine_Lbl; FieldCaption("Invoice Discount Amount"))
+ {
+ }
+ column(SubcInvoiceDiscountBaseAmount_VATAmountLine; "Inv. Disc. Base Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(SubcInvoiceDiscountBaseAmount_VATAmountLine_Lbl; FieldCaption("Inv. Disc. Base Amount"))
+ {
+ }
+ column(SubcLineAmount_VatAmountLine; "Line Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(SubcLineAmount_VatAmountLine_Lbl; FieldCaption("Line Amount"))
+ {
+ }
+ column(SubcNoOfVATIdentifiers; Count)
+ {
+ }
+ column(SubcOfLbl; OfLbl)
+ {
+ }
+ column(SubcTotalExclVATText; SubTotalExclVATText)
+ {
+ }
+ column(SubcTotalInclVATText; SubTotalInclVATText)
+ {
+ }
+ column(SubcVATAmount_VatAmountLine; "VAT Amount")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(SubcVATAmount_VatAmountLine_Lbl; FieldCaption("VAT Amount"))
+ {
+ }
+ column(SubcVATBase_VatAmountLine; "VAT Base")
+ {
+ AutoFormatExpression = "Purchase Header"."Currency Code";
+ AutoFormatType = 1;
+ }
+ column(SubcVATBase_VatAmountLine_Lbl; FieldCaption("VAT Base"))
+ {
+ }
+ column(SubcVATClause2Description; VATClause2.Description)
+ {
+ }
+ column(SubcVATClause2Description2; VATClause2."Description 2")
+ {
+ }
+ column(SubcVATIdentifier_VatAmountLine; "VAT Identifier")
+ {
+ }
+ column(SubcVATIdentifier_VatAmountLine_Lbl; FieldCaption("VAT Identifier"))
+ {
+ }
+ column(SubcVATPct_VatAmountLine; "VAT %")
+ {
+ DecimalPlaces = 0 : 5;
+ }
+ column(SubcVATPct_VatAmountLine_Lbl; FieldCaption("VAT %"))
+ {
+ }
+ column(SubcVATPercentCaptionLbl; VATPercentCaptionLbl)
+ {
+ }
+ }
+ trigger OnAfterGetRecord()
+ begin
+ TotalAmount := 0;
+ CurrReport.Language := LanguageMgt.GetLanguageIdOrDefault("Language Code");
+ FormatAddress.SetLanguageCode("Language Code");
+
+ FormatAddressFields("Purchase Header");
+ FormatDocumentFields("Purchase Header");
+ if BuyFromContact.Get("Buy-from Contact No.") then;
+ if PayToContact.Get("Pay-to Contact No.") then;
+
+ if not IsReportInPreviewMode() then begin
+ Codeunit.Run(Codeunit::"Purch.Header-Printed", "Purchase Header");
+ if ShouldArchiveDocument then
+ ArchiveManagement.StorePurchDocument("Purchase Header", ShouldLogInteraction);
+ end;
+
+ SubFormatAddress.PurchHeaderShipTo(ShipToAddr, "Purchase Header");
+
+ if not SubcVATAmountLine.IsEmpty() then
+ SubcVATAmountLine.DeleteAll();
+ "Purchase Line".CalcVATAmountLines(0, "Purchase Header", "Purchase Line", SubcVATAmountLine);
+ "Purchase Line".UpdateVATOnLines(0, "Purchase Header", "Purchase Line", SubcVATAmountLine);
+ ShowShippingAddr := ("Purchase Header"."Buy-from Vendor No." <> "Purchase Header"."Sell-to Customer No.");
+ if not ShowShippingAddr then
+ Clear(ShipToAddr);
+
+ SetTotalLabels("Currency Code", SubTotalText);
+ SetInvDisAmountLbl("Currency Code", InvDiscAmtCaptionLbl);
+
+ FormatFooter();
+
+ SubVATAmount := SubcVATAmountLine.GetTotalVATAmount();
+ SubVATBaseAmount := SubcVATAmountLine.GetTotalVATBase();
+ SubVATDiscountAmount := SubcVATAmountLine.GetTotalVATDiscount("Purchase Header"."Currency Code", "Purchase Header"."Prices Including VAT");
+
+ SetLabels();
+
+ Vendor.Get("Purchase Header"."Buy-from Vendor No.");
+ end;
+ }
+ dataitem(SubcShipToAddr; Integer)
+ {
+ DataItemTableView = sorting(Number);
+ column(TempSUBShipToAddr__Address; TempShiptoAddress.Address)
+ {
+ }
+ trigger OnPreDataItem()
+ begin
+ SetRange(Number, 1, TempShiptoAddress.Count);
+ end;
+
+ trigger OnAfterGetRecord()
+ begin
+ if Number = 1 then
+ TempShiptoAddress.Find('-')
+ else
+ TempShiptoAddress.Next();
+ end;
+ }
+ }
+ requestpage
+ {
+ SaveValues = true;
+
+ layout
+ {
+ area(Content)
+ {
+ group(Options)
+ {
+ Caption = 'Options';
+ field(ArchiveDocument; ShouldArchiveDocument)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Archive Document';
+ ToolTip = 'Specifies whether to archive the order.';
+ Visible = false;
+ }
+ field(LogInteraction; ShouldLogInteraction)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Log Interaction';
+ Enabled = LogInteractionEnable;
+ ToolTip = 'Specifies if you want to log this interaction.';
+ }
+ field(SubcPrintAddressLine; PrintAddressLine)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Show Address Line';
+ ToolTip = 'Specifies if the address line is shown.';
+ }
+ field(SubcPrintFooterLine; PrintFooterLine)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Show Footer Line';
+ ToolTip = 'Specifies if the footer line is shown.';
+ }
+ field(SubcPrintBarCode; PrintBarCode)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Show Bar Code';
+ ToolTip = 'Specifies if the Barcode is shown.';
+ }
+ }
+ }
+ }
+ trigger OnInit()
+ begin
+ LogInteractionEnable := true;
+ ShouldArchiveDocument := PurchasesPayablesSetup."Archive Orders";
+ end;
+
+ trigger OnOpenPage()
+ begin
+ LogInteractionEnable := ShouldLogInteraction;
+ end;
+ }
+ trigger OnInitReport()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ CurrReport.Quit();
+#endif
+ GLSetup.Get();
+ CompanyInformation.Get();
+ PurchasesPayablesSetup.Get();
+ CompanyInformation.CalcFields(Picture);
+
+ SalesReceivablesSetup.Get();
+ SubFormatDocument.SetLogoPosition(SalesReceivablesSetup."Logo Position on Documents", CompanyInformation1, CompanyInformation2, SubCompanyInformation);
+ end;
+
+ trigger OnPostReport()
+ begin
+ if ShouldLogInteraction and not IsReportInPreviewMode() then
+ if "Purchase Header".FindSet() then
+ repeat
+ SegManagement.LogDocument(
+ 13, "Purchase Header"."No.", 0, 0, Database::Vendor, "Purchase Header"."Buy-from Vendor No.",
+ "Purchase Header"."Purchaser Code", '', "Purchase Header"."Posting Description", '');
+ until "Purchase Header".Next() = 0;
+ end;
+
+ trigger OnPreReport()
+ begin
+ if not CurrReport.UseRequestPage then
+ InitLogInteraction();
+ end;
+
+ var
+ CompanyInformation: Record "Company Information";
+ SubCompanyInformation: Record "Company Information";
+ CompanyInformation1: Record "Company Information";
+ CompanyInformation2: Record "Company Information";
+ TempCompanyInformation: Record "Company Information" temporary;
+ BuyFromContact: Record Contact;
+ PayToContact: Record Contact;
+ CurrencyExchangeRate: Record "Currency Exchange Rate";
+ GLSetup: Record "General Ledger Setup";
+ PaymentTerms: Record "Payment Terms";
+ PrepmtPaymentTerms: Record "Payment Terms";
+ TempPrepaymentInvLineBuffer: Record "Prepayment Inv. Line Buffer" temporary;
+ TempPurchaseLine: Record "Purchase Line" temporary;
+ PurchasesPayablesSetup: Record "Purchases & Payables Setup";
+ ResponsibilityCenter: Record "Responsibility Center";
+ SalesReceivablesSetup: Record "Sales & Receivables Setup";
+ SalespersonPurchaser: Record "Salesperson/Purchaser";
+ TempShiptoAddress: Record "Ship-to Address" temporary;
+ ShipmentMethod: Record "Shipment Method";
+ TempPrepmtVATAmountLine: Record "VAT Amount Line" temporary;
+ TempPrePmtVATAmountLineDeduct: Record "VAT Amount Line" temporary;
+ TempVATAmountLine: Record "VAT Amount Line" temporary;
+ VATClause2: Record "VAT Clause";
+ Vendor: Record Vendor;
+ ArchiveManagement: Codeunit ArchiveManagement;
+ SubFormatAddress: Codeunit "Format Address";
+ FormatAddress: Codeunit "Format Address";
+ SubFormatDocument: Codeunit "Format Document";
+ FormatDocument: Codeunit "Format Document";
+ LanguageMgt: Codeunit Language;
+ PurchPost: Codeunit "Purch.-Post";
+ PurchasePostPrepayments: Codeunit "Purchase-Post Prepayments";
+ SegManagement: Codeunit SegManagement;
+ ShouldArchiveDocument: Boolean;
+ PrintAddressLine: Boolean;
+ PrintBarCode: Boolean;
+ PrintFooterLine: Boolean;
+ PrintPaymentTerms: Boolean;
+ ShowShippingAddr: Boolean;
+ ShouldLogInteraction: Boolean;
+ LogInteractionEnable: Boolean;
+ VATAmount, VATBaseAmount, VATDiscountAmount : Decimal;
+ PrepmtLineAmount: Decimal;
+ PrepmtTotalAmountInclVAT: Decimal;
+ PrepmtVATAmount: Decimal;
+ PrepmtVATBaseAmount: Decimal;
+ TotalAmount: Decimal;
+ TotalAmountInclVAT: Decimal;
+ TotalInvoiceDiscountAmount: Decimal;
+ TotalSubTotal: Decimal;
+ VALVATAmountLCY: Decimal;
+ VALVATBaseLCY: Decimal;
+ SubVATAmount: Decimal;
+ SubVATBaseAmount: Decimal;
+ SubVATDiscountAmount: Decimal;
+ CompanyLogoPosition: Integer;
+ OutputNo: Integer;
+ AllowInvoiceDiscCaptionLbl: Label 'Allow Invoice Discount';
+ AmountCaptionLbl: Label 'Amount';
+ BodyLbl: Label 'The purchase order is attached to this message.';
+ BuyerCaptionLbl: Label 'Buyer';
+ BuyFromContactEmailLbl: Label 'Buy-from Contact E-Mail';
+ BuyFromContactMobilePhoneNoLbl: Label 'Buy-from Contact Mobile Phone No.';
+ BuyFromContactPhoneNoLbl: Label 'Buy-from Contact Phone No.';
+ ClosingLbl: Label 'Sincerely';
+ CompanyInfoBankAccNoCaptionLbl: Label 'Account No.';
+ CompanyInfoBankNameCaptionLbl: Label 'Bank';
+ CompanyInfoGiroNoCaptionLbl: Label 'Giro No.';
+ CompanyInfoPhoneNoCaptionLbl: Label 'Phone No.';
+ ComponentsToShipLbl: Label 'Components to ship';
+ ConfirmToCaptionLbl: Label 'Confirm To';
+ ContinuedCaptionLbl: Label 'Continued';
+ DirectUniCostCaptionLbl: Label 'Direct Unit Cost';
+ DocumentDateCaptionLbl: Label 'Document Date';
+ DocumentTitleLbl: Label 'Purchase Order';
+ EmailIDCaptionLbl: Label 'Email';
+ EndingDateLbl: Label 'Ending Date';
+ ExchangeRateLbl: Label 'Exchange rate: %1/%2', Comment = '%1=Currency Code, %2=Currency Code';
+ ExpectedReceiptDateLbl: Label 'Expected Receipt Date';
+ SubAmountCaptionLbl: Label 'Amount';
+ CompanyInfoBankNameLbl: Label 'Bank';
+ CompanyInfoFaxLbl: Label 'Fax No.';
+ CompanyInfoPhoneNoLbl: Label 'Phone No.';
+ CourtLocationLbl: Label 'Court Location: ';
+ DocCaptionLbl: Label 'Subcontractor - Dispatch List';
+ DocumentDateLbl: Label 'Document Date';
+ EMailLbl: Label 'Email';
+ ExternalDocumentNoCaptionLbl: Label 'Ext. Doc. No.';
+ HomePageLbl: Label 'Home Page';
+ OfForPageLbl: Label 'of';
+ OfLbl: Label 'of';
+ SubOrderNoCaptionLbl: Label 'Order No.';
+ PageLbl: Label 'Page';
+ PaymentTermsDescriptionLbl: Label 'Payment Terms:';
+ PromisedDeliveryDateLbl: Label 'Deliv. Date';
+ RegisterCourtNoLbl: Label 'Register Court: ';
+ SalesLineLineDiscountLbl: Label 'Discount %';
+ SalespersonLbl: Label 'Purchaser';
+ SalesPersonPurchaserEmailLbl: Label 'E-Mail';
+ ShptMethodDescLbl: Label 'Shipment Method:';
+ Unit_PriceCaptionLbl: Label 'Unit Price';
+ UnitOfMeasureLbl: Label 'Unit of Measure';
+ VATAmountSpecificationCaptionLbl: Label 'VAT Amount Specification';
+ VATClauseCaptionLbl: Label 'VAT Clause';
+ VATNoLbl: Label 'VAT Registration No.';
+ VATPercentCaptionLbl: Label '% VAT';
+ VendorNoLbl: Label 'Vendor No.';
+ YourReferenceLbl: Label 'Your Reference';
+ GreetingLbl: Label 'Hello';
+ HomePageCaptionLbl: Label 'Home Page';
+ InvDiscCaptionLbl: Label 'Invoice Discount:';
+ ItemDescriptionCaptionLbl: Label 'Description';
+ ItemLbl: Label 'Item';
+ ItemLineAmountCaptionLbl: Label 'Line Amount';
+ ItemNumberCaptionLbl: Label 'Item No.';
+ ItemQuantityCaptionLbl: Label 'Quantity';
+ ItemUnitCaptionLbl: Label 'Unit';
+ ItemUnitOfMeasureCaptionLbl: Label 'Unit';
+ ItemUnitPriceCaptionLbl: Label 'Unit Price';
+ JobNoLbl: Label 'Job No.';
+ JobTaskNoLbl: Label 'Job Task No.';
+ LocalCurrencyLbl: Label 'Local Currency';
+ OrderDateLbl: Label 'Order Date';
+ OrderDatenLbl: Label 'Order Date';
+ OrderNoCaptionLbl: Label 'Order No.';
+ OutstandingLbl: Label 'Outstanding';
+ PageCaptionLbl: Label 'Page';
+ SubcPageLbl: Label 'Page';
+ PaymentDetailsCaptionLbl: Label 'Payment Details';
+ PaymentTermsDescCaptionLbl: Label 'Payment Terms';
+ PayToContactEmailLbl: Label 'Pay-to Contact E-Mail';
+ PayToContactMobilePhoneNoLbl: Label 'Pay-to Contact Mobile Phone No.';
+ PayToContactPhoneNoLbl: Label 'Pay-to Contact Phone No.';
+ PlannedReceiptDateLbl: Label 'Planned Receipt Date';
+ PrepaymentSpecCaptionLbl: Label 'Prepayment Specification';
+ PrepmtInvBuDescCaptionLbl: Label 'Description';
+ PrepmtInvBufGLAccNoCaptionLbl: Label 'G/L Account No.';
+ PrepymtTermsDescCaptionLbl: Label 'Prepmt. Payment Terms';
+ PrepymtVATAmtSpecCaptionLbl: Label 'Prepayment VAT Amount Specification';
+ PricesIncludingVATCaptionLbl: Label 'Prices Including VAT';
+ PricesInclVATtxtLbl: Label 'Prices Including VAT';
+ ProdOrderLbl: Label 'Prod. Order:';
+ PromisedReceiptDateLbl: Label 'Promised Receipt Date';
+ PurchLineInvDiscAmtCaptionLbl: Label 'Invoice Discount Amount';
+ PurchLineLineDiscCaptionLbl: Label 'Discount %';
+ PurchOrderCaptionLbl: Label 'PURCHASE ORDER';
+ PurchOrderDateCaptionLbl: Label 'Purchase Order Date:';
+ PurchOrderNumCaptionLbl: Label 'Order No.';
+ QtyToShipLbl: Label 'Qty to ship';
+ QuantityBaseLbl: Label 'Quantity (Base)';
+ QuantityLbl: Label 'Quantity';
+ ReceivebyCaptionLbl: Label 'Receive By';
+ RemainingQuantityLbl: Label 'Remaining Qty';
+ RequestedReceiptDateLbl: Label 'Requested Receipt Date';
+ ShipmentMethodDescCaptionLbl: Label 'Shipment Method';
+ ShiptoAddressCaptionLbl: Label 'Ship-to Address';
+ StartingDateLbl: Label 'Starting Date';
+ SubcontractorDispatchListLbl: Label 'Subcontractor Dispatch List';
+ SubcontractorLbl: Label 'Subcontractor';
+ SubcOrdNoLbl: Label 'Subc. Ord. No.';
+ SubtotalCaptionLbl: Label 'Subtotal';
+ TaxIdentTypeCaptionLbl: Label 'Tax Ident. Type';
+ ToCaptionLbl: Label 'To:';
+ TotalCaptionLbl: Label 'Total';
+ TotalPriceCaptionLbl: Label 'Total Price';
+ UnitPriceLbl: Label 'Unit Price (LCY)';
+ UoMLbl: Label 'UoM';
+ VALVATBaseLCYCaptionLbl: Label 'VAT Base';
+ VATAmountSpecificationLbl: Label 'VAT Amount Specification in ';
+ VATAmtLineInvDiscBaseAmtCaptionLbl: Label 'Invoice Discount Base Amount';
+ VATAmtLineLineAmtCaptionLbl: Label 'Line Amount';
+ VATAmtLineVATAmtCaptionLbl: Label 'VAT Amount';
+ VATAmtLineVATCaptionLbl: Label 'VAT %';
+ VATAmtSpecCaptionLbl: Label 'VAT Amount Specification';
+ VATDiscountAmountCaptionLbl: Label 'Payment Discount on VAT';
+ VATIdentifierCaptionLbl: Label 'VAT Identifier';
+ VendNoCaptionLbl: Label 'Vendor No.';
+ VendorIDCaptionLbl: Label 'Vendor ID';
+ VendorInvoiceNoLbl: Label 'Vendor Invoice No.';
+ VendorOrderNoLbl: Label 'Vendor Order No.';
+ LineDiscountPctPlaceholderLbl: Label '%1%', Locked = true;
+ InvDiscAmtCapLbl: Label 'Invoice Discount Amount %1', Comment = '%1=Currency Code';
+ TotalTxt: Label 'Net Amount %1', Comment = '%1=Currency Code';
+ CustomGiroLbl, CustomGiroTxt, LegalOfficeLbl, LegalOfficeTxt : Text;
+ FormattedDirectUnitCost: Text;
+ FormattedLineAmount: Text;
+ FormattedQuanitity: Text;
+ FormattedVATPct: Text;
+ AddrInfoLine: Text;
+ CompanyInfoBankNameLblFooter, CompanyInfoIBANLblFooter, CompanyInfoSWIFTLblFooter : Text;
+ CompanyInfoEMailAddressLblFooter, CompanyInfoFaxNoLblFooter, CompanyInfoHomepageLblFooter, CompanyInfoPhoneNoLblFooter : Text;
+ CompanyVATRegNoFooter, CompanyVATRegNoLblFooter, SellToCustomerNoCaption, SellToCustomerNoShipToAddr, ShipToAddressCaption : Text;
+ CourtLocationLblFooter, ExecutiveDirectorsLblFooter, RegisterCourtLblFooter : Text;
+ DecimalAmountFormatExpression: Text;
+ FooterMark: Text;
+ HeaderDateFormatExpression: Text;
+ HeaderMark: Text;
+ InvDiscAmount: Text;
+ LineAmount, PosNoText, UnitCost : Text;
+ LineDateFormatExpression: Text;
+ LineDiscountPctText: Text;
+ LineMark: Text;
+ PaymentTermsDescriptionTxt: Text;
+ PriceUnit: Text;
+ PricingUoMCode: Text;
+ Seperator2Lbl, Seperator3Lbl, SeperatorLbl : Text;
+ ShptMethodDescTxt: Text;
+ TotalAmountIncludingVATTxt: Text;
+ ItemNo: Text;
+ CompanyInfoSWIFTFooter: Text[20];
+ AllowInvDisctxt: Text[30];
+ CompanyInfoCourtLocationFooter, CompanyInfoFaxNoFooter, CompanyInfoPhoneNoFooter, CompanyInfoRegisterCourtNoFooter : Text[30];
+ HeaderPhoneNo: Text[30];
+
+ CompanyInfoIBANFooter: Text[50];
+ InvDiscAmtCaptionLbl: Text[50];
+ SubTotalExclVATText, SubTotalInclVATText, SubTotalText : Text[50];
+ PurchaserText: Text[50];
+ TotalExclVATText: Text[50];
+ TotalInclVATText: Text[50];
+ TotalText: Text[50];
+ VALExchRate: Text[50];
+ CompanyInfoEmailFooter: Text[80];
+ CompanyInfoExecutiveDirector1Footer, CompanyInfoExecutiveDirector2Footer, CompanyInfoExecutiveDirector3Footer, CompanyInfoExecutiveDirector4Footer : Text[80];
+ ReferenceText: Text[80];
+ VALSpecLCYHeader: Text[80];
+ VATNoText: Text[80];
+ BuyFromAddr: array[8] of Text[100];
+ CompanyAddr: array[8] of Text[100];
+ CompanyAddress1Footer, CompanyAddress2Footer, CompanyAddress3Footer, CompanyAddress4Footer : Text[100];
+ CompanyInfoBankNameFooter: Text[100];
+ ShipToAddr: array[8] of Text[100];
+ VendAddr: array[8] of Text[100];
+ DimText: Text[120];
+ CompanyInfoHomepageFooter: Text[255];
+
+ procedure InitializeRequest(LogInteractionParam: Boolean)
+ begin
+ ShouldLogInteraction := LogInteractionParam;
+ end;
+
+ local procedure IsReportInPreviewMode(): Boolean
+ var
+ MailManagement: Codeunit "Mail Management";
+ begin
+ exit(CurrReport.Preview or MailManagement.IsHandlingGetEmailBody());
+ end;
+
+ local procedure FormatAddressFields(var PurchaseHeader: Record "Purchase Header")
+ begin
+ FormatAddress.GetCompanyAddr(PurchaseHeader."Responsibility Center", ResponsibilityCenter, CompanyInformation, CompanyAddr);
+ FormatAddress.PurchHeaderBuyFrom(BuyFromAddr, PurchaseHeader);
+ if PurchaseHeader."Buy-from Vendor No." <> PurchaseHeader."Pay-to Vendor No." then
+ FormatAddress.PurchHeaderPayTo(VendAddr, PurchaseHeader);
+ FormatAddress.PurchHeaderShipTo(ShipToAddr, PurchaseHeader);
+ end;
+
+ local procedure FormatDocumentFields(PurchaseHeader: Record "Purchase Header")
+ begin
+ FormatDocument.SetTotalLabels(PurchaseHeader."Currency Code", TotalText, TotalInclVATText, TotalExclVATText);
+ FormatDocument.SetPurchaser(SalespersonPurchaser, PurchaseHeader."Purchaser Code", PurchaserText);
+ FormatDocument.SetPaymentTerms(PaymentTerms, PurchaseHeader."Payment Terms Code", PurchaseHeader."Language Code");
+ FormatDocument.SetPaymentTerms(PrepmtPaymentTerms, PurchaseHeader."Prepmt. Payment Terms Code", PurchaseHeader."Language Code");
+ FormatDocument.SetShipmentMethod(ShipmentMethod, PurchaseHeader."Shipment Method Code", PurchaseHeader."Language Code");
+
+ ReferenceText := CopyStr(FormatDocument.SetText(PurchaseHeader."Your Reference" <> '', CopyStr(PurchaseHeader.FieldCaption("Your Reference"), 1, 80)), 1, MaxStrLen(ReferenceText));
+ VATNoText := CopyStr(FormatDocument.SetText(PurchaseHeader."VAT Registration No." <> '', CopyStr(PurchaseHeader.FieldCaption("VAT Registration No."), 1, 80)), 1, MaxStrLen(VATNoText));
+ end;
+
+ local procedure InitLogInteraction()
+ begin
+ ShouldLogInteraction := SegManagement.FindInteractionTemplateCode("Interaction Log Entry Document Type"::"Purch. Ord.") <> '';
+ end;
+
+ local procedure BlankZero(PurchaseLine: Record "Purchase Line")
+ begin
+ if PurchaseLine."Line Discount %" = 0 then
+ LineDiscountPctText := ''
+ else
+ LineDiscountPctText := StrSubstNo(LineDiscountPctPlaceholderLbl, Round(PurchaseLine."Line Discount %", 0.1));
+
+ if PurchaseLine."Line Amount" = 0 then
+ LineAmount := ''
+ else
+ LineAmount := Format(PurchaseLine."Line Amount", 0, DecimalAmountFormatExpression);
+
+ if PurchaseLine."Unit Cost" = 0 then
+ UnitCost := ''
+ else
+ UnitCost := FormattedDirectUnitCost;
+ end;
+
+ local procedure FormatFooter()
+ begin
+ if PrintFooterLine then begin
+ CompanyAddress1Footer := CompanyAddr[1];
+ CompanyAddress2Footer := CompanyAddr[2];
+ CompanyAddress3Footer := CompanyAddr[3];
+ CompanyAddress4Footer := CompanyAddr[4];
+
+ CompanyInfoPhoneNoLblFooter := CompanyInfoPhoneNoLbl;
+ CompanyInfoFaxNoLblFooter := CompanyInfoFaxLbl;
+ CompanyInfoEMailAddressLblFooter := EMailLbl;
+ CompanyInfoHomepageLblFooter := HomePageLbl;
+ CompanyVATRegNoLblFooter := CompanyInformation.GetVATRegistrationNumberLbl();
+
+ CompanyInfoPhoneNoFooter := CompanyInformation."Phone No.";
+ CompanyInfoFaxNoFooter := CompanyInformation."Fax No.";
+ CompanyInfoEmailFooter := CompanyInformation."E-Mail";
+ CompanyVATRegNoFooter := CompanyInformation.GetVATRegistrationNumber();
+
+ CourtLocationLblFooter := CourtLocationLbl;
+ RegisterCourtLblFooter := RegisterCourtNoLbl;
+
+ CompanyInfoBankNameLblFooter := CompanyInfoBankNameLbl;
+ CompanyInfoIBANLblFooter := CompanyInformation.FieldCaption(IBAN);
+ CompanyInfoSWIFTLblFooter := CompanyInformation.FieldCaption("SWIFT Code");
+
+ CompanyInfoBankNameFooter := CompanyInformation."Bank Name";
+ CompanyInfoIBANFooter := CompanyInformation.IBAN;
+ CompanyInfoSWIFTFooter := CompanyInformation."SWIFT Code";
+ end;
+ end;
+
+ local procedure SetInvDisAmountLbl(CurrencyCode: Code[10]; var SubInvDiscAmtCaptionLbl: Text[50])
+ var
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ begin
+ if CurrencyCode = '' then begin
+ GeneralLedgerSetup.Get();
+ GeneralLedgerSetup.TestField("LCY Code");
+ SubInvDiscAmtCaptionLbl := StrSubstNo(InvDiscAmtCapLbl, GeneralLedgerSetup."LCY Code");
+ end else
+ SubInvDiscAmtCaptionLbl := StrSubstNo(InvDiscAmtCapLbl, CurrencyCode);
+ end;
+
+ local procedure SetTotalLabels(CurrencyCode: Code[10]; var TotalAsText: Text[50])
+ var
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ begin
+ if CurrencyCode = '' then begin
+ GeneralLedgerSetup.Get();
+ GeneralLedgerSetup.TestField("LCY Code");
+ TotalAsText := StrSubstNo(TotalTxt, GeneralLedgerSetup."LCY Code");
+ end else
+ TotalAsText := StrSubstNo(TotalTxt, CurrencyCode);
+ end;
+
+ local procedure SetLabels()
+ begin
+ if "Purchase Header"."Invoice Discount Value" = 0 then begin
+ InvDiscAmount := '';
+ InvDiscAmtCaptionLbl := '';
+ end else
+ InvDiscAmount := Format("Purchase Header"."Invoice Discount Value", 0, DecimalAmountFormatExpression);
+
+ if ("Purchase Header"."Payment Terms Code" = '') or (not PrintPaymentTerms) then
+ PaymentTermsDescriptionTxt := ''
+ else
+ PaymentTermsDescriptionTxt := PaymentTermsDescriptionLbl;
+
+ if "Purchase Header"."Shipment Method Code" = '' then
+ ShptMethodDescTxt := ''
+ else
+ ShptMethodDescTxt := ShptMethodDescLbl;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcDispatchingList.docx b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcDispatchingList.docx
new file mode 100644
index 0000000000..0d99e66bbb
Binary files /dev/null and b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcDispatchingList.docx differ
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcFinishedProdOrder.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcFinishedProdOrder.PageExt.al
new file mode 100644
index 0000000000..9305138f0c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcFinishedProdOrder.PageExt.al
@@ -0,0 +1,69 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+pageextension 99001548 "Subc. Finished Prod. Order" extends "Finished Production Order"
+{
+ actions
+ {
+ addafter("Registered Put-away Lines")
+ {
+ action("Subcontracting Purchase Lines")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Order Lines';
+ Image = SubcontractingWorksheet;
+ RunObject = page "Purchase Lines";
+ RunPageLink = "Document Type" = const(Order), "Prod. Order No." = field("No.");
+ ToolTip = 'Show purchase order lines for subcontracting.';
+ }
+ }
+ addafter("&Warehouse Entries")
+ {
+ action("Subc. Transfer Orders")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Orders';
+ Image = TransferOrder;
+ ToolTip = 'View the subcontracting transfer orders related to this production order.';
+
+ trigger OnAction()
+ var
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersFromProductionOrder(Rec);
+ end;
+ }
+ action("Subc. Transfer Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Entries';
+ Image = ItemLedger;
+ RunObject = page "Item Ledger Entries";
+ RunPageLink = "Entry Type" = const(Transfer), "Subc. Prod. Order No." = field("No.");
+ RunPageView = sorting("Order Type", "Order No.");
+ ToolTip = 'View the list of subcontracting transfers.';
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Prod. Order Status" = field(Status), "Prod. Order No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries for this production order.';
+ }
+ }
+ addlast(Category_Entries)
+ {
+ actionref("Subc. Transfer Entries_Promoted"; "Subc. Transfer Entries") { }
+ actionref("WIP Ledger Entries_Promoted"; "WIP Ledger Entries") { }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcFinishedProdOrders.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcFinishedProdOrders.PageExt.al
new file mode 100644
index 0000000000..ff954e609e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcFinishedProdOrders.PageExt.al
@@ -0,0 +1,64 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+pageextension 99001543 "Subc. Finished Prod. Orders" extends "Finished Production Orders"
+{
+ actions
+ {
+ addafter("E&ntries")
+ {
+ action("Subcontracting Purchase Lines")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Order Lines';
+ Image = SubcontractingWorksheet;
+ RunObject = page "Purchase Lines";
+ RunPageLink = "Document Type" = const(Order), "Prod. Order No." = field("No.");
+ ToolTip = 'Show purchase order lines for subcontracting.';
+ }
+ }
+ addafter("&Warehouse Entries")
+ {
+ action("Subc. Transfer Orders")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Orders';
+ Image = TransferOrder;
+ ToolTip = 'View the subcontracting transfer orders related to this production order.';
+
+ trigger OnAction()
+ var
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersFromProductionOrder(Rec);
+ end;
+ }
+ action("Subc. Transfer Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Entries';
+ Image = ItemLedger;
+ RunObject = page "Item Ledger Entries";
+ RunPageLink = "Entry Type" = const(Transfer), "Subc. Prod. Order No." = field("No.");
+ RunPageView = sorting("Order Type", "Order No.");
+ ToolTip = 'View the list of subcontracting transfers.';
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Prod. Order Status" = field(Status), "Prod. Order No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries for this production order.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningComp.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningComp.PageExt.al
new file mode 100644
index 0000000000..0d33f17d31
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningComp.PageExt.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.Inventory.Planning;
+
+pageextension 99001511 "Subc. Planning Comp" extends "Planning Components"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Component Supply Method"; Rec."Component Supply Method")
+ {
+ ApplicationArea = Subcontracting;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningCompExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningCompExt.Codeunit.al
new file mode 100644
index 0000000000..9a8f1e299b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningCompExt.Codeunit.al
@@ -0,0 +1,120 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Inventory.Planning;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Purchases.Vendor;
+
+codeunit 99001522 "Subc. Planning Comp. Ext."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Table, Database::"Planning Component", OnAfterValidateEvent, "Routing Link Code", false, false)]
+ local procedure OnAfterValidateRoutingLinkCode(var Rec: Record "Planning Component"; var xRec: Record "Planning Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ HandleRoutingLinkCodeValidation(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Planning Component", OnAfterTransferFromComponent, '', false, false)]
+ local procedure OnAfterTransferFromComponent(var PlanningComponent: Record "Planning Component"; var ProdOrderComp: Record "Prod. Order Component")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PlanningComponent."Component Supply Method" := ProdOrderComp."Component Supply Method";
+ PlanningComponent."Orig. Location Code" := ProdOrderComp."Subc. Original Location Code";
+ PlanningComponent."Orig. Bin Code" := ProdOrderComp."Subc. Orig. Bin Code";
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Planning Component", OnAfterValidateEvent, "Location Code", false, false)]
+ local procedure OnAfterValidateLocationCode(var Rec: Record "Planning Component"; var xRec: Record "Planning Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ if Rec."Location Code" <> xRec."Location Code" then
+ Rec."Orig. Location Code" := xRec."Location Code";
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Planning Component", OnAfterValidateEvent, "Bin Code", false, false)]
+ local procedure OnAfterValidateBinCode(var Rec: Record "Planning Component"; var xRec: Record "Planning Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ if Rec."Bin Code" <> xRec."Bin Code" then
+ Rec."Orig. Bin Code" := xRec."Bin Code";
+ end;
+
+ local procedure HandleRoutingLinkCodeValidation(var PlanningComponent: Record "Planning Component"; var xPlanningComponent: Record "Planning Component")
+ var
+ PlanningRoutingLine: Record "Planning Routing Line";
+ StockkeepingUnit: Record "Stockkeeping Unit";
+ Vendor: Record Vendor;
+ PlanningGetParameters: Codeunit "Planning-Get Parameters";
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+ if PlanningComponent."Component Supply Method" = PlanningComponent."Component Supply Method"::"Transfer to Vendor" then
+ exit;
+
+ if PlanningComponent."Routing Link Code" <> '' then begin
+ PlanningRoutingLine.SetRange("Worksheet Template Name", PlanningComponent."Worksheet Template Name");
+ PlanningRoutingLine.SetRange("Worksheet Batch Name", PlanningComponent."Worksheet Batch Name");
+ PlanningRoutingLine.SetRange("Worksheet Line No.", PlanningComponent."Worksheet Line No.");
+ PlanningRoutingLine.SetRange("Routing Link Code", PlanningComponent."Routing Link Code");
+ PlanningRoutingLine.SetRange(Type, PlanningRoutingLine.Type::"Work Center");
+ PlanningRoutingLine.SetLoadFields("No.");
+ if PlanningRoutingLine.FindFirst() then
+ if SubcontractingManagement.GetSubcontractor(PlanningRoutingLine."No.", Vendor) then
+ SubcontractingManagement.ChangeLocationOnPlanningComponent(PlanningComponent, Vendor."Subc. Location Code", PlanningComponent."Orig. Location Code", PlanningComponent."Orig. Bin Code");
+ end else
+ if xPlanningComponent."Routing Link Code" <> '' then
+ if PlanningComponent."Orig. Location Code" <> '' then begin
+ PlanningComponent.Validate("Location Code", PlanningComponent."Orig. Location Code");
+ PlanningComponent."Orig. Location Code" := '';
+ if PlanningComponent."Orig. Bin Code" <> '' then begin
+ PlanningComponent.Validate("Bin Code", PlanningComponent."Orig. Bin Code");
+ PlanningComponent."Orig. Bin Code" := '';
+ end;
+ end else begin
+ PlanningGetParameters.AtSKU(
+ StockkeepingUnit,
+ PlanningComponent."Item No.",
+ PlanningComponent."Variant Code",
+ PlanningComponent."Location Code");
+ PlanningComponent.Validate(PlanningComponent."Location Code", StockkeepingUnit."Components at Location");
+ end;
+ end;
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningCompExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningCompExt.TableExt.al
new file mode 100644
index 0000000000..176bbf0b92
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningCompExt.TableExt.al
@@ -0,0 +1,59 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Planning;
+using Microsoft.Warehouse.Structure;
+
+tableextension 99001503 "Subc. Planning Comp Ext." extends "Planning Component"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001524; "Component Supply Method"; Enum "Component Supply Method")
+ {
+ Caption = 'Component Supply Method';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the planning component. Vendor-supplied - components are provided by the subcontractor. Consignment at Vendor - components are owned by your company but stored at the subcontractor location. Transfer to Vendor - components are sent to the subcontractor through a transfer order.';
+ trigger OnValidate()
+ var
+ Item: Record Item;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Component Supply Method" = "Component Supply Method"::"Transfer to Vendor" then
+ if "Item No." <> '' then begin
+ Item.Get("Item No.");
+ Item.TestField(Type, Item.Type::Inventory);
+ end;
+ SubcontractingManagement.UpdateComponentSupplyMethodForPlanningComponent(Rec);
+ end;
+ }
+ field(99001525; "Orig. Location Code"; Code[10])
+ {
+ Caption = 'Original Location Code';
+ DataClassification = CustomerContent;
+ TableRelation = Location;
+ }
+ field(99001526; "Orig. Bin Code"; Code[20])
+ {
+ Caption = 'Original Bin Code';
+ DataClassification = CustomerContent;
+ TableRelation = Bin;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningLineMgmtExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningLineMgmtExt.Codeunit.al
new file mode 100644
index 0000000000..7b8f8a706f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcPlanningLineMgmtExt.Codeunit.al
@@ -0,0 +1,91 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Planning;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+
+codeunit 99001518 "Subc. Planning Line Mgmt Ext."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+#if not CLEAN27
+#pragma warning disable AL0432
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Planning Line Management", OnAfterTransferRtngLine, '', false, false)]
+#pragma warning restore AL0432
+#else
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Planning Line Management", OnAfterTransferRtngLine, '', false, false)]
+#endif
+ local procedure OnAfterTransferRtngLine(var ReqLine: Record "Requisition Line"; var RoutingLine: Record "Routing Line"; var PlanningRoutingLine: Record "Planning Routing Line")
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcPriceManagement.ApplySubcontractorPricingToPlanningRouting(ReqLine, RoutingLine, PlanningRoutingLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Planning Line Management", OnCreatePlanningComponentFromProdBOMOnBeforeGetPlanningParameters, '', false, false)]
+ local procedure TransferComponentSupplyMethod_OnCreatePlanningComponentFromProdBOMOnBeforeGetPlanningParameters(var PlanningComponent: Record "Planning Component"; ProductionBOMLine: Record "Production BOM Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PlanningComponent."Component Supply Method" := ProductionBOMLine."Component Supply Method";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Planning Line Management", OnTransferBOMOnBeforeUpdatePlanningComp, '', false, false)]
+ local procedure IgnorePurchaseComponentsFromSubcontracting_OnTransferBOMOnBeforeUpdatePlanningComp(var ProductionBOMLine: Record "Production BOM Line"; var UpdateCondition: Boolean; var IsHandled: Boolean; var ReqQty: Decimal)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProductionBOMLine."Component Supply Method" = "Component Supply Method"::"Vendor-Supplied" then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnAfterFilterLinesWithItemToPlan, '', false, false)]
+ local procedure ProdOrderComponent_OnAfterFilterLinesWithItemToPlan(var ProdOrderComponent: Record "Prod. Order Component"; var Item: Record Item; IncludeFirmPlanned: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderComponent.SetFilter("Component Supply Method", '<>%1', "Component Supply Method"::"Vendor-Supplied");
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnAfterFilterLinesWithItemToPlan, '', false, false)]
+ local procedure TransferLine_OnAfterFilterLinesWithItemToPlan(var Sender: Record "Transfer Line"; var Item: Record Item; IsReceipt: Boolean; IsSupplyForPlanning: Boolean; var TransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine.SetRange("Transfer WIP Item", false);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMLineExt.TableExt.al
new file mode 100644
index 0000000000..2a543c74c3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMLineExt.TableExt.al
@@ -0,0 +1,43 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Manufacturing.ProductionBOM;
+
+tableextension 99001531 "Subc. Prod BOM Line Ext." extends "Production BOM Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001522; "Component Supply Method"; Enum "Component Supply Method")
+ {
+ Caption = 'Component Supply Method';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the production BOM line. Vendor-supplied - components are provided by the subcontractor. Consignment at Vendor - components are owned by your company but stored at the subcontractor location. Transfer to Vendor - components are sent to the subcontractor through a transfer order.';
+ trigger OnValidate()
+ var
+ Item: Record Item;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Component Supply Method" = "Component Supply Method"::"Transfer to Vendor" then
+ if (Type = Type::Item) and ("No." <> '') then begin
+ Item.Get("No.");
+ Item.TestField(Type, Item.Type::Inventory);
+ end;
+ end;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMLines.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMLines.PageExt.al
new file mode 100644
index 0000000000..bca0cf31e7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMLines.PageExt.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.Manufacturing.ProductionBOM;
+
+pageextension 99001510 "Subc. Prod BOM Lines" extends "Production BOM Lines"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Component Supply Method"; Rec."Component Supply Method")
+ {
+ ApplicationArea = Subcontracting;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMVersionLines.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMVersionLines.PageExt.al
new file mode 100644
index 0000000000..ae14805545
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdBOMVersionLines.PageExt.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;
+
+using Microsoft.Manufacturing.ProductionBOM;
+
+pageextension 99001514 "Subc. ProdBOMVersionLines" extends "Production BOM Version Lines"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Component Supply Method"; Rec."Component Supply Method")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the production BOM line.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOFactboxMgmt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOFactboxMgmt.Codeunit.al
new file mode 100644
index 0000000000..d081e2e9b8
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOFactboxMgmt.Codeunit.al
@@ -0,0 +1,295 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Utilities;
+using System.Reflection;
+
+codeunit 99001559 "Subc. ProdO. Factbox Mgmt."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ ///
+ /// Opens the appropriate Production Order page (Released or Finished) for the production order linked to the given variant record.
+ ///
+ /// A record variant of a purchase or transfer document line.
+ procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ var
+ ProductionOrder: Record "Production Order";
+ FinishedProductionOrder: Page "Finished Production Order";
+ ReleasedProductionOrder: Page "Released Production Order";
+ OperationNo: Code[10];
+ ProdOrderNo: Code[20];
+ RoutingNo: Code[20];
+ ProdOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not SetProdOrderInformationByVariant(RecRelatedVariant, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo) then
+ exit;
+ ProductionOrder.SetFilter(Status, '>=%1', ProductionOrder.Status::Released);
+ ProductionOrder.SetRange("No.", ProdOrderNo);
+ if not ProductionOrder.FindFirst() then
+ exit;
+ case ProductionOrder.Status of
+ ProductionOrder.Status::Released:
+ begin
+ ProductionOrder.SetRange(Status, ProductionOrder.Status::Released);
+ ReleasedProductionOrder.SetTableView(ProductionOrder);
+ ReleasedProductionOrder.Editable := false;
+ ReleasedProductionOrder.Run();
+ end;
+ ProductionOrder.Status::Finished:
+ begin
+ ProductionOrder.SetRange(Status, ProductionOrder.Status::Finished);
+ FinishedProductionOrder.SetTableView(ProductionOrder);
+ FinishedProductionOrder.Editable := false;
+ FinishedProductionOrder.Run();
+ end;
+ end;
+ end;
+
+ ///
+ /// Opens the Production Order Routing page for the routing line linked to the given variant record.
+ ///
+ /// A record variant of a purchase or transfer document line.
+ procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProdOrderRouting: Page "Prod. Order Routing";
+ OperationNo: Code[10];
+ ProdOrderNo: Code[20];
+ RoutingNo: Code[20];
+ ProdOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not SetProdOrderInformationByVariant(RecRelatedVariant, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo) then
+ exit;
+ SetFilterProductionOrderRouting(ProdOrderRoutingLine, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo);
+ ProdOrderRoutingLine.FindFirst();
+ ProdOrderRouting.SetTableView(ProdOrderRoutingLine);
+ ProdOrderRouting.Editable := false;
+ ProdOrderRouting.Run();
+ end;
+
+ ///
+ /// Returns the number of production order routing lines linked to the given variant record.
+ ///
+ /// A record variant of a purchase or transfer document line.
+ /// The count of matching production order routing lines, or 0 if no production order is found.
+ procedure CalcNoOfProductionOrderRoutings(RecRelatedVariant: Variant): Integer
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ OperationNo: Code[10];
+ ProdOrderNo: Code[20];
+ RoutingNo: Code[20];
+ ProdOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if not SetProdOrderInformationByVariant(RecRelatedVariant, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo) then
+ exit;
+ SetFilterProductionOrderRouting(ProdOrderRoutingLine, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo);
+ exit(ProdOrderRoutingLine.Count());
+ end;
+
+ local procedure SetFilterProductionOrderRouting(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderNo: Code[20]; ProdOrderLineNo: Integer; RoutingNo: Code[20]; OperationNo: Code[10])
+ begin
+ ProdOrderRoutingLine.SetFilter(Status, '>=%1', ProdOrderRoutingLine.Status::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLineNo);
+ ProdOrderRoutingLine.SetRange("Routing No.", RoutingNo);
+ ProdOrderRoutingLine.SetRange("Operation No.", OperationNo);
+ end;
+
+ ///
+ /// Opens the Production Order Components page filtered to the production order linked to the given variant record.
+ ///
+ /// A record variant of a purchase or transfer document line.
+ procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ PageManagement: Codeunit "Page Management";
+ OperationNo: Code[10];
+ ProdOrderNo: Code[20];
+ RoutingNo: Code[20];
+ ProdOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not SetProdOrderInformationByVariant(RecRelatedVariant, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo) then
+ exit;
+ SetFilterProductionOrderComponents(ProdOrderComponent, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo);
+ PageManagement.PageRun(ProdOrderComponent);
+ end;
+
+ ///
+ /// Returns the number of production order components linked to the given variant record.
+ ///
+ /// A record variant of a purchase or transfer document line.
+ /// The count of matching production order components, or 0 if no production order is found.
+ procedure CalcNoOfProductionOrderComponents(RecRelatedVariant: Variant): Integer
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ OperationNo: Code[10];
+ ProdOrderNo: Code[20];
+ RoutingNo: Code[20];
+ ProdOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if not SetProdOrderInformationByVariant(RecRelatedVariant, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo) then
+ exit(0);
+
+ SetFilterProductionOrderComponents(ProdOrderComponent, ProdOrderNo, ProdOrderLineNo, RoutingNo, OperationNo);
+ exit(ProdOrderComponent.Count());
+ end;
+
+ local procedure SetFilterProductionOrderComponents(var ProdOrderComponent: Record "Prod. Order Component"; ProdOrderNo: Code[20]; ProdOrderLineNo: Integer; RoutingNo: Code[20]; OperationNo: Code[10])
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ ProdOrderRoutingLine.SetLoadFields("Routing Link Code");
+ ProdOrderRoutingLine.SetFilter(Status, '>=%1', ProdOrderRoutingLine.Status::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLineNo);
+ ProdOrderRoutingLine.SetRange("Routing No.", RoutingNo);
+ ProdOrderRoutingLine.SetRange("Operation No.", OperationNo);
+ if ProdOrderRoutingLine.FindFirst() then
+ ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+
+ ProdOrderComponent.SetFilter(Status, '>=%1', ProdOrderComponent.Status::Released);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderComponent.SetRange("Prod. Order Line No.", ProdOrderLineNo);
+ end;
+
+ local procedure SetProdOrderInformationByVariant(RecRelatedVariant: Variant; var ProdOrderNo: Code[20]; var ProdOrderLineNo: Integer; var RoutingNo: Code[20]; var OperationNo: Code[10]): Boolean
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ PurchaseLine: Record "Purchase Line";
+ PurchInvLine: Record "Purch. Inv. Line";
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ TransferLine: Record "Transfer Line";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ TransferShipmentLine: Record "Transfer Shipment Line";
+ DataTypeManagement: Codeunit "Data Type Management";
+ ResultRecordRef: RecordRef;
+ RecId: RecordId;
+ begin
+ if not RecRelatedVariant.IsRecord() then
+ exit(false);
+
+ DataTypeManagement.GetRecordRef(RecRelatedVariant, ResultRecordRef);
+
+ RecId := ResultRecordRef.RecordId();
+
+ if RecId.TableNo() = 0 then
+ exit(false);
+
+ case RecId.TableNo() of
+ Database::"Purchase Line":
+ begin
+ ResultRecordRef.SetTable(PurchaseLine);
+ ProdOrderNo := PurchaseLine."Prod. Order No.";
+ ProdOrderLineNo := PurchaseLine."Prod. Order Line No.";
+ RoutingNo := PurchaseLine."Routing No.";
+ OperationNo := PurchaseLine."Operation No.";
+ end;
+ Database::"Purch. Rcpt. Line":
+ begin
+ ResultRecordRef.SetTable(PurchRcptLine);
+ ProdOrderNo := PurchRcptLine."Prod. Order No.";
+ ProdOrderLineNo := PurchRcptLine."Prod. Order Line No.";
+ RoutingNo := PurchRcptLine."Routing No.";
+ OperationNo := PurchRcptLine."Operation No.";
+ end;
+ Database::"Purch. Inv. Line":
+ begin
+ ResultRecordRef.SetTable(PurchInvLine);
+ ProdOrderNo := PurchInvLine."Prod. Order No.";
+ ProdOrderLineNo := PurchInvLine."Prod. Order Line No.";
+ RoutingNo := PurchInvLine."Routing No.";
+ OperationNo := PurchInvLine."Operation No.";
+ end;
+ Database::"Transfer Line":
+ begin
+ ResultRecordRef.SetTable(TransferLine);
+ ProdOrderNo := TransferLine."Subc. Prod. Order No.";
+ ProdOrderLineNo := TransferLine."Subc. Prod. Order Line No.";
+ RoutingNo := TransferLine."Subc. Routing No.";
+ OperationNo := TransferLine."Subc. Operation No.";
+ end;
+ Database::"Transfer Shipment Line":
+ begin
+ ResultRecordRef.SetTable(TransferShipmentLine);
+ ProdOrderNo := TransferShipmentLine."Subc. Prod. Order No.";
+ ProdOrderLineNo := TransferShipmentLine."Subc. Prod. Order Line No.";
+ RoutingNo := TransferShipmentLine."Subc. Routing No.";
+ OperationNo := TransferShipmentLine."Subc. Operation No.";
+ end;
+ Database::"Transfer Receipt Line":
+ begin
+ ResultRecordRef.SetTable(TransferReceiptLine);
+ ProdOrderNo := TransferReceiptLine."Subc. Prod. Order No.";
+ ProdOrderLineNo := TransferReceiptLine."Subc. Prod. Order Line No.";
+ RoutingNo := TransferReceiptLine."Subc. Routing No.";
+ OperationNo := TransferReceiptLine."Subc. Operation No.";
+ end;
+ Database::"Item Ledger Entry":
+ begin
+ ResultRecordRef.SetTable(ItemLedgerEntry);
+ ProdOrderNo := ItemLedgerEntry."Order No.";
+ ProdOrderLineNo := ItemLedgerEntry."Order Line No.";
+ OperationNo := ItemLedgerEntry."Subc. Operation No.";
+ RoutingNo := GetRoutingNoFromProdOrderRoutingLine(ProdOrderNo, ProdOrderLineNo, OperationNo);
+ end;
+ end;
+ exit((ProdOrderNo <> '') and (ProdOrderLineNo <> 0));
+ end;
+
+ local procedure GetRoutingNoFromProdOrderRoutingLine(ProdOrderNo: Code[20]; ProdOrderLineNo: Integer; OperationNo: Code[10]): Code[20]
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLineNo);
+ ProdOrderRoutingLine.SetRange("Operation No.", OperationNo);
+ if ProdOrderRoutingLine.FindFirst() then
+ exit(ProdOrderRoutingLine."Routing No.");
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrdCompRes.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrdCompRes.Codeunit.al
new file mode 100644
index 0000000000..8d3269e9e2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrdCompRes.Codeunit.al
@@ -0,0 +1,39 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+
+codeunit 99001530 "Subc. Prod. Ord. Comp. Res."
+{
+ EventSubscriberInstance = Manual;
+
+ ///
+ /// When a transfer is created, the storage location in the production component is swapped and thus triggers the verification.\
+ /// The verification attempts to perform an auto tracking if the order tracking policy of the item is not equal to None, but this is not possible.\
+ /// Therefore the verification is overwritten and set to false.
+ ///
+ ///
+ ///
+ ///
+ ///
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Comp.-Reserve", OnVerifyChangeOnBeforeHasError, '', false, false)]
+ local procedure "Prod. Order Comp.-Reserve_OnVerifyChangeOnBeforeHasError"(NewProdOrderComp: Record "Prod. Order Component"; OldProdOrderComp: Record "Prod. Order Component"; var HasError: Boolean; var ShowError: Boolean)
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ HasError := false;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderComp.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderComp.PageExt.al
new file mode 100644
index 0000000000..bcecc24cef
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderComp.PageExt.al
@@ -0,0 +1,38 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+
+pageextension 99001512 "Subc. Prod Order Comp" extends "Prod. Order Components"
+{
+ layout
+ {
+ addafter("Remaining Quantity")
+ {
+ field("Subc. Qty.on TransOrder (Base)"; Rec."Subc. Qty.on TransOrder (Base)")
+ {
+ ApplicationArea = Subcontracting;
+ }
+ field("Subc. Qty. in Transit (Base)"; Rec."Subc. Qty. in Transit (Base)")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ field("Subc. Qty. transf. to Subcontractor"; Rec."Subc. Qty. transf. to Subcontr")
+ {
+ ApplicationArea = Subcontracting;
+ }
+ }
+ addlast(Control1)
+ {
+ field("Component Supply Method"; Rec."Component Supply Method")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the production component.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al
new file mode 100644
index 0000000000..858cbba555
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.Codeunit.al
@@ -0,0 +1,549 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Inventory.Planning;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using System.Utilities;
+
+codeunit 99001524 "Subc. Prod. Order Comp. Ext."
+{
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ ExistingPostedTransferLineQst: Label 'The component has already been assigned to the posted subcontracting transfer order %1.\\Do you want to continue?', Comment = '%1=Transfer Order No';
+ 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';
+ LocationCodeChangeNotAllowedErr: Label 'The component has already been assigned to the subcontracting transfer order %1.\\The location code 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")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ValidateSubcontractingReservationConstraints(ProdOrderComponent);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnAfterValidateEvent, "Bin Code", false, false)]
+ local procedure OnAfterValidateBinCode(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ SetOriginalBinCode(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnAfterValidateEvent, "Location Code", false, false)]
+ local procedure OnAfterValidateLocationCode(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ SetOriginalLocationCode(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnAfterValidateEvent, "Routing Link Code", false, false)]
+ local procedure OnAfterValidateRoutingLinkCode(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ HandleRoutingLinkCodeValidation(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Component", OnBeforeValidateEvent, "Location Code", false, false)]
+ local procedure OnBeforeValidateLocationCode(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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)]
+ local procedure OnBeforeValidateQuantityPer(var Rec: Record "Prod. Order Component"; var xRec: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+ var
+ TransferShipmentLine: Record "Transfer Shipment Line";
+ ConfirmManagement: Codeunit "Confirm Management";
+ begin
+ if ProdOrderComponent."Component Supply Method" <> "Component Supply Method"::"Transfer to Vendor" then
+ exit;
+
+ TransferShipmentLine.SetRange("Subc. Prod. Order No.", ProdOrderComponent."Prod. Order No.");
+ TransferShipmentLine.SetRange("Subc. Prod. Order Line No.", ProdOrderComponent."Prod. Order Line No.");
+ TransferShipmentLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComponent."Line No.");
+ TransferShipmentLine.SetRange("Item No.", ProdOrderComponent."Item No.");
+ TransferShipmentLine.SetLoadFields(SystemId);
+ if not TransferShipmentLine.IsEmpty() then begin
+ TransferShipmentLine.FindFirst();
+ if not ConfirmManagement.GetResponse(StrSubstNo(ExistingPostedTransferLineQst, TransferShipmentLine."Document No.")) then
+ Error('');
+ end;
+ end;
+
+ local procedure CheckExistingReservationOnTransferLine(ProdOrderComponent: Record "Prod. Order Component"; TransferLine: Record "Transfer Line") Result: Boolean
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ ReservationEntry.SetCurrentKey("Source Type", "Source Subtype", "Source ID", "Source Ref. No.", "Reservation Status");
+ ReservationEntry.SetRange("Source Type", Database::"Transfer Line");
+ ReservationEntry.SetRange("Source ID", TransferLine."Document No.");
+ ReservationEntry.SetRange("Source Ref. No.", TransferLine."Line No.");
+ ReservationEntry.SetRange("Item No.", ProdOrderComponent."Item No.");
+ ReservationEntry.SetRange("Variant Code", ProdOrderComponent."Variant Code");
+
+ Result := not ReservationEntry.IsEmpty();
+ exit(Result);
+ end;
+
+ local procedure CheckExistingSubcontractingPurchaseOrder(ProdOrderComponent: Record "Prod. Order Component"): Boolean
+ var
+ PurchaseLine: Record "Purchase Line";
+ TempPurchaseLine: Record "Purchase Line" temporary;
+ begin
+ if ProdOrderComponent."Component Supply Method" <> ProdOrderComponent."Component Supply Method"::"Vendor-Supplied" then
+ exit;
+
+ PurchaseLine.SetCurrentKey("Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderComponent."Prod. Order No.");
+ PurchaseLine.SetRange("Prod. Order Line No.", ProdOrderComponent."Prod. Order Line No.");
+ PurchaseLine.SetLoadFields("Document Type", "Document No.", "Line No.");
+ if PurchaseLine.FindSet() then
+ repeat
+ TempPurchaseLine.Init();
+ TempPurchaseLine."Document Type" := PurchaseLine."Document Type";
+ TempPurchaseLine."Document No." := PurchaseLine."Document No.";
+ TempPurchaseLine."Line No." := PurchaseLine."Line No.";
+ TempPurchaseLine.Insert();
+ until PurchaseLine.Next() = 0;
+
+ if TempPurchaseLine.FindSet() then
+ repeat
+ PurchaseLine.Reset();
+ PurchaseLine.SetRange("Document Type", TempPurchaseLine."Document Type");
+ PurchaseLine.SetRange("Document No.", TempPurchaseLine."Document No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("No.", ProdOrderComponent."Item No.");
+ PurchaseLine.SetRange("Prod. Order No.", '');
+ PurchaseLine.SetLoadFields("Document No.");
+ if PurchaseLine.FindFirst() then
+ Error(ExistingPurchLineErr, PurchaseLine."Document No.");
+ until TempPurchaseLine.Next() = 0;
+ end;
+
+ local procedure CheckExistingSubcontractingTransferOrder(var ProdOrderComponent: Record "Prod. Order Component"; var xProdOrderComponent: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+ if CurrFieldNo = 0 then
+ exit;
+
+ if ProdOrderComponent."Location Code" = xProdOrderComponent."Location Code" then
+ exit;
+
+ if ProdOrderComponent."Component Supply Method" <> "Component Supply Method"::"Transfer to Vendor" then
+ exit;
+
+ TransferLine.SetCurrentKey("Subc. Prod. Order No.", "Subc. Routing No.", "Subc. Routing Reference No.", "Subc. Operation No.", "Subc. Purch. Order 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.");
+ TransferLine.SetRange("Item No.", ProdOrderComponent."Item No.");
+ TransferLine.SetLoadFields(SystemId);
+ if TransferLine.FindFirst() then
+ Error(LocationCodeChangeNotAllowedErr, TransferLine."Document No.");
+ end;
+
+ local procedure CheckIfProdOrderCompIsInSubcontractingOrder(ProdOrderComponent: Record "Prod. Order Component") Result: Boolean
+ var
+ PurchOrderNo: Code[20];
+ PurchOrderLineNo: Integer;
+ begin
+ GetPurchOrderFromProdOrderComp(ProdOrderComponent, PurchOrderNo, PurchOrderLineNo);
+
+ Result := PurchOrderNo <> '';
+ exit(Result);
+ end;
+
+ local procedure CheckIfTransferLineOnProdOrderCompLineExists(ProdOrderComponent: Record "Prod. Order Component"; var TransferLine: Record "Transfer Line"): Boolean
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ ProdOrderLine.SetLoadFields("Routing Reference No.");
+ if not ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.") then
+ exit(false);
+
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ TransferLine.SetCurrentKey("Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Routing Reference No.", "Subc. Routing No.", "Subc. Operation No.");
+ TransferLine.SetRange("Subc. Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ TransferLine.SetRange("Subc. Prod. Order Line No.", ProdOrderLine."Line No.");
+ TransferLine.SetRange("Subc. Routing Reference No.", ProdOrderLine."Routing Reference No.");
+ TransferLine.SetRange("Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ TransferLine.SetRange("Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ TransferLine.SetRange("Item No.", ProdOrderComponent."Item No.");
+ TransferLine.SetRange("Variant Code", ProdOrderComponent."Variant Code");
+ if TransferLine.IsEmpty() then
+ exit(false);
+
+ TransferLine.SetLoadFields(SystemId);
+ TransferLine.FindFirst();
+ exit(true);
+ end;
+
+ local procedure GetProdOrderRtngLineFromProdOrderComp(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ ProdOrderLine.SetLoadFields("Routing Reference No.");
+ if not ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.") then
+ exit;
+
+ 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");
+ if ProdOrderRoutingLine.IsEmpty() then
+ exit;
+
+ ProdOrderRoutingLine.SetLoadFields(SystemId);
+ ProdOrderRoutingLine.FindFirst();
+ end;
+
+ local procedure GetPurchOrderFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"; var PurchOrderNo: Code[20]; var PurchOrderLineNo: Integer)
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ 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.");
+ PurchaseLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ if PurchaseLine.IsEmpty() then
+ exit;
+
+ PurchaseLine.FindFirst();
+ PurchOrderNo := PurchaseLine."Document No.";
+ PurchOrderLineNo := PurchaseLine."Line No.";
+ end;
+
+ local procedure ValidateSubcontractingReservationConstraints(var ProdOrderComponent: Record "Prod. Order Component")
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+ if not CheckIfProdOrderCompIsInSubcontractingOrder(ProdOrderComponent) then
+ exit;
+
+ if not CheckIfTransferLineOnProdOrderCompLineExists(ProdOrderComponent, TransferLine) then
+ exit;
+
+ if not CheckExistingReservationOnTransferLine(ProdOrderComponent, TransferLine) then
+ exit;
+
+ Error(ExistingTransferLineErr, TransferLine."Document No.");
+ end;
+
+ local procedure HandleRoutingLinkCodeValidation(var ProdOrderComponent: Record "Prod. Order Component"; var xProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ StockkeepingUnit: Record "Stockkeeping Unit";
+ Vendor: Record Vendor;
+ PlanningGetParameters: Codeunit "Planning-Get Parameters";
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+ if ProdOrderComponent."Component Supply Method" = ProdOrderComponent."Component Supply Method"::"Transfer to Vendor" then
+ exit;
+
+ ProdOrderLine.SetLoadFields("Routing No.", "Routing Reference No.", "Item No.", "Variant Code", "Location Code");
+ ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.");
+ if ProdOrderComponent."Routing Link Code" <> '' then begin
+ ProdOrderRoutingLine.SetRange(Status, ProdOrderComponent.Status);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderComponent."Prod. Order No.");
+ ProdOrderRoutingLine.SetRange("Routing No.", ProdOrderLine."Routing No.");
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLine."Routing Reference No.");
+ ProdOrderRoutingLine.SetRange("Routing Link Code", ProdOrderComponent."Routing Link Code");
+ ProdOrderRoutingLine.SetLoadFields("Starting Date", "Starting Time", Type, "No.");
+ if ProdOrderRoutingLine.FindFirst() then begin
+ ProdOrderComponent."Due Date" := ProdOrderRoutingLine."Starting Date";
+ ProdOrderComponent."Due Time" := ProdOrderRoutingLine."Starting Time";
+ if (ProdOrderRoutingLine.Type = ProdOrderRoutingLine.Type::"Work Center") then
+ if SubcontractingManagement.GetSubcontractor(ProdOrderRoutingLine."No.", Vendor) then
+ SubcontractingManagement.ChangeLocationOnProdOrderComponent(ProdOrderComponent, Vendor."Subc. Location Code", ProdOrderComponent."Subc. Original Location Code", ProdOrderComponent."Subc. Orig. Bin Code");
+ end;
+ 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");
+ ProdOrderComponent."Subc. Original Location Code" := '';
+ if ProdOrderComponent."Subc. Orig. Bin Code" <> '' then begin
+ ProdOrderComponent.Validate("Bin Code", ProdOrderComponent."Subc. Orig. Bin Code");
+ ProdOrderComponent."Subc. Orig. Bin Code" := '';
+ end;
+ end else begin
+ PlanningGetParameters.AtSKU(
+ StockkeepingUnit,
+ ProdOrderLine."Item No.",
+ ProdOrderLine."Variant Code",
+ ProdOrderLine."Location Code");
+ ProdOrderComponent.Validate("Location Code", StockkeepingUnit."Components at Location");
+ end;
+ end;
+
+ local procedure SetOriginalBinCode(var ProdOrderComponent: Record "Prod. Order Component"; var xProdOrderComponent: Record "Prod. Order Component")
+ begin
+ if ProdOrderComponent."Bin Code" <> xProdOrderComponent."Bin Code" then
+ ProdOrderComponent."Subc. Orig. Bin Code" := xProdOrderComponent."Bin Code";
+ end;
+
+ local procedure SetOriginalLocationCode(var ProdOrderComponent: Record "Prod. Order Component"; var xProdOrderComponent: Record "Prod. Order Component")
+ begin
+ if (ProdOrderComponent."Location Code" <> xProdOrderComponent."Location Code") then
+ ProdOrderComponent."Subc. Original Location Code" := xProdOrderComponent."Location Code";
+ end;
+
+ local procedure CheckExistingDocumentsForSubcontracting(var ProdOrderComponent: Record "Prod. Order Component"; var xProdOrderComponent: Record "Prod. Order Component"; CurrFieldNo: Integer)
+ begin
+ if CurrFieldNo = 0 then
+ exit;
+
+ if ProdOrderComponent."Quantity per" <> xProdOrderComponent."Quantity per" then begin
+ CheckExistingSubcontractingTransferOrder(ProdOrderComponent, xProdOrderComponent, CurrFieldNo);
+ CheckExistingPostedSubcontractingTransferOrder(ProdOrderComponent);
+ 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/Manufacturing/SubcProdOrderCompExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.TableExt.al
new file mode 100644
index 0000000000..ce85c33c98
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompExt.TableExt.al
@@ -0,0 +1,144 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Warehouse.Structure;
+
+tableextension 99001502 "Subc. Prod Order Comp Ext." extends "Prod. Order Component"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001522; "Component Supply Method"; Enum "Component Supply Method")
+ {
+ Caption = 'Component Supply Method';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the production order component. Vendor-supplied - components are provided by the subcontractor. Consignment at Vendor - components are owned by your company but stored at the subcontractor location. Transfer to Vendor - components are sent to the subcontractor through a transfer order.';
+ trigger OnValidate()
+ var
+ Item: Record Item;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Component Supply Method" = "Component Supply Method"::"Transfer to Vendor" then
+ if "Item No." <> '' then begin
+ Item.Get("Item No.");
+ Item.TestField(Type, Item.Type::Inventory);
+ end;
+ SubcontractingManagement.UpdateComponentSupplyMethodForProdOrderComponent(Rec);
+ end;
+ }
+ field(99001523; "Subc. Original Location Code"; Code[10])
+ {
+ Caption = 'Original Location Code';
+ DataClassification = CustomerContent;
+ TableRelation = Location;
+ }
+ field(99001524; "Subc. Qty. transf. to Subcontr"; Decimal)
+ {
+ AutoFormatType = 0;
+ CalcFormula = sum("Item Ledger Entry".Quantity where("Entry Type" = const(Transfer),
+ "Subc. Prod. Order No." = field("Prod. Order No."),
+ "Subc. Prod. Order Line No." = field("Prod. Order Line No."),
+ "Prod. Order Comp. Line No." = field("Line No."),
+ "Subc. Purch. Order No." = field("Subc. Purchase Order Filter"),
+ "Location Code" = field("Location Code"))
+
+ );
+ Caption = 'Qty. transf. to Subcontractor';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the item amount transferred to the subcontractor.';
+ }
+ field(99001525; "Subc. Qty. in Transit (Base)"; Decimal)
+ {
+ AutoFormatType = 0;
+ CalcFormula = sum("Transfer Line"."Qty. in Transit (Base)" where("Subc. Prod. Order No." = field("Prod. Order No."),
+ "Subc. Prod. Order Line No." = field("Prod. Order Line No."),
+ "Subc. Prod. Ord. Comp Line No." = field("Line No."),
+ "Subc. Purch. Order No." = field("Subc. Purchase Order Filter"),
+ "Subc. Return Order" = const(false),
+ "Derived From Line No." = const(0)));
+ Caption = 'Qty. in Transit (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the items that are in transit.';
+ }
+ field(99001526; "Subc. Qty.on TransOrder (Base)"; Decimal)
+ {
+ AutoFormatType = 0;
+ CalcFormula = sum("Transfer Line"."Outstanding Qty. (Base)" where("Subc. Prod. Order No." = field("Prod. Order No."),
+ "Subc. Prod. Order Line No." = field("Prod. Order Line No."),
+ "Subc. Prod. Ord. Comp Line No." = field("Line No."),
+ "Subc. Purch. Order No." = field("Subc. Purchase Order Filter"),
+ "Subc. Return Order" = const(false),
+ "Derived From Line No." = const(0)));
+ Caption = 'Qty. on Transfer Order (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the item amount that is on the transfer order.';
+ }
+ field(99001527; "Subc. Purchase Order Filter"; Code[20])
+ {
+ Caption = 'Subc. Purchase Order Filter';
+ FieldClass = FlowFilter;
+ TableRelation = "Purchase Header"."No." where("Document Type" = const(Order),
+ "Subc. Order" = const(true));
+ }
+ field(99001528; "Subc. Orig. Bin Code"; Code[20])
+ {
+ Caption = 'Original Bin Code';
+ DataClassification = CustomerContent;
+ TableRelation = Bin;
+ }
+ field(99001530; "RetQtyInTransit (Base)"; Decimal)
+ {
+ AutoFormatType = 0;
+ CalcFormula = sum("Transfer Line"."Qty. in Transit (Base)" where("Subc. Prod. Order No." = field("Prod. Order No."),
+ "Subc. Prod. Order Line No." = field("Prod. Order Line No."),
+ "Subc. Prod. Ord. Comp Line No." = field("Line No."),
+ "Subc. Purch. Order No." = field("Subc. Purchase Order Filter"),
+ "Subc. Return Order" = const(true),
+ "Derived From Line No." = const(0)));
+ Caption = 'Return Qty. in Transit (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ }
+ field(99001531; "RetQtyOnTransOrder (Base)"; Decimal)
+ {
+ AutoFormatType = 0;
+ CalcFormula = sum("Transfer Line"."Outstanding Qty. (Base)" where("Subc. Prod. Order No." = field("Prod. Order No."),
+ "Subc. Prod. Order Line No." = field("Prod. Order Line No."),
+ "Subc. Prod. Ord. Comp Line No." = field("Line No."),
+ "Subc. Purch. Order No." = field("Subc. Purchase Order Filter"),
+ "Subc. Return Order" = const(true),
+ "Derived From Line No." = const(0)));
+ Caption = 'Return Qty. on Transfer Order (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompLine.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompLine.PageExt.al
new file mode 100644
index 0000000000..29df1f0de7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderCompLine.PageExt.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;
+
+using Microsoft.Manufacturing.Document;
+
+pageextension 99001513 "Subc. ProdOrderCompLine" extends "Prod. Order Comp. Line List"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Component Supply Method"; Rec."Component Supply Method")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the production component.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderComponents.Page.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderComponents.Page.al
new file mode 100644
index 0000000000..f7482c07c6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderComponents.Page.al
@@ -0,0 +1,183 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+
+page 99001503 "Subc. Prod. Order Components"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Prod. Order Comp. Line List';
+ Editable = false;
+ PageType = List;
+ SourceTable = "Prod. Order Component";
+ UsageCategory = None;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Components)
+ {
+ ShowCaption = false;
+ field("Component Supply Method"; Rec."Component Supply Method")
+ {
+ ToolTip = 'Specifies how components are supplied to the subcontractor for the production order component.';
+ }
+ field("Item No."; Rec."Item No.")
+ {
+ ToolTip = 'Specifies the number of the item that is a component in the production order component list.';
+ }
+ field("Variant Code"; Rec."Variant Code")
+ {
+ ToolTip = 'Specifies the variant of the item on the line.';
+ Visible = false;
+ }
+ field(Description; Rec.Description)
+ {
+ ToolTip = 'Specifies a description of the item on the line.';
+ }
+ field("Description 2"; Rec."Description 2")
+ {
+ ToolTip = 'Specifies a second description of the item on the line.';
+ Visible = false;
+ }
+ field("Location Code"; Rec."Location Code")
+ {
+ ToolTip = 'Specifies the location where the component is stored. Copies the location code from the corresponding field on the production order line.';
+ Visible = false;
+ }
+ field("Quantity per"; Rec."Quantity per")
+ {
+ AutoFormatType = 0;
+ ToolTip = 'Specifies how many units of the component are required to produce the parent item.';
+ }
+ field("Expected Quantity"; Rec."Expected Quantity")
+ {
+ AutoFormatType = 0;
+ ToolTip = 'Specifies the quantity of the component expected to be consumed during the production of the quantity on this line.';
+ }
+ field("Remaining Quantity"; Rec."Remaining Quantity")
+ {
+ AutoFormatType = 0;
+ ToolTip = 'Specifies the difference between the finished and planned quantities, or zero if the finished quantity is greater than the remaining quantity.';
+ }
+ field("OutQtyOnPurch Order (Base)"; SubcCompFactboxMgmt.GetPurchOrderOutstandingQtyBaseFromProdOrderComp(Rec))
+ {
+ AutoFormatType = 0;
+ Caption = 'Outstanding Qty (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ ToolTip = 'Specifies the outstanding item amount that is on the subcontracting order.';
+ trigger OnDrillDown()
+ begin
+ SubcCompFactboxMgmt.ShowPurchOrderOutstandingQtyBaseFromProdOrderComp(Rec);
+ end;
+ }
+ field("ReceivedQtyOnPurch Order (Base)"; SubcCompFactboxMgmt.GetPurchOrderQtyReceivedBaseFromProdOrderComp(Rec))
+ {
+ AutoFormatType = 0;
+ Caption = 'Qty. received (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ ToolTip = 'Specifies the received item amount that is on the subcontracting order.';
+ trigger OnDrillDown()
+ begin
+ SubcCompFactboxMgmt.ShowPurchOrderQtyReceivedBaseFromProdOrderComp(Rec);
+ end;
+ }
+ field("Subc. Qty.on Transfer Order (Base)"; Rec."Subc. Qty.on TransOrder (Base)")
+ {
+ ToolTip = 'Specifies the item amount that is on the transfer order.';
+ }
+ field("RetQtyOnTransOrder (Base)"; Rec."RetQtyOnTransOrder (Base)")
+ {
+ ToolTip = 'Specifies the item amount that is on the transfer order to be returned.';
+ }
+ field("Subc.Qty. in Transit (Base)"; Rec."Subc. Qty. in Transit (Base)")
+ {
+ ToolTip = 'Specifies the items that are in transit.';
+ Visible = false;
+ }
+ field("RetQtyInTransit (Base)"; Rec."RetQtyInTransit (Base)")
+ {
+ ToolTip = 'Specifies the items that are in transit for return.';
+ Visible = false;
+ }
+ field("Qty. transf. to Subcontractor"; Rec."Subc. Qty. transf. to Subcontr")
+ {
+ ToolTip = 'Specifies the item amount transferred to the subcontractor.';
+ }
+ field(ConsumedQty; SubcCompFactboxMgmt.GetConsumptionQtyFromProdOrderComponent(Rec))
+ {
+ AutoFormatType = 0;
+ Caption = 'Consumed';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ ToolTip = 'Specifies the consumed Quantity from assigned Components.';
+ trigger OnDrillDown()
+ begin
+ SubcCompFactboxMgmt.ShowConsumptionQtyFromProdOrderComponent(Rec);
+ end;
+ }
+ field("Due Date"; Rec."Due Date")
+ {
+ ToolTip = 'Specifies the date when the produced item must be available. The date is copied from the header of the production order.';
+ }
+ field("Unit Cost"; Rec."Unit Cost")
+ {
+ AutoFormatExpression = '';
+ AutoFormatType = 2;
+ ToolTip = 'Specifies the cost of one unit of the item or resource on the line.';
+ Visible = false;
+ }
+ field("Cost Amount"; Rec."Cost Amount")
+ {
+ AutoFormatExpression = '';
+ AutoFormatType = 2;
+ ToolTip = 'Specifies the total cost on the line by multiplying the unit cost by the quantity.';
+ Visible = false;
+ }
+ }
+ }
+ area(FactBoxes)
+ {
+ systempart(LinksFactbox; Links)
+ {
+ Visible = false;
+ }
+ systempart(NotesFactbox; Notes)
+ {
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ area(Navigation)
+ {
+ group("Line")
+ {
+ Caption = 'Line';
+ Image = Line;
+ action("Item Tracking Lines")
+ {
+ Caption = 'Item Tracking Lines';
+ Image = ItemTrackingLines;
+ ShortcutKey = 'Shift+Ctrl+I';
+ ToolTip = 'View or edit serial numbers and lot numbers that are assigned to the item on the document or journal line.';
+
+ trigger OnAction()
+ begin
+ Rec.OpenItemTrackingLines();
+ end;
+ }
+ }
+ }
+ }
+
+ var
+ SubcCompFactboxMgmt: Codeunit "Subc. Comp. Factbox Mgmt.";
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtng.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtng.PageExt.al
new file mode 100644
index 0000000000..311eaf6d92
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtng.PageExt.al
@@ -0,0 +1,229 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+pageextension 99001503 "Subc. Prod. Order Rtng." extends "Prod. Order Routing"
+{
+ layout
+ {
+ modify(Type)
+ {
+ trigger OnAfterValidate()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+ }
+ modify("No.")
+ {
+ trigger OnAfterValidate()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+ }
+ addafter(Description)
+ {
+ field(Subcontracting; Rec.Subcontracting)
+ {
+ ApplicationArea = Subcontracting;
+ }
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = TransferWIPItemEnabled;
+ }
+ field("Transfer Description"; Rec."Transfer Description")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = Rec."Transfer WIP Item";
+ }
+ field("Transfer Description 2"; Rec."Transfer Description 2")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = Rec."Transfer WIP Item";
+ Visible = false;
+ }
+ field("WIP Qty. (Base) at Subc."; Rec."WIP Qty. (Base) at Subc.")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ field("WIP Qty. (Base) in Transit"; Rec."WIP Qty. (Base) in Transit")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ addbefore(Control1900383207)
+ {
+ part("Subc. Routing Info Factbox"; "Subc. Routing Info Factbox")
+ {
+ ApplicationArea = Subcontracting;
+ SubPageLink = "Prod. Order No." = field("Prod. Order No."), "Routing No." = field("Routing No."), "Routing Reference No." = field("Routing Reference No."), "Operation No." = field("Operation No.");
+ }
+ }
+ }
+ actions
+ {
+ addafter("Allocated Capacity")
+ {
+ action("Subcontracting Purchase Lines")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Purchase Order Lines';
+ Image = SubcontractingWorksheet;
+ Enabled = SubcontractingActionsEnabled;
+ RunObject = page "Purchase Lines";
+ RunPageLink = "Document Type" = const(Order), "Prod. Order No." = field("Prod. Order No."), "Routing No." = field("Routing No."), "Routing Reference No." = field("Routing Reference No."), "Operation No." = field("Operation No.");
+ ToolTip = 'Show purchase order lines for subcontracting.';
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ Enabled = SubcontractingActionsEnabled;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Prod. Order Status" = field(Status),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing Reference No." = field("Routing Reference No."),
+ "Routing No." = field("Routing No."),
+ "Operation No." = field("Operation No.");
+ ToolTip = 'View the Subcontracting WIP Entries for this routing line.';
+ }
+ }
+ addlast("F&unctions")
+ {
+ action(CreateSubcontracting)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Subcontracting Order';
+ Image = CreateDocument;
+ Enabled = SubcontractingActionsEnabled;
+ Visible = CreateSubcontractingVisible;
+ ToolTip = 'Create Purchase Orders for Subcontracting directly from the Production Routing Line.';
+ trigger OnAction()
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ CurrPage.SetSelectionFilter(ProdOrderRoutingLine);
+ CreateSubcontractingOrders(ProdOrderRoutingLine);
+ end;
+ }
+ action("WIP Adjustment")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'WIP Adjustment';
+ Image = AdjustEntries;
+ Enabled = SubcontractingActionsEnabled;
+ ToolTip = 'Manually adjust the WIP quantity for the selected prod. order routing line.';
+
+ trigger OnAction()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ begin
+ WIPLedgerEntry.SetProductionOrderRoutingFilter(Rec, true);
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.SetDocumentNo(Rec."Prod. Order No.");
+ WIPAdjustmentPage.RunModal();
+ end;
+ }
+ }
+ }
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ TransferWIPItemEnabled: Boolean;
+ SubcontractingActionsEnabled: Boolean;
+ CreateSubcontractingVisible: Boolean;
+ NoPurchOrderCreatedMsg: Label 'No subcontracting order was created for the selected operations in production order %1. Please check whether the operation or operations have already been completed.', Comment = '%1=Production Order No.';
+
+ trigger OnOpenPage()
+ var
+ StatusFilter: Text;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ StatusFilter := Rec.GetFilter(Rec.Status);
+ if StatusFilter.Contains(Format("Production Order Status"::Released)) then
+ CreateSubcontractingVisible := true
+ else
+ CreateSubcontractingVisible := false;
+ end;
+
+ trigger OnAfterGetRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ SubcontractingActionsEnabled := Rec.Subcontracting and (Rec.Status = "Production Order Status"::Released);
+ end;
+
+ local procedure UpdateWIPEnabled()
+ begin
+ Rec.Calcfields(Subcontracting);
+ TransferWIPItemEnabled := Rec.Subcontracting;
+ end;
+
+ internal procedure CreateSubcontractingOrders(var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ PurchaseLine: Record "Purchase Line";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ NoOfCreatedPurchOrder: Integer;
+ begin
+ NoOfCreatedPurchOrder := SubcPurchaseOrderCreator.CreateSubcontractingOrdersForRoutingLineSelection(ProdOrderRoutingLine);
+
+ if NoOfCreatedPurchOrder = 0 then begin
+ PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ PurchaseLine.SetRange("Prod. Order No.", Rec."Prod. Order No.");
+ PurchaseLine.SetRange("Routing No.", Rec."Routing No.");
+ PurchaseLine.SetRange("Routing Reference No.", Rec."Routing Reference No.");
+ PurchaseLine.SetRange("Operation No.", Rec."Operation No.");
+ if PurchaseLine.IsEmpty() then
+ Message(NoPurchOrderCreatedMsg, ProdOrderRoutingLine."Prod. Order No.")
+ end else begin
+ if NoOfCreatedPurchOrder = 1 then begin
+ SubcPurchaseOrderCreator.ClearOperationNoForCreatedPurchaseOrder();
+ SubcPurchaseOrderCreator.SetOperationNoForCreatedPurchaseOrder(Rec."Operation No.");
+ SubcPurchaseOrderCreator.ClearRoutingReferenceNoForCreatedPurchaseOrder();
+ SubcPurchaseOrderCreator.SetRoutingReferenceNoForCreatedPurchaseOrder(Rec."Routing Reference No.");
+ end;
+ SubcPurchaseOrderCreator.ShowCreatedPurchaseOrder(Rec."Prod. Order No.", NoOfCreatedPurchOrder);
+ end;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtngExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtngExt.Codeunit.al
new file mode 100644
index 0000000000..a0fd4dd15d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtngExt.Codeunit.al
@@ -0,0 +1,317 @@
+// ------------------------------------------------------------------------------------------------
+// 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.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
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ if not RunTrigger then
+ exit;
+
+ HandleSubcontractingAfterRoutingLineDelete(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnBeforeValidateEvent, "No.", false, false)]
+ local procedure OnBeforeValidateNo(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer)
+ var
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ HandleRoutingLinkCodeValidation(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnAfterValidateEvent, "Standard Task Code", false, false)]
+ local procedure OnAfterValidateStandardTaskCode(var Rec: Record "Prod. Order Routing Line"; var xRec: Record "Prod. Order Routing Line"; CurrFieldNo: Integer)
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+ SubcPriceManagement.GetSubcPriceList(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnAfterWorkCenterTransferFields, '', false, false)]
+ local procedure OnAfterWorkCenterTransferFields(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; WorkCenter: Record "Work Center")
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcPriceManagement.GetSubcPriceList(ProdOrderRoutingLine);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Prod. Order Routing Line", OnAfterCopyFromRoutingLine, '', false, false)]
+ local procedure OnAfterCopyFromRoutingLine(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; RoutingLine: Record "Routing Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderRoutingLine."Transfer WIP Item" := RoutingLine."Transfer WIP Item";
+ ProdOrderRoutingLine."Transfer Description" := RoutingLine."Transfer Description";
+ ProdOrderRoutingLine."Transfer Description 2" := RoutingLine."Transfer Description 2";
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderRoutingLine.CheckForSubcontractingPurchaseLineTypeMismatch();
+ end;
+
+ local procedure HandleRoutingLinkCodeValidation(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var xProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+ if ProdOrderRoutingLine."Routing Link Code" <> xProdOrderRoutingLine."Routing Link Code" then
+ if xProdOrderRoutingLine."Routing Link Code" <> '' then begin
+ SubcontractingManagement.DelLocationLinkedComponents(xProdOrderRoutingLine, true);
+ if ProdOrderRoutingLine."Routing Link Code" <> '' then
+ SubcontractingManagement.UpdLinkedComponents(ProdOrderRoutingLine, false);
+ end else
+ if ProdOrderRoutingLine."Routing Link Code" <> '' then
+ SubcontractingManagement.UpdLinkedComponents(ProdOrderRoutingLine, true);
+ end;
+
+ local procedure HandleSubcontractingAfterRoutingLineDelete(var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ WorkCenter: Record "Work Center";
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+ if ProdOrderRoutingLine.Status = ProdOrderRoutingLine.Status::Released then
+ if ProdOrderRoutingLine.Type = ProdOrderRoutingLine.Type::"Work Center" then begin
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ if WorkCenter.Get(ProdOrderRoutingLine."No.") then
+ if (ProdOrderRoutingLine."Routing Link Code" <> '') and (WorkCenter."Subcontractor No." <> '') then
+ 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;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al
new file mode 100644
index 0000000000..138ca54f36
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProdOrderRtngLineExt.TableExt.al
@@ -0,0 +1,277 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Manufacturing.Capacity;
+
+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"
+{
+ fields
+ {
+ modify(Type)
+ {
+ trigger OnAfterValidate()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Type = xRec.Type then
+ exit;
+
+ if Type <> "Capacity Type"::"Work Center" then
+ "Transfer WIP Item" := false;
+ end;
+ }
+ modify("No.")
+ {
+ trigger OnAfterValidate()
+ var
+ WorkCenter: Record "Work Center";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "No." = xRec."No." then
+ exit;
+ if Type <> "Capacity Type"::"Work Center" then begin
+ "Transfer WIP Item" := false;
+ exit;
+ end;
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ WorkCenter.Get("No.");
+ if WorkCenter."Subcontractor No." = '' then
+ "Transfer WIP Item" := false;
+ end;
+ }
+ field(99001550; "Vendor No. Subc. Price"; Code[20])
+ {
+ AllowInCustomizations = AsReadOnly;
+ Caption = 'Vendor No. Subcontracting Prices';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = Vendor;
+ }
+ field(99001551; Subcontracting; Boolean)
+ {
+ AllowInCustomizations = AsReadOnly;
+ CalcFormula = exist("Work Center" where("No." = field("Work Center No."),
+ "Subcontractor No." = filter(<> '')));
+ Caption = 'Subcontracting';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies whether the Work Center Group is set up with a Vendor for Subcontracting.';
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ AllowInCustomizations = AsReadWrite;
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies whether the production order parent item (WIP item) is transferred to the subcontractor for this operation.';
+
+ trigger OnValidate()
+ begin
+ if "Transfer WIP Item" then begin
+ CalcFields(Subcontracting);
+ TestField(Subcontracting, true);
+ end;
+ end;
+ }
+ field(99001561; "Transfer Description"; Text[100])
+ {
+ AllowInCustomizations = AsReadWrite;
+ Caption = 'Transfer Description';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the operation-specific description used on transfer orders for the semi-finished item as it is shipped to the subcontracting location. If empty, the standard description is used.';
+ }
+ field(99001562; "Transfer Description 2"; Text[50])
+ {
+ AllowInCustomizations = AsReadWrite;
+ Caption = 'Transfer Description 2';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies an additional operation-specific description line used on transfer orders for the semi-finished item as it is shipped to the subcontracting location.';
+ }
+#pragma warning disable AA0232
+ field(99001563; "WIP Qty. (Base) at Subc."; Decimal)
+#pragma warning restore AA0232
+ {
+ AllowInCustomizations = AsReadOnly;
+ AutoFormatType = 0;
+ CalcFormula = sum("Subcontractor WIP Ledger Entry"."Quantity (Base)" where("Prod. Order Status" = field(Status),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Prod. Order Line No." = field("Prod. Order Line Filter"),
+ "Routing Reference No." = field("Routing Reference No."),
+ "Routing No." = field("Routing No."),
+ "Operation No." = field("Operation No."),
+ "Location Code" = field("WIP Location Filter"),
+ "In Transit" = const(false)));
+ Caption = 'WIP Qty. (Base) at Subcontractor';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the total work-in-progress quantity (base) of the production order parent item currently held at the subcontractor location for this operation, as tracked by Subcontracting WIP Entries.';
+ }
+ field(99001564; "WIP Qty. (Base) in Transit"; Decimal)
+ {
+ AllowInCustomizations = AsReadOnly;
+ AutoFormatType = 0;
+ CalcFormula = sum("Subcontractor WIP Ledger Entry"."Quantity (Base)" where("Prod. Order Status" = field(Status),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Prod. Order Line No." = field("Prod. Order Line Filter"),
+ "Routing Reference No." = field("Routing Reference No."),
+ "Routing No." = field("Routing No."),
+ "Operation No." = field("Operation No."),
+ "In Transit" = const(true)));
+ Caption = 'WIP Qty. (Base) in Transit';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the outstanding quantity of the production order parent item on transfer orders that is currently in transit to the subcontractor for this operation.';
+ }
+ field(99001534; "WIP Location Filter"; Code[10])
+ {
+ Caption = 'WIP Location Filter';
+ FieldClass = FlowFilter;
+ TableRelation = Location;
+ ToolTip = 'Specifies the location filter used for FlowField calculations.';
+ }
+ field(99001535; "Prod. Order Line Filter"; Integer)
+ {
+ Caption = 'Prod. Order Line Filter';
+ FieldClass = FlowFilter;
+ ToolTip = 'Specifies the production order line filter used for FlowField calculations.';
+ }
+ }
+
+ 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";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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("Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchLine.SetRange("Prod. Order No.", "Prod. Order No.");
+ PurchLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ PurchLine.SetRange("Operation No.", "Operation No.");
+ PurchLine.SetRange("Document Type", PurchLine."Document Type"::Order);
+ PurchLine.SetRange(Type, PurchLine.Type::Item);
+ 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";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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("Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchLine.SetRange("Prod. Order No.", "Prod. Order No.");
+ PurchLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ PurchLine.SetRange("Document Type", PurchLine."Document Type"::Order);
+ PurchLine.SetRange(Type, PurchLine.Type::Item);
+ 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/Manufacturing/SubcProductionOrderExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProductionOrderExt.TableExt.al
new file mode 100644
index 0000000000..084227bf0a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcProductionOrderExt.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.Manufacturing.Document;
+
+tableextension 99001505 "Subc. Production Order Ext." extends "Production Order"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001552; "Created from Purch. Order"; Boolean)
+ {
+ Caption = 'Created from Purchase Order';
+ DataClassification = CustomerContent;
+ Description = 'For internal use only';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRelProdOrder.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRelProdOrder.PageExt.al
new file mode 100644
index 0000000000..7612fdcf78
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRelProdOrder.PageExt.al
@@ -0,0 +1,85 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+pageextension 99001504 "Subc. Rel. Prod. Order" extends "Released Production Order"
+{
+ actions
+ {
+ addafter("Registered Put-away Lines")
+ {
+ action("Subcontracting Purchase Lines")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Order Lines';
+ Image = SubcontractingWorksheet;
+ RunObject = page "Purchase Lines";
+ RunPageLink = "Document Type" = const(Order), "Prod. Order No." = field("No.");
+ ToolTip = 'Show purchase order lines for subcontracting.';
+ }
+ }
+ addafter("&Warehouse Entries")
+ {
+ action("Subc. Transfer Orders")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Orders';
+ Image = TransferOrder;
+ ToolTip = 'View the subcontracting transfer orders related to this production order.';
+
+ trigger OnAction()
+ var
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersFromProductionOrder(Rec);
+ end;
+ }
+ action("Subc. Transfer Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Entries';
+ Image = ItemLedger;
+ RunObject = page "Item Ledger Entries";
+ RunPageLink = "Entry Type" = const(Transfer),
+ "Subc. Prod. Order No." = field("No.");
+ RunPageView = sorting("Order Type", "Order No.");
+ ToolTip = 'View the list of subcontracting transfers.';
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Prod. Order Status" = field(Status), "Prod. Order No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries for this production order.';
+ }
+ }
+ addlast("F&unctions")
+ {
+ action("WIP Adjustment")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'WIP Adjustment';
+ Image = AdjustEntries;
+ ToolTip = 'Manually adjust the WIP quantities for all routing operations of this production order.';
+
+ trigger OnAction()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ begin
+ WIPLedgerEntry.SetProductionOrderFilter(Rec, true);
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.SetDocumentNo(Rec."No.");
+ WIPAdjustmentPage.RunModal();
+ end;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRelProdOrders.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRelProdOrders.PageExt.al
new file mode 100644
index 0000000000..0446a28a16
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRelProdOrders.PageExt.al
@@ -0,0 +1,63 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+pageextension 99001505 "Subc. Rel. Prod. Orders" extends "Released Production Orders"
+{
+ actions
+ {
+ addafter("E&ntries")
+ {
+ action("Subcontracting Purchase Lines")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Order Lines';
+ Image = SubcontractingWorksheet;
+ RunObject = page "Purchase Lines";
+ RunPageLink = "Document Type" = const(Order), "Prod. Order No." = field("No.");
+ ToolTip = 'Show purchase order lines for subcontracting.';
+ }
+ }
+ addafter("&Warehouse Entries")
+ {
+ action("Subc. Transfer Orders")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Orders';
+ Image = TransferOrder;
+ ToolTip = 'View the subcontracting transfer orders related to this production order.';
+
+ trigger OnAction()
+ var
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersFromProductionOrder(Rec);
+ end;
+ }
+ action("Subc. Transfer Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Entries';
+ Image = ItemLedger;
+ RunObject = page "Item Ledger Entries";
+ RunPageLink = "Entry Type" = const(Transfer), "Subc. Prod. Order No." = field("No.");
+ RunPageView = sorting("Order Type", "Order No.");
+ ToolTip = 'View the list of subcontracting transfers.';
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Prod. Order Status" = field(Status), "Prod. Order No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries for this production order.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al
new file mode 100644
index 0000000000..b4c0c324d5
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcReqWkshMakeOrd.Codeunit.al
@@ -0,0 +1,124 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+codeunit 99001516 "Subc. Req. Wksh. Make Ord."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Req. Wksh.-Make Order", OnAfterInsertPurchOrderLine, '', false, false)]
+ local procedure OnAfterInsertPurchOrderLine(var PurchOrderLine: Record "Purchase Line"; var NextLineNo: Integer; var RequisitionLine: Record "Requisition Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ HandleSubcontractingAfterPurchOrderLineInsert(PurchOrderLine, RequisitionLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Req. Wksh.-Make Order", OnInsertPurchOrderLineOnAfterCheckInsertFinalizePurchaseOrderHeader, '', false, false)]
+ local procedure OnInsertPurchOrderLineOnAfterCheckInsertFinalizePurchaseOrderHeader(var RequisitionLine: Record "Requisition Line"; var PurchaseHeader: Record "Purchase Header"; var NextLineNo: Integer)
+ var
+ PurchaseLineWithService: Record "Purchase Line";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if RequisitionLine."Prod. Order No." = '' then
+ exit;
+ PurchaseLineWithService."Document Type" := PurchaseHeader."Document Type";
+ PurchaseLineWithService."Document No." := PurchaseHeader."No.";
+ SubcPurchaseOrderCreator.TransferSubcontractingProdOrderComp(PurchaseLineWithService, RequisitionLine, NextLineNo);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Carry Out Action", OnPurchOrderChgAndResheduleOnAfterGetPurchHeader, '', false, false)]
+ local procedure OnPurchOrderChgAndResheduleOnAfterGetPurchHeader(var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; var RequisitionLine: Record "Requisition Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ UpdateSubcontractingComponentPurchLines(PurchaseLine, RequisitionLine);
+ end;
+
+ local procedure HandleSubcontractingAfterPurchOrderLineInsert(var PurchaseLine: Record "Purchase Line"; var RequisitionLine: Record "Requisition Line")
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ begin
+ SubcPurchaseOrderCreator.InsertProdDescriptionOnAfterInsertPurchOrderLine(PurchaseLine, RequisitionLine);
+ if (RequisitionLine."Prod. Order No." <> '') and (RequisitionLine."Operation No." <> '') then begin
+ ProdOrderRoutingLine.SetLoadFields("Transfer WIP Item");
+ if ProdOrderRoutingLine.Get(
+ "Production Order Status"::Released,
+ RequisitionLine."Prod. Order No.",
+ RequisitionLine."Routing Reference No.",
+ RequisitionLine."Routing No.",
+ RequisitionLine."Operation No.")
+ then begin
+ PurchaseLine."Transfer WIP Item" := ProdOrderRoutingLine."Transfer WIP Item";
+ PurchaseLine.Modify();
+ end;
+ end;
+ end;
+
+ local procedure UpdateSubcontractingComponentPurchLines(PurchaseLine: Record "Purchase Line"; RequisitionLine: Record "Requisition Line")
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLineComp: Record "Purchase Line";
+ begin
+ if RequisitionLine."Prod. Order No." = '' then
+ exit;
+ if RequisitionLine."Operation No." = '' then
+ exit;
+
+ ProdOrderRoutingLine.SetLoadFields("Routing Link Code");
+ if not ProdOrderRoutingLine.Get(
+ "Production Order Status"::Released, RequisitionLine."Prod. Order No.",
+ RequisitionLine."Routing Reference No.", RequisitionLine."Routing No.", RequisitionLine."Operation No.")
+ then
+ exit;
+
+ ProdOrderComponent.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderComponent.SetRange("Prod. Order No.", RequisitionLine."Prod. Order No.");
+ 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");
+ PurchaseLineComp.SetRange("Document No.", PurchaseLine."Document No.");
+ PurchaseLineComp.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLineComp.SetRange("No.", ProdOrderComponent."Item No.");
+ PurchaseLineComp.SetRange("Variant Code", ProdOrderComponent."Variant Code");
+ PurchaseLineComp.SetRange("Subc. Prod. Order No.", ProdOrderComponent."Prod. Order No.");
+ PurchaseLineComp.SetRange("Subc. Operation No.", RequisitionLine."Operation No.");
+ if PurchaseLineComp.FindFirst() then
+ if PurchaseLineComp.Quantity <> ProdOrderComponent."Remaining Quantity" then begin
+ PurchaseLineComp.Validate(Quantity, ProdOrderComponent."Remaining Quantity");
+ PurchaseLineComp.Modify(true);
+ end;
+ until ProdOrderComponent.Next() = 0;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingFactboxMgmt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingFactboxMgmt.Codeunit.al
new file mode 100644
index 0000000000..e2da280b94
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingFactboxMgmt.Codeunit.al
@@ -0,0 +1,344 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Purchases.Vendor;
+
+codeunit 99001561 "Subc. Routing Factbox Mgmt."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ ///
+ /// Returns the subcontractor vendor number for the work center on the given production order routing line.
+ ///
+ /// The production order routing line to retrieve the subcontractor for.
+ /// The subcontractor vendor number, or an empty string if the line is a machine center type.
+ procedure GetSubcontractorNo(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Code[20]
+ var
+ WorkCenter: Record "Work Center";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+
+#endif
+ if ProdOrderRoutingLine.Type = ProdOrderRoutingLine.Type::"Machine Center" then
+ exit('');
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ if WorkCenter.Get(ProdOrderRoutingLine."Work Center No.") then
+ exit(WorkCenter."Subcontractor No.");
+ end;
+
+ ///
+ /// Opens the Vendor Card for the subcontractor assigned to the work center on the given production order routing line.
+ ///
+ /// The production order routing line whose work center subcontractor to show.
+ procedure ShowSubcontractor(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProdOrderRoutingLine.Type = ProdOrderRoutingLine.Type::"Work Center" then begin
+ WorkCenter.Get(ProdOrderRoutingLine."Work Center No.");
+ if Vendor.Get(WorkCenter."Subcontractor No.") then
+ Page.Run(Page::"Vendor Card", Vendor);
+ end;
+ end;
+
+ ///
+ /// Returns the total quantity on open purchase order lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to sum purchase order quantities for.
+ /// The sum of Quantity on matching open purchase order lines.
+ procedure GetPurchOrderQtyFromRoutingLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Decimal
+ var
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ PurchaseLine.SetRange(PurchaseLine."Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(PurchaseLine."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetRange(PurchaseLine."Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange(PurchaseLine."Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.CalcSums(PurchaseLine.Quantity);
+ exit(PurchaseLine.Quantity);
+ end;
+
+ ///
+ /// Opens the Purchase Lines page filtered to open purchase lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to filter purchase lines by.
+ procedure ShowPurchaseOrderLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchaseLine.SetRange(PurchaseLine."Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(PurchaseLine."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchaseLine.SetRange(PurchaseLine."Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange(PurchaseLine."Operation No.", ProdOrderRoutingLine."Operation No.");
+
+ Page.Run(Page::"Purchase Lines", PurchaseLine);
+ end;
+
+ ///
+ /// Returns the total received quantity on purchase receipt lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to sum received quantities for.
+ /// The sum of Quantity on matching purchase receipt lines.
+ procedure GetPurchReceiptQtyFromRoutingLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Decimal
+ var
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ PurchRcptLine.SetRange(PurchRcptLine."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchRcptLine.SetRange(PurchRcptLine."Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchRcptLine.SetRange(PurchRcptLine."Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchRcptLine.CalcSums(PurchRcptLine.Quantity);
+ exit(PurchRcptLine.Quantity);
+ end;
+
+ ///
+ /// Opens the Purch. Receipt Lines page filtered to receipt lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to filter receipt lines by.
+ procedure ShowPurchaseReceiptLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchRcptLine.SetRange(PurchRcptLine."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchRcptLine.SetRange(PurchRcptLine."Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchRcptLine.SetRange(PurchRcptLine."Operation No.", ProdOrderRoutingLine."Operation No.");
+
+ Page.Run(Page::"Purch. Receipt Lines", PurchRcptLine);
+ end;
+
+ ///
+ /// Returns the total invoiced quantity on purchase invoice lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to sum invoiced quantities for.
+ /// The sum of Quantity on matching purchase invoice lines.
+ procedure GetPurchInvoicedQtyFromRoutingLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Decimal
+ var
+ PurchInvLine: Record "Purch. Inv. Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ PurchInvLine.SetRange(PurchInvLine."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchInvLine.SetRange(PurchInvLine."Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchInvLine.SetRange(PurchInvLine."Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchInvLine.CalcSums(PurchInvLine.Quantity);
+ exit(PurchInvLine.Quantity);
+ end;
+
+ ///
+ /// Opens the Posted Purchase Invoice Lines page filtered to invoice lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to filter invoice lines by.
+ procedure ShowPurchaseInvoiceLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ PurchInvLine: Record "Purch. Inv. Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchInvLine.SetCurrentKey(PurchInvLine.Type, PurchInvLine."Prod. Order No.", PurchInvLine."Prod. Order Line No.");
+ PurchInvLine.SetRange(PurchInvLine."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ PurchInvLine.SetRange(PurchInvLine."Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchInvLine.SetRange(PurchInvLine."Operation No.", ProdOrderRoutingLine."Operation No.");
+
+ Page.Run(Page::"Posted Purchase Invoice Lines", PurchInvLine);
+ end;
+
+ ///
+ /// Returns the number of outbound subcontracting transfer lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to count transfer lines for.
+ /// The count of matching outbound transfer lines.
+ procedure GetNoOfTransferLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Decimal
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ TransferLine.SetCurrentKey(TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Routing Reference No.", TransferLine."Subc. Routing No.", TransferLine."Subc. Operation No.");
+ TransferLine.SetRange(TransferLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ TransferLine.SetRange(TransferLine."Subc. Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ TransferLine.SetRange(TransferLine."Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ TransferLine.SetRange(TransferLine."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+ TransferLine.SetRange("Derived From Line No.", 0);
+ exit(TransferLine.Count());
+ end;
+
+ ///
+ /// Returns the number of return subcontracting transfer lines linked to the given production order routing line.
+ ///
+ /// The production order routing line to count return transfer lines for.
+ /// The count of matching return transfer lines.
+ procedure GetNoOfReturnTransferLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Decimal
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ TransferLine.SetCurrentKey(TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Routing Reference No.", TransferLine."Subc. Operation No.");
+ TransferLine.SetRange(TransferLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ TransferLine.SetRange(TransferLine."Subc. Routing Reference No.", 0);
+ TransferLine.SetRange(TransferLine."Subc. Operation No.", '');
+ TransferLine.SetRange("Derived From Line No.", 0);
+ TransferLine.SetRange("Subc. Return Order", true);
+ exit(TransferLine.Count());
+ end;
+
+ ///
+ /// Opens the Transfer Lines page filtered to outbound subcontracting transfer lines for the given production order routing line.
+ ///
+ /// The production order routing line to filter transfer lines by.
+ procedure ShowTransferLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine.SetCurrentKey(TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Routing Reference No.", TransferLine."Subc. Routing No.", TransferLine."Subc. Operation No.");
+ TransferLine.SetRange(TransferLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ TransferLine.SetRange(TransferLine."Subc. Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ TransferLine.SetRange(TransferLine."Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ TransferLine.SetRange(TransferLine."Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ TransferLine.SetRange("Derived From Line No.", 0);
+ Page.Run(Page::"Transfer Lines", TransferLine);
+ end;
+
+ ///
+ /// Opens the Transfer Lines page filtered to return subcontracting transfer lines for the given production order routing line.
+ ///
+ /// The production order routing line to filter return transfer lines by.
+ procedure ShowReturnTransferLinesFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine.SetCurrentKey(TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Routing Reference No.", TransferLine."Subc. Operation No.");
+ TransferLine.SetRange(TransferLine."Subc. Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ TransferLine.SetRange(TransferLine."Subc. Routing Reference No.", 0);
+ TransferLine.SetRange(TransferLine."Subc. Operation No.", '');
+ TransferLine.SetRange("Derived From Line No.", 0);
+ Page.Run(Page::"Transfer Lines", TransferLine);
+ end;
+
+ ///
+ /// Returns the number of production order components linked via routing link code to the given production order routing line.
+ ///
+ /// The production order routing line to count linked components for.
+ /// The count of matching production order components, or 0 if the routing link code is empty.
+ procedure GetNoOfLinkedComponentsFromRouting(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Integer
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if ProdOrderRoutingLine."Routing Link Code" = '' then
+ exit(0);
+ ProdOrderComponent.SetRange(ProdOrderComponent.Status, ProdOrderRoutingLine.Status);
+ ProdOrderComponent.SetRange(ProdOrderComponent."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderComponent.SetRange(ProdOrderComponent."Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ ProdOrderComponent.SetRange(ProdOrderComponent."Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ exit(ProdOrderComponent.Count());
+ end;
+
+ ///
+ /// Opens the Subc. Prod. Order Components page filtered to components linked to the given production order routing line.
+ ///
+ /// The production order routing line to filter production order components by.
+ procedure ShowProdOrderComponents(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProdOrderComponent.SetRange(ProdOrderComponent.Status, ProdOrderRoutingLine.Status);
+ ProdOrderComponent.SetRange(ProdOrderComponent."Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderComponent.SetRange(ProdOrderComponent."Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ ProdOrderComponent.SetRange(ProdOrderComponent."Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ Page.Run(Page::"Subc. Prod. Order Components", ProdOrderComponent);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingInfoFactbox.Page.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingInfoFactbox.Page.al
new file mode 100644
index 0000000000..9b19c32709
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingInfoFactbox.Page.al
@@ -0,0 +1,126 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+
+page 99001502 "Subc. Routing Info Factbox"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Routing Details';
+ Editable = false;
+ PageType = CardPart;
+ RefreshOnActivate = true;
+ SourceTable = "Prod. Order Routing Line";
+ layout
+ {
+ area(Content)
+ {
+ field(ShowSubcontractor; SubcRoutingFactboxMgmt.GetSubcontractorNo(Rec))
+ {
+ Caption = 'Subcontractor';
+ ToolTip = 'Specifies the assigned Subcontractor No. of this Prod. Order Routing Line.';
+ trigger OnDrillDown()
+ begin
+ ShowSubcontractorFromRouting();
+ end;
+ }
+ field(ShowQtyInSubcontractingOrder; SubcRoutingFactboxMgmt.GetPurchOrderQtyFromRoutingLine(Rec))
+ {
+ AutoFormatType = 0;
+ Caption = 'Purch. Order Qty.';
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the dependent Quantity in Subcontracting Orders of this Prod. Order Routing Line.';
+ trigger OnDrillDown()
+ begin
+ ShowPurchaseOrders();
+ end;
+ }
+ field(ShowQtyShippedRequest; SubcRoutingFactboxMgmt.GetPurchReceiptQtyFromRoutingLine(Rec))
+ {
+ AutoFormatType = 0;
+ Caption = 'Quantity Received';
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the dependent Quantity Received in Subcontracting Receipts of this Prod. Order Routing Line.';
+ trigger OnDrillDown()
+ begin
+ ShowPurchaseReceipts();
+ end;
+ }
+ field(ShowQtyInvoicedRequest; SubcRoutingFactboxMgmt.GetPurchInvoicedQtyFromRoutingLine(Rec))
+ {
+ AutoFormatType = 0;
+ Caption = 'Quantity Invoiced';
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the dependent Quantity Invoiced in Subcontracting Invoices of this Prod. Order Routing Line.';
+ trigger OnDrillDown()
+ begin
+ ShowPurchaseInvoices();
+ end;
+ }
+ field(ShowNoOfTransferOrdersFromProdOrderComp; SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, false, false))
+ {
+ Caption = 'Transfer Order Lines';
+ ToolTip = 'Specifies the number of transfer order lines assigned to this routing line.';
+ trigger OnDrillDown()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, false);
+ end;
+ }
+ field(ShowNoOfReturnTransferOrdersFromProdOrderComp; SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, false, true))
+ {
+ Caption = 'Return Transfer Order Lines';
+ ToolTip = 'Specifies the number of Return transfer order lines assigned to this routing line.';
+ trigger OnDrillDown()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, true);
+ end;
+ }
+ field(ShowNoOfLinkedComp; SubcRoutingFactboxMgmt.GetNoOfLinkedComponentsFromRouting(Rec))
+ {
+ Caption = 'Components';
+ ToolTip = 'Specifies the number of components linked to this routing line.';
+ trigger OnDrillDown()
+ begin
+ ShowProdOrderComponents();
+ end;
+ }
+ field("WIP Qty. (Base) at Subc."; Rec."WIP Qty. (Base) at Subc.")
+ {
+ }
+ field("WIP Qty. (Base) in Transit"; Rec."WIP Qty. (Base) in Transit")
+ {
+ }
+ }
+ }
+ local procedure ShowSubcontractorFromRouting()
+ begin
+ SubcRoutingFactboxMgmt.ShowSubcontractor(Rec);
+ end;
+
+ local procedure ShowPurchaseOrders()
+ begin
+ SubcRoutingFactboxMgmt.ShowPurchaseOrderLinesFromRouting(Rec);
+ end;
+
+ local procedure ShowPurchaseReceipts()
+ begin
+ SubcRoutingFactboxMgmt.ShowPurchaseReceiptLinesFromRouting(Rec);
+ end;
+
+ local procedure ShowPurchaseInvoices()
+ begin
+ SubcRoutingFactboxMgmt.ShowPurchaseInvoiceLinesFromRouting(Rec);
+ end;
+
+ local procedure ShowProdOrderComponents()
+ begin
+ SubcRoutingFactboxMgmt.ShowProdOrderComponents(Rec);
+ end;
+
+ var
+ SubcRoutingFactboxMgmt: Codeunit "Subc. Routing Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingLine.TableExt.al
new file mode 100644
index 0000000000..fd1d98d05d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingLine.TableExt.al
@@ -0,0 +1,118 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+
+tableextension 99001560 "Subc. Routing Line" extends "Routing Line"
+{
+ AllowInCustomizations = AsReadWrite;
+ fields
+ {
+ modify(Type)
+ {
+ trigger OnAfterValidate()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+
+#endif
+ if Type = xRec.Type then
+ exit;
+
+ if Type <> "Capacity Type"::"Work Center" then
+ "Transfer WIP Item" := false;
+ end;
+ }
+ modify("No.")
+ {
+ trigger OnAfterValidate()
+ var
+ WorkCenter: Record "Work Center";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "No." = xRec."No." then
+ exit;
+ if Type <> "Capacity Type"::"Work Center" then begin
+ "Transfer WIP Item" := false;
+ exit;
+ end;
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ WorkCenter.Get("No.");
+ if WorkCenter."Subcontractor No." = '' then
+ "Transfer WIP Item" := false;
+ end;
+ }
+ field(99001551; Subcontracting; Boolean)
+ {
+ AllowInCustomizations = AsReadOnly;
+ CalcFormula = exist("Work Center" where("No." = field("Work Center No."),
+ "Subcontractor No." = filter(<> '')));
+ Caption = 'Subcontracting';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies whether the Work Center Group is set up with a Vendor for Subcontracting.';
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies whether the production order parent item (WIP item) is transferred to the subcontractor for this operation.';
+
+ trigger OnValidate()
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Transfer WIP Item" then begin
+ CalcFields(Subcontracting);
+ TestField(Subcontracting, true);
+ end;
+ end;
+ }
+ field(99001561; "Transfer Description"; Text[100])
+ {
+ Caption = 'Transfer Description';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the operation-specific description used on transfer orders for the semi-finished item as it is shipped to the subcontracting location. If empty, the standard description is used.';
+ }
+ field(99001562; "Transfer Description 2"; Text[50])
+ {
+ Caption = 'Transfer Description 2';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies an additional operation-specific description line used on transfer orders for the semi-finished item as it is shipped to the subcontracting location.';
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingLines.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingLines.PageExt.al
new file mode 100644
index 0000000000..6e3bf00d92
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingLines.PageExt.al
@@ -0,0 +1,136 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Routing;
+
+pageextension 99001508 "Subc. Routing Lines" extends "Routing Lines"
+{
+ layout
+ {
+ modify("No.")
+ {
+ trigger OnAfterValidate()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+ }
+ modify(Type)
+ {
+ trigger OnAfterValidate()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+ }
+ addafter("Send-Ahead Quantity")
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = TransferWIPItemEnabled;
+ }
+ field("Transfer Description"; Rec."Transfer Description")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = Rec."Transfer WIP Item";
+ }
+ field("Transfer Description 2"; Rec."Transfer Description 2")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = Rec."Transfer WIP Item";
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("&Quality Measures")
+ {
+ action("Subc. Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Prices';
+ Image = Price;
+ ToolTip = 'View the related subcontracting prices.';
+
+ trigger OnAction()
+ begin
+ ShowRelatedSubcontractorPrices();
+ end;
+ }
+ }
+ }
+
+#if not CLEAN28
+ trigger OnOpenPage()
+ begin
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+ end;
+#endif
+
+ trigger OnAfterGetRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ UpdateWIPEnabled();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ UpdateWIPEnabled();
+ end;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ TransferWIPItemEnabled: Boolean;
+
+ local procedure UpdateWIPEnabled()
+ begin
+ Rec.Calcfields(Subcontracting);
+ TransferWIPItemEnabled := Rec.Subcontracting;
+ end;
+
+ procedure ShowRelatedSubcontractorPrices()
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ Rec.TestField(Type, Rec.Type::"Work Center");
+ SubcontractorPrice.SetRange("Work Center No.", Rec."No.");
+ if Rec."Standard Task Code" <> '' then
+ SubcontractorPrice.SetRange("Standard Task Code", Rec."Standard Task Code")
+ else
+ SubcontractorPrice.SetRange("Standard Task Code");
+
+ Page.Run(Page::"Subcontractor Prices", SubcontractorPrice);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingVersionLines.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingVersionLines.PageExt.al
new file mode 100644
index 0000000000..dcbbfff831
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcRoutingVersionLines.PageExt.al
@@ -0,0 +1,129 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Routing;
+
+pageextension 99001509 "Subc. Routing Version Lines" extends "Routing Version Lines"
+{
+ layout
+ {
+ modify("No.")
+ {
+ trigger OnAfterValidate()
+ begin
+ UpdateWIPEnabled();
+ end;
+ }
+ modify(Type)
+ {
+ trigger OnAfterValidate()
+ begin
+ UpdateWIPEnabled();
+ end;
+ }
+ addafter(Description)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = TransferWIPItemEnabled;
+ ToolTip = 'Specifies whether a WIP item should be transferred for this subcontracting routing line.';
+ }
+ field("Transfer Description"; Rec."Transfer Description")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = Rec."Transfer WIP Item";
+ ToolTip = 'Specifies the description of the WIP item to transfer.';
+ }
+ field("Transfer Description 2"; Rec."Transfer Description 2")
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = Rec."Transfer WIP Item";
+ ToolTip = 'Specifies an additional description of the WIP item to transfer.';
+ }
+ }
+ }
+ actions
+ {
+ addafter("Quality Measures")
+ {
+ action("Subc. Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Prices';
+ Image = Price;
+ ToolTip = 'View the related subcontracting prices.';
+
+ trigger OnAction()
+ begin
+ ShowRelatedSubcontractorPrices();
+ end;
+ }
+ }
+ }
+
+#if not CLEAN28
+ trigger OnOpenPage()
+ begin
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+ if not SubcontractingEnabled then
+ exit;
+ end;
+#endif
+
+ trigger OnAfterGetRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ UpdateWIPEnabled();
+ end;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ TransferWIPItemEnabled: Boolean;
+
+ local procedure UpdateWIPEnabled()
+ begin
+ Rec.Calcfields(Subcontracting);
+ TransferWIPItemEnabled := Rec.Subcontracting;
+ end;
+
+ procedure ShowRelatedSubcontractorPrices()
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ Rec.TestField(Type, Rec.Type::"Work Center");
+ SubcontractorPrice.SetRange("Work Center No.", Rec."No.");
+ if Rec."Standard Task Code" <> '' then
+ SubcontractorPrice.SetRange("Standard Task Code", Rec."Standard Task Code")
+ else
+ SubcontractorPrice.SetRange("Standard Task Code");
+
+ Page.Run(Page::"Subcontractor Prices", SubcontractorPrice);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterCard.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterCard.PageExt.al
new file mode 100644
index 0000000000..cfcd353b6c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterCard.PageExt.al
@@ -0,0 +1,86 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.WorkCenter;
+
+pageextension 99001506 "Subc. Work Center Card" extends "Work Center Card"
+{
+ layout
+ {
+ modify("Subcontractor No.")
+ {
+ trigger OnAfterValidate()
+ begin
+ CurrPage.Update(false);
+ end;
+ }
+ }
+ actions
+ {
+ addafter("Pla&nning")
+ {
+ group(Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ Image = SubcontractingWorksheet;
+
+ action("Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ Enabled = IsSubcontractingWorkCenter;
+ Image = Price;
+ RunObject = page "Subcontractor Prices";
+ RunPageLink = "Work Center No." = field("No.");
+ RunPageView = sorting("Vendor No.", "Item No.", "Standard Task Code", "Work Center No.", "Variant Code", "Starting Date", "Unit of Measure Code", "Minimum Quantity", "Currency Code");
+ ToolTip = 'Set up different prices for the work center and vendor in subcontracting.';
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Enabled = IsSubcontractingWorkCenter;
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Work Center No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries that track work-in-progress quantities at this work center''s subcontracting location.';
+ }
+ }
+ }
+ modify("Subcontractor - Dispatch List")
+ {
+ Enabled = IsSubcontractingWorkCenter;
+ }
+ }
+
+ trigger OnOpenPage()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+#endif
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ IsSubcontractingWorkCenter := Rec."Subcontractor No." <> '';
+ end;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ IsSubcontractingWorkCenter: Boolean;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterExtension.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterExtension.Codeunit.al
new file mode 100644
index 0000000000..c252f3044a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterExtension.Codeunit.al
@@ -0,0 +1,35 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.WorkCenter;
+
+codeunit 99001519 "Subc. Work Center Extension"
+{
+ [EventSubscriber(ObjectType::Table, Database::"Work Center", OnAfterDeleteEvent, '', false, false)]
+ local procedure OnAfterDeleteWorkCenter(var Rec: Record "Work Center"; RunTrigger: Boolean)
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if not RunTrigger then
+ exit;
+
+ SubcontractorPrice.DeletePricesForWorkCenter(Rec."No.");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterList.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterList.PageExt.al
new file mode 100644
index 0000000000..870e6efe6f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Manufacturing/SubcWorkCenterList.PageExt.al
@@ -0,0 +1,76 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.WorkCenter;
+
+pageextension 99001507 "Subc. Work Center List" extends "Work Center List"
+{
+ actions
+ {
+ addafter("Pla&nning")
+ {
+ group(Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ Image = SubcontractingWorksheet;
+ action("Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ Enabled = IsSubcontractingWorkCenter;
+ Image = Price;
+ ToolTip = 'Set up different prices for the work center and vendor in subcontracting.';
+ trigger OnAction()
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ SubcontractorPrices: Page "Subcontractor Prices";
+ begin
+ SubcontractorPrice.SetRange("Work Center No.", Rec."No.");
+ SubcontractorPrices.SetTableView(SubcontractorPrice);
+ SubcontractorPrices.RunModal();
+ end;
+ }
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Work Center No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries that track work-in-progress quantities at this work center''s subcontracting location.';
+ }
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+#endif
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ IsSubcontractingWorkCenter := Rec."Subcontractor No." <> '';
+ end;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ IsSubcontractingWorkCenter: Boolean;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemCard.PageExt.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemCard.PageExt.al
new file mode 100644
index 0000000000..5d99d33d23
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemCard.PageExt.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+
+pageextension 99001518 "Subc. Item Card" extends "Item Card"
+{
+ actions
+ {
+ addafter(PurchPriceLists)
+ {
+ action("Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ Image = Price;
+ RunObject = page "Subcontractor Prices";
+ RunPageLink = "Item No." = field("No.");
+ RunPageView = sorting("Vendor No.", "Item No.", "Standard Task Code", "Work Center No.", "Variant Code", "Starting Date", "Unit of Measure Code", "Minimum Quantity", "Currency Code");
+ ToolTip = 'Set up different prices for the item in subcontracting.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemExtension.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemExtension.Codeunit.al
new file mode 100644
index 0000000000..a1b9ce4cfc
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemExtension.Codeunit.al
@@ -0,0 +1,35 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+
+codeunit 99001532 "Subc. Item Extension"
+{
+ [EventSubscriber(ObjectType::Table, Database::Item, OnAfterDeleteEvent, '', false, false)]
+ local procedure OnAfterDeleteItem(var Rec: Record Item; RunTrigger: Boolean)
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if not RunTrigger then
+ exit;
+
+ SubcontractorPrice.DeletePricesForItem(Rec."No.");
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemJournalLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemJournalLineExt.TableExt.al
new file mode 100644
index 0000000000..55d3d6ff29
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemJournalLineExt.TableExt.al
@@ -0,0 +1,49 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Journal;
+using Microsoft.Purchases.Document;
+
+tableextension 99001508 "Subc. Item Journal Line Ext." extends "Item Journal Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001510; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Subc. Prod. Order No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001511; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001512; "Subc. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subc. Purch. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Purchase Header"."No." where("Document Type" = const(Order));
+ }
+ field(99001513; "Subc. Purch. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Purchase Line"."Line No." where("Document Type" = const(Order),
+ "Document No." = field("Subc. Purch. Order No."));
+ }
+ field(99001514; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Operation No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001542; "Subc. Item Charge Assign."; Boolean)
+ {
+ Caption = 'Subc. Item Charge Assignment';
+ DataClassification = CustomerContent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemList.PageExt.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemList.PageExt.al
new file mode 100644
index 0000000000..8e233b9ec0
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcItemList.PageExt.al
@@ -0,0 +1,39 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+
+pageextension 99001519 "Subc. Item List" extends "Item List"
+{
+ actions
+ {
+ addafter(PurchPriceLists)
+ {
+ action("Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ Image = Price;
+ RunObject = page "Subcontractor Prices";
+ RunPageLink = "Item No." = field("No.");
+ RunPageView = sorting("Vendor No.", "Item No.", "Standard Task Code", "Work Center No.", "Variant Code", "Starting Date", "Unit of Measure Code", "Minimum Quantity", "Currency Code");
+ ToolTip = 'Set up different prices for the item in subcontracting.';
+ }
+ }
+ addlast(History)
+ {
+ action("WIP Ledger Entries")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Image = LedgerEntries;
+ RunObject = page "Subc. WIP Ledger Entries";
+ RunPageLink = "Item No." = field("No.");
+ ToolTip = 'View the Subcontracting WIP Entries that track work-in-progress quantities for this item across subcontracting locations.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendor.TableExt.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendor.TableExt.al
new file mode 100644
index 0000000000..39122891f8
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendor.TableExt.al
@@ -0,0 +1,75 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Vendor;
+
+tableextension 99001507 "Subc. Vendor" extends Vendor
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001515; "Subc. Location Code"; Code[10])
+ {
+ Caption = 'Subcontracting Location Code';
+ DataClassification = CustomerContent;
+ TableRelation = Location where("Use As In-Transit" = const(false));
+ trigger OnValidate()
+ var
+ Location: Record Location;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ ErrorInfo: ErrorInfo;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Subc. Location Code" = '' then
+ exit;
+ Location.Get("Subc. Location Code");
+ if Location."Bin Mandatory" or Location."Require Pick" or Location."Require Put-away" or Location."Require Receive" or Location."Require Shipment" then begin
+ ErrorInfo.Title := CannotUseLocationLbl;
+ ErrorInfo.Message := StrSubstNo(BinWarehouseEnabledOnLocationErr, "Subc. Location Code");
+ ErrorInfo.Verbosity := ErrorInfo.Verbosity::Error;
+ ErrorInfo.PageNo := Page::"Location Card";
+ ErrorInfo.RecordId := Location.RecordId;
+ ErrorInfo.AddNavigationAction(ShowLocationCardLbl);
+ Error(ErrorInfo);
+ end;
+ end;
+ }
+ field(99001516; "Subc. Linked to Work Center"; Boolean)
+ {
+ CalcFormula = exist("Work Center" where("Subcontractor No." = field("No.")));
+ Caption = 'Linked to Work Center';
+ Editable = false;
+ FieldClass = FlowField;
+ }
+ field(99001517; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Work Center" where("Subcontractor No." = field("No."));
+ }
+ }
+
+ keys
+ {
+ key(SubcLocationCode; "Subc. Location Code") { }
+ }
+
+ var
+ CannotUseLocationLbl: Label 'Cannot use the location for subcontracting';
+ ShowLocationCardLbl: Label 'Show Location Card';
+ BinWarehouseEnabledOnLocationErr: Label 'Location %1 cannot be used as a subcontracting location because Bin Mandatory or warehouse handling is enabled on the location.', Comment = '%1 = Location Code';
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorCard.PageExt.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorCard.PageExt.al
new file mode 100644
index 0000000000..9c6585a2a8
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorCard.PageExt.al
@@ -0,0 +1,43 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Vendor;
+
+pageextension 99001516 "Subc. Vendor Card" extends "Vendor Card"
+{
+ layout
+ {
+ addafter("Location Code")
+ {
+ field("Subc. Location Code"; Rec."Subc. Location Code")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the subcontracting location where items from the vendor must be received by default after having performed an outside work.';
+ }
+ field("Subc. Linked to Work Center"; Rec."Subc. Linked to Work Center")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies if a work center is related to the vendor.';
+ }
+ }
+ }
+ actions
+ {
+ addafter("Prepa&yment Percentages")
+ {
+ action("Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ Image = Price;
+ RunObject = page "Subcontractor Prices";
+ RunPageLink = "Vendor No." = field("No.");
+ RunPageView = sorting("Vendor No.", "Item No.", "Standard Task Code", "Work Center No.", "Variant Code", "Starting Date", "Unit of Measure Code", "Minimum Quantity", "Currency Code");
+ ToolTip = 'Set up different prices for the vendor in subcontracting.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorExtension.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorExtension.Codeunit.al
new file mode 100644
index 0000000000..1108624a31
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorExtension.Codeunit.al
@@ -0,0 +1,35 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Vendor;
+
+codeunit 99001531 "Subc. Vendor Extension"
+{
+ [EventSubscriber(ObjectType::Table, Database::Vendor, OnAfterDeleteEvent, '', false, false)]
+ local procedure OnAfterDeleteVendor(var Rec: Record Vendor; RunTrigger: Boolean)
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if not RunTrigger then
+ exit;
+
+ SubcontractorPrice.DeletePricesForVendor(Rec."No.");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorList.PageExt.al b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorList.PageExt.al
new file mode 100644
index 0000000000..00ac3b4e54
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/MasterData/SubcVendorList.PageExt.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Vendor;
+
+pageextension 99001517 "Subc. Vendor List" extends "Vendor List"
+{
+ actions
+ {
+ addafter("Prepa&yment Percentages")
+ {
+ action("Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ Image = Price;
+ RunObject = page "Subcontractor Prices";
+ RunPageLink = "Vendor No." = field("No.");
+ RunPageView = sorting("Vendor No.", "Item No.", "Standard Task Code", "Work Center No.", "Variant Code", "Starting Date", "Unit of Measure Code", "Minimum Quantity", "Currency Code");
+ ToolTip = 'Set up different prices for the vendor in subcontracting.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Permissions/D365BasicSubcontracting.PermissionSetExt.al b/src/Apps/W1/Subcontracting/App/src/Permissions/D365BasicSubcontracting.PermissionSetExt.al
new file mode 100644
index 0000000000..2de90c35de
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Permissions/D365BasicSubcontracting.PermissionSetExt.al
@@ -0,0 +1,12 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Security.AccessControl;
+
+permissionsetextension 99001502 "D365 BASIC - Subcontracting" extends "D365 BASIC"
+{
+ IncludedPermissionSets = "Subcontract. - Read";
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Permissions/D365BusFullAccessSubcontracting.PermissionSetExt.al b/src/Apps/W1/Subcontracting/App/src/Permissions/D365BusFullAccessSubcontracting.PermissionSetExt.al
new file mode 100644
index 0000000000..86a171820c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Permissions/D365BusFullAccessSubcontracting.PermissionSetExt.al
@@ -0,0 +1,12 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Security.AccessControl;
+
+permissionsetextension 99001501 "D365 BUS FULL ACCESS - Subcontracting" extends "D365 BUS FULL ACCESS"
+{
+ IncludedPermissionSets = "Subcontract. - Edit";
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Permissions/D365ReadSubcontracting.PermissionSetExt.al b/src/Apps/W1/Subcontracting/App/src/Permissions/D365ReadSubcontracting.PermissionSetExt.al
new file mode 100644
index 0000000000..b69754142f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Permissions/D365ReadSubcontracting.PermissionSetExt.al
@@ -0,0 +1,12 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Security.AccessControl;
+
+permissionsetextension 99001500 "D365 READ - Subcontracting" extends "D365 READ"
+{
+ IncludedPermissionSets = "Subcontract. - Read";
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractEdit.PermissionSet.al b/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractEdit.PermissionSet.al
new file mode 100644
index 0000000000..871f4ad41e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractEdit.PermissionSet.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+permissionset 99001503 "Subcontract. - Edit"
+{
+ Caption = 'Subcontracting - Edit';
+ Access = Public;
+ Assignable = true;
+
+ IncludedPermissionSets = "Subcontract. - Read";
+
+ Permissions =
+ tabledata "Subcontractor Price" = IMD,
+ tabledata "Subcontractor WIP Ledger Entry" = IMD;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractObjs.PermissionSet.al b/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractObjs.PermissionSet.al
new file mode 100644
index 0000000000..78bea29794
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractObjs.PermissionSet.al
@@ -0,0 +1,97 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+permissionset 99001501 "Subcontract. - Objs"
+{
+ Caption = 'Subcontracting - Objects';
+ Assignable = true;
+ Access = Internal;
+ Permissions =
+ // Tables
+ table "Subcontractor Price" = X,
+ table "Subcontractor WIP Ledger Entry" = X,
+
+ // Codeunits
+ codeunit "Subc. Session State" = X,
+ codeunit "Subc. Business Setup Ext." = X,
+ codeunit "Subc. Calc. Prod. Order Ext." = X,
+ codeunit "Subc. Calc.StandardCost Ext." = X,
+ codeunit "Subc. Calc BOM Tree Ext." = X,
+ codeunit "Subc. Calc Subcontracts Ext." = X,
+ codeunit "Subc. Carry Out Action Ext." = X,
+ codeunit "Subc. DirectTransferLine Ext." = X,
+ codeunit "Subc. Comp. Factbox Mgmt." = X,
+ codeunit "Subc. ProdO. Factbox Mgmt." = X,
+ codeunit "Subc. Purch. Factbox Mgmt." = X,
+ codeunit "Subc. Routing Factbox Mgmt." = X,
+ codeunit "Subc. ItemChargeAssPurchExt" = X,
+ codeunit "Subc. Item Extension" = X,
+ codeunit "Subc. ItemJnlPostLine Ext" = X,
+ codeunit "Subc. Notification Mgmt." = X,
+ codeunit "Subc. Planning Comp. Ext." = X,
+ codeunit "Subc. Planning Line Mgmt Ext." = X,
+ codeunit "Subc. Price Management" = X,
+ codeunit "Subc. Prod. Order Comp. Ext." = X,
+ codeunit "Subc. Prod. Order Rtng. Ext." = X,
+ codeunit "Subc. Prod. Ord. Comp. Res." = X,
+ codeunit "Subc. Purch. Post Ext" = X,
+ codeunit "Subc. Purchase Header Ext" = X,
+ codeunit "Subc. Purchase Line Ext" = X,
+ codeunit "Subc. Reporting Triggers Ext" = X,
+ codeunit "Subc. Req.Line Extension" = X,
+ codeunit "Subc. Req. Wksh. Make Ord." = X,
+ codeunit "Subcontracting Comp. Init." = X,
+ codeunit "Subcontracting Management" = X,
+ codeunit "Subc. Synchronize Management" = X,
+ codeunit "Subc. Transfer Header Ext." = X,
+ codeunit "Subc. Transfer Line Ext." = X,
+ codeunit "Subc. Transfer Management" = X,
+ codeunit "Subc. Transfer Rcpt Line Ext." = X,
+ codeunit "Subc. Transfer Shpt Line Ext." = X,
+ codeunit "Subc. TransOrderPostRcpt Ext" = X,
+ codeunit "Subc. TransOrderPostShpt Ext" = X,
+ codeunit "Subc. TransOrderPostTrans Ext" = X,
+ codeunit "Subc. Trans Rcpt Header Ext" = X,
+ codeunit "Subc. Trans Shpt Header Ext" = X,
+ codeunit "Subc. Vendor Extension" = X,
+ codeunit "Subc. WhsePostReceipt Ext" = X,
+ codeunit "Subc. WhsePurchRelease Ext" = X,
+ codeunit "Subc. Work Center Extension" = X,
+ codeunit "Subcontracting Install" = X,
+ codeunit "Subc. Change Prod.Order Status" = X,
+ codeunit "Subc. Posting Preview Binding" = X,
+ codeunit "Subc. Posting Preview Subscr." = X,
+ codeunit "Subc. Pst. Prev. Event Handler" = X,
+ codeunit "Subc. Purchase Order Creator" = X,
+ codeunit "Subc. Transfer WIP Posting" = X,
+ codeunit "Subc. WhsePostShipment Ext" = X,
+ codeunit "Subc. WIP Item Ledg Find Entry" = X,
+ codeunit "Subc. Application Area Mgmt." = X,
+#if not CLEAN28
+#pragma warning disable AL0432
+ codeunit "Subc. Feature Flag Handler" = X,
+#pragma warning restore AL0432
+#endif
+ codeunit "Subc. Upgrade Tag Def. Ext." = X,
+ codeunit "Subc. Worksheet Handler" = X,
+
+ // Pages
+ page "Subc. Prod. Order Components" = X,
+ page "Subc. Subcontracting Worksheet" = X,
+ page "Subc. Purchase Line Factbox" = X,
+ page "Subc. Routing Info Factbox" = X,
+ page "Subc. Transfer Line Factbox" = X,
+ page "Subcontractor Prices" = X,
+ page "Subc. WIP Adjustment" = X,
+ page "Subc. WIP Ledger Entries" = X,
+
+ // Reports
+ report "Subc. Calculate Subcontracts" = X,
+ report "Subc. Create Transf. Order" = X,
+ report "Subc. Create SubCReturnOrder" = X,
+ report "Subc. Detailed Calculation" = X,
+ report "Subc. Dispatching List" = X;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractRead.PermissionSet.al b/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractRead.PermissionSet.al
new file mode 100644
index 0000000000..2bbbecde21
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Permissions/SubcontractRead.PermissionSet.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+permissionset 99001502 "Subcontract. - Read"
+{
+ Caption = 'Subcontracting - Read';
+ Access = Public;
+ Assignable = true;
+
+ IncludedPermissionSets = "Subcontract. - Objs";
+
+ Permissions =
+ tabledata "Subcontractor Price" = R,
+ tabledata "Subcontractor WIP Ledger Entry" = R;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/Rep99001500.SubcDetailedCalculation.rdl b/src/Apps/W1/Subcontracting/App/src/Purchase/Rep99001500.SubcDetailedCalculation.rdl
new file mode 100644
index 0000000000..f941688ff9
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/Rep99001500.SubcDetailedCalculation.rdl
@@ -0,0 +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
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCalculateSubcontracts.Report.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCalculateSubcontracts.Report.al
new file mode 100644
index 0000000000..23e484b936
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCalculateSubcontracts.Report.al
@@ -0,0 +1,421 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.GeneralLedger.Setup;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Costing;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Item.Catalog;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+
+report 99001505 "Subc. Calculate Subcontracts"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Calculate Subcontracts';
+ ProcessingOnly = true;
+
+ dataset
+ {
+ dataitem("Work Center"; "Work Center")
+ {
+ DataItemTableView = sorting("No.");
+ RequestFilterFields = "No.", "Subcontractor No.";
+ dataitem("Prod. Order Routing Line"; "Prod. Order Routing Line")
+ {
+ DataItemLink = "No." = field("No.");
+ DataItemTableView = sorting(Type, "No.") where(Status = const(Released), Type = const("Work Center"), "Routing Status" = filter(< Finished));
+ RequestFilterFields = "Prod. Order No.", "Starting Date";
+
+ trigger OnAfterGetRecord()
+ begin
+ TempProdOrderRoutingLine.Init();
+ TempProdOrderRoutingLine := "Prod. Order Routing Line";
+ TempProdOrderRoutingLine.Insert();
+ end;
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ if "Subcontractor No." = '' then
+ CurrReport.Skip();
+
+ Window.Update(1, "No.");
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ TempProdOrderRoutingLine.DeleteAll();
+ ReqLine.SetRange("Worksheet Template Name", ReqLine."Worksheet Template Name");
+ ReqLine.SetRange("Journal Batch Name", ReqLine."Journal Batch Name");
+ ReqLine.DeleteAll();
+ end;
+
+ trigger OnPostDataItem()
+ begin
+ CalculateSubContractRequirements();
+ end;
+ }
+ }
+
+ requestpage
+ {
+
+ layout
+ {
+ }
+
+ actions
+ {
+ }
+ }
+
+ labels
+ {
+ }
+
+ trigger OnInitReport()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ CurrReport.Quit();
+#endif
+ MfgSetup.Get();
+ end;
+
+ trigger OnPreReport()
+ begin
+ ReqWkshTmpl.Get(ReqLine."Worksheet Template Name");
+ ReqWkShName.Get(ReqLine."Worksheet Template Name", ReqLine."Journal Batch Name");
+ ReqLine.SetRange("Worksheet Template Name", ReqLine."Worksheet Template Name");
+ ReqLine.SetRange("Journal Batch Name", ReqLine."Journal Batch Name");
+ ReqLine.LockTable();
+
+ if ReqLine.FindLast() then
+ ReqLine.Init();
+
+ Window.Open(ProcessingWorkCentersLbl + ProcessingOrdersLbl);
+ end;
+
+ var
+ MfgSetup: Record "Manufacturing Setup";
+ ReqWkshTmpl: Record "Req. Wksh. Template";
+ ReqWkShName: Record "Requisition Wksh. Name";
+ ReqLine: Record "Requisition Line";
+ ProdOrderLine: Record "Prod. Order Line";
+ GLSetup: Record "General Ledger Setup";
+ PurchLine: Record "Purchase Line";
+ Item: Record Item;
+ ItemVariant: Record "Item Variant";
+ TempProdOrderRoutingLine: Record "Prod. Order Routing Line" temporary;
+ MfgCostCalcMgt: Codeunit "Mfg. Cost Calculation Mgt.";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ UOMMgt: Codeunit "Unit of Measure Management";
+ Window: Dialog;
+ BaseQtyToPurch: Decimal;
+ QtyToPurch: Decimal;
+ GLSetupRead: Boolean;
+
+ ProcessingWorkCentersLbl: Label 'Processing Work Centers #1##########\', Comment = '#1 = current work center number being processed';
+ ProcessingOrdersLbl: Label 'Processing Orders #2########## ', Comment = '#2 = current order number being processed';
+ ProductionBlockedOutputItemQst: Label 'Item %1 is blocked for production output and cannot be calculated. Do you want to continue?', Comment = '%1 Item No.';
+ ProductionBlockedOutputItemVariantQst: Label 'Variant %1 for item %2 is blocked for production output and cannot be calculated. Do you want to continue?', Comment = '%1 - Item Variant Code, %2 - Item No.';
+
+ procedure SetWkShLine(NewReqLine: Record "Requisition Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ReqLine := NewReqLine;
+ end;
+
+ local procedure InsertReqWkshLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ WorkCenter: Record "Work Center";
+ IsHandled: Boolean;
+ begin
+ IsHandled := false;
+ if ProdOrderRoutingLine.Type = ProdOrderRoutingLine.Type::"Work Center" then
+ WorkCenter.Get(ProdOrderRoutingLine."No.");
+ OnBeforeInsertReqWkshLine(ProdOrderRoutingLine, WorkCenter, ReqLine, IsHandled, ProdOrderLine);
+ if IsHandled then
+ exit;
+
+ ProdOrderLine.CalcFields("Total Exp. Oper. Output (Qty.)");
+
+ ReqLine.SetSubcontracting(true);
+ ReqLine.BlockDynamicTracking(true);
+
+ if not CanCreateRequisitionLineFromProdOrderLine(ProdOrderLine."Item No.", ProdOrderLine."Variant Code") then
+ exit;
+
+ ReqLine.Init();
+ ReqLine."Line No." := ReqLine."Line No." + 10000;
+ ReqLine.Validate(Type, ReqLine.Type::Item);
+ ReqLine.Validate("No.", ProdOrderLine."Item No.");
+ ReqLine.Validate("Variant Code", ProdOrderLine."Variant Code");
+ ReqLine.Validate("Unit of Measure Code", ProdOrderLine."Unit of Measure Code");
+ ReqLine.Validate(Quantity, QtyToPurch);
+ GetGLSetup();
+ IsHandled := false;
+ OnBeforeValidateUnitCost(ReqLine, WorkCenter, IsHandled, ProdOrderLine, ProdOrderRoutingLine);
+ if not IsHandled then
+ if ReqLine.Quantity <> 0 then begin
+ if WorkCenter."Unit Cost Calculation" = WorkCenter."Unit Cost Calculation"::Units then
+ ReqLine.Validate(
+ ReqLine."Direct Unit Cost",
+ Round(
+ ProdOrderRoutingLine."Direct Unit Cost" * ProdOrderLine."Qty. per Unit of Measure",
+ GLSetup."Unit-Amount Rounding Precision"))
+ else
+ ReqLine.Validate(
+ ReqLine."Direct Unit Cost",
+ Round(
+ (ProdOrderRoutingLine."Expected Operation Cost Amt." - ProdOrderRoutingLine."Expected Capacity Ovhd. Cost") /
+ ProdOrderLine."Total Exp. Oper. Output (Qty.)",
+ GLSetup."Unit-Amount Rounding Precision"));
+ end else
+ ReqLine.Validate(ReqLine."Direct Unit Cost", 0);
+ ReqLine."Qty. per Unit of Measure" := 0;
+ ReqLine."Quantity (Base)" := 0;
+ ReqLine."Qty. Rounding Precision" := ProdOrderLine."Qty. Rounding Precision";
+ ReqLine."Qty. Rounding Precision (Base)" := ProdOrderLine."Qty. Rounding Precision (Base)";
+ ReqLine."Prod. Order No." := ProdOrderLine."Prod. Order No.";
+ ReqLine."Prod. Order Line No." := ProdOrderLine."Line No.";
+ ReqLine."Due Date" := ProdOrderRoutingLine."Ending Date";
+ ReqLine."Requester ID" := CopyStr(UserId(), 1, 50);
+ ReqLine."Location Code" := ProdOrderLine."Location Code";
+ ReqLine."Bin Code" := ProdOrderLine."Bin Code";
+ ReqLine."Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ ReqLine."Routing No." := ProdOrderRoutingLine."Routing No.";
+ ReqLine."Operation No." := ProdOrderRoutingLine."Operation No.";
+ ReqLine."Work Center No." := ProdOrderRoutingLine."Work Center No.";
+ ReqLine.Validate(ReqLine."Vendor No.", WorkCenter."Subcontractor No.");
+ ReqLine.Description := ProdOrderRoutingLine.Description;
+ ReqLine."Description 2" := ProdOrderRoutingLine."Description 2";
+ SetVendorItemNo();
+ OnAfterTransferProdOrderRoutingLine(ReqLine, ProdOrderRoutingLine);
+ // If purchase order already exist we will change this if possible
+ PurchLine.Reset();
+ PurchLine.SetCurrentKey("Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ PurchLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ PurchLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ PurchLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchLine.SetRange("Document Type", PurchLine."Document Type"::Order);
+ PurchLine.SetRange(Type, PurchLine.Type::Item);
+ PurchLine.SetRange("Planning Flexibility", PurchLine."Planning Flexibility"::Unlimited);
+ PurchLine.SetRange("Quantity Received", 0);
+ if PurchLine.FindFirst() then begin
+ ReqLine.Validate(ReqLine.Quantity, ReqLine.Quantity + PurchLine."Outstanding Quantity");
+ ReqLine."Quantity (Base)" := 0;
+ ReqLine."Replenishment System" := ReqLine."Replenishment System"::Purchase;
+ ReqLine."Ref. Order No." := PurchLine."Document No.";
+ ReqLine."Ref. Order Type" := ReqLine."Ref. Order Type"::Purchase;
+ ReqLine."Ref. Line No." := PurchLine."Line No.";
+ if PurchLine."Expected Receipt Date" = ReqLine."Due Date" then
+ ReqLine."Action Message" := ReqLine."Action Message"::"Change Qty."
+ else
+ ReqLine."Action Message" := ReqLine."Action Message"::"Resched. & Chg. Qty.";
+ ReqLine."Accept Action Message" := true;
+ end else begin
+ ReqLine."Replenishment System" := ReqLine."Replenishment System"::"Prod. Order";
+ ReqLine."Ref. Order No." := ProdOrderLine."Prod. Order No.";
+ ReqLine."Ref. Order Type" := ReqLine."Ref. Order Type"::"Prod. Order";
+ ReqLine."Ref. Order Status" := ProdOrderLine.Status;
+ ReqLine."Ref. Line No." := ProdOrderLine."Line No.";
+ ReqLine."Action Message" := ReqLine."Action Message"::New;
+ ReqLine."Accept Action Message" := true;
+ end;
+
+ if ReqLine."Ref. Order No." <> '' then
+ ReqLine.GetDimFromRefOrderLine(true);
+
+ OnBeforeReqWkshLineInsert(ReqLine, ProdOrderLine);
+ ReqLine.Insert();
+ end;
+
+ local procedure GetGLSetup()
+ begin
+ if not GLSetupRead then
+ GLSetup.Get();
+ GLSetupRead := true;
+ end;
+
+ local procedure DeleteRepeatedReqLines(ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ RequisitionLine: Record "Requisition Line";
+ begin
+ RequisitionLine.SetRange(Type, RequisitionLine.Type::Item);
+ RequisitionLine.SetRange("No.", ProdOrderLine."Item No.");
+ RequisitionLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ RequisitionLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ RequisitionLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ OnDeleteRepeatedReqLinesOnAfterRequisitionLineSetFilters(RequisitionLine, ProdOrderLine, ProdOrderRoutingLine);
+ RequisitionLine.DeleteAll(true);
+ end;
+
+ local procedure SetVendorItemNo()
+ var
+ ItemVendor: Record "Item Vendor";
+ begin
+ if ReqLine."No." = '' then
+ exit;
+
+ if Item."No." <> ReqLine."No." then begin
+ Item.SetLoadFields("No.");
+ Item.Get(ReqLine."No.");
+ end;
+
+ ItemVendor.Init();
+ ItemVendor."Vendor No." := ReqLine."Vendor No.";
+ ItemVendor."Variant Code" := ReqLine."Variant Code";
+ Item.FindItemVend(ItemVendor, ReqLine."Location Code");
+ ReqLine.Validate("Vendor Item No.", ItemVendor."Vendor Item No.");
+ OnAfterSetVendorItemNo(ReqLine, ItemVendor, Item);
+ end;
+
+ local procedure CanCreateRequisitionLineFromProdOrderLine(ItemNo: Code[20]; VariantCode: Code[20]): Boolean
+ begin
+ if not GuiAllowed() then
+ exit;
+
+ if ItemNo <> '' then begin
+ if Item."No." <> ItemNo then begin
+ Item.SetLoadFields("Production Blocked");
+ Item.Get(ItemNo);
+ end;
+ case Item."Production Blocked" of
+ Item."Production Blocked"::Output:
+ begin
+ ShowProdBlockedForItemConfirmation(ItemNo);
+ exit(false);
+ end;
+ end;
+ end;
+
+ if (ItemNo <> '') and (VariantCode <> '') then begin
+ if (ItemVariant."Item No." <> ItemNo) or (ItemVariant.Code <> VariantCode) then begin
+ ItemVariant.SetLoadFields("Production Blocked");
+ ItemVariant.Get(ItemNo, VariantCode);
+ end;
+ case ItemVariant."Production Blocked" of
+ ItemVariant."Production Blocked"::Output:
+ begin
+ ShowProdBlockedForItemVariantConfirmation(ItemNo, VariantCode);
+ exit(false);
+ end;
+ end;
+ end;
+
+ exit(true);
+ end;
+
+ local procedure CalculateSubContractRequirements()
+ begin
+ OnProdOrderRoutingLineOnBeforeCalculateSubContractRequirements(TempProdOrderRoutingLine);
+
+ if TempProdOrderRoutingLine.IsEmpty() then
+ exit;
+
+ TempProdOrderRoutingLine.SetCurrentKey("Prod. Order No.", "Routing Reference No.", Status, "Routing No.", "Operation No.");
+ if TempProdOrderRoutingLine.FindSet() then
+ repeat
+ Window.Update(2, TempProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderLine.SetCurrentKey(Status, "Prod. Order No.", "Routing No.", "Routing Reference No.");
+ ProdOrderLine.SetRange(Status, TempProdOrderRoutingLine.Status);
+ ProdOrderLine.SetRange("Prod. Order No.", TempProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderLine.SetRange("Routing No.", TempProdOrderRoutingLine."Routing No.");
+ ProdOrderLine.SetRange("Routing Reference No.", TempProdOrderRoutingLine."Routing Reference No.");
+ OnProdOrderRoutingLineOnAfterGetRecordOnAfterProdOrderLineSetFilters(ProdOrderLine, TempProdOrderRoutingLine);
+ if ProdOrderLine.FindSet() then begin
+ DeleteRepeatedReqLines(TempProdOrderRoutingLine);
+ repeat
+ BaseQtyToPurch :=
+ MfgCostCalcMgt.CalcQtyAdjdForRoutingScrap(
+ MfgCostCalcMgt.CalcQtyAdjdForBOMScrap(
+ ProdOrderLine."Quantity (Base)", ProdOrderLine."Scrap %"),
+ TempProdOrderRoutingLine."Scrap Factor % (Accumulated)", TempProdOrderRoutingLine."Fixed Scrap Qty. (Accum.)") -
+ (MfgCostCalcMgt.CalcOutputQtyBaseOnPurchOrder(ProdOrderLine, TempProdOrderRoutingLine) +
+ MfgCostCalcMgt.CalcActOutputQtyBase(ProdOrderLine, TempProdOrderRoutingLine));
+ QtyToPurch := Round(BaseQtyToPurch / ProdOrderLine."Qty. per Unit of Measure", UOMMgt.QtyRndPrecision());
+ OnAfterCalcQtyToPurch(ProdOrderLine, QtyToPurch);
+ if QtyToPurch > 0 then
+ InsertReqWkshLine(TempProdOrderRoutingLine);
+ until ProdOrderLine.Next() = 0;
+ end;
+ until TempProdOrderRoutingLine.Next() = 0;
+ end;
+
+ local procedure ShowProdBlockedForItemConfirmation(ItemNo: Code[20])
+ begin
+ if not Confirm(StrSubstNo(ProductionBlockedOutputItemQst, ItemNo)) then
+ Error('');
+ end;
+
+ local procedure ShowProdBlockedForItemVariantConfirmation(ItemNo: Code[20]; VariantCode: Code[20])
+ begin
+ if not Confirm(StrSubstNo(ProductionBlockedOutputItemVariantQst, VariantCode, ItemNo)) then
+ Error('');
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnAfterCalcQtyToPurch(ProdOrderLine: Record "Prod. Order Line"; var QtyToPurch: Decimal)
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnAfterTransferProdOrderRoutingLine(var RequisitionLine: Record "Requisition Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeInsertReqWkshLine(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var WorkCenter: Record "Work Center"; var ReqLine: Record "Requisition Line"; var IsHandled: Boolean; ProdOrderLine: Record "Prod. Order Line");
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeValidateUnitCost(var RequisitionLine: Record "Requisition Line"; var WorkCenter: Record "Work Center"; var IsHandled: Boolean; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeReqWkshLineInsert(var RequisitionLine: Record "Requisition Line"; ProdOrderLine: Record "Prod. Order Line")
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnDeleteRepeatedReqLinesOnAfterRequisitionLineSetFilters(var RequisitionLine: Record "Requisition Line"; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnProdOrderRoutingLineOnAfterGetRecordOnAfterProdOrderLineSetFilters(var ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnAfterSetVendorItemNo(var RequisitionLine: Record "Requisition Line"; ItemVendor: Record "Item Vendor"; Item: Record Item)
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnProdOrderRoutingLineOnBeforeCalculateSubContractRequirements(var TempProdOrderRoutingLine: Record "Prod. Order Routing Line" temporary)
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al
new file mode 100644
index 0000000000..f7e44f1fd2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcCreateSubCReturnOrder.Report.al
@@ -0,0 +1,420 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.UOM;
+using Microsoft.Inventory.Costing;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+
+report 99001502 "Subc. Create SubCReturnOrder"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Subcontracting Return Order';
+ ProcessingOnly = true;
+ dataset
+ {
+ dataitem("Purchase Header"; "Purchase Header")
+ {
+ DataItemTableView = sorting("Document Type", "No.") order(ascending);
+ dataitem("Purchase Line"; "Purchase Line")
+ {
+ DataItemLink = "Document No." = field("No.");
+ DataItemTableView = sorting("Document Type", "Document No.", "Line No.") order(ascending) where("Prod. Order No." = filter(<> ''));
+ trigger OnAfterGetRecord()
+ var
+ QtyToPost: Decimal;
+ begin
+ HandleComponentReturnForPurchLine("Purchase Line", true, QtyToPost);
+ HandleWIPReturnForPurchLine("Purchase Line", true);
+ end;
+ }
+ trigger OnAfterGetRecord()
+ begin
+ "Purchase Header".CalcFields("Subc. Order");
+ if not "Subc. Order" then
+ Error(OrderNoIsNotSubcontractorErr, PurchOrderNo);
+
+ if not CheckTransferToCreate() then
+ Error(NothingToCreateErr);
+
+ Vendor.Get("Purchase Header"."Buy-from Vendor No.");
+ end;
+
+ trigger OnPostDataItem()
+ begin
+ ShowDocument();
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ PurchOrderNo := CopyStr("Purchase Header".GetFilter("No."), 1, MaxStrLen(PurchOrderNo));
+ if PurchOrderNo = '' then
+ Error(WarningToSpecifyPurchOrderErr);
+ end;
+ }
+ }
+
+#if not CLEAN28
+ trigger OnInitReport()
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ begin
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ CurrReport.Quit();
+ end;
+
+#endif
+ var
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ PurchOrderNo: Code[20];
+ LineNo: Integer;
+ NothingToCreateErr: Label 'Nothing to create. No components or WIP items to return for the specified subcontracting order.';
+ OrderNoDoesNotExistInProdOrderErr: Label 'Operation %1 in the subcontracting order %2 does not exist in the routing %3 of the production order %4.', Comment = '%1=Operation No., %2=Purchase Order No., %3=Routing No., %4=Production Order No.';
+ OrderNoIsNotSubcontractorErr: Label 'Order %1 is not a Subcontractor work.', Comment = '%1=Purchase Order No.';
+ SubcLocationCodeMissingErr: Label 'The Subc. Location Code must be specified on Vendor %1 or the Subcontracting Purchase Order to create return orders.', Comment = '%1=Vendor No.';
+ WarningToSpecifyPurchOrderErr: Label 'Warning. Specify a Purchase Order No. for the Subcontractor work.';
+
+ local procedure InsertTransferHeader(TransferFromLocationCode: Code[10]; TransferToLocationCode: Code[10])
+ var
+ TransferRoute: Record "Transfer Route";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+ TransferHeader.Reset();
+ TransferHeader.SetRange("Source Subtype", TransferHeader."Source Subtype"::"2");
+ TransferHeader.SetRange("Source ID", "Purchase Header"."Buy-from Vendor No.");
+ TransferHeader.SetRange(Status, TransferHeader.Status::Open);
+ TransferHeader.SetRange("Completely Shipped", false);
+ TransferHeader.SetRange("Transfer-from Code", TransferFromLocationCode);
+ TransferHeader.SetRange("Transfer-to Code", TransferToLocationCode);
+ TransferHeader.SetRange("Subc. Return Order", true);
+ if not TransferHeader.FindFirst() then begin
+ TransferHeader.Init();
+ TransferHeader."No." := '';
+ TransferHeader.Insert(true);
+
+ TransferHeader.Validate("Transfer-from Code", TransferFromLocationCode);
+ TransferHeader.Validate("Transfer-to Code", TransferToLocationCode);
+
+ if not TransferRoute.Get(TransferFromLocationCode, TransferToLocationCode) or (TransferRoute."In-Transit Code" = '') then begin
+ SubcTransferManagement.CheckDirectTransferIsAllowedForTransferHeader(TransferHeader);
+ TransferHeader.Validate("Direct Transfer", true);
+ end;
+
+ TransferHeader."Subc. Source Type" := TransferHeader."Subc. Source Type"::Subcontracting;
+ TransferHeader."Source Subtype" := TransferHeader."Source Subtype"::"2";
+ TransferHeader."Source ID" := "Purchase Header"."Buy-from Vendor No.";
+ TransferHeader."Subcontr. Purch. Order No." := "Purchase Header"."No.";
+ TransferHeader."Subcontr. PO Line No." := "Purchase Line"."Line No.";
+ TransferHeader."Subc. Return Order" := true;
+ TransferHeader."Transfer-from Name" := Vendor.Name;
+ TransferHeader."Transfer-from Name 2" := Vendor."Name 2";
+ TransferHeader."Transfer-from Address" := Vendor.Address;
+ TransferHeader."Transfer-from Address 2" := Vendor."Address 2";
+ TransferHeader."Transfer-from Post Code" := Vendor."Post Code";
+ TransferHeader."Transfer-from City" := Vendor.City;
+ TransferHeader."Transfer-from County" := Vendor.County;
+ TransferHeader."Trsf.-from Country/Region Code" := Vendor."Country/Region Code";
+
+ TransferHeader.Modify();
+ LineNo := 0;
+ end else begin
+ TransferLine.SetRange("Document No.", TransferHeader."No.");
+ if TransferLine.FindLast() then
+ LineNo := TransferLine."Line No."
+ else
+ LineNo := 0;
+ end;
+ end;
+
+ local procedure CheckTransferToCreate(): Boolean
+ var
+ PurchaseLine: Record "Purchase Line";
+ QtyToPost: Decimal;
+ begin
+ PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchaseLine.SetRange("Document No.", PurchOrderNo);
+ PurchaseLine.SetFilter("Prod. Order No.", '<>''''');
+ PurchaseLine.SetFilter("Prod. Order Line No.", '<>0');
+ PurchaseLine.SetFilter("Operation No.", '<>0');
+ if PurchaseLine.FindSet() then
+ repeat
+ if HandleComponentReturnForPurchLine(PurchaseLine, false, QtyToPost) then
+ exit(true);
+ if HandleWIPReturnForPurchLine(PurchaseLine, false) then
+ exit(true);
+ until PurchaseLine.Next() = 0;
+
+ exit(false);
+ end;
+
+ local procedure HandleComponentReturnForPurchLine(PurchaseLine: Record "Purchase Line"; InsertLine: Boolean; var QtyToPost: Decimal): Boolean
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt.";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+ SubcFromLocationCode: Code[10];
+ AvailableToReturn: Decimal;
+ QtyPerUom: Decimal;
+ begin
+ if not ProdOrderLine.Get(ProdOrderLine.Status::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine.Get(ProdOrderRoutingLine.Status::Released, PurchaseLine."Prod. Order No.",
+ PurchaseLine."Routing Reference No.", PurchaseLine."Routing No.", PurchaseLine."Operation No.")
+ then
+ Error(OrderNoDoesNotExistInProdOrderErr, PurchaseLine."Operation No.", PurchOrderNo, PurchaseLine."Routing No.", PurchaseLine."Prod. Order No.");
+
+ if TransferLineAlreadyExists(PurchaseLine) then
+ exit(false);
+
+ Item.SetLoadFields("Base Unit of Measure", "Rounding Precision");
+ Item.Get(PurchaseLine."No.");
+ QtyPerUom := UnitofMeasureManagement.GetQtyPerUnitOfMeasure(Item, PurchaseLine."Unit of Measure Code");
+
+ ProdOrderComponent.SetCurrentKey(Status, "Prod. Order No.", "Routing Link Code");
+ ProdOrderComponent.SetRange(Status, ProdOrderComponent.Status::Released);
+ ProdOrderComponent.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No.");
+ ProdOrderComponent.SetRange("Prod. Order Line No.", PurchaseLine."Prod. Order Line No.");
+ ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ ProdOrderComponent.SetRange("Subc. Purchase Order Filter", PurchaseLine."Document No.");
+ ProdOrderComponent.SetRange("Component Supply Method", ProdOrderComponent."Component Supply Method"::"Transfer to Vendor");
+ if ProdOrderComponent.FindSet() then begin
+ GetTransferFromLocationCode(SubcFromLocationCode);
+ repeat
+ Item.Get(ProdOrderComponent."Item No.");
+ QtyToPost := MfgCostCalculationMgt.CalcActNeededQtyBase(ProdOrderLine, ProdOrderComponent,
+ Round(PurchaseLine."Outstanding Quantity" * QtyPerUom, UnitofMeasureManagement.QtyRndPrecision()));
+ ProdOrderComponent.CalcFields(
+ "Subc. Qty. in Transit (Base)", "Subc. Qty. transf. to Subcontr",
+ "RetQtyOnTransOrder (Base)", "RetQtyInTransit (Base)");
+
+ AvailableToReturn :=
+ Abs(ProdOrderComponent."Subc. Qty. in Transit (Base)") + Abs(ProdOrderComponent."Subc. Qty. transf. to Subcontr")
+ - SubcTransferManagement.CalcConsumedQtyAtSubcLocation(ProdOrderComponent)
+ - Abs(ProdOrderComponent."RetQtyOnTransOrder (Base)") - Abs(ProdOrderComponent."RetQtyInTransit (Base)");
+ if QtyToPost > AvailableToReturn then
+ QtyToPost := AvailableToReturn;
+ if QtyToPost > 0 then
+ if InsertLine then begin
+
+ InsertTransferHeader(SubcFromLocationCode, ProdOrderComponent."Subc. Original Location Code");
+
+ LineNo := LineNo + 10000;
+
+ TransferLine.Init();
+ TransferLine."Document No." := TransferHeader."No.";
+ TransferLine."Line No." := LineNo;
+ TransferLine.Validate("Item No.", ProdOrderComponent."Item No.");
+ TransferLine.Validate("Variant Code", ProdOrderComponent."Variant Code");
+ TransferLine."Unit of Measure Code" := ProdOrderComponent."Unit of Measure Code";
+ TransferLine."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+ TransferLine.Validate(Quantity, Round(QtyToPost / ProdOrderComponent."Qty. per Unit of Measure", Item."Rounding Precision", '>'));
+ TransferLine."Subc. Purch. Order No." := PurchaseLine."Document No.";
+ TransferLine."Subc. Purch. Order Line No." := PurchaseLine."Line No.";
+ TransferLine."Subc. Prod. Order No." := PurchaseLine."Prod. Order No.";
+ TransferLine."Subc. Prod. Order Line No." := PurchaseLine."Prod. Order Line No.";
+ TransferLine."Subc. Prod. Ord. Comp Line No." := ProdOrderComponent."Line No.";
+ TransferLine."Subc. Return Order" := true;
+
+ TransferLine."Subc. Routing No." := ProdOrderRoutingLine."Routing No.";
+ TransferLine."Subc. Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ TransferLine."Subc. Work Center No." := ProdOrderRoutingLine."Work Center No.";
+ TransferLine."Subc. Operation No." := ProdOrderRoutingLine."Operation No.";
+
+ TransferLine."Subc. Return Order" := true;
+
+ TransferLine.Insert();
+
+ SubcTransferManagement.TransferReservationEntryFromProdOrderCompToTransferOrder(TransferLine, ProdOrderComponent);
+
+ if ProdOrderComponent."Subc. Original Location Code" = '' then
+ ProdOrderComponent."Subc. Original Location Code" := ProdOrderComponent."Location Code";
+ 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;
+ ProdOrderComponent.Modify();
+
+ SubcTransferManagement.CreateReservEntryForTransferReceiptToProdOrderComp(TransferLine, ProdOrderComponent);
+ end else
+ exit(true);
+ until ProdOrderComponent.Next() = 0;
+ end;
+ exit(false);
+ end;
+
+ local procedure ShowDocument()
+ var
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ begin
+ Commit(); // Used for following call of Transfer Pages
+
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder("Purchase Line", true, true);
+ end;
+
+ local procedure TransferLineAlreadyExists(PurchaseLine: Record "Purchase Line"): Boolean
+ var
+ TransferLine2: Record "Transfer Line";
+ begin
+ if PurchaseLine."Document No." = '' then
+ exit(false);
+ TransferLine2.SetRange("Subc. Purch. Order No.", PurchaseLine."Document No.");
+ TransferLine2.SetRange("Subc. Purch. Order Line No.", PurchaseLine."Line No.");
+ TransferLine2.SetRange("Subc. Prod. Order No.", PurchaseLine."Prod. Order No.");
+ TransferLine2.SetRange("Subc. Prod. Order Line No.", PurchaseLine."Prod. Order Line No.");
+ TransferLine2.SetRange("Subc. Return Order", true);
+ exit(not TransferLine2.IsEmpty());
+ end;
+
+ local procedure GetTransferFromLocationCode(var SubcLocationCode: Code[10])
+ begin
+ SubcLocationCode := "Purchase Header"."Subc. Location Code";
+ if SubcLocationCode = '' then begin
+ SubcLocationCode := Vendor."Subc. Location Code";
+ if SubcLocationCode = '' then
+ Error(SubcLocationCodeMissingErr, Vendor."No.");
+ end;
+ end;
+
+ local procedure HandleWIPReturnForPurchLine(PurchaseLine: Record "Purchase Line"; InsertLine: Boolean): Boolean
+ var
+ Item: Record Item;
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ UOMManagement: Codeunit "Unit of Measure Management";
+ CompanyWHLocationCode: Code[10];
+ TransferFromLoc: Code[10];
+ WIPQtyBase: Decimal;
+ WIPQtyInUOM: Decimal;
+ WIPSourceQtyDict: Dictionary of [Code[10], Decimal];
+ WIPSourceLocationList: List of [Code[10]];
+ begin
+ if not ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.",
+ PurchaseLine."Routing Reference No.", PurchaseLine."Routing No.", PurchaseLine."Operation No.")
+ then
+ exit(false);
+
+ if not ProdOrderRoutingLine."Transfer WIP Item" then
+ exit(false);
+
+ if WIPReturnTransferLineAlreadyExists(PurchaseLine) then
+ exit(false);
+
+ CompanyWHLocationCode := ProdOrderLine."Location Code";
+ GetWIPReturnFromLocations(ProdOrderLine, ProdOrderRoutingLine, WIPSourceLocationList, WIPSourceQtyDict);
+
+ if WIPSourceLocationList.Count() = 0 then
+ exit(false);
+
+ if not InsertLine then
+ exit(true);
+
+ Item.SetLoadFields("Base Unit of Measure", "Rounding Precision", Description, "Description 2");
+ Item.Get(ProdOrderLine."Item No.");
+
+ foreach TransferFromLoc in WIPSourceLocationList do begin
+ WIPQtyBase := WIPSourceQtyDict.Get(TransferFromLoc);
+ if ProdOrderLine."Qty. per Unit of Measure" <> 0 then
+ WIPQtyInUOM := Round(WIPQtyBase / ProdOrderLine."Qty. per Unit of Measure", UOMManagement.QtyRndPrecision())
+ else
+ WIPQtyInUOM := Round(WIPQtyBase, UOMManagement.QtyRndPrecision());
+ if WIPQtyInUOM > 0 then begin
+ InsertTransferHeader(TransferFromLoc, CompanyWHLocationCode);
+ InsertWIPReturnTransferLine(PurchaseLine, ProdOrderLine, ProdOrderRoutingLine, WIPQtyInUOM);
+ end;
+ end;
+
+ exit(false);
+ end;
+
+ local procedure GetWIPReturnFromLocations(ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var WIPSourceLocationList: List of [Code[10]]; var WIPSourceQtyList: Dictionary of [Code[10], Decimal])
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ LocationCode: Code[10];
+ begin
+ WIPLedgerEntry.SetRange("Prod. Order Status", "Production Order Status"::Released);
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ WIPLedgerEntry.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ WIPLedgerEntry.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ WIPLedgerEntry.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ WIPLedgerEntry.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ WIPLedgerEntry.SetRange("In Transit", false);
+ if WIPLedgerEntry.FindSet() then
+ repeat
+ LocationCode := WIPLedgerEntry."Location Code";
+ if WIPSourceQtyList.ContainsKey(LocationCode) then
+ WIPSourceQtyList.Set(LocationCode, WIPSourceQtyList.Get(LocationCode) + WIPLedgerEntry."Quantity (Base)")
+ else begin
+ WIPSourceLocationList.Add(LocationCode);
+ WIPSourceQtyList.Add(LocationCode, WIPLedgerEntry."Quantity (Base)");
+ end;
+ until WIPLedgerEntry.Next() = 0;
+ end;
+
+ local procedure InsertWIPReturnTransferLine(PurchaseLine: Record "Purchase Line"; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"; WIPQty: Decimal)
+ begin
+ LineNo := LineNo + 10000;
+
+ TransferLine.Init();
+ TransferLine."Document No." := TransferHeader."No.";
+ TransferLine."Line No." := LineNo;
+ TransferLine.Validate("Item No.", ProdOrderLine."Item No.");
+ if ProdOrderLine."Variant Code" <> '' then
+ TransferLine.Validate("Variant Code", ProdOrderLine."Variant Code");
+ TransferLine.Validate("Unit of Measure Code", ProdOrderLine."Unit of Measure Code");
+ TransferLine.Validate("Transfer WIP Item", true);
+ TransferLine.Validate(Quantity, WIPQty);
+
+ if ProdOrderRoutingLine."Transfer Description" <> '' then
+ TransferLine.Description := ProdOrderRoutingLine."Transfer Description";
+
+ if ProdOrderRoutingLine."Transfer Description 2" <> '' then
+ TransferLine."Description 2" := ProdOrderRoutingLine."Transfer Description 2";
+
+ TransferLine."Subc. Purch. Order No." := PurchaseLine."Document No.";
+ TransferLine."Subc. Purch. Order Line No." := PurchaseLine."Line No.";
+ TransferLine."Subc. Prod. Order No." := ProdOrderLine."Prod. Order No.";
+ TransferLine."Subc. Prod. Order Line No." := ProdOrderLine."Line No.";
+ TransferLine."Subc. Routing No." := ProdOrderRoutingLine."Routing No.";
+ TransferLine."Subc. Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ TransferLine."Subc. Work Center No." := ProdOrderRoutingLine."Work Center No.";
+ TransferLine."Subc. Operation No." := ProdOrderRoutingLine."Operation No.";
+ TransferLine."Subc. Return Order" := true;
+
+ TransferLine.Insert();
+ end;
+
+ local procedure WIPReturnTransferLineAlreadyExists(PurchaseLine: Record "Purchase Line"): Boolean
+ var
+ TransferLineToCheck: Record "Transfer Line";
+ begin
+ TransferLineToCheck.SetRange("Subc. Purch. Order No.", PurchaseLine."Document No.");
+ TransferLineToCheck.SetRange("Subc. Prod. Order No.", PurchaseLine."Prod. Order No.");
+ TransferLineToCheck.SetRange("Subc. Prod. Order Line No.", PurchaseLine."Prod. Order Line No.");
+ TransferLineToCheck.SetRange("Subc. Operation No.", PurchaseLine."Operation No.");
+ TransferLineToCheck.SetRange("Transfer WIP Item", true);
+ TransferLineToCheck.SetRange("Subc. Return Order", true);
+ exit(not TransferLineToCheck.IsEmpty());
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcDetailedCalculation.Report.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcDetailedCalculation.Report.al
new file mode 100644
index 0000000000..cac50f34c3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcDetailedCalculation.Report.al
@@ -0,0 +1,501 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Enums;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Costing;
+using Microsoft.Inventory.Item;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+using System.Utilities;
+
+report 99001500 "Subc. Detailed Calculation"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Detailed Calculation';
+ DefaultLayout = RDLC;
+ RDLCLayout = 'src\Purchase\Rep99001500.SubcDetailedCalculation.rdl';
+ UsageCategory = ReportsAndAnalysis;
+
+ dataset
+ {
+ dataitem(Item; Item)
+ {
+ DataItemTableView = sorting("Low-Level Code");
+ RequestFilterFields = "No.";
+ column(BaseUnitOfMeasure_Item; "Base Unit of Measure")
+ {
+ }
+ column(CalculateDate; AsOfLbl + Format(CalculateDate))
+ {
+ }
+ column(CompanyName; CompanyProperty.DisplayName())
+ {
+ }
+ column(Description_Item; Description)
+ {
+ IncludeCaption = true;
+ }
+ column(DetailedCalculationCaption; DetailedCalculationCaptionLbl)
+ {
+ }
+ column(ItemFilter; ItemFilter)
+ {
+ }
+ column(ItemFilterCaption; Item.TableCaption() + ': ' + ItemFilter)
+ {
+ }
+ column(LotSize_Item; "Lot Size")
+ {
+ IncludeCaption = true;
+ }
+ column(No_Item; "No.")
+ {
+ IncludeCaption = true;
+ }
+ column(PageNoCaption; PageNoCaptionLbl)
+ {
+ }
+ column(PBOMVersionCode1; PBOMVersionCode[1])
+ {
+ }
+ column(ProductionBOMNo_Item; "Production BOM No.")
+ {
+ IncludeCaption = true;
+ }
+ column(RoutingNo_Item; "Routing No.")
+ {
+ IncludeCaption = true;
+ }
+ column(RtngVersionCode; RtngVersionCode)
+ {
+ }
+ column(TodayFormatted; Format(Today(), 0, 4))
+ {
+ }
+ column(UnitCostCaption; UnitCostCaptionLbl)
+ {
+ }
+ dataitem("Routing Line"; "Routing Line")
+ {
+ DataItemLink = "Routing No." = field("Routing No.");
+ DataItemTableView = sorting("Routing No.", "Version Code", "Operation No.");
+ column(CostTime; CostTime)
+ {
+ DecimalPlaces = 0 : 5;
+ }
+ column(CostTimeCaption; CostTimeCaptionLbl)
+ {
+ }
+ column(Description_RtngLine; Description)
+ {
+ IncludeCaption = true;
+ }
+ column(InRouting; InRouting)
+ {
+ }
+ column(No_RtngLine; "No.")
+ {
+ IncludeCaption = true;
+ }
+ column(OperationNo_RtngLine; "Operation No.")
+ {
+ IncludeCaption = true;
+ }
+ column(ProdTotalCost; ProdTotalCost)
+ {
+ AutoFormatType = 1;
+ }
+ column(ProdUnitCost; ProdUnitCost)
+ {
+ AutoFormatType = 2;
+ }
+ column(RunTime_RtngLine; "Run Time")
+ {
+ IncludeCaption = true;
+ }
+ column(SetupTime_RtngLine; "Setup Time")
+ {
+ IncludeCaption = true;
+ }
+ column(TotalCostCaption; TotalCostCaptionLbl)
+ {
+ }
+ column(Type_RtngLine; Type)
+ {
+ IncludeCaption = true;
+ }
+ column(VersionCode_RtngLine; "Version Code")
+ {
+ }
+ trigger OnAfterGetRecord()
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ UnitCostCalculationType: Enum "Unit Cost Calculation Type";
+ begin
+ ProdUnitCost := "Unit Cost per";
+
+ if "Routing Line".Type = "Routing Line".Type::"Work Center" then
+ WorkCenter.Get("Routing Line"."Work Center No.");
+ if ("Routing Line".Type = "Routing Line".Type::"Work Center") and
+ (WorkCenter."Subcontractor No." <> '')
+ then begin
+ SubcontractorPrice."Vendor No." := WorkCenter."Subcontractor No.";
+ SubcontractorPrice."Item No." := Item."No.";
+ SubcontractorPrice."Standard Task Code" := "Routing Line"."Standard Task Code";
+ SubcontractorPrice."Work Center No." := WorkCenter."No.";
+ SubcontractorPrice."Variant Code" := '';
+ SubcontractorPrice."Unit of Measure Code" := Item."Base Unit of Measure";
+ SubcontractorPrice."Starting Date" := CalculateDate;
+ SubcontractorPrice."Currency Code" := '';
+ SubcPriceManagement.SetRoutingPriceListCost(
+ SubcontractorPrice,
+ WorkCenter,
+ DirectUnitCost,
+ IndirectCostPct,
+ OverheadRate,
+ ProdUnitCost,
+ UnitCostCalculationType,
+ 1,
+ 1,
+ 1);
+ end else
+ MfgCostCalculationMgt.CalcRoutingCostPerUnit(
+ Type,
+ "No.",
+ DirectUnitCost,
+ IndirectCostPct,
+ OverheadRate, ProdUnitCost, UnitCostCalculationType);
+
+ CostTime :=
+ MfgCostCalculationMgt.CalculateCostTime(
+ MfgCostCalculationMgt.CalcQtyAdjdForBOMScrap(Item."Lot Size", Item."Scrap %"),
+ "Setup Time", "Setup Time Unit of Meas. Code",
+ "Run Time", "Run Time Unit of Meas. Code", "Lot Size",
+ "Scrap Factor % (Accumulated)", "Fixed Scrap Qty. (Accum.)",
+ "Work Center No.", UnitCostCalculationType, ManufacturingSetup."Cost Incl. Setup",
+ "Concurrent Capacities") /
+ Item."Lot Size";
+
+ ProdTotalCost := CostTime * ProdUnitCost;
+
+ FooterProdTotalCost += ProdTotalCost;
+ end;
+
+ trigger OnPostDataItem()
+ begin
+ InRouting := false;
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ Clear(ProdTotalCost);
+ SetRange("Version Code", RtngVersionCode);
+
+ InRouting := true;
+ end;
+ }
+ dataitem(BOMLoop; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ column(BaseUnitOfMeasureCaption; BaseUnitOfMeasureCaptionLbl)
+ {
+ }
+ column(DescriptionCaption; DescriptionCaptionLbl)
+ {
+ }
+ column(InBOM; InBOM)
+ {
+ }
+ column(NoCaption; NoCaptionLbl)
+ {
+ }
+ column(QuantityCaption; QuantityCaptionLbl)
+ {
+ }
+ column(TotalCost1Caption; TotalCost1CaptionLbl)
+ {
+ }
+ column(TypeCaption; TypeCaptionLbl)
+ {
+ }
+ dataitem(BOMComponentLine; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ MaxIteration = 1;
+ column(CompItemBaseUOM; CompItem."Base Unit of Measure")
+ {
+ }
+ column(CompItemUnitCost; CompItem."Unit Cost")
+ {
+ AutoFormatType = 2;
+ DecimalPlaces = 2 : 5;
+ }
+ column(CostTotal; CostTotal)
+ {
+ AutoFormatType = 1;
+ }
+ column(ProdBOMLineLevelDesc; ProdBOMLine[Level].Description)
+ {
+ }
+ column(ProdBOMLineLevelNo; ProdBOMLine[Level]."No.")
+ {
+ }
+ column(ProdBOMLineLevelQuantity; ProdBOMLine[Level].Quantity)
+ {
+ }
+ column(ProdBOMLineLevelType; Format(ProdBOMLine[Level].Type))
+ {
+ }
+ column(ShowLine; ProdBOMLine[Level].Type = ProdBOMLine[Level].Type::Item)
+ {
+ }
+ }
+ trigger OnAfterGetRecord()
+ var
+ UOMFactor: Decimal;
+ begin
+ while ProdBOMLine[Level].Next() = 0 do begin
+ Level := Level - 1;
+ if Level < 1 then
+ CurrReport.Break();
+ ProdBOMLine[Level].SetRange("Production BOM No.", PBOMNoList[Level]);
+ ProdBOMLine[Level].SetRange("Version Code", PBOMVersionCode[Level]);
+ end;
+
+ NextLevel := Level;
+ Clear(CompItem);
+
+ if Level = 1 then
+ UOMFactor :=
+ UnitofMeasureManagement.GetQtyPerUnitOfMeasure(Item, VersionManagement.GetBOMUnitOfMeasure(PBOMNoList[Level], PBOMVersionCode[Level]))
+ else
+ UOMFactor := 1;
+
+ CompItemQtyBase :=
+ MfgCostCalculationMgt.CalcCompItemQtyBase(ProdBOMLine[Level], CalculateDate, Quantity[Level], Item."Routing No.", Level = 1) /
+ UOMFactor;
+
+ case ProdBOMLine[Level].Type of
+ ProdBOMLine[Level].Type::Item:
+ begin
+ CompItem.Get(ProdBOMLine[Level]."No.");
+ ProdBOMLine[Level].Quantity := CompItemQtyBase / Item."Lot Size";
+ CostTotal := ProdBOMLine[Level].Quantity * CompItem."Unit Cost";
+ FooterCostTotal += CostTotal;
+ end;
+ ProdBOMLine[Level].Type::"Production BOM":
+ begin
+ NextLevel := Level + 1;
+ Clear(ProdBOMLine[NextLevel]);
+ PBOMNoList[NextLevel] := ProdBOMLine[Level]."No.";
+ PBOMVersionCode[NextLevel] :=
+ VersionManagement.GetBOMVersion(ProdBOMLine[Level]."No.", CalculateDate, false);
+ ProdBOMLine[NextLevel].SetRange("Production BOM No.", PBOMNoList[NextLevel]);
+ ProdBOMLine[NextLevel].SetRange("Version Code", PBOMVersionCode[NextLevel]);
+ ProdBOMLine[NextLevel].SetFilter("Starting Date", '%1|..%2', 0D, CalculateDate);
+ ProdBOMLine[NextLevel].SetFilter("Ending Date", '%1|%2..', 0D, CalculateDate);
+ Quantity[NextLevel] := CompItemQtyBase;
+ Level := NextLevel;
+ end;
+ end;
+ end;
+
+ trigger OnPostDataItem()
+ begin
+ InBOM := false;
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ if Item."Production BOM No." = '' then
+ CurrReport.Break();
+
+ Clear(CostTotal);
+ Level := 1;
+
+ ProductionBOMHeader.Get(PBOMNoList[Level]);
+
+ Clear(ProdBOMLine);
+ ProdBOMLine[Level].SetRange("Production BOM No.", PBOMNoList[Level]);
+ ProdBOMLine[Level].SetRange("Version Code", PBOMVersionCode[Level]);
+ ProdBOMLine[Level].SetFilter("Starting Date", '%1|..%2', 0D, CalculateDate);
+ ProdBOMLine[Level].SetFilter("Ending Date", '%1|%2..', 0D, CalculateDate);
+
+ Quantity[Level] := MfgCostCalculationMgt.CalcQtyAdjdForBOMScrap(Item."Lot Size", Item."Scrap %");
+
+ InBOM := true;
+ end;
+ }
+ dataitem(Footer; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ MaxIteration = 1;
+ column(Number_IntegerLine; Number)
+ {
+ }
+ }
+ dataitem("Integer"; "Integer")
+ {
+ DataItemTableView = sorting(Number);
+ MaxIteration = 1;
+ column(CostOfComponentsCaption; CostOfComponentsCaptionLbl)
+ {
+ }
+ column(CostOfProductionCaption; CostOfProductionCaptionLbl)
+ {
+ }
+ column(FooterCostTotal; FooterCostTotal)
+ {
+ }
+ column(FooterProdTotalCost; FooterProdTotalCost)
+ {
+ }
+ column(FormatCostTotal; CostTotal)
+ {
+ AutoFormatType = 1;
+ }
+ column(SingleLevelMfgOverheadCostCaption; SingleLevelMfgOverheadCostCaptionLbl)
+ {
+ }
+ column(SingleLevelMfgOvhd; SingleLevelMfgOvhd)
+ {
+ AutoFormatType = 1;
+ }
+ column(TotalProdTotalCost; ProdTotalCost)
+ {
+ AutoFormatType = 1;
+ }
+ column(UnitCost_Item; Item."Unit Cost")
+ {
+ AutoFormatType = 1;
+ }
+ }
+ trigger OnAfterGetRecord()
+ begin
+ if "Lot Size" = 0 then
+ "Lot Size" := 1;
+
+ if ("Production BOM No." = '') and
+ ("Routing No." = '')
+ then
+ CurrReport.Skip();
+
+ CostTotal := 0;
+
+ PBOMNoList[1] := "Production BOM No.";
+
+ if "Production BOM No." <> '' then
+ PBOMVersionCode[1] :=
+ VersionManagement.GetBOMVersion("Production BOM No.", CalculateDate, false);
+
+ if "Routing No." <> '' then
+ RtngVersionCode := VersionManagement.GetRtngVersion("Routing No.", CalculateDate, false);
+
+ SingleLevelMfgOvhd := Item."Single-Level Mfg. Ovhd Cost";
+
+ FooterProdTotalCost := 0;
+ FooterCostTotal := 0;
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ ItemFilter := Item.GetFilters();
+ end;
+ }
+ }
+ requestpage
+ {
+ layout
+ {
+ area(Content)
+ {
+ group(Options)
+ {
+ Caption = 'Options';
+ field(CalculationDate; CalculateDate)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Calculation Date';
+ ToolTip = 'Specifies the specific date for which to get the cost list. The standard entry in this field is the working date.';
+ }
+ }
+ }
+ }
+ actions
+ {
+ }
+ trigger OnOpenPage()
+ begin
+ CalculateDate := WorkDate();
+ end;
+ }
+ trigger OnInitReport()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ CurrReport.Quit();
+#endif
+ ManufacturingSetup.Get();
+ end;
+
+ var
+ CompItem: Record Item;
+ ManufacturingSetup: Record "Manufacturing Setup";
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProdBOMLine: array[99] of Record "Production BOM Line";
+ MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt.";
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+ VersionManagement: Codeunit VersionManagement;
+ InBOM: Boolean;
+ InRouting: Boolean;
+ PBOMNoList: array[99] of Code[20];
+ PBOMVersionCode: array[99] of Code[20];
+ RtngVersionCode: Code[20];
+ CalculateDate: Date;
+ CompItemQtyBase: Decimal;
+ CostTime: Decimal;
+ CostTotal: Decimal;
+ DirectUnitCost: Decimal;
+ FooterCostTotal: Decimal;
+ FooterProdTotalCost: Decimal;
+ IndirectCostPct: Decimal;
+ OverheadRate: Decimal;
+ ProdTotalCost: Decimal;
+ ProdUnitCost: Decimal;
+ Quantity: array[99] of Decimal;
+ SingleLevelMfgOvhd: Decimal;
+ Level: Integer;
+ NextLevel: Integer;
+ AsOfLbl: Label 'As of ';
+ BaseUnitOfMeasureCaptionLbl: Label 'Base Unit of Measure Code';
+ CostOfComponentsCaptionLbl: Label 'Cost of Components';
+ CostOfProductionCaptionLbl: Label 'Cost of Production';
+ CostTimeCaptionLbl: Label 'Cost Time';
+ DescriptionCaptionLbl: Label 'Description';
+ DetailedCalculationCaptionLbl: Label 'Detailed Calculation';
+ NoCaptionLbl: Label 'No.';
+ PageNoCaptionLbl: Label 'Page';
+ QuantityCaptionLbl: Label 'Quantity (Base)';
+ SingleLevelMfgOverheadCostCaptionLbl: Label 'Single-Level Mfg. Overhead Cost';
+ TotalCost1CaptionLbl: Label 'Total Cost';
+ TotalCostCaptionLbl: Label 'Total Cost';
+ TypeCaptionLbl: Label 'Type';
+ UnitCostCaptionLbl: Label 'Unit Cost';
+ ItemFilter: Text;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcItemChargeAssPurchExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcItemChargeAssPurchExt.Codeunit.al
new file mode 100644
index 0000000000..ffa49ba5cd
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcItemChargeAssPurchExt.Codeunit.al
@@ -0,0 +1,55 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+using Microsoft.Purchases.History;
+
+codeunit 99001536 "Subc. ItemChargeAssPurchExt"
+{
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Charge Assgnt. (Purch.)", OnBeforeCreateRcptChargeAssgnt, '', false, false)]
+ local procedure "Item Charge Assgnt. (Purch.)_OnBeforeCreateRcptChargeAssgnt"(var FromPurchRcptLine: Record "Purch. Rcpt. Line"; ItemChargeAssignmentPurch: Record "Item Charge Assignment (Purch)"; var IsHandled: Boolean)
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if FromPurchRcptLine."Subc. Prod. Order No." = '' then
+ exit;
+
+ IsHandled := true;
+ CreateRcptChargeAssgnt(FromPurchRcptLine, ItemChargeAssignmentPurch);
+ end;
+
+ local procedure CreateRcptChargeAssgnt(var FromPurchRcptLine: Record "Purch. Rcpt. Line"; ItemChargeAssignmentPurch: Record "Item Charge Assignment (Purch)")
+ var
+ ItemChargeAssignmentPurch2: Record "Item Charge Assignment (Purch)";
+ ItemChargeAssgntPurch: Codeunit "Item Charge Assgnt. (Purch.)";
+ NextLine: Integer;
+ begin
+ NextLine := ItemChargeAssignmentPurch."Line No.";
+ ItemChargeAssignmentPurch2.SetRange("Document Type", ItemChargeAssignmentPurch."Document Type");
+ ItemChargeAssignmentPurch2.SetRange("Document No.", ItemChargeAssignmentPurch."Document No.");
+ ItemChargeAssignmentPurch2.SetRange("Document Line No.", ItemChargeAssignmentPurch."Document Line No.");
+ ItemChargeAssignmentPurch2.SetRange("Applies-to Doc. Type", "Purchase Applies-to Document Type"::Receipt);
+ repeat
+ ItemChargeAssignmentPurch2.SetRange("Applies-to Doc. No.", FromPurchRcptLine."Document No.");
+ ItemChargeAssignmentPurch2.SetRange("Applies-to Doc. Line No.", FromPurchRcptLine."Line No.");
+ if ItemChargeAssignmentPurch2.IsEmpty() then
+ ItemChargeAssgntPurch.InsertItemChargeAssignment(
+ ItemChargeAssignmentPurch, "Purchase Applies-to Document Type"::Receipt,
+ FromPurchRcptLine."Document No.", FromPurchRcptLine."Line No.",
+ FromPurchRcptLine."No.", FromPurchRcptLine.Description, NextLine);
+ until FromPurchRcptLine.Next() = 0;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPOSubform.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPOSubform.PageExt.al
new file mode 100644
index 0000000000..039907685a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPOSubform.PageExt.al
@@ -0,0 +1,114 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+
+pageextension 99001524 "Subc. PO Subform" extends "Purchase Order Subform"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("F&unctions")
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+ Visible = HasSubcontractingContext;
+ action("Production Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order';
+ Image = Production;
+ ToolTip = 'View the related production order.';
+ trigger OnAction()
+ begin
+ ShowProductionOrder(Rec);
+ end;
+ }
+ action("Production Order Routing")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Routing';
+ Image = Route;
+ ToolTip = 'View the related production order routing.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderRouting(Rec);
+ end;
+ }
+ action("Production Order Components")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Components';
+ Image = Components;
+ ToolTip = 'View the related production order components.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderComponents(Rec);
+ end;
+ }
+ action("Transfer Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Order';
+ Image = TransferOrder;
+ ToolTip = 'View the related transfer order.';
+ trigger OnAction()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, false);
+ end;
+ }
+ action("Return Transfer Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Return Transfer Order';
+ Image = ReturnRelated;
+ ToolTip = 'View the related return transfer order.';
+ trigger OnAction()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, true);
+ end;
+ }
+ }
+ }
+ }
+
+ var
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ HasSubcontractingContext: Boolean;
+
+ internal procedure SetIsSubcontracting(IsSubcontractingRelated: Boolean)
+ begin
+ HasSubcontractingContext := IsSubcontractingRelated;
+ CurrPage.Update();
+ end;
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPriceManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPriceManagement.Codeunit.al
new file mode 100644
index 0000000000..59140635fe
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPriceManagement.Codeunit.al
@@ -0,0 +1,585 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.Currency;
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Foundation.Enums;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Costing;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+
+codeunit 99001508 "Subc. Price Management"
+{
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ procedure ApplySubcontractorPricingToProdOrderRouting(var ProdOrderLine: Record "Prod. Order Line"; var RoutingLine: Record "Routing Line"; var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not ManufacturingSetup.Get() then
+ exit;
+
+ if ProdOrderRoutingLine.Type <> "Capacity Type Routing"::"Work Center" then
+ exit;
+
+ WorkCenter.Get(ProdOrderRoutingLine."Work Center No.");
+
+ if WorkCenter."Subcontractor No." = '' then
+ exit;
+
+ SetSubcontractorPriceForPriceCalculation(
+ SubcontractorPrice,
+ WorkCenter."Subcontractor No.",
+ ProdOrderLine."Item No.",
+ ProdOrderLine."Variant Code",
+ ProdOrderRoutingLine."Standard Task Code",
+ WorkCenter."No.",
+ ProdOrderLine."Unit of Measure Code",
+ WorkDate());
+
+ SetRoutingPriceListCost(
+ SubcontractorPrice,
+ WorkCenter,
+ ProdOrderRoutingLine."Direct Unit Cost",
+ ProdOrderRoutingLine."Indirect Cost %",
+ ProdOrderRoutingLine."Overhead Rate",
+ ProdOrderRoutingLine."Unit Cost per",
+ ProdOrderRoutingLine."Unit Cost Calculation",
+ ProdOrderLine.Quantity,
+ ProdOrderLine."Qty. per Unit of Measure",
+ ProdOrderLine."Quantity (Base)");
+ end;
+
+ procedure ApplySubcontractorPricingToPlanningRouting(var RequisitionLine: Record "Requisition Line"; var RoutingLine: Record "Routing Line"; var PlanningRoutingLine: Record "Planning Routing Line")
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not ManufacturingSetup.Get() then
+ exit;
+
+ if RoutingLine.Type <> "Capacity Type Routing"::"Work Center" then
+ exit;
+
+ WorkCenter.Get(RoutingLine."Work Center No.");
+
+ if WorkCenter."Subcontractor No." = '' then
+ exit;
+
+ SetSubcontractorPriceForPriceCalculation(
+ SubcontractorPrice,
+ WorkCenter."Subcontractor No.",
+ RequisitionLine."No.",
+ RequisitionLine."Variant Code",
+ PlanningRoutingLine."Standard Task Code",
+ WorkCenter."No.",
+ RequisitionLine."Unit of Measure Code",
+ RequisitionLine."Order Date");
+
+ SetRoutingPriceListCost(
+ SubcontractorPrice,
+ WorkCenter,
+ PlanningRoutingLine."Direct Unit Cost",
+ PlanningRoutingLine."Indirect Cost %",
+ PlanningRoutingLine."Overhead Rate",
+ PlanningRoutingLine."Unit Cost per",
+ PlanningRoutingLine."Unit Cost Calculation",
+ RequisitionLine.Quantity,
+ RequisitionLine."Qty. per Unit of Measure",
+ RequisitionLine."Quantity (Base)");
+
+ PlanningRoutingLine.Validate(PlanningRoutingLine."Direct Unit Cost");
+
+ PlanningRoutingLine.UpdateDatetime();
+ end;
+
+ procedure CalcStandardCostOnAfterCalcRtngLineCost(RoutingLine: Record "Routing Line"; MfgItemQtyBase: Decimal; var SLSub: Decimal)
+ var
+ Item: Record Item;
+ WorkCenter: Record "Work Center";
+ MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt.";
+ SubcSessionState: Codeunit "Subc. Session State";
+ ItemRecordID: RecordId;
+ RecRef: RecordRef;
+ CalculationDate: Date;
+ CostTime: Decimal;
+ DirectUnitCost: Decimal;
+ IndirCostPct: Decimal;
+ OvhdRate: Decimal;
+ UnitCost: Decimal;
+ UnitCostCalculationType: Enum "Unit Cost Calculation Type";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if RoutingLine.Type <> "Capacity Type Routing"::"Work Center" then
+ exit;
+
+ if RoutingLine."No." = '' then
+ exit;
+
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ if not WorkCenter.Get(RoutingLine."No.") then
+ exit;
+
+ if WorkCenter."Subcontractor No." = '' then
+ exit;
+
+ SubcSessionState.GetRecordID('OnBeforeCalcRoutingLineCosts', ItemRecordID);
+ if ItemRecordID.TableNo() <> 0 then
+ RecRef := ItemRecordID.GetRecord()
+ else begin
+ SubcSessionState.GetRecordID('OnCalcMfgItemOnBeforeCalcRtngCost', ItemRecordID);
+ if ItemRecordID.TableNo() = 0 then
+ exit;
+ RecRef := ItemRecordID.GetRecord()
+ end;
+
+ RecRef.SetTable(Item);
+ CalculationDate := SubcSessionState.GetDate('OnAfterSetProperties');
+ if CalculationDate = 0D then
+ CalculationDate := WorkDate();
+
+ UnitCost := RoutingLine."Unit Cost per";
+ CalcRtngCostPerUnit(RoutingLine."No.", DirectUnitCost, IndirCostPct, OvhdRate, UnitCost, UnitCostCalculationType, Item, RoutingLine."Standard Task Code", CalculationDate);
+
+ ManufacturingSetup.SetLoadFields("Cost Incl. Setup");
+ ManufacturingSetup.Get();
+
+ CostTime :=
+ MfgCostCalculationMgt.CalculateCostTime(
+ MfgItemQtyBase,
+ RoutingLine."Setup Time", RoutingLine."Setup Time Unit of Meas. Code",
+ RoutingLine."Run Time", RoutingLine."Run Time Unit of Meas. Code", RoutingLine."Lot Size",
+ RoutingLine."Scrap Factor % (Accumulated)", RoutingLine."Fixed Scrap Qty. (Accum.)",
+ RoutingLine."Work Center No.", UnitCostCalculationType, ManufacturingSetup."Cost Incl. Setup",
+ RoutingLine."Concurrent Capacities");
+ SLSub := (CostTime * DirectUnitCost);
+
+ SubcSessionState.ClearAllDictionariesForKey('OnBeforeCalcRoutingLineCosts');
+ SubcSessionState.ClearAllDictionariesForKey('OnCalcMfgItemOnBeforeCalcRtngCost');
+ end;
+
+ local procedure CalcRtngCostPerUnit(No: Code[20]; var DirUnitCost: Decimal; var IndirCostPct: Decimal; var OvhdRate: Decimal; var UnitCost: Decimal; var UnitCostCalculationType: Enum "Unit Cost Calculation Type"; Item: Record Item; StandardTaskCode: Code[10]; CalculationDate: Date)
+ var
+ SubContractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ begin
+ WorkCenter.Get(No);
+
+ SetSubcontractorPriceForPriceCalculation(
+ SubContractorPrice,
+ WorkCenter."Subcontractor No.",
+ Item."No.",
+ '',
+ StandardTaskCode,
+ WorkCenter."No.",
+ Item."Base Unit of Measure",
+ CalculationDate);
+
+ SetRoutingPriceListCost(
+ SubContractorPrice,
+ WorkCenter,
+ DirUnitCost,
+ IndirCostPct,
+ OvhdRate,
+ UnitCost,
+ UnitCostCalculationType,
+ 1,
+ 1,
+ 1);
+ end;
+
+ procedure GetSubcPriceList(var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ VendorNo: Code[20];
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (ProdOrderRoutingLine.Type <> "Capacity Type"::"Work Center") then
+ exit;
+
+ WorkCenter.Get(ProdOrderRoutingLine."No.");
+
+ if (WorkCenter."Subcontractor No." = '') and (ProdOrderRoutingLine."Vendor No. Subc. Price" = '') then
+ exit;
+
+ VendorNo := WorkCenter."Subcontractor No.";
+ if ProdOrderRoutingLine."Vendor No. Subc. Price" <> '' then
+ VendorNo := ProdOrderRoutingLine."Vendor No. Subc. Price";
+
+ GetLine(ProdOrderLine, ProdOrderRoutingLine);
+
+ SetSubcontractorPriceForPriceCalculation(
+ SubcontractorPrice,
+ VendorNo,
+ ProdOrderLine."Item No.",
+ ProdOrderLine."Variant Code",
+ ProdOrderRoutingLine."Standard Task Code",
+ WorkCenter."No.",
+ ProdOrderLine."Unit of Measure Code",
+ WorkDate());
+
+ SetRoutingPriceListCost(
+ SubcontractorPrice,
+ WorkCenter,
+ ProdOrderRoutingLine."Direct Unit Cost",
+ ProdOrderRoutingLine."Indirect Cost %",
+ ProdOrderRoutingLine."Overhead Rate",
+ ProdOrderRoutingLine."Unit Cost per",
+ ProdOrderRoutingLine."Unit Cost Calculation",
+ ProdOrderLine.Quantity,
+ ProdOrderLine."Qty. per Unit of Measure",
+ ProdOrderLine."Quantity (Base)");
+ end;
+
+ local procedure GetLine(var ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ ProdOrderLine.SetRange(Status, ProdOrderRoutingLine.Status);
+ ProdOrderLine.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ ProdOrderLine.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ ProdOrderLine.FindFirst();
+ end;
+
+ procedure SetRoutingPriceListCost(var InSubcontractorPrice: Record "Subcontractor Price"; WorkCenter: Record "Work Center"; var DirUnitCost: Decimal; var IndirCostPct: Decimal; var OvhdRate: Decimal; var UnitCost: Decimal; var UnitCostCalculationType: Enum "Unit Cost Calculation Type"; QtyUoM: Decimal; ProdQtyPerUom: Decimal; QtyBase: Decimal)
+ var
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ SubcontractorPrice: Record "Subcontractor Price";
+ PriceListUOM: Code[10];
+ DirectCost: Decimal;
+ PriceListCost: Decimal;
+ PriceListQty: Decimal;
+ PriceListQtyPerUOM: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PriceListQtyPerUOM := 0;
+ PriceListQty := 0;
+ PriceListCost := 0;
+ DirectCost := 0;
+ PriceListUOM := '';
+
+ UnitCostCalculationType := WorkCenter."Unit Cost Calculation";
+ IndirCostPct := WorkCenter."Indirect Cost %";
+ OvhdRate := WorkCenter."Overhead Rate";
+ if WorkCenter."Specific Unit Cost" then
+ DirUnitCost := (UnitCost - OvhdRate) / (1 + IndirCostPct / 100)
+ else begin
+ DirUnitCost := WorkCenter."Direct Unit Cost";
+ UnitCost := WorkCenter."Unit Cost";
+ end;
+
+ if InSubcontractorPrice."Starting Date" = 0D then
+ InSubcontractorPrice."Starting Date" := WorkDate();
+
+ SubcontractorPrice.Reset();
+ SubcontractorPrice.SetRange("Vendor No.", InSubcontractorPrice."Vendor No.");
+ SubcontractorPrice.SetRange("Work Center No.", InSubcontractorPrice."Work Center No.");
+ SubcontractorPrice.SetRange("Item No.", InSubcontractorPrice."Item No.");
+ SubcontractorPrice.SetFilter("Variant Code", '%1|%2', InSubcontractorPrice."Variant Code", '');
+ SubcontractorPrice.SetFilter("Unit of Measure Code", '%1|%2', InSubcontractorPrice."Unit of Measure Code", '');
+ SubcontractorPrice.SetFilter("Standard Task Code", '%1|%2', InSubcontractorPrice."Standard Task Code", '');
+ SubcontractorPrice.SetFilter("Currency Code", '%1|%2', InSubcontractorPrice."Currency Code", '');
+ SubcontractorPrice.SetRange("Starting Date", 0D, InSubcontractorPrice."Starting Date");
+ SubcontractorPrice.SetFilter("Ending Date", '>=%1|%2', InSubcontractorPrice."Starting Date", 0D);
+ if SubcontractorPrice.FindLast() then begin
+ if SubcontractorPrice."Unit of Measure Code" = InSubcontractorPrice."Unit of Measure Code" then begin
+ PriceListQtyPerUOM := ProdQtyPerUom;
+ PriceListQty := QtyUoM;
+ PriceListUOM := SubcontractorPrice."Unit of Measure Code";
+ end else
+ GetUOMPrice(InSubcontractorPrice."Item No.", QtyBase, SubcontractorPrice, PriceListUOM, PriceListQtyPerUOM, PriceListQty);
+
+ GetPriceByUOM(SubcontractorPrice, PriceListQty, PriceListCost);
+ if PriceListCost <> 0 then begin
+ ConvertPriceToUOM(InSubcontractorPrice."Unit of Measure Code", ProdQtyPerUom, PriceListUOM, PriceListQtyPerUOM, PriceListCost, DirectCost);
+ if SubcontractorPrice."Currency Code" <> '' then
+ ConvertPriceFromCurrency(SubcontractorPrice."Currency Code", InSubcontractorPrice."Starting Date", DirectCost);
+ GeneralLedgerSetup.Get();
+ DirectCost := Round(DirectCost, GeneralLedgerSetup."Unit-Amount Rounding Precision");
+ DirUnitCost := DirectCost;
+ UnitCost := (DirUnitCost * (1 + IndirCostPct / 100) + OvhdRate);
+ end;
+ end;
+ end;
+
+ local procedure GetUOMPrice(ItemNo: Code[20]; QtyBase: Decimal; SubcontractorPrice: Record "Subcontractor Price"; var PriceListUOM: Code[10]; var PriceListQtyPerUOM: Decimal; var PriceListQty: Decimal)
+ var
+ Item: Record Item;
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+ begin
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get(ItemNo);
+ PriceListQtyPerUOM := UnitofMeasureManagement.GetQtyPerUnitOfMeasure(Item, SubcontractorPrice."Unit of Measure Code");
+
+ if (PriceListQtyPerUOM = 1) and (SubcontractorPrice."Unit of Measure Code" = '') then
+ PriceListUOM := Item."Base Unit of Measure"
+ else
+ PriceListUOM := SubcontractorPrice."Unit of Measure Code";
+
+ PriceListQty := QtyBase / PriceListQtyPerUOM;
+ end;
+
+ local procedure GetPriceByUOM(var SubcontractorPrice: Record "Subcontractor Price"; PriceListQty: Decimal; var PriceListCost: Decimal)
+ begin
+ SubcontractorPrice.SetRange(SubcontractorPrice."Minimum Quantity", 0, PriceListQty);
+ SubcontractorPrice.SetRange(SubcontractorPrice."Unit of Measure Code", SubcontractorPrice."Unit of Measure Code");
+ if SubcontractorPrice.FindLast() then begin
+ PriceListCost := SubcontractorPrice."Direct Unit Cost";
+ if PriceListCost <> 0 then
+ if (PriceListCost * PriceListQty) < SubcontractorPrice."Minimum Amount" then
+ PriceListCost := SubcontractorPrice."Minimum Amount" / PriceListQty;
+ end;
+ end;
+
+ procedure ConvertPriceToUOM(ProdUOM: Code[10]; ProdQtyPerUoM: Decimal; PriceListUOM: Code[10]; PriceListQtyPerUOM: Decimal; PriceListCost: Decimal; var DirectCost: Decimal)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ProdUOM <> PriceListUOM then begin
+ DirectCost := PriceListCost / PriceListQtyPerUOM;
+ DirectCost := DirectCost * ProdQtyPerUoM;
+ end else
+ DirectCost := PriceListCost;
+ end;
+
+ local procedure ConvertPriceToCurrency(TargetCurrencyCode: Code[10]; PriceListCurrencyCode: Code[10]; PriceListCost: Decimal; var DirectCost: Decimal)
+ var
+ Currency: Record Currency;
+ CurrencyExchangeRate: Record "Currency Exchange Rate";
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ UnitAmtRngPrecision: Decimal;
+ begin
+ if TargetCurrencyCode = '' then begin
+ GeneralLedgerSetup.SetLoadFields("Unit-Amount Rounding Precision");
+ GeneralLedgerSetup.Get();
+ UnitAmtRngPrecision := GeneralLedgerSetup."Unit-Amount Rounding Precision";
+ end else begin
+ Currency.SetLoadFields("Unit-Amount Rounding Precision");
+ Currency.Get(TargetCurrencyCode);
+ Currency.TestField("Unit-Amount Rounding Precision");
+ UnitAmtRngPrecision := Currency."Unit-Amount Rounding Precision";
+ end;
+
+ case true of
+ TargetCurrencyCode = PriceListCurrencyCode:
+ DirectCost := Round(DirectCost, UnitAmtRngPrecision);
+ (TargetCurrencyCode <> '') and (PriceListCurrencyCode = ''):
+ DirectCost := CurrencyExchangeRate.ExchangeAmtLCYToFCY(
+ WorkDate(), TargetCurrencyCode, PriceListCost,
+ CurrencyExchangeRate.ExchangeRate(WorkDate(), TargetCurrencyCode));
+ (TargetCurrencyCode = '') and (PriceListCurrencyCode <> ''):
+ DirectCost := CurrencyExchangeRate.ExchangeAmtFCYToLCY(
+ WorkDate(), PriceListCurrencyCode, PriceListCost,
+ CurrencyExchangeRate.ExchangeRate(WorkDate(), PriceListCurrencyCode));
+ (TargetCurrencyCode <> '') and (PriceListCurrencyCode <> ''):
+ DirectCost := CurrencyExchangeRate.ExchangeAmtFCYToFCY(
+ WorkDate(), PriceListCurrencyCode, TargetCurrencyCode, PriceListCost);
+ end;
+
+ DirectCost := Round(DirectCost, UnitAmtRngPrecision);
+ end;
+
+ local procedure ConvertPriceFromCurrency(CurrencyCode: Code[10]; OrderDate: Date; var DirectCost: Decimal)
+ var
+ Currency: Record Currency;
+ CurrencyExchangeRate: Record "Currency Exchange Rate";
+ begin
+ Currency.Get(CurrencyCode);
+ DirectCost := CurrencyExchangeRate.ExchangeAmtFCYToLCY(
+ OrderDate, CurrencyCode, DirectCost,
+ CurrencyExchangeRate.ExchangeRate(OrderDate, CurrencyCode));
+ end;
+
+ procedure GetSubcPriceForReqLine(var RequisitionLine: Record "Requisition Line"; FixedUOM: Code[10])
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ PriceListUOM: Code[10];
+ OrderDate: Date;
+ DirectCost: Decimal;
+ PriceListCost: Decimal;
+ PriceListQty: Decimal;
+ PriceListQtyPerUOM: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ OrderDate := RequisitionLine."Order Date";
+ if OrderDate = 0D then
+ OrderDate := WorkDate();
+
+ SubcontractorPrice.SetRange("Vendor No.", RequisitionLine."Vendor No.");
+ SubcontractorPrice.SetRange("Work Center No.", RequisitionLine."Work Center No.");
+ SubcontractorPrice.SetRange("Item No.", RequisitionLine."No.");
+ SubcontractorPrice.SetFilter("Variant Code", '%1|%2', RequisitionLine."Variant Code", '');
+ if FixedUOM <> '' then
+ SubcontractorPrice.SetRange("Unit of Measure Code", FixedUOM)
+ else
+ if RequisitionLine."Unit of Measure Code" <> '' then
+ SubcontractorPrice.SetFilter("Unit of Measure Code", '%1|%2', RequisitionLine."Unit of Measure Code", '');
+ SubcontractorPrice.SetFilter("Standard Task Code", '%1|%2', RequisitionLine."Subc. Standard Task Code", '');
+ SubcontractorPrice.SetFilter("Currency Code", '%1|%2', RequisitionLine."Currency Code", '');
+ SubcontractorPrice.SetRange("Starting Date", 0D, OrderDate);
+ SubcontractorPrice.SetFilter("Ending Date", '>=%1|%2', OrderDate, 0D);
+
+
+ if SubcontractorPrice.FindLast() then begin
+ if SubcontractorPrice."Unit of Measure Code" = RequisitionLine."Unit of Measure Code" then begin
+ PriceListQtyPerUOM := RequisitionLine.GetQuantityForUOM();
+ PriceListQty := RequisitionLine.Quantity;
+ PriceListUOM := RequisitionLine."Unit of Measure Code";
+ end else
+ GetUOMPrice(RequisitionLine."No.", RequisitionLine.GetQuantityBase(), SubcontractorPrice, PriceListUOM, PriceListQtyPerUOM, PriceListQty);
+
+ GetPriceByUOM(SubcontractorPrice, PriceListQty, PriceListCost);
+ if PriceListCost <> 0 then begin
+ ConvertPriceToUOM(RequisitionLine."Unit of Measure Code", RequisitionLine.GetQuantityForUOM(), PriceListUOM, PriceListQtyPerUOM, PriceListCost, DirectCost);
+ ConvertPriceToCurrency(RequisitionLine."Currency Code", SubcontractorPrice."Currency Code", PriceListCost, DirectCost);
+ end;
+ RequisitionLine."Direct Unit Cost" := DirectCost;
+ RequisitionLine."Subc. Pricelist Cost" := PriceListCost;
+ RequisitionLine."Subc. UoM for Pricelist" := PriceListUOM;
+ RequisitionLine."Base UM Qty/PL UM Qty" := PriceListQtyPerUOM;
+ if RequisitionLine."Base UM Qty/PL UM Qty" = 0 then
+ RequisitionLine."Base UM Qty/PL UM Qty" := 1;
+ if RequisitionLine."Unit of Measure Code" = RequisitionLine."Subc. UoM for Pricelist" then
+ RequisitionLine."PL UM Qty/Base UM Qty" := RequisitionLine.Quantity
+ else
+ RequisitionLine."PL UM Qty/Base UM Qty" := RequisitionLine.GetQuantityBase() / RequisitionLine."Base UM Qty/PL UM Qty";
+ if RequisitionLine."PL UM Qty/Base UM Qty" = 0 then
+ RequisitionLine."PL UM Qty/Base UM Qty" := 1;
+ end;
+ end;
+
+ procedure GetSubcPriceForPurchLine(var PurchaseLine: Record "Purchase Line")
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ SubcontractorPrice: Record "Subcontractor Price";
+ PriceListUOM: Code[10];
+ OrderDate: Date;
+ DirectCost, PriceListCost, PriceListQty, PriceListQtyPerUOM : Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ OrderDate := PurchaseLine."Order Date";
+ if OrderDate = 0D then
+ OrderDate := WorkDate();
+
+ SubcontractorPrice.SetRange("Vendor No.", PurchaseLine."Buy-from Vendor No.");
+ SubcontractorPrice.SetRange("Work Center No.", PurchaseLine."Work Center No.");
+ SubcontractorPrice.SetRange("Item No.", PurchaseLine."No.");
+ SubcontractorPrice.SetFilter("Variant Code", '%1|%2', PurchaseLine."Variant Code", '');
+ SubcontractorPrice.SetFilter("Unit of Measure Code", '%1|%2', PurchaseLine."Unit of Measure Code", '');
+
+ GetProdOrderRtngLine(PurchaseLine."Prod. Order No.", PurchaseLine."Routing Reference No.", PurchaseLine."Routing No.", PurchaseLine."Operation No.", ProdOrderRoutingLine);
+
+ SubcontractorPrice.SetFilter("Standard Task Code", '%1|%2', ProdOrderRoutingLine."Standard Task Code", '');
+ SubcontractorPrice.SetFilter("Currency Code", '%1|%2', PurchaseLine."Currency Code", '');
+ SubcontractorPrice.SetRange("Starting Date", 0D, OrderDate);
+ SubcontractorPrice.SetFilter("Ending Date", '>=%1|%2', OrderDate, 0D);
+
+ if SubcontractorPrice.FindLast() then begin
+ if SubcontractorPrice."Unit of Measure Code" = PurchaseLine."Unit of Measure Code" then
+ PriceListUOM := SubcontractorPrice."Unit of Measure Code";
+ GetUOMPrice(PurchaseLine."No.", GetQuantityBase(PurchaseLine), SubcontractorPrice, PriceListUOM, PriceListQtyPerUOM, PriceListQty);
+ GetPriceByUOM(SubcontractorPrice, PriceListQty, PriceListCost);
+ if PriceListCost <> 0 then begin
+ ConvertPriceToUOM(PurchaseLine."Unit of Measure Code", PurchaseLine.GetQuantityPerUOM(), PriceListUOM, PriceListQtyPerUOM, PriceListCost, DirectCost);
+ ConvertPriceToCurrency(PurchaseLine."Currency Code", SubcontractorPrice."Currency Code", PriceListCost, DirectCost)
+ end;
+ end else begin
+ GetUOMPrice(PurchaseLine."No.", PurchaseLine.GetQuantityBase(), SubcontractorPrice, PriceListUOM, PriceListQtyPerUOM, PriceListQty);
+ ProdOrderRoutingLine.TestField(Type, "Capacity Type"::"Work Center");
+ DirectCost := ProdOrderRoutingLine."Direct Unit Cost";
+ end;
+
+ PurchaseLine."Direct Unit Cost" := DirectCost;
+ PurchaseLine.Validate("Line Discount %");
+ end;
+
+ local procedure GetProdOrderRtngLine(ProdOrderNo: Code[20]; RtngRefNo: Integer; RoutingNo: Code[20]; OperationNo: Code[10]; var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ ProdOrderRoutingLine.SetFilter(Status, '%1|%2', ProdOrderRoutingLine.Status::Released, ProdOrderRoutingLine.Status::Finished);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", RtngRefNo);
+ ProdOrderRoutingLine.SetRange("Routing No.", RoutingNo);
+ ProdOrderRoutingLine.SetRange("Operation No.", OperationNo);
+
+ ProdOrderRoutingLine.FindFirst();
+ end;
+
+ local procedure SetSubcontractorPriceForPriceCalculation(var SubcontractorPrice: Record "Subcontractor Price"; VendorNo: Code[20]; ItemNo: Code[20]; VariantCode: Code[10]; StandardTaskCode: Code[10]; WorkCenterNo: Code[20]; UoM: Code[10]; StartingDate: Date)
+ begin
+ SubcontractorPrice."Vendor No." := VendorNo;
+ SubcontractorPrice."Item No." := ItemNo;
+ SubcontractorPrice."Standard Task Code" := StandardTaskCode;
+ SubcontractorPrice."Work Center No." := WorkCenterNo;
+ SubcontractorPrice."Variant Code" := VariantCode;
+ SubcontractorPrice."Unit of Measure Code" := UoM;
+ SubcontractorPrice."Starting Date" := StartingDate;
+ SubcontractorPrice."Currency Code" := '';
+ end;
+
+ local procedure GetQuantityBase(var PurchaseLine: Record "Purchase Line"): Decimal
+ var
+ ItemUnitofMeasure: Record "Item Unit of Measure";
+ begin
+ ItemUnitofMeasure.Get(PurchaseLine."No.", PurchaseLine."Unit of Measure Code");
+ exit(Round(PurchaseLine.Quantity * ItemUnitofMeasure."Qty. per Unit of Measure", 0.00001));
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchCrMemoLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchCrMemoLine.TableExt.al
new file mode 100644
index 0000000000..c140d28a95
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchCrMemoLine.TableExt.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.History;
+
+tableextension 99001515 "Subc. Purch. CrMemo Line" extends "Purch. Cr. Memo Line"
+{
+ fields
+ {
+ field(99001543; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001544; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001545; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Routing No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Routing Header";
+ }
+ field(99001546; "Subc. Rtng Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001547; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Operation No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."),
+ "Routing Reference No." = field("Subc. Rtng Reference No."));
+ }
+ field(99001548; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Work Center";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchFactboxMgmt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchFactboxMgmt.Codeunit.al
new file mode 100644
index 0000000000..37a6de4927
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchFactboxMgmt.Codeunit.al
@@ -0,0 +1,641 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Utilities;
+using System.Reflection;
+using System.Text;
+
+codeunit 99001560 "Subc. Purch. Factbox Mgmt."
+{
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ MultipleLbl: Label 'Multiple', MaxLength = 20;
+ NoTransferExistsMsg: Label 'No transfer order exists for this purchase order.';
+
+ ///
+ /// Opens the Purchase Order page for the subcontracting purchase order linked to the given variant record.
+ ///
+ /// A record variant of a related document line (Purchase Line, Transfer Line, ledger entry, etc.).
+ procedure ShowPurchaseOrder(RecRelatedVariant: Variant)
+ var
+ PurchaseHeader: Record "Purchase Header";
+ PageManagement: Codeunit "Page Management";
+ PurchOrderNo: Code[20];
+ PurchOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not GetPurchaseOrderNoByVariant(RecRelatedVariant, PurchOrderNo, PurchOrderLineNo) then
+ exit;
+ PurchaseHeader.Reset();
+ PurchaseHeader.SetRange("Document Type", PurchaseHeader."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchOrderNo);
+ PageManagement.PageRun(PurchaseHeader);
+ end;
+
+ ///
+ /// Returns the number of subcontracting transfer order lines linked to the given variant record.
+ ///
+ /// A record variant of a related document line.
+ /// The count of matching transfer lines, or 0 if no purchase order is found.
+ procedure CalcNoOfTransferOrders(RecRelatedVariant: Variant): Integer
+ var
+ TransferLine: Record "Transfer Line";
+ PurchOrderNo: Code[20];
+ PurchOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if not GetPurchaseOrderNoByVariant(RecRelatedVariant, PurchOrderNo, PurchOrderLineNo) then
+ exit(0);
+
+ TransferLine.SetCurrentKey("Subc. Purch. Order No.");
+ TransferLine.SetRange("Subc. Purch. Order No.", PurchOrderNo);
+ TransferLine.SetRange("Subc. Purch. Order Line No.", PurchOrderLineNo);
+ exit(TransferLine.Count());
+ end;
+
+ ///
+ /// Returns the transfer order document number linked to the given variant record, or 'Multiple' if more than one exists.
+ ///
+ /// A record variant of a related document line.
+ /// The transfer order document number, 'Multiple' if more than one exists, or an empty string if none.
+ procedure GetTransferOrderNo(RecRelatedVariant: Variant): Code[20]
+ var
+ TransferLine: Record "Transfer Line";
+ PurchOrderNo: Code[20];
+ NoOfTransferOrders: Integer;
+ PurchOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+
+#endif
+ if not GetPurchaseOrderNoByVariant(RecRelatedVariant, PurchOrderNo, PurchOrderLineNo) then
+ exit('');
+
+ NoOfTransferOrders := GetNoOfTransferOrders(RecRelatedVariant);
+ case NoOfTransferOrders of
+ 0:
+ exit('');
+ 1:
+ begin
+ FilterTransferLineToSubcontractorPurchaseOrder(PurchOrderNo, PurchOrderLineNo, TransferLine);
+ TransferLine.SetLoadFields(SystemId);
+ TransferLine.FindFirst();
+ exit(TransferLine."Document No.");
+ end;
+ else
+ exit(MultipleLbl);
+ end;
+ end;
+
+ ///
+ /// Returns the return transfer order document number linked to the given variant record.
+ ///
+ /// A record variant of a related document line.
+ /// The return transfer order document number, or an empty string if none exists.
+ procedure GetReturnTransferOrderNo(RecRelatedVariant: Variant): Code[20]
+ var
+ TransferLine: Record "Transfer Line";
+ PurchOrderNo: Code[20];
+ PurchOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit('');
+
+#endif
+ if not GetPurchaseOrderNoByVariant(RecRelatedVariant, PurchOrderNo, PurchOrderLineNo) then
+ exit('');
+
+ TransferLine.SetCurrentKey("Subc. Purch. Order No.");
+ TransferLine.SetRange("Subc. Purch. Order No.", PurchOrderNo);
+ TransferLine.SetRange("Subc. Purch. Order Line No.", PurchOrderLineNo);
+ TransferLine.SetFilter("Subc. Operation No.", '%1', '');
+ TransferLine.SetFilter("Subc. Routing No.", '%1', '');
+ TransferLine.SetLoadFields(SystemId);
+ if TransferLine.IsEmpty() then
+ exit('');
+ TransferLine.FindFirst();
+ exit(TransferLine."Document No.");
+ end;
+
+ ///
+ /// Returns the number of distinct transfer orders (by document number) linked to the subcontracting purchase order for the given variant record.
+ ///
+ /// A record variant of a related document line.
+ /// The count of distinct transfer order header numbers, or 0 if none found.
+ procedure GetNoOfTransferOrders(RecRelatedVariant: Variant) NoOfTransferOrders: Integer
+ var
+ TransferLine: Record "Transfer Line";
+ ListOfTransferHeaderNo: List of [Code[20]];
+ PurchOrderNo: Code[20];
+ PurchOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if not GetPurchaseOrderNoByVariant(RecRelatedVariant, PurchOrderNo, PurchOrderLineNo) then
+ exit(0);
+
+ FilterTransferLineToSubcontractorPurchaseOrder(PurchOrderNo, PurchOrderLineNo, TransferLine);
+ if not TransferLine.FindSet() then
+ exit(0);
+
+ repeat
+ if not ListOfTransferHeaderNo.Contains(TransferLine."Document No.") then
+ ListOfTransferHeaderNo.Add(TransferLine."Document No.");
+ until TransferLine.Next() = 0;
+ NoOfTransferOrders := ListOfTransferHeaderNo.Count();
+
+ exit(NoOfTransferOrders);
+ end;
+
+ ///
+ /// Finds the transfer order number linked to the purchase or receipt document in the given variant record.
+ ///
+ /// A record variant of a Purchase Line, Purch. Rcpt. Line, or Purch. Inv. Line.
+ /// Returns the transfer order document number if found.
+ /// True if a matching transfer line was found; otherwise false.
+ procedure GetTransferOrderNoByVariant(RecRelatedVariant: Variant; var TransferOrderNo: Code[20]): Boolean
+ var
+ PurchaseLine: Record "Purchase Line";
+ PurchInvLine: Record "Purch. Inv. Line";
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ TransferLine: Record "Transfer Line";
+ DataTypeManagement: Codeunit "Data Type Management";
+ ResultRecordRef: RecordRef;
+ RecId: RecordId;
+ ProdOperation: Code[10];
+ ProdOrderNo: Code[20];
+ PurchOrderNo: Code[20];
+ ProdOrderLineNo: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(false);
+
+#endif
+ if not RecRelatedVariant.IsRecord() then
+ exit(false);
+
+ DataTypeManagement.GetRecordRef(RecRelatedVariant, ResultRecordRef);
+
+ RecId := ResultRecordRef.RecordId();
+ if RecId.TableNo() = 0 then
+ exit(false);
+
+ case RecId.TableNo() of
+ Database::"Purchase Line":
+ begin
+ ResultRecordRef.SetTable(PurchaseLine);
+ PurchOrderNo := PurchaseLine."Document No.";
+ ProdOrderNo := PurchaseLine."Prod. Order No.";
+ ProdOrderLineNo := PurchaseLine."Prod. Order Line No.";
+ ProdOperation := PurchaseLine."Operation No.";
+ end;
+ Database::"Purch. Rcpt. Line":
+ begin
+ ResultRecordRef.SetTable(PurchRcptLine);
+ PurchOrderNo := PurchRcptLine."Document No.";
+ ProdOrderNo := PurchRcptLine."Prod. Order No.";
+ ProdOrderLineNo := PurchRcptLine."Prod. Order Line No.";
+ ProdOperation := PurchRcptLine."Operation No.";
+ end;
+ Database::"Purch. Inv. Line":
+ begin
+ ResultRecordRef.SetTable(PurchInvLine);
+ PurchOrderNo := PurchInvLine."Document No.";
+ ProdOrderNo := PurchInvLine."Prod. Order No.";
+ ProdOrderLineNo := PurchInvLine."Prod. Order Line No.";
+ ProdOperation := PurchInvLine."Operation No.";
+ end;
+ end;
+
+ TransferLine.SetCurrentKey("Subc. Purch. Order No.", "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Operation No.");
+ TransferLine.SetRange("Subc. Purch. Order No.", PurchOrderNo);
+ TransferLine.SetRange("Subc. Prod. Order No.", ProdOrderNo);
+ TransferLine.SetRange("Subc. Prod. Order Line No.", ProdOrderLineNo);
+ TransferLine.SetRange("Subc. Operation No.", ProdOperation);
+
+ if not TransferLine.IsEmpty() then begin
+ TransferLine.SetLoadFields(SystemId);
+
+ TransferLine.FindFirst();
+ TransferOrderNo := TransferLine."Document No.";
+ exit(TransferOrderNo <> '');
+ end;
+ end;
+
+ ///
+ /// Shows the transfer order(s) or return transfer order linked to the given variant record.
+ ///
+ /// A record variant of a Prod. Order Component, Purchase Line, or Prod. Order Routing Line.
+ /// When true, opens the transfer order page; when false, only populates the temp table.
+ /// When true, filters to return transfer orders; when false, filters to outbound transfer orders.
+ /// The number of transfer lines linked to the given record.
+ procedure ShowTransferOrdersAndReturnOrder(RecRelatedVariant: Variant; LookUpPage: Boolean; IsReturn: Boolean): Integer
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferHeaderToOpen: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ DataTypeManagement: Codeunit "Data Type Management";
+ PageManagement: Codeunit "Page Management";
+ SelectionFilterMgt: Codeunit SelectionFilterManagement;
+ RecRef: RecordRef;
+ NoOfTransferHeaders: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if not RecRelatedVariant.IsRecord() then
+ exit(0);
+
+ DataTypeManagement.GetRecordRef(RecRelatedVariant, RecRef);
+ ProductionOrder.SetLoadFields("No.", "Source Type");
+
+ case RecRef.Number() of
+ Database::"Prod. Order Component":
+ begin
+ RecRef.SetTable(ProdOrderComponent);
+ if not ProductionOrder.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.") then
+ exit(0);
+ if not ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.") then
+ exit(0);
+
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+ end;
+ Database::"Purchase Line":
+ begin
+ RecRef.SetTable(PurchaseLine);
+ if not ProductionOrder.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.") then
+ exit(0);
+ GetProdOrderRtngLineFromPurchaseLine(ProdOrderRoutingLine, PurchaseLine);
+ if ProductionOrder."Source Type" <> "Prod. Order Source Type"::Family then
+ if not ProdOrderLine.Get(ProdOrderRoutingLine.Status, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit(0);
+ end;
+ Database::"Prod. Order Routing Line":
+ begin
+ RecRef.SetTable(ProdOrderRoutingLine);
+ if not ProductionOrder.Get(ProdOrderRoutingLine.Status, ProdOrderRoutingLine."Prod. Order No.") then
+ exit(0);
+ if ProductionOrder."Source Type" <> "Prod. Order Source Type"::Family then
+ if not ProdOrderLine.Get(ProdOrderRoutingLine.Status, ProdOrderRoutingLine."Prod. Order No.", ProdOrderRoutingLine."Routing Reference No.") then
+ exit(0);
+ end;
+ else
+ exit(0);
+ end;
+
+ TransferLine.SetCurrentKey("Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Routing Reference No.", "Subc. Routing No.", "Subc. Operation No.");
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ if ProductionOrder."Source Type" <> "Prod. Order Source Type"::Family then begin
+ TransferLine.SetRange("Subc. Prod. Order Line No.", ProdOrderLine."Line No.");
+ TransferLine.SetRange("Subc. Routing Reference No.", ProdOrderLine."Routing Reference No.");
+ end else begin
+ TransferLine.SetRange("Subc. Prod. Order Line No.");
+ TransferLine.SetRange("Subc. Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ end;
+ TransferLine.SetRange("Subc. Return Order", IsReturn);
+ TransferLine.SetRange("Subc. Routing No.", ProdOrderRoutingLine."Routing No.");
+ TransferLine.SetRange("Subc. Operation No.", ProdOrderRoutingLine."Operation No.");
+ TransferLine.SetRange("Derived From Line No.", 0);
+ if not LookUpPage then
+ exit(TransferLine.Count());
+
+ if not TransferLine.IsEmpty() then
+ if TransferLine.FindSet() then
+ repeat
+ if TransferHeader.Get(TransferLine."Document No.") then
+ TransferHeader.Mark(true);
+ until TransferLine.Next() = 0;
+ TransferHeader.MarkedOnly(true);
+
+ if LookUpPage then begin
+ NoOfTransferHeaders := TransferHeader.Count();
+ case true of
+ NoOfTransferHeaders = 0:
+ Message(NoTransferExistsMsg);
+ NoOfTransferHeaders = 1:
+ if TransferHeader.FindFirst() then begin
+ TransferHeaderToOpen.Get(TransferHeader."No.");
+ PageManagement.PageRun(TransferHeaderToOpen);
+ end;
+ NoOfTransferHeaders > 1:
+ begin
+ // As we do not expect more than a handful tranfer orders linked to the purchase order, there is no need to
+ // add extra processing if the number of records linked are more than allowed.
+ TransferHeaderToOpen.SetFilter("No.", SelectionFilterMgt.GetSelectionFilterForTransferHeader(TransferHeader));
+ PageManagement.PageRunList(TransferHeaderToOpen);
+ end;
+ end;
+ end;
+ end;
+
+ ///
+ /// Opens the subcontracting transfer order(s) linked to the given production order.
+ ///
+ /// The production order to show the related subcontracting transfer orders for.
+ procedure ShowTransferOrdersFromProductionOrder(ProductionOrder: Record "Production Order")
+ var
+ TransferHeader: Record "Transfer Header";
+ TransferHeaderToOpen: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ PageManagement: Codeunit "Page Management";
+ SelectionFilterMgt: Codeunit SelectionFilterManagement;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine.SetCurrentKey("Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Routing Reference No.", "Subc. Routing No.", "Subc. Operation No.");
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Derived From Line No.", 0);
+ if TransferLine.FindSet() then
+ repeat
+ if TransferHeader.Get(TransferLine."Document No.") then
+ TransferHeader.Mark(true);
+ until TransferLine.Next() = 0;
+ TransferHeader.MarkedOnly(true);
+
+ if TransferHeader.IsEmpty() then
+ TransferHeaderToOpen.SetRange("No.", '')
+ else
+ TransferHeaderToOpen.SetFilter("No.", SelectionFilterMgt.GetSelectionFilterForTransferHeader(TransferHeader));
+ PageManagement.PageRunList(TransferHeaderToOpen);
+ end;
+
+ ///
+ /// Returns the number of subcontractor prices matching the given purchase line.
+ ///
+ /// The purchase line to match subcontractor prices against.
+ /// The count of matching subcontractor price entries, or 0 if the line is not an item line.
+ procedure CalcNoOfPurchasePrices(var PurchaseLine: Record "Purchase Line"): Integer
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ if IsItemLine(PurchaseLine) then
+ exit(CountPriceOnPurchItemLine(PurchaseLine));
+ end;
+
+ ///
+ /// Opens the Subcontractor Prices page filtered to the subcontractor prices matching the given purchase line.
+ ///
+ /// The purchase line used to filter subcontractor prices.
+ procedure ShowSubcontractorPrices(PurchaseLine: Record "Purchase Line")
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ FilterSubContractorPriceForPurchLine(SubcontractorPrice, PurchaseLine);
+
+ Page.Run(Page::"Subcontractor Prices", SubcontractorPrice);
+ end;
+
+ local procedure GetPurchaseOrderNoByVariant(RecRelatedVariant: Variant; var PurchOrderNo: Code[20]; var PurchOrderLineNo: Integer): Boolean
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ PurchInvLine: Record "Purch. Inv. Line";
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ TransferLine: Record "Transfer Line";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ TransferShipmentLine: Record "Transfer Shipment Line";
+ DataTypeManagement: Codeunit "Data Type Management";
+ ResultRecordRef: RecordRef;
+ RecId: RecordId;
+ begin
+ if not RecRelatedVariant.IsRecord() then
+ exit(false);
+
+ DataTypeManagement.GetRecordRef(RecRelatedVariant, ResultRecordRef);
+
+ RecId := ResultRecordRef.RecordId();
+ if RecId.TableNo() = 0 then
+ exit(false);
+
+ case RecId.TableNo() of
+ Database::"Purchase Line":
+ begin
+ ResultRecordRef.SetTable(PurchaseLine);
+ PurchOrderNo := PurchaseLine."Document No.";
+ PurchOrderLineNo := PurchaseLine."Line No.";
+ end;
+ Database::"Purch. Rcpt. Line":
+ begin
+ ResultRecordRef.SetTable(PurchRcptLine);
+ PurchOrderNo := PurchRcptLine."Order No.";
+ PurchOrderLineNo := PurchRcptLine."Order Line No.";
+ end;
+ Database::"Purch. Inv. Line":
+ begin
+ ResultRecordRef.SetTable(PurchInvLine);
+ PurchOrderNo := PurchInvLine."Order No.";
+ PurchOrderLineNo := PurchInvLine."Order Line No.";
+ end;
+ Database::"Transfer Line":
+ begin
+ ResultRecordRef.SetTable(TransferLine);
+ PurchOrderNo := TransferLine."Subc. Purch. Order No.";
+ PurchOrderLineNo := TransferLine."Subc. Purch. Order Line No.";
+ end;
+ Database::"Transfer Shipment Line":
+ begin
+ ResultRecordRef.SetTable(TransferShipmentLine);
+ PurchOrderNo := TransferShipmentLine."Subc. Purch. Order No.";
+ PurchOrderLineNo := TransferShipmentLine."Subc. Purch. Order Line No.";
+ end;
+ Database::"Transfer Receipt Line":
+ begin
+ ResultRecordRef.SetTable(TransferReceiptLine);
+ PurchOrderNo := TransferReceiptLine."Subc. Purch. Order No.";
+ PurchOrderLineNo := TransferReceiptLine."Subc. Purch. Order Line No.";
+ end;
+ Database::"Item Ledger Entry":
+ begin
+ ResultRecordRef.SetTable(ItemLedgerEntry);
+ PurchOrderNo := ItemLedgerEntry."Subc. Purch. Order No.";
+ PurchOrderLineNo := ItemLedgerEntry."Subc. Purch. Order Line No.";
+ end;
+ Database::"Capacity Ledger Entry":
+ begin
+ ResultRecordRef.SetTable(CapacityLedgerEntry);
+ PurchOrderNo := CapacityLedgerEntry."Subc. Purch. Order No.";
+ PurchOrderLineNo := CapacityLedgerEntry."Subc. Purch. Order Line No.";
+ end;
+ Database::"Prod. Order Routing Line":
+ begin
+ ResultRecordRef.SetTable(ProdOrderRoutingLine);
+ GetPurchOrderFromProdOrderRtngLine(ProdOrderRoutingLine, PurchOrderNo, PurchOrderLineNo);
+ end;
+ Database::"Prod. Order Component":
+ begin
+ ResultRecordRef.SetTable(ProdOrderComponent);
+ if ProdOrderComponent."Routing Link Code" <> '' then
+ GetPurchOrderFromProdOrderComp(ProdOrderComponent, PurchOrderNo, PurchOrderLineNo);
+ end;
+ end;
+ exit(PurchOrderNo <> '');
+ end;
+
+ local procedure GetPurchOrderFromProdOrderRtngLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var PurchOrderNo: Code[20]; var PurchOrderLineNo: Integer)
+ 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("Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ if PurchaseLine.IsEmpty() then
+ exit;
+
+ PurchaseLine.FindFirst();
+ PurchOrderNo := PurchaseLine."Document No.";
+ PurchOrderLineNo := PurchaseLine."Line No.";
+ end;
+
+ local procedure GetPurchOrderFromProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"; var PurchOrderNo: Code[20]; var PurchOrderLineNo: Integer)
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+ GetProdOrderRtngLineFromProdOrderComp(ProdOrderRoutingLine, ProdOrderComponent);
+
+ 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.");
+ PurchaseLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ if PurchaseLine.IsEmpty() then
+ exit;
+
+ PurchaseLine.FindFirst();
+ PurchOrderNo := PurchaseLine."Document No.";
+ PurchOrderLineNo := PurchaseLine."Line No.";
+ end;
+
+ local procedure GetProdOrderRtngLineFromProdOrderComp(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ if not ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.") then
+ exit;
+
+ 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");
+ if ProdOrderRoutingLine.IsEmpty() then
+ exit;
+
+ ProdOrderRoutingLine.FindFirst();
+ end;
+
+ local procedure GetProdOrderRtngLineFromPurchaseLine(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; PurchaseLine: Record "Purchase Line")
+ begin
+ ProdOrderRoutingLine.SetCurrentKey(Status, "Prod. Order No.", "Routing Reference No.", "Routing No.", "Operation No.");
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No.");
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", PurchaseLine."Routing Reference No.");
+ ProdOrderRoutingLine.SetRange("Routing No.", PurchaseLine."Routing No.");
+ ProdOrderRoutingLine.SetRange("Operation No.", PurchaseLine."Operation No.");
+ if ProdOrderRoutingLine.IsEmpty() then
+ exit;
+
+ ProdOrderRoutingLine.FindFirst();
+ end;
+
+ local procedure CountPriceOnPurchItemLine(PurchaseLine: Record "Purchase Line"): Decimal
+ var
+ SubcontractorPrice: Record "Subcontractor Price";
+ begin
+ FilterSubContractorPriceForPurchLine(SubcontractorPrice, PurchaseLine);
+
+ exit(SubcontractorPrice.Count());
+ end;
+
+ local procedure IsItemLine(PurchaseLine: Record "Purchase Line"): Boolean
+ begin
+ if (PurchaseLine.Type <> PurchaseLine.Type::Item) or (PurchaseLine."No." = '') then
+ exit(false);
+ exit(true);
+ end;
+
+ local procedure FilterSubContractorPriceForPurchLine(var SubcontractorPrice: Record "Subcontractor Price"; PurchaseLine: Record "Purchase Line")
+ begin
+ SubcontractorPrice.SetCurrentKey("Vendor No.", "Item No.", "Work Center No.", "Variant Code", "Unit of Measure Code", "Currency Code");
+ SubcontractorPrice.SetRange("Vendor No.", PurchaseLine."Buy-from Vendor No.");
+ SubcontractorPrice.SetRange("Item No.", PurchaseLine."No.");
+ SubcontractorPrice.SetRange("Work Center No.", PurchaseLine."Work Center No.");
+ SubcontractorPrice.SetRange("Variant Code", PurchaseLine."Variant Code");
+ SubcontractorPrice.SetRange("Unit of Measure Code", PurchaseLine."Unit of Measure Code");
+ SubcontractorPrice.SetRange("Currency Code", PurchaseLine."Currency Code");
+ end;
+
+ local procedure FilterTransferLineToSubcontractorPurchaseOrder(PurchOrderNo: Code[20]; PurchOrderLineNo: Integer; var TransferLine: Record "Transfer Line")
+ begin
+ TransferLine.SetCurrentKey("Subc. Purch. Order No.");
+ TransferLine.SetRange("Subc. Purch. Order No.", PurchOrderNo);
+ TransferLine.SetRange("Subc. Purch. Order Line No.", PurchOrderLineNo);
+ TransferLine.SetFilter("Subc. Operation No.", '<>%1', '');
+ TransferLine.SetFilter("Subc. Routing No.", '<>%1', '');
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchInvLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchInvLine.TableExt.al
new file mode 100644
index 0000000000..df57c6e053
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchInvLine.TableExt.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.History;
+
+tableextension 99001514 "Subc. Purch. Inv. Line" extends "Purch. Inv. Line"
+{
+ fields
+ {
+ field(99001543; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001544; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001545; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Routing No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Routing Header";
+ }
+ field(99001546; "Subc. Rtng Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001547; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Operation No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."),
+ "Routing Reference No." = field("Subc. Rtng Reference No."));
+ }
+ field(99001548; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Work Center";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchOrder.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchOrder.PageExt.al
new file mode 100644
index 0000000000..89ea1d2daa
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchOrder.PageExt.al
@@ -0,0 +1,130 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+
+pageextension 99001523 "Subc. Purch. Order" extends "Purchase Order"
+{
+ layout
+ {
+ addafter(Status)
+ {
+ field("Subc. Order"; Rec."Subc. Order")
+ {
+ ApplicationArea = Subcontracting;
+ Importance = Additional;
+ }
+ field("Subc. Location Code"; Rec."Subc. Location Code")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Importance = Additional;
+ }
+ }
+ addafter(WorkflowStatus)
+ {
+ part(" Sub Purchase Line Factbox"; "Subc. Purchase Line Factbox")
+ {
+ ApplicationArea = Subcontracting;
+ Provider = PurchLines;
+ SubPageLink = "Document Type" = field("Document Type"), "Document No." = field("Document No."), "Line No." = field("Line No.");
+ Visible = HasSubcontractingContext;
+ }
+ }
+ }
+ actions
+ {
+ addafter(IncomingDocument)
+ {
+ group(Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ Visible = HasSubcontractingContext;
+ action(CreateTransfOrdToSubcontractor)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Transf. Ord. to Subcontractor';
+ Image = NewDocument;
+ ToolTip = 'Create a transfer order to send to the subcontractor.';
+
+ trigger OnAction()
+ var
+ PurchaseHeader: Record "Purchase Header";
+ begin
+ PurchaseHeader := Rec;
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Create Transf. Order", false, false, PurchaseHeader);
+ end;
+ }
+ action(CreateReturnFromSubcontractor)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Return from Subcontractor';
+ Image = ReturnRelated;
+ ToolTip = 'Create a return document from the subcontractor.';
+
+ trigger OnAction()
+ var
+ PurchaseHeader: Record "Purchase Header";
+ begin
+ PurchaseHeader := Rec;
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Create SubCReturnOrder", false, false, PurchaseHeader);
+ end;
+ }
+ action(PrintSubcDispatchingList)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Print Subcontractor Dispatching List';
+ Image = Print;
+ ToolTip = 'Print the dispatching list for the subcontractor.';
+
+ trigger OnAction()
+ var
+ PurchaseHeader: Record "Purchase Header";
+ begin
+ PurchaseHeader := Rec;
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Dispatching List", true, false, PurchaseHeader);
+ end;
+ }
+ }
+ }
+ }
+ var
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ HasSubcontractingContext: Boolean;
+
+ trigger OnOpenPage()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ HasSubcontractingContext := SubcontractingManagement.IsSubcontractingPurchaseDocument(Rec);
+ CurrPage.PurchLines.Page.SetIsSubcontracting(HasSubcontractingContext);
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ HasSubcontractingContext := SubcontractingManagement.IsSubcontractingPurchaseDocument(Rec);
+ CurrPage.PurchLines.Page.SetIsSubcontracting(HasSubcontractingContext);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchOrderList.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchOrderList.PageExt.al
new file mode 100644
index 0000000000..60e080ed1a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchOrderList.PageExt.al
@@ -0,0 +1,87 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+
+pageextension 99001525 "Subc. PurchOrderList" extends "Purchase Order List"
+{
+ layout
+ {
+ addafter("Location Code")
+ {
+ field("Subc. Location Code"; Rec."Subc. Location Code")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("Create Inventor&y Put-away/Pick")
+ {
+ action(CreateTransfOrdToSubcontractor)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Transf. Ord. to Subcontractor';
+ Image = NewDocument;
+ ToolTip = 'Create a transfer order to send to the subcontractor.';
+
+ trigger OnAction()
+ var
+ PurchaseHeader: Record "Purchase Header";
+ begin
+ PurchaseHeader := Rec;
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Create Transf. Order", false, false, PurchaseHeader);
+ end;
+ }
+ action(CreateReturnFromSubcontractor)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Return from Subcontractor';
+ Image = ReturnRelated;
+ ToolTip = 'Create a return document from the subcontractor.';
+
+ trigger OnAction()
+ var
+ PurchaseHeader: Record "Purchase Header";
+ begin
+ PurchaseHeader := Rec;
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Create SubCReturnOrder", false, false, PurchaseHeader);
+ end;
+ }
+ action(PrintSubcDispatchingList)
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Print Subcontractor Dispatching List';
+ Image = Print;
+ ToolTip = 'Print the dispatching list for the subcontractor.';
+
+ trigger OnAction()
+ var
+ PurchaseHeader: Record "Purchase Header";
+ begin
+ PurchaseHeader := Rec;
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Dispatching List", true, false, PurchaseHeader);
+ end;
+ }
+ }
+ }
+ views
+ {
+ addlast
+ {
+ view(SubcontractingOrders)
+ {
+ Caption = 'Subcontracting Orders';
+ Filters = where("Subc. Order" = const(true));
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchPostExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchPostExt.Codeunit.al
new file mode 100644
index 0000000000..59615e7456
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchPostExt.Codeunit.al
@@ -0,0 +1,184 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Enums;
+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.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Purchases.Posting;
+codeunit 99001535 "Subc. Purch. Post Ext"
+{
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ CancelNotSupportedErr: Label 'You cannot cancel or correct posted purchase invoice %1 because it contains item charges assigned to a subcontracting order receipt.\Use the ''Create Corrective Credit Memo'' action to create a credit memo for this invoice.', Comment = '%1 = Posted Purchase Invoice No.';
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Correct Posted Purch. Invoice", OnAfterTestCorrectInvoiceIsAllowed, '', false, false)]
+ local procedure BlockCancelIfHasSubcontractingItemChargeValueEntry(var PurchInvHeader: Record "Purch. Inv. Header"; Cancelling: Boolean)
+ var
+ ValueEntry: Record "Value Entry";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ValueEntry.SetRange("Document Type", ValueEntry."Document Type"::"Purchase Invoice");
+ ValueEntry.SetRange("Document No.", PurchInvHeader."No.");
+ ValueEntry.SetFilter("Item Charge No.", '<>%1', '');
+ ValueEntry.SetFilter("Capacity Ledger Entry No.", '<>%1', 0);
+ if not ValueEntry.IsEmpty() then
+ Error(CancelNotSupportedErr, PurchInvHeader."No.");
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnBeforeItemJnlPostLine, '', false, false)]
+ local procedure "Purch.-Post_OnBeforeItemJnlPostLine"(var ItemJournalLine: Record "Item Journal Line"; TempItemChargeAssignmentPurch: Record "Item Charge Assignment (Purch)" temporary)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ FillItemJnlLineForSubcontractingItemCharge(ItemJournalLine, TempItemChargeAssignmentPurch);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Mfg. Purch.-Post", OnAfterPostItemJnlLineCopyProdOrder, '', false, false)]
+ local procedure MfgPurchPostOnAfterPostItemJnlLineCopyProdOrder(var ItemJnlLine: Record "Item Journal Line"; PurchLine: Record "Purchase Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ItemJnlLine."Subc. Purch. Order No." := PurchLine."Document No.";
+ ItemJnlLine."Subc. Purch. Order Line No." := PurchLine."Line No.";
+ ItemJnlLine."Subc. Operation No." := PurchLine."Operation No.";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purch.-Post", OnPostItemChargePerRcptOnAfterCalcDistributeCharge, '', false, false)]
+ local procedure "Purch.-Post_OnPostItemChargePerRcptOnAfterCalcDistributeCharge"(PurchHeader: Record "Purchase Header"; PurchLine: Record "Purchase Line"; var PurchRcptLine: Record "Purch. Rcpt. Line"; var TempItemLedgEntry: Record "Item Ledger Entry" temporary; var DistributeCharge: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SetQuantityBaseOnSubcontractingServiceLine(PurchLine, PurchRcptLine);
+ end;
+
+ local procedure FillItemJnlLineForSubcontractingItemCharge(var ItemJournalLine: Record "Item Journal Line"; TempItemChargeAssignmentPurch: Record "Item Charge Assignment (Purch)" temporary)
+ var
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ begin
+ 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;
+
+ CopySubcontractingProdOrderFieldsToItemJnlLine(ItemJournalLine, PurchRcptLine);
+ end;
+
+ local procedure SetQuantityBaseOnSubcontractingServiceLine(PurchaseLine: Record "Purchase Line"; var PurchRcptLine: Record "Purch. Rcpt. Line")
+ var
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+ begin
+ if PurchRcptLine."Quantity (Base)" = 0 then
+ if PurchRcptLineHasProdOrder(PurchRcptLine) then
+ PurchRcptLine."Quantity (Base)" := UnitofMeasureManagement.CalcBaseQty(
+ PurchRcptLine."No.", PurchRcptLine."Variant Code", PurchRcptLine."Unit of Measure Code", PurchRcptLine.Quantity, PurchRcptLine."Qty. per Unit of Measure", PurchaseLine."Qty. Rounding Precision (Base)");
+ end;
+
+ local procedure PurchRcptLineHasProdOrder(PurchRcptLine: Record "Purch. Rcpt. Line") HasProdOrder: Boolean
+ begin
+ HasProdOrder := (PurchRcptLine."Prod. Order No." <> '') and
+ (PurchRcptLine."Routing No." <> '') and
+ (PurchRcptLine."Operation No." <> '');
+ exit(HasProdOrder);
+ end;
+
+ local procedure CopySubcontractingProdOrderFieldsToItemJnlLine(var ItemJournalLine: Record "Item Journal Line"; PurchRcptLine: Record "Purch. Rcpt. Line")
+ var
+ Item: Record Item;
+ begin
+ ItemJournalLine.Subcontracting := true;
+ ItemJournalLine."Order Type" := "Inventory Order Type"::Production;
+ ItemJournalLine."Order No." := PurchRcptLine."Prod. Order No.";
+ ItemJournalLine."Order Line No." := PurchRcptLine."Prod. Order Line No.";
+ Item.SetLoadFields("Inventory Posting Group");
+ Item.Get(ItemJournalLine."Item No.");
+ ItemJournalLine."Inventory Posting Group" := Item."Inventory Posting Group";
+ ItemJournalLine."Subc. Item Charge Assign." := true;
+ if PurchRcptLineIsLastOperation(PurchRcptLine) then
+ exit;
+ ItemJournalLine."Entry Type" := "Item Ledger Entry Type"::Output;
+ ItemJournalLine.Type := "Capacity Type Journal"::"Work Center";
+ ItemJournalLine."No." := PurchRcptLine."Subc. Work Center No.";
+ ItemJournalLine."Routing No." := PurchRcptLine."Routing No.";
+ ItemJournalLine."Routing Reference No." := PurchRcptLine."Routing Reference No.";
+ ItemJournalLine."Operation No." := PurchRcptLine."Operation No.";
+ ItemJournalLine."Work Center No." := PurchRcptLine."Work Center No.";
+ ItemJournalLine."Unit Cost Calculation" := ItemJournalLine."Unit Cost Calculation"::Units;
+ end;
+
+ local procedure PurchRcptLineIsLastOperation(PurchRcptLine: Record "Purch. Rcpt. Line"): Boolean
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ ProdOrderRoutingLine.SetLoadFields("Next Operation No.");
+ if ProdOrderRoutingLine.Get("Production Order Status"::Released, PurchRcptLine."Prod. Order No.", PurchRcptLine."Routing Reference No.", PurchRcptLine."Routing No.", PurchRcptLine."Operation No.") then
+ exit(ProdOrderRoutingLine."Next Operation No." = '');
+ if ProdOrderRoutingLine.Get("Production Order Status"::Finished, PurchRcptLine."Prod. Order No.", PurchRcptLine."Routing Reference No.", PurchRcptLine."Routing No.", PurchRcptLine."Operation No.") then
+ exit(ProdOrderRoutingLine."Next Operation No." = '');
+ exit(false);
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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/Purchase/SubcPurchRcptLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchRcptLine.TableExt.al
new file mode 100644
index 0000000000..6bf785cb5f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchRcptLine.TableExt.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.History;
+
+tableextension 99001513 "Subc. Purch. Rcpt. Line" extends "Purch. Rcpt. Line"
+{
+ fields
+ {
+ field(99001543; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001544; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001545; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Routing No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Routing Header";
+ }
+ field(99001546; "Subc. Rtng Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001547; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Operation No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."),
+ "Routing Reference No." = field("Subc. Rtng Reference No."));
+ }
+ field(99001548; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Work Center";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeader.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeader.TableExt.al
new file mode 100644
index 0000000000..8bcd4393c6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeader.TableExt.al
@@ -0,0 +1,45 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Purchases.Document;
+
+tableextension 99001509 "Subc. Purchase Header" extends "Purchase Header"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001520; "Subc. Location Code"; Code[10])
+ {
+ Caption = 'Subcontracting Location Code';
+ DataClassification = CustomerContent;
+ TableRelation = Location where("Use As In-Transit" = const(false));
+ ToolTip = 'Specifies the code for the location where the subcontracted items are stored for pickup and delivery.';
+ trigger OnValidate()
+ var
+ Location: Record Location;
+ BinMandatorySubcontrLocationErr: Label 'Location %1 cannot be used as a Subcontracting Location because Bin Mandatory is enabled.', Comment = '%1 = Location Code';
+ begin
+ if "Subc. Location Code" = '' then
+ exit;
+ Location.Get("Subc. Location Code");
+ if Location."Bin Mandatory" then
+ Error(BinMandatorySubcontrLocationErr, "Subc. Location Code");
+ end;
+ }
+ field(99001521; "Subc. Order"; Boolean)
+ {
+ CalcFormula = exist("Purchase Line" where("Document Type" = const(Order),
+ "Document No." = field("No."),
+ "Prod. Order No." = filter(<> ''),
+ "Prod. Order Line No." = filter(<> 0)));
+ Caption = 'Subcontracting Order';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the subcontracting orders that have been created.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeaderArch.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeaderArch.TableExt.al
new file mode 100644
index 0000000000..771a5dd23f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeaderArch.TableExt.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Purchases.Archive;
+using Microsoft.Purchases.Document;
+
+tableextension 99001511 "Subc. Purchase Header Arch" extends "Purchase Header Archive"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001520; "Subc. Location Code"; Code[10])
+ {
+ Caption = 'Subcontracting Location Code';
+ DataClassification = CustomerContent;
+ TableRelation = Location where("Use As In-Transit" = const(false));
+ ;
+ }
+ field(99001521; "Subc. Order"; Boolean)
+ {
+ CalcFormula = exist("Purchase Line" where("Document Type" = const(Order),
+ "Document No." = field("No."),
+ "Prod. Order No." = filter(<> ''),
+ "Prod. Order Line No." = filter(<> 0)));
+ Caption = 'Subcontracting Order';
+ Editable = false;
+ FieldClass = FlowField;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeaderExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeaderExt.Codeunit.al
new file mode 100644
index 0000000000..833e52f152
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseHeaderExt.Codeunit.al
@@ -0,0 +1,100 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Utilities;
+
+codeunit 99001533 "Subc. Purchase Header Ext"
+{
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ SubcSynchronizeManagement: Codeunit "Subc. Synchronize Management";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Header", OnAfterCopyBuyFromVendorFieldsFromVendor, '', false, false)]
+ local procedure OnAfterCopyBuyFromVendorFieldsFromVendor(var PurchaseHeader: Record "Purchase Header"; Vendor: Record Vendor; xPurchaseHeader: Record "Purchase Header")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchaseHeader."Subc. Location Code" := Vendor."Subc. Location Code";
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Header", OnAfterValidateEvent, "Buy-from Vendor No.", false, false)]
+ local procedure OnAfterValidateEventBuyFromVendorNo(var Rec: Record "Purchase Header"; var xRec: Record "Purchase Header")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcSynchronizeManagement.DeleteEnhancedDocumentsByChangeOfVendorNo(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Header", OnBeforeDeleteEvent, '', false, false)]
+ local procedure CheckTransferOrderOnBeforeDeleteEvent(var Rec: Record "Purchase Header"; RunTrigger: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+ if not RunTrigger then
+ exit;
+
+ Rec.CalcFields("Subc. Order");
+ if not Rec."Subc. Order" then
+ exit;
+
+ SubcSynchronizeManagement.CheckTransferOrderExistsForPurchaseHeader(Rec);
+ SubcTransferManagement.CheckStockAtSubcLocationForPurchHeader(Rec);
+ end;
+
+ internal procedure ShowTransferOrdersForPurchHeader(TransferOrderErrorInfo: ErrorInfo)
+ var
+ PurchaseHeader: Record "Purchase Header";
+ TransferHeader: Record "Transfer Header";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchaseHeader.Get(TransferOrderErrorInfo.RecordId);
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ if TransferHeader.Count() = 1 then begin
+ TransferHeader.FindFirst();
+ Page.Run(Page::"Transfer Order", TransferHeader);
+ end else
+ Page.Run(Page::"Transfer Orders", TransferHeader);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Copy Document Mgt.", OnAfterCopyPurchHeaderDone, '', false, false)]
+ local procedure ClearSubcLocationCodeOnAfterCopyPurchHeaderDone(var ToPurchaseHeader: Record "Purchase Header")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ToPurchaseHeader."Subc. Location Code" := '';
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLine.TableExt.al
new file mode 100644
index 0000000000..3f144d1458
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLine.TableExt.al
@@ -0,0 +1,252 @@
+// ------------------------------------------------------------------------------------------------
+// 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.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 OnAfterValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+
+#endif
+ SetSubcontractingLineType();
+ end;
+ }
+ modify("Work Center No.")
+ {
+ trigger OnAfterValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+
+#endif
+ SetSubcontractingLineType();
+ end;
+ }
+ field(99001543; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001544; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001545; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Routing No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Routing Header";
+ }
+ field(99001546; "Subc. Rtng Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001547; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Operation No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."),
+ "Routing Reference No." = field("Subc. Rtng Reference No."));
+ }
+ field(99001548; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No. (Sub)';
+ DataClassification = CustomerContent;
+ 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; "Subc.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;
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies whether this purchase line is linked to a WIP item transfer operation.';
+ }
+ }
+
+ keys
+ {
+ key(SubcPurchLineKey; "Subc. Purchase Line Type") { }
+ }
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ procedure GetQuantityPerUOM(): Decimal
+ var
+ ItemUnitofMeasure: Record "Item Unit of Measure";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+#endif
+ ItemUnitofMeasure.SetLoadFields("Qty. per Unit of Measure");
+ ItemUnitofMeasure.Get("No.", "Unit of Measure Code");
+ exit(ItemUnitofMeasure."Qty. per Unit of Measure");
+ end;
+
+ procedure GetQuantityBase(): Decimal
+ var
+ ItemUnitofMeasure: Record "Item Unit of Measure";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(false);
+#endif
+ 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.");
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(false);
+#endif
+ 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/Purchase/SubcPurchaseLineArchive.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineArchive.TableExt.al
new file mode 100644
index 0000000000..4a76a2e6be
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineArchive.TableExt.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Archive;
+
+tableextension 99001516 "Subc. Purchase Line Archive" extends "Purchase Line Archive"
+{
+ fields
+ {
+ field(99001543; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001544; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001545; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Routing No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Routing Header";
+ }
+ field(99001546; "Subc. Rtng Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001547; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Operation No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."),
+ "Routing Reference No." = field("Subc. Rtng Reference No."));
+ }
+ field(99001548; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No. (Sub)';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Work Center";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineExt.Codeunit.al
new file mode 100644
index 0000000000..29aaeeae53
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineExt.Codeunit.al
@@ -0,0 +1,459 @@
+// ------------------------------------------------------------------------------------------------
+// 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.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
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ 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.';
+ ChangeVariantNoNotAllowedErr: Label 'You cannot change %1 because the order line is associated with production order %2.', Comment = '%1=Field Caption, %2=Production Order No.';
+ ShowProductionOrderActionLbl: Label 'Show Prod. Order';
+ AdjustQtyActionLbl: Label 'Adjust Quantity';
+ OpenItemTrackingAnywayActionLbl: Label 'Open anyway';
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnAfterDeleteEvent, '', false, false)]
+ local procedure OnAfterDeleteEvent(var Rec: Record "Purchase Line"; RunTrigger: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if not RunTrigger then
+ exit;
+
+ SubcSynchronizeManagement.DeleteEnhancedDocumentsByDeletePurchLine(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeDeleteEvent, '', false, false)]
+ local procedure CheckTransferOnBeforeDeleteEvent(var Rec: Record "Purchase Line"; RunTrigger: Boolean)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+ if not RunTrigger then
+ exit;
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeDeleted(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnAfterValidateEvent, "Expected Receipt Date", false, false)]
+ local procedure OnAfterValidateExpectedReceiptDate(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ if CurrFieldNo <> 0 then
+ SubcSynchronizeManagement.SynchronizeExpectedReceiptDate(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnAfterValidateEvent, Quantity, false, false)]
+ local procedure OnAfterValidateQuantity(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ if CurrFieldNo <> 0 then
+ SubcSynchronizeManagement.SynchronizeQuantity(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnAfterValidateEvent, "Unit of Measure Code", false, false)]
+ local procedure OnAfterValidateUnitOfMeasureCode(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ if CurrFieldNo <> 0 then
+ SubcSynchronizeManagement.SynchronizeQuantity(Rec, xRec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeValidateEvent, Quantity, false, false)]
+ local procedure OnBeforeValidateQuantity(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec.Quantity = xRec.Quantity then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(Rec, Rec.FieldCaption(Quantity));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeValidateEvent, "No.", false, false)]
+ local procedure OnBeforeValidateNo(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."No." = xRec."No." then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(Rec, Rec.FieldCaption("No."));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeValidateEvent, "Location Code", false, false)]
+ local procedure OnBeforeValidateLocationCode(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."Location Code" = xRec."Location Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(Rec, Rec.FieldCaption("Location Code"));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeValidateEvent, "Bin Code", false, false)]
+ local procedure OnBeforeValidateBinCode(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."Bin Code" = xRec."Bin Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(Rec, Rec.FieldCaption("Bin Code"));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeValidateEvent, "Variant Code", false, false)]
+ local procedure OnBeforeValidateVariantCode(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."Variant Code" = xRec."Variant Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(Rec, Rec.FieldCaption("Variant Code"));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeValidateEvent, "Unit of Measure Code", false, false)]
+ local procedure OnBeforeValidateUnitOfMeasureCode(var Rec: Record "Purchase Line"; var xRec: Record "Purchase Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if Rec."Prod. Order No." = '' then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."Unit of Measure Code" = xRec."Unit of Measure Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(Rec, Rec.FieldCaption("Unit of Measure Code"));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeUpdateDirectUnitCost, '', false, false)]
+ local procedure OnBeforeUpdateDirectUnitCost(var PurchLine: Record "Purchase Line"; xPurchLine: Record "Purchase Line"; CalledByFieldNo: Integer; CurrFieldNo: Integer; var Handled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PurchLine."Prod. Order No." <> '' then begin
+ Handled := true;
+ PurchLine.UpdateAmounts();
+
+ GetSubcontractingPrice(PurchLine);
+ PurchLine.Validate("Line Discount %");
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnValidateVariantCodeOnBeforeDropShipmentError, '', false, false)]
+ local procedure OnValidateVariantCodeOnBeforeDropShipmentError(PurchaseLine: Record "Purchase Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PurchaseLine."Prod. Order No." <> '' then
+ Error(ChangeVariantNoNotAllowedErr, PurchaseLine.FieldCaption(PurchaseLine."Variant Code"), PurchaseLine."Prod. Order No.");
+ end;
+
+ local procedure GetSubcontractingPrice(var PurchaseLine: Record "Purchase Line")
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if OpenItemTrackingOfProdOrderLine(PurchaseLine, false) then
+ 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
+ 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): 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(false);
+ 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(false);
+ if not PurchaseLine.IsSubcontractingLineWithLastOperation(ProdOrderLine) then
+ exit(false);
+
+ 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();
+ exit(true);
+ end;
+
+ internal procedure ShowProductionOrder(OverDeliveryErrorInfo: ErrorInfo)
+ var
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ PageManagement: Codeunit "Page Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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/Purchase/SubcPurchaseLineFactbox.Page.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineFactbox.Page.al
new file mode 100644
index 0000000000..8297924093
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineFactbox.Page.al
@@ -0,0 +1,141 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+
+page 99001518 "Subc. Purchase Line Factbox"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Details';
+ Editable = false;
+ PageType = CardPart;
+ SourceTable = "Purchase Line";
+
+ layout
+ {
+ area(Content)
+ {
+ field(ShowProdOrder; Rec."Prod. Order No.")
+ {
+ Caption = 'Production Order';
+ ToolTip = 'Specifies the dependent Production Order of this Subcontracting Purchase Order.';
+ trigger OnDrillDown()
+ begin
+ ShowProductionOrder(Rec);
+ end;
+ }
+ field(ShowTransOrder; GetTransferOrderNo(Rec))
+ {
+ Caption = 'Transfer Order';
+ ToolTip = 'Specifies the dependent Transfer Order of this Subcontracting Purchase Order.';
+ trigger OnDrillDown()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, false);
+ end;
+ }
+ field(NoOfTransOrder; GetNoOfTransferOrders(Rec))
+ {
+ Caption = 'No. of Transfer Orders';
+ ToolTip = 'Specifies the number of Transfer Orders created for this Subcontracting Purchase Order.';
+ trigger OnDrillDown()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, false);
+ end;
+ }
+ field(ShowReturnTransOrder; GetReturnTransferOrderNo(Rec))
+ {
+ Caption = 'Return Transfer Order';
+ ToolTip = 'Specifies the dependent Return Transfer Order of this Subcontracting Purchase Order.';
+ trigger OnDrillDown()
+ begin
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(Rec, true, true);
+ end;
+ }
+ field(ShowProdOrderRouting; GetNoOfProductionOrderRoutings(Rec))
+ {
+ Caption = 'Production Routing';
+ ToolTip = 'Specifies the dependent Production Routing of this Subcontracting Purchase Order.';
+ trigger OnDrillDown()
+ begin
+ ShowProductionOrderRouting(Rec);
+ end;
+ }
+ field(ShowProdOrderComponents; GetNoOfProductionComponents(Rec))
+ {
+ Caption = 'Production Components';
+ ToolTip = 'Specifies the dependent Production Components of this Subcontracting Purchase Order.';
+
+ trigger OnDrillDown()
+ begin
+ ShowProductionOrderComponents(Rec);
+ end;
+ }
+ field(SubcontractingPrices; StrSubstNo(PlaceholderLbl, SubcPurchFactboxMgmt.CalcNoOfPurchasePrices(Rec)))
+ {
+ Caption = 'Subcontractor Prices';
+ DrillDown = true;
+ Editable = true;
+ ToolTip = 'Specifies how many special subcontractor prices your vendor grants you for the purchase line.';
+
+ trigger OnDrillDown()
+ begin
+ ShowSubcontractorPrices();
+ end;
+ }
+ }
+ }
+ var
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+
+ local procedure GetNoOfProductionComponents(RecRelatedVariant: Variant): Integer
+ begin
+ exit(SubcProdOrderFactboxMgmt.CalcNoOfProductionOrderComponents(RecRelatedVariant))
+ end;
+
+ local procedure GetNoOfProductionOrderRoutings(RecRelatedVariant: Variant): Integer
+ begin
+ exit(SubcProdOrderFactboxMgmt.CalcNoOfProductionOrderRoutings(RecRelatedVariant))
+ end;
+
+ local procedure GetTransferOrderNo(RecRelatedVariant: Variant): Code[20]
+ begin
+ exit(SubcPurchFactboxMgmt.GetTransferOrderNo(RecRelatedVariant))
+ end;
+
+ local procedure GetReturnTransferOrderNo(RecRelatedVariant: Variant): Code[20]
+ begin
+ exit(SubcPurchFactboxMgmt.GetReturnTransferOrderNo(RecRelatedVariant))
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowSubcontractorPrices()
+ begin
+ SubcPurchFactboxMgmt.ShowSubcontractorPrices(Rec);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure GetNoOfTransferOrders(RecRelatedVariant: Variant): Integer
+ begin
+ exit(SubcPurchFactboxMgmt.GetNoOfTransferOrders(RecRelatedVariant))
+ end;
+
+ var
+ PlaceholderLbl: Label '%1', Locked = true;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineType.Enum.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseLineType.Enum.al
new file mode 100644
index 0000000000..485d20a82d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/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/Purchase/SubcPurchaseOrderCreator.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseOrderCreator.Codeunit.al
new file mode 100644
index 0000000000..00f1eecaf5
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcPurchaseOrderCreator.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;
+
+using Microsoft.Finance.Dimension;
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Foundation.Enums;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Costing;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Item.Catalog;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Utilities;
+using System.Utilities;
+
+codeunit 99001557 "Subc. Purchase Order Creator"
+{
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ PageManagement: Codeunit "Page Management";
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ HasManufacturingSetup: Boolean;
+ OperationNo: Code[10];
+ RoutingReferenceNo: Integer;
+ PurchOrderCreatedSingularTxt: Label 'A purchase order was created.\\Do you want to view it?';
+ PurchOrderCreatedPluralTxt: Label '%1 purchase orders were created.\\Do you want to view them?', Comment = '%1 = number of purchase orders created';
+ PurchOrderAlreadyCreatedQst: Label 'Purchase orders have already been created.\\Do you want to view them?';
+ CreationOfSubcontractingOrderIsNotAllowedErr: Label 'You cannot create Subcontracting Order, because the Production Order %1 is not released.', Comment = '%1=Production Order No.';
+ BlankLocationConfirmQst: Label 'One or more Prod. Order Components with Component Supply Method Transfer to Vendor have a blank Location Code. Without a Location Code, you will not be able to create a transfer order to send the components to the subcontractor.\\Do you want to create the Subcontracting Order anyway?';
+ SameAsSubcLocConfirmQst: Label 'One or more Prod. Order Components with Component Supply Method Transfer to Vendor have Location Code %1, which is the same as the Subcontracting Location Code of vendor %2. A transfer order cannot be created from and to the same location.\\Do you want to create the Subcontracting Order anyway?', Comment = '%1=Component Location Code, %2=Vendor No.';
+ NotEnoughSpaceErr: Label 'There is not enough space to insert the subcontracting info line.';
+
+ procedure CreateSubcontractingPurchaseOrderFromRoutingLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line") NoOfCreatedPurchOrder: Integer
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ BaseQtyToPurch: Decimal;
+ QtyToPurch: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+
+#endif
+ GetManufacturingSetup();
+ ManufacturingSetup.TestField("Subcontracting Template Name");
+ ManufacturingSetup.TestField("Subcontracting Batch Name");
+
+ if not CheckProdOrderRtngLine(ProdOrderRoutingLine, ProdOrderLine) then
+ exit(0);
+
+ if not CheckProdOrderComponentLines(ProdOrderRoutingLine) then
+ exit;
+
+ ProdOrderLine.SetLoadFields("Quantity (Base)", "Scrap %", "Qty. per Unit of Measure", "Item No.", "Variant Code", "Unit of Measure Code", "Total Exp. Oper. Output (Qty.)", "Location Code", "Bin Code");
+ ProdOrderLine.FindSet();
+ repeat
+ BaseQtyToPurch := GetBaseQtyToPurchase(ProdOrderRoutingLine, ProdOrderLine);
+ QtyToPurch := Round(BaseQtyToPurch / ProdOrderLine."Qty. per Unit of Measure", UnitofMeasureManagement.QtyRndPrecision());
+ if QtyToPurch > 0 then
+ CreateSubcontractingPurchase(ProdOrderRoutingLine,
+ ProdOrderLine,
+ QtyToPurch,
+ NoOfCreatedPurchOrder);
+ until ProdOrderLine.Next() = 0;
+
+ exit(NoOfCreatedPurchOrder);
+ end;
+
+ procedure InsertProdDescriptionOnAfterInsertPurchOrderLine(PurchOrderLine: Record "Purchase Line"; var RequisitionLine: Record "Requisition Line")
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetManufacturingSetup();
+
+ if not HasManufacturingSetup then
+ exit;
+
+ if not ManufacturingSetup."Create Prod. Order Info Line" then
+ exit;
+
+ if (RequisitionLine."Prod. Order No." <> '') and
+ (RequisitionLine."Prod. Order Line No." <> 0) and
+ (RequisitionLine."Operation No." <> '') and
+ (RequisitionLine."Routing Reference No." <> 0)
+ then begin
+ ProdOrderLine.SetLoadFields(Description, "Description 2");
+ ProdOrderLine.Get("Production Order Status"::Released, RequisitionLine."Prod. Order No.", RequisitionLine."Prod. Order Line No.");
+
+ PurchaseLine.Init();
+ PurchaseLine."Line No." := GetLineNoBeforeInsertedLineNo(PurchOrderLine);
+ PurchaseLine."Document Type" := PurchOrderLine."Document Type";
+ PurchaseLine."Document No." := PurchOrderLine."Document No.";
+ PurchaseLine.Type := "Purchase Line Type"::" ";
+ PurchaseLine.Description := ProdOrderLine.Description;
+ PurchaseLine."Description 2" := ProdOrderLine."Description 2";
+
+ PurchaseLine.Insert();
+ end;
+ end;
+
+ procedure TransferSubcontractingProdOrderComp(var PurchaseLine: Record "Purchase Line"; var RequisitionLine: Record "Requisition Line"; var NextLineNo: Integer)
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseHeader: Record "Purchase Header";
+ Purchasing: Record Purchasing;
+ WorkCenter: Record "Work Center";
+ DimensionManagement: Codeunit DimensionManagement;
+ SubContractorWorkCenterNo: Code[20];
+ DimensionSetIDArr: array[10] of Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetManufacturingSetup();
+ ProdOrderRoutingLine.SetLoadFields("Work Center No.", Status, "Prod. Order No.", "Routing Link Code");
+ if ProdOrderRoutingLine.Get("Production Order Status"::Released, RequisitionLine."Prod. Order No.", RequisitionLine."Routing Reference No.", RequisitionLine."Routing No.", RequisitionLine."Operation No.") then begin
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ if WorkCenter.Get(ProdOrderRoutingLine."Work Center No.") then begin
+ SubContractorWorkCenterNo := WorkCenter."No.";
+ OnBeforeHandleProdOrderRtngWorkCenterWithSubcontractor(SubContractorWorkCenterNo);
+ if SubContractorWorkCenterNo <> '' then begin
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ ProdOrderComponent.SetRange(Status, ProdOrderRoutingLine.Status);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderComponent.SetRange("Prod. Order Line No.", RequisitionLine."Prod. Order Line No.");
+ ProdOrderComponent.SetRange("Component Supply Method", "Component Supply Method"::"Vendor-Supplied");
+ ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ if ProdOrderComponent.FindSet() then
+ repeat
+ InitPurchOrderLine(PurchaseLine, PurchaseHeader, RequisitionLine, ProdOrderComponent, NextLineNo);
+
+ PurchaseLine."Drop Shipment" := RequisitionLine."Sales Order Line No." <> 0;
+
+ if Purchasing.Get(RequisitionLine."Purchasing Code") then
+ if PurchaseLine."Special Order" then begin
+ PurchaseLine."Special Order Sales No." := RequisitionLine."Sales Order No.";
+ PurchaseLine."Special Order Sales Line No." := RequisitionLine."Sales Order Line No.";
+ PurchaseLine."Special Order" := true;
+ PurchaseLine."Drop Shipment" := false;
+ PurchaseLine."Sales Order No." := '';
+ PurchaseLine."Sales Order Line No." := 0;
+ PurchaseLine.UpdateUnitCost();
+ end;
+
+ DimensionSetIDArr[1] := ProdOrderComponent."Dimension Set ID";
+ DimensionSetIDArr[2] := PurchaseLine."Dimension Set ID";
+ PurchaseLine."Dimension Set ID" :=
+ DimensionManagement.GetCombinedDimensionSetID(
+ DimensionSetIDArr, PurchaseLine."Shortcut Dimension 1 Code", PurchaseLine."Shortcut Dimension 2 Code");
+ PurchaseLine."Order Date" := WorkDate();
+
+ PurchaseLine."Subc. Prod. Order No." := ProdOrderRoutingLine."Prod. Order No.";
+ PurchaseLine."Subc. Prod. Order Line No." := ProdOrderRoutingLine."Routing Reference No.";
+ PurchaseLine."Subc. Routing No." := ProdOrderRoutingLine."Routing No.";
+ PurchaseLine."Subc. Rtng Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ PurchaseLine."Subc. Operation No." := ProdOrderRoutingLine."Operation No.";
+ PurchaseLine."Subc. Work Center No." := ProdOrderRoutingLine."Work Center No.";
+
+ PurchaseLine.Insert();
+ until ProdOrderComponent.Next() = 0;
+ end;
+ end
+ end;
+ end;
+
+ procedure ShowCreatedPurchaseOrder(ProdOrderNo: Code[20]; NoOfCreatedPurchOrder: Integer)
+ var
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ InstructionMgt: Codeunit "Instruction Mgt.";
+ SubcNotificationMgmt: Codeunit "Subc. Notification Mgmt.";
+ IsHandled: Boolean;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ OnBeforeShowCreatedPurchaseOrder(ProdOrderNo, NoOfCreatedPurchOrder, IsHandled);
+ if IsHandled then
+ exit;
+
+ if NoOfCreatedPurchOrder = 0 then
+ exit;
+ if InstructionMgt.IsEnabled(SubcNotificationMgmt.GetShowCreatedSubContPurchOrderCode()) then
+ if InstructionMgt.ShowConfirm(GetPurchOrderCreatedMessage(NoOfCreatedPurchOrder), SubcNotificationMgmt.GetShowCreatedSubContPurchOrderCode()) and
+ GuiAllowed()
+ then begin
+ PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.");
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderNo);
+ if NoOfCreatedPurchOrder > 1 then
+ PageManagement.PageRun(PurchaseLine)
+ else begin
+ PurchaseLine.SetLoadFields(SystemId);
+ if (NoOfCreatedPurchOrder = 1) and (OperationNo <> '') then
+ PurchaseLine.SetRange("Operation No.", OperationNo);
+ if (NoOfCreatedPurchOrder = 1) and (RoutingReferenceNo <> 0) then
+ PurchaseLine.SetRange("Routing Reference No.", RoutingReferenceNo);
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PageManagement.PageRun(PurchaseHeader);
+ end;
+ end;
+ end;
+
+ local procedure GetPurchOrderCreatedMessage(NoOfCreatedPurchOrder: Integer): Text
+ begin
+ if NoOfCreatedPurchOrder > 1 then
+ exit(StrSubstNo(PurchOrderCreatedPluralTxt, NoOfCreatedPurchOrder));
+ exit(PurchOrderCreatedSingularTxt);
+ end;
+
+ internal procedure SetOperationNoForCreatedPurchaseOrder(OperationNoToSet: Code[10])
+ begin
+ OperationNo := OperationNoToSet;
+ end;
+
+ internal procedure ClearOperationNoForCreatedPurchaseOrder()
+ begin
+ Clear(OperationNo);
+ end;
+
+ internal procedure SetRoutingReferenceNoForCreatedPurchaseOrder(RoutingReferenceNoToSet: Integer)
+ begin
+ RoutingReferenceNo := RoutingReferenceNoToSet;
+ end;
+
+ internal procedure ClearRoutingReferenceNoForCreatedPurchaseOrder()
+ begin
+ Clear(RoutingReferenceNo);
+ end;
+
+ local procedure CheckProdOrderRtngLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var ProdOrderLine: Record "Prod. Order Line"): Boolean
+ var
+ WorkCenter: Record "Work Center";
+ begin
+ if ProdOrderRoutingLine.Status <> "Production Order Status"::Released then
+ Error(CreationOfSubcontractingOrderIsNotAllowedErr, ProdOrderRoutingLine."Prod. Order No.");
+
+ ProdOrderLine.SetCurrentKey(Status, "Prod. Order No.", "Routing No.", "Routing Reference No.");
+ ProdOrderLine.SetRange(Status, ProdOrderRoutingLine.Status);
+ ProdOrderLine.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ ProdOrderLine.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ ProdOrderLine.SetFilter("Remaining Quantity", '<>%1', 0);
+ if ProdOrderLine.IsEmpty() then
+ exit(false);
+
+ WorkCenter.SetLoadFields("Gen. Prod. Posting Group", "Subcontractor No.");
+ WorkCenter.Get(ProdOrderRoutingLine."Work Center No.");
+ WorkCenter.TestField("Subcontractor No.");
+ WorkCenter.TestField("Gen. Prod. Posting Group");
+ exit(true);
+ end;
+
+ internal procedure ShowExistingPurchaseOrdersForRoutingLines(var ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ var
+ PurchaseLine: Record "Purchase Line";
+ ConfirmManagement: Codeunit "Confirm Management";
+ ExistingPOFound: Boolean;
+ ProdOrderNo: Code[20];
+ begin
+ if not ProdOrderRoutingLine.FindSet() then
+ exit;
+
+ ProdOrderNo := ProdOrderRoutingLine."Prod. Order No.";
+ repeat
+ PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.");
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ 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 not PurchaseLine.IsEmpty() then
+ ExistingPOFound := true;
+ until (ProdOrderRoutingLine.Next() = 0) or ExistingPOFound;
+
+ if not ExistingPOFound then
+ exit;
+
+ if not ConfirmManagement.GetResponseOrDefault(PurchOrderAlreadyCreatedQst, false) then
+ exit;
+
+ PurchaseLine.Reset();
+ PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.");
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderNo);
+ PageManagement.PageRun(PurchaseLine);
+ end;
+
+ internal procedure CreateSubcontractingOrdersForRoutingLineSelection(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Integer
+ var
+ NoOfCreatedPurchOrder: Integer;
+ begin
+ ProdOrderRoutingLine.SetRange(Type, "Capacity Type"::"Work Center");
+ ShowExistingPurchaseOrdersForRoutingLines(ProdOrderRoutingLine);
+ if ProdOrderRoutingLine.FindSet() then
+ repeat
+ NoOfCreatedPurchOrder += CreateSubcontractingPurchaseOrderFromRoutingLine(ProdOrderRoutingLine);
+ until ProdOrderRoutingLine.Next() = 0;
+ exit(NoOfCreatedPurchOrder);
+ end;
+
+ local procedure CheckProdOrderComponentLines(ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Boolean
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ WorkCenter: Record "Work Center";
+ Vendor: Record Vendor;
+ ConfirmManagement: Codeunit "Confirm Management";
+ begin
+ WorkCenter.SetLoadFields("Subcontractor No.");
+ WorkCenter.Get(ProdOrderRoutingLine."Work Center No.");
+
+ Vendor.SetLoadFields("Subc. Location Code");
+ if not Vendor.Get(WorkCenter."Subcontractor No.") then
+ exit(true);
+
+ ProdOrderComponent.SetRange(Status, ProdOrderRoutingLine.Status);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ ProdOrderComponent.SetRange("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ ProdOrderComponent.SetRange("Component Supply Method", "Component Supply Method"::"Transfer to Vendor");
+
+ ProdOrderComponent.SetRange("Location Code", '');
+ if not ProdOrderComponent.IsEmpty() then
+ if not ConfirmManagement.GetResponseOrDefault(BlankLocationConfirmQst, true) then
+ exit(false);
+
+ if Vendor."Subc. Location Code" <> '' then begin
+ ProdOrderComponent.SetRange("Location Code", Vendor."Subc. Location Code");
+ if not ProdOrderComponent.IsEmpty() then
+ if not ConfirmManagement.GetResponseOrDefault(StrSubstNo(SameAsSubcLocConfirmQst, Vendor."Subc. Location Code", Vendor."No."), true) then
+ exit(false);
+ end;
+
+ exit(true);
+ end;
+
+ local procedure CreateSubcontractingPurchase(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderLine: Record "Prod. Order Line"; QtyToPurch: Decimal; var NoOfCreatedPurchOrder: Integer)
+ var
+ RequisitionLine: Record "Requisition Line";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ begin
+ ProdOrderLine.CalcFields("Total Exp. Oper. Output (Qty.)");
+
+ RequisitionLine.SetRange("Worksheet Template Name", ManufacturingSetup."Subcontracting Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", ManufacturingSetup."Subcontracting Batch Name");
+ FilterReqLineWithProdOrderAndRtngLine(RequisitionLine, ProdOrderLine, ProdOrderRoutingLine);
+ if RequisitionLine.FindFirst() then
+ RequisitionLine.Delete();
+
+ InsertReqWkshLine(ProdOrderRoutingLine, ProdOrderLine, ManufacturingSetup."Subcontracting Template Name", ManufacturingSetup."Subcontracting Batch Name", QtyToPurch);
+
+ if RequisitionLine.FindFirst() then begin
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.SetHideDialog(true);
+ CarryOutActionMsgReq.RunModal();
+ Clear(CarryOutActionMsgReq);
+ NoOfCreatedPurchOrder += 1;
+ end;
+ end;
+
+ local procedure FilterReqLineWithProdOrderAndRtngLine(var RequisitionLine: Record "Requisition Line"; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ RequisitionLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ RequisitionLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+
+ RequisitionLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ RequisitionLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ RequisitionLine.SetRange("Work Center No.", ProdOrderRoutingLine."Work Center No.");
+ RequisitionLine.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ end;
+
+ local procedure GetBaseQtyToPurchase(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderLine: Record "Prod. Order Line") BaseQuantityToPurch: Decimal
+ var
+ MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt.";
+ ActOutputQtyBase: Decimal;
+ OutputQtyBaseOnPurchOrder: Decimal;
+ QtyAdjdForRoutingScrap: Decimal;
+ QtyAdjForBomScrap: Decimal;
+ begin
+ QtyAdjForBomScrap := MfgCostCalculationMgt.CalcQtyAdjdForBOMScrap(ProdOrderLine."Quantity (Base)", ProdOrderLine."Scrap %");
+
+ QtyAdjdForRoutingScrap := MfgCostCalculationMgt.CalcQtyAdjdForRoutingScrap(QtyAdjForBomScrap, ProdOrderRoutingLine."Scrap Factor % (Accumulated)", ProdOrderRoutingLine."Fixed Scrap Qty. (Accum.)");
+
+ OutputQtyBaseOnPurchOrder := MfgCostCalculationMgt.CalcOutputQtyBaseOnPurchOrder(ProdOrderLine, ProdOrderRoutingLine);
+
+ ActOutputQtyBase := MfgCostCalculationMgt.CalcActOutputQtyBase(ProdOrderLine, ProdOrderRoutingLine);
+
+ BaseQuantityToPurch := QtyAdjdForRoutingScrap - (OutputQtyBaseOnPurchOrder + ActOutputQtyBase);
+
+ exit(BaseQuantityToPurch);
+ end;
+
+ local procedure GetManufacturingSetup()
+ begin
+ if HasManufacturingSetup then
+ exit;
+ HasManufacturingSetup := ManufacturingSetup.Get();
+ end;
+
+ local procedure GetLineNoBeforeInsertedLineNo(PurchaseLine: Record "Purchase Line") BeforeLineNo: Integer
+ var
+ ToPurchaseLine: Record "Purchase Line";
+ LineSpacing: Integer;
+ begin
+ ToPurchaseLine.Reset();
+ ToPurchaseLine.SetRange("Document Type", PurchaseLine."Document Type");
+ ToPurchaseLine.SetRange("Document No.", PurchaseLine."Document No.");
+ ToPurchaseLine := PurchaseLine;
+#pragma warning disable AA0181
+ if ToPurchaseLine.Find('<') then begin
+#pragma warning restore AA0181
+ LineSpacing :=
+ (PurchaseLine."Line No." - ToPurchaseLine."Line No.") div 2;
+ if LineSpacing = 0 then
+ Error(NotEnoughSpaceErr);
+ end else
+ LineSpacing := 5000;
+
+ BeforeLineNo := PurchaseLine."Line No." - LineSpacing;
+ end;
+
+ local procedure GetNextReqLineNo(RequisitionLine: Record "Requisition Line"): Integer
+ var
+ RequisitionLine2: Record "Requisition Line";
+ NextLineNo: Integer;
+ begin
+ RequisitionLine2.SetRange(RequisitionLine2."Worksheet Template Name", RequisitionLine."Worksheet Template Name");
+ RequisitionLine2.SetRange(RequisitionLine2."Journal Batch Name", RequisitionLine."Journal Batch Name");
+ RequisitionLine2.SetLoadFields("Line No.");
+ if RequisitionLine2.FindLast() then
+ NextLineNo := RequisitionLine2."Line No." + 10000
+ else
+ NextLineNo += 10000;
+ exit(NextLineNo);
+ end;
+
+ local procedure InitPurchOrderLine(var PurchaseLine: Record "Purchase Line"; PurchaseHeader: Record "Purchase Header"; RequisitionLine: Record "Requisition Line"; ProdOrderComponent: Record "Prod. Order Component"; var NextLineNo: Integer)
+ var
+ Item: Record Item;
+ begin
+ GetManufacturingSetup();
+
+ Item.SetLoadFields("Item Category Code");
+ Item.Get(ProdOrderComponent."Item No.");
+
+ PurchaseLine.Init();
+ PurchaseLine.BlockDynamicTracking(true);
+ PurchaseLine."Document Type" := "Purchase Document Type"::Order;
+ PurchaseLine."Buy-from Vendor No." := RequisitionLine."Vendor No.";
+ PurchaseLine."Document No." := PurchaseHeader."No.";
+ NextLineNo := NextLineNo + 10000;
+ PurchaseLine."Line No." := NextLineNo;
+
+ PurchaseLine.Validate(Type, "Purchase Line Type"::Item);
+
+ PurchaseLine.Validate("No.", ProdOrderComponent."Item No.");
+
+ PurchaseLine.Validate("Variant Code", ProdOrderComponent."Variant Code");
+
+ PurchaseLine.Validate("Location Code", ProdOrderComponent."Location Code");
+ if ProdOrderComponent."Bin Code" <> '' then
+ PurchaseLine.Validate("Bin Code", ProdOrderComponent."Bin Code");
+ PurchaseLine.Validate("Unit of Measure Code", ProdOrderComponent."Unit of Measure Code");
+ PurchaseLine."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+
+ PurchaseLine.Validate(Quantity, ProdOrderComponent."Remaining Quantity");
+
+ if ManufacturingSetup."Component Direct Unit Cost" <> ManufacturingSetup."Component Direct Unit Cost"::Standard then begin
+ if PurchaseHeader."Prices Including VAT" then
+ PurchaseLine.Validate("Direct Unit Cost", ProdOrderComponent."Direct Unit Cost" * (1 + PurchaseLine."VAT %" / 100))
+ else
+ PurchaseLine.Validate("Direct Unit Cost", ProdOrderComponent."Direct Unit Cost");
+ PurchaseLine.Validate("Line Discount %", RequisitionLine."Line Discount %");
+ end;
+
+ PurchaseLine.Description := ProdOrderComponent.Description;
+ PurchaseLine."Description 2" := ProdOrderComponent."Description 2";
+
+ PurchaseLine."Sales Order No." := RequisitionLine."Sales Order No.";
+ PurchaseLine."Sales Order Line No." := RequisitionLine."Sales Order Line No.";
+
+ PurchaseLine."Item Category Code" := Item."Item Category Code";
+ PurchaseLine.Validate("Purchasing Code", RequisitionLine."Purchasing Code");
+
+ if RequisitionLine."Due Date" <> 0D then begin
+ PurchaseLine.Validate("Expected Receipt Date", RequisitionLine."Due Date");
+ PurchaseLine."Requested Receipt Date" := PurchaseLine."Planned Receipt Date";
+ end;
+ end;
+
+ local procedure InsertReqWkshLine(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; ProdOrderLine: Record "Prod. Order Line"; ReqWkshTemplateName: Code[10]; WkshName: Code[10]; QtyToPurch: Decimal)
+ var
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ PurchaseLine: Record "Purchase Line";
+ RequisitionLine: Record "Requisition Line";
+ WorkCenter: Record "Work Center";
+ begin
+ WorkCenter.SetLoadFields("Subcontractor No.", "Unit Cost Calculation", "Location Code", "Open Shop Floor Bin Code");
+ WorkCenter.Get(ProdOrderRoutingLine."Work Center No.");
+ RequisitionLine.GetProdOrderLine(ProdOrderLine);
+
+ ProdOrderLine.CalcFields("Total Exp. Oper. Output (Qty.)");
+
+ RequisitionLine.SetSubcontracting(true);
+ RequisitionLine.BlockDynamicTracking(true);
+
+ RequisitionLine.Init();
+ RequisitionLine."Worksheet Template Name" := ReqWkshTemplateName;
+ RequisitionLine."Journal Batch Name" := WkshName;
+
+ RequisitionLine."Line No." := GetNextReqLineNo(RequisitionLine);
+
+ RequisitionLine.Validate(Type, "Requisition Line Type"::Item);
+ RequisitionLine.Validate("No.", ProdOrderLine."Item No.");
+ RequisitionLine.Validate("Variant Code", ProdOrderLine."Variant Code");
+ RequisitionLine.Validate("Unit of Measure Code", ProdOrderLine."Unit of Measure Code");
+ RequisitionLine.Validate(Quantity, QtyToPurch);
+
+ GeneralLedgerSetup.Get();
+ if RequisitionLine.Quantity <> 0 then begin
+ if WorkCenter."Unit Cost Calculation" = "Unit Cost Calculation Type"::Units then
+ RequisitionLine.Validate(
+ RequisitionLine."Direct Unit Cost",
+ Round(
+ ProdOrderRoutingLine."Direct Unit Cost" * ProdOrderLine."Qty. per Unit of Measure",
+ GeneralLedgerSetup."Unit-Amount Rounding Precision"))
+ else
+ RequisitionLine.Validate(
+ RequisitionLine."Direct Unit Cost",
+ Round(
+ (ProdOrderRoutingLine."Expected Operation Cost Amt." - ProdOrderRoutingLine."Expected Capacity Ovhd. Cost") /
+ ProdOrderLine."Total Exp. Oper. Output (Qty.)",
+ GeneralLedgerSetup."Unit-Amount Rounding Precision"))
+ end else
+ RequisitionLine.Validate("Direct Unit Cost", 0);
+
+ RequisitionLine."Qty. per Unit of Measure" := 0;
+ RequisitionLine."Quantity (Base)" := 0;
+ RequisitionLine."Qty. Rounding Precision" := ProdOrderLine."Qty. Rounding Precision";
+ RequisitionLine."Qty. Rounding Precision (Base)" := ProdOrderLine."Qty. Rounding Precision (Base)";
+ RequisitionLine."Prod. Order No." := ProdOrderLine."Prod. Order No.";
+ RequisitionLine."Prod. Order Line No." := ProdOrderLine."Line No.";
+ RequisitionLine."Due Date" := ProdOrderRoutingLine."Ending Date";
+ RequisitionLine."Requester ID" := CopyStr(UserId(), 1, MaxStrLen(RequisitionLine."Requester ID"));
+
+ if WorkCenter."Location Code" <> '' then begin
+ RequisitionLine."Location Code" := WorkCenter."Location Code";
+ RequisitionLine."Bin Code" := WorkCenter."Open Shop Floor Bin Code";
+ end else begin
+ RequisitionLine."Location Code" := ProdOrderLine."Location Code";
+ RequisitionLine."Bin Code" := ProdOrderLine."Bin Code";
+ end;
+
+ RequisitionLine."Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ RequisitionLine."Routing No." := ProdOrderRoutingLine."Routing No.";
+ RequisitionLine."Operation No." := ProdOrderRoutingLine."Operation No.";
+ RequisitionLine."Work Center No." := ProdOrderRoutingLine."Work Center No.";
+ RequisitionLine."Variant Code" := ProdOrderLine."Variant Code";
+
+ RequisitionLine.Validate("Vendor No.", WorkCenter."Subcontractor No.");
+
+ RequisitionLine.Description := ProdOrderRoutingLine.Description;
+ RequisitionLine."Description 2" := ProdOrderRoutingLine."Description 2";
+ RequisitionLine.Validate("Subc. Standard Task Code", ProdOrderRoutingLine."Standard Task Code");
+ SetVendorItemNo(RequisitionLine);
+
+ if PurchLineExists(PurchaseLine, ProdOrderLine, ProdOrderRoutingLine) then begin
+ RequisitionLine.Validate(Quantity, RequisitionLine.Quantity + PurchaseLine."Outstanding Quantity");
+ RequisitionLine."Quantity (Base)" := 0;
+ RequisitionLine."Replenishment System" := "Replenishment System"::Purchase;
+
+ RequisitionLine."Ref. Order No." := PurchaseLine."Document No.";
+ RequisitionLine."Ref. Order Type" := RequisitionLine."Ref. Order Type"::Purchase;
+ RequisitionLine."Ref. Line No." := PurchaseLine."Line No.";
+
+ if PurchaseLine."Expected Receipt Date" = RequisitionLine."Due Date" then
+ RequisitionLine."Action Message" := "Action Message Type"::"Change Qty."
+ else
+ RequisitionLine."Action Message" := "Action Message Type"::"Resched. & Chg. Qty.";
+ RequisitionLine."Accept Action Message" := true;
+ end else begin
+ RequisitionLine."Replenishment System" := "Replenishment System"::"Prod. Order";
+ RequisitionLine."Ref. Order No." := ProdOrderLine."Prod. Order No.";
+ RequisitionLine."Ref. Order Type" := RequisitionLine."Ref. Order Type"::"Prod. Order";
+ RequisitionLine."Ref. Order Status" := ProdOrderLine.Status;
+ RequisitionLine."Ref. Line No." := ProdOrderLine."Line No.";
+ RequisitionLine."Action Message" := "Action Message Type"::New;
+ RequisitionLine."Accept Action Message" := true;
+ end;
+
+ if RequisitionLine."Ref. Order No." <> '' then
+ RequisitionLine.GetDimFromRefOrderLine(true);
+
+ RequisitionLine.Insert();
+ end;
+
+ local procedure SetVendorItemNo(var RequisitionLine: Record "Requisition Line")
+ var
+ Item: Record Item;
+ ItemVendor: Record "Item Vendor";
+ begin
+ if RequisitionLine."No." = '' then
+ exit;
+
+ if Item."No." <> RequisitionLine."No." then begin
+ Item.SetLoadFields("No.");
+ Item.Get(RequisitionLine."No.");
+ end;
+
+ ItemVendor.Init();
+ ItemVendor."Vendor No." := RequisitionLine."Vendor No.";
+ ItemVendor."Variant Code" := RequisitionLine."Variant Code";
+ Item.FindItemVend(ItemVendor, RequisitionLine."Location Code");
+ RequisitionLine.Validate("Vendor Item No.", ItemVendor."Vendor Item No.");
+ end;
+
+ local procedure PurchLineExists(var PurchaseLine: Record "Purchase Line"; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"): Boolean
+ begin
+ PurchaseLine.SetCurrentKey("Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ PurchaseLine.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ PurchaseLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ PurchaseLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Planning Flexibility", "Reservation Planning Flexibility"::Unlimited);
+ PurchaseLine.SetRange("Quantity Received", 0);
+ exit(PurchaseLine.FindFirst());
+ end;
+
+ [InternalEvent(false, false)]
+ local procedure OnBeforeHandleProdOrderRtngWorkCenterWithSubcontractor(var SubContractorWorkCenterNo: Code[20])
+ begin
+ end;
+
+ [InternalEvent(false, false)]
+ local procedure OnBeforeShowCreatedPurchaseOrder(ProdOrderNo: Code[20]; NoOfCreatedPurchOrder: Integer; var IsHandled: Boolean)
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcReqLineExtension.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcReqLineExtension.Codeunit.al
new file mode 100644
index 0000000000..7d099dd1b4
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcReqLineExtension.Codeunit.al
@@ -0,0 +1,81 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+
+codeunit 99001513 "Subc. Req.Line Extension"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Table, Database::"Requisition Line", OnAfterGetDirectCost, '', false, false)]
+ local procedure OnAfterGetDirectCost(var RequisitionLine: Record "Requisition Line"; CalledByFieldNo: Integer)
+ var
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcontractingManagement.UpdateSubcontractorPriceForRequisitionLine(RequisitionLine);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Requisition Line", OnAfterValidateEvent, "Vendor No.", false, false)]
+ local procedure OnAfterValidateVendorNo(var Rec: Record "Requisition Line"; var xRec: Record "Requisition Line"; CurrFieldNo: Integer)
+ var
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ SubcontractingManagement.UpdateSubcontractorPriceForRequisitionLine(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Requisition Line", OnAfterValidateEvent, Quantity, false, false)]
+ local procedure OnAfterValidateQuantity(var Rec: Record "Requisition Line"; var xRec: Record "Requisition Line"; CurrFieldNo: Integer)
+ var
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ SubcontractingManagement.UpdateSubcontractorPriceForRequisitionLine(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Req. Wksh. Template", 'OnAfterValidateEvent', 'Recurring', true, false)]
+ local procedure ReqWkshTemplateOnAfterValidateRecurring(var Rec: Record "Req. Wksh. Template")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not Rec.Recurring then
+ case Rec.Type of
+ Rec.Type::Subcontracting:
+ Rec."Page ID" := Page::"Subc. Subcontracting Worksheet";
+ end;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcRequisitionLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcRequisitionLine.TableExt.al
new file mode 100644
index 0000000000..7c468695d3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcRequisitionLine.TableExt.al
@@ -0,0 +1,191 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.Currency;
+using Microsoft.Finance.GeneralLedger.Setup;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Routing;
+
+tableextension 99001510 "Subc. RequisitionLine" extends "Requisition Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001516; "Subc. Standard Task Code"; Code[10])
+ {
+ Caption = 'Standard Task Code';
+ DataClassification = CustomerContent;
+ TableRelation = "Standard Task";
+ ToolTip = 'Specifies the code that is assigned to the standard task.';
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (Type = Type::Item) and
+ ("No." <> '') and
+ ("Prod. Order No." <> '') and
+ (xRec."Subc. Standard Task Code" <> "Subc. Standard Task Code")
+ then
+ UpdateSubcontractorPrice();
+ end;
+ }
+ field(99001517; "Base UM Qty/PL UM Qty"; Decimal)
+ {
+ Access = Internal;
+ AutoFormatType = 0;
+ Caption = 'Base UM Qty/Price list UM Qty';
+ DataClassification = CustomerContent;
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ InitValue = 1;
+ ToolTip = 'Specifies the quantity of the base unit of measure or the price list unit of measure.';
+ }
+ field(99001518; "PL UM Qty/Base UM Qty"; Decimal)
+ {
+ Access = Internal;
+ AutoFormatType = 0;
+ Caption = 'Price list UM Qty/Base UM Qty';
+ DataClassification = CustomerContent;
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the quantity of the price list unit of measure or the base unit of measure.';
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (CurrFieldNo = FieldNo("PL UM Qty/Base UM Qty")) and
+ ("Prod. Order No." <> '') and
+ (Type = Type::Item) and
+ ("PL UM Qty/Base UM Qty" <> xRec."PL UM Qty/Base UM Qty")
+ then begin
+ "Base UM Qty/PL UM Qty" := GetQuantityBase() / "PL UM Qty/Base UM Qty";
+ Validate("Subc. Pricelist Cost");
+ end;
+ end;
+ }
+ field(99001519; "Subc. UoM for Pricelist"; Code[10])
+ {
+ Access = Internal;
+ Caption = 'UoM for Price list';
+ DataClassification = CustomerContent;
+ TableRelation = "Unit of Measure";
+ ToolTip = 'Specifies the unit of measure for the price list that is on the subcontracting worksheet.';
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (CurrFieldNo = FieldNo("Subc. UoM for Pricelist")) and
+ ("Prod. Order No." <> '') and
+ (Type = Type::Item)
+ then
+ UpdateSubcontractorPriceUOM();
+ end;
+ }
+ field(99001520; "Subc. Pricelist Cost"; Decimal)
+ {
+ Access = Internal;
+ AutoFormatExpression = "Currency Code";
+ AutoFormatType = 2;
+ Caption = 'Price list Cost';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the price list cost for the item on the subcontracting worksheet.';
+ trigger OnValidate()
+ var
+ Currency: Record Currency;
+ GeneralLedgerSetup: Record "General Ledger Setup";
+ begin
+ if ("Prod. Order No." <> '') and
+ (Type = Type::Item)
+ then begin
+ "Direct Unit Cost" := "Subc. Pricelist Cost" / "Base UM Qty/PL UM Qty" * GetQuantityForUOM();
+ if ("Currency Code" <> '') and ("Direct Unit Cost" <> 0) then begin
+ Currency.Initialize("Currency Code");
+ Currency.TestField("Unit-Amount Rounding Precision");
+ "Direct Unit Cost" := Round("Direct Unit Cost", Currency."Unit-Amount Rounding Precision");
+ end else begin
+ GeneralLedgerSetup.Get();
+ "Direct Unit Cost" := Round("Direct Unit Cost", GeneralLedgerSetup."Unit-Amount Rounding Precision");
+ end;
+ end;
+ end;
+ }
+ }
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ procedure GetQuantityForUOM(): Decimal
+ var
+ ItemUnitofMeasure: Record "Item Unit of Measure";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+#endif
+ ItemUnitofMeasure.Get("No.", "Unit of Measure Code");
+ exit(ItemUnitofMeasure."Qty. per Unit of Measure");
+ end;
+
+ procedure GetQuantityBase(): Decimal
+ var
+ ItemUnitofMeasure: Record "Item Unit of Measure";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+#endif
+ ItemUnitofMeasure.Get("No.", "Unit of Measure Code");
+ exit(Round(Quantity * ItemUnitofMeasure."Qty. per Unit of Measure", 0.00001));
+ end;
+
+ procedure UpdateSubcontractorPrice()
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (Type = Type::Item) and ("No." <> '') and ("Prod. Order No." <> '') then
+ SubcPriceManagement.GetSubcPriceForReqLine(Rec, '');
+ end;
+
+ procedure UpdateSubcontractorPriceUOM()
+ var
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (Type = Type::Item) and ("No." <> '') and ("Prod. Order No." <> '') then
+ SubcPriceManagement.GetSubcPriceForReqLine(Rec, "Subc. UoM for Pricelist");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcontractorPrice.Table.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcontractorPrice.Table.al
new file mode 100644
index 0000000000..d45049c371
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcontractorPrice.Table.al
@@ -0,0 +1,261 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.Currency;
+using Microsoft.Inventory.Item;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Vendor;
+
+table 99001500 "Subcontractor Price"
+{
+ AllowInCustomizations = AsReadOnly;
+ Caption = 'Subcontractor Price';
+ DataClassification = CustomerContent;
+ DrillDownPageId = "Subcontractor Prices";
+ LookupPageId = "Subcontractor Prices";
+
+ fields
+ {
+ field(1; "Vendor No."; Code[20])
+ {
+ Caption = 'Vendor No.';
+ NotBlank = true;
+ TableRelation = Vendor;
+
+ trigger OnValidate()
+ var
+ Vendor: Record Vendor;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Vendor.Get("Vendor No.") then
+ "Currency Code" := Vendor."Currency Code";
+ end;
+ }
+ field(2; "Item No."; Code[20])
+ {
+ Caption = 'Item No.';
+ NotBlank = true;
+ TableRelation = Item;
+
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Item No." <> xRec."Item No." then begin
+ "Unit of Measure Code" := '';
+ "Variant Code" := '';
+ end;
+ end;
+ }
+ field(3; "Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No.';
+ NotBlank = true;
+ TableRelation = "Work Center"."No." where("Subcontractor No." = filter(<> ''));
+ }
+ field(4; "Variant Code"; Code[10])
+ {
+ Caption = 'Variant Code';
+ TableRelation = "Item Variant".Code where("Item No." = field("Item No."));
+ }
+ field(5; "Standard Task Code"; Code[10])
+ {
+ Caption = 'Standard Task Code';
+ TableRelation = "Standard Task";
+ }
+ field(6; "Starting Date"; Date)
+ {
+ Caption = 'Starting Date';
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ("Starting Date" > "Ending Date") and ("Ending Date" <> 0D) then
+ Error(InvalidStartingDateErr, FieldCaption("Starting Date"), FieldCaption("Ending Date"));
+ end;
+ }
+ field(7; "Unit of Measure Code"; Code[10])
+ {
+ Caption = 'Unit of Measure Code';
+ TableRelation = "Item Unit of Measure".Code where("Item No." = field("Item No."));
+ }
+ field(8; "Minimum Quantity"; Decimal)
+ {
+ AutoFormatType = 0;
+ Caption = 'Minimum Quantity';
+ DecimalPlaces = 0 : 5;
+ MinValue = 0;
+ }
+ field(9; "Currency Code"; Code[10])
+ {
+ Caption = 'Currency Code';
+ TableRelation = Currency;
+ }
+ field(10; "Direct Unit Cost"; Decimal)
+ {
+ AutoFormatExpression = "Currency Code";
+ AutoFormatType = 2;
+ Caption = 'Direct Unit Cost';
+ MinValue = 0;
+ }
+ field(20; "Minimum Amount"; Decimal)
+ {
+ AutoFormatExpression = "Currency Code";
+ AutoFormatType = 1;
+ Caption = 'Minimum Amount';
+ MinValue = 0;
+ }
+ field(30; "Ending Date"; Date)
+ {
+ Caption = 'Ending Date';
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ Validate("Starting Date");
+ end;
+ }
+ }
+ keys
+ {
+ key(PK; "Vendor No.", "Item No.", "Work Center No.", "Variant Code", "Standard Task Code", "Starting Date", "Unit of Measure Code", "Minimum Quantity", "Currency Code")
+ {
+ Clustered = true;
+ }
+ key(Key01; "Vendor No.", "Item No.", "Starting Date", "Currency Code", "Unit of Measure Code", "Minimum Quantity")
+ {
+ }
+ key(Key02; "Vendor No.", "Item No.", "Work Center No.", "Variant Code", "Unit of Measure Code", "Currency Code")
+ {
+ }
+ key(Key03; "Work Center No.")
+ {
+ }
+ key(Key04; "Item No.")
+ {
+ }
+ }
+ fieldgroups
+ {
+ fieldgroup(Brick; "Work Center No.", "Vendor No.", "Item No.", "Starting Date", "Direct Unit Cost", "Ending Date")
+ {
+ }
+ fieldgroup(DropDown; "Work Center No.", "Vendor No.", "Item No.", "Starting Date", "Direct Unit Cost", "Ending Date")
+ {
+ }
+ }
+ trigger OnInsert()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TestField("Vendor No.");
+ TestField("Item No.");
+ end;
+
+ trigger OnRename()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TestField("Vendor No.");
+ TestField("Item No.");
+ end;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ InvalidStartingDateErr: Label '%1 cannot be after %2', Comment = '%1=Field Caption for starting date, %2=Field Caption for ending date';
+
+ procedure CopySubcontractorPriceToVendorsSubcontractorPrice(var SubcontractorPrice: Record "Subcontractor Price"; VendNo: Code[20]; WorkCenterNo: Code[20])
+ var
+ NewSubcontractorPrice: Record "Subcontractor Price";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if SubcontractorPrice.FindSet() then
+ repeat
+ NewSubcontractorPrice := SubcontractorPrice;
+ NewSubcontractorPrice."Vendor No." := VendNo;
+ NewSubcontractorPrice."Work Center No." := WorkCenterNo;
+ if NewSubcontractorPrice.Insert() then;
+ until SubcontractorPrice.Next() = 0;
+ end;
+
+ internal procedure DeletePricesForVendor(VendorNo: Code[20])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SetCurrentKey("Vendor No.");
+ SetRange("Vendor No.", VendorNo);
+ if not IsEmpty() then
+ DeleteAll(true);
+ end;
+
+ internal procedure DeletePricesForWorkCenter(WorkCenterNo: Code[20])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SetCurrentKey("Work Center No.");
+ SetRange("Work Center No.", WorkCenterNo);
+ if not IsEmpty() then
+ DeleteAll(true);
+ end;
+
+ internal procedure DeletePricesForItem(ItemNo: Code[20])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SetCurrentKey("Item No.");
+ SetRange("Item No.", ItemNo);
+ if not IsEmpty() then
+ DeleteAll(true);
+ end;
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Purchase/SubcontractorPrices.Page.al b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcontractorPrices.Page.al
new file mode 100644
index 0000000000..64ad8c72a7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Purchase/SubcontractorPrices.Page.al
@@ -0,0 +1,413 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Vendor;
+using System.Globalization;
+using System.Text;
+
+page 99001500 "Subcontractor Prices"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ DataCaptionExpression = GetCaption();
+ DelayedInsert = true;
+ PageType = Worksheet;
+ SourceTable = "Subcontractor Price";
+ UsageCategory = Administration;
+
+ layout
+ {
+ area(Content)
+ {
+ group(General)
+ {
+ Caption = 'General';
+ field(VendNoFilterCtrl; VendNoFilter)
+ {
+ Caption = 'Vendor No. Filter';
+ ToolTip = 'Specifies a filter for which subcontractor prices display.';
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ VendorList: Page "Vendor List";
+ begin
+ VendorList.LookupMode := true;
+ if VendorList.RunModal() = Action::LookupOK then
+ Text := VendorList.GetSelectionFilter()
+ else
+ exit(false);
+
+ exit(true);
+ end;
+
+ trigger OnValidate()
+ begin
+ VendNoFilterOnAfterValidate();
+ end;
+ }
+ field(WorkCenterNoFilterCtrl; WorkCenterNoFilter)
+ {
+ Caption = 'Work Center No. Filter';
+ ToolTip = 'Specifies a filter for which subcontractor prices to display.';
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterList: Page "Work Center List";
+ begin
+ WorkCenter.SetFilter("Subcontractor No.", '<>%1', '');
+ WorkCenterList.SetTableView(WorkCenter);
+ WorkCenterList.LookupMode := true;
+ if WorkCenterList.RunModal() = Action::LookupOK then
+ Text := WorkCenterList.GetCurrSelectionFilter()
+ else
+ exit(false);
+
+ exit(true);
+ end;
+
+ trigger OnValidate()
+ begin
+ WorkCenterNoFilterOnAfterValidate();
+ end;
+ }
+ field(TaskCodeFilterCtrl; StandardTaskCodeFilter)
+ {
+ Caption = 'Standard Task Code Filter';
+ ToolTip = 'Specifies a filter for which subcontractor prices to display.';
+
+ trigger OnValidate()
+ begin
+ StandardTaskCodeFilterOnAfterValidate();
+ end;
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ StandardTasks: Page "Standard Tasks";
+ begin
+
+ StandardTasks.Editable(false);
+ StandardTasks.LookupMode(true);
+ if StandardTasks.RunModal() = Action::LookupOK then
+ Text := StandardTasks.GetCurrSelectionFilter()
+ else
+ exit(false);
+
+ exit(true);
+ end;
+ }
+ field(ItemNoFilterCtrl; ItemNoFilter)
+ {
+ Caption = 'Item No. Filter';
+ ToolTip = 'Specifies a filter for which subcontractor prices to display.';
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ Item: Record Item;
+ ItemList: Page "Item List";
+ begin
+ Item.SetFilter("Routing No.", '<>%1', '');
+ ItemList.SetTableView(Item);
+ ItemList.LookupMode := true;
+ if ItemList.RunModal() = Action::LookupOK then
+ Text := ItemList.GetSelectionFilter()
+ else
+ exit(false);
+
+ exit(true);
+ end;
+
+ trigger OnValidate()
+ begin
+ ItemNoFilterOnAfterValidate();
+ end;
+ }
+ field(StartingDateFilterCtrl; StartingDateFilter)
+ {
+ Caption = 'Starting Date Filter';
+ ToolTip = 'Specifies a filter for which subcontractor prices to display.';
+
+ trigger OnValidate()
+ var
+ FilterTokens: Codeunit "Filter Tokens";
+ begin
+ FilterTokens.MakeDateFilter(StartingDateFilter);
+ StartingDateFilterOnAfterValid();
+ end;
+ }
+ }
+ repeater(Prices)
+ {
+ ShowCaption = false;
+
+ field("Work Center No."; Rec."Work Center No.")
+ {
+ ToolTip = 'Specifies the number of the work center that the subcontractor price applies to.';
+ }
+ field("Vendor No."; Rec."Vendor No.")
+ {
+ ToolTip = 'Specifies the number of the vendor who offers the line subcontractor price on the item.';
+ }
+ field("Standard Task Code"; Rec."Standard Task Code")
+ {
+ ToolTip = 'Specifies the code of the standard task that the subcontractor price applies to.';
+ }
+ field("Item No."; Rec."Item No.")
+ {
+ ToolTip = 'Specifies the number of the Item that the subcontractor price applies to.';
+ }
+ field("Variant Code"; Rec."Variant Code")
+ {
+ ToolTip = 'Specifies the variant of the item on the line.';
+ Visible = false;
+ }
+ field("Starting Date"; Rec."Starting Date")
+ {
+ ToolTip = 'Specifies the date from which the subcontractor price is valid.';
+ }
+ field("Ending Date"; Rec."Ending Date")
+ {
+ ToolTip = 'Specifies the date to which the subcontractor price is valid.';
+ }
+ field("Unit of Measure Code"; Rec."Unit of Measure Code")
+ {
+ ToolTip = 'Specifies how each unit of the item is measured, such as in pieces or hours. By default, the value in the Base Unit of Measure field on the item card is inserted.';
+ }
+ field("Minimum Quantity"; Rec."Minimum Quantity")
+ {
+ ToolTip = 'Specifies the minimum quantity of the item that you must buy from the vendor in order to get the subcontractor price.';
+ }
+ field("Currency Code"; Rec."Currency Code")
+ {
+ ToolTip = 'Specifies the currency code of the subcontractor price.';
+ Visible = false;
+ }
+ field("Direct Unit Cost"; Rec."Direct Unit Cost")
+ {
+ ToolTip = 'Specifies the cost of one unit of the selected item.';
+ }
+ field("Minimum Amount"; Rec."Minimum Amount")
+ {
+ ToolTip = 'Specifies the minimum amount, in money, of the item that you must buy from the vendor in order to get the subcontractor price.';
+ }
+ }
+ }
+ area(FactBoxes)
+ {
+ systempart(LinksFactbox; Links)
+ {
+ Visible = false;
+ }
+ systempart(NotesFactbox; Notes)
+ {
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ area(Processing)
+ {
+ action(CopyPrices)
+ {
+ Caption = 'Copy Subcontractor Prices';
+ Image = Copy;
+ ToolTip = 'Select prices and press OK to copy them to Vendor No. and Work Center No.';
+ Visible = not IsLookupMode;
+
+ trigger OnAction()
+ begin
+ CopyPricesToVendor();
+ CurrPage.Update();
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ Caption = 'Process';
+ actionref(CopyPrices_Promoted; CopyPrices)
+ {
+ }
+ }
+ }
+ }
+ trigger OnOpenPage()
+ begin
+ GetRecFilters();
+ SetRecFilters();
+ IsLookupMode := CurrPage.LookupMode();
+ end;
+
+ var
+ Vendor: Record Vendor;
+ IsLookupMode: Boolean;
+ MultipleVendorsSelectedErr: Label 'More than one vendor uses these subcontractor prices. To copy prices, the Vendor No. Filter field must contain one vendor only.';
+ MultipleWorkCenterSelectedErr: Label 'More than one work center uses these subcontractor prices. To copy prices, the Work Center No. Filter field must contain one work center only.';
+ NoDataWithinFilterErr: Label 'There is no %1 within the filter %2.',
+ Comment = '@@@=%1: Field(Code), %2: GetFilter(Code)';
+ PlaceholderLbl: Label '%1 %2 %3 %4 ', Locked = true;
+ ItemNoFilter: Text;
+ StandardTaskCodeFilter: Text;
+ StartingDateFilter: Text;
+ VendNoFilter: Text;
+ WorkCenterNoFilter: Text;
+
+ local procedure GetRecFilters()
+ begin
+ if Rec.GetFilters() <> '' then begin
+ VendNoFilter := Rec.GetFilter("Vendor No.");
+ ItemNoFilter := Rec.GetFilter("Item No.");
+ WorkCenterNoFilter := Rec.GetFilter("Work Center No.");
+ StandardTaskCodeFilter := Rec.GetFilter("Standard Task Code");
+ Evaluate(StartingDateFilter, Rec.GetFilter("Starting Date"));
+ end;
+ end;
+
+ procedure SetRecFilters()
+ begin
+ if VendNoFilter <> '' then
+ Rec.SetFilter("Vendor No.", VendNoFilter)
+ else
+ Rec.SetRange("Vendor No.");
+
+ if StartingDateFilter <> '' then
+ Rec.SetFilter("Starting Date", StartingDateFilter)
+ else
+ Rec.SetRange("Starting Date");
+
+ if ItemNoFilter <> '' then
+ Rec.SetFilter("Item No.", ItemNoFilter)
+ else
+ Rec.SetRange("Item No.");
+
+ if WorkCenterNoFilter <> '' then
+ Rec.SetFilter("Work Center No.", WorkCenterNoFilter)
+ else
+ Rec.SetRange("Work Center No.");
+
+ if StandardTaskCodeFilter <> '' then
+ Rec.SetFilter("Standard Task Code", StandardTaskCodeFilter)
+ else
+ Rec.SetRange("Standard Task Code");
+
+ CheckFilters(Database::Vendor, VendNoFilter);
+ CheckFilters(Database::"Work Center", WorkCenterNoFilter);
+ CheckFilters(Database::Item, ItemNoFilter);
+ CheckFilters(Database::"Standard Task", StandardTaskCodeFilter);
+
+ CurrPage.Update(false);
+ end;
+
+ local procedure GetCaption(): Text
+ var
+ ObjectTranslation: Record "Object Translation";
+ Description: Text[100];
+ SourceTableName: Text[250];
+ begin
+ GetRecFilters();
+
+ if ItemNoFilter <> '' then
+ SourceTableName := ObjectTranslation.TranslateObject(ObjectTranslation."Object Type"::Table, 27)
+ else
+ SourceTableName := '';
+
+ if Vendor.Get(CopyStr(VendNoFilter, 1, MaxStrLen(Vendor."No."))) then
+ Description := Vendor.Name;
+
+ exit(StrSubstNo(PlaceholderLbl, VendNoFilter, Description, SourceTableName, ItemNoFilter));
+ end;
+
+ local procedure VendNoFilterOnAfterValidate()
+ var
+ Item: Record Item;
+ begin
+ if Item.Get(Rec."Item No.") then
+ CurrPage.SaveRecord();
+ SetRecFilters();
+ end;
+
+ local procedure StartingDateFilterOnAfterValid()
+ begin
+ CurrPage.SaveRecord();
+ SetRecFilters();
+ end;
+
+ local procedure ItemNoFilterOnAfterValidate()
+ begin
+ CurrPage.SaveRecord();
+ SetRecFilters();
+ end;
+
+ local procedure WorkCenterNoFilterOnAfterValidate()
+ begin
+ CurrPage.SaveRecord();
+ SetRecFilters();
+ end;
+
+ local procedure StandardTaskCodeFilterOnAfterValidate()
+ begin
+ CurrPage.SaveRecord();
+ SetRecFilters();
+ end;
+
+ procedure CheckFilters(TableNo: Integer; FilterTxt: Text)
+ var
+ FilterRecordRef: RecordRef;
+ FilterFieldRef: FieldRef;
+ begin
+ if FilterTxt = '' then
+ exit;
+ Clear(FilterRecordRef);
+ Clear(FilterFieldRef);
+ FilterRecordRef.Open(TableNo);
+ FilterFieldRef := FilterRecordRef.Field(1);
+ FilterFieldRef.SetFilter(FilterTxt);
+ if FilterRecordRef.IsEmpty() then
+ Error(NoDataWithinFilterErr, FilterRecordRef.Caption(), FilterTxt);
+ end;
+
+ local procedure CopyPricesToVendor()
+ var
+ SelectedSubcontractorPrice: Record "Subcontractor Price";
+ SubcontractorPrice: Record "Subcontractor Price";
+ ToVendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrices: Page "Subcontractor Prices";
+ CopyToVendorNo: Code[20];
+ CopyToWorkCenterNo: Code[20];
+ begin
+ ToVendor.SetFilter("No.", VendNoFilter);
+ if ToVendor.Count() <> 1 then
+ Error(MultipleVendorsSelectedErr);
+ CopyToVendorNo := CopyStr(VendNoFilter, 1, MaxStrLen(CopyToVendorNo));
+
+ WorkCenter.SetFilter("No.", WorkCenterNoFilter);
+ if WorkCenter.Count() <> 1 then
+ Error(MultipleWorkCenterSelectedErr);
+ CopyToWorkCenterNo := CopyStr(WorkCenterNoFilter, 1, MaxStrLen(CopyToWorkCenterNo));
+
+ SubcontractorPrice.SetFilter("Vendor No.", StrSubstNo('<>%1', VendNoFilter));
+ SubcontractorPrice.SetFilter("Work Center No.", WorkCenterNoFilter);
+ if ItemNoFilter <> '' then
+ SubcontractorPrice.SetRange("Item No.", ItemNoFilter);
+ SubcontractorPrices.LookupMode(true);
+ SubcontractorPrices.SetTableView(SubcontractorPrice);
+ if SubcontractorPrices.RunModal() = Action::LookupOK then begin
+ SubcontractorPrices.GetSelectionFilter(SelectedSubcontractorPrice);
+ Rec.CopySubcontractorPriceToVendorsSubcontractorPrice(SelectedSubcontractorPrice, CopyToVendorNo, CopyToWorkCenterNo);
+ end;
+ end;
+
+ procedure GetSelectionFilter(var SubcontractorPrice: Record "Subcontractor Price")
+ begin
+ CurrPage.SetSelectionFilter(SubcontractorPrice);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcManufacturingCue.TableExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcManufacturingCue.TableExt.al
new file mode 100644
index 0000000000..51da1b880a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcManufacturingCue.TableExt.al
@@ -0,0 +1,67 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.RoleCenters;
+using Microsoft.Purchases.Document;
+
+tableextension 99001529 "Subc. Manufacturing Cue" extends "Manufacturing Cue"
+{
+ fields
+ {
+ field(99001560; "Subcontracting Purchase Orders"; Integer)
+ {
+ AccessByPermission = tabledata "Purchase Header" = R;
+ CalcFormula = count("Purchase Header" where("Document Type" = const(Order),
+ "Subc. Order" = const(true)));
+ Caption = 'Subcontracting Purchase Orders';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the number of open purchase orders that are subcontracting orders.';
+ }
+ field(99001561; "Subc. Purch. Lines Outstd."; Integer)
+ {
+ AccessByPermission = tabledata "Purchase Line" = R;
+ CalcFormula = count("Purchase Line" where("Document Type" = const(Order),
+ "Subc. Purchase Line Type" = filter(<> None),
+ "Outstanding Quantity" = filter(<> 0)));
+ Caption = 'Outstanding Subc. Purch. Lines';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the number of outstanding subcontracting purchase order lines that have not yet been fully received.';
+ }
+ field(99001562; "Subc. Purch. Lines Total"; Integer)
+ {
+ AccessByPermission = tabledata "Purchase Line" = R;
+ CalcFormula = count("Purchase Line" where("Document Type" = const(Order),
+ "Subc. Purchase Line Type" = filter(<> None)));
+ Caption = 'Total Subc. Purchase Lines';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the total number of subcontracting purchase order lines.';
+ }
+ field(99001563; "Transfers to Subcontractor"; Integer)
+ {
+ AccessByPermission = tabledata "Transfer Header" = R;
+ CalcFormula = count("Transfer Header" where("Subc. Source Type" = const(Subcontracting),
+ "Subc. Return Order" = const(false)));
+ Caption = 'Transfers to Subcontractor';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the number of transfer orders to subcontractors.';
+ }
+ field(99001564; "Returns from Subcontractor"; Integer)
+ {
+ AccessByPermission = tabledata "Transfer Header" = R;
+ CalcFormula = count("Transfer Header" where("Subc. Source Type" = const(Subcontracting),
+ "Subc. Return Order" = const(true)));
+ Caption = 'Returns from Subcontractor';
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the number of transfer orders that are returns from subcontractors.';
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcManufacturingManagerRC.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcManufacturingManagerRC.PageExt.al
new file mode 100644
index 0000000000..bdb7ab4355
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcManufacturingManagerRC.PageExt.al
@@ -0,0 +1,35 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.RoleCenters;
+
+pageextension 99001536 "Subc. Manufacturing Manager RC" extends "Manufacturing Manager RC"
+{
+ actions
+ {
+ addlast(Sections)
+ {
+ group(Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ action("Subc. Subcontracting Worksheet")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheets';
+ RunObject = page "Subc. Subcontracting Worksheet";
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ action("Subc. Subcontractor Prices")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontractor Prices';
+ RunObject = page "Subcontractor Prices";
+ ToolTip = 'View and maintain the prices that subcontractors charge for the operations they perform.';
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcProdPlannerActivities.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcProdPlannerActivities.PageExt.al
new file mode 100644
index 0000000000..b216f5b2a3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcProdPlannerActivities.PageExt.al
@@ -0,0 +1,68 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.RoleCenters;
+using Microsoft.Purchases.Document;
+
+pageextension 99001537 "Subc. Prod. Planner Activities" extends "Production Planner Activities"
+{
+ layout
+ {
+ addlast(content)
+ {
+ cuegroup(SubcontractingCuegroup)
+ {
+ Caption = 'Subcontracting';
+ field("Subcontracting Purchase Orders"; Rec."Subcontracting Purchase Orders")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Purchase Order List";
+ ToolTip = 'Specifies the number of open purchase orders that are subcontracting orders.';
+ }
+ field("Subc. Purch. Lines Outstd."; Rec."Subc. Purch. Lines Outstd.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of outstanding subcontracting purchase order lines that have not yet been fully received.';
+ Visible = false;
+ }
+ field("Subc. Purch. Lines Total"; Rec."Subc. Purch. Lines Total")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the total number of subcontracting purchase order lines.';
+ Visible = false;
+ }
+ field("Transfers to Subcontractor"; Rec."Transfers to Subcontractor")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Transfer Orders";
+ ToolTip = 'Specifies the number of transfer orders to subcontractors.';
+ }
+ field("Returns from Subcontractor"; Rec."Returns from Subcontractor")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Transfer Orders";
+ ToolTip = 'Specifies the number of transfer orders that are returns from subcontractors.';
+ }
+ }
+ cuegroup(SubcontractingActionsCuegroup)
+ {
+ Caption = 'Subcontracting - Operations';
+
+ actions
+ {
+ action("Subc. Edit Subcontracting Worksheet")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Edit Subcontracting Worksheet';
+ RunObject = Page "Subc. Subcontracting Worksheet";
+ ToolTip = 'Plan outsourcing of operation on released production orders.';
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcProdPlannerRoleCenter.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcProdPlannerRoleCenter.PageExt.al
new file mode 100644
index 0000000000..8efde52996
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcProdPlannerRoleCenter.PageExt.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.RoleCenters;
+using Microsoft.Purchases.Document;
+
+pageextension 99001538 "Subc. Prod. Planner RoleCenter" extends "Production Planner Role Center"
+{
+ actions
+ {
+ addlast(sections)
+ {
+ group(Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ action("Subc. Subcontracting Worksheets")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheets';
+ RunObject = Page "Req. Wksh. Names";
+ RunPageView = where("Template Type" = const(Subcontracting),
+ Recurring = const(false));
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ action("Subc. Subcontracting Purch. Orders")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Purchase Orders';
+ RunObject = Page "Purchase Order List";
+ RunPageView = where("Document Type" = const(Order),
+ "Subc. Order" = const(true));
+ ToolTip = 'View the list of purchase orders that are subcontracting orders.';
+ }
+ action("Subc. Subcontracting Transfers")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfers';
+ RunObject = Page "Transfer Orders";
+ RunPageView = where("Subc. Source Type" = const(Subcontracting),
+ "Subc. Return Order" = const(false));
+ ToolTip = 'View the list of outbound transfer orders to subcontractors.';
+ }
+ }
+ }
+ addafter("Planning Works&heet")
+ {
+ action("Subc. Subcontracting Worksheet")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheet';
+ Image = SubcontractingWorksheet;
+ RunObject = Page "Subc. Subcontracting Worksheet";
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcPurchAgentRoleCenter.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcPurchAgentRoleCenter.PageExt.al
new file mode 100644
index 0000000000..45c45a9724
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcPurchAgentRoleCenter.PageExt.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Purchases.RoleCenters;
+
+pageextension 99001541 "Subc. Purch. Agent Role Center" extends "Purchasing Agent Role Center"
+{
+ actions
+ {
+ addafter("RequisitionWorksheets")
+ {
+ action("Subc. Subcontracting Worksheets")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheets';
+ RunObject = Page "Req. Wksh. Names";
+ RunPageView = where("Template Type" = const(Subcontracting),
+ Recurring = const(false));
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcPurchasingManagerRC.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcPurchasingManagerRC.PageExt.al
new file mode 100644
index 0000000000..d9bc2312f6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcPurchasingManagerRC.PageExt.al
@@ -0,0 +1,24 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.RoleCenters;
+
+pageextension 99001535 "Subc. Purchasing Manager RC" extends "Purchasing Manager Role Center"
+{
+ actions
+ {
+ addafter("Certificates of Supply")
+ {
+ action("Subc. Subcontracting Worksheet")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheets';
+ RunObject = page "Subc. Subcontracting Worksheet";
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSMfgFoundation.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSMfgFoundation.PageExt.al
new file mode 100644
index 0000000000..91bdb6ade5
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSMfgFoundation.PageExt.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Manufacturing.RoleCenters;
+
+pageextension 99001539 "Subc. Shop S. Mfg Foundation" extends "Shop Supervisor Mfg Foundation"
+{
+ actions
+ {
+ addafter("Standard Cost Worksheets")
+ {
+ action("Subc. Subcontracting Worksheets")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheets';
+ RunObject = Page "Req. Wksh. Names";
+ RunPageView = where("Template Type" = const(Subcontracting),
+ Recurring = const(false));
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperActivities.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperActivities.PageExt.al
new file mode 100644
index 0000000000..ac3f18d320
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperActivities.PageExt.al
@@ -0,0 +1,68 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.RoleCenters;
+using Microsoft.Purchases.Document;
+
+pageextension 99001549 "Subc. Shop Super. Activities" extends "Shop Supervisor Activities"
+{
+ layout
+ {
+ addlast(content)
+ {
+ cuegroup(SubcontractingCuegroup)
+ {
+ Caption = 'Subcontracting';
+ field("Subcontracting Purchase Orders"; Rec."Subcontracting Purchase Orders")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Purchase Order List";
+ ToolTip = 'Specifies the number of open purchase orders that are subcontracting orders.';
+ }
+ field("Subc. Purch. Lines Outstd."; Rec."Subc. Purch. Lines Outstd.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of outstanding subcontracting purchase order lines that have not yet been fully received.';
+ Visible = false;
+ }
+ field("Subc. Purch. Lines Total"; Rec."Subc. Purch. Lines Total")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the total number of subcontracting purchase order lines.';
+ Visible = false;
+ }
+ field("Transfers to Subcontractor"; Rec."Transfers to Subcontractor")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Transfer Orders";
+ ToolTip = 'Specifies the number of transfer orders to subcontractors.';
+ }
+ field("Returns from Subcontractor"; Rec."Returns from Subcontractor")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Transfer Orders";
+ ToolTip = 'Specifies the number of transfer orders that are returns from subcontractors.';
+ }
+ }
+ cuegroup(SubcontractingActionsCuegroup)
+ {
+ Caption = 'Subcontracting - Operations';
+
+ actions
+ {
+ action("Subc. Edit Subcontracting Worksheet")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Edit Subcontracting Worksheet';
+ RunObject = Page "Subc. Subcontracting Worksheet";
+ ToolTip = 'Plan outsourcing of operation on released production orders.';
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperRoleCenter.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperRoleCenter.PageExt.al
new file mode 100644
index 0000000000..995ab72af3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperRoleCenter.PageExt.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+using Microsoft.Manufacturing.RoleCenters;
+
+pageextension 99001540 "Subc. Shop Super. Role Center" extends "Shop Supervisor Role Center"
+{
+ actions
+ {
+ addafter("RequisitionWorksheets")
+ {
+ action("Subc. Subcontracting Worksheets")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Worksheets';
+ RunObject = Page "Req. Wksh. Names";
+ RunPageView = where("Template Type" = const(Subcontracting),
+ Recurring = const(false));
+ ToolTip = 'Calculate the needed production supply, find the production orders that have material ready to send to a subcontractor, and automatically create purchase orders for subcontracted operations from production order routings.';
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperbasicActivity.PageExt.al b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperbasicActivity.PageExt.al
new file mode 100644
index 0000000000..6b9a33a974
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/RoleCenters/SubcShopSuperbasicActivity.PageExt.al
@@ -0,0 +1,68 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.RoleCenters;
+using Microsoft.Purchases.Document;
+
+pageextension 99001550 "Subc. ShopSuperbasicActivity" extends "Shop Super. basic Activities"
+{
+ layout
+ {
+ addlast(content)
+ {
+ cuegroup(SubcontractingCuegroup)
+ {
+ Caption = 'Subcontracting';
+ field("Subcontracting Purchase Orders"; Rec."Subcontracting Purchase Orders")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Purchase Order List";
+ ToolTip = 'Specifies the number of open purchase orders that are subcontracting orders.';
+ }
+ field("Subc. Purch. Lines Outstd."; Rec."Subc. Purch. Lines Outstd.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of outstanding subcontracting purchase order lines that have not yet been fully received.';
+ Visible = false;
+ }
+ field("Subc. Purch. Lines Total"; Rec."Subc. Purch. Lines Total")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the total number of subcontracting purchase order lines.';
+ Visible = false;
+ }
+ field("Transfers to Subcontractor"; Rec."Transfers to Subcontractor")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Transfer Orders";
+ ToolTip = 'Specifies the number of transfer orders to subcontractors.';
+ }
+ field("Returns from Subcontractor"; Rec."Returns from Subcontractor")
+ {
+ ApplicationArea = Subcontracting;
+ DrillDownPageId = "Transfer Orders";
+ ToolTip = 'Specifies the number of transfer orders that are returns from subcontractors.';
+ }
+ }
+ cuegroup(SubcontractingActionsCuegroup)
+ {
+ Caption = 'Subcontracting - Operations';
+
+ actions
+ {
+ action("Subc. Edit Subcontracting Worksheet")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Edit Subcontracting Worksheet';
+ RunObject = Page "Subc. Subcontracting Worksheet";
+ ToolTip = 'Plan outsourcing of operation on released production orders.';
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Setup/SubcApplicationAreaMgmt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Setup/SubcApplicationAreaMgmt.Codeunit.al
new file mode 100644
index 0000000000..7b30810729
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Setup/SubcApplicationAreaMgmt.Codeunit.al
@@ -0,0 +1,49 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+#if not CLEAN28
+using Microsoft.Manufacturing.Setup;
+#endif
+using System.Environment.Configuration;
+
+codeunit 99001571 "Subc. Application Area Mgmt."
+{
+ Access = Internal;
+
+ internal procedure IsSubcontractingApplicationAreaEnabled(): Boolean
+ var
+ ApplicationAreaSetup: Record "Application Area Setup";
+ ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
+ begin
+ if ApplicationAreaMgmtFacade.GetApplicationAreaSetupRecFromCompany(ApplicationAreaSetup, CompanyName()) then
+ exit(ApplicationAreaSetup.Subcontracting);
+ end;
+
+ internal procedure RefreshExperienceTierCurrentCompany()
+ var
+ ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
+ begin
+ ApplicationAreaMgmtFacade.RefreshExperienceTierCurrentCompany();
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Application Area Mgmt. Facade", 'OnGetPremiumExperienceAppAreas', '', false, true)]
+ local procedure HandleOnGetPremiumExperienceAppAreas(var TempApplicationAreaSetup: Record "Application Area Setup" temporary);
+#if not CLEAN28
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+#endif
+ begin
+#if not CLEAN28
+ if ManufacturingSetup.Get() then
+#pragma warning disable AL0432
+ TempApplicationAreaSetup.Subcontracting := not ManufacturingSetup."Legacy Subcontracting";
+#pragma warning restore AL0432
+#else
+ TempApplicationAreaSetup.Subcontracting := true;
+#endif
+
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Setup/SubcApplicationAreaSetup.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Setup/SubcApplicationAreaSetup.TableExt.al
new file mode 100644
index 0000000000..6551dec314
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Setup/SubcApplicationAreaSetup.TableExt.al
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------------
+// 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 System.Environment.Configuration;
+
+tableextension 99001571 "Subc. Application Area Setup" extends "Application Area Setup"
+{
+ fields
+ {
+ field(99001500; Subcontracting; Boolean)
+ {
+ Caption = 'Subcontracting';
+ DataClassification = CustomerContent;
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Setup/SubcManufacturingSetup.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Setup/SubcManufacturingSetup.PageExt.al
new file mode 100644
index 0000000000..8e5e436d0a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Setup/SubcManufacturingSetup.PageExt.al
@@ -0,0 +1,51 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Setup;
+
+pageextension 99001542 "Subc. Manufacturing Setup" extends "Manufacturing Setup"
+{
+ layout
+ {
+ addlast(Content)
+ {
+ group(SubcontractingSetup)
+ {
+ Caption = 'Subcontracting';
+
+ group(SubcGeneral)
+ {
+ Caption = 'General';
+ field("Create Prod. Order Info Line"; Rec."Create Prod. Order Info Line")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies whether an additional Information Line of the Production Order Line will be created in a Subcontracting Purchase Order.';
+ }
+ field("Subc. Comp. Transfer Lead Time"; Rec."Subc. Comp. Transfer Lead Time")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the lead time for transferring components to the subcontractor. This time is subtracted from the production component due date to calculate the transfer order receipt date.';
+ }
+ field("Subcontracting Template Name"; Rec."Subcontracting Template Name")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the name of the subcontracting journal template to be used for the direct creation of subcontracting orders from a released routing.';
+ }
+ field("Subcontracting Batch Name"; Rec."Subcontracting Batch Name")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the name of the subcontracting journal batch to be used for the direct creation of subcontracting orders from a released routing.';
+ }
+ field("Component Direct Unit Cost"; Rec."Component Direct Unit Cost")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies which Direct Unit Cost of a Prod. Order Component is to be used in the subcontracting purchase order. Standard: Standard pricing is used when procuring the component. Prod. Order Component: The calculated Direct Unit Cost of the Prod. Order Component Line is transferred to the subcontracting purchase order.';
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Setup/SubcManufacturingSetup.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Setup/SubcManufacturingSetup.TableExt.al
new file mode 100644
index 0000000000..ce3a1e14a0
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Setup/SubcManufacturingSetup.TableExt.al
@@ -0,0 +1,89 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Company;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Setup;
+
+tableextension 99001501 "Subc. Manufacturing Setup" extends "Manufacturing Setup"
+{
+ fields
+ {
+ field(99001500; "Create Prod. Order Info Line"; Boolean)
+ {
+ Caption = 'Create Prod. Order Info Line';
+ DataClassification = CustomerContent;
+ }
+ field(99001501; "Subcontracting Template Name"; Code[10])
+ {
+ Caption = 'Subcontracting Worksheet Template Name';
+ DataClassification = CustomerContent;
+#pragma warning disable AL0432
+#pragma warning disable AL0520
+ TableRelation = "Req. Wksh. Template" where(Type = const(Subcontracting));
+#pragma warning restore AL0432
+#pragma warning restore AL0520
+ }
+ field(99001502; "Subcontracting Batch Name"; Code[10])
+ {
+ Caption = 'Subcontracting Worksheet Batch Name';
+ DataClassification = CustomerContent;
+#pragma warning disable AL0432
+#pragma warning disable AL0520
+ TableRelation = "Requisition Wksh. Name".Name where("Template Type" = const(Subcontracting),
+ "Worksheet Template Name" = field("Subcontracting Template Name"));
+#pragma warning restore AL0432
+#pragma warning restore AL0520
+ }
+ field(99001504; "Component Direct Unit Cost"; Option)
+ {
+ Caption = 'Component Direct Unit Cost';
+ DataClassification = CustomerContent;
+ OptionCaption = 'Standard,Prod. Order Component';
+ OptionMembers = Standard,"Prod. Order Component";
+ }
+ field(99001505; "Subc. Comp. Transfer Lead Time"; DateFormula)
+ {
+ Caption = 'Subcontracting Component Transfer Lead Time';
+ DataClassification = CustomerContent;
+ }
+ field(99001509; "Subc. Default Comp. Location"; Enum "Components at Location")
+ {
+ Caption = 'Default Component Location Source';
+ DataClassification = CustomerContent;
+
+ trigger OnValidate()
+ var
+ CompanyInformation: Record "Company Information";
+ ManufacturingSetup: Record "Manufacturing Setup";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ case "Subc. Default Comp. Location" of
+ "Subc. Default Comp. Location"::Company:
+ begin
+ CompanyInformation.Get();
+ CompanyInformation.TestField("Location Code");
+ end;
+ "Subc. Default Comp. Location"::Manufacturing:
+ begin
+ ManufacturingSetup.Get();
+ ManufacturingSetup.TestField("Components at Location");
+ end;
+ end;
+ end;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al
new file mode 100644
index 0000000000..9d4141abb7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcCreateTransfOrder.Report.al
@@ -0,0 +1,601 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.UOM;
+using Microsoft.Inventory.Costing;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+
+report 99001501 "Subc. Create Transf. Order"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Create Subcontracting Transfer Order';
+ ProcessingOnly = true;
+ dataset
+ {
+ dataitem("Purchase Header"; "Purchase Header")
+ {
+ DataItemTableView = sorting("Document Type", "No.") order(ascending);
+ dataitem("Purchase Line"; "Purchase Line")
+ {
+ DataItemLink = "Document No." = field("No.");
+ DataItemTableView = sorting("Document Type", "Document No.", "Line No.") order(ascending) where("Prod. Order No." = filter(<> ''), "Operation No." = filter(<> ''));
+ trigger OnAfterGetRecord()
+ begin
+ HandleComponentsForPurchLine("Purchase Line", true);
+ HandleWIPTransferForPurchLine("Purchase Line", true);
+ end;
+ }
+ trigger OnAfterGetRecord()
+ begin
+ "Purchase Header".CalcFields("Subc. Order");
+ if not "Subc. Order" then
+ Error(OrderNoIsNotSubcontractorErr, PurchOrderNo);
+
+ if not CheckTransferCreated() then
+ Error(NothingToCreateErr);
+
+ Vendor.Get("Purchase Header"."Buy-from Vendor No.");
+ end;
+
+ trigger OnPostDataItem()
+ begin
+ ShowDocument();
+ end;
+
+ trigger OnPreDataItem()
+ begin
+ PurchOrderNo := CopyStr("Purchase Header".GetFilter("No."), 1, MaxStrLen(PurchOrderNo));
+ if PurchOrderNo = '' then
+ Error(WarningToSpecifyPurchOrderErr);
+ end;
+ }
+ }
+#if not CLEAN28
+ trigger OnInitReport()
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ begin
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ CurrReport.Quit();
+ end;
+#endif
+
+ var
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ PurchOrderNo: Code[20];
+ LineNo: Integer;
+ ExcessReservationsErr: Label 'The transfer quantity (%1) is less than the reserved quantity (%2) on the production order component for item %3. Cancel existing reservations on the component before creating a partial transfer.', Comment = '%1=Transfer Quantity, %2=Reserved Quantity, %3=Item No.';
+ NothingToCreateErr: Label 'Nothing to create. No components or WIP to transfer for the specified subcontracting order.';
+ OrderNoDoesNotExistInProdOrderErr: Label 'Operation %1 in the subcontracting order %2 does not exist in the routing %3 of the production order %4.', Comment = '%1=Operation No., %2=Purchase Order No., %3=Routing No., %4=Production Order No.';
+ OrderNoIsNotSubcontractorErr: Label 'Order %1 is not a Subcontractor work.', Comment = '%1=Purchase Order No.';
+ WarningToSpecifyPurchOrderErr: Label 'Warning. Specify a Purchase Order No. for the Subcontractor work.';
+
+ local procedure InsertTransferHeader(TransferFromLocation: Code[10])
+ var
+ TransferRoute: Record "Transfer Route";
+ TransferToLocationCode: Code[10];
+ begin
+ GetTransferToLocationCode(TransferToLocationCode);
+
+ TransferHeader.Reset();
+ TransferHeader.SetRange("Source Subtype", TransferHeader."Source Subtype"::"2");
+ TransferHeader.SetRange("Source ID", "Purchase Header"."Buy-from Vendor No.");
+ TransferHeader.SetRange(Status, TransferHeader.Status::Open);
+ TransferHeader.SetRange("Completely Shipped", false);
+ TransferHeader.SetRange("Transfer-from Code", TransferFromLocation);
+ TransferHeader.SetRange("Transfer-to Code", TransferToLocationCode);
+ TransferHeader.SetRange("Subc. Return Order", false);
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", "Purchase Header"."No.");
+ if not TransferHeader.FindFirst() then begin
+ TransferHeader.Init();
+ TransferHeader."No." := '';
+ TransferHeader.Insert(true);
+ TransferHeader.Validate("Transfer-from Code", TransferFromLocation);
+ TransferHeader.Validate("Transfer-to Code", TransferToLocationCode);
+ if not TransferRoute.Get(TransferFromLocation, TransferToLocationCode) or (TransferRoute."In-Transit Code" = '') then
+ TransferHeader.Validate("Direct Transfer", true);
+
+ TransferHeader."Subc. Source Type" := TransferHeader."Subc. Source Type"::Subcontracting;
+ TransferHeader."Source Subtype" := TransferHeader."Source Subtype"::"2";
+ TransferHeader."Source ID" := "Purchase Header"."Buy-from Vendor No.";
+ TransferHeader."Subcontr. Purch. Order No." := "Purchase Header"."No.";
+ TransferHeader."Subcontr. PO Line No." := "Purchase Line"."Line No.";
+
+ TransferHeader."Transfer-to Name" := Vendor.Name;
+ TransferHeader."Transfer-to Name 2" := Vendor."Name 2";
+ TransferHeader."Transfer-to Address" := Vendor.Address;
+ TransferHeader."Transfer-to Address 2" := Vendor."Address 2";
+ TransferHeader."Transfer-to Post Code" := Vendor."Post Code";
+ TransferHeader."Transfer-to City" := Vendor.City;
+ TransferHeader."Transfer-to County" := Vendor.County;
+ TransferHeader."Trsf.-from Country/Region Code" := Vendor."Country/Region Code";
+
+ TransferHeader.Modify();
+ LineNo := 0;
+ end else begin
+ TransferLine.SetRange("Document No.", TransferHeader."No.");
+ if TransferLine.FindLast() then
+ LineNo := TransferLine."Line No."
+ else
+ LineNo := 0;
+ end;
+ end;
+
+ local procedure CheckTransferCreated(): Boolean
+ var
+ PurchaseLine: Record "Purchase Line";
+ begin
+ PurchaseLine.SetCurrentKey("Document Type", Type, "Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchaseLine.SetRange("Document No.", PurchOrderNo);
+ PurchaseLine.SetFilter("Prod. Order No.", '<>''''');
+ PurchaseLine.SetFilter("Prod. Order Line No.", '<>0');
+ PurchaseLine.SetFilter("Operation No.", '<>0');
+ if PurchaseLine.FindSet() then
+ repeat
+ if HandleComponentsForPurchLine(PurchaseLine, false) then
+ exit(true);
+ if HandleWIPTransferForPurchLine(PurchaseLine, false) then
+ exit(true);
+ until PurchaseLine.Next() = 0;
+
+ exit(false);
+ end;
+
+ local procedure HandleComponentsForPurchLine(PurchaseLine: Record "Purchase Line"; InsertLine: Boolean): Boolean
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ MfgCostCalculationMgt: Codeunit "Mfg. Cost Calculation Mgt.";
+ SubcProdOrdCompRes: Codeunit "Subc. Prod. Ord. Comp. Res.";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ UnitofMeasureManagement: Codeunit "Unit of Measure Management";
+ TransferFromLocationCode: Code[10];
+ QtyPerUom: Decimal;
+ QtyToPost: Decimal;
+ begin
+ if not ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.",
+ PurchaseLine."Routing Reference No.", PurchaseLine."Routing No.", PurchaseLine."Operation No.")
+ then
+ Error(OrderNoDoesNotExistInProdOrderErr, PurchaseLine."Operation No.", PurchOrderNo, PurchaseLine."Routing No.", PurchaseLine."Prod. Order No.");
+
+ Item.SetLoadFields("Base Unit of Measure", "Rounding Precision");
+ Item.Get(PurchaseLine."No.");
+ QtyPerUom := UnitofMeasureManagement.GetQtyPerUnitOfMeasure(Item, PurchaseLine."Unit of Measure Code");
+
+ 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("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ ProdOrderComponent.SetRange("Subc. Purchase Order Filter", PurchaseLine."Document No.");
+ ProdOrderComponent.SetRange("Component Supply Method", ProdOrderComponent."Component Supply Method"::"Transfer to Vendor");
+ if ProdOrderComponent.FindSet() then
+ repeat
+ Item.SetLoadFields("Rounding Precision", "Order Tracking Policy");
+ Item.Get(ProdOrderComponent."Item No.");
+ QtyToPost := MfgCostCalculationMgt.CalcActNeededQtyBase(ProdOrderLine, ProdOrderComponent, Round(PurchaseLine.Quantity * QtyPerUom, UnitofMeasureManagement.QtyRndPrecision()));
+ ProdOrderComponent.CalcFields("Subc. Qty.on TransOrder (Base)", "Subc. Qty. in Transit (Base)", "Subc. Qty. transf. to Subcontr");
+ if QtyToPost > (ProdOrderComponent."Subc. Qty.on TransOrder (Base)" +
+ ProdOrderComponent."Subc. Qty. in Transit (Base)" +
+ Abs(ProdOrderComponent."Subc. Qty. transf. to Subcontr"))
+ then
+ if InsertLine then begin
+ TransferFromLocationCode := GetTransferFromLocationForComponent(ProdOrderComponent);
+ InsertTransferHeader(TransferFromLocationCode);
+
+ LineNo := LineNo + 10000;
+
+ TransferLine.Init();
+ TransferLine."Document No." := TransferHeader."No.";
+ TransferLine."Line No." := LineNo;
+
+ TransferLine.Insert();
+
+ TransferLine.Validate("Item No.", ProdOrderComponent."Item No.");
+ TransferLine.Validate("Variant Code", ProdOrderComponent."Variant Code");
+
+ TransferLine."Unit of Measure Code" := ProdOrderComponent."Unit of Measure Code";
+ TransferLine."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+
+ QtyToPost := QtyToPost -
+ (ProdOrderComponent."Subc. Qty.on TransOrder (Base)" +
+ ProdOrderComponent."Subc. Qty. in Transit (Base)" +
+ Abs(ProdOrderComponent."Subc. Qty. transf. to Subcontr"));
+
+ TransferLine.Validate(Quantity, Round(QtyToPost / ProdOrderComponent."Qty. per Unit of Measure", Item."Rounding Precision", '>'));
+
+ if ProdOrderComponent."Due Date" <> 0D then
+ TransferLine.Validate("Receipt Date", SubcTransferManagement.CalcReceiptDateFromProdCompDueDateWithCompTransferLeadTime(ProdOrderComponent));
+
+ TransferLine."Subc. Purch. Order No." := PurchaseLine."Document No.";
+ TransferLine."Subc. Purch. Order Line No." := PurchaseLine."Line No.";
+ TransferLine."Subc. Prod. Order No." := PurchaseLine."Prod. Order No.";
+ TransferLine."Subc. Prod. Order Line No." := PurchaseLine."Prod. Order Line No.";
+ TransferLine."Subc. Prod. Ord. Comp Line No." := ProdOrderComponent."Line No.";
+ TransferLine."Subc. Routing No." := PurchaseLine."Routing No.";
+ TransferLine."Subc. Routing Reference No." := PurchaseLine."Routing Reference No.";
+ TransferLine."Subc. Work Center No." := PurchaseLine."Work Center No.";
+ TransferLine."Subc. Operation No." := PurchaseLine."Operation No.";
+ TransferLine.Modify();
+
+ if ProdOrderComponent."Subc. Original Location Code" = '' then
+ ProdOrderComponent."Subc. Original Location Code" := ProdOrderComponent."Location Code";
+ if ProdOrderComponent."Subc. Orig. Bin Code" = '' then
+ ProdOrderComponent."Subc. Orig. Bin Code" := ProdOrderComponent."Bin Code";
+
+ if SubcTransferManagement.ComponentHasExcessReservations(ProdOrderComponent, TransferLine."Quantity (Base)") then
+ 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 Item."Order Tracking Policy" = Item."Order Tracking Policy"::None then
+ ProdOrderComponent.Validate("Location Code", TransferHeader."Transfer-to Code")
+ else begin
+ BindSubscription(SubcProdOrdCompRes);
+ ProdOrderComponent.Validate("Location Code", TransferHeader."Transfer-to Code");
+ UnbindSubscription(SubcProdOrdCompRes);
+ end;
+ ProdOrderComponent.GetDefaultBin();
+ end;
+ ProdOrderComponent.Modify();
+
+ SubcTransferManagement.CreateReservEntryForTransferReceiptToProdOrderComp(TransferLine, ProdOrderComponent);
+ end else
+ exit(true);
+ until ProdOrderComponent.Next() = 0;
+
+ exit(false);
+ end;
+
+ local procedure ShowDocument()
+ var
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ begin
+ Commit(); // Used for following call of Transfer Pages
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder("Purchase Line", true, false);
+ end;
+
+ local procedure GetTransferFromLocationForComponent(ProdOrderComponent: Record "Prod. Order Component"): Code[10]
+ var
+ ResultLocationCode: Code[10];
+ SubcontrLocationCode: Code[10];
+ begin
+ SubcontrLocationCode := "Purchase Header"."Subc. Location Code";
+ if SubcontrLocationCode = '' then
+ SubcontrLocationCode := Vendor."Subc. Location Code";
+
+ if (ProdOrderComponent."Location Code" = SubcontrLocationCode) and
+ (ProdOrderComponent."Subc. Original Location Code" <> '')
+ then
+ ResultLocationCode := ProdOrderComponent."Subc. Original Location Code"
+ else
+ ResultLocationCode := ProdOrderComponent."Location Code";
+ exit(ResultLocationCode);
+ end;
+
+ local procedure GetTransferToLocationCode(var TransferToLocationCode: Code[10])
+ begin
+ GetTransferToLocationCodeForPurchaseHeader("Purchase Header", Vendor, TransferToLocationCode);
+ if TransferToLocationCode = '' then
+ Vendor.TestField("Subc. Location Code");
+ end;
+
+ local procedure GetTransferToLocationCodeForPurchaseHeader(PurchaseHeader: Record "Purchase Header"; VendorFromPurchaseHeader: Record Vendor; var TransferToLocationCode: Code[10])
+ begin
+ TransferToLocationCode := PurchaseHeader."Subc. Location Code";
+ if TransferToLocationCode = '' then
+ TransferToLocationCode := VendorFromPurchaseHeader."Subc. Location Code";
+ end;
+
+ local procedure HandleWIPTransferForPurchLine(PurchaseLine: Record "Purchase Line"; InsertLine: Boolean): Boolean
+ var
+ Item: Record Item;
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ UOMManagement: Codeunit "Unit of Measure Management";
+ TransferFromLoc: Code[10];
+ TransferToLocCode: Code[10];
+ WIPPreviousOperationNo: Code[10];
+ PostedWIPQtyBase: Decimal;
+ PurchLineQtyBase: Decimal;
+ QtyPerPurchUom: Decimal;
+ WIPQtyBase: Decimal;
+ WIPQtyInUOM: Decimal;
+ WIPPreviousOperationNoDict: Dictionary of [Code[10], Code[10]];
+ WIPSourceQtyDict: Dictionary of [Code[10], Decimal];
+ WIPSourceLocationList: List of [Code[10]];
+ begin
+ if not ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Routing Reference No.", PurchaseLine."Routing No.", PurchaseLine."Operation No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine."Transfer WIP Item" then
+ exit(false);
+
+ if not CheckCreateWIPTransfer(PurchaseLine) then
+ exit(false);
+
+ PurchLineQtyBase := CalcPurchLineQtyBase(PurchaseLine, ProdOrderLine);
+
+ GetWIPTransferFromLocations(ProdOrderLine, ProdOrderRoutingLine, WIPSourceLocationList, WIPSourceQtyDict, WIPPreviousOperationNoDict, PurchLineQtyBase);
+
+ if WIPSourceLocationList.Count() = 0 then
+ exit(false);
+
+ if not InsertLine then
+ exit(true);
+
+ if WIPSourceLocationList.Count() = 1 then begin
+ GetTransferToLocationCodeForPurchaseHeader("Purchase Header", Vendor, TransferToLocCode);
+ PostedWIPQtyBase := GetWIPQtyBase(PurchaseLine, TransferToLocCode);
+ end;
+
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get(ProdOrderLine."Item No.");
+ QtyPerPurchUom := UOMManagement.GetQtyPerUnitOfMeasure(Item, PurchaseLine."Unit of Measure Code");
+
+ foreach TransferFromLoc in WIPSourceLocationList do begin
+ WIPQtyBase := WIPSourceQtyDict.Get(TransferFromLoc);
+ WIPPreviousOperationNoDict.Get(TransferFromLoc, WIPPreviousOperationNo);
+ if WIPSourceLocationList.Count() = 1 then
+ if WIPQtyBase > PurchLineQtyBase - PostedWIPQtyBase then
+ WIPQtyBase := PurchLineQtyBase - PostedWIPQtyBase;
+ if QtyPerPurchUom <> 0 then
+ WIPQtyInUOM := Round(WIPQtyBase / QtyPerPurchUom, UOMManagement.QtyRndPrecision())
+ else
+ WIPQtyInUOM := Round(WIPQtyBase, UOMManagement.QtyRndPrecision());
+ if WIPQtyInUOM > 0 then begin
+ InsertTransferHeader(TransferFromLoc);
+ InsertWIPTransferLine(PurchaseLine, ProdOrderLine, ProdOrderRoutingLine, WIPQtyInUOM, WIPPreviousOperationNo);
+ end;
+ end;
+
+ exit(false);
+ end;
+
+ local procedure InsertWIPTransferLine(PurchaseLine: Record "Purchase Line"; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"; WIPQty: Decimal; WIPPreviousOperationNo: Code[10])
+ begin
+ LineNo := LineNo + 10000;
+
+ TransferLine.Init();
+ TransferLine."Document No." := TransferHeader."No.";
+ TransferLine."Line No." := LineNo;
+ TransferLine.Insert();
+
+ TransferLine.Validate("Item No.", ProdOrderLine."Item No.");
+ if ProdOrderLine."Variant Code" <> '' then
+ TransferLine.Validate("Variant Code", ProdOrderLine."Variant Code");
+ TransferLine.Validate("Unit of Measure Code", PurchaseLine."Unit of Measure Code");
+ TransferLine.Validate("Transfer WIP Item", true);
+ TransferLine.Validate(Quantity, WIPQty);
+
+ if ProdOrderRoutingLine."Transfer Description" <> '' then
+ TransferLine.Description := ProdOrderRoutingLine."Transfer Description";
+
+ if ProdOrderRoutingLine."Transfer Description 2" <> '' then
+ TransferLine."Description 2" := ProdOrderRoutingLine."Transfer Description 2";
+
+ TransferLine."Subc. Purch. Order No." := PurchaseLine."Document No.";
+ TransferLine."Subc. Purch. Order Line No." := PurchaseLine."Line No.";
+ TransferLine."Subc. Prod. Order No." := ProdOrderLine."Prod. Order No.";
+ TransferLine."Subc. Prod. Order Line No." := ProdOrderLine."Line No.";
+ TransferLine."Subc. Routing No." := ProdOrderRoutingLine."Routing No.";
+ TransferLine."Subc. Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ TransferLine."Subc. Work Center No." := ProdOrderRoutingLine."Work Center No.";
+ TransferLine."Subc. Operation No." := ProdOrderRoutingLine."Operation No.";
+ TransferLine."Prev. Operation No." := WIPPreviousOperationNo;
+
+ TransferLine.Modify();
+ end;
+
+ local procedure GetWIPTransferFromLocations(ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var WIPSourceLocationList: List of [Code[10]]; var WIPSourceQtyDict: Dictionary of [Code[10], Decimal]; var WIPPreviousOperationNoDict: Dictionary of [Code[10], Code[10]]; PurchLineQtyBase: Decimal)
+ var
+ PrevProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ LocCode: Code[10];
+ WIPQtyBase: Decimal;
+ IsSerial, TransferWIPItem, FoundSubcontractingPrevOp : Boolean;
+ WIPItemTransferDifferentErr: Label 'Field ''''%1'''' must have the same value for all previous operations of the routing.', Comment = '%1=Transfer WIP Item';
+ begin
+ // No previous operation: initial transfer directly from Prod. Order Line location
+ if ProdOrderRoutingLine."Previous Operation No." = '' then begin
+ LocCode := ProdOrderLine."Location Code";
+ if LocCode <> '' then begin
+ WIPSourceLocationList.Add(LocCode);
+ WIPSourceQtyDict.Add(LocCode, PurchLineQtyBase);
+ WIPPreviousOperationNoDict.Add(LocCode, '');
+ end;
+ exit;
+ end;
+
+ IsSerial := ProdOrderRoutingLine.IsSerial();
+
+ PrevProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ PrevProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ PrevProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ PrevProdOrderRoutingLine.SetFilter("Operation No.", ProdOrderRoutingLine."Previous Operation No.");
+ PrevProdOrderRoutingLine.SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ PrevProdOrderRoutingLine.SetLoadFields("Operation No.", "Transfer WIP Item");
+ PrevProdOrderRoutingLine.SetAutoCalcFields(Subcontracting);
+ if PrevProdOrderRoutingLine.FindSet() then
+ repeat
+ if not FoundSubcontractingPrevOp then begin
+ TransferWIPItem := PrevProdOrderRoutingLine."Transfer WIP Item";
+ FoundSubcontractingPrevOp := true;
+ end else
+ if TransferWIPItem <> PrevProdOrderRoutingLine."Transfer WIP Item" then
+ Error(WIPItemTransferDifferentErr, PrevProdOrderRoutingLine.FieldCaption("Transfer WIP Item"));
+
+ GetWIPLocationAndQtyForPreviousOp(
+ ProdOrderLine, PrevProdOrderRoutingLine, IsSerial, PurchLineQtyBase, LocCode, WIPQtyBase);
+
+ if (LocCode <> '') and (WIPQtyBase > 0) and (not WIPSourceQtyDict.ContainsKey(LocCode)) then begin
+ WIPSourceLocationList.Add(LocCode);
+ WIPSourceQtyDict.Add(LocCode, WIPQtyBase);
+ WIPPreviousOperationNoDict.Add(LocCode, PrevProdOrderRoutingLine."Operation No.");
+ end;
+ until PrevProdOrderRoutingLine.Next() = 0;
+
+ if WIPSourceLocationList.Count() = 0 then begin
+ LocCode := ProdOrderLine."Location Code";
+ if LocCode <> '' then begin
+ WIPSourceLocationList.Add(LocCode);
+ WIPSourceQtyDict.Add(LocCode, PurchLineQtyBase);
+ WIPPreviousOperationNoDict.Add(LocCode, '');
+ end;
+ end;
+ end;
+
+ local procedure GetWIPLocationAndQtyForPreviousOp(ProdOrderLine: Record "Prod. Order Line"; PrevProdOrderRoutingLine: Record "Prod. Order Routing Line"; IsSerial: Boolean; PurchLineQtyBase: Decimal; var LocationCode: Code[10]; var WIPQtyBase: Decimal)
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ PrevVendor: Record Vendor;
+ PrevWorkCenter: Record "Work Center";
+ begin
+ LocationCode := ProdOrderLine."Location Code";
+ WIPQtyBase := PurchLineQtyBase;
+
+ if PrevProdOrderRoutingLine."Transfer WIP Item" then begin
+ // Previous op has a subcontracting WIP transfer
+ PrevWorkCenter.SetLoadFields("Subcontractor No.");
+ if PrevWorkCenter.Get(PrevProdOrderRoutingLine."Work Center No.") then
+ if PrevWorkCenter."Subcontractor No." <> '' then begin
+ PrevVendor.SetLoadFields("Subc. Location Code");
+ if PrevVendor.Get(PrevWorkCenter."Subcontractor No.") then
+ if PrevVendor."Subc. Location Code" <> '' then
+ LocationCode := PrevVendor."Subc. Location Code";
+ end;
+
+ if LocationCode <> '' then begin
+ WIPLedgerEntry.SetRange("Prod. Order Status", "Production Order Status"::Released);
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderLine."Prod. Order No.");
+ WIPLedgerEntry.SetRange("Prod. Order Line No.", ProdOrderLine."Line No.");
+ WIPLedgerEntry.SetRange("Routing No.", PrevProdOrderRoutingLine."Routing No.");
+ WIPLedgerEntry.SetRange("Routing Reference No.", PrevProdOrderRoutingLine."Routing Reference No.");
+ WIPLedgerEntry.SetRange("Operation No.", PrevProdOrderRoutingLine."Operation No.");
+ WIPLedgerEntry.SetRange("Location Code", LocationCode);
+ WIPLedgerEntry.SetRange("In Transit", false);
+ WIPLedgerEntry.CalcSums("Quantity (Base)");
+ end;
+ end;
+
+ // Parallel routings always use Prod. Order Line quantity as preset
+ if IsSerial and (WIPLedgerEntry."Quantity (Base)" <> 0) then
+ WIPQtyBase := WIPLedgerEntry."Quantity (Base)";
+ end;
+
+ local procedure CheckCreateWIPTransfer(PurchaseLine: Record "Purchase Line"): Boolean
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseHeader: Record "Purchase Header";
+ VendorFromPurchOrder: Record Vendor;
+ TransferLineToCheck: Record "Transfer Line";
+ LocCode: Code[10];
+ PurchLineQtyBase: Decimal;
+ TransferToLocationCode: Code[10];
+ ExpectedQtyBase: Decimal;
+ PostedWIPQtyBase: Decimal;
+ WIPPreviousOperationNoDict: Dictionary of [Code[10], Code[10]];
+ WIPSourceQtyDict: Dictionary of [Code[10], Decimal];
+ WIPSourceLocationList: List of [Code[10]];
+ begin
+ TransferLineToCheck.SetCurrentKey("Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Routing Reference No.", "Subc. Routing No.", "Subc. Operation No.");
+ TransferLineToCheck.SetRange("Subc. Purch. Order No.", PurchaseLine."Document No.");
+ TransferLineToCheck.SetRange("Subc. Prod. Order No.", PurchaseLine."Prod. Order No.");
+ TransferLineToCheck.SetRange("Subc. Prod. Order Line No.", PurchaseLine."Prod. Order Line No.");
+ TransferLineToCheck.SetRange("Subc. Operation No.", PurchaseLine."Operation No.");
+ TransferLineToCheck.SetRange("Derived From Line No.", 0);
+ TransferLineToCheck.SetRange("Transfer WIP Item", true);
+ if not TransferLineToCheck.IsEmpty() then
+ exit(false);
+
+ if not ProdOrderLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Prod. Order Line No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine.Get("Production Order Status"::Released, PurchaseLine."Prod. Order No.", PurchaseLine."Routing Reference No.", PurchaseLine."Routing No.", PurchaseLine."Operation No.") then
+ exit(false);
+
+ if not ProdOrderRoutingLine."Transfer WIP Item" then
+ exit(false);
+
+ PurchLineQtyBase := CalcPurchLineQtyBase(PurchaseLine, ProdOrderLine);
+
+ GetWIPTransferFromLocations(ProdOrderLine, ProdOrderRoutingLine, WIPSourceLocationList, WIPSourceQtyDict, WIPPreviousOperationNoDict, PurchLineQtyBase);
+
+ ExpectedQtyBase := 0;
+ foreach LocCode in WIPSourceLocationList do
+ ExpectedQtyBase += WIPSourceQtyDict.Get(LocCode);
+
+ if ExpectedQtyBase = 0 then
+ exit(false);
+
+ if not PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.") then
+ exit(false);
+
+ if not VendorFromPurchOrder.Get(PurchaseHeader."Buy-from Vendor No.") then
+ exit(false);
+
+ GetTransferToLocationCodeForPurchaseHeader(PurchaseHeader, VendorFromPurchOrder, TransferToLocationCode);
+
+ if TransferToLocationCode = '' then
+ exit(false);
+
+ PostedWIPQtyBase := GetWIPQtyBase(PurchaseLine, TransferToLocationCode);
+
+ if WIPPreviousOperationNoDict.Keys().Count() > 1 then
+ foreach LocCode in WIPPreviousOperationNoDict.Keys() do
+ if LocCode <> '' then
+ exit(ExpectedQtyBase > 0);
+
+ exit(PostedWIPQtyBase < ExpectedQtyBase);
+ end;
+
+ local procedure CalcPurchLineQtyBase(PurchaseLine: Record "Purchase Line"; ProdOrderLine: Record "Prod. Order Line"): Decimal
+ var
+ Item: Record Item;
+ UOMManagement: Codeunit "Unit of Measure Management";
+ QtyPerUom: Decimal;
+ begin
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get(ProdOrderLine."Item No.");
+ QtyPerUom := UOMManagement.GetQtyPerUnitOfMeasure(Item, PurchaseLine."Unit of Measure Code");
+ exit(UOMManagement.CalcBaseQty(PurchaseLine.Quantity, QtyPerUom));
+ end;
+
+ local procedure GetWIPQtyBase(PurchaseLine: Record "Purchase Line"; LocationCode: Code[10]): Decimal
+ var
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order Status", "Production Order Status"::Released);
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No.");
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order Line No.", PurchaseLine."Prod. Order Line No.");
+ SubcontractorWIPLedgerEntry.SetRange("Routing No.", PurchaseLine."Routing No.");
+ SubcontractorWIPLedgerEntry.SetRange("Routing Reference No.", PurchaseLine."Routing Reference No.");
+ SubcontractorWIPLedgerEntry.SetRange("Operation No.", PurchaseLine."Operation No.");
+ SubcontractorWIPLedgerEntry.SetRange("Location Code", LocationCode);
+ SubcontractorWIPLedgerEntry.SetRange("In Transit", false);
+ SubcontractorWIPLedgerEntry.CalcSums("Quantity (Base)");
+ exit(SubcontractorWIPLedgerEntry."Quantity (Base)");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransHeaderExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransHeaderExt.TableExt.al
new file mode 100644
index 0000000000..57f6a846c6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransHeaderExt.TableExt.al
@@ -0,0 +1,80 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Purchases.Vendor;
+
+tableextension 99001524 "Subc. DirectTransHeader Ext." extends "Direct Trans. Header"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subcontr. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subcontr. Purch. Order No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001531; "Subcontr. PO Line No."; Integer)
+ {
+ Caption = 'Subcontr. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001536; "Source ID"; Code[20])
+ {
+ Caption = 'Source ID';
+ DataClassification = CustomerContent;
+ trigger OnLookup()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ HandleSubcontractingSourceLookup(Rec);
+ end;
+ }
+ field(99001537; "Source Ref. No."; Integer)
+ {
+ Caption = 'Source Ref. No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001540; "Source Type"; Enum "Transfer Source Type")
+ {
+ Caption = 'Source Type';
+ DataClassification = CustomerContent;
+ }
+ field(99001541; "Return Order"; Boolean)
+ {
+ Caption = 'Return Order';
+ DataClassification = CustomerContent;
+ }
+ }
+ keys
+ {
+ key(Key99001500; "Subcontr. Purch. Order No.") { }
+ key(Key99001501; "Source ID", "Source Type") { }
+ }
+
+ local procedure HandleSubcontractingSourceLookup(var DirectTransHeader: Record "Direct Trans. Header")
+ var
+ Vendor: Record Vendor;
+ begin
+ if DirectTransHeader."Source Type" = DirectTransHeader."Source Type"::Subcontracting then begin
+ Vendor.SetRange("No.", DirectTransHeader."Source ID");
+ Page.RunModal(0, Vendor);
+ end;
+ end;
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransLineExt.TableExt.al
new file mode 100644
index 0000000000..cafad48243
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransLineExt.TableExt.al
@@ -0,0 +1,87 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+
+tableextension 99001523 "Subc. DirectTrans. Line Ext" extends "Direct Trans. Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subcontr. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subcontr. Purch. Order No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001531; "Subcontr. PO Line No."; Integer)
+ {
+ Caption = 'Subcontr. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001532; "Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001533; "Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."));
+ }
+ field(99001534; "Prod. Order Comp. Line No."; Integer)
+ {
+ Caption = 'Prod. Order Comp. Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Component"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Prod. Order Line No." = field("Prod. Order Line No."));
+ }
+ field(99001535; "Routing No."; Code[20])
+ {
+ Caption = 'Routing No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Routing Header";
+ }
+ field(99001536; "Routing Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001537; "Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Work Center";
+ }
+ field(99001538; "Operation No."; Code[10])
+ {
+ Caption = 'Operation No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Routing No."));
+ }
+ field(99001539; "Return Order"; Boolean)
+ {
+ Caption = 'Return Order';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer shipment line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransferLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransferLineExt.Codeunit.al
new file mode 100644
index 0000000000..9abee3b532
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcDirectTransferLineExt.Codeunit.al
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+codeunit 99001548 "Subc. DirectTransferLine Ext."
+{
+ [EventSubscriber(ObjectType::Table, Database::"Direct Trans. Line", OnAfterCopyFromTransferLine, '', false, false)]
+ local procedure OnAfterCopyFromTransferLine_T5745(var DirectTransLine: Record "Direct Trans. Line"; TransferLine: Record "Transfer Line")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ DirectTransLine."Subcontr. Purch. Order No." := TransferLine."Subc. Purch. Order No.";
+ DirectTransLine."Subcontr. PO Line No." := TransferLine."Subc. Purch. Order Line No.";
+ DirectTransLine."Prod. Order No." := TransferLine."Subc. Prod. Order No.";
+ DirectTransLine."Prod. Order Line No." := TransferLine."Subc. Prod. Order Line No.";
+ DirectTransLine."Prod. Order Comp. Line No." := TransferLine."Subc. Prod. Ord. Comp Line No.";
+ DirectTransLine."Routing No." := TransferLine."Subc. Routing No.";
+ DirectTransLine."Routing Reference No." := TransferLine."Subc. Routing Reference No.";
+ DirectTransLine."Work Center No." := TransferLine."Subc. Work Center No.";
+ DirectTransLine."Operation No." := TransferLine."Subc. Operation No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTrans.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTrans.PageExt.al
new file mode 100644
index 0000000000..e5feed5595
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTrans.PageExt.al
@@ -0,0 +1,59 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001531 "Subc. Pstd. Direct Trans." extends "Posted Direct Transfer"
+{
+ layout
+ {
+ addlast(General)
+ {
+ field(SourceType; Rec."Source Type")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies for which source type the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceID; Rec."Source ID")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies which source ID the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceRefNo; Rec."Source Ref. No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies a reference number for the line, which the transfer order is related to.';
+ Visible = false;
+ }
+ field("Return Order"; Rec."Return Order")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies whether the existing transfer order is a return of the subcontractor.';
+ Visible = false;
+ }
+ field("Subcontr. Purch. Order No."; Rec."Subcontr. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ Visible = false;
+ }
+ field("Subcontr. PO Line No."; Rec."Subcontr. PO Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ Visible = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTransfSub.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTransfSub.PageExt.al
new file mode 100644
index 0000000000..4dc52dcc85
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTransfSub.PageExt.al
@@ -0,0 +1,153 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001532 "Subc. PstdDirectTransfSub" extends "Posted Direct Transfer Subform"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Subcontr. Purch. Order No."; Rec."Subcontr. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ Visible = false;
+ }
+ field("Subcontr. PO Line No."; Rec."Subcontr. PO Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ Visible = false;
+ }
+ field("Prod. Order No."; Rec."Prod. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production order.';
+ Visible = false;
+ }
+ field("Prod. Order Line No."; Rec."Prod. Order Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production order line.';
+ Visible = false;
+ }
+ field("Prod. Order. Comp. Line No."; Rec."Prod. Order Comp. Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the line number of the related production order component line.';
+ Visible = false;
+ }
+ field("Routing No."; Rec."Routing No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production routing.';
+ Visible = false;
+ }
+ field("Routing Reference No."; Rec."Routing Reference No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production routing reference no.';
+ Visible = false;
+ }
+ field("WorkCenter No."; Rec."Work Center No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production work center.';
+ Visible = false;
+ }
+ field("Operation No."; Rec."Operation No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related production operation no.';
+ Visible = false;
+ }
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("&Line")
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+ action("Production Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order';
+ Image = Production;
+ ToolTip = 'View the related production order.';
+ trigger OnAction()
+ begin
+ ShowProductionOrder(Rec);
+ end;
+ }
+ action("Production Order Routing")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Routing';
+ Image = Route;
+ ToolTip = 'View the related production order routing.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderRouting(Rec);
+ end;
+ }
+ action("Production Order Components")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Components';
+ Image = Components;
+ ToolTip = 'View the related production order components.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderComponents(Rec);
+ end;
+ }
+ action("Purchase Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Purchase Order';
+ Image = Order;
+ ToolTip = 'View the related subcontracting purchase order.';
+ trigger OnAction()
+ begin
+ ShowPurchaseOrder(Rec);
+ end;
+ }
+ }
+ }
+ }
+ var
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowPurchaseOrder(RecRelatedVariant: Variant)
+ begin
+ SubcPurchFactboxMgmt.ShowPurchaseOrder(RecRelatedVariant);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTransfers.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTransfers.PageExt.al
new file mode 100644
index 0000000000..8b1b0c7e9e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdDirectTransfers.PageExt.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;
+
+using Microsoft.Inventory.Transfer;
+
+pageextension 99001554 "Subc. Pstd. Direct Transfers" extends "Posted Direct Transfers"
+{
+ views
+ {
+ addlast
+ {
+ view(SubcontractingDirectTransfers)
+ {
+ Caption = 'Subcontracting Direct Transfers';
+ Filters = where("Source Type" = const(Subcontracting));
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferRcpt.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferRcpt.PageExt.al
new file mode 100644
index 0000000000..42d33edd4c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferRcpt.PageExt.al
@@ -0,0 +1,66 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001528 "Subc. Pstd. Transfer Rcpt" extends "Posted Transfer Receipt"
+{
+ layout
+ {
+ addlast(General)
+ {
+ field("Subc. Source Type"; Rec."Subc. Source Type")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies for which source type the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceSubtype; Rec."Source Subtype")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies which source subtype the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceID; Rec."Source ID")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies which source ID the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceRefNo; Rec."Source Ref. No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies a reference number for the line, which the transfer order is related to.';
+ Visible = false;
+ }
+ field("Subc. Return Order"; Rec."Subc. Return Order")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies whether the existing transfer order is a return of the subcontractor.';
+ Visible = false;
+ }
+ field("Subcontr. Purch. Order No."; Rec."Subcontr. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ Visible = false;
+ }
+ field("Subcontr. PO Line No."; Rec."Subcontr. PO Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ Visible = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferRcpts.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferRcpts.PageExt.al
new file mode 100644
index 0000000000..644db37213
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferRcpts.PageExt.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;
+
+using Microsoft.Inventory.Transfer;
+
+pageextension 99001553 "Subc. Pstd. Transfer Rcpts." extends "Posted Transfer Receipts"
+{
+ views
+ {
+ addlast
+ {
+ view(SubcontractingReceipts)
+ {
+ Caption = 'Subcontracting Receipts';
+ Filters = where("Subc. Source Type" = const(Subcontracting));
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferShpt.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferShpt.PageExt.al
new file mode 100644
index 0000000000..aad43991c5
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferShpt.PageExt.al
@@ -0,0 +1,59 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001527 "Subc. Pstd. Transfer Shpt" extends "Posted Transfer Shipment"
+{
+ layout
+ {
+ addlast(General)
+ {
+ field("Subc. Source Type"; Rec."Subc. Source Type")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field(SourceSubtype; Rec."Source Subtype")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field(SourceID; Rec."Source ID")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field(SourceRefNo; Rec."Source Ref. No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Return Order"; Rec."Subc. Return Order")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subcontr. Purch. Order No."; Rec."Subcontr. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subcontr. PO Line No."; Rec."Subcontr. PO Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferShpts.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferShpts.PageExt.al
new file mode 100644
index 0000000000..9789bdbef1
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcPstdTransferShpts.PageExt.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;
+
+using Microsoft.Inventory.Transfer;
+
+pageextension 99001552 "Subc. Pstd. Transfer Shpts." extends "Posted Transfer Shipments"
+{
+ views
+ {
+ addlast
+ {
+ view(SubcontractingShipments)
+ {
+ Caption = 'Subcontracting Shipments';
+ Filters = where("Subc. Source Type" = const(Subcontracting));
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al
new file mode 100644
index 0000000000..46086d6166
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostRcptExt.Codeunit.al
@@ -0,0 +1,81 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Journal;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+
+codeunit 99001540 "Subc. TransOrderPostRcpt Ext"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Receipt", OnBeforePostItemJournalLine, '', false, false)]
+ local procedure OnBeforePostItemJournalLine(var ItemJournalLine: Record "Item Journal Line"; TransferLine: Record "Transfer Line"; TransferReceiptHeader: Record "Transfer Receipt Header"; TransferReceiptLine: Record "Transfer Receipt Line"; CommitIsSuppressed: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ItemJournalLine."Subc. Prod. Order No." := TransferReceiptLine."Subc. Prod. Order No.";
+ ItemJournalLine."Subc. Prod. Order Line No." := TransferReceiptLine."Subc. Prod. Order Line No.";
+ ItemJournalLine."Source No." := TransferReceiptHeader."Source ID";
+ ItemJournalLine."Source Type" := TransferReceiptHeader."Subc. Source Type";
+ ItemJournalLine."Prod. Order Comp. Line No." := TransferReceiptLine."Subc. Prod. Ord. Comp Line No.";
+ ItemJournalLine."Subc. Purch. Order No." := TransferReceiptLine."Subc. Purch. Order No.";
+ ItemJournalLine."Subc. Purch. Order Line No." := TransferReceiptLine."Subc. Purch. Order Line No.";
+ ItemJournalLine."Subc. Operation No." := TransferReceiptLine."Subc. Operation No.";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Receipt", OnBeforeInsertTransRcptLine, '', false, false)]
+ local procedure OnBeforeInsertTransRcptLine(var TransRcptLine: Record "Transfer Receipt Line"; TransLine: Record "Transfer Line"; CommitIsSuppressed: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransRcptLine."Subc. Purch. Order No." := TransLine."Subc. Purch. Order No.";
+ TransRcptLine."Subc. Purch. Order Line No." := TransLine."Subc. Purch. Order Line No.";
+ TransRcptLine."Subc. Prod. Order No." := TransLine."Subc. Prod. Order No.";
+ TransRcptLine."Subc. Prod. Order Line No." := TransLine."Subc. Prod. Order Line No.";
+ TransRcptLine."Subc. Prod. Ord. Comp Line No." := TransLine."Subc. Prod. Ord. Comp Line No.";
+ TransRcptLine."Subc. Return Order" := TransLine."Subc. Return Order";
+ TransRcptLine."Subc. Routing No." := TransLine."Subc. Routing No.";
+ TransRcptLine."Subc. Routing Reference No." := TransLine."Subc. Routing Reference No.";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Receipt", OnCheckTransLine, '', false, false)]
+ local procedure OnCheckTransLine(TransferLine: Record "Transfer Line"; TransferHeader: Record "Transfer Header"; Location: Record Location; WhseReceive: Boolean)
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (TransferLine."Subc. Prod. Order No." = '') or (TransferLine."Subc. Prod. Order Line No." = 0) or (TransferLine."Subc. Prod. Ord. Comp Line No." = 0) then
+ exit;
+
+ 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;
+
+ if Location.Code <> ProdOrderComponent."Location Code" then begin
+ ProdOrderComponent.Validate("Location Code", Location.Code);
+ ProdOrderComponent.Modify();
+ end;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostShptExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostShptExt.Codeunit.al
new file mode 100644
index 0000000000..b748a6050a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostShptExt.Codeunit.al
@@ -0,0 +1,56 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Journal;
+using Microsoft.Inventory.Transfer;
+
+codeunit 99001539 "Subc. TransOrderPostShpt Ext"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Shipment", OnAfterCreateItemJnlLine, '', false, false)]
+ local procedure OnAfterCreateItemJnlLine(var ItemJournalLine: Record "Item Journal Line"; TransferLine: Record "Transfer Line"; TransferShipmentHeader: Record "Transfer Shipment Header"; TransferShipmentLine: Record "Transfer Shipment Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ItemJournalLine."Subc. Prod. Order No." := TransferShipmentLine."Subc. Prod. Order No.";
+ ItemJournalLine."Subc. Prod. Order Line No." := TransferShipmentLine."Subc. Prod. Order Line No.";
+ ItemJournalLine."Source No." := TransferShipmentHeader."Source ID";
+ ItemJournalLine."Source Type" := TransferShipmentHeader."Subc. Source Type";
+ ItemJournalLine."Prod. Order Comp. Line No." := TransferShipmentLine."Subc. Prod. Ord. Comp Line No.";
+ ItemJournalLine."Subc. Purch. Order No." := TransferShipmentLine."Subc. Purch. Order No.";
+ ItemJournalLine."Subc. Purch. Order Line No." := TransferShipmentLine."Subc. Purch. Order Line No.";
+ ItemJournalLine."Subc. Operation No." := TransferLine."Subc. Operation No."
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Shipment", OnBeforeInsertTransShptLine, '', false, false)]
+ local procedure OnBeforeInsertTransShptLine(var TransShptLine: Record "Transfer Shipment Line"; TransLine: Record "Transfer Line"; CommitIsSuppressed: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransShptLine."Subc. Purch. Order No." := TransLine."Subc. Purch. Order No.";
+ TransShptLine."Subc. Purch. Order Line No." := TransLine."Subc. Purch. Order Line No.";
+ TransShptLine."Subc. Prod. Order No." := TransLine."Subc. Prod. Order No.";
+ TransShptLine."Subc. Prod. Order Line No." := TransLine."Subc. Prod. Order Line No.";
+ TransShptLine."Subc. Prod. Ord. Comp Line No." := TransLine."Subc. Prod. Ord. Comp Line No.";
+ TransShptLine."Subc. Return Order" := TransLine."Subc. Return Order";
+ TransShptLine."Subc. Routing No." := TransLine."Subc. Routing No.";
+ TransShptLine."Subc. Routing Reference No." := TransLine."Subc. Routing Reference No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al
new file mode 100644
index 0000000000..271ec96e0a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderPostTransExt.Codeunit.al
@@ -0,0 +1,318 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Posting;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+
+codeunit 99001547 "Subc. TransOrderPostTrans Ext"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Transfer", OnAfterCreateItemJnlLine, '', false, false)]
+ local procedure OnAfterCreateItemJnlLine(var ItemJnlLine: Record "Item Journal Line"; TransLine: Record "Transfer Line"; DirectTransHeader: Record "Direct Trans. Header"; DirectTransLine: Record "Direct Trans. Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ItemJnlLine."Subc. Prod. Order No." := DirectTransLine."Prod. Order No.";
+ ItemJnlLine."Subc. Prod. Order Line No." := DirectTransLine."Prod. Order Line No.";
+ ItemJnlLine."Prod. Order Comp. Line No." := DirectTransLine."Prod. Order Comp. Line No.";
+ ItemJnlLine."Subc. Purch. Order No." := DirectTransLine."Subcontr. Purch. Order No.";
+ ItemJnlLine."Subc. Purch. Order Line No." := DirectTransLine."Subcontr. PO Line No.";
+ ItemJnlLine."Subc. Operation No." := TransLine."Subc. Operation No."
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Transfer", OnInsertDirectTransHeaderOnBeforeGetNextNo, '', false, false)]
+ local procedure OnInsertDirectTransHeaderOnBeforeGetNextNo(var DirectTransHeader: Record "Direct Trans. Header"; TransferHeader: Record "Transfer Header")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ DirectTransHeader."Source Type" := TransferHeader."Subc. Source Type";
+ DirectTransHeader."Source ID" := TransferHeader."Source ID";
+ DirectTransHeader."Source Ref. No." := TransferHeader."Source Ref. No.";
+ DirectTransHeader."Return Order" := TransferHeader."Subc. Return Order";
+ DirectTransHeader."Subcontr. Purch. Order No." := TransferHeader."Subcontr. Purch. Order No.";
+ DirectTransHeader."Subcontr. PO Line No." := TransferHeader."Subcontr. PO Line No.";
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Transfer", OnBeforeInsertDirectTransLine, '', false, false)]
+ local procedure OnBeforeInsertDirectTransLine(TransferLine: Record "Transfer Line")
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (TransferLine."Subc. Prod. Order No." = '') or (TransferLine."Subc. Prod. Order Line No." = 0) or (TransferLine."Subc. Prod. Ord. Comp Line No." = 0) then
+ exit;
+
+ 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");
+ ProdOrderComponent.Modify();
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Receipt", OnAfterPostItemJnlLine, '', false, false)]
+ local procedure OnAfterPostItemJnlLineReceipt(ItemJnlLine: Record "Item Journal Line"; var TransLine3: Record "Transfer Line"; var TransRcptHeader2: Record "Transfer Receipt Header"; var TransRcptLine2: Record "Transfer Receipt Line"; var ItemJnlPostLine: Codeunit "Item Jnl.-Post Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not ItemJnlLine."Direct Transfer" then
+ exit;
+
+ HandleDirectTransferReservationAndTracking(TransLine3, ItemJnlPostLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Transfer", OnAfterPostItemJnlLine, '', false, false)]
+ local procedure OnAfterPostItemJnlLineDirectTransfer(var TransferLine3: Record "Transfer Line"; DirectTransHeader2: Record "Direct Trans. Header"; DirectTransLine2: Record "Direct Trans. Line"; ItemJournalLine: Record "Item Journal Line"; var ItemJnlPostLine: Codeunit "Item Jnl.-Post Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ HandleDirectTransferReservationAndTracking(TransferLine3, ItemJnlPostLine);
+ end;
+
+ local procedure HandleDirectTransferReservationAndTracking(var TransferLine: Record "Transfer Line"; var ItemJnlPostLine: Codeunit "Item Jnl.-Post Line")
+ var
+ TempItemEntryRelation: Record "Item Entry Relation" temporary;
+ begin
+ if not ItemJnlPostLine.CollectItemEntryRelation(TempItemEntryRelation) then
+ exit;
+
+ if not TempItemEntryRelation.FindSet() then
+ exit;
+
+ HandleReservationEntries(TransferLine, TempItemEntryRelation);
+
+ HandleItemTrackingSurplus(TransferLine, TempItemEntryRelation);
+ end;
+
+ local procedure HandleReservationEntries(var TransferLine: Record "Transfer Line"; var TempItemEntryRelation: Record "Item Entry Relation" temporary)
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ OldReservationEntry: Record "Reservation Entry";
+ OldReservationEntryPair: Record "Reservation Entry";
+ MatchFound: Boolean;
+ begin
+ // Find old reservations: Transfer Line (Inbound) → Prod. Order Component
+ OldReservationEntry.SetSourceFilter(Database::"Transfer Line", 1, TransferLine."Document No.", -1, true);
+ OldReservationEntry.SetRange("Source Batch Name", '');
+ OldReservationEntry.SetRange("Source Prod. Order Line", TransferLine."Derived From Line No.");
+ OldReservationEntry.SetRange("Reservation Status", OldReservationEntry."Reservation Status"::Reservation);
+
+ if not OldReservationEntry.FindSet() then
+ exit;
+
+ // Process each old reservation entry
+ repeat
+ // Get the opposite side of the reservation (should be Prod. Order Component)
+ if not OldReservationEntryPair.Get(OldReservationEntry."Entry No.", not OldReservationEntry.Positive) then
+ continue;
+
+ if OldReservationEntryPair."Source Type" <> Database::"Prod. Order Component" then
+ continue;
+
+ // Find matching Item Ledger Entry based on tracking (Serial No, Lot No)
+ MatchFound := false;
+ TempItemEntryRelation.Reset();
+ if TempItemEntryRelation.FindSet() then
+ repeat
+ if ItemLedgerEntry.Get(TempItemEntryRelation."Item Entry No.") then
+ // Check if tracking matches
+ if (ItemLedgerEntry."Serial No." = OldReservationEntry."Serial No.") and
+ (ItemLedgerEntry."Lot No." = OldReservationEntry."Lot No.") and
+ (ItemLedgerEntry."Package No." = OldReservationEntry."Package No.")
+ then begin
+ // Create new reservation: Item Ledger Entry → Prod. Order Component
+ CreateReservEntryForProdOrderComp(
+ ItemLedgerEntry,
+ OldReservationEntryPair,
+ OldReservationEntry,
+ OldReservationEntry."Reservation Status"::Reservation);
+
+ MatchFound := true;
+ end;
+ until (TempItemEntryRelation.Next() = 0) or MatchFound;
+
+ // Delete the old reservation pair only if we successfully created a new one
+ if MatchFound then begin
+ OldReservationEntry.Delete();
+ OldReservationEntryPair.Delete();
+ end;
+ until OldReservationEntry.Next() = 0;
+ end;
+
+ local procedure HandleItemTrackingSurplus(var TransferLine: Record "Transfer Line"; var TempItemEntryRelation: Record "Item Entry Relation" temporary)
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProdOrderComponent: Record "Prod. Order Component";
+ begin
+ if not FindProdOrderComponentsForTransferLine(TransferLine, ProdOrderComponent) then
+ exit;
+
+ // Process each Item Ledger Entry that was created
+ TempItemEntryRelation.Reset();
+ if not TempItemEntryRelation.FindSet() then
+ exit;
+
+ repeat
+ if ItemLedgerEntry.Get(TempItemEntryRelation."Item Entry No.") then begin
+ // Only process entries with item tracking
+ if not ItemLedgerEntry.TrackingExists() then
+ continue;
+
+ // Check if this tracking already has a reservation (was handled above)
+ if ItemLedgerEntryHasReservation(ItemLedgerEntry) then
+ continue;
+
+ // Check if this component needs this specific tracking
+ if ShouldCreateSurplusForComponent(ItemLedgerEntry, ProdOrderComponent) then
+ // Create surplus entry: Item Ledger Entry → Prod. Order Component
+ CreateSurplusEntryForProdOrderComp(ItemLedgerEntry, ProdOrderComponent);
+ end;
+ until TempItemEntryRelation.Next() = 0;
+ end;
+
+ local procedure CreateReservEntryForProdOrderComp(var ItemLedgerEntry: Record "Item Ledger Entry"; var OldReservationEntryPair: Record "Reservation Entry";
+ OldReservationEntry: Record "Reservation Entry"; ReservationStatus: Enum "Reservation Status")
+ var
+ FromTrackingSpecification: Record "Tracking Specification";
+ CreateReservEntry: Codeunit "Create Reserv. Entry";
+ begin
+ // Set up the "For" side (Item Ledger Entry)
+ CreateReservEntry.CreateReservEntryFor(
+ Database::"Item Ledger Entry", 0, '', '', 0, ItemLedgerEntry."Entry No.",
+ ItemLedgerEntry."Qty. per Unit of Measure",
+ 0, ItemLedgerEntry.Quantity,
+ OldReservationEntry);
+
+ // Set up the "From" side (Prod. Order Component)
+ FromTrackingSpecification.SetSourceFromReservEntry(OldReservationEntryPair);
+ FromTrackingSpecification."Qty. per Unit of Measure" := OldReservationEntryPair."Qty. per Unit of Measure";
+ FromTrackingSpecification.CopyTrackingFromReservEntry(OldReservationEntryPair);
+ CreateReservEntry.CreateReservEntryFrom(FromTrackingSpecification);
+
+ CreateReservEntry.SetApplyFromEntryNo(ItemLedgerEntry."Entry No.");
+
+ CreateReservEntry.CreateEntry(
+ ItemLedgerEntry."Item No.",
+ ItemLedgerEntry."Variant Code",
+ ItemLedgerEntry."Location Code",
+ '',
+ 0D,
+ 0D,
+ 0,
+ ReservationStatus);
+ end;
+
+ local procedure CreateSurplusEntryForProdOrderComp(
+ var ItemLedgerEntry: Record "Item Ledger Entry";
+ var ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ FromTrackingSpecification: Record "Tracking Specification";
+ CreateReservEntry: Codeunit "Create Reserv. Entry";
+ begin
+ // Initialize dummy reservation entry for the demand side
+ ReservationEntry.Init();
+ ReservationEntry."Source Type" := Database::"Prod. Order Component";
+ ReservationEntry."Source Subtype" := ProdOrderComponent.Status.AsInteger();
+ ReservationEntry."Source ID" := ProdOrderComponent."Prod. Order No.";
+ ReservationEntry."Source Prod. Order Line" := ProdOrderComponent."Prod. Order Line No.";
+ ReservationEntry."Source Ref. No." := ProdOrderComponent."Line No.";
+ ReservationEntry."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+ ReservationEntry.CopyTrackingFromItemLedgEntry(ItemLedgerEntry);
+ ReservationEntry."Expected Receipt Date" := ProdOrderComponent."Due Date";
+
+ CreateReservEntry.CreateReservEntryFor(
+ Database::"Prod. Order Component",
+ ProdOrderComponent.Status.AsInteger(),
+ ProdOrderComponent."Prod. Order No.",
+ '',
+ ProdOrderComponent."Prod. Order Line No.",
+ ProdOrderComponent."Line No.",
+ ProdOrderComponent."Qty. per Unit of Measure",
+ 0,
+ ItemLedgerEntry.Quantity,
+ ReservationEntry);
+
+ // Set up the "From" side (Item Ledger Entry - SUPPLY)
+ FromTrackingSpecification.InitTrackingSpecification(
+ Database::"Item Ledger Entry",
+ 0, '', '', 0,
+ ItemLedgerEntry."Entry No.",
+ ItemLedgerEntry."Variant Code",
+ ItemLedgerEntry."Location Code",
+ ItemLedgerEntry."Qty. per Unit of Measure");
+ FromTrackingSpecification.CopyTrackingFromItemLedgEntry(ItemLedgerEntry);
+ CreateReservEntry.CreateReservEntryFrom(FromTrackingSpecification);
+
+ CreateReservEntry.SetItemLedgEntryNo(ItemLedgerEntry."Entry No.");
+
+ CreateReservEntry.CreateEntry(
+ ItemLedgerEntry."Item No.",
+ ItemLedgerEntry."Variant Code",
+ ItemLedgerEntry."Location Code",
+ '',
+ ProdOrderComponent."Due Date",
+ 0D,
+ 0,
+ "Reservation Status"::Surplus);
+ end;
+
+ local procedure ItemLedgerEntryHasReservation(var ItemLedgerEntry: Record "Item Ledger Entry"): Boolean
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ ReservationEntry.SetSourceFilter(Database::"Item Ledger Entry", 0, '', ItemLedgerEntry."Entry No.", true);
+ ReservationEntry.SetRange("Reservation Status", ReservationEntry."Reservation Status"::Reservation);
+ exit(not ReservationEntry.IsEmpty());
+ end;
+
+ local procedure FindProdOrderComponentsForTransferLine(var TransferLine: Record "Transfer Line"; var ProdOrderComponent: Record "Prod. Order Component"): Boolean
+ begin
+ exit(ProdOrderComponent.Get("Production Order Status"::Released, TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Prod. Ord. Comp Line No."));
+ end;
+
+ local procedure ShouldCreateSurplusForComponent(var ItemLedgerEntry: Record "Item Ledger Entry"; var ProdOrderComponent: Record "Prod. Order Component"): Boolean
+ begin
+ if (ProdOrderComponent."Item No." <> ItemLedgerEntry."Item No.") or
+ (ProdOrderComponent."Variant Code" <> ItemLedgerEntry."Variant Code") or
+ (ProdOrderComponent."Location Code" <> ItemLedgerEntry."Location Code")
+ then
+ exit(false);
+
+ exit(true);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderSub.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderSub.PageExt.al
new file mode 100644
index 0000000000..65f4dc267f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderSub.PageExt.al
@@ -0,0 +1,184 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001529 "Subc. Trans. Order Sub." extends "Transfer Order Subform"
+{
+ layout
+ {
+ addafter(Description)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = HasSubcontractingContext;
+ }
+ }
+ addafter("Receipt Date")
+ {
+ field("Subc. Purch. Order No."; Rec."Subc. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Purch. Order Line No."; Rec."Subc. Purch. Order Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Prod. Order No."; Rec."Subc. Prod. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Prod. Order Line No."; Rec."Subc. Prod. Order Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Prod. Ord. Comp. Line No."; Rec."Subc. Prod. Ord. Comp Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Routing No."; Rec."Subc. Routing No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Routing Reference No."; Rec."Subc. Routing Reference No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. WorkCenter No."; Rec."Subc. Work Center No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Subc. Operation No."; Rec."Subc. Operation No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ modify(Reserve)
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ modify(ReserveFromInventory)
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ modify("Item &Tracking Lines")
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ modify(Shipment)
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ modify(Receipt)
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ addafter("F&unctions")
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+ Visible = HasSubcontractingContext;
+ action("Production Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order';
+ Image = Production;
+ ToolTip = 'View the related production order.';
+ trigger OnAction()
+ begin
+ ShowProductionOrder(Rec);
+ end;
+ }
+ action("Production Order Routing")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Routing';
+ Image = Route;
+ ToolTip = 'View the related production order routing.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderRouting(Rec);
+ end;
+ }
+ action("Production Order Components")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Components';
+ Image = Components;
+ ToolTip = 'View the related production order components.';
+ trigger OnAction()
+ begin
+ ShowProductionOrderComponents(Rec);
+ end;
+ }
+ action("Purchase Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Purchase Order';
+ Image = Order;
+ ToolTip = 'View the related subcontracting purchase order.';
+ trigger OnAction()
+ begin
+ ShowPurchaseOrder(Rec);
+ end;
+ }
+ }
+ }
+ }
+ var
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ HasSubcontractingContext: Boolean;
+
+ internal procedure SetIsSubcontracting(IsSubcontractingRelated: Boolean)
+ begin
+ HasSubcontractingContext := IsSubcontractingRelated;
+ CurrPage.Update();
+ end;
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant)
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowPurchaseOrder(RecRelatedVariant: Variant)
+ begin
+ SubcPurchFactboxMgmt.ShowPurchaseOrder(RecRelatedVariant);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransRcptHeaderExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransRcptHeaderExt.Codeunit.al
new file mode 100644
index 0000000000..2f2c9c82c6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransRcptHeaderExt.Codeunit.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+codeunit 99001542 "Subc. Trans Rcpt Header Ext"
+{
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Receipt Header", OnAfterCopyFromTransferHeader, '', false, false)]
+ local procedure OnAfterCopyFromTransferHeader(var TransferReceiptHeader: Record "Transfer Receipt Header"; TransferHeader: Record "Transfer Header")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferReceiptHeader."Subc. Source Type" := TransferHeader."Subc. Source Type";
+ TransferReceiptHeader."Source Subtype" := TransferHeader."Source Subtype";
+ TransferReceiptHeader."Source ID" := TransferHeader."Source ID";
+ TransferReceiptHeader."Source Ref. No." := TransferHeader."Source Ref. No.";
+ TransferReceiptHeader."Subc. Return Order" := TransferHeader."Subc. Return Order";
+ TransferReceiptHeader."Subcontr. Purch. Order No." := TransferHeader."Subcontr. Purch. Order No.";
+ TransferReceiptHeader."Subcontr. PO Line No." := TransferHeader."Subcontr. PO Line No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransRcptHeaderExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransRcptHeaderExt.TableExt.al
new file mode 100644
index 0000000000..32d493212c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransRcptHeaderExt.TableExt.al
@@ -0,0 +1,59 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+tableextension 99001521 "Subc. Trans Rcpt Header Ext." extends "Transfer Receipt Header"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subcontr. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subcontr. Purch. Order No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001531; "Subcontr. PO Line No."; Integer)
+ {
+ Caption = 'Subcontr. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001535; "Source Subtype"; Option)
+ {
+ Caption = 'Source Subtype';
+ DataClassification = CustomerContent;
+ OptionCaption = '0,1,2,3,4,5,6,7,8,9,10';
+ OptionMembers = "0","1","2","3","4","5","6","7","8","9","10";
+ }
+ field(99001536; "Source ID"; Code[20])
+ {
+ Caption = 'Source ID';
+ DataClassification = CustomerContent;
+ }
+ field(99001537; "Source Ref. No."; Integer)
+ {
+ Caption = 'Source Ref. No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001540; "Subc. Source Type"; Enum "Transfer Source Type")
+ {
+ Caption = 'Source Type';
+ DataClassification = CustomerContent;
+ }
+ field(99001541; "Subc. Return Order"; Boolean)
+ {
+ Caption = 'Return Order';
+ DataClassification = CustomerContent;
+ }
+ }
+ keys
+ {
+ key(Key99001500; "Subcontr. Purch. Order No.") { }
+ key(Key99001501; "Source ID", "Subc. Source Type", "Source Subtype") { }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransShptHeaderExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransShptHeaderExt.Codeunit.al
new file mode 100644
index 0000000000..e8bb8d872f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransShptHeaderExt.Codeunit.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+codeunit 99001543 "Subc. Trans Shpt Header Ext"
+{
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Shipment Header", OnAfterCopyFromTransferHeader, '', false, false)]
+ local procedure OnAfterCopyFromTransferHeader(var TransferShipmentHeader: Record "Transfer Shipment Header"; TransferHeader: Record "Transfer Header")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferShipmentHeader."Subc. Source Type" := TransferHeader."Subc. Source Type";
+ TransferShipmentHeader."Source Subtype" := TransferHeader."Source Subtype";
+ TransferShipmentHeader."Source ID" := TransferHeader."Source ID";
+ TransferShipmentHeader."Source Ref. No." := TransferHeader."Source Ref. No.";
+ TransferShipmentHeader."Subc. Return Order" := TransferHeader."Subc. Return Order";
+ TransferShipmentHeader."Subcontr. Purch. Order No." := TransferHeader."Subcontr. Purch. Order No.";
+ TransferShipmentHeader."Subcontr. PO Line No." := TransferHeader."Subcontr. PO Line No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransShptHeaderExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransShptHeaderExt.TableExt.al
new file mode 100644
index 0000000000..0c3ecc1c4b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransShptHeaderExt.TableExt.al
@@ -0,0 +1,66 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+tableextension 99001522 "Subc. Trans Shpt Header Ext." extends "Transfer Shipment Header"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subcontr. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subcontr. Purch. Order No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ }
+ field(99001531; "Subcontr. PO Line No."; Integer)
+ {
+ Caption = 'Subcontr. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ }
+ field(99001535; "Source Subtype"; Option)
+ {
+ Caption = 'Source Subtype';
+ DataClassification = CustomerContent;
+ OptionCaption = '0,1,2,3,4,5,6,7,8,9,10';
+ OptionMembers = "0","1","2","3","4","5","6","7","8","9","10";
+ ToolTip = 'Specifies which source subtype the transfer order is related to.';
+ }
+ field(99001536; "Source ID"; Code[20])
+ {
+ Caption = 'Source ID';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies which source ID the transfer order is related to.';
+ }
+ field(99001537; "Source Ref. No."; Integer)
+ {
+ Caption = 'Source Ref. No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies a reference number for the line, which the transfer order is related to.';
+ }
+ field(99001540; "Subc. Source Type"; Enum "Transfer Source Type")
+ {
+ Caption = 'Source Type';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies for which source type the transfer order is related to.';
+ }
+ field(99001541; "Subc. Return Order"; Boolean)
+ {
+ Caption = 'Return Order';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies whether the existing transfer order is a return of the subcontractor.';
+ }
+ }
+ keys
+ {
+ key(Key99001500; "Subcontr. Purch. Order No.") { }
+ key(Key99001501; "Source ID", "Subc. Source Type", "Source Subtype") { }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferHeader.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferHeader.TableExt.al
new file mode 100644
index 0000000000..6d2f64ce35
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferHeader.TableExt.al
@@ -0,0 +1,121 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Sales.Customer;
+
+tableextension 99001520 "Subc. Transfer Header" extends "Transfer Header"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subcontr. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subcontr. Purch. Order No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001531; "Subcontr. PO Line No."; Integer)
+ {
+ Caption = 'Subcontr. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001535; "Source Subtype"; Option)
+ {
+ Caption = 'Source Subtype';
+ DataClassification = CustomerContent;
+ OptionCaption = '0,1,2,3,4,5,6,7,8,9,10';
+ OptionMembers = "0","1","2","3","4","5","6","7","8","9","10";
+ }
+ field(99001536; "Source ID"; Code[20])
+ {
+ Caption = 'Source ID';
+ DataClassification = CustomerContent;
+ trigger OnLookup()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ HandleSubcontractingSourceLookup(Rec);
+ end;
+ }
+ field(99001537; "Source Ref. No."; Integer)
+ {
+ Caption = 'Source Ref. No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001540; "Subc. Source Type"; Enum "Transfer Source Type")
+ {
+ Caption = 'Source Type';
+ DataClassification = CustomerContent;
+ }
+ field(99001541; "Subc. Return Order"; Boolean)
+ {
+ Caption = 'Return Order';
+ DataClassification = CustomerContent;
+ }
+ }
+ keys
+ {
+ key(Key99001500; "Subcontr. Purch. Order No.") { }
+ key(Key99001501; "Source ID", "Subc. Source Type", "Source Subtype") { }
+ }
+
+ local procedure HandleSubcontractingSourceLookup(var TransferHeader: Record "Transfer Header")
+ var
+ Customer: Record Customer;
+ Item: Record Item;
+ Vendor: Record Vendor;
+ begin
+ if TransferHeader."Subc. Source Type" = TransferHeader."Subc. Source Type"::Subcontracting then
+ case TransferHeader."Source Subtype" of
+ TransferHeader."Source Subtype"::"1":
+ begin
+ Customer.SetRange("No.", TransferHeader."Source ID");
+ Page.RunModal(0, Customer);
+ end;
+ TransferHeader."Source Subtype"::"2":
+ begin
+ Vendor.SetRange("No.", TransferHeader."Source ID");
+ Page.RunModal(0, Vendor);
+ end;
+ TransferHeader."Source Subtype"::"3":
+ begin
+ Item.SetRange("No.", TransferHeader."Source ID");
+ Page.RunModal(0, Item);
+ end;
+ end;
+ end;
+
+ procedure CheckDirectTransferPosting()
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TestField("Transfer-to Code");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferHeaderExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferHeaderExt.Codeunit.al
new file mode 100644
index 0000000000..85c0a588da
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferHeaderExt.Codeunit.al
@@ -0,0 +1,62 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+codeunit 99001507 "Subc. Transfer Header Ext."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Header", OnBeforeValidateEvent, "Transfer-from Code", false, false)]
+ local procedure OnBeforeValidateTransferFromCode(var Rec: Record "Transfer Header"; var xRec: Record "Transfer Header"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if CurrFieldNo <> Rec.FieldNo("Transfer-from Code") then
+ exit;
+
+ if Rec."Transfer-from Code" = xRec."Transfer-from Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcTransferHeaderCanBeModified(Rec, Rec.FieldCaption("Transfer-from Code"));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Header", OnBeforeValidateEvent, "Transfer-to Code", false, false)]
+ local procedure OnBeforeValidateTransferToCode(var Rec: Record "Transfer Header"; var xRec: Record "Transfer Header"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if CurrFieldNo <> Rec.FieldNo("Transfer-to Code") then
+ exit;
+
+ if Rec."Transfer-to Code" = xRec."Transfer-to Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcTransferHeaderCanBeModified(Rec, Rec.FieldCaption("Transfer-to Code"));
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLine.TableExt.al
new file mode 100644
index 0000000000..94fa71aef6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLine.TableExt.al
@@ -0,0 +1,249 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Enums;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Warehouse.Document;
+
+tableextension 99001517 "Subc. Transfer Line" extends "Transfer Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subc. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subc. Purch. Order No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ }
+ field(99001531; "Subc. Purch. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ }
+ field(99001532; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Subc. Prod. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ ToolTip = 'Specifies the number of the related production order.';
+ }
+ field(99001533; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ ToolTip = 'Specifies the number of the related production order line.';
+ }
+ field(99001534; "Subc. Prod. Ord. Comp Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Order Comp. Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Component"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Prod. Order Line No." = field("Subc. Prod. Order Line No."));
+ ToolTip = 'Specifies the line number of the related production order component line.';
+ }
+ field(99001535; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Subc. Routing No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Routing Header";
+ ToolTip = 'Specifies the number of the related production routing.';
+ }
+ field(99001536; "Subc. Routing Reference No."; Integer)
+ {
+ Caption = 'Subc. Routing Reference No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the number of the related production routing reference no.';
+ }
+ field(99001537; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Subc. Work Center No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Work Center";
+ ToolTip = 'Specifies the number of the related production work center.';
+ }
+ field(99001538; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Subc. Operation No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."));
+ ToolTip = 'Specifies the number of the related production operation no.';
+ }
+ field(99001539; "Subc. Return Order"; Boolean)
+ {
+ Caption = 'Subc. Return Order';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether the existing transfer order is a return of the subcontractor.';
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer line represents a WIP item transfer. When enabled, a WIP item transfer can be created.';
+
+ trigger OnValidate()
+ var
+ Item: Record Item;
+ UnitOfMeasureManagement: Codeunit "Unit of Measure Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if "Transfer WIP Item" then begin
+ CheckForExistingReservationsOrItemTracking();
+ "Qty. per Unit of Measure" := 0;
+ end else begin
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get(Rec."Item No.");
+ "Qty. per Unit of Measure" := UnitOfMeasureManagement.GetQtyPerUnitOfMeasure(Item, "Unit of Measure Code");
+ end;
+ UpdateDescriptions();
+ Validate(Quantity);
+ end;
+ }
+ field(99001561; "Whse. Inbnd. Otsdg. Qty"; Decimal)
+ {
+ AutoFormatType = 0;
+ BlankZero = true;
+ CalcFormula = sum("Warehouse Receipt Line"."Qty. Outstanding" where("Source Type" = const(5741),
+ "Source Subtype" = const("1"),
+ "Source No." = field("Document No."),
+ "Source Line No." = field("Line No.")));
+ Caption = 'Whse. Inbnd. Otsdg. Qty';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the outstanding quantity on warehouse receipts for this transfer line.';
+ }
+ field(99001562; "Whse Outbnd. Otsdg. Qty"; Decimal)
+ {
+ AutoFormatType = 0;
+ BlankZero = true;
+ CalcFormula = sum("Warehouse Shipment Line"."Qty. Outstanding" where("Source Type" = const(5741),
+ "Source Subtype" = const("0"),
+ "Source No." = field("Document No."),
+ "Source Line No." = field("Line No.")));
+ Caption = 'Whse Outbnd. Otsdg. Qty';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ FieldClass = FlowField;
+ ToolTip = 'Specifies the outstanding quantity on warehouse shipments for this transfer line.';
+ }
+ field(99001563; "Prev. Operation No."; Code[10])
+ {
+ AllowInCustomizations = AsReadOnly;
+ Caption = 'Previous Operation No.';
+ DataClassification = CustomerContent;
+ Editable = false;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."));
+ ToolTip = 'Specifies the number of the related previous production operation no.';
+ }
+ }
+ keys
+ {
+ key(Key99001500; "Subc. Purch. Order No.", "Subc. Purch. Order Line No.", "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Prod. Ord. Comp Line No.") { }
+ key(Key99001501; "Subc. Prod. Order No.", "Subc. Routing No.", "Subc. Routing Reference No.", "Subc. Operation No.", "Subc. Purch. Order No.") { }
+ key(Key99001502; "Subc. Purch. Order No.", "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Operation No.") { }
+ key(Key99001503; "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Routing Reference No.", "Subc. Routing No.", "Subc. Operation No.") { }
+ key(Key99001504; "Subc. Prod. Order No.", "Subc. Prod. Order Line No.", "Subc. Prod. Ord. Comp Line No.", "Subc. Purch. Order No.", "Subc. Return Order") { }
+ }
+
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ internal procedure CheckForExistingReservationsOrItemTracking()
+ var
+ ReservationEntry: Record "Reservation Entry";
+ ExistingReservationsErr: Label 'There are existing reservations for this transfer line. Please remove the reservations before changing the line to/from a WIP item transfer.';
+ ExistingItemTrackingErr: Label 'There is existing item tracking for this transfer line. Please remove the item tracking before changing the line to/from a WIP item transfer.';
+ ExistingReservationEntriesErr: Label 'There are existing reservation entries for this transfer line. Please remove the reservation entries before changing the line to/from a WIP item transfer.';
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ Rec.SetReservationFilters(ReservationEntry, "Transfer Direction"::Outbound);
+ ReservationEntry.SetRange("Reservation Status", "Reservation Status"::Reservation);
+ if not ReservationEntry.IsEmpty() then
+ Error(ExistingReservationsErr);
+
+ ReservationEntry.Reset();
+ Rec.SetReservationFilters(ReservationEntry, "Transfer Direction"::Inbound);
+ ReservationEntry.SetRange("Reservation Status", "Reservation Status"::Reservation);
+ if not ReservationEntry.IsEmpty() then
+ Error(ExistingReservationsErr);
+
+ ReservationEntry.Reset();
+ Rec.SetReservationFilters(ReservationEntry, "Transfer Direction"::Outbound);
+ ReservationEntry.SetRange("Source Subtype");//Ignore Direction
+ ReservationEntry.SetRange("Reservation Status", "Reservation Status"::Surplus);
+ if not ReservationEntry.IsEmpty() then
+ Error(ExistingItemTrackingErr);
+
+ if Rec.ReservEntryExist() then
+ Error(ExistingReservationEntriesErr);
+ end;
+
+ procedure CalcBaseQty(Quantity: Decimal) BaseQty: Decimal
+ var
+ Item: Record Item;
+ UnitOfMeasureManagement: Codeunit "Unit of Measure Management";
+ QtyPerUoM: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get("Item No.");
+ QtyPerUoM := UnitOfMeasureManagement.GetQtyPerUnitOfMeasure(Item, "Unit of Measure Code");
+ BaseQty := UnitOfMeasureManagement.CalcBaseQty(Quantity, QtyPerUoM);
+ end;
+
+ internal procedure UpdateDescriptions()
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec."Transfer WIP Item" then
+ if ProdOrderRoutingLine.Get("Production Order Status"::Released, "Subc. Prod. Order No.", "Subc. Routing Reference No.", "Subc. Routing No.", "Subc. Operation No.") then begin
+ Rec.Description := ProdOrderRoutingLine."Transfer Description";
+ Rec."Description 2" := ProdOrderRoutingLine."Transfer Description 2";
+ end;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLineExt.Codeunit.al
new file mode 100644
index 0000000000..9c98ac68dc
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLineExt.Codeunit.al
@@ -0,0 +1,158 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Transfer;
+
+codeunit 99001544 "Subc. Transfer Line Ext."
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+
+#endif
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnAfterGetTransHeader, '', false, false)]
+ local procedure OnAfterGetTransHeader(var TransferLine: Record "Transfer Line"; TransferHeader: Record "Transfer Header")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine."Subc. Return Order" := TransferHeader."Subc. Return Order";
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnAfterDeleteEvent, '', false, false)]
+ local procedure OnAfterDeleteEvent(var Rec: Record "Transfer Line"; RunTrigger: Boolean)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary then
+ exit;
+
+ if not RunTrigger then
+ exit;
+
+ SubcTransferManagement.UpdateLocationCodeInProdOrderCompAfterDeleteTransferLine(Rec);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeValidateEvent, "Item No.", false, false)]
+ local procedure OnBeforeValidateItemNo(var Rec: Record "Transfer Line"; var xRec: Record "Transfer Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."Item No." = xRec."Item No." then
+ exit;
+
+ SubcTransferManagement.CheckSubcTransferLineCanBeModified(Rec, Rec.FieldCaption("Item No."));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeValidateEvent, "Variant Code", false, false)]
+ local procedure OnBeforeValidateVariantCode(var Rec: Record "Transfer Line"; var xRec: Record "Transfer Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec."Variant Code" = xRec."Variant Code" then
+ exit;
+
+ SubcTransferManagement.CheckSubcTransferLineCanBeModified(Rec, Rec.FieldCaption("Variant Code"));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeValidateEvent, Quantity, false, false)]
+ local procedure OnBeforeValidateQuantity(var Rec: Record "Transfer Line"; var xRec: Record "Transfer Line"; CurrFieldNo: Integer)
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if CurrFieldNo = 0 then
+ exit;
+
+ if Rec.Quantity = xRec.Quantity then
+ exit;
+
+ SubcTransferManagement.CheckSubcTransferLineCanBeModified(Rec, Rec.FieldCaption(Quantity));
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnValidateItemNoOnCopyFromTempTransLine, '', false, false)]
+ local procedure OnValidateItemNoOnCopyFromTempTransLine_TransferLine(var TransferLine: Record "Transfer Line"; TempTransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ CopySubFieldsFromTempTransferLineToTransferLine(TransferLine, TempTransferLine);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnValidateUnitofMeasureCodeOnBeforeValidateQuantity, '', false, false)]
+ local procedure OnValidateUnitofMeasureCodeOnBeforeValidateQuantity(var TransferLine: Record "Transfer Line"; Item: Record Item; xTransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Transfer WIP Item" then
+ TransferLine."Qty. per Unit of Measure" := 0;
+ end;
+
+ local procedure CopySubFieldsFromTempTransferLineToTransferLine(var TransferLine: Record "Transfer Line"; TempTransferLine: Record "Transfer Line")
+ begin
+ TransferLine."Subc. Purch. Order No." := TempTransferLine."Subc. Purch. Order No.";
+ TransferLine."Subc. Purch. Order Line No." := TempTransferLine."Subc. Purch. Order Line No.";
+ TransferLine."Subc. Prod. Order No." := TempTransferLine."Subc. Prod. Order No.";
+ TransferLine."Subc. Prod. Order Line No." := TempTransferLine."Subc. Prod. Order Line No.";
+ TransferLine."Subc. Prod. Ord. Comp Line No." := TempTransferLine."Subc. Prod. Ord. Comp Line No.";
+ TransferLine."Subc. Routing No." := TempTransferLine."Subc. Routing No.";
+ TransferLine."Subc. Routing Reference No." := TempTransferLine."Subc. Routing Reference No.";
+ TransferLine."Subc. Work Center No." := TempTransferLine."Subc. Work Center No.";
+ TransferLine."Subc. Operation No." := TempTransferLine."Subc. Operation No.";
+ TransferLine."Subc. Return Order" := TempTransferLine."Subc. Return Order";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLineFactbox.Page.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLineFactbox.Page.al
new file mode 100644
index 0000000000..4448ef9ad3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLineFactbox.Page.al
@@ -0,0 +1,93 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+page 99001501 "Subc. Transfer Line Factbox"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Details';
+ Editable = false;
+ PageType = CardPart;
+ SourceTable = "Transfer Line";
+
+ layout
+ {
+ area(Content)
+ {
+ field(ShowPurchOrder; Rec."Subc. Purch. Order No.")
+ {
+ Caption = 'Purchase Order';
+ ToolTip = 'Specifies the dependent Purchase Order of this Subcontracting Transfer Order.';
+ trigger OnDrillDown()
+ begin
+ ShowPurchaseOrder(Rec);
+ end;
+ }
+ field(ShowProdOrder; Rec."Subc. Prod. Order No.")
+ {
+ Caption = 'Production Order';
+ ToolTip = 'Specifies the dependent Production Order of this Subcontracting Transfer Order.';
+ trigger OnDrillDown()
+ begin
+ ShowProductionOrder(Rec);
+ end;
+ }
+ field(ShowProdOrderRouting; GetNoOfProductionOrderRoutings(Rec))
+ {
+ Caption = 'Production Routing';
+ ToolTip = 'Specifies the dependent Production Routing of this Subcontracting Transfer Order.';
+ trigger OnDrillDown()
+ begin
+ ShowProductionOrderRouting(Rec);
+ end;
+ }
+ field(ShowProdOrderComponents; GetNoOfProductionComponents(Rec))
+ {
+ Caption = 'Production Components';
+ ToolTip = 'Specifies the dependent Production Components of this Subcontracting Transfer Order.';
+
+ trigger OnDrillDown()
+ begin
+ ShowProductionOrderComponents(Rec);
+ end;
+ }
+ }
+ }
+ var
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+
+ local procedure GetNoOfProductionComponents(RecRelatedVariant: Variant): Integer
+ begin
+ exit(SubcProdOrderFactboxMgmt.CalcNoOfProductionOrderComponents(RecRelatedVariant))
+ end;
+
+ local procedure GetNoOfProductionOrderRoutings(RecRelatedVariant: Variant): Integer
+ begin
+ exit(SubcProdOrderFactboxMgmt.CalcNoOfProductionOrderRoutings(RecRelatedVariant))
+ end;
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowPurchaseOrder(RecRelatedVariant: Variant)
+ begin
+ SubcPurchFactboxMgmt.ShowPurchaseOrder(RecRelatedVariant);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLines.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLines.PageExt.al
new file mode 100644
index 0000000000..0841fd4d42
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferLines.PageExt.al
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001530 "Subc. Transfer Lines" extends "Transfer Lines"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Subc. Return Order"; Rec."Subc. Return Order")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ Visible = false;
+ }
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al
new file mode 100644
index 0000000000..77d43b2169
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferManagement.Codeunit.al
@@ -0,0 +1,475 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Purchases.Document;
+
+codeunit 99001504 "Subc. Transfer Management"
+{
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ TempGlobalReservationEntry: Record "Reservation Entry" temporary;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ CannotModifySubcPurchLineErr: Label 'You cannot change %1 on the subcontracting purchase line because transfer orders exist for the linked production order %2.', Comment = '%1=Field Caption, %2=Production Order No.';
+ CannotModifyStockAtSubcErr: Label 'You cannot change %1 on the subcontracting purchase line because there are remaining components or WIP items transferred to the subcontractor for production order %2.', Comment = '%1=Field Caption, %2=Production Order No.';
+ CannotModifySubcTransferLineErr: Label 'You cannot change %1 on the subcontracting transfer line because it is linked to production order %2.', Comment = '%1=Field Caption, %2=Production Order No.';
+ CannotModifySubcTransferHeaderErr: Label 'You cannot change %1 on the subcontracting transfer order because it contains lines linked to a production order.', Comment = '%1=Field Caption';
+ CannotDeletePurchLineTransferExistsErr: Label 'You cannot delete the subcontracting purchase line because transfer orders exist for the linked production order %1.', Comment = '%1=Production Order No.';
+ CannotDeletePurchLineStockAtSubcErr: Label 'You cannot delete the subcontracting purchase line because there are remaining components or WIP items transferred to the subcontractor for production order %1.', Comment = '%1=Production Order No.';
+ CannotDeleteStockAtSubcErr: Label 'You cannot delete Subcontracting Order %1 because components or WIP items have been transferred to the subcontractor location for production order %2.', Comment = '%1=Purchase Order No., %2=Production Order No.';
+ HasManufacturingSetup: Boolean;
+
+ procedure CalcReceiptDateFromProdCompDueDateWithCompTransferLeadTime(ProdOrderComponent: Record "Prod. Order Component") ReceiptDate: Date
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetManufacturingSetup();
+ if not HasManufacturingSetup or (Format(ManufacturingSetup."Subc. Comp. Transfer Lead Time") = '') then
+ exit(ProdOrderComponent."Due Date");
+
+ ReceiptDate := CalcDate('-' + Format(ManufacturingSetup."Subc. Comp. Transfer Lead Time"), ProdOrderComponent."Due Date");
+
+ exit(ReceiptDate);
+ end;
+
+ procedure CheckDirectTransferIsAllowedForTransferHeader(TransferHeader: Record "Transfer Header")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferHeader.CheckDirectTransferPosting();
+ end;
+
+ procedure TransferReservationEntryFromProdOrderCompToTransferOrder(TransferLine: Record "Transfer Line"; ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ ProdOrderCompReserve: Codeunit "Prod. Order Comp.-Reserve";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TempGlobalReservationEntry.Reset();
+ TempGlobalReservationEntry.DeleteAll();
+
+ if not ProdOrderCompReserve.FindReservEntry(ProdOrderComponent, ReservationEntry) then
+ exit;
+
+ if ReservationEntry.FindSet() then
+ repeat
+ TempGlobalReservationEntry := ReservationEntry;
+ TempGlobalReservationEntry.Insert();
+ until ReservationEntry.Next() = 0;
+
+ ReservationEntry.TransferReservations(
+ ReservationEntry,
+ TransferLine."Item No.",
+ TransferLine."Variant Code",
+ TransferLine."Transfer-from Code",
+ true,
+ TransferLine."Quantity (Base)",
+ TransferLine."Qty. per Unit of Measure",
+ Database::"Transfer Line",
+ 0, // Direction::Outbound
+ TransferLine."Document No.",
+ '',
+ 0,
+ TransferLine."Line No.");
+ end;
+
+ procedure ComponentHasExcessReservations(ProdOrderComponent: Record "Prod. Order Component"; MaxQtyBase: Decimal): Boolean
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ exit(GetComponentReservedQtyBase(ProdOrderComponent) > MaxQtyBase);
+ end;
+
+ procedure GetComponentReservedQtyBase(ProdOrderComponent: Record "Prod. Order Component"): Decimal
+ var
+ ReservationEntry: Record "Reservation Entry";
+ ProdOrderCompReserve: Codeunit "Prod. Order Comp.-Reserve";
+ TotalReservedQtyBase: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not ProdOrderCompReserve.FindReservEntry(ProdOrderComponent, ReservationEntry) then
+ exit(0);
+
+ if ReservationEntry.FindSet() then
+ repeat
+ TotalReservedQtyBase += Abs(ReservationEntry."Quantity (Base)");
+ until ReservationEntry.Next() = 0;
+
+ exit(TotalReservedQtyBase);
+ end;
+
+ procedure CreateReservEntryForTransferReceiptToProdOrderComp(
+ TransferLine: Record "Transfer Line";
+ ProdOrderComponent: Record "Prod. Order Component")
+ var
+ Item: Record Item;
+ TempTrackingSpecification: Record "Tracking Specification" temporary;
+ CreateReservEntry: Codeunit "Create Reserv. Entry";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TempGlobalReservationEntry.SetRange("Reservation Status", TempGlobalReservationEntry."Reservation Status"::Reservation);
+ if not TempGlobalReservationEntry.FindSet() then
+ exit;
+
+ repeat
+ if TempGlobalReservationEntry.GetItemTrackingEntryType() <> "Item Tracking Entry Type"::None then
+ if Item.Get(TempGlobalReservationEntry."Item No.") then begin
+ TempGlobalReservationEntry."Location Code" := ProdOrderComponent."Location Code";
+ CreateReservEntry.CreateReservEntryFor(
+ Database::"Transfer Line",
+ 1, // Direction::Inbound
+ TransferLine."Document No.",
+ '',
+ TransferLine."Derived From Line No.",
+ TransferLine."Line No.",
+ TransferLine."Qty. per Unit of Measure",
+ Abs(TempGlobalReservationEntry.Quantity),
+ Abs(TempGlobalReservationEntry."Quantity (Base)"),
+ TempGlobalReservationEntry);
+
+ TempTrackingSpecification.Init();
+ TempTrackingSpecification.SetSource(
+ Database::"Prod. Order Component",
+ ProdOrderComponent.Status.AsInteger(),
+ ProdOrderComponent."Prod. Order No.",
+ ProdOrderComponent."Line No.",
+ '',
+ ProdOrderComponent."Prod. Order Line No.");
+ TempTrackingSpecification."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+ TempTrackingSpecification.CopyTrackingFromReservEntry(TempGlobalReservationEntry);
+
+ CreateReservEntry.CreateReservEntryFrom(TempTrackingSpecification);
+
+ CreateReservEntry.CreateEntry(
+ TempGlobalReservationEntry."Item No.",
+ TempGlobalReservationEntry."Variant Code",
+ TransferLine."Transfer-to Code",
+ TempGlobalReservationEntry.Description,
+ TransferLine."Receipt Date",
+ ProdOrderComponent."Due Date",
+ 0,
+ TempGlobalReservationEntry."Reservation Status");
+ end;
+ until TempGlobalReservationEntry.Next() = 0;
+ end;
+
+ procedure TransferReservationEntryFromPstTransferLineToProdOrderComp(var TransferReceiptLine: Record "Transfer Receipt Line")
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProdOrderComponent: Record "Prod. Order Component";
+ TempForReservationEntry: Record "Reservation Entry" temporary;
+ TempTrackingSpecification: Record "Tracking Specification" temporary;
+ ProdOrderCompReserve: Codeunit "Prod. Order Comp.-Reserve";
+ UnitOfMeasureManagement: Codeunit "Unit of Measure Management";
+ QtyToReserve: Decimal;
+ QtyToReserveBase: Decimal;
+ AvailableToReserveBase: Decimal;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (TransferReceiptLine."Subc. Prod. Order No." = '') or (TransferReceiptLine."Subc. Operation No." = '') then
+ exit;
+ if not ProdOrderComponent.Get("Production Order Status"::Released, TransferReceiptLine."Subc. Prod. Order No.", TransferReceiptLine."Subc. Prod. Order Line No.", TransferReceiptLine."Subc. Prod. Ord. Comp Line No.") then
+ exit;
+ ItemLedgerEntry.SetCurrentKey("Item No.", Open, "Variant Code", Positive, "Expiration Date", "Lot No.", "Serial No.");
+ ItemLedgerEntry.SetRange("Item No.", TransferReceiptLine."Item No.");
+ ItemLedgerEntry.SetRange(Open, true);
+ ItemLedgerEntry.SetRange(Positive, true);
+ ItemLedgerEntry.SetRange("Document No.", TransferReceiptLine."Document No.");
+ ItemLedgerEntry.SetRange("Document Line No.", TransferReceiptLine."Line No.");
+ ItemLedgerEntry.SetRange("Location Code", TransferReceiptLine."Transfer-to Code");
+ ItemLedgerEntry.SetLoadFields("Serial No.", "Lot No.", "Package No.", "Variant Code", "Location Code", Quantity);
+ if not ItemLedgerEntry.IsEmpty() then begin
+ ItemLedgerEntry.FindSet();
+ repeat
+ if (ItemLedgerEntry."Lot No." <> '') or (ItemLedgerEntry."Serial No." <> '') or (ItemLedgerEntry."Package No." <> '') then begin
+ // Only reserve up to the component's remaining need. Excess received quantity
+ // (e.g. when more was transferred to/from the subcontractor than the component requires)
+ // is left as free inventory instead of failing with "Reserved quantity cannot be greater than 0".
+ ProdOrderComponent.CalcFields("Reserved Qty. (Base)");
+ AvailableToReserveBase := Abs(ProdOrderComponent."Remaining Qty. (Base)") - Abs(ProdOrderComponent."Reserved Qty. (Base)");
+
+ // Item ledger entry quantities are always stored in the base unit of measure.
+ QtyToReserveBase := ItemLedgerEntry.Quantity;
+ if QtyToReserveBase > AvailableToReserveBase then
+ // Serial-tracked entries are indivisible, so skip the entry entirely when it no longer
+ // fully fits. Lot- and package-tracked entries can be reserved partially.
+ if ItemLedgerEntry."Serial No." <> '' then
+ QtyToReserveBase := 0
+ else
+ QtyToReserveBase := AvailableToReserveBase;
+
+ if QtyToReserveBase > 0 then begin
+ if ProdOrderComponent."Qty. per Unit of Measure" <> 0 then
+ QtyToReserve := UnitOfMeasureManagement.CalcQtyFromBase(QtyToReserveBase, ProdOrderComponent."Qty. per Unit of Measure")
+ else
+ QtyToReserve := QtyToReserveBase;
+
+ if not TempTrackingSpecification.IsEmpty() then
+ TempTrackingSpecification.DeleteAll();
+ TempTrackingSpecification."Source Type" := Database::"Item Ledger Entry";
+ TempTrackingSpecification."Source Subtype" := 0;
+ TempTrackingSpecification."Source ID" := '';
+ TempTrackingSpecification."Source Batch Name" := '';
+ TempTrackingSpecification."Source Prod. Order Line" := 0;
+ TempTrackingSpecification."Source Ref. No." := ItemLedgerEntry."Entry No.";
+ TempTrackingSpecification."Variant Code" := ItemLedgerEntry."Variant Code";
+ TempTrackingSpecification."Location Code" := ItemLedgerEntry."Location Code";
+ TempTrackingSpecification."Serial No." := ItemLedgerEntry."Serial No.";
+ TempTrackingSpecification."Lot No." := ItemLedgerEntry."Lot No.";
+ TempTrackingSpecification."Package No." := ItemLedgerEntry."Package No.";
+ TempTrackingSpecification."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+ TempTrackingSpecification.Insert();
+
+ ProdOrderCompReserve.CreateReservationSetFrom(TempTrackingSpecification);
+ TempForReservationEntry.CopyTrackingFromSpec(TempTrackingSpecification);
+ ProdOrderCompReserve.CreateReservation(
+ ProdOrderComponent,
+ ProdOrderComponent.Description,
+ ProdOrderComponent."Due Date",
+ QtyToReserve,
+ QtyToReserveBase,
+ TempForReservationEntry);
+ end;
+ end;
+ until ItemLedgerEntry.Next() = 0;
+ end;
+ end;
+
+ procedure UpdateLocationCodeInProdOrderCompAfterDeleteTransferLine(var TransferLine: Record "Transfer Line")
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ SubcontractingManagement: Codeunit "Subcontracting Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Quantity Shipped" <> 0 then
+ exit;
+
+ if not ProdOrderComponent.Get("Production Order Status"::Released, TransferLine."Subc. Prod. Order No.", TransferLine."Subc. Prod. Order Line No.", TransferLine."Subc. Prod. Ord. Comp Line No.") then
+ exit;
+
+ if TransferLine."Subc. Return Order" then begin
+ // Return TO deletion (unshipped): component location was changed to Transfer-to (original location)
+ // 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");
+ ProdOrderComponent.Modify();
+ end;
+ exit;
+ end;
+
+ if ProdOrderComponent."Subc. Original Location Code" <> '' then begin
+ SubcontractingManagement.ChangeLocationOnProdOrderComponent(ProdOrderComponent, '', ProdOrderComponent."Subc. Original Location Code", ProdOrderComponent."Subc. Orig. Bin Code");
+ ProdOrderComponent."Subc. Original Location Code" := '';
+ ProdOrderComponent."Subc. Orig. Bin Code" := '';
+
+ ProdOrderComponent.Modify();
+ end;
+ end;
+
+ internal procedure IsSubcontractingTransferDocument(TransferHeader: Record "Transfer Header"): Boolean
+ begin
+ exit(TransferHeader."Subc. Source Type" = TransferHeader."Subc. Source Type"::Subcontracting);
+ end;
+
+ internal procedure IsSubcontractingTransferLine(TransferLine: Record "Transfer Line"): Boolean
+ begin
+ exit((TransferLine."Subc. Prod. Order No." <> '') and (TransferLine."Subc. Prod. Order Line No." <> 0));
+ end;
+
+ internal procedure CheckSubcPurchLineCanBeModified(PurchaseLine: Record "Purchase Line"; FieldCaption: Text)
+ begin
+ if not PurchaseLine."Transfer WIP Item" then
+ exit;
+
+ if HasSubcTransferForPurchLine(PurchaseLine) then
+ Error(CannotModifySubcPurchLineErr, FieldCaption, PurchaseLine."Prod. Order No.");
+ if HasStockAtSubcLocation(PurchaseLine) then
+ Error(CannotModifyStockAtSubcErr, FieldCaption, PurchaseLine."Prod. Order No.");
+ end;
+
+ internal procedure CheckSubcPurchLineCanBeDeleted(PurchaseLine: Record "Purchase Line")
+ begin
+ if not PurchaseLine."Transfer WIP Item" then
+ exit;
+
+ if HasSubcTransferForPurchLine(PurchaseLine) then
+ Error(CannotDeletePurchLineTransferExistsErr, PurchaseLine."Prod. Order No.");
+ if HasStockAtSubcLocation(PurchaseLine) then
+ Error(CannotDeletePurchLineStockAtSubcErr, PurchaseLine."Prod. Order No.");
+ end;
+
+ internal procedure CheckStockAtSubcLocationForPurchHeader(PurchaseHeader: Record "Purchase Header")
+ var
+ PurchaseLine: Record "Purchase Line";
+ begin
+ PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine.SetFilter("Prod. Order No.", '<>%1', '');
+ PurchaseLine.SetRange("Transfer WIP Item", true);
+ if PurchaseLine.FindSet() then
+ repeat
+ if HasStockAtSubcLocation(PurchaseLine) then
+ Error(CannotDeleteStockAtSubcErr, PurchaseHeader."No.", PurchaseLine."Prod. Order 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"): Boolean
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ SubcWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ NetStockAtSubcLocation: Decimal;
+ begin
+ GetProdOrderRoutingLinkCode(ProdOrderRoutingLine, PurchaseLine);
+ 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("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
+ if ProdOrderComponent."Subc. Qty. transf. to Subcontr" <> 0 then begin
+ NetStockAtSubcLocation := ProdOrderComponent."Subc. Qty. transf. to Subcontr";
+ NetStockAtSubcLocation -= 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.", 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
+ exit(true);
+
+ exit(false);
+ end;
+
+ internal procedure CalcConsumedQtyAtSubcLocation(ProdOrderComponent: Record "Prod. Order Component"): Decimal
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ ItemLedgerEntry.SetCurrentKey("Order Type", "Order No.", "Order Line No.", "Entry Type", "Prod. Order Comp. Line No.");
+ ItemLedgerEntry.SetRange("Order Type", ItemLedgerEntry."Order Type"::Production);
+ ItemLedgerEntry.SetRange("Order No.", ProdOrderComponent."Prod. Order No.");
+ ItemLedgerEntry.SetRange("Order Line No.", ProdOrderComponent."Prod. Order Line No.");
+ 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;
+
+ internal procedure CheckSubcTransferLineCanBeModified(TransferLine: Record "Transfer Line"; FieldCaption: Text)
+ begin
+ if IsSubcontractingTransferLine(TransferLine) then
+ if not TransferLine."Transfer WIP Item" then //for now allow updating WIP item lines
+ Error(CannotModifySubcTransferLineErr, FieldCaption, TransferLine."Subc. Prod. Order No.");
+ end;
+
+ internal procedure CheckSubcTransferHeaderCanBeModified(TransferHeader: Record "Transfer Header"; FieldCaption: Text)
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+ if not IsSubcontractingTransferDocument(TransferHeader) then
+ exit;
+
+ TransferLine.SetRange("Document No.", TransferHeader."No.");
+ TransferLine.SetFilter("Subc. Prod. Order No.", '<>%1', '');
+ if not TransferLine.IsEmpty() then
+ Error(CannotModifySubcTransferHeaderErr, FieldCaption);
+ end;
+
+ local procedure GetManufacturingSetup()
+ begin
+ if HasManufacturingSetup then
+ exit;
+ HasManufacturingSetup := ManufacturingSetup.Get();
+ end;
+
+ local procedure GetProdOrderRoutingLinkCode(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; PurchaseLine: Record "Purchase Line")
+ var
+ RoutingOperationNotFoundErr: Label 'Operation %1 in the subcontracting order %2 does not exist in the routing %3 of the production order %4.', Comment = '%1=Operation No., %2=Purchase Order No., %3=Routing No., %4=Production Order No.';
+ begin
+ if not ProdOrderRoutingLine.Get(
+ "Production Order Status"::Released,
+ PurchaseLine."Prod. Order No.",
+ PurchaseLine."Routing Reference No.",
+ PurchaseLine."Routing No.",
+ PurchaseLine."Operation No.")
+ then
+ Error(RoutingOperationNotFoundErr, PurchaseLine."Operation No.", PurchaseLine."Document No.", PurchaseLine."Routing No.", PurchaseLine."Prod. Order No.");
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrder.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrder.PageExt.al
new file mode 100644
index 0000000000..834fde0673
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrder.PageExt.al
@@ -0,0 +1,134 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001526 "Subc. Transfer Order" extends "Transfer Order"
+{
+ layout
+ {
+ modify("Direct Transfer")
+ {
+ Enabled = EsEnableTransferFields;
+ }
+ addlast(General)
+ {
+ field("Subc. Source Type"; Rec."Subc. Source Type")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies for which source type the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceSubtype; Rec."Source Subtype")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies which source subtype the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceID; Rec."Source ID")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies which source ID the transfer order is related to.';
+ Visible = false;
+ }
+ field(SourceRefNo; Rec."Source Ref. No.")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies a reference number for the line, which the transfer order is related to.';
+ Visible = false;
+ }
+ field("Subc. Return Order"; Rec."Subc. Return Order")
+ {
+ ApplicationArea = Subcontracting;
+ Editable = false;
+ ToolTip = 'Specifies whether the existing transfer order is a return of the subcontractor.';
+ Visible = false;
+ }
+ field("Subcontr. Purch. Order No."; Rec."Subcontr. Purch. Order No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order.';
+ Visible = false;
+ }
+ field("Subcontr. PO Line No."; Rec."Subcontr. PO Line No.")
+ {
+ ApplicationArea = Subcontracting;
+ ToolTip = 'Specifies the number of the related purchase order line.';
+ Visible = false;
+ }
+ }
+ addbefore(Control1900383207)
+ {
+ part("Subc. Transfer Line Factbox"; "Subc. Transfer Line Factbox")
+ {
+ ApplicationArea = Subcontracting;
+ Provider = TransferLines;
+ SubPageLink = "Document No." = field("Document No."), "Line No." = field("Line No.");
+ Visible = ShowSubcontractingFactBox;
+ }
+ }
+ }
+ protected var
+ EsEnableTransferFields: Boolean;
+
+ var
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ SubcontractingEnabled: Boolean;
+#endif
+ ShowSubcontractingFactBox: Boolean;
+
+ trigger OnOpenPage()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcontractingEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
+#pragma warning restore AL0432
+ if not SubcontractingEnabled then
+ exit;
+#endif
+ ShowSubcontractingFactBox := SubcTransferManagement.IsSubcontractingTransferDocument(Rec);
+ CurrPage.TransferLines.Page.SetIsSubcontracting(ShowSubcontractingFactBox);
+ EsEnableTransferFields := not IsPartiallyShipped();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ ShowSubcontractingFactBox := SubcTransferManagement.IsSubcontractingTransferDocument(Rec);
+ CurrPage.TransferLines.Page.SetIsSubcontracting(ShowSubcontractingFactBox);
+ end;
+
+ trigger OnAfterGetRecord()
+ begin
+#if not CLEAN28
+ if not SubcontractingEnabled then
+ exit;
+
+#endif
+ EsEnableTransferFields := not IsPartiallyShipped();
+ end;
+
+ local procedure IsPartiallyShipped(): Boolean
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+ TransferLine.SetRange("Document No.", Rec."No.");
+ TransferLine.SetFilter("Quantity Shipped", '> 0');
+ exit(not TransferLine.IsEmpty());
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrders.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrders.PageExt.al
new file mode 100644
index 0000000000..b24eccc349
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrders.PageExt.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+pageextension 99001551 "Subc. Transfer Orders" extends "Transfer Orders"
+{
+ views
+ {
+ addlast
+ {
+ view(SubcontractingOutbound)
+ {
+ Caption = 'To Subcontractor';
+ Filters = where("Subc. Source Type" = const(Subcontracting), "Subc. Return Order" = const(false));
+ }
+ view(SubcontractingReturns)
+ {
+ Caption = 'Returns from Subcontractor';
+ Filters = where("Subc. Source Type" = const(Subcontracting), "Subc. Return Order" = const(true));
+ }
+ }
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferRcptLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferRcptLineExt.Codeunit.al
new file mode 100644
index 0000000000..f6f2460dc7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferRcptLineExt.Codeunit.al
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+codeunit 99001538 "Subc. Transfer Rcpt Line Ext."
+{
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Receipt Line", OnAfterCopyFromTransferLine, '', false, false)]
+ local procedure OnAfterCopyFromTransferLine_T5745(var TransferReceiptLine: Record "Transfer Receipt Line"; TransferLine: Record "Transfer Line")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferReceiptLine."Subc. Purch. Order No." := TransferLine."Subc. Purch. Order No.";
+ TransferReceiptLine."Subc. Purch. Order Line No." := TransferLine."Subc. Purch. Order Line No.";
+ TransferReceiptLine."Subc. Prod. Order No." := TransferLine."Subc. Prod. Order No.";
+ TransferReceiptLine."Subc. Prod. Order Line No." := TransferLine."Subc. Prod. Order Line No.";
+ TransferReceiptLine."Subc. Prod. Ord. Comp Line No." := TransferLine."Subc. Prod. Ord. Comp Line No.";
+ TransferReceiptLine."Subc. Routing No." := TransferLine."Subc. Routing No.";
+ TransferReceiptLine."Subc. Routing Reference No." := TransferLine."Subc. Routing Reference No.";
+ TransferReceiptLine."Subc. Work Center No." := TransferLine."Subc. Work Center No.";
+ TransferReceiptLine."Subc. Operation No." := TransferLine."Subc. Operation No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferRcptLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferRcptLineExt.TableExt.al
new file mode 100644
index 0000000000..9773cf5516
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferRcptLineExt.TableExt.al
@@ -0,0 +1,87 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+
+tableextension 99001518 "Subc. Transfer Rcpt. Line Ext" extends "Transfer Receipt Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subc. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subc. Purch. Order No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001531; "Subc. Purch. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001532; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Subc. Prod. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001533; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001534; "Subc. Prod. Ord. Comp Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Ord. Comp Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Component"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Prod. Order Line No." = field("Subc. Prod. Order Line No."));
+ }
+ field(99001535; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Subc. Routing No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Routing Header";
+ }
+ field(99001536; "Subc. Routing Reference No."; Integer)
+ {
+ Caption = 'Subc. Routing Reference No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001537; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Subc. Work Center No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Work Center";
+ }
+ field(99001538; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Subc. Operation No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."));
+ }
+ field(99001539; "Subc. Return Order"; Boolean)
+ {
+ Caption = 'Subc. Return Order';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer receipt line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferShptLineExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferShptLineExt.Codeunit.al
new file mode 100644
index 0000000000..f5b324f67e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferShptLineExt.Codeunit.al
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+
+codeunit 99001537 "Subc. Transfer Shpt Line Ext."
+{
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Shipment Line", OnAfterCopyFromTransferLine, '', false, false)]
+ local procedure OnAfterCopyFromTransferLine_T5745(var TransferShipmentLine: Record "Transfer Shipment Line"; TransferLine: Record "Transfer Line")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferShipmentLine."Subc. Purch. Order No." := TransferLine."Subc. Purch. Order No.";
+ TransferShipmentLine."Subc. Purch. Order Line No." := TransferLine."Subc. Purch. Order Line No.";
+ TransferShipmentLine."Subc. Prod. Order No." := TransferLine."Subc. Prod. Order No.";
+ TransferShipmentLine."Subc. Prod. Order Line No." := TransferLine."Subc. Prod. Order Line No.";
+ TransferShipmentLine."Subc. Prod. Ord. Comp Line No." := TransferLine."Subc. Prod. Ord. Comp Line No.";
+ TransferShipmentLine."Subc. Routing No." := TransferLine."Subc. Routing No.";
+ TransferShipmentLine."Subc. Routing Reference No." := TransferLine."Subc. Routing Reference No.";
+ TransferShipmentLine."Subc. Work Center No." := TransferLine."Subc. Work Center No.";
+ TransferShipmentLine."Subc. Operation No." := TransferLine."Subc. Operation No.";
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferShptLineExt.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferShptLineExt.TableExt.al
new file mode 100644
index 0000000000..f7cfd6f708
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferShptLineExt.TableExt.al
@@ -0,0 +1,87 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+
+tableextension 99001519 "Subc. Transfer Shpt. Line Ext" extends "Transfer Shipment Line"
+{
+ AllowInCustomizations = AsReadOnly;
+ fields
+ {
+ field(99001530; "Subc. Purch. Order No."; Code[20])
+ {
+ Caption = 'Subc. Purch. Order No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001531; "Subc. Purch. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Purch. Order Line No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001532; "Subc. Prod. Order No."; Code[20])
+ {
+ Caption = 'Subc. Prod. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Production Order"."No." where(Status = const(Released));
+ }
+ field(99001533; "Subc. Prod. Order Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."));
+ }
+ field(99001534; "Subc. Prod. Ord. Comp Line No."; Integer)
+ {
+ Caption = 'Subc. Prod. Ord. Comp Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Component"."Line No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Prod. Order Line No." = field("Subc. Prod. Order Line No."));
+ }
+ field(99001535; "Subc. Routing No."; Code[20])
+ {
+ Caption = 'Subc. Routing No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Routing Header";
+ }
+ field(99001536; "Subc. Routing Reference No."; Integer)
+ {
+ Caption = 'Subc. Routing Reference No.';
+ DataClassification = CustomerContent;
+ }
+ field(99001537; "Subc. Work Center No."; Code[20])
+ {
+ Caption = 'Subc. Work Center No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Work Center";
+ }
+ field(99001538; "Subc. Operation No."; Code[10])
+ {
+ Caption = 'Subc. Operation No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = const(Released),
+ "Prod. Order No." = field("Subc. Prod. Order No."),
+ "Routing No." = field("Subc. Routing No."));
+ }
+ field(99001539; "Subc. Return Order"; Boolean)
+ {
+ Caption = 'Subc. Return Order';
+ DataClassification = CustomerContent;
+ Editable = false;
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer shipment line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Transfer/TransferSourceType.Enum.al b/src/Apps/W1/Subcontracting/App/src/Transfer/TransferSourceType.Enum.al
new file mode 100644
index 0000000000..40c862fdcc
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Transfer/TransferSourceType.Enum.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// 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 99001501 "Transfer Source Type"
+{
+ Extensible = true;
+ value(0; Empty)
+ {
+ Caption = ' ', Locked = true;
+ }
+ value(1; Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcChangeProdOrderStatus.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcChangeProdOrderStatus.Codeunit.al
new file mode 100644
index 0000000000..7a19a960f2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcChangeProdOrderStatus.Codeunit.al
@@ -0,0 +1,124 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+
+codeunit 99001549 "Subc. Change Prod.Order Status"
+{
+ Permissions = TableData "Subcontractor WIP Ledger Entry" = RIMD;
+#if not CLEAN28
+
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ [EventSubscriber(ObjectType::Page, Page::"Change Status on Prod. Order", OnAfterSet, '', false, false)]
+ local procedure SetSubcontractingProductionOrderOnAfterSetSubcontractingWIPEntriesAffected(var Sender: Page "Change Status on Prod. Order"; ProdOrder: Record "Production Order"; var PostingDate: Date; var ReqUpdUnitCost: Boolean; var ProductionOrderStatus: Record "Production Order"; var FirmPlannedStatusEditable: Boolean; var ReleasedStatusEditable: Boolean; var FinishedStatusEditable: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ Sender.SubcSetOrder(ProdOrder);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Status Management", OnRunOnAfterChangeStatusFormRun, '', false, false)]
+ local procedure ChangeProdOrderStatusOnRunOnAfterChangeStatusFormRun(var ProductionOrder: Record "Production Order"; var ChangeStatusOnProdOrder: Page "Change Status on Prod. Order")
+ var
+ SubcTransferWIPPosting: Codeunit "Subc. Transfer WIP Posting";
+ FinishOrderWithoutOutput: Boolean;
+ NewUpdateUnitCost: Boolean;
+ NewPostingDate: Date;
+ NewStatus: Enum "Production Order Status";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ChangeStatusOnProdOrder.ReturnSubWIPQuantityCleanUp() then begin
+ ChangeStatusOnProdOrder.ReturnPostingInfo(NewStatus, NewPostingDate, NewUpdateUnitCost, FinishOrderWithoutOutput);
+ SubcTransferWIPPosting.CreateAdjustmentWIPEntriesOnFinishProdOrder(ChangeStatusOnProdOrder.SubcGetOrder(), NewPostingDate);
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Status Management", OnAfterTransferRelatedTablesToReleasedProdOrder, '', false, false)]
+ local procedure ReopenWIPEntriesOnAfterTransferRelatedTablesToReleasedProdOrder(ProductionOrder: Record "Production Order")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ReopenWIPEntries(ProductionOrder);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Status Management", OnBeforeChangeStatusOnProdOrder, '', false, false)]
+ local procedure CheckForOpenTransferOrdersOnBeforeChangeStatusOnProdOrder(var ProductionOrder: Record "Production Order"; NewStatus: Option; var IsHandled: Boolean; NewPostingDate: Date; NewUpdateUnitCost: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if NewStatus = "Production Order Status"::Finished.AsInteger() then
+ CheckForOpenTransferOrders(ProductionOrder);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Prod. Order Status Management", OnAfterChangeStatusOnProdOrder, '', false, false)]
+ local procedure UpdateWIPLedgerEntryProdOrderRelationOnAfterChangeStatus(var ProdOrder: Record "Production Order"; var ToProdOrder: Record "Production Order"; NewStatus: Enum "Production Order Status"; NewPostingDate: Date; NewUpdateUnitCost: Boolean; var SuppressCommit: Boolean; xProductionOrder: Record "Production Order")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ UpdateWIPLedgerEntryProdOrderRelation(xProductionOrder, ToProdOrder, NewStatus);
+ end;
+
+ local procedure ReopenWIPEntries(ProductionOrder: Record "Production Order")
+ var
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ SubcontractorWIPLedgerEntry.SetCurrentKey("Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code");
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order No.", ProductionOrder."No.");
+ if not SubcontractorWIPLedgerEntry.IsEmpty() then
+ SubcontractorWIPLedgerEntry.ModifyAll("Prod. Order Status", "Production Order Status"::Released);
+ end;
+
+ local procedure CheckForOpenTransferOrders(var ProductionOrder: Record "Production Order")
+ var
+ TransferLine: Record "Transfer Line";
+ TransferOrderExistsErr: Label 'There is an open transfer order (Transfer Order No.: %1) related to this production order. Please close the transfer order before finishing the production order.',
+Comment = '%1=Transfer Header No';
+ begin
+ TransferLine.SetLoadFields("Document No.");
+ TransferLine.SetCurrentKey("Subc. Prod. Order No.", "Subc. Routing No.", "Subc. Routing Reference No.", "Subc. Operation No.", "Subc. Purch. Order No.");
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Derived From Line No.", 0);
+ if TransferLine.FindFirst() then
+ Error(TransferOrderExistsErr, TransferLine."Document No.");
+ end;
+
+ local procedure UpdateWIPLedgerEntryProdOrderRelation(xProductionOrder: Record "Production Order"; var ToProdOrder: Record "Production Order"; NewStatus: Enum Microsoft.Manufacturing.Document."Production Order Status")
+ var
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ SubcontractorWIPLedgerEntry.SetProductionOrderFilter(xProductionOrder, true);
+ SubcontractorWIPLedgerEntry.ModifyAll("Prod. Order Status", NewStatus);
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order Status", NewStatus);
+ SubcontractorWIPLedgerEntry.ModifyAll("Prod. Order No.", ToProdOrder."No.");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcChangeStatusProdOrder.PageExt.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcChangeStatusProdOrder.PageExt.al
new file mode 100644
index 0000000000..e5dd4bd0de
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcChangeStatusProdOrder.PageExt.al
@@ -0,0 +1,111 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+pageextension 99001544 "Subc.Change Status Prod. Order" extends "Change Status on Prod. Order"
+{
+ layout
+ {
+ addafter("Finish Order without Output")
+ {
+ field("WIP Quantity Clean Up"; WIPQuantityCleanUp)
+ {
+ ApplicationArea = Subcontracting;
+ Enabled = WIPQuantityCleanUpEnabled;
+ Visible = WIPQuantityCleanUpVisible;
+ Caption = 'WIP Quantity Clean Up';
+ ToolTip = 'Specifies whether the WIP quantity on the production order should be set to zero. When enabled, the WIP quantity on the production order will be set to zero. This is used when the production order is finished but there is still WIP quantity that needs to be cleaned up.';
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ WIPQuantityCleanUp := true;
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SetControlProperties();
+ end;
+
+ protected var
+ WIPQuantityCleanUp: Boolean;
+
+ var
+ ProductionOrder: Record "Production Order";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ WIPQuantityCleanUpEnabled, WIPQuantityCleanUpVisible : Boolean;
+
+ procedure ReturnSubWIPQuantityCleanUp(): Boolean
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(false);
+#endif
+ exit(WIPQuantityCleanUp);
+ end;
+
+ procedure SubcSetOrder(var ProductionOrderForStatusChange: Record "Production Order")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ProductionOrder := ProductionOrderForStatusChange;
+ SetControlProperties();
+ end;
+
+ procedure SubcGetOrder() ProductionOrderForStatusChange: Record "Production Order"
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(ProductionOrder);
+#endif
+ ProductionOrderForStatusChange := ProductionOrder;
+ end;
+
+ local procedure SetControlProperties()
+ var
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ if ProductionOrder.Status <> "Production Order Status"::Released then begin
+ WIPQuantityCleanUpEnabled := false;
+ WIPQuantityCleanUpVisible := false;
+ WIPQuantityCleanUp := false;
+ exit;
+ end;
+ SubcontractorWIPLedgerEntry.SetCurrentKey("Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code");
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order No.", ProductionOrder."No.");
+ SubcontractorWIPLedgerEntry.SetRange("Prod. Order Status", "Production Order Status"::Released);
+ WIPQuantityCleanUpEnabled := not SubcontractorWIPLedgerEntry.IsEmpty();
+ WIPQuantityCleanUpVisible := WIPQuantityCleanUpEnabled;
+ if not WIPQuantityCleanUpEnabled then
+ WIPQuantityCleanUp := false;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPostingPreviewBinding.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPostingPreviewBinding.Codeunit.al
new file mode 100644
index 0000000000..2eaf529a7b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPostingPreviewBinding.Codeunit.al
@@ -0,0 +1,56 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.GeneralLedger.Preview;
+
+codeunit 99001565 "Subc. Posting Preview Binding"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Preview", OnAfterBindSubscription, '', true, false)]
+ local procedure BindPostPrevEventHandlerOnAfterBindSubscription()
+ begin
+ TryBindPostingPreviewHandler();
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Preview", OnAfterUnbindSubscription, '', true, false)]
+ local procedure UnbindPostPrecEventHandlerOnAfterUnbindSubscription()
+ begin
+ TryUnbindPostingPreviewHandler();
+ end;
+
+ local procedure TryBindPostingPreviewHandler(): Boolean
+ var
+ SubcPostingPreviewHandler: Codeunit "Subc. Pst. Prev. Event Handler";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcPostingPreviewHandler.DeleteAll();
+ exit(BindSubscription(SubcPostingPreviewHandler));
+ end;
+
+ local procedure TryUnbindPostingPreviewHandler(): Boolean
+ var
+ SubcPostingPreviewHandler: Codeunit "Subc. Pst. Prev. Event Handler";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ exit(UnbindSubscription(SubcPostingPreviewHandler));
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPostingPreviewSubscr.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPostingPreviewSubscr.Codeunit.al
new file mode 100644
index 0000000000..054186997b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPostingPreviewSubscr.Codeunit.al
@@ -0,0 +1,72 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.GeneralLedger.Preview;
+using Microsoft.Foundation.Navigate;
+
+codeunit 99001566 "Subc. Posting Preview Subscr."
+{
+ var
+ TempSubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry" temporary;
+ PostingPreviewEventHandler: Codeunit "Posting Preview Event Handler";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Posting Preview Event Handler", OnGetEntries, '', true, false)]
+ local procedure GetEntriesOnGetEntries(TableNo: Integer; var RecRef: RecordRef)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetAllTables();
+ case TableNo of
+ Database::"Subcontractor WIP Ledger Entry":
+ RecRef.GetTable(TempSubcontractorWIPLedgerEntry);
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Posting Preview Event Handler", OnAfterShowEntries, '', true, false)]
+ local procedure ShowEntriesOnAfterShowEntries(TableNo: Integer)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetAllTables();
+ case TableNo of
+ Database::"Subcontractor WIP Ledger Entry":
+ Page.Run(Page::"Subc. WIP Ledger Entries", TempSubcontractorWIPLedgerEntry);
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Posting Preview Event Handler", OnAfterFillDocumentEntry, '', true, false)]
+ local procedure FillDocumentEntryOnAfterFillDocumentEntry(var DocumentEntry: Record "Document Entry")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ GetAllTables();
+ PostingPreviewEventHandler.InsertDocumentEntry(TempSubcontractorWIPLedgerEntry, DocumentEntry);
+ end;
+
+ local procedure GetAllTables()
+ var
+ SubcPostingPreviewHandler: Codeunit "Subc. Pst. Prev. Event Handler";
+ begin
+ SubcPostingPreviewHandler.GetTempSubcontractorWIPLedgerEntry(TempSubcontractorWIPLedgerEntry);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPstPrevEventHandler.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPstPrevEventHandler.Codeunit.al
new file mode 100644
index 0000000000..204e32c49f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcPstPrevEventHandler.Codeunit.al
@@ -0,0 +1,63 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+codeunit 99001567 "Subc. Pst. Prev. Event Handler"
+{
+ EventSubscriberInstance = Manual;
+ SingleInstance = true;
+
+ var
+ TempSubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry" temporary;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ DocumentMaskTok: Label '***', Locked = true;
+
+ [EventSubscriber(ObjectType::Table, Database::"Subcontractor WIP Ledger Entry", OnAfterInsertEvent, '', false, false)]
+ local procedure OnInsertWIPEntry(var Rec: Record "Subcontractor WIP Ledger Entry")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if Rec.IsTemporary() then
+ exit;
+
+ if TempSubcontractorWIPLedgerEntry.Get(Rec."Entry No.") then
+ exit;
+
+ TempSubcontractorWIPLedgerEntry := Rec;
+ TempSubcontractorWIPLedgerEntry."Document No." := DocumentMaskTok;
+ TempSubcontractorWIPLedgerEntry.Insert();
+ end;
+
+ procedure DeleteAll()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TempSubcontractorWIPLedgerEntry.Reset();
+ TempSubcontractorWIPLedgerEntry.DeleteAll();
+ end;
+
+ procedure GetTempSubcontractorWIPLedgerEntry(var OutTempSubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry" temporary)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ OutTempSubcontractorWIPLedgerEntry.Copy(TempSubcontractorWIPLedgerEntry, true);
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcTransferWIPPosting.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcTransferWIPPosting.Codeunit.al
new file mode 100644
index 0000000000..7e48d610d2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcTransferWIPPosting.Codeunit.al
@@ -0,0 +1,522 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Enums;
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Planning;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Warehouse.Document;
+
+codeunit 99001541 "Subc. Transfer WIP Posting"
+{
+
+ Permissions = TableData "Subcontractor WIP Ledger Entry" = RIMD;
+
+ var
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ WIPLedgEntryNo: Integer;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Header", OnUpdateTransLinesOnAfterUpdateFromDirectTransfer, '', false, false)]
+ local procedure OnUpdateTransLinesOnAfterUpdateFromDirectTransfer(var TransferLine: Record "Transfer Line"; TempTransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TempTransferLine."Transfer WIP Item" then begin
+ TransferLine.Validate("Transfer WIP Item", TempTransferLine."Transfer WIP Item");
+ TransferLine.UpdateDescriptions();
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Header", OnBeforeValidateEvent, "Direct Transfer", false, false)]
+ local procedure UpdateTransferRoutesOnBeforeUpdateTransLines(var Rec: Record "Transfer Header"; var xRec: Record "Transfer Header"; CurrFieldNo: Integer)
+ var
+ TransferRoute: Record "Transfer Route";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not Rec."Direct Transfer" and xRec."Direct Transfer" then
+ TransferRoute.GetTransferRoute(
+ Rec."Transfer-from Code", Rec."Transfer-to Code", Rec."In-Transit Code",
+ Rec."Shipping Agent Code", Rec."Shipping Agent Service Code");
+ end;
+
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeValidateQuantityShipIsBalanced, '', false, false)]
+ local procedure HandleWipTransferOnBeforeValidateQuantityShipIsBalanced(var TransferLine: Record "Transfer Line"; xTransferLine: Record "Transfer Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Transfer WIP Item" then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeValidateQuantityReceiveIsBalanced, '', false, false)]
+ local procedure HandleWipTransferOnBeforeValidateQuantityReceiveIsBalanced(var TransferLine: Record "Transfer Line"; xTransferLine: Record "Transfer Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Transfer WIP Item" then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Shipment", OnBeforeCheckItemInInventory, '', false, false)]
+ local procedure HandleWipTransferOnBeforeCheckItemInInventory(TransferLine: Record "Transfer Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Transfer WIP Item" then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Jnl.-Check Line", OnBeforeCheckEmptyQuantity, '', false, false)]
+ local procedure HandleWipTransferOnBeforeCheckEmptyQuantity(ItemJnlLine: Record "Item Journal Line"; var IsHandled: Boolean)
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if ItemJnlLine."Order Type" <> "Inventory Order Type"::Transfer then
+ exit;
+ TransferLine.SetLoadFields("Transfer WIP Item");
+ if not TransferLine.Get(ItemJnlLine."Order No.", ItemJnlLine."Order Line No.") then
+ exit;
+ if not TransferLine."Transfer WIP Item" then
+ exit;
+ IsHandled := true;
+ end;
+
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Shipment Line", OnAfterCopyFromTransferLine, '', false, false)]
+ local procedure HandleWipTransferShipmentLineOnAfterCopyFromTransferLine(var TransferShipmentLine: Record "Transfer Shipment Line"; TransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferShipmentLine."Transfer WIP Item" := TransferLine."Transfer WIP Item";
+ CreateWIPLedgerEntryForShipment(TransferShipmentLine, TransferLine);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Receipt Line", OnAfterCopyFromTransferLine, '', false, false)]
+ local procedure HandleWipTransferReceiptLineOnAfterCopyFromTransferLine(var TransferReceiptLine: Record "Transfer Receipt Line"; TransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferReceiptLine."Transfer WIP Item" := TransferLine."Transfer WIP Item";
+ CreateWIPLedgerEntryForReceive(TransferReceiptLine, TransferLine);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Direct Trans. Line", OnAfterCopyFromTransferLine, '', false, false)]
+ local procedure HandleWipDirectTransLineOnAfterCopyFromTransferLine(var DirectTransLine: Record "Direct Trans. Line"; TransferLine: Record "Transfer Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ DirectTransLine."Transfer WIP Item" := TransferLine."Transfer WIP Item";
+ CreateWIPLedgerEntryForDirectTransfer(DirectTransLine, TransferLine);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Undo Transfer Shipment", OnNoItemLedgerEntriesCheckIsNeeded, '', false, false)]
+ local procedure HandleWipTransferOnNoItemLedgerEntriesCheckIsNeeded(TransShptLine: Record "Transfer Shipment Line"; var NoCheckNeeded: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransShptLine."Transfer WIP Item" then
+ NoCheckNeeded := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeShowReservation, '', false, false)]
+ local procedure HandleWipTransferOnBeforeShowReservation(var TransferLine: Record "Transfer Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine.TestField("Transfer WIP Item", false);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", OnSetReservSource, '', false, false)]
+ local procedure HandleWipTransferOnSetReservSource(var Sender: Codeunit "Reservation Management"; SourceRecRef: RecordRef; var ReservEntry: Record "Reservation Entry"; Direction: Enum "Transfer Direction"; var RefOrderType: Enum "Requisition Ref. Order Type"; var PlanningLineOrigin: Enum "Planning Line Origin Type"; Positive: Boolean)
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if SourceRecRef.Number = Database::"Transfer Line" then begin
+ SourceRecRef.SetTable(TransferLine);
+ if TransferLine."Transfer WIP Item" then
+ TransferLine.CheckForExistingReservationsOrItemTracking();
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Transfer Line", OnBeforeOpenItemTrackingLines, '', false, false)]
+ local procedure HandleWipTransferOnBeforeOpenItemTrackingLines(var TransferLine: Record "Transfer Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ TransferLine.TestField("Transfer WIP Item", false);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Transfer Warehouse Mgt.", OnBeforeCheckIfTransLine2ReceiptLine, '', false, false)]
+ local procedure HandleWipTransferOnBeforeCheckIfTransLine2ReceiptLine(var TransferLine: Record "Transfer Line"; var IsHandled: Boolean; var ReturnValue: Boolean)
+ var
+ Location: Record Location;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Transfer WIP Item" then begin
+ TransferLine.CalcFields("Whse. Inbnd. Otsdg. Qty");
+ if Location.GetLocationSetup(TransferLine."Transfer-to Code", Location) then
+ if Location."Use As In-Transit" then begin
+ IsHandled := true;
+ ReturnValue := false;
+ exit;
+ end;
+ IsHandled := true;
+ ReturnValue := (TransferLine."Qty. in Transit" > TransferLine."Whse. Inbnd. Otsdg. Qty");
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Transfer Warehouse Mgt.", OnTransLine2ReceiptLineOnAfterInitNewLine, '', false, false)]
+ local procedure HandleWipTransferOnTransLine2ReceiptLineOnAfterInitNewLine(var WarehouseReceiptLine: Record "Warehouse Receipt Line"; WarehouseReceiptHeader: Record "Warehouse Receipt Header"; TransferLine: Record "Transfer Line"; var QtyOnRcptLineSet: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ WarehouseReceiptLine."Transfer WIP Item" := TransferLine."Transfer WIP Item";
+ if WarehouseReceiptLine."Transfer WIP Item" then begin
+ WarehouseReceiptLine.Validate(WarehouseReceiptLine."Qty. Received", TransferLine."Quantity Received");
+ TransferLine.CalcFields("Whse. Inbnd. Otsdg. Qty");
+ WarehouseReceiptLine.Quantity := TransferLine."Quantity Received" + TransferLine."Qty. in Transit" - TransferLine."Whse. Inbnd. Otsdg. Qty";
+ WarehouseReceiptLine."Qty. (Base)" := 0;
+ WarehouseReceiptLine.InitOutstandingQtys();
+ QtyOnRcptLineSet := true;
+ end;
+ end;
+
+ local procedure CreateWIPLedgerEntryForShipment(var TransferShipmentLine: Record "Transfer Shipment Line"; TransferLine: Record "Transfer Line")
+ begin
+ if not TransferShipmentLine."Transfer WIP Item" then
+ exit;
+
+ if IsUsedAsSubcontractingLocation(TransferLine."Transfer-from Code") then
+ CreateAndInsertWIPLedgerEntry(
+ TransferLine,
+ "WIP Ledger Entry Type"::"Negative Adjustment",
+ TransferLine."Transfer-from Code",
+ false,
+ true,
+ -TransferLine.CalcBaseQty(TransferShipmentLine.Quantity),
+ TransferShipmentLine."Document No.",
+ TransferShipmentLine."Line No.");
+
+ if TransferLine."In-Transit Code" <> '' then
+ CreateAndInsertWIPLedgerEntry(
+ TransferLine,
+ "WIP Ledger Entry Type"::"Positive Adjustment",
+ TransferLine."In-Transit Code",
+ true,
+ true,
+ TransferLine.CalcBaseQty(TransferShipmentLine.Quantity),
+ TransferShipmentLine."Document No.",
+ TransferShipmentLine."Line No.");
+ end;
+
+ local procedure CreateWIPLedgerEntryForReceive(var TransferReceiptLine: Record "Transfer Receipt Line"; TransferLine: Record "Transfer Line")
+ begin
+ if not TransferReceiptLine."Transfer WIP Item" then
+ exit;
+
+ if TransferLine."In-Transit Code" <> '' then
+ CreateAndInsertWIPLedgerEntry(
+ TransferLine,
+ "WIP Ledger Entry Type"::"Negative Adjustment",
+ TransferLine."In-Transit Code",
+ true,
+ false,
+ -TransferLine.CalcBaseQty(TransferReceiptLine.Quantity),
+ TransferReceiptLine."Document No.",
+ TransferReceiptLine."Line No.");
+
+ if IsUsedAsSubcontractingLocation(TransferLine."Transfer-to Code") then
+ CreateAndInsertWIPLedgerEntry(
+ TransferLine,
+ "WIP Ledger Entry Type"::"Positive Adjustment",
+ TransferLine."Transfer-to Code",
+ false,
+ false,
+ TransferLine.CalcBaseQty(TransferReceiptLine.Quantity),
+ TransferReceiptLine."Document No.",
+ TransferReceiptLine."Line No.");
+ end;
+
+ local procedure CreateWIPLedgerEntryForDirectTransfer(var DirectTransLine: Record "Direct Trans. Line"; TransferLine: Record "Transfer Line")
+ begin
+ if not DirectTransLine."Transfer WIP Item" then
+ exit;
+
+ if IsUsedAsSubcontractingLocation(TransferLine."Transfer-from Code") then
+ CreateAndInsertWIPLedgerEntry(
+ TransferLine,
+ "WIP Ledger Entry Type"::"Negative Adjustment",
+ TransferLine."Transfer-from Code",
+ false,
+ true,
+ -TransferLine.CalcBaseQty(DirectTransLine.Quantity),
+ DirectTransLine."Document No.",
+ DirectTransLine."Line No.");
+
+ if IsUsedAsSubcontractingLocation(TransferLine."Transfer-to Code") then
+ CreateAndInsertWIPLedgerEntry(
+ TransferLine,
+ "WIP Ledger Entry Type"::"Positive Adjustment",
+ TransferLine."Transfer-to Code",
+ false,
+ false,
+ TransferLine.CalcBaseQty(DirectTransLine.Quantity),
+ DirectTransLine."Document No.",
+ DirectTransLine."Line No.");
+ end;
+
+ local procedure CreateAndInsertWIPLedgerEntry(var TransferLine: Record "Transfer Line"; EntryType: Enum "WIP Ledger Entry Type"; LocationCode: Code[10]; InTransit: Boolean; IsShipment: Boolean; QuantityBase: Decimal; DocumentNo: Code[20]; DocumentLineNo: Integer)
+ var
+ TransferHeader: Record "Transfer Header";
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ TransferHeader.SetLoadFields("Posting Date");
+ TransferHeader.Get(TransferLine."Document No.");
+
+ InitWIPItemLedgerEntry(SubcontractorWIPLedgerEntry, TransferHeader."Posting Date");
+ SubcontractorWIPLedgerEntry."Entry Type" := EntryType;
+ SubcontractorWIPLedgerEntry."Location Code" := LocationCode;
+ SubcontractorWIPLedgerEntry."In Transit" := InTransit;
+ AssignFieldsFromTransferLine(SubcontractorWIPLedgerEntry, TransferLine, IsShipment);
+ SubcontractorWIPLedgerEntry."Quantity (Base)" := QuantityBase;
+ AssignSourceDocument(SubcontractorWIPLedgerEntry, "WIP Document Type"::"Transfer Order", DocumentNo, DocumentLineNo);
+ InsertWIPItemLedgerEntry(SubcontractorWIPLedgerEntry);
+ end;
+
+ internal procedure CreateAdjustmentWIPEntriesOnFinishProdOrder(ProductionOrder: Record "Production Order"; PostingDate: Date)
+ var
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcontractorWIPLedgerEntry.SetProductionOrderFilter(ProductionOrder, false);
+ SearchForAllWIPLedgerEntryCombinationAndCreateAdjustmentEntryToBalanceTheQuantities(SubcontractorWIPLedgerEntry, PostingDate);
+ end;
+
+ local procedure SearchForAllWIPLedgerEntryCombinationAndCreateAdjustmentEntryToBalanceTheQuantities(var FilteredWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; PostingDate: Date)
+ var
+ LastWIPEntry: Record "Subcontractor WIP Ledger Entry";
+ IsFirstEntry: Boolean;
+ TotalQty: Decimal;
+ begin
+ IsFirstEntry := true;
+ TotalQty := 0;
+
+ FilteredWIPLedgerEntry.SetCurrentKey("Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code", "Item No.", "Variant Code");
+ if not FilteredWIPLedgerEntry.FindSet() then
+ exit;
+
+ repeat
+ if not IsFirstEntry then
+ if (FilteredWIPLedgerEntry."Prod. Order Line No." <> LastWIPEntry."Prod. Order Line No.") or
+ (FilteredWIPLedgerEntry."Routing Reference No." <> LastWIPEntry."Routing Reference No.") or
+ (FilteredWIPLedgerEntry."Routing No." <> LastWIPEntry."Routing No.") or
+ (FilteredWIPLedgerEntry."Operation No." <> LastWIPEntry."Operation No.") or
+ (FilteredWIPLedgerEntry."Location Code" <> LastWIPEntry."Location Code") or
+ (FilteredWIPLedgerEntry."Item No." <> LastWIPEntry."Item No.") or
+ (FilteredWIPLedgerEntry."Variant Code" <> LastWIPEntry."Variant Code")
+ then begin
+ if TotalQty <> 0 then
+ CreateAdjustmentWIPEntry(LastWIPEntry, PostingDate, TotalQty);
+ TotalQty := 0;
+ end;
+
+ TotalQty += FilteredWIPLedgerEntry."Quantity (Base)";
+ LastWIPEntry := FilteredWIPLedgerEntry;
+ IsFirstEntry := false;
+ until FilteredWIPLedgerEntry.Next() = 0;
+
+ if TotalQty <> 0 then
+ CreateAdjustmentWIPEntry(LastWIPEntry, PostingDate, TotalQty);
+ end;
+
+ local procedure CreateAdjustmentWIPEntry(TemplateWIPEntry: Record "Subcontractor WIP Ledger Entry"; PostingDate: Date; TotalQty: Decimal)
+ var
+ AdjustmentEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ InitWIPItemLedgerEntry(AdjustmentEntry, PostingDate);
+ AdjustmentEntry."Item No." := TemplateWIPEntry."Item No.";
+ AdjustmentEntry."Variant Code" := TemplateWIPEntry."Variant Code";
+ AdjustmentEntry."Base Unit of Measure" := TemplateWIPEntry."Base Unit of Measure";
+ AdjustmentEntry."Location Code" := TemplateWIPEntry."Location Code";
+ AdjustmentEntry."Prod. Order Status" := TemplateWIPEntry."Prod. Order Status";
+ AdjustmentEntry."Prod. Order No." := TemplateWIPEntry."Prod. Order No.";
+ AdjustmentEntry."Prod. Order Line No." := TemplateWIPEntry."Prod. Order Line No.";
+ AdjustmentEntry."Routing No." := TemplateWIPEntry."Routing No.";
+ AdjustmentEntry."Routing Reference No." := TemplateWIPEntry."Routing Reference No.";
+ AdjustmentEntry."Operation No." := TemplateWIPEntry."Operation No.";
+ AdjustmentEntry."Work Center No." := TemplateWIPEntry."Work Center No.";
+ AdjustmentEntry.Description := TemplateWIPEntry.Description;
+ AdjustmentEntry."Description 2" := TemplateWIPEntry."Description 2";
+ AdjustmentEntry."In Transit" := TemplateWIPEntry."In Transit";
+ AdjustmentEntry."Quantity (Base)" := -TotalQty;
+ if TotalQty > 0 then
+ AdjustmentEntry."Entry Type" := "WIP Ledger Entry Type"::"Negative Adjustment"
+ else
+ AdjustmentEntry."Entry Type" := "WIP Ledger Entry Type"::"Positive Adjustment";
+ AdjustmentEntry."Document Type" := "WIP Document Type"::"Adjustment (Finish Prod Order)";
+ AdjustmentEntry."Document No." := TemplateWIPEntry."Prod. Order No.";
+ InsertWIPItemLedgerEntry(AdjustmentEntry);
+ end;
+
+ [InherentPermissions(PermissionObjectType::TableData, Database::"Subcontractor WIP Ledger Entry", 'r')]
+ local procedure ValidateSequenceNo(LedgEntryNo: Integer; xLedgEntryNo: Integer; TableNo: Integer)
+ var
+ SequenceNoMgt: Codeunit "Sequence No. Mgt.";
+ begin
+ if LedgEntryNo = xLedgEntryNo then
+ exit;
+ SequenceNoMgt.ValidateSeqNo(TableNo);
+ end;
+
+ local procedure InitWIPItemLedgerEntry(var SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; PostingDate: Date)
+ begin
+ SubcontractorWIPLedgerEntry.Init();
+ SubcontractorWIPLedgerEntry."Posting Date" := PostingDate;
+ end;
+
+ local procedure AssignFieldsFromTransferLine(var SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; var TransferLine: Record "Transfer Line"; IsShipment: Boolean)
+ var
+ Item: Record Item;
+ begin
+ SubcontractorWIPLedgerEntry."Item No." := TransferLine."Item No.";
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get(TransferLine."Item No.");
+ SubcontractorWIPLedgerEntry."Base Unit of Measure" := Item."Base Unit of Measure";
+ SubcontractorWIPLedgerEntry."Prod. Order Status" := "Production Order Status"::Released;
+ SubcontractorWIPLedgerEntry."Variant Code" := TransferLine."Variant Code";
+ SubcontractorWIPLedgerEntry."Prod. Order No." := TransferLine."Subc. Prod. Order No.";
+ SubcontractorWIPLedgerEntry."Prod. Order Line No." := TransferLine."Subc. Prod. Order Line No.";
+ SubcontractorWIPLedgerEntry."Routing No." := TransferLine."Subc. Routing No.";
+ SubcontractorWIPLedgerEntry."Routing Reference No." := TransferLine."Subc. Routing Reference No.";
+ SubcontractorWIPLedgerEntry."Operation No." := TransferLine."Subc. Operation No.";
+ if IsShipment and not (SubcontractorWIPLedgerEntry."In Transit") then
+ if TransferLine."Prev. Operation No." <> '' then
+ SubcontractorWIPLedgerEntry."Operation No." := TransferLine."Prev. Operation No.";
+ SubcontractorWIPLedgerEntry."Work Center No." := TransferLine."Subc. Work Center No.";
+ SubcontractorWIPLedgerEntry.Description := TransferLine.Description;
+ SubcontractorWIPLedgerEntry."Description 2" := TransferLine."Description 2";
+ end;
+
+ local procedure AssignSourceDocument(var SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; WIPDocumentType: Enum "WIP Document Type"; DocumentNo: Code[20]; DocumentLineNo: Integer)
+ begin
+ SubcontractorWIPLedgerEntry."Document Type" := WIPDocumentType;
+ SubcontractorWIPLedgerEntry."Document No." := DocumentNo;
+ SubcontractorWIPLedgerEntry."Document Line No." := DocumentLineNo;
+ end;
+
+ local procedure InsertWIPItemLedgerEntry(var SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry")
+ var
+ xWIPLedgEntryNo: Integer;
+ begin
+ if SubcontractorWIPLedgerEntry."Quantity (Base)" = 0 then
+ exit;
+
+ WIPLedgEntryNo := SubcontractorWIPLedgerEntry.GetNextEntryNo();
+ SubcontractorWIPLedgerEntry."Entry No." := WIPLedgEntryNo;
+
+ xWIPLedgEntryNo := WIPLedgEntryNo;
+ OnBeforeInsertWIPLedgerEntry(SubcontractorWIPLedgerEntry, WIPLedgEntryNo);
+ ValidateSequenceNo(WIPLedgEntryNo, xWIPLedgEntryNo, Database::"Subcontractor WIP Ledger Entry");
+ SubcontractorWIPLedgerEntry.Insert();
+ end;
+
+ local procedure IsUsedAsSubcontractingLocation(LocationCode: Code[10]): Boolean
+ var
+ Vendor: Record Vendor;
+ begin
+ Vendor.SetCurrentKey("Subc. Location Code");
+ Vendor.SetRange("Subc. Location Code", LocationCode);
+ exit(not Vendor.IsEmpty());
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeInsertWIPLedgerEntry(var SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; var WIPLedgEntryNo: Integer)
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPAdjustment.Page.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPAdjustment.Page.al
new file mode 100644
index 0000000000..9f0b8a83e6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPAdjustment.Page.al
@@ -0,0 +1,440 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Manufacturing.Document;
+
+page 99001561 "Subc. WIP Adjustment"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'WIP Adjustment';
+ PageType = StandardDialog;
+ SourceTable = "Subcontractor WIP Ledger Entry";
+ SourceTableTemporary = true;
+ DeleteAllowed = false;
+ InsertAllowed = false;
+ DataCaptionExpression = CreatePageCaption();
+ UsageCategory = None;
+
+ layout
+ {
+ area(Content)
+ {
+ group(Adjustment)
+ {
+ Caption = 'Adjustment';
+ field("Document Type"; DocumentType)
+ {
+ Caption = 'Document Type';
+ ToolTip = 'Specifies the document type applied to all created adjustment entries.';
+ Editable = false;
+ }
+ field("Document No."; DocumentNo)
+ {
+ Caption = 'Document No.';
+ ToolTip = 'Specifies the document number applied to all created adjustment entries.';
+ }
+ }
+ group("Production Order")
+ {
+ Caption = 'Production Order';
+ Visible = LineCount = 1;
+ field("Prod. Order Status"; Rec."Prod. Order Status")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field("Prod. Order No."; Rec."Prod. Order No.")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field("Prod. Order Line No."; Rec."Prod. Order Line No.")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field("Routing No."; Rec."Routing No.")
+ {
+ Editable = false;
+ }
+ field("Routing Reference No."; Rec."Routing Reference No.")
+ {
+ Editable = false;
+ }
+ field("Operation No."; Rec."Operation No.")
+ {
+ Editable = false;
+ }
+ field("Work Center No."; Rec."Work Center No.")
+ {
+ Editable = false;
+ }
+ }
+ group(General)
+ {
+ Caption = 'General';
+ Visible = LineCount = 1;
+ field("Location Code"; Rec."Location Code")
+ {
+ Editable = false;
+ }
+ field("Item No."; Rec."Item No.")
+ {
+ Editable = false;
+ }
+ field("Variant Code"; Rec."Variant Code")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field(Description; Rec.Description)
+ {
+ }
+ field("Description 2"; Rec."Description 2")
+ {
+ }
+ field("Current Quantity (Base)"; Rec."Quantity (Base)")
+ {
+ Caption = 'Current Quantity (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ ToolTip = 'Specifies the current WIP quantity base for this operation and location.';
+ }
+ field("New Quantity (Base)"; NewQuantityBase)
+ {
+ AutoFormatType = 0;
+ Caption = 'New Quantity (Base)';
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the new target WIP quantity base after adjustment.';
+
+ trigger OnValidate()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ValidateNewQuantity(NewQuantityBase);
+ NewQuantities.Set(Rec."Entry No.", NewQuantityBase);
+ UpdateQuantityStyle();
+ end;
+ }
+ field("Quantity to Adjust (Base)"; QuantityToAdjustBase)
+ {
+ AutoFormatType = 0;
+ Caption = 'Quantity to Adjust (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ StyleExpr = QuantityStyle;
+ ToolTip = 'Specifies the quantity that will be adjusted (New Quantity (Base) minus Current Quantity (Base)).';
+ }
+ field("Base Unit of Measure"; Rec."Base Unit of Measure")
+ {
+ Editable = false;
+ Caption = 'Base Unit of Measure';
+ }
+ }
+ repeater(Lines)
+ {
+ Caption = 'Lines';
+ Visible = LineCount > 1;
+ field("Prod. Order Status Line"; Rec."Prod. Order Status")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field("Prod. Order No. Line"; Rec."Prod. Order No.")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field("Prod. Order Line No. Line"; Rec."Prod. Order Line No.")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field("Routing No. Line"; Rec."Routing No.")
+ {
+ Editable = false;
+ }
+ field("Routing Reference No. Line"; Rec."Routing Reference No.")
+ {
+ Editable = false;
+ }
+ field("Operation No. Line"; Rec."Operation No.")
+ {
+ Editable = false;
+ }
+ field("Work Center No. Line"; Rec."Work Center No.")
+ {
+ Editable = false;
+ }
+ field("Item No. Line"; Rec."Item No.")
+ {
+ Editable = false;
+ }
+ field("Variant Code Line"; Rec."Variant Code")
+ {
+ Editable = false;
+ Visible = false;
+ }
+ field(DescriptionLine; Rec.Description)
+ {
+ }
+ field("Description 2 Line"; Rec."Description 2")
+ {
+ Visible = false;
+ }
+ field("Location Code Line"; Rec."Location Code")
+ {
+ Caption = 'Location Code';
+ Editable = false;
+ }
+ field("Current Quantity Line"; Rec."Quantity (Base)")
+ {
+ Caption = 'Current Quantity (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ }
+ field("New Quantity Line"; NewQuantityBase)
+ {
+ AutoFormatType = 0;
+ Caption = 'New Quantity (Base)';
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the new target WIP quantity after adjustment.';
+
+ trigger OnValidate()
+ begin
+ ;
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ValidateNewQuantity(NewQuantityBase);
+ NewQuantities.Set(Rec."Entry No.", NewQuantityBase);
+ UpdateQuantityStyle();
+ end;
+ }
+ field("Quantity to Adjust Line"; QuantityToAdjustBase)
+ {
+ AutoFormatType = 0;
+ Caption = 'Qty. to Adjust (Base)';
+ DecimalPlaces = 0 : 5;
+ Editable = false;
+ StyleExpr = QuantityStyle;
+ ToolTip = 'Specifies the quantity that will be adjusted (New Quantity minus Current Quantity).';
+ }
+ field("Base Unit of Measure Line"; Rec."Base Unit of Measure")
+ {
+ Caption = 'Base Unit of Measure';
+ Editable = false;
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ NewQuantities.Get(Rec."Entry No.", NewQuantityBase);
+ UpdateQuantityStyle();
+ end;
+
+ trigger OnOpenPage()
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PostingDate := WorkDate();
+ DocumentType := DocumentType::"Adjustment (Manual)";
+
+ if not Rec.FindFirst() then
+ Error(NothingToAdjustErr);
+ end;
+
+ trigger OnQueryClosePage(CloseAction: Action): Boolean
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(true);
+#endif
+ if CloseAction in [ACTION::OK, ACTION::LookupOK] then
+ CreateAdjustmentEntries();
+ exit(true);
+ end;
+
+ var
+ Item: Record Item;
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ NewQuantities: Dictionary of [Integer, Decimal];
+ PostingDate: Date;
+ DocumentType: Enum "WIP Document Type";
+ DocumentNo: Code[20];
+ NewQuantityBase: Decimal;
+ QuantityToAdjustBase: Decimal;
+ QuantityStyle: Text;
+ LineCount: Integer;
+ CaptionLbl: Label 'Production Order %1 %2', Comment = '%1=Prod. Order Status,%2=Prod. Order Number';
+ NothingToAdjustErr: Label 'There are no WIP quantities to adjust, because there are no existing ledger entries for the specified source.';
+ NewQuantityMustNotBeNegativeErr: Label 'New Quantity (Base) must not be negative.';
+ NewQuantityExceedsProdOrderQtyErr: Label 'New Quantity (Base) must not exceed the production order line quantity of %1.', Comment = '%1=Production order line quantity (base)';
+
+ ///
+ /// Populates the page source table with one row per (Routing Reference No., Operation No., Location Code)
+ /// combination, aggregating the current WIP quantity from the supplied ledger entries.
+ /// Must be called before running the page.
+ ///
+ procedure SetWIPLedgerEntry(var WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry")
+ var
+ EntrySeq: Integer;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ EntrySeq := 1;
+
+ if not Rec.IsEmpty() then
+ Rec.DeleteAll();
+
+ if not WIPLedgerEntry.FindSet() then
+ Error(NothingToAdjustErr);
+
+ repeat
+ Rec.SetRange("Prod. Order Status", WIPLedgerEntry."Prod. Order Status");
+ Rec.SetRange("Prod. Order No.", WIPLedgerEntry."Prod. Order No.");
+ Rec.SetRange("Prod. Order Line No.", WIPLedgerEntry."Prod. Order Line No.");
+ Rec.SetRange("Routing Reference No.", WIPLedgerEntry."Routing Reference No.");
+ Rec.SetRange("Routing No.", WIPLedgerEntry."Routing No.");
+ Rec.SetRange("Operation No.", WIPLedgerEntry."Operation No.");
+ Rec.SetRange("Location Code", WIPLedgerEntry."Location Code");
+ Rec.SetRange("In Transit", WIPLedgerEntry."In Transit");
+ Rec.SetRange("Item No.", WIPLedgerEntry."Item No.");
+ Rec.SetRange("Variant Code", WIPLedgerEntry."Variant Code");
+ if Rec.FindFirst() then begin
+ Rec."Quantity (Base)" += WIPLedgerEntry."Quantity (Base)";
+ Rec.Modify();
+ NewQuantities.Set(Rec."Entry No.", Rec."Quantity (Base)");
+ end else begin
+ Rec.Init();
+ Rec.TransferFields(WIPLedgerEntry);
+ Rec."Entry No." := EntrySeq;
+ Rec."Document Line No." := 0;
+ Rec."In Transit" := WIPLedgerEntry."In Transit";
+ Rec."Quantity (Base)" := WIPLedgerEntry."Quantity (Base)";
+ Rec."Base Unit of Measure" := GetItemBaseUnitOfMeasure(WIPLedgerEntry."Item No.");
+ Rec.Insert();
+ NewQuantities.Add(Rec."Entry No.", Rec."Quantity (Base)");
+ EntrySeq += 1;
+ end;
+ until WIPLedgerEntry.Next() = 0;
+
+ Rec.SetRange("Location Code");
+ Rec.SetRange("In Transit");
+ Rec.SetRange("Item No.");
+ Rec.SetRange("Variant Code");
+ Rec.SetRange("Prod. Order Line No.");
+
+ LineCount := Rec.Count();
+ if Rec.FindFirst() then;
+ end;
+
+ procedure SetDocumentNo(DocNo: Code[20])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ DocumentNo := DocNo;
+ end;
+
+ local procedure CreateAdjustmentEntries()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TempWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry" temporary;
+ AdjEntryType: Enum "WIP Ledger Entry Type";
+ TargetQty: Decimal;
+ begin
+ TempWIPLedgerEntry.Copy(Rec, true);
+
+ TempWIPLedgerEntry.FindSet();
+
+ repeat
+ NewQuantities.Get(TempWIPLedgerEntry."Entry No.", TargetQty);
+ if TargetQty <> TempWIPLedgerEntry."Quantity (Base)" then begin
+ WIPLedgerEntry.Init();
+ WIPLedgerEntry.TransferFields(TempWIPLedgerEntry);
+ WIPLedgerEntry."Entry No." := WIPLedgerEntry.GetNextEntryNo();
+ WIPLedgerEntry."Posting Date" := PostingDate;
+ WIPLedgerEntry."Document Type" := DocumentType;
+ WIPLedgerEntry."Document No." := DocumentNo;
+
+ WIPLedgerEntry."Quantity (Base)" := TargetQty - TempWIPLedgerEntry."Quantity (Base)";
+ if WIPLedgerEntry."Quantity (Base)" >= 0 then
+ WIPLedgerEntry."Entry Type" := AdjEntryType::"Positive Adjustment"
+ else
+ WIPLedgerEntry."Entry Type" := AdjEntryType::"Negative Adjustment";
+ WIPLedgerEntry.Insert(true);
+ end;
+ until TempWIPLedgerEntry.Next() = 0;
+ end;
+
+ local procedure UpdateQuantityStyle()
+ begin
+ QuantityToAdjustBase := NewQuantityBase - Rec."Quantity (Base)";
+ if QuantityToAdjustBase >= 0 then
+ QuantityStyle := Format(PageStyle::Strong)
+ else
+ QuantityStyle := Format(PageStyle::Unfavorable);
+ end;
+
+ local procedure CreatePageCaption(): Text
+ begin
+ exit(StrSubstNo(CaptionLbl, Rec."Prod. Order Status", Rec."Prod. Order No."));
+ end;
+
+ local procedure GetItemBaseUnitOfMeasure(ItemNo: Code[20]): Code[10]
+ begin
+ Item.SetLoadFields("Base Unit of Measure");
+ if ItemNo <> Item."No." then
+ Item.Get(ItemNo);
+ exit(Item."Base Unit of Measure");
+ end;
+
+ local procedure ValidateNewQuantity(NewQty: Decimal)
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ if NewQty < 0 then
+ Error(NewQuantityMustNotBeNegativeErr);
+ ProdOrderLine.SetLoadFields("Quantity (Base)");
+ ProdOrderLine.Get(Rec."Prod. Order Status", Rec."Prod. Order No.", Rec."Prod. Order Line No.");
+ if NewQty > ProdOrderLine."Quantity (Base)" then
+ Error(NewQuantityExceedsProdOrderQtyErr, ProdOrderLine."Quantity (Base)");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPItemLedgFindEntry.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPItemLedgFindEntry.Codeunit.al
new file mode 100644
index 0000000000..599d4d032b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPItemLedgFindEntry.Codeunit.al
@@ -0,0 +1,66 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.Navigate;
+
+codeunit 99001564 "Subc. WIP Item Ledg Find Entry"
+{
+
+ var
+ [SecurityFiltering(SecurityFilter::Filtered)]
+ SubcontractorWIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+
+ [EventSubscriber(ObjectType::Page, Page::Navigate, OnAfterFindLedgerEntries, '', false, false)]
+ local procedure OnFindWIPLedgerEntries(var DocumentEntry: Record "Document Entry"; DocNoFilter: Text; PostingDateFilter: Text)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ FindWIPItemEntries(DocumentEntry, DocNoFilter, PostingDateFilter);
+ end;
+
+ local procedure FindWIPItemEntries(var DocumentEntry: Record "Document Entry"; DocNoFilter: Text; PostingDateFilter: Text)
+ begin
+ if (DocNoFilter = '') and (PostingDateFilter = '') then
+ exit;
+ if SubcontractorWIPLedgerEntry.ReadPermission() then begin
+ FilterWIPLedgerEntries(DocNoFilter, PostingDateFilter);
+ DocumentEntry.InsertIntoDocEntry(Database::"Subcontractor WIP Ledger Entry", SubcontractorWIPLedgerEntry.TableCaption(), SubcontractorWIPLedgerEntry.Count);
+ end;
+ end;
+
+ local procedure FilterWIPLedgerEntries(DocNoFilter: Text; PostingDateFilter: Text)
+ begin
+ SubcontractorWIPLedgerEntry.Reset();
+ SubcontractorWIPLedgerEntry.SetCurrentKey("Document No.", "Posting Date");
+ SubcontractorWIPLedgerEntry.SetFilter("Document No.", DocNoFilter);
+ SubcontractorWIPLedgerEntry.SetFilter("Posting Date", PostingDateFilter);
+ end;
+
+ [EventSubscriber(ObjectType::Page, Page::Navigate, OnAfterShowRecords, '', false, false)]
+ local procedure OnShowWIPLedgerEntries(var Sender: Page Navigate; var DocumentEntry: Record "Document Entry"; DocNoFilter: Text; PostingDateFilter: Text; ItemTrackingSearch: Boolean; ContactType: Enum "Navigate Contact Type"; ContactNo: Code[250]; ExtDocNo: Code[250])
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if DocumentEntry."Table ID" = Database::"Subcontractor WIP Ledger Entry" then begin
+ FilterWIPLedgerEntries(DocNoFilter, PostingDateFilter);
+ Page.Run(0, SubcontractorWIPLedgerEntry);
+ end;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPLedgerEntries.Page.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPLedgerEntries.Page.al
new file mode 100644
index 0000000000..5e9e984d89
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcWIPLedgerEntries.Page.al
@@ -0,0 +1,153 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+
+page 99001560 "Subc. WIP Ledger Entries"
+{
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting WIP Entries';
+ Editable = false;
+ PageType = List;
+ SourceTable = "Subcontractor WIP Ledger Entry";
+ UsageCategory = History;
+ SaveValues = false;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Group)
+ {
+ field("Posting Date"; Rec."Posting Date")
+ {
+ }
+ field("Entry Type"; Rec."Entry Type")
+ {
+ }
+ field("Document Type"; Rec."Document Type")
+ {
+ }
+ field("Document No."; Rec."Document No.")
+ {
+ }
+ field("Document Line No."; Rec."Document Line No.")
+ {
+ }
+ field("Item No."; Rec."Item No.")
+ {
+ }
+ field("Variant Code"; Rec."Variant Code")
+ {
+ Visible = false;
+ }
+ field(Description; Rec.Description)
+ {
+ }
+ field("Description 2"; Rec."Description 2")
+ {
+ Visible = false;
+ }
+ field("Location Code"; Rec."Location Code")
+ {
+ }
+ field("Quantity (Base)"; Rec."Quantity (Base)")
+ {
+ }
+ field("Base Unit of Measure"; Rec."Base Unit of Measure")
+ {
+ }
+ field("Prod. Order Status"; Rec."Prod. Order Status")
+ {
+ }
+ field("Prod. Order No."; Rec."Prod. Order No.")
+ {
+ }
+ field("Prod. Order Line No."; Rec."Prod. Order Line No.")
+ {
+ }
+ field("Routing No."; Rec."Routing No.")
+ {
+ }
+ field("Routing Reference No."; Rec."Routing Reference No.")
+ {
+ }
+ field("Operation No."; Rec."Operation No.")
+ {
+ }
+ field("Work Center No."; Rec."Work Center No.")
+ {
+ }
+ field("Entry No."; Rec."Entry No.")
+ {
+ }
+ field("In Transit"; Rec."In Transit")
+ {
+ Visible = false;
+ }
+ }
+ }
+ area(factboxes)
+ {
+ systempart(Links; Links)
+ {
+ ApplicationArea = RecordLinks;
+ Visible = false;
+ }
+ systempart(Notes; Notes)
+ {
+ ApplicationArea = Notes;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ area(Processing)
+ {
+ action("WIP Adjustment")
+ {
+ Caption = 'WIP Adjustment';
+ Image = AdjustEntries;
+ ToolTip = 'Manually adjust the WIP quantity for the selected WIP ledger entry.';
+ Enabled = WIPAdjustmentEnabled;
+ Visible = WIPAdjustmentVisible;
+
+ trigger OnAction()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ begin
+ WIPLedgerEntry := Rec;
+ WIPLedgerEntry.SetRecFilter();
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.SetDocumentNo(Rec."Document No.");
+ WIPAdjustmentPage.RunModal();
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ actionref(WipAdjustment_Promoted; "WIP Adjustment") { }
+ }
+ }
+
+ var
+ WIPAdjustmentEnabled, WIPAdjustmentVisible : Boolean;
+
+ trigger OnOpenPage()
+ begin
+ WIPAdjustmentVisible := not Rec.IsTemporary();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ var
+ ProductionOrder: Record "Production Order";
+ begin
+ ProductionOrder.SetLoadFields(SystemId);
+ WIPAdjustmentEnabled := ProductionOrder.Get(Rec."Prod. Order Status", Rec."Prod. Order No.");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcontractorWIPLedgerEntry.Table.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcontractorWIPLedgerEntry.Table.al
new file mode 100644
index 0000000000..98f888fe89
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/SubcontractorWIPLedgerEntry.Table.al
@@ -0,0 +1,249 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.WorkCenter;
+
+table 99001560 "Subcontractor WIP Ledger Entry"
+{
+ AllowInCustomizations = AsReadOnly;
+ Caption = 'Subcontractor WIP Ledger Entry';
+ DataClassification = CustomerContent;
+ DrillDownPageId = "Subc. WIP Ledger Entries";
+ LookupPageId = "Subc. WIP Ledger Entries";
+
+ fields
+ {
+ field(1; "Entry No."; Integer)
+ {
+ Caption = 'Entry No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the number of the Subcontractor WIP Ledger Entry.';
+ }
+ field(2; "Item No."; Code[20])
+ {
+ Caption = 'Item No.';
+ DataClassification = CustomerContent;
+ TableRelation = Item;
+ ToolTip = 'Specifies the item number.';
+ }
+ field(3; "Variant Code"; Code[10])
+ {
+ Caption = 'Variant Code';
+ DataClassification = CustomerContent;
+ TableRelation = "Item Variant".Code where("Item No." = field("Item No."));
+ ToolTip = 'Specifies the variant code.';
+ }
+ field(4; "Location Code"; Code[10])
+ {
+ Caption = 'Location Code';
+ DataClassification = CustomerContent;
+ TableRelation = Location;
+ ToolTip = 'Specifies the location where the WIP quantity is tracked.';
+ }
+ field(5; "Posting Date"; Date)
+ {
+ Caption = 'Posting Date';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the date when the WIP ledger entry was posted.';
+ }
+ field(6; "Entry Type"; Enum "WIP Ledger Entry Type")
+ {
+ Caption = 'Entry Type';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies which type of transaction that the entry is created from.';
+ }
+ field(7; "Quantity (Base)"; Decimal)
+ {
+ AutoFormatType = 0;
+ Caption = 'Quantity (Base)';
+ DataClassification = CustomerContent;
+ DecimalPlaces = 0 : 5;
+ ToolTip = 'Specifies the WIP quantity in base unit of measure';
+ }
+ field(8; "Base Unit of Measure"; Code[10])
+ {
+ Caption = 'Base Unit of Measure';
+ DataClassification = CustomerContent;
+ TableRelation = "Item Unit of Measure".Code where("Item No." = field("Item No."));
+ ToolTip = 'Specifies the base unit of measure of the item.';
+ }
+ field(9; "Document Type"; Enum "WIP Document Type")
+ {
+ Caption = 'Document Type';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the document type that created this entry.';
+ }
+ field(10; "Document No."; Code[20])
+ {
+ Caption = 'Document No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the document number.';
+ }
+ field(11; "Document Line No."; Integer)
+ {
+ Caption = 'Document Line No.';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the document line number.';
+ }
+ field(12; "Prod. Order Status"; Enum "Production Order Status")
+ {
+ Caption = 'Prod. Order Status';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies the production order status.';
+ }
+ field(13; "Prod. Order No."; Code[20])
+ {
+ Caption = 'Prod. Order No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Production Order"."No." where(Status = field("Prod. Order Status"));
+ ToolTip = 'Specifies the production order number.';
+ }
+ field(14; "Prod. Order Line No."; Integer)
+ {
+ Caption = 'Prod. Order Line No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Line"."Line No." where(Status = field("Prod. Order Status"),
+ "Prod. Order No." = field("Prod. Order No."));
+ ToolTip = 'Specifies the production order line number.';
+ }
+ field(15; "Routing No."; Code[20])
+ {
+ Caption = 'Routing No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Routing Header";
+ ToolTip = 'Specifies the routing number.';
+ }
+ field(16; "Routing Reference No."; Integer)
+ {
+ Caption = 'Routing Reference No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Routing Line"."Routing Reference No." where(Status = field("Prod. Order Status"),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Routing No."));
+ ToolTip = 'Specifies the routing reference number.';
+ }
+ field(17; "Operation No."; Code[10])
+ {
+ Caption = 'Operation No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Prod. Order Routing Line"."Operation No." where(Status = field("Prod. Order Status"),
+ "Prod. Order No." = field("Prod. Order No."),
+ "Routing No." = field("Routing No."));
+ ToolTip = 'Specifies the operation number.';
+ }
+ field(18; "Work Center No."; Code[20])
+ {
+ Caption = 'Work Center No.';
+ DataClassification = CustomerContent;
+ TableRelation = "Work Center";
+ ToolTip = 'Specifies the work center number.';
+ }
+ field(19; Description; Text[100])
+ {
+ Caption = 'Description';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies a description for the WIP ledger entry.';
+ }
+ field(20; "Description 2"; Text[50])
+ {
+ Caption = 'Description 2';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies description 2 for the WIP ledger entry.';
+ }
+ field(21; "In Transit"; Boolean)
+ {
+ Caption = 'In Transit';
+ DataClassification = CustomerContent;
+ ToolTip = 'Specifies whether the WIP quantity is currently in transit.';
+ }
+ }
+ keys
+ {
+ key(PK; "Entry No.")
+ {
+ Clustered = true;
+ }
+ key(Key2; "Item No.", "Variant Code", "Location Code")
+ {
+ IncludedFields = "Quantity (Base)";
+ }
+ key(Key3; "Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code")
+ {
+ IncludedFields = "Quantity (Base)";
+ }
+ key(Key4; "Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code", "Item No.", "Variant Code")
+ {
+ IncludedFields = "Quantity (Base)";
+ }
+ key(Key5; "Document No.", "Posting Date") { }
+ }
+
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ ///
+ /// Filters the record set to WIP entries for the given production order.
+ /// When SetKey is true, the sort key is aligned to Key3 before applying the filters.
+ ///
+ procedure SetProductionOrderFilter(ProductionOrder: Record "Production Order"; SetKey: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if SetKey then
+ SetCurrentKey("Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code");
+ SetRange("Prod. Order No.", ProductionOrder."No.");
+ SetRange("Prod. Order Status", ProductionOrder.Status);
+ end;
+
+ ///
+ /// Filters the record set to WIP entries for the given prod. order routing line.
+ /// When SetKey is true, the sort key is aligned to Key3 before applying the filters.
+ ///
+ procedure SetProductionOrderRoutingFilter(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; SetKey: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if SetKey then
+ SetCurrentKey("Prod. Order No.", "Prod. Order Status", "Prod. Order Line No.", "Routing Reference No.", "Routing No.", "Operation No.", "Location Code");
+ SetRange("Prod. Order No.", ProdOrderRoutingLine."Prod. Order No.");
+ SetRange("Prod. Order Status", ProdOrderRoutingLine.Status);
+ SetRange("Routing Reference No.", ProdOrderRoutingLine."Routing Reference No.");
+ SetRange("Routing No.", ProdOrderRoutingLine."Routing No.");
+ SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ end;
+ ///
+ /// Gets the next entry number for the Subcontractor WIP Ledger Entry table.
+ ///
+ /// The next entry number.
+ procedure GetNextEntryNo(): Integer
+ var
+ SequenceNoMgt: Codeunit "Sequence No. Mgt.";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit(0);
+#endif
+ exit(SequenceNoMgt.GetNextSeqNo(DATABASE::"Subcontractor WIP Ledger Entry"));
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/WIPDocumentType.Enum.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/WIPDocumentType.Enum.al
new file mode 100644
index 0000000000..ce2d071dda
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/WIPDocumentType.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 99001509 "WIP Document Type"
+{
+ Extensible = true;
+ value(0; "Transfer Order")
+ {
+ Caption = 'Transfer Order';
+ }
+ value(1; "Adjustment (Manual)")
+ {
+ Caption = 'Adjustment (Manual)';
+ }
+ value(2; "Adjustment (Finish Prod Order)")
+ {
+ Caption = 'Adjustment (Finish Prod Order)';
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/WIPItem/WIPLedgerEntryType.Enum.al b/src/Apps/W1/Subcontracting/App/src/WIPItem/WIPLedgerEntryType.Enum.al
new file mode 100644
index 0000000000..c5fcfe47e6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/WIPItem/WIPLedgerEntryType.Enum.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// 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 99001508 "WIP Ledger Entry Type"
+{
+ Extensible = true;
+ value(0; "Positive Adjustment")
+ {
+ Caption = 'Positive Adjustment';
+ }
+ value(1; "Negative Adjustment")
+ {
+ Caption = 'Negative Adjustment';
+ }
+}
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPostedWhseReceiptLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPostedWhseReceiptLine.TableExt.al
new file mode 100644
index 0000000000..5aaac1af9b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPostedWhseReceiptLine.TableExt.al
@@ -0,0 +1,29 @@
+
+// ------------------------------------------------------------------------------------------------
+// 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;
+ ToolTip = 'Specifies the subcontracting purchase line type associated with the warehouse receipt line.';
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer receipt line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseRcptSub.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseRcptSub.PageExt.al
new file mode 100644
index 0000000000..90670d9df7
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseRcptSub.PageExt.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;
+
+using Microsoft.Warehouse.History;
+
+pageextension 99001545 "Subc. Pstd. Whse Rcpt Sub" extends "Posted Whse. Receipt Subform"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseShipmSub.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseShipmSub.PageExt.al
new file mode 100644
index 0000000000..2fc6b6d084
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseShipmSub.PageExt.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;
+
+using Microsoft.Warehouse.History;
+
+pageextension 99001546 "Subc. Pstd. Whse Shipm Sub" extends "Posted Whse. Shipment Subform"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseShipmentLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseShipmentLine.TableExt.al
new file mode 100644
index 0000000000..ac95dfec99
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcPstdWhseShipmentLine.TableExt.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;
+
+using Microsoft.Warehouse.History;
+
+tableextension 99001528 "Subc. Pstd. Whse Shipment Line" extends "Posted Whse. Shipment Line"
+{
+ fields
+ {
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer shipment line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWarehouseReceiptLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWarehouseReceiptLine.TableExt.al
new file mode 100644
index 0000000000..3705adfa33
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWarehouseReceiptLine.TableExt.al
@@ -0,0 +1,29 @@
+
+// ------------------------------------------------------------------------------------------------
+// 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;
+ ToolTip = 'Specifies the subcontracting purchase line type associated with the warehouse receipt line.';
+ }
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer receipt line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWarehouseShipmentLine.TableExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWarehouseShipmentLine.TableExt.al
new file mode 100644
index 0000000000..25bf71260d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWarehouseShipmentLine.TableExt.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;
+
+using Microsoft.Warehouse.Document;
+
+tableextension 99001527 "Subc. Warehouse Shipment Line" extends "Warehouse Shipment Line"
+{
+ fields
+ {
+ field(99001560; "Transfer WIP Item"; Boolean)
+ {
+ Caption = 'Transfer WIP Item';
+ DataClassification = CustomerContent;
+ Editable = false;
+ ToolTip = 'Specifies whether this transfer shipment line represents a WIP item transfer.';
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePostReceiptExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePostReceiptExt.Codeunit.al
new file mode 100644
index 0000000000..abb7b8f24a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePostReceiptExt.Codeunit.al
@@ -0,0 +1,399 @@
+// ------------------------------------------------------------------------------------------------
+// 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.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
+#if not CLEAN28
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ 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.';
+ NoWIPItemTrackingAllowedErr: Label 'Item tracking is not supported for WIP item transfers.';
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if WarehouseReceiptLine."Transfer WIP Item" then
+ Error(NoWIPItemTrackingAllowedErr);
+
+ 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
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SubcTransferManagement.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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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("Subc.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."Subc.Whse.Outstanding Quantity", PurchaseLine.FieldCaption("Qty. Rounding Precision"), PurchaseLine.FieldCaption("Subc.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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ SuppressQtyPerUoMTestfield := WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation;
+ SuppressQtyPerUoMTestfield := SuppressQtyPerUoMTestfield or WarehouseReceiptLine."Transfer WIP Item";
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if (WarehouseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation) then
+ IsHandled := true;
+ if WarehouseReceiptLine."Transfer WIP Item" 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PostedWhseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation then
+ IsHandled := true;
+ if PostedWhseReceiptLine."Transfer WIP Item" 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PostedWhseRcptLine."Subc. Purchase Line Type" <> "Subc. Purchase Line Type"::None then
+ IsHandled := true;
+ if PostedWhseRcptLine."Transfer WIP Item" 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if SkipPutAwayProcessing then
+ exit;
+ SkipPutAwayProcessing := PostedWhseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation;
+ SkipPutAwayProcessing := SkipPutAwayProcessing or PostedWhseReceiptLine."Transfer WIP Item";
+ 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 not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PostedWhseReceiptLine."Subc. Purchase Line Type" = "Subc. Purchase Line Type"::NotLastOperation then
+ SkipPutAwayCreationForLine := true;
+ if PostedWhseReceiptLine."Transfer WIP Item" 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/Warehouse/SubcWhsePostShipmentExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePostShipmentExt.Codeunit.al
new file mode 100644
index 0000000000..9d5d0b8144
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePostShipmentExt.Codeunit.al
@@ -0,0 +1,145 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Location;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.History;
+using Microsoft.Warehouse.Request;
+
+codeunit 99001563 "Subc. WhsePostShipment Ext"
+{
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Transfer Warehouse Mgt.", OnBeforeCheckIfTransLine2ShipmentLine, '', false, false)]
+ local procedure HandleWipTransferOnBeforeCheckIfTransLine2ShipmentLine(var TransferLine: Record "Transfer Line"; var IsHandled: Boolean; var ReturnValue: Boolean)
+ var
+ Location: Record Location;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if TransferLine."Transfer WIP Item" then begin
+ if Location.GetLocationSetup(TransferLine."Transfer-from Code", Location) then
+ if Location."Use As In-Transit" then
+ exit;
+
+ TransferLine.CalcFields("Whse Outbnd. Otsdg. Qty");
+ ReturnValue := TransferLine."Outstanding Quantity" > TransferLine."Whse Outbnd. Otsdg. Qty";
+ IsHandled := true;
+ end;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Create Source Document", OnBeforeSetQtysOnShptLine, '', false, false)]
+ local procedure HandleWipTransferOnBeforeSetQtysOnShptLine(var WarehouseShipmentLine: Record "Warehouse Shipment Line"; var Qty: Decimal; var QtyBase: Decimal; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if WarehouseShipmentLine."Transfer WIP Item" then
+ WarehouseShipmentLine.Validate("Qty. Picked", Qty);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Transfer Warehouse Mgt.", OnFromTransLine2ShptLineOnAfterInitNewLine, '', false, false)]
+ local procedure HandleWipTransferOnFromTransLine2ShptLineOnAfterInitNewLine(var WarehouseShipmentLine: Record "Warehouse Shipment Line"; WarehouseShipmentHeader: Record "Warehouse Shipment Header"; TransferLine: Record "Transfer Line"; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ WarehouseShipmentLine."Transfer WIP Item" := TransferLine."Transfer WIP Item";
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Shipment Line", OnBeforeValidateQuantityIsBalanced, '', false, false)]
+ local procedure HandleWipTransferOnBeforeValidateQuantityIsBalanced(var WarehouseShipmentLine: Record "Warehouse Shipment Line"; var IsHandled: Boolean; xWarehouseShipmentLine: Record "Warehouse Shipment Line")
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if WarehouseShipmentLine."Transfer WIP Item" then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Shipment Line", OnBeforeOpenItemTrackingLines, '', false, false)]
+ local procedure HandleWipTransferOnBeforeOpenItemTrackingLines(var WarehouseShipmentLine: Record "Warehouse Shipment Line"; var IsHandled: Boolean)
+ var
+ NoWIPItemTrackingAllowedErr: Label 'Item tracking is not supported for WIP item transfers.';
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if WarehouseShipmentLine."Transfer WIP Item" then
+ Error(NoWIPItemTrackingAllowedErr);
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Shipment Line", OnBeforeCalcBaseQty, '', false, false)]
+ local procedure HandleWipTransferOnBeforeCalcBaseQty(var WarehouseShipmentLine: Record "Warehouse Shipment Line"; var Qty: Decimal; FromFieldName: Text; ToFieldName: Text; var SuppressQtyPerUoMTestfield: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if WarehouseShipmentLine."Transfer WIP Item" then
+ SuppressQtyPerUoMTestfield := true;
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Post Shipment", OnBeforePostWhseJnlLine, '', false, false)]
+ local procedure HandleWipTransferOnBeforePostWhseJnlLine(var PostedWhseShipmentLine: Record "Posted Whse. Shipment Line"; var TempTrackingSpecification: Record "Tracking Specification" temporary; var IsHandled: Boolean)
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if PostedWhseShipmentLine."Transfer WIP Item" then
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Warehouse Shipment Line", OnBeforeValidateQtyToShipBase, '', false, false)]
+ local procedure HandleWipTransferOnBeforeValidateQtyToShipBase(var WarehouseShipmentLine: Record "Warehouse Shipment Line"; xWarehouseShipmentLine: Record "Warehouse Shipment Line"; CallingFieldNo: Integer; var IsHandled: Boolean)
+ var
+ Location: Record Location;
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ if not WarehouseShipmentLine."Transfer WIP Item" then
+ exit;
+ Location.SetLoadFields("Require Pick");
+ Location.Get(WarehouseShipmentLine."Location Code");
+ if Location."Require Pick" then
+ WarehouseShipmentLine.Validate("Qty. to Ship", WarehouseShipmentLine."Qty. Picked" - WarehouseShipmentLine."Qty. Shipped")
+ else
+ WarehouseShipmentLine.Validate("Qty. to Ship", WarehouseShipmentLine."Qty. Outstanding");
+ IsHandled := true;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePurchReleaseExt.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePurchReleaseExt.Codeunit.al
new file mode 100644
index 0000000000..35579f4f8e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhsePurchReleaseExt.Codeunit.al
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+
+codeunit 99001550 "Subc. WhsePurchRelease Ext"
+{
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Whse.-Purch. Release", OnAfterReleaseSetFilters, '', false, false)]
+ local procedure OnAfterReleaseSetFilters(var PurchaseLine: Record "Purchase Line"; PurchaseHeader: Record "Purchase Header")
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ PurchaseHeader.CalcFields("Subc. Order");
+ if PurchaseHeader."Subc. Order" then
+ PurchaseLine.SetRange("Work Center No.");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseRcptLinesExt.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseRcptLinesExt.PageExt.al
new file mode 100644
index 0000000000..06be6e6c90
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseRcptLinesExt.PageExt.al
@@ -0,0 +1,124 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+using Microsoft.Warehouse.Document;
+
+pageextension 99001534 "Subc. Whse Rcpt Lines Ext." extends "Whse. Receipt Lines"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ addafter("&Show Source Document Line")
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+ action("Production Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order';
+ Image = Production;
+ ToolTip = 'View the related production order.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ ShowProductionOrder(PurchaseLine);
+ end;
+ }
+ action("Production Order Routing")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Routing';
+ Image = Route;
+ ToolTip = 'View the related production order routing.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ ShowProductionOrderRouting(PurchaseLine);
+ end;
+ }
+ action("Production Order Components")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Components';
+ Image = Components;
+ ToolTip = 'View the related production order components.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ ShowProductionOrderComponents(PurchaseLine);
+ end;
+ }
+ action("Transfer Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Order';
+ Image = TransferOrder;
+ ToolTip = 'View the related transfer order.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(PurchaseLine, true, false);
+ end;
+ }
+ action("Return Transfer Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Return Transfer Order';
+ Image = ReturnRelated;
+ ToolTip = 'View the related return transfer order.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(PurchaseLine, true, true);
+ end;
+ }
+ }
+ }
+ }
+ var
+ PurchaseLine: Record "Purchase Line";
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+
+ local procedure GetSourcePurchaseLine(): Boolean
+ begin
+ if Rec."Source Type" <> Database::"Purchase Line" then
+ exit(false);
+ if (PurchaseLine."Document Type".AsInteger() = Rec."Source Subtype") and
+ (PurchaseLine."Document No." = Rec."Source No.") and
+ (PurchaseLine."Line No." = Rec."Source Line No.")
+ then
+ exit(true);
+ exit(PurchaseLine.Get(Rec."Source Subtype", Rec."Source No.", Rec."Source Line No."));
+ end;
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseRcptSubformExt.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseRcptSubformExt.PageExt.al
new file mode 100644
index 0000000000..9892ed621c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseRcptSubformExt.PageExt.al
@@ -0,0 +1,127 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Purchases.Document;
+using Microsoft.Warehouse.Document;
+
+pageextension 99001533 "Subc. Whse Rcpt Subform Ext." extends "Whse. Receipt Subform"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ modify(ItemTrackingLines)
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ addafter(ItemTrackingLines)
+ {
+ group(Production)
+ {
+ Caption = 'Production';
+ action("Production Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order';
+ Image = Production;
+ ToolTip = 'View the related production order.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ ShowProductionOrder(PurchaseLine);
+ end;
+ }
+ action("Production Order Routing")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Routing';
+ Image = Route;
+ ToolTip = 'View the related production order routing.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ ShowProductionOrderRouting(PurchaseLine);
+ end;
+ }
+ action("Production Order Components")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Production Order Components';
+ Image = Components;
+ ToolTip = 'View the related production order components.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ ShowProductionOrderComponents(PurchaseLine);
+ end;
+ }
+ action("Transfer Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Transfer Order';
+ Image = TransferOrder;
+ ToolTip = 'View the related transfer order.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(PurchaseLine, true, false);
+ end;
+ }
+ action("Return Transfer Order")
+ {
+ ApplicationArea = Subcontracting;
+ Caption = 'Subcontracting Return Transfer Order';
+ Image = ReturnRelated;
+ ToolTip = 'View the related return transfer order.';
+ trigger OnAction()
+ begin
+ if GetSourcePurchaseLine() then
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(PurchaseLine, true, true);
+ end;
+ }
+ }
+ }
+ }
+ var
+ PurchaseLine: Record "Purchase Line";
+ SubcProdOrderFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+
+ local procedure GetSourcePurchaseLine(): Boolean
+ begin
+ if Rec."Source Type" <> Database::"Purchase Line" then
+ exit(false);
+ if (PurchaseLine."Document Type".AsInteger() = Rec."Source Subtype") and
+ (PurchaseLine."Document No." = Rec."Source No.") and
+ (PurchaseLine."Line No." = Rec."Source Line No.") then
+ exit(true);
+ exit(PurchaseLine.Get(Rec."Source Subtype", Rec."Source No.", Rec."Source Line No."));
+ end;
+
+ local procedure ShowProductionOrder(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrder(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderComponents(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderComponents(RecRelatedVariant);
+ end;
+
+ local procedure ShowProductionOrderRouting(RecRelatedVariant: Variant)
+ begin
+ SubcProdOrderFactboxMgmt.ShowProductionOrderRouting(RecRelatedVariant);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseShipmSubformExt.PageExt.al b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseShipmSubformExt.PageExt.al
new file mode 100644
index 0000000000..db73fe7749
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Warehouse/SubcWhseShipmSubformExt.PageExt.al
@@ -0,0 +1,29 @@
+// ------------------------------------------------------------------------------------------------
+// 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;
+
+pageextension 99001547 "Subc. Whse Shipm. Subform Ext." extends "Whse. Shipment Subform"
+{
+ layout
+ {
+ addlast(Control1)
+ {
+ field("Transfer WIP Item"; Rec."Transfer WIP Item")
+ {
+ ApplicationArea = Subcontracting;
+ Visible = false;
+ }
+ }
+ }
+ actions
+ {
+ modify(ItemTrackingLines)
+ {
+ Enabled = not Rec."Transfer WIP Item";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcReqWkshTempTypeExt.EnumExt.al b/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcReqWkshTempTypeExt.EnumExt.al
new file mode 100644
index 0000000000..116deab476
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcReqWkshTempTypeExt.EnumExt.al
@@ -0,0 +1,14 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+enumextension 99001500 "Subc. ReqWkshTempType Ext." extends "Req. Worksheet Template Type"
+{
+ value(99001500; Subcontracting)
+ {
+ Caption = 'Subcontracting';
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcSubcontractingWorksheet.Page.al b/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcSubcontractingWorksheet.Page.al
new file mode 100644
index 0000000000..9faebd854e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcSubcontractingWorksheet.Page.al
@@ -0,0 +1,448 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.Currency;
+using Microsoft.Finance.Dimension;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Requisition;
+using System.Security.User;
+
+page 99001504 "Subc. Subcontracting Worksheet"
+{
+ ApplicationArea = Subcontracting;
+ AutoSplitKey = true;
+ Caption = 'Subcontracting Worksheets';
+ DataCaptionFields = "Journal Batch Name";
+ DelayedInsert = true;
+ PageType = Worksheet;
+ SaveValues = true;
+ SourceTable = "Requisition Line";
+ UsageCategory = Tasks;
+
+ layout
+ {
+ area(content)
+ {
+ field(CurrentJnlBatchName; CurrentJnlBatchName)
+ {
+ Caption = 'Name';
+ Lookup = true;
+ ToolTip = 'Specifies the name of the journal batch of the subcontracting worksheet.';
+
+ trigger OnLookup(var Text: Text): Boolean
+ begin
+ CurrPage.SaveRecord();
+ ReqJnlManagement.LookupName(CurrentJnlBatchName, Rec);
+ CurrPage.Update(false);
+ end;
+
+ trigger OnValidate()
+ begin
+ ReqJnlManagement.CheckName(CurrentJnlBatchName, Rec);
+ CurrentJnlBatchNameOnAfterVali();
+ end;
+ }
+ repeater(Control1)
+ {
+ ShowCaption = false;
+ field(Type; Rec.Type)
+ {
+ ToolTip = 'Specifies the type of requisition worksheet line you are creating.';
+
+ trigger OnValidate()
+ begin
+ ReqJnlManagement.GetDescriptionAndRcptName(Rec, Description2, BuyFromVendorName);
+ end;
+ }
+ field("No."; Rec."No.")
+ {
+ ToolTip = 'Specifies the number of the involved entry or record, according to the specified number series.';
+
+ trigger OnValidate()
+ var
+ Item: Record "Item";
+ begin
+ ReqJnlManagement.GetDescriptionAndRcptName(Rec, Description2, BuyFromVendorName);
+ if Rec."Variant Code" = '' then
+ VariantCodeMandatory := Item.IsVariantMandatory(Rec.Type = Rec.Type::Item, Rec."No.");
+ end;
+ }
+ field("Accept Action Message"; Rec."Accept Action Message")
+ {
+ ToolTip = 'Specifies whether to accept the action message proposed for the line.';
+ }
+ field("Action Message"; Rec."Action Message")
+ {
+ ToolTip = 'Specifies an action to take to rebalance the demand-supply situation.';
+ }
+ field("Prod. Order No."; Rec."Prod. Order No.")
+ {
+ ToolTip = 'Specifies the number of the related production order.';
+ }
+ field("Operation No."; Rec."Operation No.")
+ {
+ ToolTip = 'Specifies the operation number for this routing line.';
+ }
+ field("Work Center No."; Rec."Work Center No.")
+ {
+ ToolTip = 'Specifies the work center number of the journal line.';
+ }
+ field(Description; Rec.Description)
+ {
+ ToolTip = 'Specifies text that describes the entry.';
+ }
+ field("Description 2"; Rec."Description 2")
+ {
+ ToolTip = 'Specifies additional text describing the entry, or a remark about the requisition worksheet line.';
+ Visible = false;
+ }
+ field("Variant Code"; Rec."Variant Code")
+ {
+ ToolTip = 'Specifies the variant of the item on the line.';
+ Visible = false;
+ ShowMandatory = VariantCodeMandatory;
+
+ trigger OnValidate()
+ var
+ Item: Record "Item";
+ begin
+ if Rec."Variant Code" = '' then
+ VariantCodeMandatory := Item.IsVariantMandatory(Rec.Type = Rec.Type::Item, Rec."No.");
+ end;
+ }
+ field("Shortcut Dimension 1 Code"; Rec."Shortcut Dimension 1 Code")
+ {
+ ApplicationArea = Dimensions;
+ ToolTip = 'Specifies the code for Shortcut Dimension 1, which is one of two global dimension codes that you set up in the General Ledger Setup window.';
+ Visible = false;
+ }
+ field("Shortcut Dimension 2 Code"; Rec."Shortcut Dimension 2 Code")
+ {
+ ApplicationArea = Dimensions;
+ ToolTip = 'Specifies the code for Shortcut Dimension 2, which is one of two global dimension codes that you set up in the General Ledger Setup window.';
+ Visible = false;
+ }
+ field("Location Code"; Rec."Location Code")
+ {
+ ApplicationArea = Location;
+ ToolTip = 'Specifies a code for an inventory location where the items that are being ordered will be registered.';
+ Visible = false;
+ }
+ field(Quantity; Rec.Quantity)
+ {
+ ToolTip = 'Specifies the number of units of the item.';
+ }
+ field("Unit of Measure Code"; Rec."Unit of Measure Code")
+ {
+ ToolTip = 'Specifies how each unit of the item or resource is measured, such as in pieces or hours. By default, the value in the Base Unit of Measure field on the item or resource card is inserted.';
+ }
+ field("Vendor No."; Rec."Vendor No.")
+ {
+ ToolTip = 'Specifies the number of the vendor who will ship the items in the purchase order.';
+
+ trigger OnValidate()
+ begin
+ ReqJnlManagement.GetDescriptionAndRcptName(Rec, Description2, BuyFromVendorName);
+ end;
+ }
+ field("Order Address Code"; Rec."Order Address Code")
+ {
+ ToolTip = 'Specifies the order address of the related vendor.';
+ Visible = false;
+ }
+ field("Vendor Item No."; Rec."Vendor Item No.")
+ {
+ ToolTip = 'Specifies the number that the vendor uses for this item.';
+ }
+ field("Sell-to Customer No."; Rec."Sell-to Customer No.")
+ {
+ ToolTip = 'Specifies the number of the customer.';
+ Visible = false;
+ }
+ field("Ship-to Code"; Rec."Ship-to Code")
+ {
+ ToolTip = 'Specifies a code for an alternate shipment address if you want to ship to another address than the one that has been entered automatically. This field is also used in case of drop shipment.';
+ Visible = false;
+ }
+ field("Currency Code"; Rec."Currency Code")
+ {
+ AssistEdit = true;
+ ToolTip = 'Specifies the currency code for the requisition lines.';
+ Visible = false;
+
+ trigger OnAssistEdit()
+ var
+ ChangeExchangeRate: Page "Change Exchange Rate";
+ begin
+ ChangeExchangeRate.SetParameter(Rec."Currency Code", Rec."Currency Factor", WorkDate());
+ if ChangeExchangeRate.RunModal() = ACTION::OK then
+ Rec.Validate("Currency Factor", ChangeExchangeRate.GetParameter());
+
+ Clear(ChangeExchangeRate);
+ end;
+ }
+ field("Direct Unit Cost"; Rec."Direct Unit Cost")
+ {
+ ToolTip = 'Specifies the cost of one unit of the selected item or resource.';
+ }
+ field("Line Discount %"; Rec."Line Discount %")
+ {
+ ToolTip = 'Specifies the discount percentage used to calculate the purchase line discount.';
+ Visible = false;
+ }
+ field("Order Date"; Rec."Order Date")
+ {
+ ToolTip = 'Specifies the order date that will apply to the requisition worksheet line.';
+ Visible = false;
+ }
+ field("Due Date"; Rec."Due Date")
+ {
+ ToolTip = 'Specifies the date when you can expect to receive the items.';
+ }
+ field("Requester ID"; Rec."Requester ID")
+ {
+ LookupPageID = "User Lookup";
+ ToolTip = 'Specifies the ID of the user who is ordering the items on the line.';
+ Visible = false;
+ }
+ field(Confirmed; Rec.Confirmed)
+ {
+ ToolTip = 'Specifies whether the items on the line have been approved for purchase.';
+ Visible = false;
+ }
+ field("Subc. Standard Task Code"; Rec."Subc. Standard Task Code")
+ {
+ Editable = false;
+ ToolTip = 'Specifies the standard task code associated with the subcontracting operation.';
+ }
+ }
+ group(Control20)
+ {
+ ShowCaption = false;
+ fixed(Control1901776201)
+ {
+ ShowCaption = false;
+ group(Control1902759801)
+ {
+ Caption = 'Description';
+ field(Description2; Description2)
+ {
+ Editable = false;
+ ShowCaption = false;
+ ToolTip = 'Specifies an additional part of the worksheet description.';
+ }
+ }
+ group("Buy-from Vendor Name")
+ {
+ Caption = 'Buy-from Vendor Name';
+ field(BuyFromVendorName; BuyFromVendorName)
+ {
+ Caption = 'Buy-from Vendor Name';
+ Editable = false;
+ ToolTip = 'Specifies the vendor''s name.';
+ }
+ }
+ }
+ }
+ }
+ area(factboxes)
+ {
+ systempart(Control1900383207; Links)
+ {
+ ApplicationArea = RecordLinks;
+ Visible = false;
+ }
+ systempart(Control1905767507; Notes)
+ {
+ ApplicationArea = Notes;
+ Visible = false;
+ }
+ }
+ }
+
+ actions
+ {
+ area(navigation)
+ {
+ group(Line)
+ {
+ Caption = 'Line';
+ Image = Line;
+ action(Card)
+ {
+ Caption = 'Card';
+ Image = EditLines;
+ RunObject = Codeunit "Req. Wksh.-Show Card";
+ ShortCutKey = 'Shift+F7';
+ ToolTip = 'View or change detailed information about the record on the document or journal line.';
+ }
+ action(ItemTrackingLines)
+ {
+ ApplicationArea = ItemTracking;
+ Caption = 'Item Tracking Lines';
+ Image = ItemTrackingLines;
+ ShortCutKey = 'Ctrl+Alt+I';
+ ToolTip = 'View or edit serial, lot and package numbers that are assigned to the item on the document or journal line.';
+
+ trigger OnAction()
+ begin
+ Rec.OpenItemTrackingLines();
+ end;
+ }
+ action(Dimensions)
+ {
+ AccessByPermission = TableData Dimension = R;
+ ApplicationArea = Dimensions;
+ Caption = 'Dimensions';
+ Image = Dimensions;
+ ShortCutKey = 'Alt+D';
+ ToolTip = 'View or edit dimensions, such as area, project, or department, that you can assign to sales and purchase documents to distribute costs and analyze transaction history.';
+
+ trigger OnAction()
+ begin
+ Rec.ShowDimensions();
+ CurrPage.SaveRecord();
+ end;
+ }
+ }
+ }
+ area(processing)
+ {
+ group("Functions")
+ {
+ Caption = 'Functions';
+ Image = "Action";
+ action("Calculate Subcontracts")
+ {
+ Caption = 'Calculate Subcontracts';
+ Ellipsis = true;
+ Image = Calculate;
+ ToolTip = 'Calculate the external work centers that are managed by a supplier under contract.';
+
+ trigger OnAction()
+ var
+ CalculateSubContract: Report "Subc. Calculate Subcontracts";
+ begin
+ CalculateSubContract.SetWkShLine(Rec);
+ CalculateSubContract.RunModal();
+ end;
+ }
+ action(CarryOutActionMessage)
+ {
+ Caption = 'Carry Out Action Message';
+ Ellipsis = true;
+ Image = CarryOutActionMessage;
+ ToolTip = 'Use a batch job to help you create actual supply orders from the order proposals.';
+
+ trigger OnAction()
+ begin
+ CarryOutActionMsg();
+ end;
+ }
+ }
+ }
+ area(Promoted)
+ {
+ group(Category_Process)
+ {
+ Caption = 'Process', Comment = 'Generated from the PromotedActionCategories property index 1.';
+
+ actionref("Calculate Subcontracts_Promoted"; "Calculate Subcontracts")
+ {
+ }
+ actionref(CarryOutActionMessage_Promoted; CarryOutActionMessage)
+ {
+ }
+ }
+ group(Category_Category4)
+ {
+ Caption = 'Line', Comment = 'Generated from the PromotedActionCategories property index 3.';
+
+ actionref(ItemTrackingLines_Promoted; ItemTrackingLines)
+ {
+ }
+ actionref(Dimensions_Promoted; Dimensions)
+ {
+ }
+ }
+ group(Category_Report)
+ {
+ Caption = 'Report', Comment = 'Generated from the PromotedActionCategories property index 2.';
+ }
+ }
+ }
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ ReqJnlManagement.GetDescriptionAndRcptName(Rec, Description2, BuyFromVendorName);
+ end;
+
+ trigger OnAfterGetRecord()
+ var
+ Item: Record Item;
+ begin
+ if Rec."Variant Code" = '' then
+ VariantCodeMandatory := Item.IsVariantMandatory(Rec.Type = Rec.Type::Item, Rec."No.");
+ end;
+
+ trigger OnNewRecord(BelowxRec: Boolean)
+ begin
+ ReqJnlManagement.SetUpNewLine(Rec, xRec);
+ end;
+
+ trigger OnOpenPage()
+ var
+ JnlSelected: Boolean;
+ begin
+ OpenedFromBatch := (Rec."Journal Batch Name" <> '') and (Rec."Worksheet Template Name" = '');
+ if OpenedFromBatch then begin
+ CurrentJnlBatchName := Rec."Journal Batch Name";
+ ReqJnlManagement.OpenJnl(CurrentJnlBatchName, Rec);
+ exit;
+ end;
+ ReqJnlManagement.WkshTemplateSelection(
+ PAGE::"Subc. Subcontracting Worksheet", false, "Req. Worksheet Template Type"::Subcontracting, Rec, JnlSelected);
+ if not JnlSelected then
+ Error('');
+ ReqJnlManagement.OpenJnl(CurrentJnlBatchName, Rec);
+ end;
+
+ var
+ ReqJnlManagement: Codeunit ReqJnlManagement;
+ CurrentJnlBatchName: Code[10];
+ OpenedFromBatch: Boolean;
+ VariantCodeMandatory: Boolean;
+
+ protected var
+ Description2: Text[100];
+ BuyFromVendorName: Text[100];
+
+ local procedure CurrentJnlBatchNameOnAfterVali()
+ begin
+ CurrPage.SaveRecord();
+ ReqJnlManagement.SetName(CurrentJnlBatchName, Rec);
+ CurrPage.Update(false);
+ end;
+
+ local procedure CarryOutActionMsg()
+ var
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ IsHandled: Boolean;
+ begin
+ IsHandled := false;
+ OnBeforeCarryOutActionMsg(Rec, IsHandled);
+ if IsHandled then
+ exit;
+
+ CarryOutActionMsgReq.SetReqWkshLine(Rec);
+ CarryOutActionMsgReq.RunModal();
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeCarryOutActionMsg(var RequisitionLine: Record "Requisition Line"; var IsHandled: Boolean);
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcWorksheetHandler.Codeunit.al b/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcWorksheetHandler.Codeunit.al
new file mode 100644
index 0000000000..fceab2be7e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/App/src/Worksheet/SubcWorksheetHandler.Codeunit.al
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Requisition;
+codeunit 99001558 "Subc. Worksheet Handler"
+{
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::ReqJnlManagement, OnOpenJnlBatchOnBeforeTemplateSelection, '', false, false)]
+ local procedure OnOpenJnlBatchOnBeforeTemplateSelection(var RequisitionWkshName: Record "Requisition Wksh. Name"; var ReqWorksheetTemplateTypeList: List of [Enum Microsoft.Inventory.Requisition."Req. Worksheet Template Type"])
+#if not CLEAN28
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+#endif
+ begin
+#if not CLEAN28
+#pragma warning disable AL0432
+ if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
+#pragma warning restore AL0432
+ exit;
+#endif
+ ReqWorksheetTemplateTypeList.Add(Enum::"Req. Worksheet Template Type"::Subcontracting);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/SubcontractingWorkspace.code-workspace b/src/Apps/W1/Subcontracting/SubcontractingWorkspace.code-workspace
new file mode 100644
index 0000000000..3bfd21d74b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/SubcontractingWorkspace.code-workspace
@@ -0,0 +1,12 @@
+{
+ "folders": [
+ {
+ "name": "Subcontracting - App",
+ "path": ".\\App"
+ },
+ {
+ "name": "Subcontracting - Test",
+ "path": ".\\Test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/DisabledTests/SubcSubcontractingTest.DisabledTest.json b/src/Apps/W1/Subcontracting/Test/DisabledTests/SubcSubcontractingTest.DisabledTest.json
new file mode 100644
index 0000000000..17e6ba8173
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/DisabledTests/SubcSubcontractingTest.DisabledTest.json
@@ -0,0 +1,8 @@
+[
+ {
+ "bug": "638702",
+ "codeunitId": 139989,
+ "codeunitName": "Subc. Subcontracting Test",
+ "method": "CancelInvoiceWithSubcontractingItemChargeIsBlocked"
+ }
+]
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/ExtensionLogo.png b/src/Apps/W1/Subcontracting/Test/ExtensionLogo.png
new file mode 100644
index 0000000000..8cce2b2fd0
Binary files /dev/null and b/src/Apps/W1/Subcontracting/Test/ExtensionLogo.png differ
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcCreateProdOrdWizLibrary.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcCreateProdOrdWizLibrary.Codeunit.al
new file mode 100644
index 0000000000..7940554fa8
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcCreateProdOrdWizLibrary.Codeunit.al
@@ -0,0 +1,337 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.VAT.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+
+codeunit 139986 "Subc. CreateProdOrdWizLibrary"
+{
+ var
+ LibraryERM: Codeunit "Library - ERM";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+
+ procedure CreateAndCalculateNeededWorkCenter(var WorkCenter: Record "Work Center"; IsSubcontracting: Boolean)
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ ShopCalendarCode: Code[10];
+ WorkCenterNo: Code[20];
+ UnitCostCalculation: Option Time,Units;
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ // Create Work Center
+ CreateWorkCenter(WorkCenterNo, ShopCalendarCode, "Flushing Method"::"Pick + Manual", IsSubcontracting, UnitCostCalculation::Time, '');
+ WorkCenter.Get(WorkCenterNo);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter, CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+ end;
+
+ local procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; IsSubcontracting: Boolean;
+ UnitCostCalc: Option;
+ CurrencyCode: Code[10])
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ VATPostingSetup: Record "VAT Posting Setup";
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ begin
+ LibraryMfgManagement.CreateWorkCenterWithFixedCost(WorkCenter, ShopCalendarCode, 0);
+
+ WorkCenter.Validate("Flushing Method", FlushingMethod);
+ WorkCenter.Validate("Direct Unit Cost", LibraryRandom.RandDec(10, 2));
+ WorkCenter.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Overhead Rate", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Unit Cost Calculation", UnitCostCalc);
+
+ if IsSubcontracting then begin
+ LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT");
+ GenProductPostingGroup.FindFirst();
+ GenProductPostingGroup.Validate("Def. VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group");
+ GenProductPostingGroup.Modify(true);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(CurrencyCode));
+
+ WorkCenter."Subcontractor No." := Vendor."No.";
+ end;
+ WorkCenter.Modify(true);
+ WorkCenterNo := WorkCenter."No.";
+ end;
+
+ procedure CreateItemWithoutBOMAndRouting(BomNo: Code[20]; RoutingNo: Code[20]): Code[20]
+ var
+ Item: Record Item;
+ ItemNo: Code[20];
+ begin
+ // Create item with specified BOM and Routing (empty for NothingPresent scenario)
+ ItemNo := LibraryInventory.CreateItemNo();
+ Item.Get(ItemNo);
+ Item."Production BOM No." := BomNo;
+ Item."Routing No." := RoutingNo;
+ Item.Modify();
+ exit(ItemNo);
+ end;
+
+ procedure CreatePurchaseLineWithSubcontractingVendor(var PurchLine: Record "Purchase Line"; ItemNo: Code[20])
+ var
+ Location, VendorLocation : Record Location;
+ PurchaseHeader: Record "Purchase Header";
+ Vendor: Record Vendor;
+ begin
+ // Create locations
+ LibraryWarehouse.CreateLocation(Location);
+ LibraryWarehouse.CreateLocation(VendorLocation);
+
+ // Create vendor with subcontracting location
+ LibraryPurchase.CreateVendor(Vendor);
+ Vendor."Subc. Location Code" := VendorLocation.Code;
+ Vendor.Modify();
+
+ // Create purchase order with purchase line
+ LibraryPurchase.CreatePurchaseOrderWithLocation(PurchaseHeader, Vendor."No.", Location.Code);
+ LibraryPurchase.CreatePurchaseLine(PurchLine, PurchaseHeader, PurchLine.Type::Item, ItemNo, LibraryRandom.RandIntInRange(1, 10));
+ PurchLine.Validate("Direct Unit Cost", LibraryRandom.RandDecInRange(10, 100, 2));
+ PurchLine.Validate("Expected Receipt Date", WorkDate());
+ PurchLine.Modify(true);
+ end;
+
+ procedure CreateBOMWithTwoLines(): Code[20]
+ var
+ ComponentItem1: Record Item;
+ ComponentItem2: Record Item;
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+
+ begin
+ // Create two component items
+ LibraryInventory.CreateItem(ComponentItem1);
+ LibraryInventory.CreateItem(ComponentItem2);
+
+ // Create BOM header
+ LibraryManufacturing.CreateProductionBOMHeader(ProductionBOMHeader, ComponentItem1."Base Unit of Measure");
+
+ // Create first BOM line
+ LibraryManufacturing.CreateProductionBOMLine(
+ ProductionBOMHeader, ProductionBOMLine, '', ProductionBOMLine.Type::Item, ComponentItem1."No.", 1);
+
+ // Create second BOM line
+ LibraryManufacturing.CreateProductionBOMLine(
+ ProductionBOMHeader, ProductionBOMLine, '', ProductionBOMLine.Type::Item, ComponentItem2."No.", 2);
+
+ ProductionBOMHeader.Validate("Version Nos.", LibraryERM.CreateNoSeriesCode());
+
+ // Certify the BOM
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+
+ exit(ProductionBOMHeader."No.");
+ end;
+
+ procedure CreateRoutingWithTwoLines(): Code[20]
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ WorkCenter1: Record "Work Center";
+ WorkCenter2: Record "Work Center";
+ begin
+ // Create two work centers
+ CreateAndCalculateNeededWorkCenter(WorkCenter1, false);
+ CreateAndCalculateNeededWorkCenter(WorkCenter2, true);
+
+ // Create routing header
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+
+ // Create first routing line
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Work Center", WorkCenter1."No.");
+ RoutingLine.Validate("Setup Time", 10);
+ RoutingLine.Validate("Run Time", 5);
+ RoutingLine.Modify(true);
+
+ // Create second routing line
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '20', RoutingLine.Type::"Work Center", WorkCenter2."No.");
+ RoutingLine.Validate("Setup Time", 15);
+ RoutingLine.Validate("Run Time", 8);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate("Version Nos.", LibraryERM.CreateNoSeriesCode());
+
+ // Certify the routing
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ exit(RoutingHeader."No.");
+ end;
+
+ procedure CreateItemWithBOMAndRouting(BOMNo: Code[20]; RoutingNo: Code[20]): Code[20]
+ var
+ Item: Record Item;
+ ItemNo: Code[20];
+ begin
+ // Create item with specified BOM and Routing
+ ItemNo := LibraryInventory.CreateItemNo();
+ Item.Get(ItemNo);
+ Item."Production BOM No." := BOMNo;
+ Item."Routing No." := RoutingNo;
+ Item.Modify();
+ exit(ItemNo);
+ end;
+
+ procedure CreateLocationCode(): Code[10]
+ var
+ Location: Record Location;
+ begin
+ // Create a location and return its code
+ LibraryWarehouse.CreateLocation(Location);
+ exit(Location.Code);
+ end;
+
+ procedure CreateStockkeepingUnit(var StockkeepingUnit: Record "Stockkeeping Unit"; ItemNo: Code[20]; LocationCode: Code[10])
+ begin
+ // Create a stockkeeping unit for the given item and location
+ StockkeepingUnit.Init();
+ StockkeepingUnit."Location Code" := LocationCode;
+ StockkeepingUnit."Item No." := ItemNo;
+ StockkeepingUnit."Variant Code" := '';
+ StockkeepingUnit.Insert(true);
+ end;
+
+ procedure CreateBOMVersionWithTwoLines(BOMNo: Code[20]; VersionCode: Code[20])
+ var
+ ComponentItem1: Record Item;
+ ComponentItem2: Record Item;
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionBOMVersion: Record "Production BOM Version";
+ begin
+ // Create two component items
+ LibraryInventory.CreateItem(ComponentItem1);
+ LibraryInventory.CreateItem(ComponentItem2);
+
+ // Create BOM version
+ LibraryManufacturing.CreateProductionBOMVersion(ProductionBOMVersion, BOMNo, VersionCode, ComponentItem1."Base Unit of Measure");
+
+ // Get the BOM header for creating lines
+ ProductionBOMHeader.Get(BOMNo);
+
+ // Create first BOM line for version
+ LibraryManufacturing.CreateProductionBOMLine(
+ ProductionBOMHeader, ProductionBOMLine, VersionCode, ProductionBOMLine.Type::Item, ComponentItem1."No.", 1);
+
+ // Create second BOM line for version
+ LibraryManufacturing.CreateProductionBOMLine(
+ ProductionBOMHeader, ProductionBOMLine, VersionCode, ProductionBOMLine.Type::Item, ComponentItem2."No.", 2);
+
+ // Certify the BOM version
+ ProductionBOMVersion.Validate(Status, ProductionBOMVersion.Status::Certified);
+ ProductionBOMVersion.Modify(true);
+ end;
+
+ procedure CreateRoutingVersionWithTwoLines(RoutingNo: Code[20]; VersionCode: Code[20])
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ RoutingVersion: Record "Routing Version";
+ WorkCenter1: Record "Work Center";
+ WorkCenter2: Record "Work Center";
+ begin
+ // Create two work centers
+ CreateAndCalculateNeededWorkCenter(WorkCenter1, false);
+ CreateAndCalculateNeededWorkCenter(WorkCenter2, true);
+
+ // Create routing version
+ LibraryManufacturing.CreateRoutingVersion(RoutingVersion, RoutingNo, VersionCode);
+
+ // Get the routing header for creating lines
+ RoutingHeader.Get(RoutingNo);
+
+ // Create first routing line for version
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, VersionCode, '10', RoutingLine.Type::"Work Center", WorkCenter1."No.");
+ RoutingLine.Validate("Setup Time", 10);
+ RoutingLine.Validate("Run Time", 5);
+ RoutingLine.Modify(true);
+
+ // Create second routing line for version
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, VersionCode, '20', RoutingLine.Type::"Work Center", WorkCenter2."No.");
+ RoutingLine.Validate("Setup Time", 15);
+ RoutingLine.Validate("Run Time", 8);
+ RoutingLine.Modify(true);
+
+ // Certify the routing version
+ RoutingVersion.Validate(Status, RoutingVersion.Status::Certified);
+ RoutingVersion.Modify(true);
+ end;
+
+ procedure CreateBOMWithDescription2(var BOMLineDescription2: Text[50]): Code[20]
+ var
+ ComponentItem: Record Item;
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ begin
+ // Create a BOM with a single line that has Description 2 set
+ LibraryInventory.CreateItem(ComponentItem);
+ LibraryManufacturing.CreateProductionBOMHeader(ProductionBOMHeader, ComponentItem."Base Unit of Measure");
+ LibraryManufacturing.CreateProductionBOMLine(
+ ProductionBOMHeader, ProductionBOMLine, '', ProductionBOMLine.Type::Item, ComponentItem."No.", 1);
+ BOMLineDescription2 := CopyStr(LibraryRandom.RandText(MaxStrLen(ProductionBOMLine."Description 2")), 1, MaxStrLen(ProductionBOMLine."Description 2"));
+ ProductionBOMLine."Description 2" := BOMLineDescription2;
+ ProductionBOMLine.Modify();
+ ProductionBOMHeader.Validate("Version Nos.", LibraryERM.CreateNoSeriesCode());
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ exit(ProductionBOMHeader."No.");
+ end;
+
+ procedure CreateRoutingWithSubcWorkCenterAndDescription2(var SubcWorkCenterNo: Code[20]; var RoutingLineDescription2: Text[50]): Code[20]
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ WorkCenter1: Record "Work Center";
+ WorkCenter2: Record "Work Center";
+ begin
+ // Create a routing with two lines; the second work center is subcontracting and has Description 2 set
+ CreateAndCalculateNeededWorkCenter(WorkCenter1, false);
+ CreateAndCalculateNeededWorkCenter(WorkCenter2, true);
+ SubcWorkCenterNo := WorkCenter2."No.";
+
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Work Center", WorkCenter1."No.");
+ RoutingLine.Validate("Setup Time", 10);
+ RoutingLine.Validate("Run Time", 5);
+ RoutingLine.Modify(true);
+
+ LibraryManufacturing.CreateRoutingLine(
+ RoutingHeader, RoutingLine, '', '20', RoutingLine.Type::"Work Center", WorkCenter2."No.");
+ RoutingLine.Validate("Setup Time", 15);
+ RoutingLine.Validate("Run Time", 8);
+ RoutingLineDescription2 := CopyStr(LibraryRandom.RandText(MaxStrLen(RoutingLine."Description 2")), 1, MaxStrLen(RoutingLine."Description 2"));
+ RoutingLine."Description 2" := RoutingLineDescription2;
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate("Version Nos.", LibraryERM.CreateNoSeriesCode());
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ exit(RoutingHeader."No.");
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcLibraryMfgManagement.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcLibraryMfgManagement.Codeunit.al
new file mode 100644
index 0000000000..9afed09fa2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcLibraryMfgManagement.Codeunit.al
@@ -0,0 +1,305 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Journal;
+using Microsoft.Inventory.Requisition;
+#if CLEAN27
+using Microsoft.Inventory.Setup;
+#endif
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Vendor;
+
+codeunit 139984 "Subc. Library Mfg. Management"
+{
+ var
+ LibraryERM: Codeunit "Library - ERM";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibraryUtility: Codeunit "Library - Utility";
+ UnitCostCalculation: Option Time,Units;
+
+ procedure Initialize()
+ begin
+ CreateManufacturingSetup();
+ end;
+
+ local procedure CreateManufacturingSetup()
+ var
+ MfgSetup: Record "Manufacturing Setup";
+#if CLEAN27
+ InventorySetup: Record "Inventory Setup";
+#endif
+ begin
+ if not MfgSetup.Get() then
+ MfgSetup.Insert();
+ MfgSetup.Validate(MfgSetup."Normal Starting Time", 080000T);
+ MfgSetup.Validate(MfgSetup."Normal Ending Time", 230000T);
+ MfgSetup.Validate(MfgSetup."Doc. No. Is Prod. Order No.", true);
+ MfgSetup.Validate(MfgSetup."Cost Incl. Setup", true);
+ MfgSetup.Validate(MfgSetup."Planning Warning", true);
+ MfgSetup.Validate(MfgSetup."Dynamic Low-Level Code", true);
+ MfgSetup."Simulated Order Nos." := LibraryERM.CreateNoSeriesCode();
+ MfgSetup."Planned Order Nos." := LibraryERM.CreateNoSeriesCode();
+ MfgSetup."Firm Planned Order Nos." := LibraryERM.CreateNoSeriesCode();
+ MfgSetup."Released Order Nos." := LibraryERM.CreateNoSeriesCode();
+ MfgSetup."Work Center Nos." := LibraryERM.CreateNoSeriesCode();
+ MfgSetup."Routing Nos." := LibraryERM.CreateNoSeriesCode();
+ MfgSetup."Production BOM Nos." := LibraryERM.CreateNoSeriesCode();
+#if not CLEAN27
+#pragma warning disable AL0432
+ MfgSetup."Combined MPS/MRP Calculation" := true;
+ Evaluate(MfgSetup."Default Safety Lead Time", '<1D>');
+#pragma warning restore AL0432
+ MfgSetup.Modify();
+#else
+ if not InventorySetup.Get() then begin
+ InventorySetup.Init();
+ InventorySetup.Insert();
+ InventorySetup."Combined MPS/MRP Calculation" := true;
+ Evaluate(InventorySetup."Default Safety Lead Time", '<1D>');
+ InventorySetup.Modify();
+ end;
+#endif
+ end;
+
+ procedure CreateWorkCenterWithFixedCost(var WorkCenter: Record "Work Center"; ShopCalendarCode: Code[10]; DirectUnitCost: Decimal)
+ begin
+ LibraryManufacturing.CreateWorkCenter(WorkCenter);
+ WorkCenter.Validate("Direct Unit Cost", DirectUnitCost);
+ WorkCenter.Validate(Capacity, 1);
+ WorkCenter.Validate("Shop Calendar Code", ShopCalendarCode);
+ WorkCenter.Validate("Unit Cost Calculation", UnitCostCalculation);
+ WorkCenter.Modify(true);
+ end;
+
+ procedure CreateWorkCenterWithCalendar(var WorkCenter: Record "Work Center"; DirectUnitCost: Decimal)
+ var
+ ShopCalendarCode: Code[10];
+ begin
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarFullWorkingWeekCustomTime(080000T, 160000T);
+ CreateWorkCenterWithFixedCost(WorkCenter, ShopCalendarCode, DirectUnitCost);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter, CalcDate('<-1W>', WorkDate()), WorkDate());
+ end;
+
+ procedure CreateSubcontractorWithCurrency(CurrencyCode: Code[10]): Code[20]
+ var
+ Vendor: Record Vendor;
+ begin
+ // Create a Subcontractor Vendor.
+ LibraryPurchase.CreateSubcontractor(Vendor);
+ Vendor.Validate("Currency Code", CurrencyCode);
+ Vendor.Modify(true);
+ exit(Vendor."No.");
+ end;
+
+ procedure CreateMachineCenter(var MachineCenterNo: Code[20]; WorkCenterNo: Code[20]; FlushingMethod: Option)
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ MachineCenter: Record "Machine Center";
+ begin
+ // Create Machine Center with required fields where random is used, values not important for test.
+ GenProductPostingGroup.FindFirst();
+ LibraryManufacturing.CreateMachineCenter(MachineCenter, WorkCenterNo, LibraryRandom.RandDec(10, 1));
+ MachineCenter.Validate(Name, MachineCenter."No.");
+ MachineCenter.Validate("Direct Unit Cost", LibraryRandom.RandDec(5, 1));
+ MachineCenter.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 1));
+ MachineCenter.Validate("Overhead Rate", 1);
+ MachineCenter.Validate("Flushing Method", FlushingMethod);
+ MachineCenter.Validate("Gen. Prod. Posting Group", GenProductPostingGroup.Code);
+ MachineCenter.Validate(Efficiency, 100);
+ MachineCenter.Modify(true);
+ MachineCenterNo := MachineCenter."No.";
+ end;
+
+ procedure CreateRouting(var RoutingNo: Code[20]; MachineCenterNo: Code[20]; MachineCenterNo2: Code[20]; WorkCenterNo: Code[20]; WorkCenterNo2: Code[20])
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+
+ CreateRoutingLine(RoutingLine, RoutingHeader, WorkCenterNo);
+ CreateRoutingLine(RoutingLine, RoutingHeader, WorkCenterNo2);
+ RoutingLine.Type := RoutingLine.Type::"Machine Center";
+ CreateRoutingLine(RoutingLine, RoutingHeader, MachineCenterNo);
+ CreateRoutingLine(RoutingLine, RoutingHeader, MachineCenterNo2);
+
+ // Certify Routing after Routing lines creation.
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+ RoutingNo := RoutingHeader."No.";
+ end;
+
+ local procedure CreateRoutingLine(var RoutingLine: Record "Routing Line"; RoutingHeader: Record "Routing Header"; CenterNo: Code[20])
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ OperationNo: Code[10];
+ begin
+ // Create Routing Lines with required fields.
+#pragma warning disable AA0210
+ CapacityUnitOfMeasure.SetRange(Type, CapacityUnitOfMeasure.Type::Minutes);
+#pragma warning restore AA0210
+ CapacityUnitOfMeasure.FindFirst();
+
+ // Random used such that the Next Operation No is greater than the Previous Operation No.
+ OperationNo := FindLastOperationNo(RoutingHeader."No.") + Format(LibraryRandom.RandInt(5));
+
+ // Random is used, values not important for test.
+ LibraryManufacturing.CreateRoutingLineSetup(
+ RoutingLine, RoutingHeader, CenterNo, OperationNo, LibraryRandom.RandInt(5), LibraryRandom.RandInt(5));
+
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Modify(true);
+ end;
+
+ procedure AddProdOrderRoutingLine(ProductionOrder: Record "Production Order"; ProdOrderRoutingLineType: Option; MachineCenterNo: Code[20])
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ OperationNo: Code[10];
+ begin
+ ProdOrderRoutingLine.Init();
+ ProdOrderRoutingLine.Validate(Status, ProductionOrder.Status);
+ ProdOrderRoutingLine.Validate("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.Validate("Routing No.", ProductionOrder."Routing No.");
+ ProdOrderRoutingLine.Validate("Routing Reference No.", SelectRoutingRefNo(ProductionOrder."No.", ProductionOrder."Routing No."));
+ OperationNo := CopyStr(LibraryUtility.GenerateRandomCode(ProdOrderRoutingLine.FieldNo("Operation No."), Database::"Prod. Order Routing Line"), 1, 9);
+ ProdOrderRoutingLine.Validate("Operation No.", OperationNo);
+ ProdOrderRoutingLine.Insert(true);
+ ProdOrderRoutingLine.Validate(Type, ProdOrderRoutingLineType);
+ ProdOrderRoutingLine.Validate("No.", MachineCenterNo);
+ ProdOrderRoutingLine.Validate("Setup Time", LibraryRandom.RandInt(5));
+ ProdOrderRoutingLine.Validate("Run Time", LibraryRandom.RandInt(5));
+ ProdOrderRoutingLine.Modify(true);
+ end;
+
+ local procedure FindLastOperationNo(RoutingNo: Code[20]): Code[10]
+ var
+ RoutingLine: Record "Routing Line";
+ begin
+ RoutingLine.SetRange("Routing No.", RoutingNo);
+ if RoutingLine.FindLast() then
+ exit(RoutingLine."Operation No.");
+ end;
+
+ local procedure SelectRoutingRefNo(ProductionOrderNo: Code[20]; ProdOrderRoutingNo: Code[20]): Integer
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrderNo);
+ ProdOrderRoutingLine.SetRange("Routing No.", ProdOrderRoutingNo);
+ ProdOrderRoutingLine.FindFirst();
+ exit(ProdOrderRoutingLine."Routing Reference No.");
+ end;
+
+ procedure CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup()
+ var
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ CreateReqWkshTemplate(ReqWkshTemplate, false);
+ CreateRequisitionWkshName(RequisitionWkshName, ReqWkshTemplate.Name);
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Subcontracting Template Name" := ReqWkshTemplate.Name;
+ ManufacturingSetup."Subcontracting Batch Name" := RequisitionWkshName.Name;
+ ManufacturingSetup.Modify();
+ end;
+
+ procedure CreateReqWkshTemplate(var ReqWkshTemplate: Record "Req. Wksh. Template"; Recurring: Boolean)
+ begin
+ ReqWkshTemplate.Init();
+ ReqWkshTemplate.Validate(
+ Name,
+ CopyStr(LibraryUtility.GenerateRandomCode(ReqWkshTemplate.FieldNo(Name), Database::"Req. Wksh. Template"),
+ 1,
+ LibraryUtility.GetFieldLength(Database::"Req. Wksh. Template", ReqWkshTemplate.FieldNo(Name))));
+ ReqWkshTemplate.Validate(Description, ReqWkshTemplate.Name); // Validate Description as Name because value is not important.
+ ReqWkshTemplate.Recurring := Recurring;
+ ReqWkshTemplate.Validate(Type, ReqWkshTemplate.Type::Subcontracting);
+ ReqWkshTemplate.Validate("Page ID", Page::"Subc. Subcontracting Worksheet");
+ ReqWkshTemplate.Insert(true);
+ end;
+
+ procedure CreateRequisitionWkshName(var RequisitionWkshName: Record "Requisition Wksh. Name"; WorksheetTemplateName: Text[10])
+ begin
+ // Create Requisition Wksh. Name with a random Name of String length less than 10.
+ RequisitionWkshName.Init();
+ RequisitionWkshName.Validate("Worksheet Template Name", WorksheetTemplateName);
+ RequisitionWkshName.Validate(
+ Name,
+ CopyStr(
+ LibraryUtility.GenerateRandomCode(RequisitionWkshName.FieldNo(Name), Database::"Requisition Wksh. Name"),
+ 1, LibraryUtility.GetFieldLength(Database::"Requisition Wksh. Name", RequisitionWkshName.FieldNo(Name))));
+ RequisitionWkshName.Insert(true);
+ end;
+
+ procedure PostConsumptionForComponent(ProdOrderLine: Record "Prod. Order Line"; ProdOrderComponent: Record "Prod. Order Component"; ComponentItem: Record Item; Qty: Decimal)
+ var
+ ItemJournalBatch: Record "Item Journal Batch";
+ ItemJournalLine: Record "Item Journal Line";
+ begin
+ LibraryManufacturing.CreateConsumptionJournalLine(
+ ItemJournalBatch, ProdOrderLine, ComponentItem, WorkDate(),
+ ProdOrderComponent."Location Code", '', Qty, 0);
+ ItemJournalLine.SetRange("Journal Template Name", ItemJournalBatch."Journal Template Name");
+ ItemJournalLine.SetRange("Journal Batch Name", ItemJournalBatch.Name);
+ ItemJournalLine.FindFirst();
+ ItemJournalLine.Validate("Prod. Order Comp. Line No.", ProdOrderComponent."Line No.");
+ ItemJournalLine.Modify(true);
+ LibraryInventory.PostItemJournalBatch(ItemJournalBatch);
+ end;
+
+ procedure PostConsumptionForAllComponents(var ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ComponentItem: Record Item;
+ ProdOrderLine: Record "Prod. Order Line";
+ begin
+ if ProdOrderComponent.FindSet() then
+ repeat
+ ComponentItem.Get(ProdOrderComponent."Item No.");
+ ProdOrderLine.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.");
+ PostConsumptionForComponent(ProdOrderLine, ProdOrderComponent, ComponentItem, ProdOrderComponent."Expected Quantity");
+ until ProdOrderComponent.Next() = 0;
+ end;
+
+ procedure PostOutputForProdOrderLine(ProdOrderLine: Record "Prod. Order Line"; Qty: Decimal)
+ begin
+ LibraryManufacturing.PostOutput(ProdOrderLine, Qty, WorkDate(), 0);
+ end;
+
+ procedure CreateProdOrderRtngCommentLine(Stat: Enum "Production Order Status"; ProdOrderNo: Code[20]; RoutingRefNo: Integer; RoutingNo: Code[20]; OperationNo: Code[10])
+ var
+ ProdOrderRtngCommentLine: Record "Prod. Order Rtng Comment Line";
+
+ RecRef: RecordRef;
+ begin
+ ProdOrderRtngCommentLine.Init();
+ ProdOrderRtngCommentLine.Status := Stat;
+ ProdOrderRtngCommentLine."Prod. Order No." := ProdOrderNo;
+ ProdOrderRtngCommentLine."Routing Reference No." := RoutingRefNo;
+ ProdOrderRtngCommentLine."Routing No." := RoutingNo;
+ ProdOrderRtngCommentLine."Operation No." := OperationNo;
+ RecRef.GetTable(ProdOrderRtngCommentLine);
+ ProdOrderRtngCommentLine.Validate(ProdOrderRtngCommentLine."Line No.", LibraryUtility.GetNewLineNo(RecRef, ProdOrderRtngCommentLine.FieldNo("Line No.")));
+ ProdOrderRtngCommentLine.Insert(true);
+
+ ProdOrderRtngCommentLine.Validate(
+ ProdOrderRtngCommentLine.Comment,
+ Format(ProdOrderRtngCommentLine."Prod. Order No.") + Format(ProdOrderRtngCommentLine."Routing Reference No.") + Format(ProdOrderRtngCommentLine."Line No."));
+ ProdOrderRtngCommentLine.Modify(true);
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcManagementLibrary.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcManagementLibrary.Codeunit.al
new file mode 100644
index 0000000000..a8eb7264cf
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcManagementLibrary.Codeunit.al
@@ -0,0 +1,263 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Setup;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Vendor;
+
+codeunit 139983 "Subc. Management Library"
+{
+ procedure Initialize()
+ begin
+ CreateSubcontractingManagementSetup();
+ end;
+
+ procedure CreateSubcontractingManagementSetup()
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ if not ManufacturingSetup.Get() then begin
+ ManufacturingSetup.Init();
+ ManufacturingSetup.Insert(true);
+ end;
+ end;
+
+ procedure CreateSubContractingPrice(var SubcontractorPrices: Record "Subcontractor Price"; WorkCenterNo: Code[20]; VendorNo: Code[20]; ItemNo: Code[20]; StandardTaskCode: Code[10]; VariantCode: Code[10]; StartDate: Date; UnitOfMeasureCode: Code[10]; MinimumQuantity: Decimal; CurrencyCode: Code[10])
+ begin
+ SubcontractorPrices.Init();
+ SubcontractorPrices.Validate("Work Center No.", WorkCenterNo);
+ SubcontractorPrices.Validate("Vendor No.", VendorNo);
+ SubcontractorPrices.Validate("Item No.", ItemNo);
+ SubcontractorPrices.Validate("Standard Task Code", StandardTaskCode);
+ SubcontractorPrices.Validate("Variant Code", VariantCode);
+ SubcontractorPrices.Validate("Starting Date", StartDate);
+ SubcontractorPrices.Validate("Unit of Measure Code", UnitOfMeasureCode);
+ SubcontractorPrices.Validate("Minimum Quantity", MinimumQuantity);
+ SubcontractorPrices.Validate("Currency Code", CurrencyCode);
+ SubcontractorPrices.Insert(true);
+ end;
+
+ procedure CreateSubcontractorPrice(Item: Record Item; WorkCenterNo: Code[20]; var SubcontractorPrice: Record "Subcontractor Price")
+ var
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ LibraryRandom: Codeunit "Library - Random";
+ i: Integer;
+ NoOfLoops: Integer;
+ begin
+ SubcontractorPrice.DeleteAll();
+ NoOfLoops := LibraryRandom.RandInt(20);
+
+ WorkCenter.Get(WorkCenterNo);
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ for i := 1 to NoOfLoops do begin
+ SubcontractorPrice.Init();
+ SubcontractorPrice."Vendor No." := Vendor."No.";
+ SubcontractorPrice."Item No." := Item."No.";
+ SubcontractorPrice."Work Center No." := WorkCenter."No.";
+ SubcontractorPrice."Unit of Measure Code" := Item."Base Unit of Measure";
+ SubcontractorPrice."Currency Code" := Vendor."Currency Code";
+ SubcontractorPrice."Minimum Quantity" := i;
+ SubcontractorPrice."Direct Unit Cost" := LibraryRandom.RandInt(100);
+ SubcontractorPrice.Insert();
+ end;
+ end;
+
+ procedure UpdateProdBomWithComponentSupplyMethod(Item: Record Item; ComponentSupplyMethod: Enum "Component Supply Method")
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ begin
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ ProductionBOMLine.FindLast();
+ ProductionBOMLine."Component Supply Method" := ComponentSupplyMethod;
+ ProductionBOMLine.Modify(true);
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ procedure UpdateProdOrderCompWithLocationCode(ProdOrderNo: Code[20])
+ var
+ Location: Record Location;
+ ProdOrderComp: Record "Prod. Order Component";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ ProdOrderComp."Location Code" := Location.Code;
+ ProdOrderComp.Modify();
+ end;
+
+ procedure UpdateVendorWithSubcontractingLocationCode(WorkCenter: Record "Work Center")
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ begin
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ Vendor."Subc. Location Code" := Location.Code;
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+ end;
+
+ procedure CreateAndRefreshProductionOrder(var ProductionOrder: Record "Production Order"; ProdOrderStatus: Enum "Production Order Status"; ProdOrderSourceType: Enum "Prod. Order Source Type"; SourceNo: Code[20]; Quantity: Decimal)
+ var
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ begin
+ LibraryManufacturing.CreateAndRefreshProductionOrder(ProductionOrder, ProdOrderStatus, ProdOrderSourceType, SourceNo, Quantity);
+ end;
+
+ procedure CreateAndRefreshProductionOrder(var ProductionOrder: Record "Production Order"; ProdOrderStatus: Enum "Production Order Status"; ProdOrderSourceType: Enum "Prod. Order Source Type"; SourceNo: Code[20]; Quantity: Decimal; LocationCode: Code[10])
+ var
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ begin
+ LibraryManufacturing.CreateProductionOrder(ProductionOrder, ProdOrderStatus, ProdOrderSourceType, SourceNo, Quantity);
+ ProductionOrder.Validate("Location Code", LocationCode);
+ ProductionOrder.Modify();
+
+ LibraryManufacturing.RefreshProdOrder(ProductionOrder, false, true, true, true, false);
+ end;
+
+ procedure UpdateSubMgmtSetup_ComponentAtLocation(CompAtLocation: Enum "Components at Location")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Subc. Default Comp. Location" := CompAtLocation;
+ ManufacturingSetup.Modify();
+ end;
+
+ procedure CreateSubcontractingOrderFromProdOrderRtngPage(RoutingNo: Code[20]; WorkCenterNo: Code[20])
+ var
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ begin
+ ProdOrderRtngLine.SetRange("Routing No.", RoutingNo);
+ ProdOrderRtngLine.SetRange("Work Center No.", WorkCenterNo);
+ ProdOrderRtngLine.FindFirst();
+
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRtngLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+ end;
+
+ procedure SetupInventorySetup()
+ var
+ InventorySetup: Record "Inventory Setup";
+ Location: Record Location;
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryUtility: Codeunit "Library - Utility";
+ begin
+ if not InventorySetup.Get() then
+ InventorySetup.Init();
+
+ LibraryInventory.NoSeriesSetup(InventorySetup);
+ InventorySetup."Inventory Put-away Nos." := LibraryUtility.GetGlobalNoSeriesCode();
+ InventorySetup."Direct Transfer Posting" := InventorySetup."Direct Transfer Posting"::"Direct Transfer";
+ InventorySetup.Modify();
+ LibraryInventory.UpdateInventoryPostingSetup(Location);
+ end;
+
+ procedure CreateTransferRoute(WorkCenter: Record "Work Center"; ProductionOrder: Record "Production Order")
+ var
+ TransitLocation: Record Location;
+ ProdOrderComp: Record "Prod. Order Component";
+ TransferRoute: Record "Transfer Route";
+ Vendor: Record Vendor;
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ begin
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ ProdOrderComp.SetRange(Status, ProductionOrder.Status);
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ LibraryWarehouse.CreateInTransitLocation(TransitLocation);
+ LibraryWarehouse.CreateAndUpdateTransferRoute(TransferRoute, ProdOrderComp."Location Code", Vendor."Subc. Location Code", TransitLocation.Code, '', '');
+ end;
+
+ procedure UpdateManufacturingSetupWithSubcontractingLocation()
+ var
+ Location: Record Location;
+ ManufacturingSetup: Record "Manufacturing Setup";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ begin
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Components at Location" := Location.Code;
+ ManufacturingSetup.Modify();
+ UpdateSubMgmtSetup_ComponentAtLocation("Components at Location"::Manufacturing);
+ end;
+
+ procedure CreateReqWkshTemplateAndName(var ReqWkshTemplate: Record "Req. Wksh. Template"; var RequisitionWkshName: Record "Requisition Wksh. Name")
+ var
+ LibraryUtility: Codeunit "Library - Utility";
+ begin
+ ReqWkshTemplate.SetRange(Type, ReqWkshTemplate.Type::Subcontracting);
+ ReqWkshTemplate.SetRange(Recurring, false);
+ if not ReqWkshTemplate.FindFirst() then begin
+ ReqWkshTemplate.Init();
+ ReqWkshTemplate.Validate(
+ Name, CopyStr(LibraryUtility.GenerateRandomCode(ReqWkshTemplate.FieldNo(Name), Database::"Req. Wksh. Template"), 1, 10));
+ ReqWkshTemplate.Insert(true);
+ ReqWkshTemplate.Validate(Type, ReqWkshTemplate.Type::Subcontracting);
+ ReqWkshTemplate."Page ID" := Page::"Subc. Subcontracting Worksheet";
+ ReqWkshTemplate.Modify(true);
+ end;
+
+ RequisitionWkshName.Init();
+ RequisitionWkshName.Validate("Worksheet Template Name", ReqWkshTemplate.Name);
+ RequisitionWkshName.Validate(
+ Name,
+ CopyStr(LibraryUtility.GenerateRandomCode(RequisitionWkshName.FieldNo(Name), Database::"Requisition Wksh. Name"),
+ 1, LibraryUtility.GetFieldLength(Database::"Requisition Wksh. Name", RequisitionWkshName.FieldNo(Name))));
+ RequisitionWkshName.Insert(true);
+ end;
+
+ procedure CreateWIPLedgerEntry(var WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; ItemNo: Code[20]; LocationCode: Code[10]; ProductionOrder: Record "Production Order"; ProdOrderLine: Record "Prod. Order Line"; ProdOrderRoutingLine: Record "Prod. Order Routing Line"; WorkCenterNo: Code[20]; QuantityBase: Decimal; InTransit: Boolean)
+ var
+ Item: Record Item;
+ begin
+ if WIPLedgerEntry.FindLast() then;
+ WIPLedgerEntry.Init();
+ WIPLedgerEntry."Entry No." := WIPLedgerEntry.GetNextEntryNo();
+ WIPLedgerEntry."Item No." := ItemNo;
+ WIPLedgerEntry."Location Code" := LocationCode;
+ WIPLedgerEntry."Prod. Order Status" := "Production Order Status"::Released;
+ WIPLedgerEntry."Prod. Order No." := ProductionOrder."No.";
+ WIPLedgerEntry."Prod. Order Line No." := ProdOrderLine."Line No.";
+ WIPLedgerEntry."Routing No." := ProdOrderRoutingLine."Routing No.";
+ WIPLedgerEntry."Routing Reference No." := ProdOrderRoutingLine."Routing Reference No.";
+ WIPLedgerEntry."Operation No." := ProdOrderRoutingLine."Operation No.";
+ WIPLedgerEntry."Work Center No." := WorkCenterNo;
+ WIPLedgerEntry."Quantity (Base)" := QuantityBase;
+ WIPLedgerEntry."In Transit" := InTransit;
+ Item.SetLoadFields("Base Unit of Measure");
+ Item.Get(ItemNo);
+ WIPLedgerEntry."Base Unit of Measure" := Item."Base Unit of Measure";
+ WIPLedgerEntry.Insert();
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcProdOCompTestExt.PageExt.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcProdOCompTestExt.PageExt.al
new file mode 100644
index 0000000000..112f403799
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcProdOCompTestExt.PageExt.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+
+pageextension 139980 "Subc. ProdOComp TestExt" extends "Prod. Order Components"
+{
+ layout
+ {
+ modify("Location Code")
+ {
+ Visible = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcProdOrderCheckLib.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcProdOrderCheckLib.Codeunit.al
new file mode 100644
index 0000000000..16c86b8317
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcProdOrderCheckLib.Codeunit.al
@@ -0,0 +1,234 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Document;
+using Microsoft.Purchases.Document;
+
+codeunit 139987 "Subc. ProdOrderCheckLib"
+{
+ var
+ Assert: Codeunit Assert;
+ Description2MismatchOnLineLbl: Label 'Description 2 mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ Description2MismatchOnOperationLbl: Label 'Description 2 mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ 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;
+ ExpectedComponentsButFoundLbl: Label 'Expected %1 components, but found %2', Locked = true;
+ ExpectedRoutingLinesButFoundLbl: Label 'Expected %1 routing lines, but found %2', Locked = true;
+ FlushingMethodMismatchOnLineLbl: Label 'Flushing Method mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ IndirectCostPercentMismatchOnOperationLbl: Label 'Indirect Cost %% mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ ItemNoMismatchOnLineLbl: Label 'Item No. mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ LocationCodeMismatchOnLineLbl: Label 'Location Code mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ NoMismatchOnOperationLbl: Label 'No. mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ OverheadRateMismatchOnOperationLbl: Label 'Overhead Rate mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ ProdOrderComponentWithLineNoNotFoundLbl: Label 'Production Order Component with Line No. %1 not found', Locked = true;
+ ProdOrderRoutingLineWithOperationNoNotFoundLbl: Label 'Production Order Routing Line with Operation No. %1 not found', Locked = true;
+ QuantityMismatchOnLineLbl: Label 'Quantity mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ QuantityPerMismatchOnLineLbl: Label 'Quantity per mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ RoutingLinkCodeMismatchOnLineLbl: Label 'Routing Link Code mismatch on Line %1. Expected: %2, Actual: %3', Locked = true;
+ RoutingLinkCodeMismatchOnOperationLbl: Label 'Routing Link Code mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ RunTimeMismatchOnOperationLbl: Label 'Run Time mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ SetupTimeMismatchOnOperationLbl: Label 'Setup Time mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ TypeMismatchOnOperationLbl: Label 'Type mismatch on Operation %1. Expected: %2, Actual: %3', Locked = true;
+ 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;
+
+ procedure VerifyProdOrder(PurchLine: Record "Purchase Line"; var ProdOrder: Record "Production Order")
+ begin
+ PurchLine.SetLoadFields("Prod. Order No.", Quantity);
+ PurchLine.Get(PurchLine."Document Type", PurchLine."Document No.", PurchLine."Line No.");
+ Assert.AreNotEqual('', PurchLine."Prod. Order No.", 'Production Order No. should be set on Purchase Line');
+
+ ProdOrder.Get("Production Order Status"::Released, PurchLine."Prod. Order No.");
+ Assert.AreEqual(PurchLine.Quantity, ProdOrder.Quantity, 'Production Order should have correct Quantity');
+ end;
+
+ procedure VerifyProdOrderComponentsMatchTempRecords(ProdOrder: Record "Production Order"; var TempProdOrderComponent: Record "Prod. Order Component" temporary)
+ var
+ ActualProdOrderComponent: Record "Prod. Order Component";
+ ActualComponentCount: Integer;
+ LineNo: Integer;
+ TempComponentCount: Integer;
+ begin
+ // Count temporary components
+ TempProdOrderComponent.Reset();
+ TempComponentCount := TempProdOrderComponent.Count();
+
+ // Count actual components
+ ActualProdOrderComponent.SetRange(Status, ProdOrder.Status);
+ ActualProdOrderComponent.SetRange("Prod. Order No.", ProdOrder."No.");
+ ActualComponentCount := ActualProdOrderComponent.Count();
+
+ // Verify counts match
+ Assert.AreEqual(TempComponentCount, ActualComponentCount,
+ StrSubstNo(ExpectedComponentsButFoundLbl, TempComponentCount, ActualComponentCount));
+
+ // Verify each component in sequence
+ TempProdOrderComponent.Reset();
+ if TempProdOrderComponent.FindSet() then
+ repeat
+ LineNo := TempProdOrderComponent."Line No.";
+
+ // Find corresponding actual component
+ ActualProdOrderComponent.SetRange("Line No.", LineNo);
+ Assert.IsTrue(ActualProdOrderComponent.FindFirst(),
+ StrSubstNo(ProdOrderComponentWithLineNoNotFoundLbl, LineNo));
+
+ // Verify key fields match
+ VerifyProdOrderComponentFields(TempProdOrderComponent, ActualProdOrderComponent);
+ until TempProdOrderComponent.Next() = 0;
+ end;
+
+ local procedure VerifyProdOrderComponentFields(TempComponent: Record "Prod. Order Component" temporary; ActualComponent: Record "Prod. Order Component")
+ begin
+ // Verify essential fields match between temporary and actual component
+ Assert.AreEqual(TempComponent."Item No.", ActualComponent."Item No.",
+ StrSubstNo(ItemNoMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Item No.", ActualComponent."Item No."));
+
+ Assert.AreEqual(TempComponent."Flushing Method", ActualComponent."Flushing Method",
+ StrSubstNo(FlushingMethodMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Flushing Method", ActualComponent."Flushing Method"));
+
+ Assert.AreEqual(TempComponent."Routing Link Code", ActualComponent."Routing Link Code",
+ StrSubstNo(RoutingLinkCodeMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Routing Link Code", ActualComponent."Routing Link Code"));
+
+ Assert.AreEqual(TempComponent."Location Code", ActualComponent."Location Code",
+ StrSubstNo(LocationCodeMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Location Code", ActualComponent."Location Code"));
+
+ // Verify quantities if set in temporary record
+ if TempComponent."Quantity per" <> 0 then
+ Assert.AreEqual(TempComponent."Quantity per", ActualComponent."Quantity per",
+ StrSubstNo(QuantityPerMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Quantity per", ActualComponent."Quantity per"));
+
+ if TempComponent.Quantity <> 0 then
+ Assert.AreEqual(TempComponent.Quantity, ActualComponent.Quantity,
+ StrSubstNo(QuantityMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent.Quantity, ActualComponent.Quantity));
+
+ // Verify dates if set in temporary record
+ if TempComponent."Due Date" <> 0D then
+ Assert.AreEqual(TempComponent."Due Date", ActualComponent."Due Date",
+ StrSubstNo(DueDateMismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Due Date", ActualComponent."Due Date"));
+
+ // Verify Description 2 if set in temporary record
+ if TempComponent."Description 2" <> '' then
+ Assert.AreEqual(TempComponent."Description 2", ActualComponent."Description 2",
+ StrSubstNo(Description2MismatchOnLineLbl,
+ TempComponent."Line No.", TempComponent."Description 2", ActualComponent."Description 2"));
+ end;
+
+ procedure VerifyProdOrderRoutingLinesMatchTempRecords(ProdOrder: Record "Production Order"; var TempProdOrderRoutingLine: Record "Prod. Order Routing Line" temporary)
+ var
+ ActualProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ OperationNo: Code[10];
+ ActualRoutingCount: Integer;
+ TempRoutingCount: Integer;
+ begin
+ // Count temporary routing lines
+ TempProdOrderRoutingLine.Reset();
+ TempRoutingCount := TempProdOrderRoutingLine.Count();
+
+ // Count actual routing lines
+ ActualProdOrderRoutingLine.SetRange(Status, ProdOrder.Status);
+ ActualProdOrderRoutingLine.SetRange("Prod. Order No.", ProdOrder."No.");
+ ActualRoutingCount := ActualProdOrderRoutingLine.Count();
+
+ // Verify counts match
+ Assert.AreEqual(TempRoutingCount, ActualRoutingCount,
+ StrSubstNo(ExpectedRoutingLinesButFoundLbl, TempRoutingCount, ActualRoutingCount));
+
+ // Verify each routing line in sequence
+ TempProdOrderRoutingLine.Reset();
+ if TempProdOrderRoutingLine.FindSet() then
+ repeat
+ OperationNo := TempProdOrderRoutingLine."Operation No.";
+
+ // Find corresponding actual routing line
+ ActualProdOrderRoutingLine.SetRange("Operation No.", OperationNo);
+ Assert.IsTrue(ActualProdOrderRoutingLine.FindFirst(),
+ StrSubstNo(ProdOrderRoutingLineWithOperationNoNotFoundLbl, OperationNo));
+
+ // Verify key fields match
+ VerifyProdOrderRoutingLineFields(TempProdOrderRoutingLine, ActualProdOrderRoutingLine);
+ until TempProdOrderRoutingLine.Next() = 0;
+ end;
+
+ local procedure VerifyProdOrderRoutingLineFields(TempRoutingLine: Record "Prod. Order Routing Line" temporary; ActualRoutingLine: Record "Prod. Order Routing Line")
+ begin
+ // Verify essential fields match between temporary and actual routing line
+ Assert.AreEqual(TempRoutingLine.Type, ActualRoutingLine.Type,
+ StrSubstNo(TypeMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine.Type, ActualRoutingLine.Type));
+
+ Assert.AreEqual(TempRoutingLine."No.", ActualRoutingLine."No.",
+ StrSubstNo(NoMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."No.", ActualRoutingLine."No."));
+
+ Assert.AreEqual(TempRoutingLine."Work Center No.", ActualRoutingLine."Work Center No.",
+ StrSubstNo(WorkCenterNoMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Work Center No.", ActualRoutingLine."Work Center No."));
+
+ Assert.AreEqual(TempRoutingLine."Routing Link Code", ActualRoutingLine."Routing Link Code",
+ StrSubstNo(RoutingLinkCodeMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Routing Link Code", ActualRoutingLine."Routing Link Code"));
+
+ // Verify Work Center Group Code if set
+ if TempRoutingLine."Work Center Group Code" <> '' then
+ Assert.AreEqual(TempRoutingLine."Work Center Group Code", ActualRoutingLine."Work Center Group Code",
+ StrSubstNo(WorkCenterGroupCodeMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Work Center Group Code", ActualRoutingLine."Work Center Group Code"));
+
+ // Verify Unit Cost Calculation if set
+ Assert.AreEqual(TempRoutingLine."Unit Cost Calculation", ActualRoutingLine."Unit Cost Calculation",
+ StrSubstNo(UnitCostCalculationMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Unit Cost Calculation", ActualRoutingLine."Unit Cost Calculation"));
+
+ // Verify cost fields if set in temporary record
+ if TempRoutingLine."Direct Unit Cost" <> 0 then
+ Assert.AreEqual(TempRoutingLine."Direct Unit Cost", ActualRoutingLine."Direct Unit Cost",
+ StrSubstNo(DirectUnitCostMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Direct Unit Cost", ActualRoutingLine."Direct Unit Cost"));
+
+ if TempRoutingLine."Indirect Cost %" <> 0 then
+ Assert.AreEqual(TempRoutingLine."Indirect Cost %", ActualRoutingLine."Indirect Cost %",
+ StrSubstNo(IndirectCostPercentMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Indirect Cost %", ActualRoutingLine."Indirect Cost %"));
+
+ if TempRoutingLine."Overhead Rate" <> 0 then
+ Assert.AreEqual(TempRoutingLine."Overhead Rate", ActualRoutingLine."Overhead Rate",
+ StrSubstNo(OverheadRateMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Overhead Rate", ActualRoutingLine."Overhead Rate"));
+
+ // Verify time fields if set in temporary record
+ if TempRoutingLine."Setup Time" <> 0 then
+ Assert.AreEqual(TempRoutingLine."Setup Time", ActualRoutingLine."Setup Time",
+ StrSubstNo(SetupTimeMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Setup Time", ActualRoutingLine."Setup Time"));
+
+ if TempRoutingLine."Run Time" <> 0 then
+ Assert.AreEqual(TempRoutingLine."Run Time", ActualRoutingLine."Run Time",
+ StrSubstNo(RunTimeMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Run Time", ActualRoutingLine."Run Time"));
+
+ // Verify description if set
+ if TempRoutingLine.Description <> '' then
+ Assert.AreEqual(TempRoutingLine.Description, ActualRoutingLine.Description,
+ StrSubstNo(DescriptionMismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine.Description, ActualRoutingLine.Description));
+
+ // Verify Description 2 if set in temporary record
+ if TempRoutingLine."Description 2" <> '' then
+ Assert.AreEqual(TempRoutingLine."Description 2", ActualRoutingLine."Description 2",
+ StrSubstNo(Description2MismatchOnOperationLbl,
+ TempRoutingLine."Operation No.", TempRoutingLine."Description 2", ActualRoutingLine."Description 2"));
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcSetupLibrary.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcSetupLibrary.Codeunit.al
new file mode 100644
index 0000000000..d7ba6985e2
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcSetupLibrary.Codeunit.al
@@ -0,0 +1,55 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.WorkCenter;
+
+codeunit 139988 "Subc. Setup Library"
+{
+ var
+ LibraryInventory: Codeunit "Library - Inventory";
+ SubCreateProdOrdWizLibrary: Codeunit "Subc. CreateProdOrdWizLibrary";
+
+ procedure InitSetupFields()
+ var
+ Item: Record Item;
+ ManufacturingSetup: Record "Manufacturing Setup";
+ WorkCenter: Record "Work Center";
+ begin
+ // Create Work Center for subcontracting
+ SubCreateProdOrdWizLibrary.CreateAndCalculateNeededWorkCenter(WorkCenter, true);
+
+ LibraryInventory.CreateItem(Item);
+
+ // Set required fields for production order creation
+ if not ManufacturingSetup.Get() then begin
+ ManufacturingSetup.Init();
+ ManufacturingSetup.Insert();
+ end;
+
+ ManufacturingSetup."Subc. Default Comp. Location" := ManufacturingSetup."Subc. Default Comp. Location"::Purchase;
+ ManufacturingSetup.Modify();
+ end;
+
+ internal procedure InitialSetupForGenProdPostingGroup()
+ var
+ GenProdPostingGroup1: Record Microsoft.Finance.GeneralLedger.Setup."Gen. Product Posting Group";
+ GenProdPostingGroup2: Record Microsoft.Finance.GeneralLedger.Setup."Gen. Product Posting Group";
+ begin
+ // Assign Def. VAT Prod. Posting Group to a Gen. Prod. Posting Group based on W1.
+ GenProdPostingGroup2.SetFilter("Def. VAT Prod. Posting Group", '<>%1', '');
+ if not GenProdPostingGroup2.FindFirst() then
+ exit; // All Gen. Prod. Posting Groups have Def. VAT Prod. Posting Group assigned.
+
+ GenProdPostingGroup1.SetFilter("Def. VAT Prod. Posting Group", '');
+ if GenProdPostingGroup1.FindSet(true) then
+ repeat
+ GenProdPostingGroup1."Def. VAT Prod. Posting Group" := GenProdPostingGroup2."Def. VAT Prod. Posting Group";
+ GenProdPostingGroup1.Modify(true);
+ until GenProdPostingGroup1.Next() = 0;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcTestManSubscription.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcTestManSubscription.Codeunit.al
new file mode 100644
index 0000000000..569985e50b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcTestManSubscription.Codeunit.al
@@ -0,0 +1,18 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Subcontracting;
+
+codeunit 139985 "Subc. Test Man. Subscription"
+{
+ EventSubscriberInstance = Manual;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Subc. Purchase Order Creator", OnBeforeShowCreatedPurchaseOrder, '', false, false)]
+ local procedure OnBeforeShowCreatedPurchaseOrder(var IsHandled: Boolean)
+ begin
+ IsHandled := true;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Libraries/SubcWarehouseLibrary.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Libraries/SubcWarehouseLibrary.Codeunit.al
new file mode 100644
index 0000000000..8a66a3b351
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Libraries/SubcWarehouseLibrary.Codeunit.al
@@ -0,0 +1,924 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Transfer;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+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";
+
+ ///
+ /// 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."Subc. Location Code" := LibraryWarehouse.CreateLocation(Location);
+ Vendor1.Modify(true);
+ LibraryPurchase.CreateSubcontractor(Vendor2);
+ Vendor2."Subc. 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;
+
+ procedure CreateParallelRoutingItemWithSubcontracting(var Item: Record Item; var MachineCenter: array[2] of Record "Machine Center"; var WorkCenter: array[2] of Record "Work Center")
+ var
+ Item2: Record Item;
+ Item3: Record Item;
+ Location: Record Location;
+ ProductionBOMHeader: Record "Production BOM Header";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ Vendor1: Record Vendor;
+ Vendor2: Record Vendor;
+ WorkCenterNonSC: Record "Work Center";
+ ProductionBOMNo: Code[20];
+ begin
+ // Create non-subcontracting work center with machine centers for ops 10 and 20
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenterNonSC, LibraryRandom.RandDec(10, 2));
+ LibraryManufacturing.CreateMachineCenterWithCalendar(
+ MachineCenter[1], WorkCenterNonSC."No.", LibraryRandom.RandDec(10, 1));
+ LibraryManufacturing.CreateMachineCenterWithCalendar(
+ MachineCenter[2], WorkCenterNonSC."No.", LibraryRandom.RandDec(10, 1));
+
+ // Create subcontracting work center for op 30 (parallel SC branch) with dedicated vendor + location
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter[1], LibraryRandom.RandDec(10, 2));
+ LibraryPurchase.CreateSubcontractor(Vendor1);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor1."Subc. Location Code" := Location.Code;
+ Vendor1.Modify(true);
+ WorkCenter[1]."Subcontractor No." := Vendor1."No.";
+ WorkCenter[1].Modify(true);
+
+ // Create subcontracting work center for op 40 (last SC operation) with dedicated vendor + location
+ SubcLibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter[2], LibraryRandom.RandDec(10, 2));
+ LibraryPurchase.CreateSubcontractor(Vendor2);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor2."Subc. Location Code" := Location.Code;
+ Vendor2.Modify(true);
+ WorkCenter[2]."Subcontractor No." := Vendor2."No.";
+ WorkCenter[2].Modify(true);
+
+ // Create a PARALLEL routing: 10 → 20 | 30 → 40
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Parallel);
+
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Machine Center", MachineCenter[1]."No.");
+ RoutingLine."Next Operation No." := '20|30';
+ RoutingLine.Modify(true);
+
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '20', RoutingLine.Type::"Machine Center", MachineCenter[2]."No.");
+ RoutingLine."Previous Operation No." := '10';
+ RoutingLine."Next Operation No." := '40';
+ RoutingLine."Transfer WIP Item" := true;
+ RoutingLine.Modify(true);
+
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '30', RoutingLine.Type::"Work Center", WorkCenter[1]."No.");
+ RoutingLine."Previous Operation No." := '10';
+ RoutingLine."Next Operation No." := '40';
+ RoutingLine."Transfer WIP Item" := true;
+ RoutingLine.Modify(true);
+
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '40', RoutingLine.Type::"Work Center", WorkCenter[2]."No.");
+ RoutingLine."Previous Operation No." := '20|30';
+ RoutingLine."Transfer WIP Item" := true;
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ // Create two component items and a certified production BOM
+ LibraryInventory.CreateItem(Item2);
+ LibraryInventory.CreateItem(Item3);
+ ProductionBOMNo := LibraryManufacturing.CreateCertifProdBOMWithTwoComp(
+ ProductionBOMHeader, Item2."No.", Item3."No.", 1);
+
+ // Create the finished item linked to the parallel routing and production BOM
+ LibraryManufacturing.CreateItemManufacturing(
+ Item, "Costing Method"::FIFO, LibraryRandom.RandDec(10, 2),
+ "Reordering Policy"::" ", "Flushing Method"::Backward, RoutingHeader."No.", 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;
+
+ ///
+ /// 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;
+
+ ///
+ /// 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 subcontracting 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 subcontracting requirement worksheets in the subcontracting process.
+ ///
+ procedure UpdateSubMgmtSetupWithReqWkshTemplate()
+ begin
+ SubcLibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+ end;
+
+ ///
+ /// 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";
+ ManufacturingSetup: Record "Manufacturing Setup";
+ SubcCalculateSubContract: Report "Subc. Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ begin
+ // Get worksheet template and batch from setup
+ ManufacturingSetup.Get();
+
+ // Initialize requisition line for the Calculate Subcontracts report
+ RequisitionLine."Worksheet Template Name" := ManufacturingSetup."Subcontracting Template Name";
+ RequisitionLine."Journal Batch Name" := ManufacturingSetup."Subcontracting Batch Name";
+
+ // Calculate subcontracting lines to fill the worksheet
+ SubcCalculateSubContract.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContract.UseRequestPage(false);
+ SubcCalculateSubContract.RunModal();
+
+ // Find requisition lines for the production order
+ RequisitionLine.SetRange("Worksheet Template Name", ManufacturingSetup."Subcontracting Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", ManufacturingSetup."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;
+
+ ///
+ /// 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;
+
+ ///
+ /// 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;
+
+ ///
+ /// 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;
+
+ ///
+ /// 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;
+
+ ///
+ /// 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."Subc. 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;
+
+ procedure CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(var TransferHeader: Record "Transfer Header"; var TransferLine: Record "Transfer Line"; FromLocation: Code[10]; ToLocation: Code[10]; InTransitCode: Code[10]; Item: Record Item; Quantity: Decimal)
+ var
+ TransferRoute: Record "Transfer Route";
+ begin
+ if Item."No." = '' then
+ LibraryInventory.CreateItem(Item);
+ LibraryWarehouse.CreateTransferRoute(TransferRoute, FromLocation, ToLocation);
+ LibraryWarehouse.CreateTransferHeader(TransferHeader, FromLocation, ToLocation, InTransitCode);
+ LibraryWarehouse.CreateTransferLine(TransferHeader, TransferLine, Item."No.", Quantity);
+ TransferLine.Validate("Transfer WIP Item", true);
+ TransferLine.Modify();
+ end;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcFeatureFlagTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcFeatureFlagTest.Codeunit.al
new file mode 100644
index 0000000000..ab7574d09e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcFeatureFlagTest.Codeunit.al
@@ -0,0 +1,272 @@
+#if not CLEAN28
+// ------------------------------------------------------------------------------------------------
+// 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.Manufacturing.Setup;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Purchases.Vendor;
+using System.Environment.Configuration;
+
+codeunit 139993 "Subc. Feature Flag Test"
+{
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ // [FEATURE] Subcontracting] [Feature Toggle]
+ Initialized := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryApplicationArea: Codeunit "Library - Application Area";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ Initialized: Boolean;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure AppAreaSetWhenSubcontractingEnabled()
+ var
+ ApplicationAreaSetup: Record "Application Area Setup";
+ ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
+ begin
+ // [SCENARIO] ApplicationAreaSetup."Subcontracting" is true when Legacy Subcontracting toggle is disabled (Subcontracting enabled)
+ Initialize();
+
+ // [GIVEN] ManufacturingSetup."Legacy Subcontracting" = false (Subcontracting app is active)
+ SetLegacySubcontracting(false);
+
+ // [WHEN] Application areas are reloaded
+ RefreshApplicationAreas();
+
+ // [THEN] ApplicationAreaSetup."Subcontracting" = true
+ ApplicationAreaSetup.Get(CompanyName());
+ Assert.IsTrue(ApplicationAreaSetup."Subcontracting", 'Subcontracting application area should be true when Legacy Subcontracting flag is disabled.');
+ Assert.IsTrue(ApplicationAreaMgmtFacade.GetApplicationAreaSetup().Contains('#Subcontracting'), 'Subcontracting string should be present in application area setup when enabled and on premium tier.');
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure AppAreaClearedWhenSubcontractingDisabled()
+ var
+ ApplicationAreaSetup: Record "Application Area Setup";
+ ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
+ begin
+ // [SCENARIO] ApplicationAreaSetup."Subcontracting" is false when Legacy Subcontracting toggle is enabled (Subcontracting disabled)
+ Initialize();
+
+ // [GIVEN] ManufacturingSetup."Legacy Subcontracting" = true (Legacy mode active, Subcontracting app inactive)
+ SetLegacySubcontracting(true);
+
+ // [WHEN] Application areas are reloaded
+ RefreshApplicationAreas();
+
+ // [THEN] ApplicationAreaSetup."Subcontracting" = false
+ ApplicationAreaSetup.Get(CompanyName());
+ Assert.IsFalse(ApplicationAreaSetup."Subcontracting", 'Subcontracting application area should be false when Legacy Subcontracting is enabled.');
+ Assert.IsFalse(ApplicationAreaMgmtFacade.GetApplicationAreaSetup().Contains('#Subcontracting'), 'Subcontracting string must not be present when disabled.');
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure AppAreaNotSetForEssentialExperienceTier()
+ var
+ ApplicationAreaSetup: Record "Application Area Setup";
+ ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
+ begin
+ // [SCENARIO] ApplicationAreaSetup."Subcontracting" is NOT set for Essential tier even when Legacy Subcontracting is disabled
+ // The subscriber only fires for OnGetPremiumExperienceAppAreas, not for Essential
+ Initialize();
+
+ // [GIVEN] ManufacturingSetup."Legacy Subcontracting" = false (Subcontracting would be active for Premium)
+ SetLegacySubcontracting(false);
+
+ // [WHEN] Essential experience tier is activated
+ LibraryApplicationArea.EnableEssentialSetup();
+
+ // [THEN] ApplicationAreaSetup."Subcontracting" = false (subscriber does not fire for Essential)
+ ApplicationAreaSetup.Get(CompanyName());
+ Assert.IsFalse(ApplicationAreaSetup."Subcontracting", 'Subcontracting app area must not be set for Essential experience tier.');
+ Assert.IsFalse(ApplicationAreaMgmtFacade.GetApplicationAreaSetup().Contains('#Subcontracting'), 'Subcontracting string must not be present when on essential tier.');
+
+ // Restore to Premium Setup
+ LibraryApplicationArea.EnablePremiumSetup();
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure GuardReturnsTrueWhenEnabled()
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ begin
+ // [SCENARIO] IsSubcontractingEnabled returns true when Legacy Subcontracting toggle is OFF
+ Initialize();
+
+ // [GIVEN] ManufacturingSetup."Legacy Subcontracting" = false
+ SetLegacySubcontracting(false);
+
+ // [WHEN] Call IsSubcontractingEnabled
+ // [THEN] Returns true
+#pragma warning disable AL0432
+ Assert.IsTrue(SubcFeatureFlagHandler.IsSubcontractingEnabled(), 'Guard should return true when Legacy Subcontracting is disabled.');
+#pragma warning restore AL0432
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure GuardReturnsFalseWhenDisabled()
+ var
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ begin
+ // [SCENARIO] IsSubcontractingEnabled returns false when Legacy Subcontracting toggle is ON
+ Initialize();
+
+ // [GIVEN] ManufacturingSetup."Legacy Subcontracting" = true
+ SetLegacySubcontracting(true);
+
+ // [WHEN] Call IsSubcontractingEnabled
+ // [THEN] Returns false
+#pragma warning disable AL0432
+ Assert.IsFalse(SubcFeatureFlagHandler.IsSubcontractingEnabled(), 'Guard should return false when Legacy Subcontracting is enabled.');
+#pragma warning restore AL0432
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure GuardReturnsFalseWhenSetupNotExists()
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ TempManufacturingSetupBackup: Record "Manufacturing Setup" temporary;
+#pragma warning disable AL0432
+ SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
+#pragma warning restore AL0432
+ begin
+ // [SCENARIO] IsSubcontractingEnabled returns false when no ManufacturingSetup record exists
+ Initialize();
+
+ ManufacturingSetup.Get();
+ TempManufacturingSetupBackup.Copy(ManufacturingSetup);
+ ManufacturingSetup.Delete();
+
+ // [WHEN] Call IsSubcontractingEnabled
+ // [THEN] Returns false
+#pragma warning disable AL0432
+ Assert.IsFalse(SubcFeatureFlagHandler.IsSubcontractingEnabled(), 'Guard should return false when ManufacturingSetup does not exist.');
+#pragma warning restore AL0432
+
+ // Restore ManufacturingSetup
+ ManufacturingSetup.Init();
+ ManufacturingSetup.Copy(TempManufacturingSetupBackup);
+ ManufacturingSetup.Insert();
+ Commit();
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure VendorCardSubcFieldsVisibleWhenEnabled()
+ var
+ Vendor: Record Vendor;
+ VendorCard: TestPage "Vendor Card";
+ begin
+ // [SCENARIO] Subcontracting fields on Vendor Card are visible when Subcontracting app area is active
+ Initialize();
+
+ // [GIVEN] Legacy Subcontracting is disabled (Subcontracting enabled) and Premium experience is activated
+ SetLegacySubcontracting(false);
+ RefreshApplicationAreas();
+
+ // [GIVEN] A vendor exists
+ LibraryPurchase.CreateVendor(Vendor);
+
+ // [WHEN] Open the Vendor Card
+ VendorCard.OpenEdit();
+ VendorCard.GotoRecord(Vendor);
+
+ // [THEN] Subcontracting fields are visible
+ Assert.IsTrue(VendorCard."Subc. Location Code".Visible(), '"Subc. Location Code" should be visible when Subcontracting is enabled.');
+ Assert.IsTrue(VendorCard."Subc. Linked to Work Center".Visible(), '"Subc. Linked to Work Center" should be visible when Subcontracting is enabled.');
+
+ VendorCard.Close();
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure VendorCardSubcFieldsNotVisibleWhenDisabled()
+ var
+ Vendor: Record Vendor;
+ VendorCard: TestPage "Vendor Card";
+ begin
+ // [SCENARIO] Subcontracting fields on Vendor Card are NOT accessible when Subcontracting app area is inactive
+ Initialize();
+
+ // [GIVEN] Legacy Subcontracting is enabled (Subcontracting disabled) and Premium experience is activated
+ SetLegacySubcontracting(true);
+ RefreshApplicationAreas();
+
+ // [GIVEN] A vendor exists
+ LibraryPurchase.CreateVendor(Vendor);
+
+ // [WHEN] Open the Vendor Card
+ VendorCard.OpenEdit();
+ VendorCard.GotoRecord(Vendor);
+
+ // [THEN] Subcontracting fields are not accessible (accessing them raises an error)
+ asserterror Assert.IsFalse(VendorCard."Subc. Location Code".Visible(), '"Subc. Location Code" should not be visible when Subcontracting is disabled.');
+ asserterror Assert.IsFalse(VendorCard."Subc. Linked to Work Center".Visible(), '"Subc. Linked to Work Center" should not be visible when Subcontracting is disabled.');
+
+ VendorCard.Close();
+ end;
+
+ local procedure Initialize()
+ var
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Feature Flag Test");
+
+ if Initialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Feature Flag Test");
+ LibraryApplicationArea.EnablePremiumSetup();
+ SubcontractingMgmtLibrary.Initialize();
+ LibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+ Initialized := true;
+ Commit();
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Feature Flag Test");
+ end;
+
+ local procedure SetLegacySubcontracting(Enabled: Boolean)
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ if not ManufacturingSetup.Get() then begin
+ ManufacturingSetup.Init();
+ ManufacturingSetup.Insert();
+ end;
+#pragma warning disable AL0432
+ ManufacturingSetup."Legacy Subcontracting" := Enabled;
+#pragma warning restore AL0432
+ ManufacturingSetup.Modify();
+ end;
+
+ local procedure RefreshApplicationAreas()
+ var
+ ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
+ begin
+ ApplicationAreaMgmtFacade.RefreshExperienceTierCurrentCompany();
+ end;
+}
+#endif
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcItemChargePostingTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcItemChargePostingTest.Codeunit.al
new file mode 100644
index 0000000000..13d0262ced
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcItemChargePostingTest.Codeunit.al
@@ -0,0 +1,325 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.VAT.Setup;
+using Microsoft.Foundation.Enums;
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Purchases.Vendor;
+
+codeunit 149917 "Subc. Item Charge Posting Test"
+{
+ // [FEATURE] Subcontracting Item Charge Posting
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ 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";
+ LibraryRandom: Codeunit "Library - Random";
+ 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";
+ IsInitialized: Boolean;
+ UnitCostCalculation: Option Time,Units;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandlePurchaseOrderPage')]
+ procedure ItemChargePostingForLastOperationSubcontractingReceipt()
+ var
+ Item: Record Item;
+ ItemCharge: Record "Item Charge";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseHeaderInvoice: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ PurchaseLineCharge: Record "Purchase Line";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ PurchRcptLine: Record "Purch. Rcpt. Line";
+ ReservationEntry: Record "Reservation Entry";
+ ValueEntry: Record "Value Entry";
+ Vendor: Record Vendor;
+ SubcWorkCenter: Record "Work Center";
+ NoSeriesCodeunit: Codeunit "No. Series";
+ Qty: Decimal;
+ SerialNo: Code[50];
+ begin
+ // [SCENARIO] Posting an item charge assigned to a subcontracting purchase receipt for the last routing operation should succeed.
+ // [SCENARIO] Before the fix, it errored with "The Capacity Ledger Entry does not exist" because the posting path incorrectly
+ // [SCENARIO] attempted to create a value entry against a capacity ledger entry instead of the output item ledger entry.
+
+ // [GIVEN] Subcontracting setup with a single-operation routing where the operation is a subcontracting work center (= last operation)
+ Initialize();
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateItemWithSingleSubcontractingOperation(Item, SubcWorkCenter);
+ AddSerialTrackingToItem(Item);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(SubcWorkCenter);
+
+ // [GIVEN] A released production order for qty = 1 (required for serial tracking)
+ Qty := 1;
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", Qty);
+
+ // [GIVEN] Assign a serial number to the 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, '', Qty);
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", SubcWorkCenter."No.");
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+
+ // [GIVEN] The subcontracting purchase order is found and its receipt posted
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", SubcWorkCenter."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ EnsureGeneralPostingSetupIsValid(PurchaseLine."Gen. Bus. Posting Group", PurchaseLine."Gen. Prod. Posting Group");
+
+ // [GIVEN] The serial number is assigned to the purchase line before posting receipt
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [GIVEN] The posted receipt line for the subcontracting order
+ PurchRcptLine.SetRange("Order No.", PurchaseHeader."No.");
+ PurchRcptLine.SetRange("Order Line No.", PurchaseLine."Line No.");
+ PurchRcptLine.FindFirst();
+
+ // [GIVEN] A purchase invoice at the same vendor with an item charge line
+ Vendor.Get(SubcWorkCenter."Subcontractor No.");
+ LibraryPurchase.CreatePurchHeader(PurchaseHeaderInvoice, PurchaseHeaderInvoice."Document Type"::Invoice, Vendor."No.");
+ LibraryInventory.CreateItemCharge(ItemCharge);
+ LibraryPurchase.CreatePurchaseLine(PurchaseLineCharge, PurchaseHeaderInvoice, "Purchase Line Type"::"Charge (Item)", ItemCharge."No.", Qty);
+ PurchaseLineCharge.Validate("Direct Unit Cost", LibraryRandom.RandDecInRange(10, 50, 2));
+ PurchaseLineCharge.Modify(true);
+ EnsureGeneralPostingSetupIsValid(PurchaseLineCharge."Gen. Bus. Posting Group", PurchaseLineCharge."Gen. Prod. Posting Group");
+
+ // [GIVEN] The item charge is assigned to the subcontracting receipt line
+ AssignItemChargeToReceiptLine(PurchaseLineCharge, PurchRcptLine, Qty, PurchaseLineCharge."Direct Unit Cost");
+
+ // [WHEN] Post the purchase invoice
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeaderInvoice, false, true);
+
+ // [THEN] The posting succeeds and a value entry of type "Direct Cost" exists linked to the item's output ledger entry
+ ValueEntry.SetRange("Item No.", Item."No.");
+ ValueEntry.SetRange("Entry Type", ValueEntry."Entry Type"::"Direct Cost");
+ ValueEntry.SetRange("Item Charge No.", ItemCharge."No.");
+ Assert.RecordIsNotEmpty(ValueEntry);
+
+ // [THEN] The value entry is linked to an output item ledger entry (not a capacity ledger entry)
+ ValueEntry.FindLast();
+ Assert.AreNotEqual(0, ValueEntry."Item Ledger Entry No.", 'Value entry should be linked to an item ledger entry.');
+
+ // [THEN] The item ledger entry has the correct serial number
+ ItemLedgerEntry.Get(ValueEntry."Item Ledger Entry No.");
+ Assert.AreEqual(SerialNo, ItemLedgerEntry."Serial No.", 'Item ledger entry should carry the serial number from the production order.');
+
+ // [THEN] The value entry cost equals the item charge direct unit cost (qty = 1)
+ Assert.AreEqual(PurchaseLineCharge."Direct Unit Cost", ValueEntry."Cost Amount (Actual)",
+ 'Value entry cost amount should match the item charge direct unit cost.');
+ Assert.AreEqual(PurchaseLineCharge."Direct Unit Cost", ValueEntry."Cost per Unit",
+ 'Value entry cost per unit should match the item charge direct unit cost.');
+ Assert.AreEqual(Qty, ValueEntry."Valued Quantity", 'Value entry valued quantity should match the invoiced quantity.');
+
+ // [THEN] The value entry references the production order (not a blank order)
+ Assert.AreEqual("Inventory Order Type"::Production, ValueEntry."Order Type",
+ 'Value entry order type should be Production.');
+ Assert.AreEqual(ProductionOrder."No.", ValueEntry."Order No.",
+ 'Value entry order no. should match the production order.');
+ Assert.AreEqual(ProdOrderLine."Line No.", ValueEntry."Order Line No.",
+ 'Value entry order line no. should match the production order line.');
+ end;
+
+ local procedure AddSerialTrackingToItem(var Item: Record Item)
+ var
+ ItemTrackingCode: Record "Item Tracking Code";
+ SerialNoSeries: Record "No. Series";
+ SerialNoSeriesLine: Record "No. Series Line";
+ LibraryItemTracking: Codeunit "Library - Item Tracking";
+ LibraryUtility: Codeunit "Library - Utility";
+ begin
+ 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);
+ Item.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ Item.Validate("Serial Nos.", SerialNoSeries.Code);
+ Item.Modify(true);
+ end;
+
+ local procedure CreateItemWithSingleSubcontractingOperation(var Item: Record Item; var SubcWorkCenter: Record "Work Center")
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ ComponentItem1: Record Item;
+ ComponentItem2: Record Item;
+ ProductionBOMHeader: Record "Production BOM Header";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ ShopCalendarCode: Code[10];
+ WorkCenterNo: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarWorkingDays();
+ CreateSubcontractingWorkCenter(WorkCenterNo, ShopCalendarCode);
+ SubcWorkCenter.Get(WorkCenterNo);
+ LibraryManufacturing.CalculateWorkCenterCalendar(SubcWorkCenter, CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Work Center", SubcWorkCenter."No.");
+ RoutingLine.Validate("Setup Time", LibraryRandom.RandInt(5));
+ RoutingLine.Validate("Run Time", LibraryRandom.RandInt(5));
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Modify(true);
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ LibraryInventory.CreateItem(ComponentItem1);
+ LibraryInventory.CreateItem(ComponentItem2);
+ LibraryManufacturing.CreateCertifProdBOMWithTwoComp(ProductionBOMHeader, ComponentItem1."No.", ComponentItem2."No.", 1);
+
+ LibraryManufacturing.CreateItemManufacturing(
+ Item, "Costing Method"::FIFO, LibraryRandom.RandInt(10),
+ "Reordering Policy"::"Lot-for-Lot", Microsoft.Manufacturing.Setup."Flushing Method"::"Pick + Manual",
+ RoutingHeader."No.", ProductionBOMHeader."No.");
+ end;
+
+ local procedure CreateSubcontractingWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10])
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ VATPostingSetup: Record "VAT Posting Setup";
+ WorkCenter: Record "Work Center";
+ begin
+ LibraryMfgManagement.CreateWorkCenterWithFixedCost(WorkCenter, ShopCalendarCode, 0);
+ WorkCenter.Validate("Direct Unit Cost", LibraryRandom.RandDec(10, 2));
+ WorkCenter.Validate("Unit Cost Calculation", UnitCostCalculation);
+ LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT");
+ GenProductPostingGroup.FindFirst();
+ GenProductPostingGroup.Validate("Def. VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group");
+ GenProductPostingGroup.Modify(true);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+ WorkCenter.Modify(true);
+ WorkCenterNo := WorkCenter."No.";
+ end;
+
+ local procedure AssignItemChargeToReceiptLine(PurchaseLine: Record "Purchase Line"; PurchRcptLine: Record "Purch. Rcpt. Line"; QtyToAssign: Decimal; AmountToAssign: Decimal)
+ var
+ ItemChargeAssignmentPurch: Record "Item Charge Assignment (Purch)";
+ ItemChargeAssgntPurch: Codeunit "Item Charge Assgnt. (Purch.)";
+ NextLineNo: Integer;
+ begin
+ ItemChargeAssignmentPurch.SetRange("Document Type", PurchaseLine."Document Type");
+ ItemChargeAssignmentPurch.SetRange("Document No.", PurchaseLine."Document No.");
+ ItemChargeAssignmentPurch.SetRange("Document Line No.", PurchaseLine."Line No.");
+ if ItemChargeAssignmentPurch.FindLast() then
+ NextLineNo := ItemChargeAssignmentPurch."Line No." + 10000
+ else
+ NextLineNo := 10000;
+
+ ItemChargeAssignmentPurch.Init();
+ ItemChargeAssignmentPurch."Document Type" := PurchaseLine."Document Type";
+ ItemChargeAssignmentPurch."Document No." := PurchaseLine."Document No.";
+ ItemChargeAssignmentPurch."Document Line No." := PurchaseLine."Line No.";
+ ItemChargeAssignmentPurch."Item Charge No." := PurchaseLine."No.";
+ ItemChargeAssignmentPurch."Unit Cost" := PurchaseLine."Direct Unit Cost";
+
+ ItemChargeAssgntPurch.InsertItemChargeAssignmentWithValues(
+ ItemChargeAssignmentPurch,
+ "Purchase Applies-to Document Type"::Receipt,
+ PurchRcptLine."Document No.",
+ PurchRcptLine."Line No.",
+ PurchRcptLine."No.",
+ PurchRcptLine.Description,
+ QtyToAssign,
+ AmountToAssign,
+ NextLineNo);
+ end;
+
+ local procedure EnsureGeneralPostingSetupIsValid(GenBusPostingGroup: Code[20]; GenProdPostingGroup: Code[20])
+ var
+ GeneralPostingSetup: Record "General Posting Setup";
+ begin
+ if GeneralPostingSetup.Get(GenBusPostingGroup, GenProdPostingGroup) then begin
+ if GeneralPostingSetup.Blocked then begin
+ GeneralPostingSetup.Blocked := false;
+ GeneralPostingSetup.Modify();
+ end;
+ exit;
+ end;
+ GeneralPostingSetup.Init();
+ GeneralPostingSetup."Gen. Bus. Posting Group" := GenBusPostingGroup;
+ GeneralPostingSetup."Gen. Prod. Posting Group" := GenProdPostingGroup;
+ GeneralPostingSetup.Insert();
+ GeneralPostingSetup.SuggestSetupAccounts();
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Item Charge Posting Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcontractingMgmtLibrary.UpdateSubMgmtSetup_ComponentAtLocation("Components at Location"::Purchase);
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Item Charge Posting Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ LibraryMfgManagement.Initialize();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Item Charge Posting Test");
+ end;
+
+ [ConfirmHandler]
+ procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Reply := true;
+ end;
+
+ [PageHandler]
+ procedure HandlePurchaseOrderPage(var PurchaseOrderPage: TestPage "Purchase Order")
+ begin
+ PurchaseOrderPage.Close();
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcLocationHandlerTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcLocationHandlerTest.Codeunit.al
new file mode 100644
index 0000000000..c1a90353f3
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcLocationHandlerTest.Codeunit.al
@@ -0,0 +1,580 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Company;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+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;
+
+codeunit 139981 "Subc. Location Handler Test"
+{
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ // [FEATURE] Enhanced Subcontracting Location Handler
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ CreateSubcontractingOrderAnywayQst: Label 'Do you want to create the Subcontracting Order anyway?';
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Location Handler Test");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Location Handler Test");
+
+ SubcontractingMgmtLibrary.Initialize();
+ LibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Location Handler Test");
+ end;
+
+ [Test]
+ procedure TestGetComponentsLocationCode_Purchase()
+ var
+ Location: Record Location;
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ SubcontractingMgmt: Codeunit "Subcontracting Management";
+ CompLocationCode: Code[10];
+ begin
+ // [SCENARIO] GetComponentsLocationCode returns Purchase Line Location when Setup is Purchase
+ Initialize();
+
+ // [GIVEN] Sub Management Setup "Subc. Default Comp. Location" is Purchase
+ UpdateSubManagementSetup("Components at Location"::Purchase);
+
+ // [GIVEN] A Purchase Line with a Location
+ LibraryPurchase.CreatePurchHeader(PurchaseHeader, "Purchase Document Type"::Order, '');
+ LibraryWarehouse.CreateLocation(Location);
+ LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, "Purchase Line Type"::Item, '', LibraryRandom.RandInt(10));
+ PurchaseLine.Validate("Location Code", Location.Code);
+ PurchaseLine.Modify();
+
+ // [WHEN] GetComponentsLocationCode is called
+ CompLocationCode := SubcontractingMgmt.GetComponentsLocationCode(PurchaseLine);
+
+ // [THEN] The returned location code is the Purchase Line Location
+ Assert.AreEqual(Location.Code, CompLocationCode, 'Component Location Code should match Purchase Line Location');
+ end;
+
+ [Test]
+ procedure TestGetComponentsLocationCode_Company()
+ var
+ Location: Record Location;
+ PurchaseLine: Record "Purchase Line";
+ SubcontractingMgmt: Codeunit "Subcontracting Management";
+ CompLocationCode: Code[10];
+ begin
+ // [SCENARIO] GetComponentsLocationCode returns Company Location when Setup is Company
+ Initialize();
+
+ // [GIVEN] Sub Management Setup "Subc. Default Comp. Location" is Company
+ UpdateSubManagementSetup("Components at Location"::Company);
+
+ // [GIVEN] Company Information has a Location
+ LibraryWarehouse.CreateLocation(Location);
+ UpdateCompanyInformation(Location.Code);
+
+ // [WHEN] GetComponentsLocationCode is called
+ CompLocationCode := SubcontractingMgmt.GetComponentsLocationCode(PurchaseLine);
+
+ // [THEN] The returned location code is the Company Location
+ Assert.AreEqual(Location.Code, CompLocationCode, 'Component Location Code should match Company Location');
+ end;
+
+ [Test]
+ procedure TestGetComponentsLocationCode_Manufacturing()
+ var
+ Location: Record Location;
+ PurchaseLine: Record "Purchase Line";
+ SubcontractingMgmt: Codeunit "Subcontracting Management";
+ CompLocationCode: Code[10];
+ begin
+ // [SCENARIO] GetComponentsLocationCode returns Manufacturing Location when Setup is Manufacturing
+ Initialize();
+
+ // [GIVEN] Sub Management Setup "Subc. Default Comp. Location" is Manufacturing
+ UpdateSubManagementSetup("Components at Location"::Manufacturing);
+
+ // [GIVEN] Manufacturing Setup has a Location
+ LibraryWarehouse.CreateLocation(Location);
+ UpdateManufacturingSetup(Location.Code);
+
+ // [GIVEN] A Purchase Line (Location doesn't matter)
+ PurchaseLine.Init();
+
+ // [WHEN] GetComponentsLocationCode is called
+ CompLocationCode := SubcontractingMgmt.GetComponentsLocationCode(PurchaseLine);
+
+ // [THEN] The returned location code is the Manufacturing Location
+ Assert.AreEqual(Location.Code, CompLocationCode, 'Component Location Code should match Manufacturing Location');
+ end;
+
+ [Test]
+ [HandlerFunctions('HandleTransferOrder')]
+ procedure TestTransferOrderCreation_SameLocation()
+ var
+ Item: Record Item;
+ LocationOrig: Record Location;
+ LocationSub: Record Location;
+ ProdOrder: Record "Production Order";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferRoute: Record "Transfer Route";
+ TransitLocation: Record Location;
+ Vendor: Record Vendor;
+ CreateSubCTransfOrder: Report "Subc. Create Transf. Order";
+ begin
+ // [SCENARIO] Transfer Order creation uses Origin Location if Component Location equals Subcontractor Location
+ Initialize();
+
+ // [GIVEN] Locations: Subcontractor and Original
+ LibraryWarehouse.CreateLocation(LocationSub);
+ LibraryWarehouse.CreateLocation(LocationOrig);
+ LibraryWarehouse.CreateInTransitLocation(TransitLocation);
+ LibraryWarehouse.CreateAndUpdateTransferRoute(TransferRoute, LocationOrig.Code, LocationSub.Code, TransitLocation.Code, '', '');
+
+ // [GIVEN] Subcontracting Scenario Setup
+ CreateSubcontractingSetup(
+ PurchaseHeader, PurchaseLine, ProdOrder, ProdOrderLine, ProdOrderComp, ProdOrderRtngLine, Vendor,
+ LocationSub, Item, LibraryRandom.RandInt(10), LocationSub.Code, LocationOrig.Code);
+
+ // [WHEN] Running the Create Subcontracting Transfer Order report
+ Commit(); // Report requires commit
+ PurchaseHeader.SetRecFilter();
+ CreateSubCTransfOrder.SetTableView(PurchaseHeader);
+ CreateSubCTransfOrder.UseRequestPage(false);
+ CreateSubCTransfOrder.Run();
+
+ // [THEN] Transfer Order is created from Origin Location to Subcontractor Location
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ Assert.IsTrue(TransferHeader.FindFirst(), 'Transfer Order should be created');
+ Assert.AreEqual(LocationOrig.Code, TransferHeader."Transfer-from Code", 'Transfer-from Code should be Origin Location');
+ Assert.AreEqual(LocationSub.Code, TransferHeader."Transfer-to Code", 'Transfer-to Code should be Subcontractor Location');
+ end;
+
+ [Test]
+ [HandlerFunctions('HandleTransferOrder')]
+ procedure TestTransferOrderCreation_PostAndRecreate()
+ var
+ Item: Record Item;
+ ItemJournalLine: Record "Item Journal Line";
+ LocationOrig: Record Location;
+ LocationSub: Record Location;
+ ProdOrder: Record "Production Order";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferRoute: Record "Transfer Route";
+ TransitLocation: Record Location;
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ CreateSubCTransfOrder: Report "Subc. Create Transf. Order";
+ QtyFirstTransfer: Decimal;
+ QtyRemaining: Decimal;
+ QtyTotal: Decimal;
+ begin
+ // [SCENARIO] Create Transfer Order, reduce quantity, post, and create new Transfer Order for remaining
+ Initialize();
+
+ QtyTotal := 10;
+ QtyFirstTransfer := 4;
+ QtyRemaining := QtyTotal - QtyFirstTransfer;
+
+ // [GIVEN] Locations
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(LocationSub);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(LocationOrig);
+ LibraryWarehouse.CreateInTransitLocation(TransitLocation);
+ LibraryWarehouse.CreateAndUpdateTransferRoute(TransferRoute, LocationOrig.Code, LocationSub.Code, TransitLocation.Code, '', '');
+
+
+ // [GIVEN] Subcontracting Scenario Setup
+ CreateSubcontractingSetup(
+ PurchaseHeader, PurchaseLine, ProdOrder, ProdOrderLine, ProdOrderComp, ProdOrderRtngLine, Vendor,
+ LocationSub, Item, QtyTotal, LocationOrig.Code, '');
+
+ // [GIVEN] Inventory for the component at Origin Location (needed for posting transfer)
+ LibraryInventory.CreateItemJournalLineInItemTemplate(
+ ItemJournalLine, Item."No.", LocationOrig.Code, '', QtyTotal);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+
+ // [WHEN] Running the Create Subcontracting Transfer Order report (1st time)
+ Commit();
+ Clear(CreateSubCTransfOrder);
+ PurchaseHeader.SetRecFilter();
+ CreateSubCTransfOrder.SetTableView(PurchaseHeader);
+ CreateSubCTransfOrder.UseRequestPage(false);
+ CreateSubCTransfOrder.Run();
+
+ // [THEN] Transfer Order 1 is created for full quantity
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ Assert.IsTrue(TransferHeader.FindFirst(), 'Transfer Order 1 should be created');
+
+ TransferLine.SetRange("Document No.", TransferHeader."No.");
+ TransferLine.FindFirst();
+ Assert.AreEqual(QtyTotal, TransferLine.Quantity, 'Initial Transfer Quantity should be total quantity');
+
+ // [WHEN] Reduce Quantity on Transfer Order and Post
+ TransferLine.Validate(Quantity, QtyFirstTransfer);
+ TransferLine.Modify();
+ LibraryWarehouse.PostTransferOrder(TransferHeader, true, true); // Ship and Receive
+
+ // [WHEN] Running the Create Subcontracting Transfer Order report (2nd time)
+ Commit();
+ Clear(CreateSubCTransfOrder);
+ CreateSubCTransfOrder.SetTableView(PurchaseHeader);
+ CreateSubCTransfOrder.UseRequestPage(false);
+ CreateSubCTransfOrder.Run();
+
+ // [THEN] Transfer Order 2 is created for remaining quantity
+ TransferHeader.Reset();
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ TransferHeader.SetRange(Status, TransferHeader.Status::Open); // Find the new open one
+ Assert.IsTrue(TransferHeader.FindFirst(), 'Transfer Order 2 should be created');
+
+ TransferLine.Reset();
+ TransferLine.SetRange("Document No.", TransferHeader."No.");
+ TransferLine.FindFirst();
+ Assert.AreEqual(QtyRemaining, TransferLine.Quantity, 'Second Transfer Quantity should be remaining quantity');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmCreateSubcOrderAnyway_No')]
+ procedure CreateSubcOrderFromRtngLine_BlankCompLocation_UserDeclines()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ NoOfCreatedPurchOrder: Integer;
+ begin
+ // [SCENARIO] Bug 633228: When a Transfer-type Prod. Order Component has a blank Location Code,
+ // creating a Subcontracting Order from the routing line raises a confirmation. If the user
+ // declines, no Purchase Order is created.
+
+ // [GIVEN] Manufacturing setup with subcontracting work center, item, and Transfer-type BOM line
+ Initialize();
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5, '');
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Transfer-type Prod. Order Component has a blank Location Code
+ SetTransferProdOrderCompLocationCode(ProductionOrder."No.", '');
+
+ // [WHEN] Create Subcontracting Order from Prod. Order Routing; the user declines the "anyway" confirmation
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ NoOfCreatedPurchOrder := SubcPurchaseOrderCreator.CreateSubcontractingPurchaseOrderFromRoutingLine(ProdOrderRoutingLine);
+
+ // [THEN] No Purchase Order is created
+ Assert.AreEqual(0, NoOfCreatedPurchOrder, 'No Purchase Order should be created when user declines.');
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.RecordIsEmpty(PurchaseLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmCreateSubcOrderAnyway_No')]
+ procedure CreateSubcOrderFromRtngLine_CompLocationEqualsSubcLocation_UserDeclines()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ NoOfCreatedPurchOrder: Integer;
+ begin
+ // [SCENARIO] Bug 633228: When a Transfer-type Prod. Order Component has Location Code equal to
+ // the vendor's Subcontracting Location Code, creating a Subcontracting Order from the routing
+ // line raises a confirmation. If the user declines, no Purchase Order is created.
+
+ // [GIVEN] Manufacturing setup with subcontracting work center, item, and Transfer-type BOM line
+ Initialize();
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5, '');
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Transfer-type Prod. Order Component Location Code equals the vendor's Subcontracting Location Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ SetTransferProdOrderCompLocationCode(ProductionOrder."No.", Vendor."Subc. Location Code");
+
+ // [WHEN] Create Subcontracting Order from Prod. Order Routing; the user declines the "anyway" confirmation
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ NoOfCreatedPurchOrder := SubcPurchaseOrderCreator.CreateSubcontractingPurchaseOrderFromRoutingLine(ProdOrderRoutingLine);
+
+ // [THEN] No Purchase Order is created
+ Assert.AreEqual(0, NoOfCreatedPurchOrder, 'No Purchase Order should be created when user declines.');
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.RecordIsEmpty(PurchaseLine);
+ end;
+
+ [Test]
+ procedure ValidateVendorSubcontrLocationCode_BinMandatoryLocation_RaisesError()
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ // [SCENARIO 633208] Setting Vendor."Subcontr. Location Code" to a Bin Mandatory location raises an error immediately
+ Initialize();
+
+ // [GIVEN] A location with Bin Mandatory enabled
+ LibraryWarehouse.CreateLocation(Location);
+ Location."Bin Mandatory" := true;
+ Location.Modify(true);
+
+ // [GIVEN] A vendor
+ LibraryPurchase.CreateVendor(Vendor);
+
+ // [WHEN] / [THEN] Validating "Subc. Location Code" to a Bin Mandatory location raises an error immediately
+ asserterror Vendor.Validate("Subc. Location Code", Location.Code);
+ Assert.ExpectedError('Bin Mandatory');
+ end;
+
+ [Test]
+ procedure ValidatePurchHeaderSubcLocationCode_BinMandatoryLocation_RaisesError()
+ var
+ Location: Record Location;
+ PurchaseHeader: Record "Purchase Header";
+ Vendor: Record Vendor;
+ begin
+ // [SCENARIO 633208] Setting PurchaseHeader."Subc. Location Code" to a Bin Mandatory location raises an error immediately
+ Initialize();
+
+ // [GIVEN] A location with Bin Mandatory enabled
+ LibraryWarehouse.CreateLocation(Location);
+ Location."Bin Mandatory" := true;
+ Location.Modify(true);
+
+ // [GIVEN] A Purchase Header
+ LibraryPurchase.CreateVendor(Vendor);
+ LibraryPurchase.CreatePurchHeader(PurchaseHeader, "Purchase Document Type"::Order, Vendor."No.");
+
+ // [WHEN] / [THEN] Validating "Subc. Location Code" to a Bin Mandatory location raises an error immediately
+ asserterror PurchaseHeader.Validate("Subc. Location Code", Location.Code);
+ Assert.ExpectedError('Bin Mandatory');
+ end;
+
+ local procedure UpdateSubManagementSetup(ComponentAtLocation: Enum "Components at Location")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ if not ManufacturingSetup.Get() then begin
+ ManufacturingSetup.Init();
+ ManufacturingSetup.Insert();
+ end;
+ ManufacturingSetup."Subc. Default Comp. Location" := ComponentAtLocation;
+ ManufacturingSetup.Modify();
+ end;
+
+ local procedure UpdateManufacturingSetup(LocationCode: Code[10])
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Components at Location" := LocationCode;
+ ManufacturingSetup.Modify();
+ end;
+
+ local procedure UpdateCompanyInformation(LocationCode: Code[10])
+ var
+ CompanyInformation: Record "Company Information";
+ begin
+ CompanyInformation.Get();
+ CompanyInformation."Location Code" := LocationCode;
+ CompanyInformation.Modify();
+ end;
+
+ local procedure CreateSubcontractingSetup(var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; var ProdOrder: Record "Production Order"; var ProdOrderLine: Record "Prod. Order Line"; var ProdOrderComp: Record "Prod. Order Component"; var ProdOrderRtngLine: Record "Prod. Order Routing Line"; var Vendor: Record Vendor; var LocationSub: Record Location; var Item: Record Item; Qty: Decimal; CompLocationCode: Code[10]; CompOrigLocationCode: Code[10])
+ var
+ RoutingLink: Record "Routing Link";
+ begin
+ // [GIVEN] Vendor with Subcontractor Location
+ if Vendor."No." = '' then begin
+ LibraryPurchase.CreateVendor(Vendor);
+ Vendor."Subc. Location Code" := LocationSub.Code;
+ Vendor.Modify();
+ end;
+
+ // [GIVEN] Create Item
+ LibraryInventory.CreateItem(Item);
+
+ // [GIVEN] Production Order with Component
+ LibraryManufacturing.CreateProductionOrder(ProdOrder, "Production Order Status"::Released, ProdOrder."Source Type"::Item, Item."No.", Qty);
+ LibraryManufacturing.CreateProdOrderLine(ProdOrderLine, ProdOrder.Status, ProdOrder."No.", Item."No.", '', CompLocationCode, Qty);
+
+ // [GIVEN] Create a Routing Link for linking component to routing line
+ LibraryManufacturing.CreateRoutingLink(RoutingLink);
+
+ // [GIVEN] Production Order Component
+ LibraryManufacturing.CreateProductionOrderComponent(ProdOrderComp, ProdOrder.Status, ProdOrder."No.", ProdOrderLine."Line No.");
+ ProdOrderComp.Validate("Item No.", Item."No.");
+ ProdOrderComp.Validate(Quantity, Qty);
+ ProdOrderComp.Validate("Quantity per", 1);
+ ProdOrderComp."Location Code" := CompLocationCode;
+ if CompOrigLocationCode <> '' then
+ ProdOrderComp."Subc. Original Location Code" := CompOrigLocationCode;
+ ProdOrderComp."Component Supply Method" := ProdOrderComp."Component Supply Method"::"Transfer to Vendor";
+ ProdOrderComp."Routing Link Code" := RoutingLink.Code;
+ ProdOrderComp.Modify();
+
+ // [GIVEN] Prod Order Routing Line (needed for linking)
+ CreateProdOrderRoutingLine(ProdOrderRtngLine, ProdOrder, ProdOrderLine, RoutingLink.Code);
+
+ // [GIVEN] Purchase Order linked to Prod Order
+ LibraryPurchase.CreatePurchHeader(PurchaseHeader, "Purchase Document Type"::Order, Vendor."No.");
+ LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, "Purchase Line Type"::Item, Item."No.", Qty);
+ PurchaseLine."Prod. Order No." := ProdOrder."No.";
+ PurchaseLine."Prod. Order Line No." := ProdOrderLine."Line No.";
+ PurchaseLine."Routing No." := ProdOrderRtngLine."Routing No.";
+ PurchaseLine."Operation No." := ProdOrderRtngLine."Operation No.";
+ PurchaseLine."Routing Reference No." := ProdOrderRtngLine."Routing Reference No.";
+ PurchaseLine.Modify();
+ end;
+
+ local procedure CreateProdOrderRoutingLine(var ProdOrderRtngLine: Record "Prod. Order Routing Line"; ProdOrder: Record "Production Order"; ProdOrderLine: Record "Prod. Order Line"; RoutingLinkCode: Code[10])
+ var
+ OperationNo: Code[10];
+ begin
+ ProdOrderRtngLine.SetRange(Status, ProdOrder.Status);
+ ProdOrderRtngLine.SetRange("Prod. Order No.", ProdOrder."No.");
+ if ProdOrderRtngLine.FindLast() then
+ OperationNo := IncStr(ProdOrderRtngLine."Operation No.")
+ else
+ OperationNo := '10';
+
+ ProdOrderRtngLine.Init();
+ ProdOrderRtngLine.Status := ProdOrder.Status;
+ ProdOrderRtngLine."Prod. Order No." := ProdOrder."No.";
+ ProdOrderRtngLine."Routing Reference No." := ProdOrderLine."Line No.";
+ ProdOrderRtngLine."Operation No." := OperationNo;
+ ProdOrderRtngLine."Routing Link Code" := RoutingLinkCode;
+ ProdOrderRtngLine.Insert();
+ end;
+
+ local procedure UpdateProdBomWithComponentSupplyMethod(Item: Record Item; ComponentSupplyMethod: Enum "Component Supply Method")
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ begin
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ ProductionBOMLine.FindLast();
+ ProductionBOMLine."Component Supply Method" := ComponentSupplyMethod;
+ ProductionBOMLine.Modify(true);
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ local procedure UpdateVendorWithSubcontractingLocationCode(WorkCenter: Record "Work Center")
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ Vendor."Subc. Location Code" := Location.Code;
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+ end;
+
+ local procedure SetTransferProdOrderCompLocationCode(ProdOrderNo: Code[20]; LocationCode: Code[10])
+ var
+ ProdOrderComp: Record "Prod. Order Component";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ ProdOrderComp."Location Code" := LocationCode;
+ ProdOrderComp.Modify();
+ end;
+
+ [ConfirmHandler]
+ procedure ConfirmCreateSubcOrderAnyway_No(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Assert.ExpectedMessage(CreateSubcontractingOrderAnywayQst, Question);
+ Reply := false;
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcNonInvItemValidTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcNonInvItemValidTest.Codeunit.al
new file mode 100644
index 0000000000..70860fd8f6
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcNonInvItemValidTest.Codeunit.al
@@ -0,0 +1,142 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Subcontracting;
+
+codeunit 149916 "Subc. Non-Inv Item Valid. Test"
+{
+ // [FEATURE] Component Supply Method Transfer validation for Non-Inventory items
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ [Test]
+ procedure NonInventoryItemCannotBeSetToTransferOnProdBOMLine()
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ Item: Record Item;
+ begin
+ // [SCENARIO 624295] Setting Component Supply Method = Transfer on a Production BOM Line with a Non-Inventory item must raise an error.
+
+ // [GIVEN] A Non-Inventory item and a Production BOM with a line for that item
+ CreateNonInventoryItem(Item);
+ CreateProductionBOMWithItem(ProductionBOMHeader, ProductionBOMLine, Item);
+
+ // [WHEN] Setting Component Supply Method to Transfer on the BOM line
+ asserterror ProductionBOMLine.Validate("Component Supply Method", "Component Supply Method"::"Transfer to Vendor");
+
+ // [THEN] An error is raised indicating the item type must be Inventory
+ Assert.ExpectedErrorCode('TestField');
+ Assert.ExpectedError('Type must be equal to');
+ end;
+
+ [Test]
+ procedure InventoryItemCanBeSetToTransferOnProdBOMLine()
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ Item: Record Item;
+ begin
+ // [SCENARIO 624295] Setting Component Supply Method = Transfer on a Production BOM Line with an Inventory item must succeed.
+
+ // [GIVEN] An Inventory item and a Production BOM with a line for that item
+ CreateInventoryItem(Item);
+ CreateProductionBOMWithItem(ProductionBOMHeader, ProductionBOMLine, Item);
+
+ // [WHEN] Setting Component Supply Method to Transfer on the BOM line
+ ProductionBOMLine.Validate("Component Supply Method", "Component Supply Method"::"Transfer to Vendor");
+
+ // [THEN] No error is raised
+ Assert.AreEqual("Component Supply Method"::"Transfer to Vendor", ProductionBOMLine."Component Supply Method", 'Component Supply Method should be Transfer to Vendor');
+ end;
+
+ [Test]
+ procedure NonInventoryItemCannotBeSetToTransferOnProdOrderComponent()
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ Item: Record Item;
+ begin
+ // [SCENARIO 624295] Setting Component Supply Method = Transfer on a Prod. Order Component with a Non-Inventory item must raise an error.
+
+ // [GIVEN] A Non-Inventory item and a Prod. Order Component for that item
+ CreateNonInventoryItem(Item);
+ CreateProdOrderComponent(ProdOrderComponent, Item);
+
+ // [WHEN] Setting Component Supply Method to Transfer on the component
+ asserterror ProdOrderComponent.Validate("Component Supply Method", "Component Supply Method"::"Transfer to Vendor");
+
+ // [THEN] An error is raised indicating the item type must be Inventory
+ Assert.ExpectedErrorCode('TestField');
+ Assert.ExpectedError('Type must be equal to');
+ end;
+
+ [Test]
+ procedure NonInventoryItemCanBeSetToPurchaseOnProdBOMLine()
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ Item: Record Item;
+ begin
+ // [SCENARIO 624295] Setting Component Supply Method = Purchase on a Production BOM Line with a Non-Inventory item must succeed (only Transfer is blocked).
+
+ // [GIVEN] A Non-Inventory item and a Production BOM with a line for that item
+ CreateNonInventoryItem(Item);
+ CreateProductionBOMWithItem(ProductionBOMHeader, ProductionBOMLine, Item);
+
+ // [WHEN] Setting Component Supply Method to Purchase on the BOM line
+ ProductionBOMLine.Validate("Component Supply Method", "Component Supply Method"::"Vendor-Supplied");
+
+ // [THEN] No error is raised
+ Assert.AreEqual("Component Supply Method"::"Vendor-Supplied", ProductionBOMLine."Component Supply Method", 'Component Supply Method should be Vendor-Supplied');
+ end;
+
+ local procedure CreateNonInventoryItem(var Item: Record Item)
+ begin
+ LibraryInventory.CreateItem(Item);
+ Item.Validate(Type, Item.Type::"Non-Inventory");
+ Item.Modify(true);
+ end;
+
+ local procedure CreateInventoryItem(var Item: Record Item)
+ begin
+ LibraryInventory.CreateItem(Item);
+ end;
+
+ local procedure CreateProductionBOMWithItem(var ProductionBOMHeader: Record "Production BOM Header"; var ProductionBOMLine: Record "Production BOM Line"; Item: Record Item)
+ begin
+ LibraryManufacturing.CreateProductionBOMHeader(ProductionBOMHeader, Item."Base Unit of Measure");
+ LibraryManufacturing.CreateProductionBOMLine(ProductionBOMHeader, ProductionBOMLine, '', ProductionBOMLine.Type::Item, Item."No.", 1);
+ end;
+
+ local procedure CreateProdOrderComponent(var ProdOrderComponent: Record "Prod. Order Component"; Item: Record Item)
+ var
+ ProductionOrder: Record "Production Order";
+ ProductionOrderLine: Record "Prod. Order Line";
+ ParentItem: Record Item;
+ begin
+ LibraryInventory.CreateItem(ParentItem);
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, ParentItem."No.", 1);
+ LibraryManufacturing.CreateProdOrderLine(ProductionOrderLine, ProductionOrder.Status, ProductionOrder."No.", ParentItem."No.", '', '', 1);
+
+ ProdOrderComponent.Init();
+ ProdOrderComponent.Status := ProductionOrder.Status;
+ ProdOrderComponent."Prod. Order No." := ProductionOrder."No.";
+ ProdOrderComponent."Prod. Order Line No." := ProductionOrderLine."Line No.";
+ ProdOrderComponent."Line No." := 10000;
+ ProdOrderComponent.Validate("Item No.", Item."No.");
+ ProdOrderComponent.Insert(true);
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+}
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcPricingTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcPricingTest.Codeunit.al
new file mode 100644
index 0000000000..cee0b9bd6b
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcPricingTest.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.Foundation.Enums;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Vendor;
+using System.TestLibraries.Utilities;
+
+codeunit 139982 "Subc. Pricing Test"
+{
+ // [FEATURE] Subcontracting Pricing
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ [Test]
+ procedure RoutingPriceUsesOrderUoMWhenMultipleUoMPricesExist()
+ var
+ Item: Record Item;
+ ItemUOM: Record "Item Unit of Measure";
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ InSubcontractorPrice: Record "Subcontractor Price";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ UnitCostCalcType: Enum "Unit Cost Calculation Type";
+ AltUOMCode: Code[10];
+ DirUnitCost, IndirCostPct, OvhdRate, UnitCost : Decimal;
+ PcsPrice, SetPrice : Decimal;
+ QtyPerSet: Integer;
+ begin
+ // [SCENARIO 636059] SetRoutingPriceListCost must select the Subcontractor Price row matching
+ // the order's Unit of Measure (with blank fallback). With prices in both Base UoM and an
+ // alternative UoM that sorts after it, the routing line must pick the Base UoM price when
+ // the order is in Base UoM — not the alphabetically-last alternative-UoM row.
+ Initialize();
+
+ // [GIVEN] Item with Base UoM and an alternative UoM (10 base per alt) whose code sorts after the base.
+ LibraryInventory.CreateItem(Item);
+ QtyPerSet := 10;
+ AltUOMCode := CreateUOMCodeSortingAfter(Item."Base Unit of Measure");
+ LibraryInventory.CreateItemUnitOfMeasure(ItemUOM, Item."No.", AltUOMCode, QtyPerSet);
+
+ // [GIVEN] Vendor and Work Center with the vendor as its subcontractor; zero indirect/overhead.
+ LibraryPurchase.CreateVendor(Vendor);
+ LibraryManufacturing.CreateWorkCenter(WorkCenter);
+ WorkCenter.Validate("Subcontractor No.", Vendor."No.");
+ WorkCenter.Validate("Indirect Cost %", 0);
+ WorkCenter.Validate("Overhead Rate", 0);
+ WorkCenter.Modify(true);
+
+ // [GIVEN] Two subcontractor prices — Base UoM = 1001, alternative UoM = 1004.
+ PcsPrice := 1001;
+ SetPrice := 1004;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", PcsPrice);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), AltUOMCode, 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", SetPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] InSubcontractorPrice staged as SetSubcontractorPriceForPriceCalculation would — order in Base UoM.
+ InSubcontractorPrice."Vendor No." := Vendor."No.";
+ InSubcontractorPrice."Item No." := Item."No.";
+ InSubcontractorPrice."Standard Task Code" := '';
+ InSubcontractorPrice."Work Center No." := WorkCenter."No.";
+ InSubcontractorPrice."Variant Code" := '';
+ InSubcontractorPrice."Unit of Measure Code" := Item."Base Unit of Measure";
+ InSubcontractorPrice."Starting Date" := WorkDate();
+ InSubcontractorPrice."Currency Code" := '';
+
+ // [WHEN] SetRoutingPriceListCost runs for a Prod. Order Routing Line of qty 1 in the Base UoM.
+ SubcPriceManagement.SetRoutingPriceListCost(
+ InSubcontractorPrice, WorkCenter, DirUnitCost, IndirCostPct, OvhdRate, UnitCost, UnitCostCalcType, 1, 1, 1);
+
+ // [THEN] Direct Unit Cost equals the Base UoM price (1001), not the alt-UoM derived 100.40.
+ Assert.AreEqual(
+ PcsPrice, DirUnitCost,
+ 'SetRoutingPriceListCost must pick the Subcontractor Price row matching the order''s Unit of Measure.');
+ end;
+
+ [Test]
+ procedure RoutingPriceUsesLCYWhenForeignCurrencyPriceExists()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ InSubcontractorPrice: Record "Subcontractor Price";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ UnitCostCalcType: Enum "Unit Cost Calculation Type";
+ ForeignCurrencyCode: Code[10];
+ DirUnitCost, IndirCostPct, OvhdRate, UnitCost : Decimal;
+ LCYPrice: Decimal;
+ begin
+ // [SCENARIO 638367] SetRoutingPriceListCost must filter Subcontractor Price by Currency Code so the
+ // LCY (blank-currency) price drives Calc Standard Cost / Prod. Order Routing, not the alphabetically-last
+ // foreign-currency row picked by FindLast() when Currency Code is left unfiltered.
+ Initialize();
+
+ // [GIVEN] Item, vendor and a subcontracting work center with zero indirect/overhead.
+ CreateItemVendorAndSubcontractingWorkCenter(Item, Vendor, WorkCenter);
+
+ // [GIVEN] A foreign currency whose code sorts after the blank LCY code.
+ ForeignCurrencyCode := LibraryERM.CreateCurrencyWithExchangeRate(WorkDate(), 15, 15);
+
+ // [GIVEN] Two subcontractor prices — LCY = 10 and the foreign currency = 20.
+ LCYPrice := 10;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", LCYPrice);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, ForeignCurrencyCode);
+ SubcontractorPrice.Validate("Direct Unit Cost", 20);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] InSubcontractorPrice staged for an LCY routing line (blank Currency Code).
+ StageInSubcontractorPrice(InSubcontractorPrice, Vendor, WorkCenter, Item, '', '');
+
+ // [WHEN] SetRoutingPriceListCost runs for a routing line of qty 1 in the base UoM.
+ SubcPriceManagement.SetRoutingPriceListCost(
+ InSubcontractorPrice, WorkCenter, DirUnitCost, IndirCostPct, OvhdRate, UnitCost, UnitCostCalcType, 1, 1, 1);
+
+ // [THEN] The LCY price (10) is used, not the foreign-currency price converted to LCY.
+ Assert.AreEqual(
+ LCYPrice, DirUnitCost,
+ 'SetRoutingPriceListCost must use the LCY subcontractor price, not a foreign-currency row.');
+ end;
+
+ [Test]
+ procedure RoutingPriceFallsBackToCatchAllStandardTaskCode()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ InSubcontractorPrice: Record "Subcontractor Price";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ UnitCostCalcType: Enum "Unit Cost Calculation Type";
+ DirUnitCost, IndirCostPct, OvhdRate, UnitCost : Decimal;
+ CatchAllPrice: Decimal;
+ begin
+ // [SCENARIO 638400] On the Prod. Order Routing path, SetRoutingPriceListCost must fall back to the
+ // catch-all (blank Standard Task Code) subcontractor price when the routing line carries a Standard
+ // Task Code that has no dedicated price — instead of leaving the routing cost in place.
+ Initialize();
+
+ // [GIVEN] Item, vendor, subcontracting work center and a single catch-all price (blank task, blank variant).
+ CreateItemVendorAndSubcontractingWorkCenter(Item, Vendor, WorkCenter);
+ CatchAllPrice := 333;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", CatchAllPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] InSubcontractorPrice staged for a routing line with a Standard Task Code that has no own price.
+ StageInSubcontractorPrice(InSubcontractorPrice, Vendor, WorkCenter, Item, '', 'TASK1');
+
+ // [WHEN] SetRoutingPriceListCost runs.
+ SubcPriceManagement.SetRoutingPriceListCost(
+ InSubcontractorPrice, WorkCenter, DirUnitCost, IndirCostPct, OvhdRate, UnitCost, UnitCostCalcType, 1, 1, 1);
+
+ // [THEN] The catch-all price (333) is applied.
+ Assert.AreEqual(
+ CatchAllPrice, DirUnitCost,
+ 'SetRoutingPriceListCost must fall back to the blank-Standard-Task-Code price.');
+ end;
+
+ [Test]
+ procedure RoutingPriceFallsBackToCatchAllVariantCode()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ InSubcontractorPrice: Record "Subcontractor Price";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ UnitCostCalcType: Enum "Unit Cost Calculation Type";
+ DirUnitCost, IndirCostPct, OvhdRate, UnitCost : Decimal;
+ CatchAllPrice: Decimal;
+ begin
+ // [SCENARIO 638400] On the Prod. Order Routing path, SetRoutingPriceListCost must fall back to the
+ // catch-all (blank Variant Code) subcontractor price when the prod. order line has a Variant Code
+ // that has no dedicated price.
+ Initialize();
+
+ // [GIVEN] Item, vendor, subcontracting work center and a single catch-all price (blank variant, blank task).
+ CreateItemVendorAndSubcontractingWorkCenter(Item, Vendor, WorkCenter);
+ CatchAllPrice := 333;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", CatchAllPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] InSubcontractorPrice staged for a routing line with a Variant Code that has no own price.
+ StageInSubcontractorPrice(InSubcontractorPrice, Vendor, WorkCenter, Item, 'VAR1', '');
+
+ // [WHEN] SetRoutingPriceListCost runs.
+ SubcPriceManagement.SetRoutingPriceListCost(
+ InSubcontractorPrice, WorkCenter, DirUnitCost, IndirCostPct, OvhdRate, UnitCost, UnitCostCalcType, 1, 1, 1);
+
+ // [THEN] The catch-all price (333) is applied.
+ Assert.AreEqual(
+ CatchAllPrice, DirUnitCost,
+ 'SetRoutingPriceListCost must fall back to the blank-Variant-Code price.');
+ end;
+
+ [Test]
+ procedure RoutingPricePrefersVariantSpecificOverStandardTaskSpecific()
+ var
+ Item: Record Item;
+ ItemVariant: Record "Item Variant";
+ StandardTask: Record "Standard Task";
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ InSubcontractorPrice: Record "Subcontractor Price";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ UnitCostCalcType: Enum "Unit Cost Calculation Type";
+ DirUnitCost, IndirCostPct, OvhdRate, UnitCost : Decimal;
+ VariantPrice, TaskPrice : Decimal;
+ begin
+ // [SCENARIO 638400] When a routing line matches BOTH a variant-specific price (blank Standard Task Code)
+ // and a standard-task-specific price (blank Variant Code), the lookup must be deterministic. With empty
+ // fallback on both fields, FindLast follows the Subcontractor Price primary key order, where Variant Code
+ // (field 4) precedes Standard Task Code (field 5), so the variant-specific row wins. This pins that
+ // precedence so it stays consistent across the routing, worksheet, and purchase-line lookups.
+ Initialize();
+
+ // [GIVEN] Item (with a variant), a standard task, vendor and a subcontracting work center.
+ CreateItemVendorAndSubcontractingWorkCenter(Item, Vendor, WorkCenter);
+ LibraryInventory.CreateItemVariant(ItemVariant, Item."No.");
+ LibraryManufacturing.CreateStandardTask(StandardTask);
+
+ // [GIVEN] A variant-specific price (blank task = 100) and a standard-task-specific price (blank variant = 200).
+ VariantPrice := 100;
+ TaskPrice := 200;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', ItemVariant.Code, WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", VariantPrice);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", StandardTask.Code, '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", TaskPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] InSubcontractorPrice staged for a routing line carrying both the variant and the standard task.
+ StageInSubcontractorPrice(InSubcontractorPrice, Vendor, WorkCenter, Item, ItemVariant.Code, StandardTask.Code);
+
+ // [WHEN] SetRoutingPriceListCost runs.
+ SubcPriceManagement.SetRoutingPriceListCost(
+ InSubcontractorPrice, WorkCenter, DirUnitCost, IndirCostPct, OvhdRate, UnitCost, UnitCostCalcType, 1, 1, 1);
+
+ // [THEN] The variant-specific price (100) wins over the standard-task-specific price (200).
+ Assert.AreEqual(
+ VariantPrice, DirUnitCost,
+ 'When both a variant-specific and a standard-task-specific price match, the variant-specific price (per PK order) must win.');
+ end;
+
+ [Test]
+ procedure WorksheetPriceFallsBackToCatchAllStandardTaskCode()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ RequisitionLine: Record "Requisition Line";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ CatchAllPrice: Decimal;
+ begin
+ // [SCENARIO 638400] On the Subcontracting Worksheet path, GetSubcPriceForReqLine must fall back to the
+ // catch-all (blank Standard Task Code) subcontractor price when the worksheet line carries a Standard
+ // Task Code that has no dedicated price.
+ Initialize();
+
+ // [GIVEN] Item, vendor, subcontracting work center and a single catch-all price (blank task, blank variant).
+ CreateItemVendorAndSubcontractingWorkCenter(Item, Vendor, WorkCenter);
+ CatchAllPrice := 333;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", CatchAllPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] A worksheet (requisition) line with a Standard Task Code that has no own price.
+ StageRequisitionLine(RequisitionLine, Vendor, WorkCenter, Item, '', 'TASK1');
+
+ // [WHEN] GetSubcPriceForReqLine runs.
+ SubcPriceManagement.GetSubcPriceForReqLine(RequisitionLine, '');
+
+ // [THEN] The catch-all price (333) is applied to the worksheet line.
+ Assert.AreEqual(
+ CatchAllPrice, RequisitionLine."Direct Unit Cost",
+ 'GetSubcPriceForReqLine must fall back to the blank-Standard-Task-Code price.');
+ end;
+
+ [Test]
+ procedure WorksheetPriceFallsBackToCatchAllVariantCode()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ RequisitionLine: Record "Requisition Line";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ CatchAllPrice: Decimal;
+ begin
+ // [SCENARIO 638400] On the Subcontracting Worksheet path, GetSubcPriceForReqLine must fall back to the
+ // catch-all (blank Variant Code) subcontractor price when the worksheet line has a Variant Code that
+ // has no dedicated price.
+ Initialize();
+
+ // [GIVEN] Item, vendor, subcontracting work center and a single catch-all price (blank variant, blank task).
+ CreateItemVendorAndSubcontractingWorkCenter(Item, Vendor, WorkCenter);
+ CatchAllPrice := 333;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", CatchAllPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] A worksheet (requisition) line with a Variant Code that has no own price.
+ StageRequisitionLine(RequisitionLine, Vendor, WorkCenter, Item, 'VAR1', '');
+
+ // [WHEN] GetSubcPriceForReqLine runs.
+ SubcPriceManagement.GetSubcPriceForReqLine(RequisitionLine, '');
+
+ // [THEN] The catch-all price (333) is applied to the worksheet line.
+ Assert.AreEqual(
+ CatchAllPrice, RequisitionLine."Direct Unit Cost",
+ 'GetSubcPriceForReqLine must fall back to the blank-Variant-Code price.');
+ end;
+
+ [Test]
+ procedure ProdOrderRoutingUnitCostUsesLCYWhenForeignCurrencyPriceExists()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ForeignCurrencyCode: Code[10];
+ LCYPrice: Decimal;
+ begin
+ // [SCENARIO 638367] Refreshing a Released Production Order must price the subcontracting Prod. Order
+ // Routing line from the LCY (blank-currency) subcontractor price, not from the alphabetically-last
+ // foreign-currency price converted to LCY. End-to-end check of the routing path via the
+ // OnAfterTransferRoutingLine subscriber -> ApplySubcontractorPricingToProdOrderRouting.
+ Initialize();
+
+ // [GIVEN] A subcontracting item with a single-operation routing on a subcontracting work center.
+ CreateSubcontractingItemWithSingleOperationRouting(Item, Vendor, WorkCenter);
+
+ // [GIVEN] A foreign currency with a non-LCY exchange rate whose code sorts after blank/LCY.
+ ForeignCurrencyCode := LibraryERM.CreateCurrencyWithExchangeRate(WorkDate(), 15, 15);
+
+ // [GIVEN] Two subcontractor prices for the item/work center/vendor — LCY = 10 and foreign = 20.
+ LCYPrice := 10;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", LCYPrice);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, ForeignCurrencyCode);
+ SubcontractorPrice.Validate("Direct Unit Cost", 20);
+ SubcontractorPrice.Modify(true);
+
+ // [WHEN] A Released Production Order for the item is created and refreshed.
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, "Prod. Order Source Type"::Item, Item."No.", 1);
+
+ // [THEN] The Prod. Order Routing line Direct Unit Cost and Unit Cost per equal the LCY price (10).
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.FindFirst();
+ Assert.AreEqual(
+ LCYPrice, ProdOrderRoutingLine."Direct Unit Cost",
+ 'Prod. Order Routing Direct Unit Cost must use the LCY subcontractor price, not a foreign-currency one.');
+ Assert.AreEqual(
+ LCYPrice, ProdOrderRoutingLine."Unit Cost per",
+ 'Prod. Order Routing Unit Cost per must use the LCY subcontractor price, not a foreign-currency one.');
+ end;
+
+ [Test]
+ procedure ProdOrderRoutingUnitCostUsesLCYAmongMultipleForeignCurrencies()
+ var
+ Item: Record Item;
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ FirstForeignCurrencyCode: Code[10];
+ SecondForeignCurrencyCode: Code[10];
+ LCYPrice: Decimal;
+ begin
+ // [SCENARIO 638367] Even when several foreign-currency prices exist (all sorting after blank on the
+ // primary key), the Prod. Order Routing line must still resolve to the single LCY (blank-currency) price.
+ Initialize();
+
+ // [GIVEN] A subcontracting item with a single-operation routing on a subcontracting work center.
+ CreateSubcontractingItemWithSingleOperationRouting(Item, Vendor, WorkCenter);
+
+ // [GIVEN] Two foreign currencies with non-LCY exchange rates.
+ FirstForeignCurrencyCode := LibraryERM.CreateCurrencyWithExchangeRate(WorkDate(), 15, 15);
+ SecondForeignCurrencyCode := LibraryERM.CreateCurrencyWithExchangeRate(WorkDate(), 12, 12);
+
+ // [GIVEN] An LCY price (10) and two foreign-currency prices (20 and 18).
+ LCYPrice := 10;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", LCYPrice);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, FirstForeignCurrencyCode);
+ SubcontractorPrice.Validate("Direct Unit Cost", 20);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, SecondForeignCurrencyCode);
+ SubcontractorPrice.Validate("Direct Unit Cost", 18);
+ SubcontractorPrice.Modify(true);
+
+ // [WHEN] A Released Production Order for the item is created and refreshed.
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, "Prod. Order Source Type"::Item, Item."No.", 1);
+
+ // [THEN] The Prod. Order Routing line still resolves to the LCY price (10).
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.FindFirst();
+ Assert.AreEqual(
+ LCYPrice, ProdOrderRoutingLine."Direct Unit Cost",
+ 'Prod. Order Routing Direct Unit Cost must use the LCY price even among multiple foreign-currency prices.');
+ Assert.AreEqual(
+ LCYPrice, ProdOrderRoutingLine."Unit Cost per",
+ 'Prod. Order Routing Unit Cost per must use the LCY price even among multiple foreign-currency prices.');
+ end;
+
+ local procedure CreateItemVendorAndSubcontractingWorkCenter(var Item: Record Item; var Vendor: Record Vendor; var WorkCenter: Record "Work Center")
+ begin
+ LibraryInventory.CreateItem(Item);
+ LibraryPurchase.CreateVendor(Vendor);
+ LibraryManufacturing.CreateWorkCenter(WorkCenter);
+ WorkCenter.Validate("Subcontractor No.", Vendor."No.");
+ WorkCenter.Validate("Indirect Cost %", 0);
+ WorkCenter.Validate("Overhead Rate", 0);
+ WorkCenter.Modify(true);
+ end;
+
+ local procedure CreateSubcontractingItemWithSingleOperationRouting(var Item: Record Item; var Vendor: Record Vendor; var WorkCenter: Record "Work Center")
+ var
+ RoutingNo: Code[20];
+ begin
+ Vendor.Get(LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+ WorkCenter.Validate("Subcontractor No.", Vendor."No.");
+ WorkCenter.Validate("Indirect Cost %", 0);
+ WorkCenter.Validate("Overhead Rate", 0);
+ WorkCenter.Modify(true);
+
+ RoutingNo := CreateCertifiedSubcontractingRouting(WorkCenter."No.");
+
+ LibraryInventory.CreateItem(Item);
+ Item.Validate("Replenishment System", Item."Replenishment System"::"Prod. Order");
+ Item.Validate("Routing No.", RoutingNo);
+ Item.Modify(true);
+ end;
+
+ local procedure CreateCertifiedSubcontractingRouting(WorkCenterNo: Code[20]): Code[20]
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ begin
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+
+#pragma warning disable AA0210
+ CapacityUnitOfMeasure.SetRange(Type, CapacityUnitOfMeasure.Type::Minutes);
+#pragma warning restore AA0210
+ CapacityUnitOfMeasure.FindFirst();
+
+ LibraryManufacturing.CreateRoutingLineSetup(RoutingLine, RoutingHeader, WorkCenterNo, '10', 0, 1);
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+ exit(RoutingHeader."No.");
+ end;
+
+ local procedure StageInSubcontractorPrice(var InSubcontractorPrice: Record "Subcontractor Price"; Vendor: Record Vendor; WorkCenter: Record "Work Center"; Item: Record Item; VariantCode: Code[10]; StandardTaskCode: Code[10])
+ begin
+ // Mirrors how SetSubcontractorPriceForPriceCalculation stages the lookup record for a routing line.
+ InSubcontractorPrice.Init();
+ InSubcontractorPrice."Vendor No." := Vendor."No.";
+ InSubcontractorPrice."Item No." := Item."No.";
+ InSubcontractorPrice."Standard Task Code" := StandardTaskCode;
+ InSubcontractorPrice."Work Center No." := WorkCenter."No.";
+ InSubcontractorPrice."Variant Code" := VariantCode;
+ InSubcontractorPrice."Unit of Measure Code" := Item."Base Unit of Measure";
+ InSubcontractorPrice."Starting Date" := WorkDate();
+ InSubcontractorPrice."Currency Code" := '';
+ end;
+
+ local procedure StageRequisitionLine(var RequisitionLine: Record "Requisition Line"; Vendor: Record Vendor; WorkCenter: Record "Work Center"; Item: Record Item; VariantCode: Code[10]; StandardTaskCode: Code[10])
+ begin
+ RequisitionLine.Init();
+ RequisitionLine.Type := RequisitionLine.Type::Item;
+ RequisitionLine."No." := Item."No.";
+ RequisitionLine."Vendor No." := Vendor."No.";
+ RequisitionLine."Work Center No." := WorkCenter."No.";
+ RequisitionLine."Variant Code" := VariantCode;
+ RequisitionLine."Subc. Standard Task Code" := StandardTaskCode;
+ RequisitionLine."Unit of Measure Code" := Item."Base Unit of Measure";
+ RequisitionLine."Currency Code" := '';
+ RequisitionLine."Order Date" := WorkDate();
+ RequisitionLine.Quantity := 1;
+ end;
+
+ local procedure CreateUOMCodeSortingAfter(BaseUOMCode: Code[10]): Code[10]
+ var
+ UnitOfMeasure: Record "Unit of Measure";
+ LibraryUtility: Codeunit "Library - Utility";
+ NewCode: Code[10];
+ begin
+ // LibraryInventory.CreateUnitOfMeasureCode generates a hex-only code (truncated GUID), so
+ // any code with a 'Z' prefix is guaranteed to sort after it. This makes the multi-UoM test
+ // deterministic — without the fix, FindLast() picks the alt UoM row.
+ repeat
+ NewCode := CopyStr('Z' + LibraryUtility.GenerateGUID(), 1, MaxStrLen(NewCode));
+ until not UnitOfMeasure.Get(NewCode);
+ UnitOfMeasure.Init();
+ UnitOfMeasure.Code := NewCode;
+ UnitOfMeasure.Description := NewCode;
+ UnitOfMeasure.Insert(true);
+ if UnitOfMeasure.Code <= BaseUOMCode then
+ Error('Test setup: generated UoM code %1 must sort after base UoM code %2.', UnitOfMeasure.Code, BaseUOMCode);
+ exit(UnitOfMeasure.Code);
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Pricing Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcontractingMgmtLibrary.UpdateSubMgmtSetup_ComponentAtLocation("Components at Location"::Purchase);
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+ LibraryVariableStorage.Clear();
+
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Pricing Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Pricing Test");
+ end;
+
+ 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";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ LibraryVariableStorage: Codeunit "Library - Variable Storage";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ IsInitialized: Boolean;
+}
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al
new file mode 100644
index 0000000000..2335b62fe9
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcPurchSubcontTest.Codeunit.al
@@ -0,0 +1,963 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.VAT.Setup;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+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;
+
+codeunit 139991 "Subc. Purch. Subcont. Test"
+{
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ // [FEATURE] Subcontracting Management
+ IsInitialized := false;
+ end;
+
+ 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";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ IsInitialized: Boolean;
+ ItemTrackingWasOpened: Boolean;
+ UnitCostCalculation: Option Time,Units;
+
+ [Test]
+ procedure VendorLocationWithBinMandatoryThrowsError()
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ // [SCENARIO] Setting a vendor's subcontracting location to a location with "Bin Mandatory" enabled should throw an error with ErrorInfo
+
+ Initialize();
+
+ // [GIVEN] A location with Bin Mandatory enabled
+ LibraryWarehouse.CreateLocation(Location);
+ Location."Bin Mandatory" := true;
+ Location.Modify();
+
+ // [GIVEN] A vendor
+ LibraryPurchase.CreateVendor(Vendor);
+
+ // [WHEN] Try to set the vendor's Subc. Location Code to the location with Bin Mandatory
+ // [THEN] An error is thrown with ErrorInfo
+ asserterror Vendor.Validate("Subc. Location Code", Location.Code);
+ Assert.ExpectedError('Location ' + Location.Code + ' cannot be used as a subcontracting location because Bin Mandatory or warehouse handling is enabled on the location.');
+ end;
+
+ [Test]
+ procedure VendorLocationWithRequirePickThrowsError()
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ // [SCENARIO] Setting a vendor's subcontracting location to a location with "Require Pick" enabled should throw an error
+
+ Initialize();
+
+ // [GIVEN] A location with Require Pick enabled
+ LibraryWarehouse.CreateLocation(Location);
+ Location."Require Pick" := true;
+ Location.Modify();
+
+ // [GIVEN] A vendor
+ LibraryPurchase.CreateVendor(Vendor);
+
+ // [WHEN] Try to set the vendor's Subc. Location Code to the location with Require Pick
+ // [THEN] An error is thrown
+ asserterror Vendor.Validate("Subc. Location Code", Location.Code);
+ Assert.ExpectedError('Location ' + Location.Code + ' cannot be used as a subcontracting location because Bin Mandatory or warehouse handling is enabled on the location.');
+ end;
+
+ [Test]
+ procedure VendorLocationWithoutWarehouseHandlingSucceeds()
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ // [SCENARIO] Setting a vendor's subcontracting location to a valid location should succeed
+
+ Initialize();
+
+ // [GIVEN] A location without warehouse handling
+ LibraryWarehouse.CreateLocation(Location);
+ Location."Bin Mandatory" := false;
+ Location."Require Pick" := false;
+ Location."Require Put-away" := false;
+ Location."Require Receive" := false;
+ Location."Require Shipment" := false;
+ Location.Modify();
+
+ // [GIVEN] A vendor
+ LibraryPurchase.CreateVendor(Vendor);
+
+ // [WHEN] Set the vendor's Subc. Location Code to the valid location
+ // [THEN] The validation succeeds and the field is updated
+ Vendor.Validate("Subc. Location Code", Location.Code);
+ Assert.AreEqual(Location.Code, Vendor."Subc. Location Code", 'Subc. Location Code should be set to the valid location');
+ end;
+
+ [Test]
+ [HandlerFunctions('ItemTrackingLinesSimpleHandler')]
+ procedure ItemTrackingLinesCanBeOpenedOnNonSubcontractingPurchaseLine()
+ var
+ Item: Record Item;
+ ItemTrackingCode: Record "Item Tracking Code";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ Vendor: Record Vendor;
+ begin
+ // [SCENARIO] Opening item tracking lines on a regular (non-subcontracting) purchase line succeeds
+ // [FEATURE] Bug 629884 - The subcontracting extension must not intercept OnBeforeOpenItemTrackingLines for non-subcontracting lines
+
+ Initialize();
+
+ // [GIVEN] An item with lot purchase inbound tracking
+ LibraryInventory.CreateItemTrackingCode(ItemTrackingCode);
+ ItemTrackingCode.Validate("Lot Purchase Inbound Tracking", true);
+ ItemTrackingCode.Modify(true);
+ LibraryInventory.CreateItem(Item);
+ Item.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ Item.Modify(true);
+
+ // [GIVEN] A purchase order with a regular (non-subcontracting) purchase line
+ LibraryPurchase.CreateVendor(Vendor);
+ LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No.");
+ LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, Item."No.", LibraryRandom.RandIntInRange(1, 10));
+
+ // [VERIFY] The purchase line has no subcontracting link (Subc. Purchase Line Type = None)
+ Assert.AreEqual(
+ "Subc. Purchase Line Type"::None, PurchaseLine."Subc. Purchase Line Type",
+ 'Purchase line must have Subc. Purchase Line Type = None for this test');
+
+ // [WHEN] Open item tracking lines on the non-subcontracting purchase line
+ // Before fix: the event subscriber always set IsHandled = true, preventing the standard
+ // item tracking page from opening even when the purchase line was not a subcontracting line.
+ ItemTrackingWasOpened := false;
+ PurchaseLine.OpenItemTrackingLines();
+
+ // [THEN] The standard item tracking lines page was opened
+ Assert.IsTrue(
+ ItemTrackingWasOpened,
+ 'Item tracking lines page must open for a non-subcontracting purchase line');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess')]
+ procedure PostSubcontPurchOrder_PurchWithService_BackwardFlush()
+ var
+ ComponentItem: Record Item;
+ FinishedItem: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ Location, HomeLocation : Record Location;
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ RoutingLink: Record "Routing Link";
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ Qty: Decimal;
+ begin
+ // [SCENARIO] When posting a subcontracting purchase order where the BOM has a component
+ // with Component Supply Method = "Purchase with Service" and Flushing Method = Backward,
+ // the component is consumed via backward flushing when the output is posted.
+ // BOM: 1 component item (Component Supply Method = Purchase with Service, linked to Routing Line 100).
+ // Routing: 1 subcontracting line (Operation 100).
+ // Purchase order has 2 lines: Finished Good (output) + Component (Purchase with Service).
+ // After posting the purchase order:
+ // - Finished good gets positive output ILE.
+ // - Component gets positive purchase receipt ILE AND negative consumption ILE (backward flushing).
+ // - Net component inventory = 0.
+ Initialize();
+
+ // [GIVEN] A subcontracting work center with vendor and location
+ CreateAndCalculateNeededWorkCenter(WorkCenter, true);
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Subc. Location Code" := Location.Code;
+ Vendor.Modify();
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(HomeLocation);
+
+ // [GIVEN] A routing with a single subcontracting line (Operation 100)
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+ LibraryManufacturing.CreateRoutingLineSetup(
+ RoutingLine, RoutingHeader, WorkCenter."No.", '100',
+ LibraryRandom.RandInt(5), LibraryRandom.RandInt(5));
+
+ // [GIVEN] A routing link connecting BOM component to routing line
+ LibraryManufacturing.CreateRoutingLink(RoutingLink);
+ RoutingLine.Validate("Routing Link Code", RoutingLink.Code);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ // [GIVEN] A component item with Flushing Method = Backward
+ LibraryManufacturing.CreateItemManufacturing(
+ ComponentItem, "Costing Method"::FIFO, LibraryRandom.RandInt(10),
+ "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::Backward, '', '');
+
+ // [GIVEN] A production BOM with one component, Component Supply Method = Purchase with Service
+ LibraryManufacturing.CreateProductionBOMHeader(ProductionBOMHeader, ComponentItem."Base Unit of Measure");
+ LibraryManufacturing.CreateProductionBOMLine(
+ ProductionBOMHeader, ProductionBOMLine, '', ProductionBOMLine.Type::Item, ComponentItem."No.", 1);
+ ProductionBOMLine.Validate("Routing Link Code", RoutingLink.Code);
+ ProductionBOMLine.Validate("Component Supply Method", "Component Supply Method"::"Vendor-Supplied");
+ ProductionBOMLine.Modify(true);
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+
+ // [GIVEN] A finished good item with the routing and production BOM
+ LibraryManufacturing.CreateItemManufacturing(
+ FinishedItem, "Costing Method"::FIFO, LibraryRandom.RandInt(10),
+ "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual",
+ RoutingHeader."No.", ProductionBOMHeader."No.");
+
+ // [GIVEN] A released production order
+ Qty := LibraryRandom.RandInt(10) + 5;
+ SubcWarehouseLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, FinishedItem."No.", Qty, HomeLocation.Code);
+
+ // [GIVEN] Requisition worksheet template for subcontracting
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+
+ // [WHEN] Create subcontracting purchase order from Prod. Order Routing
+ ProdOrderRtngLine.SetRange("Routing No.", RoutingHeader."No.");
+ ProdOrderRtngLine.SetRange("Work Center No.", WorkCenter."No.");
+ ProdOrderRtngLine.FindFirst();
+
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRtngLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+
+ // [WHEN] Post the purchase order (receive)
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseLine.Reset();
+ PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type");
+ PurchaseLine.SetRange("Document No.", PurchaseHeader."No.");
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ if PurchaseLine.FindSet() then
+ repeat
+ EnsureGeneralPostingSetupIsValid(PurchaseLine."Gen. Bus. Posting Group", PurchaseLine."Gen. Prod. Posting Group");
+ until PurchaseLine.Next() = 0;
+
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [THEN] Finished good has a positive output ILE
+ ItemLedgerEntry.SetRange("Item No.", FinishedItem."No.");
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.FindFirst();
+ ItemLedgerEntry.TestField(Quantity, Qty);
+
+ // [THEN] Component has a positive purchase receipt ILE
+ ItemLedgerEntry.Reset();
+ ItemLedgerEntry.SetRange("Item No.", ComponentItem."No.");
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Purchase);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.FindFirst();
+ ItemLedgerEntry.TestField(Quantity, Qty);
+
+ // [THEN] Component has a negative consumption ILE (backward flushing)
+ ItemLedgerEntry.Reset();
+ ItemLedgerEntry.SetRange("Item No.", ComponentItem."No.");
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Consumption);
+ Assert.RecordIsNotEmpty(ItemLedgerEntry);
+ ItemLedgerEntry.FindFirst();
+ ItemLedgerEntry.TestField(Quantity, -Qty);
+
+ // [THEN] Net inventory of component is zero (received and consumed via backward flushing)
+ ComponentItem.CalcFields(Inventory);
+ Assert.AreEqual(0, ComponentItem.Inventory, 'Component inventory should be zero after backward flushing.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder')]
+ procedure CannotModifySubcPurchLineWhenTransferOrderExists()
+ var
+ Item: Record Item;
+ HomeLocation: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+ // [SCENARIO] Modifying key fields on a subcontracting purchase line must be blocked
+ // when a transfer order exists for the linked production order.
+ 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);
+
+ // [WHEN] Attempt to modify key fields on the subcontracting purchase line
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+
+ // [THEN] CheckSubcPurchLineCanBeModified blocks modification of Quantity
+ asserterror SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption(Quantity));
+ Assert.ExpectedError('You cannot change Quantity on the subcontracting purchase line');
+
+ // [THEN] CheckSubcPurchLineCanBeModified blocks modification of Location Code
+ asserterror SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption("Location Code"));
+ Assert.ExpectedError('You cannot change Location Code on the subcontracting purchase line');
+
+ // [THEN] CheckSubcPurchLineCanBeModified blocks modification of Variant Code
+ asserterror SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption("Variant Code"));
+ Assert.ExpectedError('You cannot change Variant Code on the subcontracting purchase line');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder,MessageHandler')]
+ procedure ModifySubcPurchLineAllowedAfterFullConsumptionAtSubcLocation()
+ var
+ 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";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ begin
+ // [SCENARIO] After all component stock transferred to the subcontractor location has been
+ // consumed, modifying purchase line fields should be allowed because net stock = 0.
+ 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);
+
+ // [VERIFY] Modification is blocked because transfer order exists
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ asserterror SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption(Quantity));
+ Assert.ExpectedError('transfer orders exist');
+
+ // [WHEN] Transfer order is posted to the subcontractor location
+ FindTransferOrderForPurchaseLine(TransferHeader, PurchaseLine);
+ PostDirectTransferOrder(TransferHeader);
+
+ // [VERIFY] Modification is blocked because stock exists at the subcontractor location
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ asserterror SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption(Quantity));
+ Assert.ExpectedError('remaining components or WIP items transferred to the subcontractor');
+
+ // [VERIFY] Purchase Order deletion is blocked because stock was transferred to the subcontractor location
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ asserterror PurchaseHeader.Delete(true);
+
+ // [GIVEN] All transferred stock has been consumed at the subcontractor location
+ FindTransferProdOrderComponent(ProdOrderComponent, PurchaseLine);
+ LibraryMfgManagement.PostConsumptionForAllComponents(ProdOrderComponent);
+
+ // [VERIFY] Modification is blocked because WIP item remains at the subcontractor location
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ asserterror SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption(Quantity));
+ Assert.ExpectedError('remaining components or WIP items transferred to the subcontractor');
+
+ // [WHEN] Return transfer order is created and posted to return remaining WIP item from subcontractor location
+ PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No.");
+ CreateReturnTransferOrderForPurchaseOrder(PurchaseHeader);
+
+ FindTransferOrderForPurchaseLine(TransferHeader, PurchaseLine);
+ PostDirectTransferOrder(TransferHeader);
+
+ // [WHEN] CheckSubcPurchLineCanBeModified is called after full consumption
+ // [THEN] No error is raised because net stock at the subcontractor location is zero
+ PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No.");
+ SubcTransferManagement.CheckSubcPurchLineCanBeModified(PurchaseLine, PurchaseLine.FieldCaption(Quantity));
+ end;
+
+ [Test]
+ [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder,MessageHandler')]
+ procedure SubcTransferPartialConsumptionAndReturnFlow()
+ var
+ ComponentItem: Record Item;
+ Item: Record Item;
+ HomeLocation: Record Location;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ 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";
+ TransferredQty: Decimal;
+ ConsumedQty: Decimal;
+ ReturnQty: Decimal;
+ begin
+ // [SCENARIO] Full subcontracting transfer lifecycle: create transfer order, post transfer
+ // to subcontractor, post partial consumption, create return transfer via purchase order action,
+ // adjust return qty to remaining stock, and post the return.
+ 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] Get transfer order and component for the purchase line
+ FindTransferProdOrderComponent(ProdOrderComponent, PurchaseLine);
+ ProdOrderComponent.FindFirst();
+
+
+ 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);
+ ReturnQty := TransferredQty - ConsumedQty;
+
+ // [WHEN] Transfer order is posted to the subcontractor location
+ PostDirectTransferOrder(TransferHeader);
+
+ // [THEN] Transfer ILE exists at the subcontractor location with correct quantity
+ ProdOrderComponent.Get(ProdOrderComponent.Status, ProdOrderComponent."Prod. Order No.", ProdOrderComponent."Prod. Order Line No.", ProdOrderComponent."Line No.");
+ ProdOrderComponent.CalcFields("Subc. Qty. transf. to Subcontr");
+ Assert.AreEqual(TransferredQty, ProdOrderComponent."Subc. Qty. transf. to Subcontr",
+ 'Transferred qty should equal full quantity after posting transfer order.');
+
+ // [WHEN] Partial consumption is posted at the subcontractor location
+ ProdOrderLine.Get(ProductionOrder.Status, ProductionOrder."No.", ProdOrderComponent."Prod. Order Line No.");
+ ComponentItem.Get(ProdOrderComponent."Item No.");
+ LibraryMfgManagement.PostConsumptionForComponent(ProdOrderLine, ProdOrderComponent, ComponentItem, ConsumedQty);
+
+ // [THEN] Consumption ILE exists with negative quantity
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Consumption);
+ ItemLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ ItemLedgerEntry.SetRange("Order Line No.", ProdOrderComponent."Prod. Order Line No.");
+ ItemLedgerEntry.SetRange("Prod. Order Comp. Line No.", ProdOrderComponent."Line No.");
+ ItemLedgerEntry.SetRange("Location Code", ProdOrderComponent."Location Code");
+ ItemLedgerEntry.CalcSums(Quantity);
+ Assert.AreEqual(-ConsumedQty, ItemLedgerEntry.Quantity,
+ 'Consumption ILE quantity should be negative consumed qty.');
+
+ // [WHEN] Return transfer order is created via the purchase order action
+ PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No.");
+ PurchaseOrderPage.OpenView();
+ PurchaseOrderPage.GoToRecord(PurchaseHeader);
+ PurchaseOrderPage.CreateReturnFromSubcontractor.Invoke();
+ PurchaseOrderPage.Close();
+
+ // [THEN] Return transfer line is created with correct quantity (only remaining physical stock, not consumed)
+ 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();
+ Assert.AreEqual(ReturnQty, ReturnTransferLine.Quantity,
+ 'Return transfer line quantity should equal remaining physical stock (transferred - consumed).');
+ ReturnTransferHeader.Get(ReturnTransferLine."Document No.");
+
+ // [THEN] Return transfer order is posted
+ 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;
+
+ [Test]
+ procedure WorkCenterRoutingLinesExcludedFromMultiSelectionWhenMachineCenterPresent()
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ Item, Item2 : Record Item;
+ MachineCenter: Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ WorkCenter: Record "Work Center";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ MachineCenterNo: Code[20];
+ NoOfCreatedOrders: Integer;
+ begin
+ // [SCENARIO] When CreateSubcontractingOrdersForRoutingLineSelection is called with a mixed
+ // selection containing both Work Center and Machine Center routing lines, only the
+ // Work Center lines result in a subcontracting purchase order.
+ // This verifies that the Work Center type filter is applied correctly when simulating
+ // multi-record selection (CurrPage.SetSelectionFilter cannot be used in test framework).
+ Initialize();
+
+ // [GIVEN] A subcontracting Work Center with a vendor
+ CreateAndCalculateNeededWorkCenter(WorkCenter, true);
+
+ // [GIVEN] A Machine Center belonging to the subcontracting Work Center
+ LibraryMfgManagement.CreateMachineCenter(MachineCenterNo, WorkCenter."No.", "Flushing Method"::"Pick + Manual".AsInteger());
+ MachineCenter.Get(MachineCenterNo);
+ LibraryManufacturing.CalculateMachCenterCalendar(MachineCenter, CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ // [GIVEN] A routing with a Work Center line (Op 010) and a Machine Center line (Op 020),
+ // both referencing the same subcontracting Work Center
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+
+ LibraryManufacturing.CreateRoutingLineSetup(RoutingLine, RoutingHeader, WorkCenter."No.", '010', 1, 1);
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Modify(true);
+
+ RoutingLine.Type := RoutingLine.Type::"Machine Center";
+ LibraryManufacturing.CreateRoutingLineSetup(RoutingLine, RoutingHeader, MachineCenter."No.", '020', 1, 1);
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ LibraryInventory.CreateItem(Item2);
+ LibraryManufacturing.CreateProductionBOM(Item2, 2);
+
+ // [GIVEN] An item with the routing and a released production order
+ LibraryManufacturing.CreateItemManufacturing(
+ Item, "Costing Method"::FIFO, LibraryRandom.RandInt(10),
+ "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual",
+ RoutingHeader."No.", Item2."Production BOM No.");
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 1);
+
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+
+ // [WHEN] All routing lines (Work Center Op 010 + Machine Center Op 020) are passed to
+ // CreateSubcontractingOrdersForRoutingLineSelection, simulating a multi-record selection
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ NoOfCreatedOrders := SubcPurchaseOrderCreator.CreateSubcontractingOrdersForRoutingLineSelection(ProdOrderRoutingLine);
+
+ // [THEN] Exactly one purchase order is created (Work Center line is filtered out)
+ Assert.AreEqual(1, NoOfCreatedOrders, 'Exactly one subcontracting purchase order must be created.');
+
+ // [THEN] The purchase order is linked to the Work Center operation (Op 010)
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Operation No.", '010');
+ Assert.RecordCount(PurchaseLine, 1);
+
+ // [THEN] No purchase order is created for the Machine Center operation (Op 020)
+ PurchaseLine.SetRange("Operation No.", '020');
+ Assert.RecordIsEmpty(PurchaseLine);
+ end;
+
+ [ModalPageHandler]
+ procedure ItemTrackingLinesSimpleHandler(var ItemTrackingLines: TestPage "Item Tracking Lines")
+ begin
+ ItemTrackingWasOpened := true;
+ ItemTrackingLines.OK().Invoke();
+ end;
+
+ [ConfirmHandler]
+ procedure DoConfirmCreateProdOrderForSubcontractingProcess(Question: Text[1024]; var Reply: Boolean)
+ begin
+ case true of
+ Question.Contains('Do you want to create a production order from'):
+ Reply := true;
+ else
+ Reply := false;
+ end;
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ end;
+
+ [MessageHandler]
+ procedure MessageHandler(Message: Text[1024])
+ begin
+ end;
+
+ local procedure CreateAndCalculateNeededWorkCenter(var WorkCenter: Record "Work Center"; IsSubcontracting: Boolean)
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ ShopCalendarCode: Code[10];
+ WorkCenterNo: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center
+ CreateWorkCenter(WorkCenterNo, ShopCalendarCode, "Flushing Method"::"Pick + Manual", IsSubcontracting, UnitCostCalculation, '');
+ WorkCenter.Get(WorkCenterNo);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter, CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+ end;
+
+ local procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; IsSubcontracting: Boolean; UnitCostCalc: Option; CurrencyCode: Code[10])
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ VATPostingSetup: Record "VAT Posting Setup";
+ WorkCenter: Record "Work Center";
+ begin
+ // Create Work Center with required fields where random is used, values not important for test.
+ LibraryMfgManagement.CreateWorkCenterWithFixedCost(WorkCenter, ShopCalendarCode, 0);
+
+ WorkCenter.Validate("Flushing Method", FlushingMethod);
+ WorkCenter.Validate("Direct Unit Cost", LibraryRandom.RandDec(10, 2));
+ WorkCenter.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Overhead Rate", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Unit Cost Calculation", UnitCostCalc);
+
+ if IsSubcontracting then begin
+ LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT");
+ GenProductPostingGroup.FindFirst();
+ GenProductPostingGroup.Validate("Def. VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group");
+ GenProductPostingGroup.Modify(true);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(CurrencyCode));
+ end;
+ WorkCenter.Modify(true);
+ WorkCenterNo := WorkCenter."No.";
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Purch. Subcont. Test");
+ LibrarySetupStorage.Restore();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Purch. Subcont. Test");
+
+ SubcontractingMgmtLibrary.Initialize();
+ LibraryMfgManagement.Initialize();
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Purch. Subcont. Test");
+ end;
+
+ local procedure EnsureGeneralPostingSetupIsValid(GenBusPostingGroup: Code[20]; GenProdPostingGroup: Code[20])
+ var
+ GeneralPostingSetup: Record "General Posting Setup";
+ begin
+ if GeneralPostingSetup.Get(GenBusPostingGroup, GenProdPostingGroup) then begin
+ if GeneralPostingSetup.Blocked then begin
+ GeneralPostingSetup.Blocked := false;
+ GeneralPostingSetup.Modify();
+ end;
+ exit;
+ end;
+
+ GeneralPostingSetup.Init();
+ GeneralPostingSetup."Gen. Bus. Posting Group" := GenBusPostingGroup;
+ GeneralPostingSetup."Gen. Prod. Posting Group" := GenProdPostingGroup;
+ GeneralPostingSetup.Insert();
+ GeneralPostingSetup.SuggestSetupAccounts();
+ end;
+
+ local procedure SetupSubcontractingEnvironment()
+ begin
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+ end;
+
+ local procedure CreateSubcontractingWorkCenters(var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ begin
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ end;
+
+ local procedure SetupProductionItemWithTransferComponent(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center"; ComponentSupplyMethod: Enum "Component Supply Method")
+ begin
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, ComponentSupplyMethod);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+ end;
+
+ local procedure CreateProductionOrderWithTransferRoute(var ProductionOrder: Record "Production Order"; var Location: Record Location; var Item: Record Item; ProductionQty: Decimal)
+ begin
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", ProductionQty, Location.Code);
+ end;
+
+ local procedure CreateSubcontractingPurchaseOrderForProdOrder(var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var ProductionOrder: Record "Production Order")
+ begin
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+ PurchaseLine.SetCurrentKey("Prod. Order No.", "Prod. Order Line No.", "Routing No.", "Operation No.");
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+#pragma warning disable AA0210
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ PurchaseLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ end;
+
+ local procedure CreateTransferOrderForPurchaseOrder(var PurchaseHeader: Record "Purchase Header")
+ begin
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Create Transf. Order", false, false, PurchaseHeader);
+ end;
+
+ local procedure CreateReturnTransferOrderForPurchaseOrder(var PurchaseHeader: Record "Purchase Header")
+ begin
+ PurchaseHeader.SetRecFilter();
+ Report.Run(Report::"Subc. Create SubCReturnOrder", false, false, PurchaseHeader);
+ end;
+
+ local procedure SetupSubContractingProdOrder(var Item: Record Item; var Location: Record Location; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center"; var ProductionOrder: Record "Production Order"; ComponentSupplyMethod: Enum "Component Supply Method"; ProductionQty: Decimal)
+ begin
+ SetupSubcontractingEnvironment();
+ CreateSubcontractingWorkCenters(WorkCenter, MachineCenter);
+ SetupProductionItemWithTransferComponent(Item, WorkCenter, MachineCenter, ComponentSupplyMethod);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ CreateProductionOrderWithTransferRoute(ProductionOrder, Location, Item, ProductionQty);
+ CreateInventoryForAllComponents(ProductionOrder);
+ end;
+
+ local procedure FindTransferProdOrderComponent(var ProdOrderComponent: Record "Prod. Order Component"; PurchaseLine: Record "Purchase Line")
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", PurchaseLine."Prod. Order No.");
+ ProdOrderRoutingLine.SetRange("Routing No.", PurchaseLine."Routing No.");
+ ProdOrderRoutingLine.SetRange("Operation No.", PurchaseLine."Operation No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ 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("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+ ProdOrderComponent.SetRange("Subc. Purchase Order Filter", PurchaseLine."Document No.");
+#pragma warning disable AA0210
+ ProdOrderComponent.SetRange("Component Supply Method", ProdOrderComponent."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ end;
+
+ local procedure FindTransferOrderForPurchaseLine(var TransferHeader: Record "Transfer Header"; PurchaseLine: Record "Purchase Line")
+ var
+ TransferLine: Record "Transfer Line";
+ begin
+#pragma warning disable AA0210
+ 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.");
+#pragma warning restore AA0210
+ TransferLine.FindFirst();
+ TransferHeader.Get(TransferLine."Document No.");
+ end;
+
+ local procedure CreateAndPostItemInventory(ItemNo: Code[20]; LocationCode: Code[10]; Qty: Decimal)
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ begin
+ LibraryInventory.CreateItemJournalLineInItemTemplate(ItemJournalLine, ItemNo, LocationCode, '', Qty);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+
+ local procedure PostDirectTransferOrder(var TransferHeader: Record "Transfer Header")
+ var
+ TransferOrderPage: TestPage "Transfer Order";
+ begin
+ TransferOrderPage.OpenView();
+ TransferOrderPage.GoToRecord(TransferHeader);
+ TransferOrderPage.Post.Invoke();
+ end;
+
+ local procedure CreateInventoryForAllComponents(ProductionOrder: Record "Production Order")
+ var
+ ProdOrderComponent: Record "Prod. Order Component";
+ begin
+ ProdOrderComponent.SetRange(Status, ProductionOrder.Status);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProductionOrder."No.");
+ if ProdOrderComponent.FindSet() then
+ repeat
+ CreateAndPostItemInventory(ProdOrderComponent."Item No.", ProdOrderComponent."Location Code", ProdOrderComponent."Expected Quantity");
+ until ProdOrderComponent.Next() = 0;
+ end;
+
+ local procedure SetTransferWIPItemOnRoutingLine(RoutingNo: Code[20]; WorkCenterNo: Code[20]; TransferWIPItem: Boolean)
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+ RoutingHeader.Get(RoutingNo);
+ 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);
+ RoutingLine.FindFirst();
+ RoutingLine."Transfer WIP Item" := TransferWIPItem;
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+ end;
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingSyncTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingSyncTest.Codeunit.al
new file mode 100644
index 0000000000..e56002b34f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingSyncTest.Codeunit.al
@@ -0,0 +1,620 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.VAT.Setup;
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+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;
+
+codeunit 139992 "Subc. Subcontracting Sync Test"
+{ // [FEATURE] Subcontracting Management
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ [Test]
+ procedure TestCreationOfPurchOrderFromRtngLineWithSubcontractorWithAddLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionOrder: Record "Production Order";
+ PurchLine: Record "Purchase Line";
+ RequisitionLine: Record "Requisition Line";
+ Work_Center: Record "Work Center";
+ WorkCenter: array[2] of Record "Work Center";
+ ManufacturingSetup: Record "Manufacturing Setup";
+ SubcCalculateSubcontracts: Report "Subc. Calculate Subcontracts";
+ ReqJnlManagement: Codeunit ReqJnlManagement;
+ SubTestManSubscription: Codeunit "Subc. Test Man. Subscription";
+ begin
+ // [SCENARIO] Calculating Subcontracting Deletes Prod Order Quantities
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ BindSubscription(SubTestManSubscription);
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ ManufacturingSetup.Get();
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Vendor-Supplied");
+
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+#pragma warning disable AA0175
+ PurchLine.SetRange("No.", Item."No.");
+ PurchLine.DeleteAll();
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+#pragma warning disable AA0210
+ ProductionBOMLine.SetRange("Component Supply Method", ProductionBOMLine."Component Supply Method"::"Vendor-Supplied");
+#pragma warning restore AA0210
+ ProductionBOMLine.FindFirst();
+#pragma warning restore
+
+ ManufacturingSetup.Get();
+ RequisitionLine."Worksheet Template Name" := ManufacturingSetup."Subcontracting Template Name";
+ RequisitionLine."Journal Batch Name" := ManufacturingSetup."Subcontracting Batch Name";
+
+ RequisitionLine.FilterGroup := 2;
+ RequisitionLine.SetRange("Worksheet Template Name", ManufacturingSetup."Subcontracting Template Name");
+ RequisitionLine.FilterGroup := 0;
+ ReqJnlManagement.OpenJnl(RequisitionLine."Journal Batch Name", RequisitionLine);
+
+ SubcCalculateSubcontracts.SetWkShLine(RequisitionLine);
+ Work_Center.SetRange("No.", WorkCenter[2]."No.");
+ SubcCalculateSubcontracts.SetTableView(WorkCenter[2]);
+ SubcCalculateSubcontracts.UseRequestPage(false);
+ SubcCalculateSubcontracts.RunModal();
+
+ MakeSubconPurchOrder(ProductionOrder."No.", WorkCenter[2]."No.");
+
+ Assert.AreNotEqual(0, ProductionOrder.Quantity, 'Prod. order Qty. must not be zero after calculation of subcontracting in work sheet.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder')]
+ procedure ChangeVendorKeepsTransferOrderWhenItemLedgerEntryExistsForProductionOrder()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ MachineCenter: array[2] of Record "Machine Center";
+ 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";
+ ProductionLocation: Record Location;
+ InitialTransferOrderNo: Code[20];
+ begin
+ // [SCENARIO 623643] When an Item Ledger Entry exists for Production Order "P", changing vendor on Subcontracting PO must NOT delete Transfer Order "T"
+ Initialize();
+
+ // [GIVEN] Subcontracting setup with Transfer-type Production Order "P" for item "I", Subcontracting PO for vendor "V1", Transfer Order "T"
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ ProductionOrder.Get(ProductionOrder.Status, ProductionOrder."No.");
+ ProductionOrder."Created from Purch. Order" := true;
+ ProductionOrder.Modify();
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SetAllProdOrderTransferComponentLocations(ProductionOrder."No.", ProductionLocation.Code);
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseLine.Validate("Location Code", ProductionLocation.Code);
+ PurchaseLine.Modify(true);
+
+ CreateTransferOrderForSubcontractingPO(PurchaseHeader);
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ TransferHeader.FindFirst();
+ InitialTransferOrderNo := TransferHeader."No.";
+
+ // [GIVEN] Item Ledger Entry of "Order Type" = Production and "Order No." = "P"
+ CreateItemLedgerEntryForProductionOrder(ItemLedgerEntry, ProductionOrder, Item);
+
+ // [GIVEN] Second subcontracting Vendor "V2"
+ CreateSecondSubcontractingVendor(Vendor, WorkCenter[2]);
+
+ // [WHEN] Validate "Buy-from Vendor No." on Subcontracting PO to "V2"
+ PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No.");
+ PurchaseHeader.Validate("Buy-from Vendor No.", Vendor."No.");
+ PurchaseHeader.Modify(true);
+
+ // [THEN] Transfer Order "T" still exists
+ Assert.IsTrue(TransferHeader.Get(InitialTransferOrderNo), 'Transfer Order must still exist when Item Ledger Entry exists for Production Order');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoConfirmCreateProdOrderForSubcontractingProcess,HandleTransferOrder')]
+ procedure ChangeVendorDeletesTransferOrderWhenNoItemLedgerEntryExistsForProductionOrder()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ 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";
+ ProductionLocation: Record Location;
+ InitialTransferOrderNo: Code[20];
+ begin
+ // [SCENARIO 623643] When NO Item Ledger Entry exists for Production Order "P", changing vendor on Subcontracting PO must delete Transfer Order "T"
+ Initialize();
+
+ // [GIVEN] Subcontracting setup with Transfer-type Production Order "P", Subcontracting PO for vendor "V1", Transfer Order "T"
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", 1); // Use quantity 1
+ ProductionOrder.Get(ProductionOrder.Status, ProductionOrder."No.");
+ ProductionOrder."Created from Purch. Order" := true;
+ ProductionOrder.Modify();
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SetAllProdOrderTransferComponentLocations(ProductionOrder."No.", ProductionLocation.Code);
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseLine.Validate("Location Code", ProductionLocation.Code);
+ PurchaseLine.Modify(true);
+
+ CreateTransferOrderForSubcontractingPO(PurchaseHeader);
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ TransferHeader.FindFirst();
+ InitialTransferOrderNo := TransferHeader."No.";
+
+ // [GIVEN] No Item Ledger Entry exists with "Order Type" = Production and "Order No." = "P"
+
+ // [GIVEN] Second subcontracting Vendor "V2"
+ CreateSecondSubcontractingVendor(Vendor, WorkCenter[2]);
+
+ // [WHEN] Validate "Buy-from Vendor No." on Subcontracting PO to "V2"
+ // Pre-clear all blocking relationships to allow Production Order deletion to succeed
+ PrepareProdOrderForDeletion(ProductionOrder."No.", PurchaseHeader."No.");
+
+ // Change vendor - triggers deletion logic with ItemLedgerEntry2.IsEmpty() check
+ PurchaseHeader.Get(PurchaseHeader."Document Type", PurchaseHeader."No.");
+ PurchaseHeader.Validate("Buy-from Vendor No.", Vendor."No.");
+ PurchaseHeader.Modify(true);
+
+ // [THEN] Transfer Order "T" no longer exists
+ Assert.IsFalse(TransferHeader.Get(InitialTransferOrderNo), 'Transfer Order must be deleted when no Item Ledger Entry exists for Production Order');
+ end;
+
+ local procedure MakeSubconPurchOrder(ProductionOrderNo: Code[20]; WorkCenterNo: Code[20])
+ var
+ RequisitionLine: Record "Requisition Line";
+ begin
+ // Update Direct unit Cost and Make Order,random is used values not important for test.
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrderNo);
+ RequisitionLine.SetRange("Work Center No.", WorkCenterNo);
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+ RequisitionLine.Validate("Direct Unit Cost", LibraryRandom.RandInt(10));
+ RequisitionLine.Modify(true);
+ LibraryPlanning.CarryOutAMSubcontractWksh(RequisitionLine);
+ end;
+
+ local procedure CreateAndCalculateNeededWorkAndMachineCenter(var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ ShopCalendarCode: Code[10];
+ MachineCenterNo: Code[20];
+ MachineCenterNo2: Code[20];
+ WorkCenterNo: Code[20];
+ WorkCenterNo2: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center
+ CreateWorkCenter(WorkCenterNo, ShopCalendarCode, "Flushing Method"::"Pick + Manual", not Subcontracting, UnitCostCalculation, '');
+ WorkCenter[1].Get(WorkCenterNo);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter[1], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ LibraryMfgManagement.CreateMachineCenter(MachineCenterNo, WorkCenterNo, "Flushing Method"::"Pick + Manual".AsInteger());
+ MachineCenter[1].Get(MachineCenterNo);
+ LibraryManufacturing.CalculateMachCenterCalendar(MachineCenter[1], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ LibraryMfgManagement.CreateMachineCenter(MachineCenterNo2, WorkCenterNo, "Flushing Method"::"Pick + Manual".AsInteger());
+ MachineCenter[2].Get(MachineCenterNo2);
+ LibraryManufacturing.CalculateMachCenterCalendar(MachineCenter[2], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ if Subcontracting then
+ CreateWorkCenter(WorkCenterNo2, ShopCalendarCode, "Flushing Method"::"Pick + Manual", Subcontracting, UnitCostCalculation, '')
+ else
+ CreateWorkCenter(WorkCenterNo2, ShopCalendarCode, "Flushing Method"::"Pick + Manual", not Subcontracting, UnitCostCalculation, '');
+ WorkCenter[2].Get(WorkCenterNo2);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter[2], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+ end;
+
+ local procedure CreateItemForProductionIncludeRoutingAndProdBOM(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ ProductionBOMHeader: Record "Production BOM Header";
+ NoSeries: Codeunit "No. Series";
+ ItemNo: Code[20];
+ ItemNo2: Code[20];
+ ProductionBOMNo: Code[20];
+ RoutingNo: Code[20];
+ begin
+ ManufacturingSetup.SetLoadFields("Routing Nos.");
+ ManufacturingSetup.Get();
+ RoutingNo := NoSeries.GetNextNo(ManufacturingSetup."Routing Nos.", WorkDate(), true);
+
+ LibraryMfgManagement.CreateRouting(RoutingNo, MachineCenter[1]."No.", MachineCenter[2]."No.", WorkCenter[1]."No.", WorkCenter[2]."No.");
+
+ // Create Items with Flushing method - Manual with the Parent Item containing Routing No. and Production BOM No.
+
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", '', '');
+ ItemNo := Item."No.";
+ Clear(Item);
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", '', '');
+ ItemNo2 := Item."No.";
+ Clear(Item);
+
+ ProductionBOMNo := LibraryManufacturing.CreateCertifProdBOMWithTwoComp(ProductionBOMHeader, ItemNo, ItemNo2, 1); // value important.
+
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", RoutingNo, ProductionBOMNo);
+ end;
+
+ local 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
+ RoutingLink.Init();
+ RoutingLink.Validate(Code, CopyStr(Item."Production BOM No.", 1, 10));
+ RoutingLink.Insert(true);
+
+ 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);
+ RoutingLine.FindFirst();
+ RoutingLine.Validate("Routing Link Code", RoutingLink.Code);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ ProductionBOMLine.FindLast();
+ ProductionBOMLine.Validate("Routing Link Code", RoutingLink.Code);
+ ProductionBOMLine.Modify(true);
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ local procedure UpdateProdBomWithComponentSupplyMethod(Item: Record Item; ComponentSupplyMethod: Enum "Component Supply Method")
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ begin
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ ProductionBOMLine.FindLast();
+ ProductionBOMLine."Component Supply Method" := ComponentSupplyMethod;
+ ProductionBOMLine.Modify(true);
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ local procedure UpdateVendorWithSubcontractingLocationCode(WorkCenter: Record "Work Center")
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ Vendor."Subc. Location Code" := Location.Code;
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+ end;
+
+ procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean; UnitCostCalc: Option; CurrencyCode: Code[10])
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ VATPostingSetup: Record "VAT Posting Setup";
+ WorkCenter: Record "Work Center";
+ begin
+ // Create Work Center with required fields where random is used, values not important for test.
+ LibraryMfgManagement.CreateWorkCenterWithFixedCost(WorkCenter, ShopCalendarCode, 0);
+
+ WorkCenter.Validate("Flushing Method", FlushingMethod);
+ WorkCenter.Validate("Direct Unit Cost", LibraryRandom.RandDec(10, 2));
+ WorkCenter.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Overhead Rate", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Unit Cost Calculation", UnitCostCalc);
+
+ if Subcontract then begin
+ LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT");
+ GenProductPostingGroup.FindFirst();
+ GenProductPostingGroup.Validate("Def. VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group");
+ GenProductPostingGroup.Modify(true);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(CurrencyCode));
+ end;
+ WorkCenter.Modify(true);
+ WorkCenterNo := WorkCenter."No.";
+ end;
+
+ local procedure CreateItem(var Item: Record Item; ItemCostingMethod: Enum "Costing Method"; ItemReorderPolicy: Enum "Reordering Policy"; FlushingMethod: Enum "Flushing Method"; RoutingNo: Code[20]; ProductionBOMNo: Code[20])
+ begin
+ // Create Item with required fields where random values not important for test.
+ LibraryManufacturing.CreateItemManufacturing(
+ Item, ItemCostingMethod, LibraryRandom.RandInt(10), ItemReorderPolicy, FlushingMethod, RoutingNo, ProductionBOMNo);
+ Item.Validate("Overhead Rate", LibraryRandom.RandDec(5, 2));
+ Item.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 2));
+ Item.Modify(true);
+ end;
+
+ local procedure CreateTransferOrderForSubcontractingPO(var PurchaseHeader: Record "Purchase Header")
+ var
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+ PurchaseHeaderPage.Close();
+ end;
+
+ local procedure CreateItemLedgerEntryForProductionOrder(var ItemLedgerEntry: Record "Item Ledger Entry"; ProductionOrder: Record "Production Order"; Item: Record Item)
+ begin
+ ItemLedgerEntry.Init();
+ if ItemLedgerEntry.FindLast() then;
+ ItemLedgerEntry."Entry No." += 1;
+ ItemLedgerEntry."Item No." := Item."No.";
+ ItemLedgerEntry."Order Type" := ItemLedgerEntry."Order Type"::Production;
+ ItemLedgerEntry."Order No." := ProductionOrder."No.";
+ ItemLedgerEntry."Entry Type" := ItemLedgerEntry."Entry Type"::Output;
+ ItemLedgerEntry.Quantity := 1;
+ ItemLedgerEntry.Insert();
+ end;
+
+ local procedure CreateSecondSubcontractingVendor(var Vendor: Record Vendor; WorkCenter: Record "Work Center")
+ var
+ Location: Record Location;
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ VATPostingSetup: Record "VAT Posting Setup";
+ begin
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT");
+ GenProductPostingGroup.Get(WorkCenter."Gen. Prod. Posting Group");
+ LibraryMfgManagement.CreateSubcontractorWithCurrency('');
+ Vendor.FindLast();
+ Vendor."Subc. Location Code" := Location.Code;
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+ end;
+
+ local procedure SetAllProdOrderTransferComponentLocations(ProdOrderNo: Code[20]; LocationCode: Code[10])
+ var
+ ProdOrderComp: Record "Prod. Order Component";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+ if ProdOrderComp.FindSet() then
+ repeat
+ ProdOrderComp."Location Code" := LocationCode;
+ ProdOrderComp.Modify();
+ until ProdOrderComp.Next() = 0;
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Subcontracting Sync Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ UpdateSubMgmtSetupComponentAtLocation("Components at Location"::Purchase);
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Subcontracting Sync Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Subcontracting Sync Test");
+ end;
+
+ local procedure CreateAndRefreshProductionOrder(var ProductionOrder: Record "Production Order"; ProdOrderStatus: Enum "Production Order Status"; ProdOrderSourceType: Enum "Prod. Order Source Type"; SourceNo: Code[20]; Quantity: Decimal)
+ begin
+ LibraryManufacturing.CreateAndRefreshProductionOrder(ProductionOrder, ProdOrderStatus, ProdOrderSourceType, SourceNo, Quantity);
+ end;
+
+ local procedure UpdateSubMgmtSetupWithReqWkshTemplate()
+ begin
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+ end;
+
+ local procedure UpdateSubMgmtSetupComponentAtLocation(CompAtLocation: Enum "Components at Location")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Subc. Default Comp. Location" := CompAtLocation;
+ ManufacturingSetup.Modify();
+ end;
+
+ local procedure CreateSubcontractingOrderFromProdOrderRtngPage(RoutingNo: Code[20]; WorkCenterNo: Code[20])
+ var
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ begin
+ ProdOrderRtngLine.SetRange("Routing No.", RoutingNo);
+ ProdOrderRtngLine.SetRange("Work Center No.", WorkCenterNo);
+ ProdOrderRtngLine.FindFirst();
+
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRtngLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+ end;
+
+ local procedure PrepareProdOrderForDeletion(ProdOrderNo: Code[20]; PurchDocNo: Code[20])
+ var
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ begin
+ // Keep main Purchase Line with Prod. Order No. so deletion logic runs,
+ // but clear Line No. and routing fields to prevent blocking
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Document No.", PurchDocNo);
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderNo);
+ if PurchaseLine.FindFirst() then begin
+ PurchaseLine."Prod. Order Line No." := 0;
+ PurchaseLine."Operation No." := '';
+ PurchaseLine."Routing No." := '';
+ PurchaseLine."Routing Reference No." := 0;
+ PurchaseLine."Qty. Received (Base)" := 0;
+ PurchaseLine.Modify();
+ end;
+
+ // Delete subcontracting Purchase Lines
+ PurchaseLine.Reset();
+ PurchaseLine.SetRange("Subc. Prod. Order No.", ProdOrderNo);
+ PurchaseLine.DeleteAll(true);
+
+ // Delete Production Order parts to allow DeleteProdOrderRelations() to succeed
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderComp.DeleteAll(true);
+
+ ProdOrderRtngLine.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderRtngLine.DeleteAll(true);
+
+ ProdOrderLine.SetRange("Prod. Order No.", ProdOrderNo);
+ ProdOrderLine.DeleteAll(true);
+ end;
+
+ [ConfirmHandler]
+ procedure DoConfirmCreateProdOrderForSubcontractingProcess(Question: Text[1024]; var Reply: Boolean)
+ begin
+ case true of
+ Question.Contains('Do you want to create a production order from'):
+ Reply := true;
+ else
+ Reply := false;
+ end;
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERM: Codeunit "Library - ERM";
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryPlanning: Codeunit "Library - Planning";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ IsInitialized: Boolean;
+ Subcontracting: Boolean;
+ UnitCostCalculation: Option Time,Units;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingTest.Codeunit.al
new file mode 100644
index 0000000000..c46a759730
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingTest.Codeunit.al
@@ -0,0 +1,4202 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Finance.VAT.Setup;
+using Microsoft.Foundation.NoSeries;
+using Microsoft.Foundation.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Planning;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Archive;
+using Microsoft.Purchases.Comment;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Sales.Customer;
+using Microsoft.Sales.Document;
+using Microsoft.Utilities;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.Structure;
+using System.TestLibraries.Utilities;
+
+codeunit 139989 "Subc. Subcontracting Test"
+{
+ // [FEATURE] Subcontracting Management
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder,HandleCreateTransferOrderMsg')]
+ procedure DirectTransferPostingWithWIPItemDoesNotErrorOnQuantity()
+ var
+ Bin: Record Bin;
+ DirectTransHeader: Record "Direct Trans. Header";
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO 636823] Direct Transfer posting with WIP items should not fail with "quantity not entered" error
+ // when Inventory Setup has Direct Transfer Posting = Direct Transfer.
+ Initialize();
+
+ // [GIVEN] Inventory Setup with Direct Transfer Posting = Direct Transfer
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ // [GIVEN] Subcontracting purchase order and transfer order to vendor (no in-transit route = direct transfer)
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.FindFirst();
+ TransferHeader.Get(TransferLine."Document No.");
+
+ Item.Get(ProdOrderComp."Item No.");
+ Location.Get(TransferHeader."Transfer-from Code");
+ CreateInventory(Item, Location, Bin, ProdOrderComp."Expected Qty. (Base)");
+
+ // [WHEN] Post the direct transfer (transfer order includes both component and WIP lines)
+ Codeunit.Run(Codeunit::"TransferOrder-Post Transfer", TransferHeader);
+
+ // [THEN] Direct Trans. Header is created without error (posting succeeds)
+ DirectTransHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ Assert.RecordIsNotEmpty(DirectTransHeader);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure CreateTransferOrderFromSecondSubcontractingOrderOpensReusedTransferOrder()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder1: Record "Production Order";
+ ProductionOrder2: Record "Production Order";
+ PurchaseHeader1: Record "Purchase Header";
+ PurchaseHeader2: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ProductionLocation: Record Location;
+ FirstTransferOrderNo: Code[20];
+ SecondTransferOrderNo: Code[20];
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO 634237] Creating transfer order for a second subcontracting PO should create and open a different transfer order.
+
+ // [GIVEN] Subcontracting setup with transfer components and an initial subcontracting order with transfer order already created
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder1, "Production Order Status"::Released, ProductionOrder1."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SetAllProdOrderTransferComponentLocations(ProductionOrder1."No.", ProductionLocation.Code);
+
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder1);
+
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder1."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+ ReleasedProdOrderRtng.Close();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder1."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader1.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader1);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+ PurchaseHeaderPage.Close();
+
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader1."No.");
+ Assert.IsTrue(TransferHeader.FindFirst(), 'Expected transfer order for the first subcontracting purchase order.');
+ FirstTransferOrderNo := TransferHeader."No.";
+ Assert.AreEqual(PurchaseHeader1."No.", TransferHeader."Subcontr. Purch. Order No.", 'First transfer order must be linked to the first subcontracting purchase order.');
+
+ // [GIVEN] A second released production order for the same subcontracting setup
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder2, "Production Order Status"::Released, ProductionOrder2."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ SetAllProdOrderTransferComponentLocations(ProductionOrder2."No.", ProductionLocation.Code);
+
+ ProdOrderRoutingLine.Reset();
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder2."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+ ReleasedProdOrderRtng.Close();
+
+ PurchaseLine.Reset();
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder2."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader2.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ Assert.AreNotEqual(PurchaseHeader1."No.", PurchaseHeader2."No.", 'Second production order should create another subcontracting purchase order.');
+
+ // [WHEN] Creating transfer order from the second subcontracting purchase order
+ OpenedTransferOrderNo := '';
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader2);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+ PurchaseHeaderPage.Close();
+
+ TransferHeader.Reset();
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader2."No.");
+ Assert.IsTrue(TransferHeader.FindFirst(), 'Expected transfer order for the second subcontracting purchase order.');
+ SecondTransferOrderNo := TransferHeader."No.";
+ Assert.AreEqual(PurchaseHeader2."No.", TransferHeader."Subcontr. Purch. Order No.", 'Second transfer order must be linked to the second subcontracting purchase order.');
+
+ // [THEN] A new transfer order is opened for the second subcontracting purchase order and contains lines for the second production order
+ Assert.AreNotEqual(FirstTransferOrderNo, SecondTransferOrderNo, 'A different subcontracting purchase order must create a new transfer order.');
+ Assert.AreEqual(SecondTransferOrderNo, OpenedTransferOrderNo, 'The transfer order opened from the second subcontracting PO must belong to that purchase order.');
+ TransferHeader.Get(FirstTransferOrderNo);
+ Assert.AreEqual(PurchaseHeader1."No.", TransferHeader."Subcontr. Purch. Order No.", 'First transfer order must remain linked to the first subcontracting purchase order.');
+ TransferHeader.Get(SecondTransferOrderNo);
+ Assert.AreEqual(PurchaseHeader2."No.", TransferHeader."Subcontr. Purch. Order No.", 'Second transfer order must remain linked to the second subcontracting purchase order.');
+
+ TransferLine.Reset();
+ TransferLine.SetRange("Document No.", SecondTransferOrderNo);
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder2."No.");
+ Assert.RecordIsNotEmpty(TransferLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder,HandleSubcTransferOrdersList')]
+ procedure SubcTransferOrdersActionOnProductionOrderOpensRelatedTransferOrder()
+ var
+ Item: Record Item;
+ ProductionOrder: Record "Production Order";
+ UnrelatedProductionOrder: Record "Production Order";
+ ProductionLocation: Record Location;
+ WorkCenter: array[2] of Record "Work Center";
+ ExpectedTransferOrderNo: Code[20];
+ ReleasedProductionOrder: TestPage "Released Production Order";
+ begin
+ // [SCENARIO 638532] The Released Production Order card provides a navigation action to view only its related subcontracting transfer orders.
+
+ // [GIVEN] Subcontracting setup with two released production orders, each ending up with its own subcontracting purchase order and transfer order.
+ // The transfer route is location-based (component location -> subcontractor location), so it is created only for the first order and reused by the second.
+ SetupSubcontractingForTransferOrderTests(Item, WorkCenter, ProductionLocation);
+ ExpectedTransferOrderNo := CreateProductionOrderWithSubcTransferOrder(Item, WorkCenter, ProductionLocation.Code, true, ProductionOrder);
+ CreateProductionOrderWithSubcTransferOrder(Item, WorkCenter, ProductionLocation.Code, false, UnrelatedProductionOrder);
+
+ // [WHEN] Invoking the "Subcontracting Transfer Orders" action on the first production order card
+ OpenedTransferOrderListNo := '';
+ ReleasedProductionOrder.OpenView();
+ ReleasedProductionOrder.GoToRecord(ProductionOrder);
+ ReleasedProductionOrder."Subc. Transfer Orders".Invoke();
+ ReleasedProductionOrder.Close();
+
+ // [THEN] Only the related transfer order is shown - the handler asserts exactly one record, so the unrelated order is excluded
+ Assert.AreEqual(
+ ExpectedTransferOrderNo, OpenedTransferOrderListNo,
+ 'The production order card action must open only the related subcontracting transfer order.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder,HandleSubcTransferOrdersList')]
+ procedure SubcTransferOrdersActionOnProductionOrdersListOpensRelatedTransferOrder()
+ var
+ Item: Record Item;
+ ProductionOrder: Record "Production Order";
+ UnrelatedProductionOrder: Record "Production Order";
+ ProductionLocation: Record Location;
+ WorkCenter: array[2] of Record "Work Center";
+ ExpectedTransferOrderNo: Code[20];
+ ReleasedProductionOrders: TestPage "Released Production Orders";
+ begin
+ // [SCENARIO 638532] The Released Production Orders list provides a navigation action to view only the related subcontracting transfer orders.
+
+ // [GIVEN] Subcontracting setup with two released production orders, each ending up with its own subcontracting purchase order and transfer order.
+ // The transfer route is location-based (component location -> subcontractor location), so it is created only for the first order and reused by the second.
+ SetupSubcontractingForTransferOrderTests(Item, WorkCenter, ProductionLocation);
+ ExpectedTransferOrderNo := CreateProductionOrderWithSubcTransferOrder(Item, WorkCenter, ProductionLocation.Code, true, ProductionOrder);
+ CreateProductionOrderWithSubcTransferOrder(Item, WorkCenter, ProductionLocation.Code, false, UnrelatedProductionOrder);
+
+ // [WHEN] Invoking the "Subcontracting Transfer Orders" action on the first production order in the list
+ OpenedTransferOrderListNo := '';
+ ReleasedProductionOrders.OpenView();
+ ReleasedProductionOrders.GoToRecord(ProductionOrder);
+ ReleasedProductionOrders."Subc. Transfer Orders".Invoke();
+ ReleasedProductionOrders.Close();
+
+ // [THEN] Only the related transfer order is shown - the handler asserts exactly one record, so the unrelated order is excluded
+ Assert.AreEqual(
+ ExpectedTransferOrderNo, OpenedTransferOrderListNo,
+ 'The production orders list action must open only the related subcontracting transfer order.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure CannotDeleteSubcontractingOrderWithAssociatedTransferOrder()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ PurchaseOrderNo: Code[20];
+ begin
+ // [SCENARIO 630806] Deleting a Subcontracting Order is blocked when an associated Transfer Order exists
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN] Work and Machine Centers, an Item with Routing and Prod. BOM configured for Transfer subcontracting
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] A Released Production Order (not created from a Purchase Order)
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [GIVEN] A Subcontracting Order created from the Production Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseOrderNo := PurchaseHeader."No.";
+
+ // [GIVEN] A Transfer Order created from the Subcontracting Order
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [WHEN] The Subcontracting Order is attempted to be deleted
+ asserterror PurchaseHeader.Delete(true);
+
+ // [THEN] An error is raised and the Transfer Order still exists
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseOrderNo);
+ Assert.RecordIsNotEmpty(TransferHeader);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure TestCreationOfPurchOrderFromRtngLineWithSubcontractor()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO] Create Subcontracting Purchase Order directly from Prod. Order Routing Line
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] Check if Purchase Line exists
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.AreEqual(false, PurchaseLine.IsEmpty(), '');
+ end;
+
+ [Test]
+ procedure CreateSubcOrderFromRtngLineEmptyDefVATProdPostGrp()
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ VATPostingSetup: Record "VAT Posting Setup";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ SubcPurchaseOrderCreator: Codeunit "Subc. Purchase Order Creator";
+ OriginalDefVATProdPostGrp: Code[20];
+ begin
+ // [SCENARIO 618715] Creating a Subcontracting Purchase Order from Prod. Order Routing Line
+ // should succeed even when "Def. VAT Prod. Posting Group" is empty on the Gen. Product Posting Group
+ // (US/Sales Tax localization).
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN] Create subcontracting Work Center (sets Def. VAT Prod. Posting Group during creation)
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Clear "Def. VAT Prod. Posting Group" on the Work Center's Gen. Product Posting Group
+ // to simulate US/Sales Tax localization where this field is intentionally empty.
+ // Done after all other setup to avoid committing the change during item/production order creation.
+ GenProductPostingGroup.Get(WorkCenter[2]."Gen. Prod. Posting Group");
+ OriginalDefVATProdPostGrp := GenProductPostingGroup."Def. VAT Prod. Posting Group";
+ GenProductPostingGroup."Def. VAT Prod. Posting Group" := '';
+ GenProductPostingGroup.Modify();
+
+ // [GIVEN] Create a VAT Posting Setup for the empty VAT Prod. Posting Group
+ // so the downstream purchase line validation can find a matching setup
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ if not VATPostingSetup.Get(Vendor."VAT Bus. Posting Group", '') then begin
+ VATPostingSetup.Init();
+ VATPostingSetup."VAT Bus. Posting Group" := Vendor."VAT Bus. Posting Group";
+ VATPostingSetup."VAT Prod. Posting Group" := '';
+ VATPostingSetup."VAT Calculation Type" := VATPostingSetup."VAT Calculation Type"::"Normal VAT";
+ VATPostingSetup."VAT %" := 0;
+ VATPostingSetup.Insert();
+ end;
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing Line
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ SubcPurchaseOrderCreator.CreateSubcontractingPurchaseOrderFromRoutingLine(ProdOrderRoutingLine);
+
+ // [THEN] Purchase Line is created successfully
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.AreEqual(false, PurchaseLine.IsEmpty(), 'Purchase Line should be created even when Def. VAT Prod. Posting Group is empty.');
+
+ // [TEARDOWN] Restore original Def. VAT Prod. Posting Group to prevent contaminating other tests
+ GenProductPostingGroup.Get(WorkCenter[2]."Gen. Prod. Posting Group");
+ GenProductPostingGroup."Def. VAT Prod. Posting Group" := OriginalDefVATProdPostGrp;
+ GenProductPostingGroup.Modify();
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure TestCreationOfPurchOrderFromRtngLineWithSubcontractorWithAddLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO] Create Subcontracting Purchase Order directly from Prod. Order Routing Line
+ // [SCENARIO] and Transfer additional Line with marked Component;
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Vendor-Supplied");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] Check if Purchase Line with additional Component for Component Supply Method exists
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+#pragma warning disable AA0210
+ ProductionBOMLine.SetRange("Component Supply Method", ProductionBOMLine."Component Supply Method"::"Vendor-Supplied");
+#pragma warning restore AA0210
+ ProductionBOMLine.FindFirst();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ PurchaseLine.SetRange("No.", ProductionBOMLine."No.");
+ Assert.AreEqual(false, PurchaseLine.IsEmpty(), '');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure TestCreationOfSubcontractingPurchOrderFromRtngLineWithAddInfoLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO] Create Subcontracting Purchase Order directly from Prod. Order Routing Line
+ // [SCENARIO] and Transfer additional Information Line;
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ UpdateSubMgmtSetupTransferInfoLine(true);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] Check if Purchase Line with Additional Information Exists
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseLine.SetRange("Document No.", PurchaseLine."Document No.");
+ PurchaseLine.SetRange("Prod. Order No.");
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::" ");
+ PurchaseLine.FindFirst();
+
+ ProdOrderRtngLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRtngLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRtngLine.FindFirst();
+ ProdOrderLine.Get(ProdOrderLine.Status::Released, ProdOrderRtngLine."Prod. Order No.", ProdOrderRtngLine."Routing Reference No.");
+
+ Assert.AreEqual(ProdOrderLine.Description, PurchaseLine.Description, '');
+
+ // [TEARDOWN]
+ UpdateSubMgmtSetupTransferInfoLine(false);
+ end;
+
+ [Test]
+ procedure TestTransferOfComponentSupplyMethodProdBOMLineToProdOrderComp()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionOrder: Record "Production Order";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO] Check Transfer of Component Supply Method from Production BOM Line to Prod Order Component
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Vendor-Supplied");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [WHEN] Creating Production Order to Transfer Information
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [THEN] Check if Production BOM Line with additional Component for Component Supply Method exists
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+ ProductionBOMLine.SetRange("Component Supply Method", ProductionBOMLine."Component Supply Method"::"Vendor-Supplied");
+ Assert.RecordIsNotEmpty(ProductionBOMLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure TestCreationOfSubcontrTransferOrderFromSubcontrPurchOrder()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] and Transfer additional Line with marked Component ;
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Check if Purchase Line with additional Component for Component Supply Method exists
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+
+ Assert.RecordIsNotEmpty(TransferLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure TestLocationInSubContractorTransferOrderAndComponentLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ CompLocation, TransferFrom : Code[10];
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] check if Component Line Location Code and Transfer Form Code are equal
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", "Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ //[GIVEN] Keep Location Code for later Check
+ CompLocation := ProdOrderComp."Location Code";
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComp."Line No.");
+ TransferLine.FindFirst();
+
+ TransferHeader.Get(TransferLine."Document No.");
+ //[GIVEN] Keep Location Code for later Check
+ TransferFrom := TransferHeader."Transfer-from Code";
+
+ // [THEN] Check if Component Location Code and Transfer Form Code are equal
+ Assert.AreEqual(CompLocation, TransferFrom, 'Transfer-from Code is not expected');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure TestLocationInSubContractorTransferOrderAndComponentLineWithChangeCompLineLocation()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ CompLocation, TransferFrom : Code[10];
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] Change Component Location Code and check if Component Line Location Code and Transfer Form Code are equal
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", "Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ ProdOrderComp."Location Code" := Location.Code;
+ ProdOrderComp.Modify();
+
+ //[GIVEN] Keep Location Code for later Check
+ CompLocation := ProdOrderComp."Location Code";
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComp."Line No.");
+ TransferLine.FindFirst();
+
+ TransferHeader.Get(TransferLine."Document No.");
+
+ //[GIVEN] Keep Location Code for later Check
+ TransferFrom := TransferHeader."Transfer-from Code";
+
+ // [THEN] Check if Component Location Code and Transfer Form Code are equal
+ Assert.AreEqual(CompLocation, TransferFrom, 'Transfer-from Code is not expected');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder,HandleCreateTransferOrderMsg')]
+ procedure CheckTransferOrderFromSubcontrAndReturnTransferOrderFromSubcontractorPurchOrder()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ TransferFrom1, TransferFrom2, TransferTo1, TransferTo2 : Code[10];
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ TransferOrder: TestPage "Transfer Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] Post transfer Order and Create Return Transfer Order
+ // [SCENARIO] check if Transfer-from and Transfer-to Locations are reversed
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("No.", ProductionOrder."Source No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Check if Purchase Line with additional Component for Component Supply Method exists
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComp."Line No.");
+ TransferLine.FindFirst();
+
+ TransferHeader.SetRange("No.", TransferLine."Document No.");
+ TransferHeader.FindFirst();
+
+ //[GIVEN]create Inventory for Transfer
+ Item.Get(ProdOrderComp."Item No.");
+ Location.Get(TransferHeader."Transfer-from Code");
+ CreateInventory(Item, Location, Bin, ProdOrderComp."Expected Qty. (Base)");
+
+ //[GIVEN] Keep Transfer Locations values for later Check
+ TransferFrom1 := TransferHeader."Transfer-from Code";
+ TransferTo1 := TransferHeader."Transfer-to Code";
+
+ //[GIVEN] Enable direct transfer for posting
+ TransferHeader.Validate("Direct Transfer", true);
+ TransferHeader.Modify(true);
+
+ //[WHEN] Post Transfer Order
+ TransferOrder.OpenView();
+ TransferOrder.GoToRecord(TransferHeader);
+ TransferOrder.Post.Invoke();
+
+ //[WHEN] Create Return Transfer Order
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComp."Line No.");
+ TransferLine.FindFirst();
+
+ TransferHeader.SetRange("No.", TransferLine."Document No.");
+ TransferHeader.FindFirst();
+
+ //[GIVEN] Keep Transfer Locations values for later Check
+ TransferFrom2 := TransferHeader."Transfer-from Code";
+ TransferTo2 := TransferHeader."Transfer-to Code";
+
+ //[THEN] Check if Transfer-from and Transfer-to Locations are reversed
+ Assert.AreEqual(TransferFrom1, TransferTo2, 'Transfer-from and Transfer-to Locations are reversed');
+ Assert.AreEqual(TransferTo1, TransferFrom2, 'Transfer-from and Transfer-to Locations are reversed');
+ end;
+
+ [Test]
+ procedure TestChangeLocationOnProdOrderCompWithComponentSupplyMethodPurchase()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ WorkCenter: array[2] of Record "Work Center";
+ ActualLocationCode: Code[10];
+ begin
+ // [SCENARIO] Check change Location Code by change Component Supply Method in Prod Order Component
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [WHEN] Get actual Location Code and Change Component Supply Method
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderComp.SetFilter("Routing Link Code", '<>%1', '');
+ ProdOrderComp.FindFirst();
+ ActualLocationCode := ProdOrderComp."Location Code";
+ ProdOrderComp.Validate("Component Supply Method", ProdOrderComp."Component Supply Method"::"Vendor-Supplied");
+ ProdOrderComp.Modify();
+
+ // [THEN] Check if Component Location differs from Origin Location Code ==> Vendor Subcontracting Location Code
+ Assert.AreNotEqual(ActualLocationCode, ProdOrderComp."Location Code", '');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure CheckGenPostGroupInSubContWorksheetAndSubConRoutingLine()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ ManufacturingSetup: Record "Manufacturing Setup";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ SubcCalculateSubContract: Report "Subc. Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ GenBusPostingGroup1, GenBusPostingGroup2 : Code[20];
+ ProdPostingGroup1, ProdPostingGroup2 : Code[20];
+ VATBusPostingGroup1, VATBusPostingGroup2 : Code[20];
+ VATProdPostingGroup1, VATProdPostingGroup2 : Code[20];
+ ProdOrderRtng: TestPage "Prod. Order Routing";
+ begin
+ // [SCENARIO] Check Gen. Prod. Posting Group value for Subcontracting Purchase Order
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ WorkCenter2 := WorkCenter[2];
+ WorkCenter2."Subcontractor No." := Vendor."No.";
+ Vendor."Subc. Location Code" := Location.Code;
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+
+ //[GIVEN] Create Production Order
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ //[GIVEN] Create requisition worksheet template
+ SubcontractingMgmtLibrary.CreateReqWkshTemplateAndName(ReqWkshTemplate, RequisitionWkshName);
+
+ //[GIVEN] create Purchase Order from Subcontracting Worksheet
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ SubcCalculateSubContract.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContract.UseRequestPage(false);
+ SubcCalculateSubContract.RunModal();
+
+ RequisitionLine.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+
+ Assert.AreEqual(ProductionOrder."No.", RequisitionLine."Prod. Order No.", 'Prod. Order No. has not found');
+
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("No.", ProductionOrder."Source No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ //[GIVEN] Keep Posting Groups values for later Check
+ ProdPostingGroup1 := PurchaseLine."Gen. Prod. Posting Group";
+ GenBusPostingGroup1 := PurchaseLine."Gen. Bus. Posting Group";
+ VATBusPostingGroup1 := PurchaseLine."VAT Bus. Posting Group";
+ VATProdPostingGroup1 := PurchaseLine."VAT Prod. Posting Group";
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ //[GIVEN] Delete Purchase Order
+ PurchaseHeader.Delete(true);
+ Commit();
+
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Subcontracting Template Name" := RequisitionLine."Worksheet Template Name";
+ ManufacturingSetup."Subcontracting Batch Name" := RequisitionLine."Journal Batch Name";
+ ManufacturingSetup.Modify();
+
+ // [GIVEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ WorkCenter2 := WorkCenter[2];
+ WorkCenter2."Subcontractor No." := Vendor."No.";
+ WorkCenter2.Modify();
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ ProdOrderRtng.OpenView();
+ ProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ProdOrderRtng.CreateSubcontracting.Invoke();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("No.", ProductionOrder."Source No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ //[GIVEN] Keep Posting Groups values for later Check
+ ProdPostingGroup2 := PurchaseLine."Gen. Prod. Posting Group";
+ GenBusPostingGroup2 := PurchaseLine."Gen. Bus. Posting Group";
+ VATBusPostingGroup2 := PurchaseLine."VAT Bus. Posting Group";
+ VATProdPostingGroup2 := PurchaseLine."VAT Prod. Posting Group";
+
+ //[THEN] Check if Posting Groups values is the same as Standard
+ Assert.AreEqual(ProdPostingGroup1, ProdPostingGroup2, 'Gen. Prod. Posting Group is not Expected');
+ Assert.AreEqual(GenBusPostingGroup1, GenBusPostingGroup2, 'Gen. Bus. Posting Group is not Expected');
+ Assert.AreEqual(VATBusPostingGroup1, VATBusPostingGroup2, 'VAT Bus. Posting Group is not Expected');
+ Assert.AreEqual(VATProdPostingGroup1, VATProdPostingGroup2, 'VAT Prod. Posting Group');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure TestTransferProdOrderRtngCommentByCreationOfSubcontrPurchOrder()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRtngLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchCommentLine: Record "Purch. Comment Line";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ begin
+ // [SCENARIO] Create Subcontracting Purchase Order
+ // [SCENARIO] and test Transfer of Prod Order Rtng. Comment to PurchLine HTML Text;
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ // Create Comment Line
+ ProdOrderRtngLine.SetRange(Status, ProdOrderRtngLine.Status::Released);
+ ProdOrderRtngLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRtngLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRtngLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRtngLine.FindFirst();
+ LibraryMfgManagement.CreateProdOrderRtngCommentLine(ProdOrderRtngLine.Status, ProdOrderRtngLine."Prod. Order No.", ProdOrderRtngLine."Routing Reference No.", ProdOrderRtngLine."Routing No.", ProdOrderRtngLine."Operation No.");
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRtngLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+
+ // [THEN] Get transferred Rtng Comment Text
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Operation No.", ProdOrderRtngLine."Operation No.");
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderRtngLine."Prod. Order No.");
+ PurchaseLine.SetRange("Prod. Order Line No.", ProdOrderRtngLine."Routing Reference No.");
+ PurchaseLine.FindLast();
+
+ PurchCommentLine.SetRange("Document Type", PurchaseLine."Document Type");
+ PurchCommentLine.SetRange("No.", PurchaseLine."Document No.");
+ PurchCommentLine.SetRange("Document Line No.", PurchaseLine."Line No.");
+
+ Assert.IsFalse(PurchaseLine.IsEmpty(), 'Purchase Comment Line must be filled');
+
+ // [TEARDOWN]
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure TestExpectedErrorOnChangingLocationCodeInProdOrderCompWithTransferOrderFromSubcontrPurchOrder()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ProdOrderCompPage: TestPage "Prod. Order Components";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] and Transfer additional Line with marked Component ;
+ // [SCENARIO] Expected Error on changing Location Code in Prod. Order Component
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ LibraryWarehouse.CreateLocation(Location);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Check if Purchase Line with additional Component for Component Supply Method exists
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+
+ Assert.RecordIsNotEmpty(TransferLine);
+
+ ProdOrderCompPage.OpenEdit();
+ ProdOrderCompPage.GoToRecord(ProdOrderComp);
+ asserterror ProdOrderCompPage."Location Code".SetValue(Location.Code);
+ Assert.ExpectedError('The component has already been assigned to the subcontracting transfer order');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure TestReceiptDateFromTransferOrderLineFromSubcontrPurchOrderIsEquallyToProdOrderCompDueDate()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ManufacturingSetup: Record "Manufacturing Setup";
+ ExpectedDate: Date;
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] and Transfer additional Line with marked Component ;
+ // [SCENARIO] Expected Error on changing Location Code in Prod. Order Component
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ UpdateSubWhseHandlingTimeInSubManagementSetup();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ LibraryWarehouse.CreateLocation(Location);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ UpdateProdOrderCompDueDate(ProductionOrder."No.");
+
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Compare Due Date from Prod Order Comp with Receipt Date from Subc. Transfer Line
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.FindFirst();
+
+ ManufacturingSetup.Get();
+
+ ExpectedDate := CalcDate(ManufacturingSetup."Subc. Comp. Transfer Lead Time", TransferLine."Receipt Date");
+
+ Assert.AreEqual(ExpectedDate, ProdOrderComp."Due Date", '');
+
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure TestLocationAndBinCodeIsSetFromOriginBinCodeAfterDeletingTransferOrder()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ LocationCode: Code[10];
+ BinCode: Code[20];
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] and Transfer additional Line with marked Component ;
+ // [SCENARIO] Expected Bin Code is filled with Original Bin Code
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ LibraryWarehouse.CreateLocation(Location);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ UpdateProdOrderCompWithLocationAndBinCode(ProductionOrder."No.", LocationCode, BinCode);
+
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Check if Purchase Line with additional Component for Component Supply Method exists
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+
+ TransferLine.FindFirst();
+
+ TransferHeader.Get(TransferLine."Document No.");
+ TransferHeader.Delete(true);
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ Assert.AreEqual(ProdOrderComp."Location Code", LocationCode, '');
+ Assert.AreEqual(ProdOrderComp."Bin Code", BinCode, '');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure CheckBtnTrackingSpecificationOnProdOrderCompOnExistingReserveInTransferLine()
+ var
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ProdOrderCompPage: TestPage "Prod. Order Components";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ ExpectedErrorMsg: Text;
+ begin
+ // [SCENARIO] Create Subcontracting Transfer Order directly from Subcontracting Purchase Order
+ // [SCENARIO] and Transfer additional Line with marked Component ;
+ // [SCENARIO] Expected Error on open Item Tracking Lines in Prod. Order Component
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ LibraryWarehouse.CreateLocation(Location);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Check if Purchase Line with additional Component for Component Supply Method exists, Mock Reservation Entries on TransferLine and try to open Item Tracking Lines from Prod order Comp. Page
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+
+ Assert.RecordIsNotEmpty(TransferLine);
+ TransferLine.FindFirst();
+
+ MockReservationEntryOnTransferLine(TransferLine, ProdOrderComp);
+
+ ProdOrderCompPage.OpenEdit();
+ ProdOrderCompPage.GoToRecord(ProdOrderComp);
+ asserterror ProdOrderCompPage.ItemTrackingLines.Invoke();
+ ExpectedErrorMsg := StrSubstNo(AlreadySpecifiedErr, TransferLine."Document No.");
+ Assert.ExpectedError(ExpectedErrorMsg);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure TestCheckSubcontractorPriceInFactbox()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: array[2] of Record "Work Center";
+ SubPurchaseLineFactbox: TestPage "Subc. Purchase Line Factbox";
+ begin
+ // [SCENARIO] Create Subcontracting Purchase Order directly from Prod. Order Routing Line
+ // Check if No of SubcontractorPrices is displayed
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ SubcontractingMgmtLibrary.CreateSubcontractorPrice(Item, WorkCenter[2]."No.", SubcontractorPrice);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] Check if Purchase Line exists
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ SubPurchaseLineFactbox.OpenView();
+ SubPurchaseLineFactbox.GoToRecord(PurchaseLine);
+ Assert.AreEqual(SubPurchaseLineFactbox.SubcontractingPrices.Value, Format(SubcontractorPrice.Count()), '');
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure DeleteWorkCenterWithPricesDeletesRelatedPrices()
+ var
+ Item: Record Item;
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ WorkCenterNo: Code[20];
+ begin
+ // [SCENARIO 620643] Deleting a Work Center deletes all associated Subcontractor Prices
+
+ // [GIVEN] A work center with a subcontractor and multiple Subcontractor Prices
+ Initialize();
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+ WorkCenter.Modify(true);
+ LibraryInventory.CreateItem(Item);
+ WorkCenterNo := WorkCenter."No.";
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(SubcontractorPrice, WorkCenterNo, WorkCenter."Subcontractor No.", Item."No.", '', '', WorkDate(), '', 0, '');
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(SubcontractorPrice, WorkCenterNo, WorkCenter."Subcontractor No.", Item."No.", '', '', WorkDate(), '', 10, '');
+
+ // [WHEN] The work center is deleted
+ WorkCenter.Delete(true);
+
+ // [THEN] All Subcontractor Prices for the work center are deleted
+ SubcontractorPrice.SetRange("Work Center No.", WorkCenterNo);
+ Assert.IsTrue(SubcontractorPrice.IsEmpty(), 'Subcontractor prices must be deleted when work center is deleted');
+ end;
+
+ [Test]
+ [Scope('OnPrem')]
+ procedure DeleteItemWithPricesDeletesRelatedPrices()
+ var
+ Item: Record Item;
+ SubcontractorPrice: Record "Subcontractor Price";
+ WorkCenter: Record "Work Center";
+ ItemNo: Code[20];
+ begin
+ // [SCENARIO 620643] Deleting an Item deletes all associated Subcontractor Prices
+
+ // [GIVEN] An item with multiple Subcontractor Prices
+ Initialize();
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+ WorkCenter.Modify(true);
+ LibraryInventory.CreateItem(Item);
+ ItemNo := Item."No.";
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(SubcontractorPrice, WorkCenter."No.", WorkCenter."Subcontractor No.", ItemNo, '', '', WorkDate(), '', 0, '');
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(SubcontractorPrice, WorkCenter."No.", WorkCenter."Subcontractor No.", ItemNo, '', '', WorkDate(), '', 10, '');
+
+ // [WHEN] The item is deleted
+ Item.Delete(true);
+
+ // [THEN] All Subcontractor Prices for the item are deleted
+ SubcontractorPrice.SetRange("Item No.", ItemNo);
+ Assert.IsTrue(SubcontractorPrice.IsEmpty(), 'Subcontractor prices must be deleted when item is deleted');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,SubcontrDispatchingListDefaultRequestPageHandler')]
+ procedure TestSubcontrDispatchingList()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ LibraryReportDataset: Codeunit "Library - Report Dataset";
+ XmlParameters: Text;
+ begin
+ // [SCENARIO] Create Subcontracting and check Subcontr Dispatching List
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseHeader.SetRange("No.", PurchaseLine."Document No.");
+ PurchaseHeader.FindFirst();
+
+ // [THEN] Print Subcontr Dispatching List
+ PurchaseHeader.SetRecFilter();
+ XmlParameters := Report.RunRequestPage(Report::"Subc. Dispatching List");
+ LibraryReportDataset.RunReportAndLoad(Report::"Subc. Dispatching List", PurchaseHeader, XmlParameters);
+ // [THEN] the company address line is blank
+ LibraryReportDataset.AssertElementWithValueExists('SubcAddrInfoLine', '');
+ // [THEN] an exemplary footer element is blank
+ LibraryReportDataset.AssertElementWithValueExists('SubcCompanyAddress1', '');
+ LibraryReportDataset.AssertElementWithValueExists('Prod__Order_Routing_Line__Prod__Order_No__', ProductionOrder."No.");
+ end;
+
+ [Test]
+ procedure TestTransferComponentSupplyMethodAndVendorLocationIntoPlanningComponent()
+ var
+ Customer: Record Customer;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ PlanningComponent: Record "Planning Component";
+ ProductionBOMLine: Record "Production BOM Line";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ SalesHeader: Record "Sales Header";
+ SalesLine: Record "Sales Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ ReqWkshTemplateName: Code[10];
+ Direction: Option Forward,Backward;
+ begin
+ // [SCENARIO] Create Sales Order and test Planning Component
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN]
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ Item."Reordering Policy" := "Reordering Policy"::Order;
+ Item.Modify();
+
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Consignment at Vendor");
+
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ LibrarySales.CreateCustomer(Customer);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ LibrarySales.CreateSalesDocumentWithItem(SalesHeader, SalesLine, "Sales Document Type"::Order, Customer."No.", Item."No.", 5, Location.Code, WorkDate());
+
+ // [WHEN]
+ LibraryPlanning.CalcRegenPlanForPlanWksh(Item, CalcDate('<-1M>', WorkDate()), CalcDate('<+1M>', WorkDate()));
+
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+ ProductionBOMLine.FindLast();
+ PlanningComponent.SetRange("Item No.", ProductionBOMLine."No.");
+ PlanningComponent.FindFirst();
+
+ // [THEN]
+ PlanningComponent.TestField("Component Supply Method", "Component Supply Method"::"Consignment at Vendor");
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ PlanningComponent.TestField("Location Code", Vendor."Subc. Location Code");
+
+ // [WHEN] A Planning Worksheet line is added manually for the same item and Refresh Planning Line is run (bug 637499 repro)
+ ReqWkshTemplateName := LibraryPlanning.SelectRequisitionTemplateName();
+ LibraryPlanning.CreateRequisitionWkshName(RequisitionWkshName, ReqWkshTemplateName);
+ LibraryPlanning.CreateRequisitionLine(RequisitionLine, ReqWkshTemplateName, RequisitionWkshName.Name);
+ RequisitionLine.Validate(Type, RequisitionLine.Type::Item);
+ RequisitionLine.Validate("No.", Item."No.");
+ RequisitionLine.Validate(Quantity, LibraryRandom.RandInt(10) + 5);
+ RequisitionLine.Validate("Location Code", Location.Code);
+ RequisitionLine.Validate("Ending Date", WorkDate());
+ RequisitionLine.Modify(true);
+ LibraryPlanning.RefreshPlanningLine(RequisitionLine, Direction::Backward, true, true);
+
+ // [THEN] The Subcontracting Type (Component Supply Method) is copied from the Production BOM Line to the Planning Component
+ Clear(PlanningComponent);
+ PlanningComponent.SetRange("Worksheet Template Name", RequisitionLine."Worksheet Template Name");
+ PlanningComponent.SetRange("Worksheet Batch Name", RequisitionLine."Journal Batch Name");
+ PlanningComponent.SetRange("Worksheet Line No.", RequisitionLine."Line No.");
+ PlanningComponent.SetRange("Item No.", ProductionBOMLine."No.");
+ PlanningComponent.FindFirst();
+ PlanningComponent.TestField("Component Supply Method", "Component Supply Method"::"Consignment at Vendor");
+ // [THEN] and the component is relocated to the subcontractor location, matching the Production Order behavior
+ PlanningComponent.TestField("Location Code", Vendor."Subc. Location Code");
+ end;
+
+
+ [Test]
+ procedure PurchaseSubcTypeProdOrderCompExcludedFromPlanning()
+ var
+ ComponentItem: Record Item;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionOrder: Record "Production Order";
+ RequisitionLine: Record "Requisition Line";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO 630597] Prod. Order Components with Component Supply Method "Purchase" should be
+ // excluded from planning engines because they will be purchased later via the subcontracting
+ // purchase order.
+
+ // [GIVEN] Complete Setup of Manufacturing, include Work- and Machine Centers, Item
+ Initialize();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Some Parameters for Creation
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN] Create subcontracting Work/Machine Centers
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+
+ // [GIVEN] Create Item for Production include Routing and Prod. BOM (2 component items)
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Assign Routing Link Code between subcontracting routing line and last BOM line
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] Set Component Supply Method = Vendor-Supplied on the linked BOM line
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Vendor-Supplied");
+
+ // [GIVEN] Set up vendor with subcontracting location
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Set component item reordering policy to Lot-for-Lot (already done during creation)
+ // [GIVEN] Create inventory for the component item so planning considers it
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+ ProductionBOMLine.FindLast();
+ ComponentItem.Get(ProductionBOMLine."No.");
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+
+ // [GIVEN] Create and refresh Released Production Order
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [GIVEN] Verify prod. order component with Purchase Component Supply Method exists
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderComp.SetRange("Item No.", ComponentItem."No.");
+ ProdOrderComp.SetRange("Component Supply Method", "Component Supply Method"::"Vendor-Supplied");
+ Assert.RecordIsNotEmpty(ProdOrderComp);
+
+ // [WHEN] Run Regenerative Plan for the component item
+ ComponentItem.SetRecFilter();
+ LibraryPlanning.CalcRegenPlanForPlanWksh(ComponentItem, CalcDate('<-1M>', WorkDate()), CalcDate('<+1M>', WorkDate()));
+
+ // [THEN] No requisition line is suggested for the component with Vendor-Supplied component supply method
+ RequisitionLine.SetRange("No.", ComponentItem."No.");
+ Assert.RecordIsEmpty(RequisitionLine);
+
+ // [WHEN] Changing the Component Supply Method to None and run planning again
+ UpdateProdOrderComponentWithComponentSupplyMethod(ProductionOrder, "Component Supply Method"::Empty);
+ LibraryPlanning.CalcRegenPlanForPlanWksh(ComponentItem, CalcDate('<-1M>', WorkDate()), CalcDate('<+1M>', WorkDate()));
+
+ // [THEN] Requisition line is suggested for the component with None component supply method
+ RequisitionLine.SetRange("No.", ComponentItem."No.");
+ Assert.RecordIsNotEmpty(RequisitionLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure SubcontractingFieldsPopulatedOnIleAfterSubcontractingPurchaseReceipt()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ SubcWorkCenter: Record "Work Center";
+ begin
+ // [SCENARIO] Bug 633292 - Output Item Ledger Entry created from posting a subcontracting purchase receipt should have the Subcontracting extension fields populated, so that the Production actions on the Item Ledger Entries page can resolve the linked production order, routing, and components.
+
+ // [GIVEN] Subcontracting setup
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN] Released Production Order whose only routing operation is a subcontracting one (so receiving the subcontracting PO posts the Output ILE)
+ CreateItemWithSingleSubcontractingOperation(Item, SubcWorkCenter);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(SubcWorkCenter);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Subcontracting Purchase Order created from the Prod. Order Routing line
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", SubcWorkCenter."No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", SubcWorkCenter."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ EnsureGeneralPostingSetupIsValid(PurchaseLine."Gen. Bus. Posting Group", PurchaseLine."Gen. Prod. Posting Group");
+
+ // [WHEN] Receive the subcontracting purchase order
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [THEN] An Output Item Ledger Entry exists with Subcontracting extension fields populated from the source purchase line
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ ItemLedgerEntry.SetRange("Order Type", ItemLedgerEntry."Order Type"::Production);
+ ItemLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ ItemLedgerEntry.FindFirst();
+
+ Assert.AreEqual(
+ PurchaseHeader."No.", ItemLedgerEntry."Subc. Purch. Order No.",
+ 'Item Ledger Entry "Subcontr. Purch. Order No." should equal the originating subcontracting purchase order.');
+ Assert.AreEqual(
+ PurchaseLine."Line No.", ItemLedgerEntry."Subc. Purch. Order Line No.",
+ 'Item Ledger Entry "Subcontr. PO Line No." should equal the originating subcontracting purchase line.');
+ Assert.AreEqual(
+ PurchaseLine."Operation No.", ItemLedgerEntry."Subc. Operation No.",
+ 'Item Ledger Entry "Operation No." (Subc) should equal the originating purchase line operation.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure ProdOFactboxMgmtResolvesProductionOrderForIleFromSubcontractingPurchaseReceipt()
+ var
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ SubcWorkCenter: Record "Work Center";
+ SubcProdOFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ begin
+ // [SCENARIO] Bug 633292 - Subc. ProdO. Factbox Mgmt. helpers should resolve a positive number of production order routings and components when given an Item Ledger Entry that originated from a subcontracting purchase receipt. Before the fix, the codeunit had no Item Ledger Entry branch in SetProdOrderInformationByVariant and returned 0 for any ILE variant.
+
+ // [GIVEN] Subcontracting setup
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN] Released Production Order whose only routing operation is a subcontracting one (so receiving the subcontracting PO posts the Output ILE)
+ CreateItemWithSingleSubcontractingOperation(Item, SubcWorkCenter);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(SubcWorkCenter);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Subcontracting Purchase Order created from the Prod. Order Routing line and posted as received
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", SubcWorkCenter."No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", SubcWorkCenter."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ EnsureGeneralPostingSetupIsValid(PurchaseLine."Gen. Bus. Posting Group", PurchaseLine."Gen. Prod. Posting Group");
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntry.SetRange("Entry Type", ItemLedgerEntry."Entry Type"::Output);
+ ItemLedgerEntry.SetRange("Order Type", ItemLedgerEntry."Order Type"::Production);
+ ItemLedgerEntry.SetRange("Order No.", ProductionOrder."No.");
+ ItemLedgerEntry.FindFirst();
+
+ // [WHEN] CalcNoOfProductionOrderRoutings / CalcNoOfProductionOrderComponents are called with the ILE variant
+ // [THEN] Both return a positive count, confirming the production order linkage is resolved
+ Assert.IsTrue(
+ SubcProdOFactboxMgmt.CalcNoOfProductionOrderRoutings(ItemLedgerEntry) > 0,
+ 'CalcNoOfProductionOrderRoutings should return a positive count for an Item Ledger Entry from a subcontracting receipt.');
+ Assert.IsTrue(
+ SubcProdOFactboxMgmt.CalcNoOfProductionOrderComponents(ItemLedgerEntry) > 0,
+ 'CalcNoOfProductionOrderComponents should return a positive count for an Item Ledger Entry from a subcontracting receipt.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmArchiveOrderHandler,HandlePurchaseOrderPage')]
+ procedure ProdOFactboxMgmtShowsDataAfterProdOrderFinished()
+ var
+ Item: Record Item;
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ SubcWorkCenter: Record "Work Center";
+ SubcProdOFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ begin
+ // [SCENARIO 634953] Subcontracting factbox drilldowns should work after production order is finished.
+ Initialize();
+
+ // [GIVEN] A released production order with a subcontracting routing operation and a subcontracting purchase order
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateItemWithSingleSubcontractingOperation(Item, SubcWorkCenter);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(SubcWorkCenter);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", SubcWorkCenter."No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", SubcWorkCenter."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ EnsureGeneralPostingSetupIsValid(PurchaseLine."Gen. Bus. Posting Group", PurchaseLine."Gen. Prod. Posting Group");
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [GIVEN] The production order is changed to Finished status
+ LibraryManufacturing.ChangeProdOrderStatus(ProductionOrder, "Production Order Status"::Finished, WorkDate(), true);
+
+ // Re-read purchase line (the order still exists because only receipt was posted)
+ PurchaseLine.FindFirst();
+
+ // [WHEN] CalcNoOfProductionOrderRoutings / CalcNoOfProductionOrderComponents are called with the Purchase Line
+ // [THEN] Both return a positive count even though the production order is now Finished
+ Assert.IsTrue(
+ SubcProdOFactboxMgmt.CalcNoOfProductionOrderRoutings(PurchaseLine) > 0,
+ 'CalcNoOfProductionOrderRoutings should return a positive count after the production order is finished.');
+ Assert.IsTrue(
+ SubcProdOFactboxMgmt.CalcNoOfProductionOrderComponents(PurchaseLine) > 0,
+ 'CalcNoOfProductionOrderComponents should return a positive count after the production order is finished.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure RoutingFactboxMgmtFiltersPurchOrderQtyByRoutingReferenceNo()
+ var
+ Item: Record Item;
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ SubcWorkCenter: Record "Work Center";
+ SubcRoutingFactboxMgmt: Codeunit "Subc. Routing Factbox Mgmt.";
+ ExpectedPurchOrderQty: Decimal;
+ begin
+ // [SCENARIO] Regression test for Subc. Routing Factbox Mgmt.
+ // [SCENARIO] GetPurchOrderQtyFromRoutingLine must filter by "Routing Reference No." and not by "Prod. Order Line No.".
+
+ // [GIVEN] A released production order with a subcontracting routing operation and a created subcontracting purchase order
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateItemWithSingleSubcontractingOperation(Item, SubcWorkCenter);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(SubcWorkCenter);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", SubcWorkCenter."No.");
+
+ ProdOrderRoutingLine.SetRange(Status, ProdOrderRoutingLine.Status::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", SubcWorkCenter."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Prod. Order Line No.", ProdOrderRoutingLine."Routing Reference No.");
+ PurchaseLine.SetRange("Operation No.", ProdOrderRoutingLine."Operation No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseLine.Validate(Quantity, LibraryRandom.RandIntInRange(7, 17));
+ // Force a mismatch to prove the codeunit does not rely on Prod. Order Line No.
+ PurchaseLine."Prod. Order Line No." := ProdOrderRoutingLine."Routing Reference No." + 1;
+ PurchaseLine.Modify(true);
+
+ Assert.AreNotEqual(
+ ProdOrderRoutingLine."Routing Reference No.", PurchaseLine."Prod. Order Line No.",
+ 'Test setup failed: Prod. Order Line No. must differ from Routing Reference No.');
+
+ // [WHEN] The factbox helper calculates purchase order quantity from the routing line
+ // [THEN] Quantity is returned for the line matched by Routing Reference No.
+ ExpectedPurchOrderQty := PurchaseLine.Quantity;
+ Assert.AreEqual(
+ ExpectedPurchOrderQty,
+ SubcRoutingFactboxMgmt.GetPurchOrderQtyFromRoutingLine(ProdOrderRoutingLine),
+ 'GetPurchOrderQtyFromRoutingLine must filter by Routing Reference No., not by Prod. Order Line No.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrderReopen')]
+ procedure FactboxDrilldownTransferOrderReopenPersists()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ WorkCenter: array[2] of Record "Work Center";
+ ProductionLocation: Record Location;
+ SubcPurchFactboxMgmt: Codeunit "Subc. Purch. Factbox Mgmt.";
+ ReleaseTransferDocument: Codeunit "Release Transfer Document";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ begin
+ // [SCENARIO] Bug 634267 - Reopen Transfer Order does not persist when opened from Subcontracting Details Factbox.
+ // ShowTransferOrdersAndReturnOrder must open the page on a real database record, so actions like
+ // Reopen that modify Rec directly persist after the page closes.
+
+ // [GIVEN] Subcontracting setup with transfer components and a released transfer order
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SetAllProdOrderTransferComponentLocations(ProductionOrder."No.", ProductionLocation.Code);
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+ ReleasedProdOrderRtng.Close();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+ PurchaseHeaderPage.Close();
+
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ TransferHeader.FindFirst();
+ ReleaseTransferDocument.Release(TransferHeader);
+ Assert.AreEqual(TransferHeader.Status::Released, TransferHeader.Status, 'Transfer order should be Released before the test.');
+
+ // [WHEN] Opening the transfer order from the factbox drill-down and performing Reopen
+ // The page handler HandleTransferOrderReopen will reopen the transfer order
+ PurchaseLine.FindFirst();
+ SubcPurchFactboxMgmt.ShowTransferOrdersAndReturnOrder(PurchaseLine, true, false);
+
+ // [THEN] The transfer order status must be Open after closing the page
+ TransferHeader.Get(TransferHeader."No.");
+ Assert.AreEqual(TransferHeader.Status::Open, TransferHeader.Status,
+ 'Transfer order status should be Open after Reopen from factbox drill-down. Before the fix, the Reopen modified a marked record and the change was lost.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure Description2CopiedFromProdOrderComponentToPurchaseLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ExpectedDescription2: Text[50];
+ begin
+ // [SCENARIO] Description 2 from Prod. Order Component is propagated to Purchase Line
+ // [FEATURE] Bug 620556 - Subcontracting Description 2 alignment
+
+ // [GIVEN] Complete Setup of Manufacturing
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Vendor-Supplied");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] A Description 2 value is set on the Prod. Order Component with Component Supply Method = Purchase
+ ProdOrderComp.SetRange(Status, ProdOrderComp.Status::Released);
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Vendor-Supplied");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ ExpectedDescription2 := 'TestDescription2_Comp';
+ ProdOrderComp."Description 2" := ExpectedDescription2;
+ ProdOrderComp.Modify();
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] Description 2 from Prod. Order Component is propagated to the component Purchase Line
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ PurchaseLine.SetRange("No.", ProdOrderComp."Item No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ Assert.AreEqual(
+ ExpectedDescription2, PurchaseLine."Description 2",
+ 'Description 2 must be propagated from Prod. Order Component to Purchase Line');
+ end;
+
+ [Test]
+ procedure Description2PopulatedOnRequisitionLineFromCalculateSubcontracts()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcCalculateSubContract: Report "Subc. Calculate Subcontracts";
+ LibraryUtility: Codeunit "Library - Utility";
+ ExpectedDescription2: Text[50];
+ begin
+ // [SCENARIO] Description 2 from Prod. Order Routing Line is populated on Requisition Line
+ // via Calculate Subcontracts report
+ // [FEATURE] Bug 620556 - Subcontracting Description 2 alignment
+
+ // [GIVEN] Complete Setup of Manufacturing
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [GIVEN] Description 2 is set on the subcontracting Work Center Name 2
+ // (SubcCalcSubcontractsExt copies WorkCenter."Name 2" → RequisitionLine."Description 2")
+ ExpectedDescription2 := 'TestDesc2_WC';
+ WorkCenter[2].Get(WorkCenter[2]."No.");
+ WorkCenter[2].Validate("Name 2", ExpectedDescription2);
+ WorkCenter[2].Modify(true);
+
+ // [GIVEN] Create requisition worksheet
+ ReqWkshTemplate.DeleteAll(true);
+ ReqWkshTemplate.Name := SelectRequisitionTemplateName();
+ RequisitionWkshName.Init();
+ RequisitionWkshName.Validate("Worksheet Template Name", ReqWkshTemplate.Name);
+ RequisitionWkshName.Validate(
+ Name,
+ CopyStr(
+ LibraryUtility.GenerateRandomCode(RequisitionWkshName.FieldNo(Name), Database::"Requisition Wksh. Name"),
+ 1, LibraryUtility.GetFieldLength(Database::"Requisition Wksh. Name", RequisitionWkshName.FieldNo(Name))));
+ RequisitionWkshName.Insert(true);
+
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ // [WHEN] Calculate Subcontracts
+ SubcCalculateSubContract.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContract.UseRequestPage(false);
+ SubcCalculateSubContract.RunModal();
+
+ // [THEN] Description 2 on the Requisition Line is populated
+ RequisitionLine.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+ Assert.AreEqual(
+ ExpectedDescription2, RequisitionLine."Description 2",
+ 'Description 2 must be populated on the Requisition Line from the subcontracting Work Center');
+ end;
+
+ [Test]
+ [HandlerFunctions('RoutingLinkCodeDuplicateConfirmHandler')]
+ procedure ValidateRoutingLinkCodeOnProdOrderRtngLineShowsConfirmOnce()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO 617395] Validating Routing Link Code on a Prod. Order Routing Line shows the
+ // duplicate-use confirmation dialog exactly once. The BaseApp OnValidate already performs
+ // this check; the Subcontracting extension must not duplicate it.
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ // [GIVEN] Work centers, item with routing and BOM, with a routing link code assigned to the subcontracting routing line
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] A released production order whose routing lines inherit the routing link code
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [GIVEN] The prod. order routing line for the subcontracting work center (has a routing link code)
+ ProdOrderRoutingLine.SetRange("Routing No.", Item."Routing No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [WHEN] The routing link code is validated (re-validates the existing code, which triggers
+ // the BaseApp duplicate-use check)
+ ConfirmDialogCalledCount := 0;
+ ProdOrderRoutingLine.Validate("Routing Link Code", ProdOrderRoutingLine."Routing Link Code");
+
+ // [THEN] The confirmation dialog is shown exactly once — from the BaseApp — not twice
+ Assert.AreEqual(
+ 1, ConfirmDialogCalledCount,
+ 'Routing Link Code duplicate confirmation must be shown exactly once, not twice');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure CalcNoOfProductionOrderRoutingsReturnsOneForSubcontractingPurchaseLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcProdOFactboxMgmt: Codeunit "Subc. ProdO. Factbox Mgmt.";
+ begin
+ // [SCENARIO 634720] CalcNoOfProductionOrderRoutings must filter by Routing No. and Operation No. so the factbox count matches the drill-down (which is always a single routing line for a subcontracting purchase line).
+
+ // [GIVEN] Manufacturing setup with a routing of multiple operations where only the second work center is subcontracting
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] A Released Production Order whose routing has more than one operation
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ ProdOrderRoutingLine.SetRange(Status, ProdOrderRoutingLine.Status::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ Assert.IsTrue(ProdOrderRoutingLine.Count() > 1, 'Test precondition: routing must have more than one operation to detect the bug.');
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] A Subcontracting Purchase Order created from the routing line of the subcontracting work center
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ // [WHEN] CalcNoOfProductionOrderRoutings is called with the purchase line
+ // [THEN] It returns 1, matching the single routing line shown by the drill-down (not the total operations of the prod order line)
+ Assert.AreEqual(
+ 1, SubcProdOFactboxMgmt.CalcNoOfProductionOrderRoutings(PurchaseLine),
+ 'CalcNoOfProductionOrderRoutings must equal the number of routing lines opened by the drill-down (exactly one for a subcontracting purchase line).');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder,HandleCreateTransferOrderMsg')]
+ procedure PostingDirectSubcontractingTransferSetsSourceFieldsOnDirectTransHeader()
+ var
+ Bin: Record Bin;
+ DirectTransHeader: Record "Direct Trans. Header";
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO 617373] Posting a direct subcontracting transfer correctly propagates Source Type and Source ID to the Direct Trans. Header
+
+ // [GIVEN] Complete manufacturing setup (no in-transit transfer route, so the report creates a Direct Transfer)
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ // [GIVEN] Subcontracting purchase order and transfer order to vendor
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.FindFirst();
+ TransferHeader.Get(TransferLine."Document No.");
+
+ Item.Get(ProdOrderComp."Item No.");
+ Location.Get(TransferHeader."Transfer-from Code");
+ CreateInventory(Item, Location, Bin, ProdOrderComp."Expected Qty. (Base)");
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+
+ // [WHEN] Post the direct transfer
+ Codeunit.Run(Codeunit::"TransferOrder-Post Transfer", TransferHeader);
+
+ // [THEN] Direct Trans. Header has Source Type = Subcontracting and Source ID = Vendor No.
+ DirectTransHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ Assert.RecordIsNotEmpty(DirectTransHeader);
+ DirectTransHeader.FindFirst();
+ Assert.AreEqual(
+ "Transfer Source Type"::Subcontracting, DirectTransHeader."Source Type",
+ 'Source Type must be Subcontracting on Direct Trans. Header');
+ Assert.AreEqual(
+ Vendor."No.", DirectTransHeader."Source ID",
+ 'Source ID must be the Vendor No. on Direct Trans. Header');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure CreateReturnTransferOrderAfterPartialShipOfOutbound()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ ReturnTransferHeader: Record "Transfer Header";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ OutboundFromCode, OutboundToCode : Code[10];
+ QtyPartialShip: Decimal;
+ begin
+ // [SCENARIO] Non-direct (in-transit) transfer: Return from Subcontractor must succeed when the outbound Transfer Order is only partially shipped, and a second Return attempt must be blocked with the existing error.
+ // [SCENARIO] Pre-fix the report errored 'Return from Subcontractor has already been created' on the first call because CheckTransferLineExists matched the unrelated outbound line.
+
+ // [GIVEN] Standard subcontracting setup with an in-transit transfer route (non-direct)
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("No.", ProductionOrder."Source No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ // [GIVEN] Outbound Subcontracting Transfer Order created from the Purchase Order
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GotoKey("Purchase Document Type"::Order, PurchaseLine."Document No.");
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComp."Line No.");
+ TransferLine.FindFirst();
+
+ TransferHeader.Get(TransferLine."Document No.");
+ OutboundFromCode := TransferHeader."Transfer-from Code";
+ OutboundToCode := TransferHeader."Transfer-to Code";
+
+ // [GIVEN] Inventory at the source location and the outbound TO is partially shipped via Qty. to Ship (Ship only — items move to in-transit, line stays open with positive Outstanding)
+ Location.Get(OutboundFromCode);
+ Item.Get(ProdOrderComp."Item No.");
+ CreateInventory(Item, Location, Bin, ProdOrderComp."Expected Qty. (Base)");
+
+ QtyPartialShip := Round(TransferLine.Quantity / 2, 1, '<');
+ TransferLine.Validate("Qty. to Ship", QtyPartialShip);
+ TransferLine.Modify(true);
+ LibraryWarehouse.PostTransferOrder(TransferHeader, true, false);
+
+ // [WHEN] Creating a Return Transfer Order while the outbound TO line is still present (partially shipped)
+ PurchaseHeaderPage.GotoKey("Purchase Document Type"::Order, PurchaseLine."Document No.");
+ PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ // [THEN] A Return Transfer Order is created with reversed Transfer-from / Transfer-to and quantity capped by the partially-shipped qty
+ ReturnTransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseLine."Document No.");
+ ReturnTransferHeader.SetRange("Subc. Return Order", true);
+ Assert.IsTrue(ReturnTransferHeader.FindFirst(), 'Return Transfer Order should be created after partial ship of outbound');
+ Assert.AreEqual(OutboundToCode, ReturnTransferHeader."Transfer-from Code", 'Return Transfer-from must be the subcontractor location');
+ Assert.AreEqual(OutboundFromCode, ReturnTransferHeader."Transfer-to Code", 'Return Transfer-to must be the original component location');
+
+ TransferLine.Reset();
+ TransferLine.SetRange("Document No.", ReturnTransferHeader."No.");
+ Assert.IsTrue(TransferLine.FindFirst(), 'Return Transfer Line should exist');
+ Assert.AreEqual(QtyPartialShip, TransferLine.Quantity, 'Return quantity should equal the in-transit qty from the partial outbound shipment');
+
+ // [WHEN] Trying to create the Return Transfer Order again, with a Return TO already in place
+ PurchaseHeaderPage.GotoKey("Purchase Document Type"::Order, PurchaseLine."Document No.");
+ asserterror PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ // [THEN] The duplicate is blocked with the existing error
+ Assert.ExpectedError('Nothing to create. No components or WIP items to return for the specified subcontracting order');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler,HandleTransferOrder')]
+ procedure CreateReturnTransferOrderAfterPartialShipOfOutboundDirectTransfer()
+ var
+ Bin: Record Bin;
+ Item: Record Item;
+ Location: Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderComp: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ ReturnTransferHeader: Record "Transfer Header";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ OutboundFromCode, OutboundToCode : Code[10];
+ QtyPartialShip: Decimal;
+ begin
+ // [SCENARIO] Direct transfer (no in-transit route): Return from Subcontractor must succeed when the outbound Transfer Order has been partially direct-transferred, and a second Return attempt must be blocked with the existing error.
+ // [SCENARIO] In direct-transfer mode Qty. to Ship is forced to equal Quantity, so a partial transfer is achieved by reducing the line Quantity before posting. After the post the items already sit at the subcontractor and the outbound line is consumed.
+
+ // [GIVEN] Standard subcontracting setup WITHOUT an in-transit transfer route — the outbound TO will be Direct Transfer
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("No.", ProductionOrder."Source No.");
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ // [GIVEN] Outbound Subcontracting Transfer Order created from the Purchase Order (Direct Transfer because no in-transit route)
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GotoKey("Purchase Document Type"::Order, PurchaseLine."Document No.");
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Item No.", ProdOrderComp."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComp."Line No.");
+ TransferLine.FindFirst();
+
+ TransferHeader.Get(TransferLine."Document No.");
+ OutboundFromCode := TransferHeader."Transfer-from Code";
+ OutboundToCode := TransferHeader."Transfer-to Code";
+
+ // [GIVEN] Inventory at the source location and the outbound Quantity is reduced to a partial value, then direct-transferred (Ship+Receive in one operation — items land at the subcontractor location)
+ Location.Get(OutboundFromCode);
+ Item.Get(ProdOrderComp."Item No.");
+ CreateInventory(Item, Location, Bin, ProdOrderComp."Expected Qty. (Base)");
+
+ QtyPartialShip := Round(TransferLine.Quantity / 2, 1, '<');
+ TransferLine.Validate(Quantity, QtyPartialShip);
+ TransferLine.Modify(true);
+ LibraryWarehouse.PostTransferOrder(TransferHeader, true, true);
+
+ // [WHEN] Creating a Return Transfer Order while items are now at the subcontractor
+ PurchaseHeaderPage.GotoKey("Purchase Document Type"::Order, PurchaseLine."Document No.");
+ PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ // [THEN] A Return Transfer Order is created with reversed Transfer-from / Transfer-to and quantity capped by the qty that actually arrived at the subcontractor
+ ReturnTransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseLine."Document No.");
+ ReturnTransferHeader.SetRange("Subc. Return Order", true);
+ Assert.IsTrue(ReturnTransferHeader.FindFirst(), 'Return Transfer Order should be created after partial direct transfer');
+ Assert.AreEqual(OutboundToCode, ReturnTransferHeader."Transfer-from Code", 'Return Transfer-from must be the subcontractor location');
+ Assert.AreEqual(OutboundFromCode, ReturnTransferHeader."Transfer-to Code", 'Return Transfer-to must be the original component location');
+
+ TransferLine.Reset();
+ TransferLine.SetRange("Document No.", ReturnTransferHeader."No.");
+ Assert.IsTrue(TransferLine.FindFirst(), 'Return Transfer Line should exist');
+ Assert.AreEqual(QtyPartialShip, TransferLine.Quantity, 'Return quantity should equal the qty already at the subcontractor');
+
+ // [WHEN] Trying to create the Return Transfer Order again, with a Return TO already in place
+ PurchaseHeaderPage.GotoKey("Purchase Document Type"::Order, PurchaseLine."Document No.");
+ asserterror PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ // [THEN] The duplicate is blocked with the existing error
+ Assert.ExpectedError('Nothing to create. No components or WIP items to return for the specified subcontracting order');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure CreateSubcontractingPOForEachProdOrderLineWhenLinesShareRoutingAndOperation()
+ var
+ Item: Record Item;
+ ProductionLocation: array[2] of Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ WorkCenter: array[2] of Record "Work Center";
+ ProdOrderRtng: TestPage "Prod. Order Routing";
+ I: Integer;
+ ProdOrderLineNo: array[2] of Integer;
+ begin
+ // [SCENARIO 634238] When a Released Production Order has multiple Prod. Order lines sharing the same
+ // Routing/Operation, creating a Subcontracting Order for the second line must not raise the false
+ // "Purchase orders have already been created" warning, and must create/show its own Purchase Order.
+
+ // [GIVEN] Subcontracting setup with direct transfer (no in-transit route)
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ // [GIVEN] One released production order created directly from item
+ LibraryManufacturing.CreateProductionOrder(ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", 0);
+
+ // [GIVEN] No production order lines exist yet for this order
+ ProdOrderLine.SetRange(Status, ProdOrderLine.Status::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ Assert.AreEqual(0, ProdOrderLine.Count(), 'Expected no production order lines to exist before manually creating them');
+
+ // [GIVEN] Two production order lines on the same production order, on different locations
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation[1]);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation[2]);
+ LibraryManufacturing.CreateProdOrderLine(ProdOrderLine, ProductionOrder.Status, ProductionOrder."No.", Item."No.", '', ProductionLocation[1].Code, LibraryRandom.RandInt(10) + 2);
+ ProdOrderLineNo[1] := ProdOrderLine."Line No.";
+ LibraryManufacturing.CreateProdOrderLine(ProdOrderLine, ProductionOrder.Status, ProductionOrder."No.", Item."No.", '', ProductionLocation[2].Code, LibraryRandom.RandInt(10) + 2);
+ ProdOrderLineNo[2] := ProdOrderLine."Line No.";
+ Assert.AreNotEqual(ProdOrderLineNo[1], ProdOrderLineNo[2], 'Expected two distinct production order lines');
+
+ // [GIVEN] Refresh the production order to update the routing and component lines
+ LibraryManufacturing.RefreshProdOrder(ProductionOrder, false, false, true, true, false);
+
+ // [GIVEN] The two production-order lines have transfer components on different locations
+ for I := 1 to 2 do begin
+ ProdOrderRoutingLine.Reset();
+ ProdOrderRoutingLine.SetRange(Status, ProdOrderRoutingLine.Status::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLineNo[I]);
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.RecordCount(ProdOrderRoutingLine, 1);
+
+ ProdOrderRoutingLine.FindFirst();
+
+ ProdOrderRtng.OpenView();
+ ProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ProdOrderRtng.CreateSubcontracting.Invoke();
+ ProdOrderRtng.Close();
+
+ Assert.AreEqual('A purchase order was created.\\Do you want to view it?', LibraryVariableStorage.DequeueText(), 'Expected "created" confirmation for each prod order line, not the false "already created" warning');
+ LibraryVariableStorage.AssertEmpty();
+ end;
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmYesShowSubcontractingPurchOrders,CapturePurchaseOrderPageNo')]
+ procedure CreateSubcontractingPONavigatesToOwnPOWhenLinesShareRoutingAndOperation()
+ var
+ Item: Record Item;
+ ProductionLocation: array[2] of Record Location;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ ProdOrderRtng: TestPage "Prod. Order Routing";
+ I: Integer;
+ ProdOrderLineNo: array[2] of Integer;
+ OpenedPurchaseOrderNo: Code[20];
+ begin
+ // [SCENARIO 634238] When a Released Production Order has multiple Prod. Order lines sharing routing/operation,
+ // confirming "view them" on the just-created Subcontracting Order must open the PO tied to the invoked
+ // routing line, not the unrelated PO of a sibling line.
+
+ // [GIVEN] Subcontracting setup with two prod order lines sharing routing/operation but different Routing Reference No.
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+
+ LibraryManufacturing.CreateProductionOrder(ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", 0);
+
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation[1]);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation[2]);
+ LibraryManufacturing.CreateProdOrderLine(ProdOrderLine, ProductionOrder.Status, ProductionOrder."No.", Item."No.", '', ProductionLocation[1].Code, LibraryRandom.RandInt(10) + 2);
+ ProdOrderLineNo[1] := ProdOrderLine."Line No.";
+ LibraryManufacturing.CreateProdOrderLine(ProdOrderLine, ProductionOrder.Status, ProductionOrder."No.", Item."No.", '', ProductionLocation[2].Code, LibraryRandom.RandInt(10) + 2);
+ ProdOrderLineNo[2] := ProdOrderLine."Line No.";
+
+ LibraryManufacturing.RefreshProdOrder(ProductionOrder, false, false, true, true, false);
+
+ // [WHEN] Creating a Subcontracting Order from each routing line and confirming "view them"
+ for I := 1 to 2 do begin
+ ProdOrderRoutingLine.Reset();
+ ProdOrderRoutingLine.SetRange(Status, ProdOrderRoutingLine.Status::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Routing Reference No.", ProdOrderLineNo[I]);
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ ProdOrderRtng.OpenView();
+ ProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ProdOrderRtng.CreateSubcontracting.Invoke();
+ ProdOrderRtng.Close();
+
+ // [THEN] The page handler opens the Purchase Order whose line carries this routing line's Routing Reference No.
+ OpenedPurchaseOrderNo := CopyStr(LibraryVariableStorage.DequeueText(), 1, MaxStrLen(OpenedPurchaseOrderNo));
+ PurchaseLine.Reset();
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Document No.", OpenedPurchaseOrderNo);
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Routing Reference No.", ProdOrderLineNo[I]);
+ Assert.IsFalse(PurchaseLine.IsEmpty(), StrSubstNo(PurchOrderRoutingErr, OpenedPurchaseOrderNo, ProdOrderLineNo[I]));
+ LibraryVariableStorage.AssertEmpty();
+ end;
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmYesShowSubcontractingPurchOrders,HandlePurchaseOrderPage,HandlePurchaseLinesPage')]
+ procedure ShowExistingPurchOrdersOpensListWhenAlreadyCreated()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO 633224] First Create Subcontracting Order opens the Purchase Order; running the action again on the same routing line opens the Purchase Lines list.
+
+ // [GIVEN] Manufacturing setup with subcontracting work center, item with routing/BOM, released production order
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Create Subcontracting Order from the routing line for the first time
+ PurchaseOrderPageOpened := false;
+ PurchaseLinesPageOpened := false;
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] The Purchase Order card is shown
+ Assert.IsTrue(PurchaseOrderPageOpened, 'Purchase Order should open after first creation.');
+ Assert.IsFalse(PurchaseLinesPageOpened, 'Purchase Lines list should not open on first creation.');
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ Assert.IsFalse(PurchaseLine.IsEmpty(), 'Purchase line should exist for the production order.');
+
+ // [WHEN] Create Subcontracting Order from the same routing line again
+ PurchaseOrderPageOpened := false;
+ PurchaseLinesPageOpened := false;
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] The Purchase Lines list is shown instead of individual Purchase Order cards
+ Assert.IsTrue(PurchaseLinesPageOpened, 'Purchase Lines list should open when purchase orders already exist.');
+ Assert.IsFalse(PurchaseOrderPageOpened, 'Purchase Order card should not open when purchase orders already exist.');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmYesShowSubcontractingPurchOrders,HandlePurchaseOrderPage,HandlePurchaseLinesPage')]
+ procedure ShowExistingPurchOrdersAfterReceiptDoesNotError()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WorkCenter: array[2] of Record "Work Center";
+ begin
+ // [SCENARIO 637777] Re-running Create Subcontracting Order after fully receiving the existing subcontracting purchase order should offer to view the existing order instead of raising "No Prod. Order Line with Remaining Quantity."
+
+ // [GIVEN] Manufacturing setup with subcontracting work center, item with routing/BOM, released production order, and a created subcontracting purchase order
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ EnsureGeneralPostingSetupIsValid(PurchaseLine."Gen. Bus. Posting Group", PurchaseLine."Gen. Prod. Posting Group");
+
+ // [GIVEN] The existing subcontracting purchase order is fully received
+ LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, false);
+
+ // [WHEN] Create Subcontracting Order is invoked again from the same routing line
+ PurchaseLinesPageOpened := false;
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [THEN] The existing purchase lines are shown and no raw remaining-quantity error is raised
+ Assert.IsTrue(PurchaseLinesPageOpened, 'Purchase Lines list should open when the subcontracting purchase order already exists after full receipt.');
+ end;
+
+ [Test]
+ procedure StandardTaskCodePropagatedAndDrivesSubcPriceLookup()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseLine: Record "Purchase Line";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionLineWithStdTask: Record "Requisition Line";
+ RequisitionLineNoStdTask: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ StandardTask: Record "Standard Task";
+ SubcontractorPrice: Record "Subcontractor Price";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ SubcCalculateSubContract: Report "Subc. Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ LibraryUtility: Codeunit "Library - Utility";
+ PriceWithoutStdTask: Decimal;
+ PriceWithStdTask: Decimal;
+ SecondOperationNo: Code[10];
+ begin
+ // [SCENARIO 633226] Standard Task Code propagates from Routing → Prod. Order Routing → Subcontracting Worksheet,
+ // is editable on the worksheet, and drives Subcontractor Price lookup. Editing or clearing it on a worksheet
+ // line re-applies the matching subcontractor price; carrying out creates Purchase Lines with the correct unit costs.
+
+ Initialize();
+
+ // [GIVEN] Subcontracting setup with a worksheet template
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Work centers and a manufacturing item with routing and BOM
+ // (helper creates one subcontracting routing line on WorkCenter[2] without a Standard Task)
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] A standard task code
+ LibraryManufacturing.CreateStandardTask(StandardTask);
+
+ // [GIVEN] A second subcontracting routing line on the same work center, with the standard task assigned
+ SecondOperationNo := AddSubcRoutingLineWithStandardTask(Item."Routing No.", WorkCenter[2]."No.", StandardTask.Code);
+
+ // [GIVEN] Two subcontractor prices for the item / work center / vendor:
+ // - PriceWithoutStdTask, with no Standard Task Code
+ // - PriceWithStdTask = 2 * PriceWithoutStdTask, tied to StandardTask.Code
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ PriceWithoutStdTask := LibraryRandom.RandIntInRange(50, 200);
+ PriceWithStdTask := PriceWithoutStdTask * 2;
+
+ SubcontractorPrice.Reset();
+ SubcontractorPrice.SetRange("Vendor No.", Vendor."No.");
+ SubcontractorPrice.SetRange("Item No.", Item."No.");
+ SubcontractorPrice.DeleteAll();
+
+ Clear(SubcontractorPrice);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter[2]."No.", Vendor."No.", Item."No.", '', '',
+ WorkDate(), Item."Base Unit of Measure", 0, Vendor."Currency Code");
+ SubcontractorPrice."Direct Unit Cost" := PriceWithoutStdTask;
+ SubcontractorPrice.Modify();
+
+ Clear(SubcontractorPrice);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter[2]."No.", Vendor."No.", Item."No.", StandardTask.Code, '',
+ WorkDate(), Item."Base Unit of Measure", 0, Vendor."Currency Code");
+ SubcontractorPrice."Direct Unit Cost" := PriceWithStdTask;
+ SubcontractorPrice.Modify();
+
+ // [GIVEN] A released production order
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [THEN] Standard Task Code is propagated from Routing Line to Prod. Order Routing Line on the second operation
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Operation No.", SecondOperationNo);
+ ProdOrderRoutingLine.FindFirst();
+ Assert.AreEqual(
+ StandardTask.Code, ProdOrderRoutingLine."Standard Task Code",
+ 'Standard Task Code must be propagated from Routing Line to Prod. Order Routing Line.');
+
+ // [GIVEN] An empty subcontracting worksheet
+ ReqWkshTemplate.Name := SelectRequisitionTemplateName();
+ RequisitionWkshName.Init();
+ RequisitionWkshName.Validate("Worksheet Template Name", ReqWkshTemplate.Name);
+ RequisitionWkshName.Validate(
+ Name,
+ CopyStr(
+ LibraryUtility.GenerateRandomCode(RequisitionWkshName.FieldNo(Name), Database::"Requisition Wksh. Name"),
+ 1, LibraryUtility.GetFieldLength(Database::"Requisition Wksh. Name", RequisitionWkshName.FieldNo(Name))));
+ RequisitionWkshName.Insert(true);
+
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ // [WHEN] Calculate Subcontracts is run on the worksheet
+ SubcCalculateSubContract.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContract.UseRequestPage(false);
+ SubcCalculateSubContract.RunModal();
+
+ // [THEN] On the worksheet line for the operation with a standard task, Standard Task Code is populated
+ // and the standard-task-bound price is applied
+ RequisitionLineWithStdTask.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLineWithStdTask.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLineWithStdTask.SetRange("Prod. Order No.", ProductionOrder."No.");
+ RequisitionLineWithStdTask.SetRange("Operation No.", SecondOperationNo);
+#pragma warning restore AA0210
+ RequisitionLineWithStdTask.FindFirst();
+ Assert.AreEqual(
+ StandardTask.Code, RequisitionLineWithStdTask."Subc. Standard Task Code",
+ 'Standard Task Code must be propagated from Prod. Order Routing Line to the Subcontracting Worksheet line.');
+ Assert.AreEqual(
+ PriceWithStdTask, RequisitionLineWithStdTask."Direct Unit Cost",
+ 'Subcontractor Price tied to the Standard Task Code must be applied to the worksheet line.');
+
+ // [THEN] On the worksheet line for the operation without a standard task, the un-tagged subcontractor price is applied
+ RequisitionLineNoStdTask.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLineNoStdTask.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLineNoStdTask.SetRange("Prod. Order No.", ProductionOrder."No.");
+ RequisitionLineNoStdTask.SetFilter("Operation No.", '<>%1', SecondOperationNo);
+#pragma warning restore AA0210
+ RequisitionLineNoStdTask.FindFirst();
+ Assert.AreEqual(
+ '', RequisitionLineNoStdTask."Subc. Standard Task Code",
+ 'Standard Task Code must be empty on the worksheet line that has no standard task on the routing.');
+ Assert.AreEqual(
+ PriceWithoutStdTask, RequisitionLineNoStdTask."Direct Unit Cost",
+ 'Subcontractor Price for the un-tagged combination must be applied to the worksheet line.');
+
+ // [WHEN] User clears Standard Task Code on the worksheet line
+ RequisitionLineWithStdTask.Validate("Subc. Standard Task Code", '');
+ RequisitionLineWithStdTask.Modify(true);
+
+ // [THEN] Direct Unit Cost falls back to the un-tagged subcontractor price
+ Assert.AreEqual(
+ PriceWithoutStdTask, RequisitionLineWithStdTask."Direct Unit Cost",
+ 'Clearing Standard Task Code on the worksheet line must re-apply the un-tagged subcontractor price.');
+
+ // [WHEN] User re-sets Standard Task Code on the worksheet line
+ RequisitionLineWithStdTask.Validate("Subc. Standard Task Code", StandardTask.Code);
+ RequisitionLineWithStdTask.Modify(true);
+
+ // [THEN] Direct Unit Cost is restored to the standard-task-bound subcontractor price
+ Assert.AreEqual(
+ PriceWithStdTask, RequisitionLineWithStdTask."Direct Unit Cost",
+ 'Re-setting Standard Task Code on the worksheet line must re-apply the standard-task-bound subcontractor price.');
+
+ // [WHEN] Carry Out Action Message creates the Subcontracting Purchase Order from the worksheet
+ Clear(RequisitionLine);
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+
+ // [THEN] The purchase line for the operation with a standard task has Direct Unit Cost = PriceWithStdTask
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, PurchaseLine.Type::Item);
+ PurchaseLine.SetRange("No.", Item."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ PurchaseLine.SetRange("Operation No.", SecondOperationNo);
+ PurchaseLine.FindFirst();
+ Assert.AreEqual(
+ PriceWithStdTask, PurchaseLine."Direct Unit Cost",
+ 'Subcontracting Purchase Line for the operation with a standard task must use the standard-task-bound subcontractor price.');
+
+ // [THEN] The purchase line for the operation without a standard task has Direct Unit Cost = PriceWithoutStdTask
+ PurchaseLine.SetFilter("Operation No.", '<>%1', SecondOperationNo);
+ PurchaseLine.FindFirst();
+ Assert.AreEqual(
+ PriceWithoutStdTask, PurchaseLine."Direct Unit Cost",
+ 'Subcontracting Purchase Line for the operation without a standard task must use the un-tagged subcontractor price.');
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ OpenedTransferOrderNo := CopyStr(TransfOrderPage."No.".Value(), 1, MaxStrLen(OpenedTransferOrderNo));
+ TransfOrderPage.OK().Invoke();
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrderReopen(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ TransfOrderPage."Reo&pen".Invoke();
+ TransfOrderPage.OK().Invoke();
+ end;
+
+ [PageHandler]
+ procedure HandleSubcTransferOrdersList(var TransferOrders: TestPage "Transfer Orders")
+ begin
+ Assert.IsTrue(TransferOrders.First(), 'Expected at least one subcontracting transfer order in the list.');
+ OpenedTransferOrderListNo := CopyStr(TransferOrders."No.".Value(), 1, MaxStrLen(OpenedTransferOrderListNo));
+ Assert.IsFalse(TransferOrders.Next(), 'Expected exactly one subcontracting transfer order in the list.');
+ TransferOrders.OK().Invoke();
+ end;
+
+ [ConfirmHandler]
+ procedure ConfirmYesShowSubcontractingPurchOrders(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Reply := true;
+ end;
+
+ [PageHandler]
+ procedure HandlePurchaseOrderPage(var PurchaseOrderPage: TestPage "Purchase Order")
+ begin
+ PurchaseOrderPageOpened := true;
+ PurchaseOrderPage.Close();
+ end;
+
+ [PageHandler]
+ procedure CapturePurchaseOrderPageNo(var PurchaseOrderPage: TestPage "Purchase Order")
+ begin
+ LibraryVariableStorage.Enqueue(PurchaseOrderPage."No.".Value);
+ PurchaseOrderPage.Close();
+ end;
+
+ [PageHandler]
+ procedure HandlePurchaseLinesPage(var PurchaseLinesPage: TestPage "Purchase Lines")
+ begin
+ PurchaseLinesPageOpened := true;
+ PurchaseLinesPage.Close();
+ end;
+
+ [PageHandler]
+ procedure HandleWarehouseReceipt(var WhseReceipt: TestPage "Warehouse Receipt")
+ begin
+ WhseReceipt.OK().Invoke();
+ end;
+
+ [MessageHandler]
+ procedure HandleCreatedWarehouseReceiptMsg(Message: Text[1024])
+ begin
+ end;
+
+ [MessageHandler]
+ procedure HandleCreateTransferOrderMsg(Message: Text[1024])
+ begin
+ end;
+
+ [MessageHandler]
+ procedure MessageHandler(Message: Text[1024])
+ begin
+ end;
+
+ [RequestPageHandler]
+ procedure SubcontrDispatchingListDefaultRequestPageHandler(var PurchaseOrderRequestPage: TestRequestPage "Subc. Dispatching List")
+ begin
+ // Empty handler used to close the request page. We use default settings.
+ end;
+
+ [ConfirmHandler]
+ procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
+ begin
+ LibraryVariableStorage.Enqueue(Question);
+ case true of
+ Question.Contains('Do you want to create a production order from'):
+ Reply := true;
+ else
+ Reply := false;
+ end;
+ end;
+
+ [ConfirmHandler]
+ procedure RoutingLinkCodeDuplicateConfirmHandler(Question: Text[1024]; var Reply: Boolean)
+ begin
+ ConfirmDialogCalledCount += 1;
+ Reply := true;
+ end;
+
+ [ConfirmHandler]
+ procedure ConfirmArchiveOrderHandler(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Reply := true;
+ end;
+
+ local procedure AddSubcRoutingLineWithStandardTask(RoutingNo: Code[20]; WorkCenterNo: Code[20]; StandardTaskCode: Code[10]) NewOperationNo: Code[10]
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+#pragma warning disable AA0210
+ CapacityUnitOfMeasure.SetRange(Type, CapacityUnitOfMeasure.Type::Minutes);
+#pragma warning restore AA0210
+ CapacityUnitOfMeasure.FindFirst();
+
+ RoutingHeader.Get(RoutingNo);
+ RoutingHeader.Validate(Status, RoutingHeader.Status::New);
+ RoutingHeader.Modify(true);
+
+ // Use a number larger than any existing operation so the certification-time ordering check is satisfied.
+ NewOperationNo := CopyStr(IncStr(FindLastRoutingOperationNo(RoutingNo)), 1, MaxStrLen(NewOperationNo));
+
+ LibraryManufacturing.CreateRoutingLineSetup(
+ RoutingLine, RoutingHeader, WorkCenterNo, NewOperationNo,
+ LibraryRandom.RandInt(5), LibraryRandom.RandInt(5));
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Standard Task Code", StandardTaskCode);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+ end;
+
+ local procedure FindLastRoutingOperationNo(RoutingNo: Code[20]): Code[10]
+ var
+ RoutingLine: Record "Routing Line";
+ begin
+ RoutingLine.SetRange("Routing No.", RoutingNo);
+ RoutingLine.FindLast();
+ exit(RoutingLine."Operation No.");
+ end;
+
+ local procedure SetupSubcontractingForTransferOrderTests(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var ProductionLocation: Record Location)
+ var
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(ProductionLocation);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ end;
+
+ local procedure CreateProductionOrderWithSubcTransferOrder(Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; ProductionLocationCode: Code[10]; CreateTransferRouteForOrder: Boolean; var ProductionOrder: Record "Production Order"): Code[20]
+ var
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ ReleasedProdOrderRtng: TestPage "Prod. Order Routing";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ SetAllProdOrderTransferComponentLocations(ProductionOrder."No.", ProductionLocationCode);
+ // The transfer route is keyed by from/to location, so it must be created only once for a given location pair.
+ // Callers that reuse the same locations pass false for subsequent orders to reuse the existing route.
+ if CreateTransferRouteForOrder then
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ ReleasedProdOrderRtng.OpenView();
+ ReleasedProdOrderRtng.GoToRecord(ProdOrderRoutingLine);
+ ReleasedProdOrderRtng.CreateSubcontracting.Invoke();
+ ReleasedProdOrderRtng.Close();
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+ PurchaseHeaderPage.Close();
+
+ TransferHeader.SetRange("Subcontr. Purch. Order No.", PurchaseHeader."No.");
+ Assert.IsTrue(TransferHeader.FindFirst(), 'Expected a subcontracting transfer order for the production order.');
+ exit(TransferHeader."No.");
+ end;
+
+ local procedure CreateAndCalculateNeededWorkAndMachineCenter(var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ ShopCalendarCode: Code[10];
+ MachineCenterNo: Code[20];
+ MachineCenterNo2: Code[20];
+ WorkCenterNo: Code[20];
+ WorkCenterNo2: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ // [GIVEN] Create and Calculate needed Work and Machine Center
+ CreateWorkCenter(WorkCenterNo, ShopCalendarCode, "Flushing Method"::"Pick + Manual", not Subcontracting, UnitCostCalculation, '');
+ WorkCenter[1].Get(WorkCenterNo);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter[1], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ LibraryMfgManagement.CreateMachineCenter(MachineCenterNo, WorkCenterNo, "Flushing Method"::"Pick + Manual".AsInteger());
+ MachineCenter[1].Get(MachineCenterNo);
+ LibraryManufacturing.CalculateMachCenterCalendar(MachineCenter[1], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ LibraryMfgManagement.CreateMachineCenter(MachineCenterNo2, WorkCenterNo, "Flushing Method"::"Pick + Manual".AsInteger());
+ MachineCenter[2].Get(MachineCenterNo2);
+ LibraryManufacturing.CalculateMachCenterCalendar(MachineCenter[2], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ if Subcontracting then
+ CreateWorkCenter(WorkCenterNo2, ShopCalendarCode, "Flushing Method"::"Pick + Manual", Subcontracting, UnitCostCalculation, '')
+ else
+ CreateWorkCenter(WorkCenterNo2, ShopCalendarCode, "Flushing Method"::"Pick + Manual", not Subcontracting, UnitCostCalculation, '');
+ WorkCenter[2].Get(WorkCenterNo2);
+ LibraryManufacturing.CalculateWorkCenterCalendar(WorkCenter[2], CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+ end;
+
+ local procedure CreateItemForProductionIncludeRoutingAndProdBOM(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ ProductionBOMHeader: Record "Production BOM Header";
+ NoSeries: Codeunit "No. Series";
+ ItemNo: Code[20];
+ ItemNo2: Code[20];
+ ProductionBOMNo: Code[20];
+ RoutingNo: Code[20];
+ begin
+ ManufacturingSetup.SetLoadFields("Routing Nos.");
+ ManufacturingSetup.Get();
+ RoutingNo := NoSeries.GetNextNo(ManufacturingSetup."Routing Nos.", WorkDate(), true);
+
+ LibraryMfgManagement.CreateRouting(RoutingNo, MachineCenter[1]."No.", MachineCenter[2]."No.", WorkCenter[1]."No.", WorkCenter[2]."No.");
+
+ // Create Items with Flushing method - Manual with the Parent Item containing Routing No. and Production BOM No.
+
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", '', '');
+ ItemNo := Item."No.";
+ Clear(Item);
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", '', '');
+ ItemNo2 := Item."No.";
+ Clear(Item);
+
+ ProductionBOMNo := LibraryManufacturing.CreateCertifProdBOMWithTwoComp(ProductionBOMHeader, ItemNo, ItemNo2, 1); // value important.
+
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", RoutingNo, ProductionBOMNo);
+ end;
+
+ local procedure SetAllProdOrderTransferComponentLocations(ProdOrderNo: Code[20]; LocationCode: Code[10])
+ var
+ ProdOrderComp: Record "Prod. Order Component";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ if ProdOrderComp.FindSet() then
+ repeat
+ ProdOrderComp.Validate("Location Code", LocationCode);
+ ProdOrderComp.Modify(true);
+ until ProdOrderComp.Next() = 0;
+ end;
+
+ local 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
+ RoutingLink.Init();
+ RoutingLink.Validate(Code, CopyStr(Item."Production BOM No.", 1, 10));
+ RoutingLink.Insert(true);
+
+ 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);
+ RoutingLine.FindFirst();
+ RoutingLine.Validate("Routing Link Code", RoutingLink.Code);
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ ProductionBOMLine.FindLast();
+ ProductionBOMLine.Validate("Routing Link Code", RoutingLink.Code);
+ ProductionBOMLine.Modify(true);
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ local procedure UpdateProdOrderCompWithLocationAndBinCode(ProdOrderNo: Code[20]; var LocationCode: Code[10]; var BinCode: Code[20])
+ var
+ Bin: Record Bin;
+ Location: Record Location;
+ ProdOrderComp: Record "Prod. Order Component";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Location."Bin Mandatory" := true;
+ Location.Modify();
+ LibraryWarehouse.CreateBin(Bin, Location.Code, '', '', '');
+ ProdOrderComp."Location Code" := Location.Code;
+ ProdOrderComp."Bin Code" := Bin.Code;
+ ProdOrderComp.Modify();
+
+ LocationCode := Location.Code;
+ BinCode := Bin.Code;
+ end;
+
+ local procedure UpdateProdOrderCompDueDate(ProdOrderNo: Code[20])
+ var
+ ProdOrderComp: Record "Prod. Order Component";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProdOrderNo);
+#pragma warning disable AA0210
+ ProdOrderComp.SetRange("Component Supply Method", ProdOrderComp."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComp.FindFirst();
+ ProdOrderComp."Due Date" := CalcDate('<+10D>', WorkDate());
+ ProdOrderComp.Modify();
+ end;
+
+ local procedure CreateItemWithSingleSubcontractingOperation(var Item: Record Item; var SubcWorkCenter: Record "Work Center")
+ var
+ CapacityUnitOfMeasure: Record "Capacity Unit of Measure";
+ ComponentItem1: Record Item;
+ ComponentItem2: Record Item;
+ ProductionBOMHeader: Record "Production BOM Header";
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ ShopCalendarCode: Code[10];
+ WorkCenterNo: Code[20];
+ begin
+ LibraryManufacturing.CreateCapacityUnitOfMeasure(CapacityUnitOfMeasure, "Capacity Unit of Measure"::Minutes);
+ ShopCalendarCode := LibraryManufacturing.UpdateShopCalendarWorkingDays();
+
+ CreateWorkCenter(WorkCenterNo, ShopCalendarCode, "Flushing Method"::"Pick + Manual", true, UnitCostCalculation, '');
+ SubcWorkCenter.Get(WorkCenterNo);
+ LibraryManufacturing.CalculateWorkCenterCalendar(SubcWorkCenter, CalcDate('<-CY-1Y>', WorkDate()), CalcDate('', WorkDate()));
+
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Work Center", SubcWorkCenter."No.");
+ RoutingLine.Validate("Setup Time", LibraryRandom.RandInt(5));
+ RoutingLine.Validate("Run Time", LibraryRandom.RandInt(5));
+ RoutingLine.Validate("Run Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Validate("Setup Time Unit of Meas. Code", CapacityUnitOfMeasure.Code);
+ RoutingLine.Modify(true);
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ LibraryInventory.CreateItem(ComponentItem1);
+ LibraryInventory.CreateItem(ComponentItem2);
+ LibraryManufacturing.CreateCertifProdBOMWithTwoComp(ProductionBOMHeader, ComponentItem1."No.", ComponentItem2."No.", 1);
+
+ CreateItem(Item, "Costing Method"::FIFO, "Reordering Policy"::"Lot-for-Lot", "Flushing Method"::"Pick + Manual", RoutingHeader."No.", ProductionBOMHeader."No.");
+ end;
+
+ local procedure UpdateVendorWithSubcontractingLocationCode(WorkCenter: Record "Work Center")
+ var
+ Location: Record Location;
+ Vendor: Record Vendor;
+ begin
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor.Get(WorkCenter."Subcontractor No.");
+ Vendor."Subc. Location Code" := Location.Code;
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ Vendor."Location Code" := Location.Code;
+ Vendor.Modify();
+ end;
+
+ procedure CreateWorkCenter(var WorkCenterNo: Code[20]; ShopCalendarCode: Code[10]; FlushingMethod: Enum "Flushing Method"; Subcontract: Boolean;
+ UnitCostCalc: Option;
+ CurrencyCode: Code[10])
+ var
+ GenProductPostingGroup: Record "Gen. Product Posting Group";
+ VATPostingSetup: Record "VAT Posting Setup";
+ WorkCenter: Record "Work Center";
+ begin
+ // Create Work Center with required fields where random is used, values not important for test.
+ LibraryMfgManagement.CreateWorkCenterWithFixedCost(WorkCenter, ShopCalendarCode, 0);
+
+ WorkCenter.Validate("Flushing Method", FlushingMethod);
+ WorkCenter.Validate("Direct Unit Cost", LibraryRandom.RandDec(10, 2));
+ WorkCenter.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Overhead Rate", LibraryRandom.RandDec(5, 1));
+ WorkCenter.Validate("Unit Cost Calculation", UnitCostCalc);
+
+ if Subcontract then begin
+ LibraryERM.FindVATPostingSetup(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT");
+ GenProductPostingGroup.FindFirst();
+ GenProductPostingGroup.Validate("Def. VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group");
+ GenProductPostingGroup.Modify(true);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(CurrencyCode));
+ end;
+ WorkCenter.Modify(true);
+ WorkCenterNo := WorkCenter."No.";
+ end;
+
+ local procedure EnsureGeneralPostingSetupIsValid(GenBusPostingGroup: Code[20]; GenProdPostingGroup: Code[20])
+ var
+ GeneralPostingSetup: Record "General Posting Setup";
+ begin
+ if GeneralPostingSetup.Get(GenBusPostingGroup, GenProdPostingGroup) then begin
+ if GeneralPostingSetup.Blocked then begin
+ GeneralPostingSetup.Blocked := false;
+ GeneralPostingSetup.Modify();
+ end;
+ exit;
+ end;
+
+ GeneralPostingSetup.Init();
+ GeneralPostingSetup."Gen. Bus. Posting Group" := GenBusPostingGroup;
+ GeneralPostingSetup."Gen. Prod. Posting Group" := GenProdPostingGroup;
+ GeneralPostingSetup.Insert();
+ GeneralPostingSetup.SuggestSetupAccounts();
+ end;
+
+ local procedure CreateItem(var Item: Record Item; ItemCostingMethod: Enum "Costing Method"; ItemReorderPolicy: Enum "Reordering Policy";
+ FlushingMethod: Enum "Flushing Method";
+ RoutingNo: Code[20];
+ ProductionBOMNo: Code[20])
+ begin
+ // Create Item with required fields where random values not important for test.
+ LibraryManufacturing.CreateItemManufacturing(
+ Item, ItemCostingMethod, LibraryRandom.RandInt(10), ItemReorderPolicy, FlushingMethod, RoutingNo, ProductionBOMNo);
+ Item.Validate("Overhead Rate", LibraryRandom.RandDec(5, 2));
+ Item.Validate("Indirect Cost %", LibraryRandom.RandDec(5, 2));
+ Item.Modify(true);
+ end;
+
+ [Test]
+ procedure CopyDocumentDoesNotCopySubcLocationCode()
+ var
+ FromPurchaseHeader: Record "Purchase Header";
+ ToPurchaseHeader: Record "Purchase Header";
+ Location: Record Location;
+ CopyPurchDoc: Report "Copy Purchase Document";
+ begin
+ // [SCENARIO 633225] Copy Document should not copy the Subcontracting Location Code to the new purchase order
+ Initialize();
+
+ // [GIVEN] A purchase order with Subcontracting Location Code set
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ LibraryPurchase.CreatePurchaseOrder(FromPurchaseHeader);
+ FromPurchaseHeader."Subc. Location Code" := Location.Code;
+ FromPurchaseHeader.Modify();
+
+ // [GIVEN] A new target purchase order for the same vendor
+ LibraryPurchase.CreatePurchHeader(ToPurchaseHeader, ToPurchaseHeader."Document Type"::Order, FromPurchaseHeader."Buy-from Vendor No.");
+
+ // [WHEN] Copy Document is used to copy the source order (IncludeHeader = true)
+ Clear(CopyPurchDoc);
+ CopyPurchDoc.SetParameters("Purchase Document Type From"::Order, FromPurchaseHeader."No.", true, false);
+ CopyPurchDoc.SetPurchHeader(ToPurchaseHeader);
+ CopyPurchDoc.UseRequestPage(false);
+ CopyPurchDoc.RunModal();
+
+ // [THEN] Subcontracting Location Code is not copied to the new purchase order
+ ToPurchaseHeader.Get(ToPurchaseHeader."Document Type", ToPurchaseHeader."No.");
+ Assert.AreEqual('', ToPurchaseHeader."Subc. Location Code", 'Subc. Location Code should not be copied by Copy Document');
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmArchiveOrderHandler,MessageHandler')]
+ procedure CopyDocumentFromArchiveDoesNotCopySubcLocationCode()
+ var
+ FromPurchaseHeader: Record "Purchase Header";
+ ToPurchaseHeader: Record "Purchase Header";
+ PurchaseHeaderArchive: Record "Purchase Header Archive";
+ Location: Record Location;
+ ArchiveManagement: Codeunit ArchiveManagement;
+ CopyDocumentMgt: Codeunit "Copy Document Mgt.";
+ FromDocNo: Code[20];
+ begin
+ // [SCENARIO 633225] Copy Document from archive should not copy the Subcontracting Location Code to the new purchase order
+ Initialize();
+
+ // [GIVEN] A purchase order with Subcontracting Location Code set
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+ LibraryPurchase.CreatePurchaseOrder(FromPurchaseHeader);
+ FromPurchaseHeader."Subc. Location Code" := Location.Code;
+ FromPurchaseHeader.Modify();
+ FromDocNo := FromPurchaseHeader."No.";
+
+ // [GIVEN] The purchase order is archived
+ ArchiveManagement.ArchivePurchDocument(FromPurchaseHeader);
+
+ // [GIVEN] A new target purchase order for the same vendor
+ LibraryPurchase.CreatePurchHeader(ToPurchaseHeader, ToPurchaseHeader."Document Type"::Order, FromPurchaseHeader."Buy-from Vendor No.");
+
+ // [WHEN] Copy Document is used to copy from the archived order (IncludeHeader = true)
+ PurchaseHeaderArchive.SetRange("Document Type", FromPurchaseHeader."Document Type");
+ PurchaseHeaderArchive.SetRange("No.", FromDocNo);
+ PurchaseHeaderArchive.FindFirst();
+ CopyDocumentMgt.SetProperties(true, false, false, false, false, false, false);
+ CopyDocumentMgt.SetArchDocVal(PurchaseHeaderArchive."Doc. No. Occurrence", PurchaseHeaderArchive."Version No.");
+ CopyDocumentMgt.CopyPurchDoc("Purchase Document Type From"::"Arch. Order", FromDocNo, ToPurchaseHeader);
+
+ // [THEN] Subcontracting Location Code is not copied to the new purchase order
+ ToPurchaseHeader.Get(ToPurchaseHeader."Document Type", ToPurchaseHeader."No.");
+ Assert.AreEqual('', ToPurchaseHeader."Subc. Location Code", 'Subc. Location Code should not be copied from archive by Copy Document');
+ end;
+
+ [Test]
+ procedure WorksheetDirectUnitCostUsesQtyPerUoMNotBaseQtyForUoMConversion()
+ var
+ Item: Record Item;
+ ItemUOM: Record "Item Unit of Measure";
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ RequisitionLine: Record "Requisition Line";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ QtyPerSet: Integer;
+ PriceListUnitCost: Decimal;
+ begin
+ // [SCENARIO 636078] Calculate Subcontracts must compute Direct Unit Cost on the Subcontracting
+ // Worksheet using the per-UoM conversion factor (GetQuantityForUOM()), not the total base
+ // quantity (GetQuantityBase()) of the order.
+
+ // [GIVEN] Item with PCS base UoM and a SET alternative UoM (10 PCS per SET).
+ Initialize();
+ LibraryInventory.CreateItem(Item);
+ QtyPerSet := 10;
+ LibraryInventory.CreateItemUnitOfMeasureCode(ItemUOM, Item."No.", QtyPerSet);
+
+ // [GIVEN] Vendor and Work Center with the vendor as its subcontractor.
+ LibraryPurchase.CreateVendor(Vendor);
+ LibraryManufacturing.CreateWorkCenter(WorkCenter);
+ WorkCenter.Validate("Subcontractor No.", Vendor."No.");
+ WorkCenter.Modify(true);
+
+ // [GIVEN] A subcontractor price in the blank fallback UoM with Minimum Quantity 1 and Direct
+ // Unit Cost 1000 — the blank-UoM row matches the SET line's '%1|%2' UoM filter and exercises
+ // the cross-UoM conversion (PriceListUOM resolves to the item's base UoM).
+ PriceListUnitCost := 1000;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), '', 1, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", PriceListUnitCost);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] A staged Requisition Line for 3 SET (= 30 PCS in base UoM).
+ RequisitionLine.Init();
+ RequisitionLine."No." := Item."No.";
+ RequisitionLine."Unit of Measure Code" := ItemUOM.Code;
+ RequisitionLine."Vendor No." := Vendor."No.";
+ RequisitionLine."Work Center No." := WorkCenter."No.";
+ RequisitionLine."Order Date" := WorkDate();
+ RequisitionLine.Quantity := 3;
+
+ // [WHEN] The subcontractor price is applied to the requisition line.
+ SubcPriceManagement.GetSubcPriceForReqLine(RequisitionLine, '');
+
+ // [THEN] Direct Unit Cost = price-list cost * Qty-per-UoM (1000 * 10 = 10000),
+ // not price-list cost * total base quantity (1000 * 30 = 30000 — the pre-fix behavior).
+ Assert.AreEqual(
+ PriceListUnitCost * QtyPerSet, RequisitionLine."Direct Unit Cost",
+ 'Direct Unit Cost on the Subcontracting Worksheet must be derived from Qty. per Unit of Measure, not from total base quantity.');
+
+ // [WHEN] The same price is applied to a Requisition Line using the base UoM (no conversion needed).
+ Clear(RequisitionLine);
+ RequisitionLine.Init();
+ RequisitionLine."No." := Item."No.";
+ RequisitionLine."Unit of Measure Code" := Item."Base Unit of Measure";
+ RequisitionLine."Vendor No." := Vendor."No.";
+ RequisitionLine."Work Center No." := WorkCenter."No.";
+ RequisitionLine."Order Date" := WorkDate();
+ RequisitionLine.Quantity := 30;
+ SubcPriceManagement.GetSubcPriceForReqLine(RequisitionLine, '');
+
+ // [THEN] Direct Unit Cost equals the price-list cost (the same-UoM path is unchanged by the fix).
+ Assert.AreEqual(
+ PriceListUnitCost, RequisitionLine."Direct Unit Cost",
+ 'Direct Unit Cost must equal the price-list cost when the worksheet UoM matches the price-list UoM.');
+ end;
+
+ [Test]
+ procedure ReqLinePriceUsesOrderUoMWhenFixedUOMIsEmpty()
+ var
+ Item: Record Item;
+ ItemUOM: Record "Item Unit of Measure";
+ Vendor: Record Vendor;
+ WorkCenter: Record "Work Center";
+ SubcontractorPrice: Record "Subcontractor Price";
+ RequisitionLine: Record "Requisition Line";
+ SubcPriceManagement: Codeunit "Subc. Price Management";
+ AltUOMCode: Code[10];
+ PcsPrice, SetPrice : Decimal;
+ QtyPerSet: Integer;
+ begin
+ // [SCENARIO 636059] GetSubcPriceForReqLine must filter Subcontractor Prices by the
+ // requisition line's Unit of Measure (with blank fallback) even when the caller passes
+ // FixedUOM = '' — otherwise the alphabetically-last UoM row wins regardless of the line's UoM.
+ Initialize();
+
+ // [GIVEN] Item with Base UoM and an alternative UoM (10 base per alt) whose code sorts after the base.
+ LibraryInventory.CreateItem(Item);
+ QtyPerSet := 10;
+ AltUOMCode := CreateUOMCodeSortingAfter(Item."Base Unit of Measure");
+ LibraryInventory.CreateItemUnitOfMeasure(ItemUOM, Item."No.", AltUOMCode, QtyPerSet);
+
+ // [GIVEN] Vendor and Work Center with the vendor as its subcontractor.
+ LibraryPurchase.CreateVendor(Vendor);
+ LibraryManufacturing.CreateWorkCenter(WorkCenter);
+ WorkCenter.Validate("Subcontractor No.", Vendor."No.");
+ WorkCenter.Modify(true);
+
+ // [GIVEN] Two subcontractor prices — Base UoM = 1001, alternative UoM = 1004.
+ PcsPrice := 1001;
+ SetPrice := 1004;
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), Item."Base Unit of Measure", 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", PcsPrice);
+ SubcontractorPrice.Modify(true);
+ SubcontractingMgmtLibrary.CreateSubContractingPrice(
+ SubcontractorPrice, WorkCenter."No.", Vendor."No.", Item."No.", '', '', WorkDate(), AltUOMCode, 0, '');
+ SubcontractorPrice.Validate("Direct Unit Cost", SetPrice);
+ SubcontractorPrice.Modify(true);
+
+ // [GIVEN] A staged Requisition Line in the Base UoM with FixedUOM = ''.
+ RequisitionLine.Init();
+ RequisitionLine."No." := Item."No.";
+ RequisitionLine."Unit of Measure Code" := Item."Base Unit of Measure";
+ RequisitionLine."Vendor No." := Vendor."No.";
+ RequisitionLine."Work Center No." := WorkCenter."No.";
+ RequisitionLine."Order Date" := WorkDate();
+ RequisitionLine.Quantity := 1;
+
+ // [WHEN] GetSubcPriceForReqLine is called with no FixedUOM.
+ SubcPriceManagement.GetSubcPriceForReqLine(RequisitionLine, '');
+
+ // [THEN] Direct Unit Cost equals the Base UoM price (1001), not the alt-UoM derived 100.40.
+ Assert.AreEqual(
+ PcsPrice, RequisitionLine."Direct Unit Cost",
+ 'GetSubcPriceForReqLine must pick the price row matching the line''s Unit of Measure when FixedUOM is empty.');
+ end;
+
+ [Test]
+ procedure VendorSuppliedCompQtyUpdatedOnPurchOrderReschedule()
+ var
+ Item: Record Item;
+ ComponentItem: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionBOMLine: Record "Production BOM Line";
+ ProductionOrder: Record "Production Order";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderComponent: Record "Prod. Order Component";
+ PurchaseLine: Record "Purchase Line";
+ PurchaseLineComp: Record "Purchase Line";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ WorkCenter: array[2] of Record "Work Center";
+ InitialQty: Decimal;
+ NewQty: Decimal;
+ begin
+ // [SCENARIO 637496] When a production order quantity changes and the subcontracting purchase order
+ // is rescheduled via the requisition worksheet, the Vendor-Supplied component purchase lines
+ // should be updated to reflect the new quantity.
+
+ // [GIVEN] A subcontracting setup with a Vendor-Supplied component
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+
+ CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter);
+ CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Vendor-Supplied");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] A released production order
+ InitialQty := LibraryRandom.RandIntInRange(5, 10);
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", InitialQty);
+
+ // [GIVEN] A subcontracting purchase order created via the requisition worksheet
+ SubcontractingMgmtLibrary.CreateReqWkshTemplateAndName(ReqWkshTemplate, RequisitionWkshName);
+ CalculateSubcontractsAndFindReqLine(RequisitionWkshName, ProductionOrder."No.", RequisitionLine);
+ CarryOutSubcontractingAction(RequisitionLine);
+
+ // [GIVEN] The vendor-supplied component purchase line exists
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+#pragma warning disable AA0210
+ ProductionBOMLine.SetRange("Component Supply Method", "Component Supply Method"::"Vendor-Supplied");
+#pragma warning restore AA0210
+ ProductionBOMLine.FindFirst();
+ ComponentItem.Get(ProductionBOMLine."No.");
+
+ FindSubcPurchLineForProdOrder(PurchaseLine, Item."No.", ProductionOrder."No.");
+ FindComponentPurchLine(PurchaseLineComp, PurchaseLine."Document No.", ComponentItem."No.");
+ Assert.IsTrue(PurchaseLineComp.FindFirst(), 'Vendor-Supplied component purchase line should exist after initial PO creation.');
+
+ // [WHEN] The production order quantity is increased and refreshed
+ NewQty := InitialQty + LibraryRandom.RandIntInRange(3, 7);
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+ ProdOrderLine.Validate(Quantity, NewQty);
+ ProdOrderLine.Modify(true);
+
+ // [WHEN] CalculateSubcontracts is run again and carried out (reschedule path)
+ CalculateSubcontractsAndFindReqLine(RequisitionWkshName, ProductionOrder."No.", RequisitionLine);
+
+ Assert.IsTrue(
+ RequisitionLine."Action Message" in
+ [RequisitionLine."Action Message"::"Change Qty.",
+ RequisitionLine."Action Message"::"Resched. & Chg. Qty."],
+ 'Requisition line should have a Change Qty or Reschedule action message.');
+
+ CarryOutSubcontractingAction(RequisitionLine);
+
+ // [THEN] The component purchase line quantity matches the updated component remaining quantity
+ ProdOrderComponent.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderComponent.SetRange("Item No.", ComponentItem."No.");
+#pragma warning disable AA0210
+ ProdOrderComponent.SetRange("Component Supply Method", "Component Supply Method"::"Vendor-Supplied");
+#pragma warning restore AA0210
+ ProdOrderComponent.FindFirst();
+
+ PurchaseLineComp.FindFirst();
+ Assert.AreEqual(
+ ProdOrderComponent."Remaining Quantity",
+ PurchaseLineComp.Quantity,
+ 'Vendor-Supplied component purchase line quantity should match the updated production order component remaining quantity.');
+ end;
+
+ local procedure CalculateSubcontractsAndFindReqLine(RequisitionWkshName: Record "Requisition Wksh. Name"; ProdOrderNo: Code[20]; var RequisitionLine: Record "Requisition Line")
+ var
+ SubcCalculateSubContract: Report "Subc. Calculate Subcontracts";
+ begin
+ Clear(RequisitionLine);
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ SubcCalculateSubContract.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContract.UseRequestPage(false);
+ SubcCalculateSubContract.RunModal();
+
+ RequisitionLine.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProdOrderNo);
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+ end;
+
+ local procedure CarryOutSubcontractingAction(var RequisitionLine: Record "Requisition Line")
+ var
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ begin
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+ end;
+
+ local procedure FindSubcPurchLineForProdOrder(var PurchaseLine: Record "Purchase Line"; ItemNo: Code[20]; ProdOrderNo: Code[20])
+ begin
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("No.", ItemNo);
+ PurchaseLine.SetRange("Prod. Order No.", ProdOrderNo);
+ PurchaseLine.FindFirst();
+ end;
+
+ local procedure FindComponentPurchLine(var PurchaseLineComp: Record "Purchase Line"; DocumentNo: Code[20]; ComponentItemNo: Code[20])
+ begin
+ PurchaseLineComp.SetRange("Document Type", PurchaseLineComp."Document Type"::Order);
+ PurchaseLineComp.SetRange("Document No.", DocumentNo);
+ PurchaseLineComp.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLineComp.SetRange("No.", ComponentItemNo);
+ end;
+
+ local procedure CreateUOMCodeSortingAfter(BaseUOMCode: Code[10]): Code[10]
+ var
+ UnitOfMeasure: Record "Unit of Measure";
+ LibraryUtility: Codeunit "Library - Utility";
+ NewCode: Code[10];
+ begin
+ // LibraryInventory.CreateUnitOfMeasureCode generates a hex-only code (truncated GUID), so
+ // any code with a 'Z' prefix is guaranteed to sort after it. This makes the multi-UoM test
+ // deterministic — without the fix, FindLast() picks the alt UoM row.
+ repeat
+ NewCode := CopyStr('Z' + LibraryUtility.GenerateGUID(), 1, MaxStrLen(NewCode));
+ until not UnitOfMeasure.Get(NewCode);
+ UnitOfMeasure.Init();
+ UnitOfMeasure.Code := NewCode;
+ UnitOfMeasure.Description := NewCode;
+ UnitOfMeasure.Insert(true);
+ if UnitOfMeasure.Code <= BaseUOMCode then
+ Error('Test setup: generated UoM code %1 must sort after base UoM code %2.', UnitOfMeasure.Code, BaseUOMCode);
+ exit(UnitOfMeasure.Code);
+ end;
+
+ [Test]
+ [HandlerFunctions('ConfirmHandler')]
+ procedure CancelInvoiceWithSubcontractingItemChargeIsBlocked()
+ var
+ Item: Record Item;
+ RegularItem: Record Item;
+ ProductionOrder: Record "Production Order";
+ SubcWorkCenter: Record "Work Center";
+ SubcPurchaseHeader: Record "Purchase Header";
+ SubcPurchaseLine: Record "Purchase Line";
+ RegularPurchaseHeader: Record "Purchase Header";
+ RegularPurchaseLine: Record "Purchase Line";
+ SubcPurchRcptLine: Record "Purch. Rcpt. Line";
+ RegPurchRcptLine: Record "Purch. Rcpt. Line";
+ ItemCharge: Record "Item Charge";
+ ItemChargeInvHeader: Record "Purchase Header";
+ ItemChargeInvLine: Record "Purchase Line";
+ PurchInvHeader: Record "Purch. Inv. Header";
+ CorrectPostedPurchInvoice: Codeunit "Correct Posted Purch. Invoice";
+ PostedInvoiceNo: Code[20];
+ SubcVendorNo: Code[20];
+ begin
+ // [SCENARIO 637502] Cancelling a Posted Purchase Invoice whose Item Charge is split between a regular item receipt
+ // line and a subcontracting service receipt line must be blocked. Letting the cancel run today silently skips the
+ // capacity portion (Value Entry has Item Ledger Entry No. = 0) and redistributes it to inventory, corrupting cost.
+ // Until a proper reversal path exists, the Subcontracting App blocks the cancel with a clear error so the user
+ // creates a corrective credit memo manually.
+
+ // [GIVEN] Subcontracting setup with a single-operation subcontracting routing on Item
+ Initialize();
+ Subcontracting := true;
+ UnitCostCalculation := UnitCostCalculation::Units;
+ CreateItemWithSingleSubcontractingOperation(Item, SubcWorkCenter);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(SubcWorkCenter);
+
+ // [GIVEN] Released production order and a subcontracting purchase order received in full
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandIntInRange(5, 10));
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", SubcWorkCenter."No.");
+
+ SubcPurchaseLine.SetRange("Document Type", SubcPurchaseLine."Document Type"::Order);
+ SubcPurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ SubcPurchaseLine.SetRange("Work Center No.", SubcWorkCenter."No.");
+#pragma warning restore AA0210
+ SubcPurchaseLine.FindFirst();
+ SubcPurchaseHeader.Get(SubcPurchaseLine."Document Type", SubcPurchaseLine."Document No.");
+ EnsureGeneralPostingSetupIsValid(SubcPurchaseLine."Gen. Bus. Posting Group", SubcPurchaseLine."Gen. Prod. Posting Group");
+ SubcVendorNo := SubcPurchaseHeader."Buy-from Vendor No.";
+
+ LibraryPurchase.PostPurchaseDocument(SubcPurchaseHeader, true, false);
+
+ SubcPurchRcptLine.SetRange("Order No.", SubcPurchaseHeader."No.");
+ SubcPurchRcptLine.SetRange("Order Line No.", SubcPurchaseLine."Line No.");
+ SubcPurchRcptLine.FindFirst();
+
+ // [GIVEN] A separate regular purchase order for the same vendor that receives a normal inventory item
+ LibraryInventory.CreateItem(RegularItem);
+ LibraryPurchase.CreatePurchHeader(RegularPurchaseHeader, RegularPurchaseHeader."Document Type"::Order, SubcVendorNo);
+ LibraryPurchase.CreatePurchaseLineWithUnitCost(
+ RegularPurchaseLine, RegularPurchaseHeader, RegularItem."No.",
+ LibraryRandom.RandDecInRange(50, 100, 2), LibraryRandom.RandIntInRange(2, 5));
+ EnsureGeneralPostingSetupIsValid(RegularPurchaseLine."Gen. Bus. Posting Group", RegularPurchaseLine."Gen. Prod. Posting Group");
+ LibraryPurchase.PostPurchaseDocument(RegularPurchaseHeader, true, false);
+
+ RegPurchRcptLine.SetRange("Order No.", RegularPurchaseHeader."No.");
+ RegPurchRcptLine.SetRange("Order Line No.", RegularPurchaseLine."Line No.");
+ RegPurchRcptLine.FindFirst();
+
+ // [GIVEN] A purchase invoice with a single Item Charge line of quantity 2 allocated 1:1 across both receipt lines
+ LibraryInventory.CreateItemCharge(ItemCharge);
+ LibraryPurchase.CreatePurchHeader(ItemChargeInvHeader, ItemChargeInvHeader."Document Type"::Invoice, SubcVendorNo);
+ LibraryPurchase.CreatePurchaseLine(ItemChargeInvLine, ItemChargeInvHeader, ItemChargeInvLine.Type::"Charge (Item)", ItemCharge."No.", 2);
+ ItemChargeInvLine.Validate("Direct Unit Cost", LibraryRandom.RandDecInRange(100, 200, 2));
+ ItemChargeInvLine.Modify(true);
+ EnsureGeneralPostingSetupIsValid(ItemChargeInvLine."Gen. Bus. Posting Group", ItemChargeInvLine."Gen. Prod. Posting Group");
+
+ AssignItemChargeToReceiptLine(ItemChargeInvLine, RegPurchRcptLine, 1);
+ AssignItemChargeToReceiptLine(ItemChargeInvLine, SubcPurchRcptLine, 1);
+
+ // [GIVEN] The invoice is posted
+ PostedInvoiceNo := LibraryPurchase.PostPurchaseDocument(ItemChargeInvHeader, false, true);
+ PurchInvHeader.Get(PostedInvoiceNo);
+ Commit();
+
+ // [WHEN] The user tries to cancel the posted invoice
+ asserterror CorrectPostedPurchInvoice.CancelPostedInvoice(PurchInvHeader);
+
+ // [THEN] The Subcontracting App blocks the cancel with the dedicated error
+ Assert.ExpectedError('contains item charges assigned to a subcontracting order receipt');
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Subcontracting Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcontractingMgmtLibrary.UpdateSubMgmtSetup_ComponentAtLocation("Components at Location"::Purchase);
+ UpdateSubMgmtSetupWithReqWkshTemplate();
+ LibraryVariableStorage.Clear();
+
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Subcontracting Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ LibraryERMCountryData.UpdateGeneralPostingSetup();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Subcontracting Test");
+ end;
+
+ local procedure UpdateSubMgmtSetupWithReqWkshTemplate()
+ begin
+ LibraryMfgManagement.CreateSubcontractingReqWkshTemplateAndNameAndUpdateSetup();
+ end;
+
+ local procedure UpdateSubMgmtSetupTransferInfoLine(Update: Boolean)
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ ManufacturingSetup."Create Prod. Order Info Line" := Update;
+ ManufacturingSetup.Modify();
+ end;
+
+ local procedure UpdateSubWhseHandlingTimeInSubManagementSetup()
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ Evaluate(ManufacturingSetup."Subc. Comp. Transfer Lead Time", '<1D>');
+ ManufacturingSetup.Modify();
+ end;
+
+ local procedure MockReservationEntryOnTransferLine(TransferLine: Record "Transfer Line"; ProdOrderComponent: Record "Prod. Order Component")
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ ReservationEntry.DeleteAll();
+ ReservationEntry.Init();
+ ReservationEntry."Source Type" := Database::"Transfer Line";
+ ReservationEntry."Source ID" := TransferLine."Document No.";
+ ReservationEntry."Source Ref. No." := TransferLine."Line No.";
+ ReservationEntry."Item No." := ProdOrderComponent."Item No.";
+ ReservationEntry."Variant Code" := ProdOrderComponent."Variant Code";
+ ReservationEntry.Insert();
+ end;
+
+ procedure CreateInventory(Item: Record Item; Location: Record Location; Bin: Record Bin; Quantity: Decimal)
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ begin
+ LibraryInventory.CreateItemJournalLineInItemTemplate(
+ ItemJournalLine, Item."No.", Location.Code, Bin.Code, Quantity);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+
+ procedure SelectRequisitionTemplateName(): Code[10]
+ var
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ LibraryUtility: Codeunit "Library - Utility";
+ begin
+ ReqWkshTemplate.SetRange(Type, ReqWkshTemplate.Type::Subcontracting);
+ ReqWkshTemplate.SetRange(Recurring, false);
+ if not ReqWkshTemplate.FindFirst() then begin
+ ReqWkshTemplate.Init();
+ ReqWkshTemplate.Validate(
+ Name, LibraryUtility.GenerateRandomCode(ReqWkshTemplate.FieldNo(Name), Database::"Req. Wksh. Template"));
+ ReqWkshTemplate.Insert(true);
+ ReqWkshTemplate.Validate(Type, ReqWkshTemplate.Type::Subcontracting);
+ ReqWkshTemplate."Page ID" := Page::"Subc. Subcontracting Worksheet";
+ ReqWkshTemplate.Modify(true);
+ end;
+ exit(ReqWkshTemplate.Name);
+ end;
+
+ local procedure AssignItemChargeToReceiptLine(ItemChargeInvLine: Record "Purchase Line"; PurchRcptLine: Record "Purch. Rcpt. Line"; QtyToAssign: Decimal)
+ var
+ ItemChargeAssignmentPurch: Record "Item Charge Assignment (Purch)";
+ begin
+ LibraryInventory.CreateItemChargeAssignPurchase(
+ ItemChargeAssignmentPurch, ItemChargeInvLine,
+ ItemChargeAssignmentPurch."Applies-to Doc. Type"::Receipt,
+ PurchRcptLine."Document No.", PurchRcptLine."Line No.", PurchRcptLine."No.");
+ ItemChargeAssignmentPurch.Validate("Qty. to Assign", QtyToAssign);
+ ItemChargeAssignmentPurch.Modify(true);
+ end;
+
+ procedure UpdateProdOrderComponentWithComponentSupplyMethod(ProductionOrder: Record "Production Order"; ComponentSupplyMethod: Enum "Component Supply Method")
+ var
+ ProdOrderComp: Record "Prod. Order Component";
+ begin
+ ProdOrderComp.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderComp.ModifyAll("Component Supply Method", ComponentSupplyMethod);
+ end;
+
+ var
+ 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";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySales: Codeunit "Library - Sales";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ LibraryVariableStorage: Codeunit "Library - Variable Storage";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ IsInitialized: Boolean;
+ Subcontracting: Boolean;
+ OpenedTransferOrderNo: Code[20];
+ OpenedTransferOrderListNo: Code[20];
+ PurchaseOrderPageOpened: Boolean;
+ PurchaseLinesPageOpened: Boolean;
+ UnitCostCalculation: Option Time,Units;
+ ConfirmDialogCalledCount: Integer;
+ AlreadySpecifiedErr: Label 'You cannot open Tracking Specification because this component is already specified in Transfer Order %1.', Comment = '|%1 = Transfer Order No.';
+ PurchOrderRoutingErr: Label 'Purchase Order %1 should contain a line tied to Routing Reference No. %2', Comment = '%1 = Purchase Order No., %2 = Routing Reference No.';
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingUITest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingUITest.Codeunit.al
new file mode 100644
index 0000000000..09674db98a
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcSubcontractingUITest.Codeunit.al
@@ -0,0 +1,650 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Ledger;
+using Microsoft.Inventory.Planning;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Manufacturing.Capacity;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.History;
+using Microsoft.Purchases.Vendor;
+using System.Reflection;
+
+codeunit 139990 "Subc. Subcontracting UI Test"
+{
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ // [FEATURE] Subcontracting Management
+ IsInitialized := false;
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. Subcontracting UI Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. Subcontracting UI Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. Subcontracting UI Test");
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PagePurchaseOrderSubContractingLocationCode()
+ var
+ PageControl: Record "Page Control Field";
+ PurchHeader: Record "Purchase Header";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Purchase Header"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Purchase Header");
+ PageControl.SetRange(PageNo, Page::"Purchase Order");
+ PageControl.SetRange(FieldNo, PurchHeader.FieldNo("Subc. Location Code"));
+ ControlExist := not PageControl.IsEmpty();
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, PurchHeader.FieldCaption("Subc. Location Code")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PagePurchaseOrderSubContractingOrder()
+ var
+ PageControl: Record "Page Control Field";
+ PurchHeader: Record "Purchase Header";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Purchase Header"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Purchase Header");
+ PageControl.SetRange(PageNo, Page::"Purchase Order");
+ PageControl.SetRange(FieldNo, PurchHeader.FieldNo("Subc. Order"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, PurchHeader.FieldCaption("Subc. Order")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageVendorCardSubContractingLocationCode()
+ var
+ PageControl: Record "Page Control Field";
+ Vendor: Record Vendor;
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Vendor Card"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::Vendor);
+ PageControl.SetRange(PageNo, Page::"Vendor Card");
+ PageControl.SetRange(FieldNo, Vendor.FieldNo("Subc. Location Code"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, Vendor.FieldCaption("Subc. Location Code")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageVendorCardLinkedToWorkCenter()
+ var
+ PageControl: Record "Page Control Field";
+ Vendor: Record Vendor;
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Vendor Card"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::Vendor);
+ PageControl.SetRange(PageNo, Page::"Vendor Card");
+ PageControl.SetRange(FieldNo, Vendor.FieldNo("Subc. Linked to Work Center"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, Vendor.FieldCaption("Subc. Linked to Work Center")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageSubcontractingWorksheetStandardTaskCode()
+ var
+ PageControl: Record "Page Control Field";
+ ReqLine: Record "Requisition Line";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Subcontracting Worksheet"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Requisition Line");
+ PageControl.SetRange(PageNo, Page::"Subc. Subcontracting Worksheet");
+ PageControl.SetRange(FieldNo, ReqLine.FieldNo("Subc. Standard Task Code"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, ReqLine.FieldCaption("Subc. Standard Task Code")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageProductionBOMLinesComponentSupplyMethod()
+ var
+ PageControl: Record "Page Control Field";
+ ProdBOMLine: Record "Production BOM Line";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Production BOM Lines"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Production BOM Line");
+ PageControl.SetRange(PageNo, Page::"Production BOM Lines");
+ PageControl.SetRange(FieldNo, ProdBOMLine.FieldNo("Component Supply Method"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, ProdBOMLine.FieldCaption("Component Supply Method")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageProductionBOMVersionLinesComponentSupplyMethod()
+ var
+ PageControl: Record "Page Control Field";
+ ProdBOMLine: Record "Production BOM Line";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Production BOM Version Lines"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Production BOM Line");
+ PageControl.SetRange(PageNo, Page::"Production BOM Version Lines");
+ PageControl.SetRange(FieldNo, ProdBOMLine.FieldNo("Component Supply Method"));
+ ControlExist := not PageControl.IsEmpty();
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, ProdBOMLine.FieldCaption("Component Supply Method")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PagePlanningComponentComponentSupplyMethod()
+ var
+ PageControl: Record "Page Control Field";
+ PlanComp: Record "Planning Component";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO] Check if Controls exist on Page "Planning Components"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Planning Component");
+ PageControl.SetRange(PageNo, Page::"Planning Components");
+ PageControl.SetRange(FieldNo, PlanComp.FieldNo("Component Supply Method"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, PlanComp.FieldCaption("Component Supply Method")));
+ end;
+
+ [Test]
+ procedure WorkCenterCardSubcontractingActionsHiddenWhenNotSubcontracting()
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterCard: TestPage "Work Center Card";
+ begin
+ // [SCENARIO 633206] Subcontracting action group is not visible on Work Center Card when Work Center has no Subcontractor No.
+ Initialize();
+
+ // [GIVEN] A Work Center without a Subcontractor No.
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+
+ // [WHEN] The Work Center Card page is opened for the Work Center
+ WorkCenterCard.OpenEdit();
+ WorkCenterCard.GotoRecord(WorkCenter);
+
+ // [THEN] Subcontractor Prices action is not enabled
+ Assert.IsFalse(WorkCenterCard."Subcontractor Prices".Enabled(), SubcontractingActionsVisibleErr);
+ WorkCenterCard.Close();
+ end;
+
+ [Test]
+ procedure WorkCenterCardSubcontractingActionsVisibleWhenSubcontracting()
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterCard: TestPage "Work Center Card";
+ begin
+ // [SCENARIO 633206] Subcontracting action group is visible on Work Center Card when Work Center has a Subcontractor No.
+ Initialize();
+
+ // [GIVEN] A Work Center with a Subcontractor No.
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+ WorkCenter.Modify(true);
+
+ // [WHEN] The Work Center Card page is opened for the Work Center
+ WorkCenterCard.OpenEdit();
+ WorkCenterCard.GotoRecord(WorkCenter);
+
+ // [THEN] Subcontractor Prices action is enabled
+ Assert.IsTrue(WorkCenterCard."Subcontractor Prices".Enabled(), SubcontractingActionsNotVisibleErr);
+ WorkCenterCard.Close();
+ end;
+
+ [Test]
+ procedure WorkCenterCardDispatchListDisabledWhenNotSubcontracting()
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterCard: TestPage "Work Center Card";
+ begin
+ // [SCENARIO 633206] Subcontractor - Dispatch List action is disabled on Work Center Card when Work Center has no Subcontractor No.
+ Initialize();
+
+ // [GIVEN] A Work Center without a Subcontractor No.
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+
+ // [WHEN] The Work Center Card page is opened for the Work Center
+ WorkCenterCard.OpenEdit();
+ WorkCenterCard.GotoRecord(WorkCenter);
+
+ // [THEN] Subcontractor - Dispatch List action is not enabled
+ Assert.IsFalse(WorkCenterCard."Subcontractor - Dispatch List".Enabled(), SubcontractingActionsEnabledErr);
+ WorkCenterCard.Close();
+ end;
+
+ [Test]
+ procedure WorkCenterCardDispatchListEnabledWhenSubcontracting()
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterCard: TestPage "Work Center Card";
+ begin
+ // [SCENARIO 633206] Subcontractor - Dispatch List action is enabled on Work Center Card when Work Center has a Subcontractor No.
+ Initialize();
+
+ // [GIVEN] A Work Center with a Subcontractor No.
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+ WorkCenter.Modify(true);
+
+ // [WHEN] The Work Center Card page is opened for the Work Center
+ WorkCenterCard.OpenEdit();
+ WorkCenterCard.GotoRecord(WorkCenter);
+
+ // [THEN] Subcontractor - Dispatch List action is enabled
+ Assert.IsTrue(WorkCenterCard."Subcontractor - Dispatch List".Enabled(), SubcontractingActionsNotEnabledErr);
+ WorkCenterCard.Close();
+ end;
+
+ [Test]
+ procedure WorkCenterListSubcontractingActionsDisabledWhenNotSubcontracting()
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterList: TestPage "Work Center List";
+ begin
+ // [SCENARIO 633206] Subcontractor Prices action is disabled on Work Center List when Work Center has no Subcontractor No.
+ Initialize();
+
+ // [GIVEN] A Work Center without a Subcontractor No.
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+
+ // [WHEN] The Work Center List page is opened and navigated to the Work Center
+ WorkCenterList.OpenEdit();
+ WorkCenterList.GotoRecord(WorkCenter);
+
+ // [THEN] Subcontractor Prices action is not enabled
+ Assert.IsFalse(WorkCenterList."Subcontractor Prices".Enabled(), SubcontractingActionsEnabledErr);
+ WorkCenterList.Close();
+ end;
+
+ [Test]
+ procedure WorkCenterListSubcontractingActionsEnabledWhenSubcontracting()
+ var
+ WorkCenter: Record "Work Center";
+ WorkCenterList: TestPage "Work Center List";
+ begin
+ // [SCENARIO 633206] Subcontractor Prices action is enabled on Work Center List when Work Center has a Subcontractor No.
+ Initialize();
+
+ // [GIVEN] A Work Center with a Subcontractor No.
+ LibraryMfgManagement.CreateWorkCenterWithCalendar(WorkCenter, 0);
+ WorkCenter.Validate("Subcontractor No.", LibraryMfgManagement.CreateSubcontractorWithCurrency(''));
+ WorkCenter.Modify(true);
+
+ // [WHEN] The Work Center List page is opened and navigated to the Work Center
+ WorkCenterList.OpenEdit();
+ WorkCenterList.GotoRecord(WorkCenter);
+
+ // [THEN] Subcontractor Prices action is enabled
+ Assert.IsTrue(WorkCenterList."Subcontractor Prices".Enabled(), SubcontractingActionsNotEnabledErr);
+ WorkCenterList.Close();
+ end;
+
+ [Test]
+ [HandlerFunctions('HandlePostedPurchaseReceiptPage')]
+ procedure CapLedgerEntriesShowDocumentOpensPostedReceipt()
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ PurchRcptHeader: Record "Purch. Rcpt. Header";
+ CapacityLedgerEntries: TestPage "Capacity Ledger Entries";
+ begin
+ // [SCENARIO 620656] Show Document action opens Posted Purchase Receipt when Document No. matches a receipt
+ Initialize();
+
+ // [GIVEN] A Posted Purchase Receipt
+ PurchRcptHeader.Init();
+ PurchRcptHeader."No." := 'TEST-RCPT-001';
+ if not PurchRcptHeader.Insert() then
+ PurchRcptHeader.Modify();
+
+ // [GIVEN] A Capacity Ledger Entry with Document No. pointing to the receipt
+ CapacityLedgerEntry.Init();
+ CapacityLedgerEntry."Entry No." := GetNextCapLedgerEntryNo();
+ CapacityLedgerEntry."Document No." := PurchRcptHeader."No.";
+ CapacityLedgerEntry.Insert();
+
+ // [WHEN] The Show Document action is invoked
+ CapacityLedgerEntries.OpenView();
+ CapacityLedgerEntries.GoToRecord(CapacityLedgerEntry);
+ CapacityLedgerEntries.ShowDocument.Invoke();
+
+ // [THEN] The Posted Purchase Receipt page is opened (verified by PageHandler)
+ CapacityLedgerEntries.Close();
+ end;
+
+ [Test]
+ [HandlerFunctions('HandlePostedPurchaseInvoicePage')]
+ procedure CapLedgerEntriesShowDocumentOpensPostedInvoice()
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ PurchInvHeader: Record "Purch. Inv. Header";
+ CapacityLedgerEntries: TestPage "Capacity Ledger Entries";
+ begin
+ // [SCENARIO 620656] Show Document action opens Posted Purchase Invoice when Document No. matches an invoice
+ Initialize();
+
+ // [GIVEN] A Posted Purchase Invoice (no matching receipt)
+ PurchInvHeader.Init();
+ PurchInvHeader."No." := 'TEST-INV-001';
+ if not PurchInvHeader.Insert() then
+ PurchInvHeader.Modify();
+
+ // [GIVEN] A Capacity Ledger Entry with Document No. pointing to the invoice
+ CapacityLedgerEntry.Init();
+ CapacityLedgerEntry."Entry No." := GetNextCapLedgerEntryNo();
+ CapacityLedgerEntry."Document No." := PurchInvHeader."No.";
+ CapacityLedgerEntry.Insert();
+
+ // [WHEN] The Show Document action is invoked
+ CapacityLedgerEntries.OpenView();
+ CapacityLedgerEntries.GoToRecord(CapacityLedgerEntry);
+ CapacityLedgerEntries.ShowDocument.Invoke();
+
+ // [THEN] The Posted Purchase Invoice page is opened (verified by PageHandler)
+ CapacityLedgerEntries.Close();
+
+ // Cleanup
+ CapacityLedgerEntry.Delete();
+ PurchInvHeader.Delete();
+ end;
+
+ [Test]
+ [HandlerFunctions('HandlePurchaseOrderPage')]
+ procedure CapLedgerEntriesShowDocumentOpensPurchaseOrder()
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ PurchaseHeader: Record "Purchase Header";
+ CapacityLedgerEntries: TestPage "Capacity Ledger Entries";
+ begin
+ // [SCENARIO 620656] Show Document action opens Purchase Order when no posted document exists
+ Initialize();
+
+ // [GIVEN] A Purchase Order
+ PurchaseHeader.Init();
+ PurchaseHeader."Document Type" := PurchaseHeader."Document Type"::Order;
+ PurchaseHeader."No." := 'TEST-PO-001';
+ if not PurchaseHeader.Insert() then
+ PurchaseHeader.Modify();
+
+ // [GIVEN] A Capacity Ledger Entry with Subc. Purch. Order No. but no matching posted document
+ CapacityLedgerEntry.Init();
+ CapacityLedgerEntry."Entry No." := GetNextCapLedgerEntryNo();
+ CapacityLedgerEntry."Document No." := '';
+ CapacityLedgerEntry."Subc. Purch. Order No." := PurchaseHeader."No.";
+ CapacityLedgerEntry.Insert();
+
+ // [WHEN] The Show Document action is invoked
+ CapacityLedgerEntries.OpenView();
+ CapacityLedgerEntries.GoToRecord(CapacityLedgerEntry);
+ CapacityLedgerEntries.ShowDocument.Invoke();
+
+ // [THEN] The Purchase Order page is opened (verified by PageHandler)
+ CapacityLedgerEntries.Close();
+
+ // Cleanup
+ CapacityLedgerEntry.Delete();
+ PurchaseHeader.Delete();
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageRoutingVersionLinesTransferWIPItem()
+ var
+ PageControl: Record "Page Control Field";
+ RoutingLine: Record "Routing Line";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO 638530] Check if Transfer WIP Item control exists on Page "Routing Version Lines"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Routing Line");
+ PageControl.SetRange(PageNo, Page::"Routing Version Lines");
+ PageControl.SetRange(FieldNo, RoutingLine.FieldNo("Transfer WIP Item"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, RoutingLine.FieldCaption("Transfer WIP Item")));
+ end;
+
+ [Test]
+ procedure CheckCustCtrl_PageRoutingVersionLinesTransferDescription()
+ var
+ PageControl: Record "Page Control Field";
+ RoutingLine: Record "Routing Line";
+ ControlExist: Boolean;
+ begin
+ // [FEATURE] Subcontracting Management
+ // [SCENARIO 638530] Check if Transfer Description control exists on Page "Routing Version Lines"
+
+ // [GIVEN]
+ Initialize();
+
+ // [WHEN] Find Control on Page
+ PageControl.SetRange(TableNo, Database::"Routing Line");
+ PageControl.SetRange(PageNo, Page::"Routing Version Lines");
+ PageControl.SetRange(FieldNo, RoutingLine.FieldNo("Transfer Description"));
+ ControlExist := not PageControl.IsEmpty();
+
+ // [THEN]
+ Assert.AreEqual(true, ControlExist, StrSubstNo(ControlNotExistMsg, RoutingLine.FieldCaption("Transfer Description")));
+ end;
+
+ local procedure GetNextCapLedgerEntryNo(): Integer
+ var
+ CapacityLedgerEntry: Record "Capacity Ledger Entry";
+ begin
+ if CapacityLedgerEntry.FindLast() then
+ exit(CapacityLedgerEntry."Entry No." + 1);
+ exit(1);
+ end;
+
+ [PageHandler]
+ procedure HandlePostedPurchaseReceiptPage(var PostedPurchaseReceipt: TestPage "Posted Purchase Receipt")
+ begin
+ PostedPurchaseReceipt.Close();
+ end;
+
+ [PageHandler]
+ procedure HandlePostedPurchaseInvoicePage(var PostedPurchaseInvoice: TestPage "Posted Purchase Invoice")
+ begin
+ PostedPurchaseInvoice.Close();
+ end;
+
+ [PageHandler]
+ procedure HandlePurchaseOrderPage(var PurchaseOrder: TestPage "Purchase Order")
+ begin
+ PurchaseOrder.Close();
+ end;
+
+ [Test]
+ procedure ItemLedgerEntriesSubcActionsDisabledWhenNotSubcontracting()
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ItemLedgerEntries: TestPage "Item Ledger Entries";
+ begin
+ // [SCENARIO 638458] Subcontracting actions on Item Ledger Entries are disabled when the entry has no subcontracting production order or purchase order.
+ Initialize();
+
+ // [GIVEN] An Item Ledger Entry that is NOT related to subcontracting (no Subc. Prod. Order No. or Subc. Purch. Order No.)
+ ItemLedgerEntry.Init();
+ ItemLedgerEntry."Entry No." := GetNextItemLedgerEntryNo();
+ ItemLedgerEntry."Item No." := 'TEST-ITEM';
+ ItemLedgerEntry."Entry Type" := ItemLedgerEntry."Entry Type"::Purchase;
+ ItemLedgerEntry."Subc. Prod. Order No." := '';
+ ItemLedgerEntry."Subc. Purch. Order No." := '';
+ ItemLedgerEntry.Insert();
+
+ // [WHEN] The Item Ledger Entries page is opened for that entry
+ ItemLedgerEntries.OpenView();
+ ItemLedgerEntries.GoToRecord(ItemLedgerEntry);
+
+ // [THEN] The Production Order action is disabled
+ Assert.IsFalse(ItemLedgerEntries."Production Order".Enabled(), ILEProdActionsEnabledErr);
+ // [THEN] The Production Order Routing action is disabled
+ Assert.IsFalse(ItemLedgerEntries."Production Order Routing".Enabled(), ILEProdActionsEnabledErr);
+ // [THEN] The Production Order Components action is disabled
+ Assert.IsFalse(ItemLedgerEntries."Production Order Components".Enabled(), ILEProdActionsEnabledErr);
+ // [THEN] The Purchase Order action is disabled
+ Assert.IsFalse(ItemLedgerEntries."Purchase Order".Enabled(), ILEPurchActionsEnabledErr);
+
+ ItemLedgerEntries.Close();
+
+ // Cleanup
+ ItemLedgerEntry.Delete();
+ end;
+
+ [Test]
+ procedure ItemLedgerEntriesSubcActionsEnabledWhenSubcontracting()
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ ItemLedgerEntries: TestPage "Item Ledger Entries";
+ begin
+ // [SCENARIO 638458] Subcontracting actions on Item Ledger Entries are enabled when the entry is related to a subcontracting production order and purchase order.
+ Initialize();
+
+ // [GIVEN] An Item Ledger Entry that IS related to subcontracting
+ ItemLedgerEntry.Init();
+ ItemLedgerEntry."Entry No." := GetNextItemLedgerEntryNo();
+ ItemLedgerEntry."Item No." := 'TEST-ITEM';
+ ItemLedgerEntry."Entry Type" := ItemLedgerEntry."Entry Type"::Purchase;
+ ItemLedgerEntry."Subc. Prod. Order No." := 'PO-SUBC-001';
+ ItemLedgerEntry."Subc. Prod. Order Line No." := 10000;
+ ItemLedgerEntry."Subc. Purch. Order No." := 'PURCH-SUBC-001';
+ ItemLedgerEntry."Subc. Purch. Order Line No." := 10000;
+ ItemLedgerEntry.Insert();
+
+ // [WHEN] The Item Ledger Entries page is opened for that entry
+ ItemLedgerEntries.OpenView();
+ ItemLedgerEntries.GoToRecord(ItemLedgerEntry);
+
+ // [THEN] The Production Order action is enabled
+ Assert.IsTrue(ItemLedgerEntries."Production Order".Enabled(), ILEProdActionsNotEnabledErr);
+ // [THEN] The Production Order Routing action is enabled
+ Assert.IsTrue(ItemLedgerEntries."Production Order Routing".Enabled(), ILEProdActionsNotEnabledErr);
+ // [THEN] The Production Order Components action is enabled
+ Assert.IsTrue(ItemLedgerEntries."Production Order Components".Enabled(), ILEProdActionsNotEnabledErr);
+ // [THEN] The Purchase Order action is enabled
+ Assert.IsTrue(ItemLedgerEntries."Purchase Order".Enabled(), ILEPurchActionsNotEnabledErr);
+
+ ItemLedgerEntries.Close();
+
+ // Cleanup
+ ItemLedgerEntry.Delete();
+ end;
+
+ local procedure GetNextItemLedgerEntryNo(): Integer
+ var
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ begin
+ if ItemLedgerEntry.FindLast() then
+ exit(ItemLedgerEntry."Entry No." + 1);
+ exit(1);
+ end;
+
+ 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";
+ IsInitialized: Boolean;
+ ControlNotExistMsg: Label 'Control %1 does not exist.', Comment = '%1 = field caption';
+ SubcontractingActionsVisibleErr: Label 'Subcontractor Prices action should not be visible for a non-subcontracting Work Center.';
+ SubcontractingActionsEnabledErr: Label 'Subcontractor Prices action should not be enabled for a non-subcontracting Work Center.';
+ SubcontractingActionsNotVisibleErr: Label 'Subcontractor Prices action should be visible for a subcontracting Work Center.';
+ SubcontractingActionsNotEnabledErr: Label 'Subcontractor Prices action should be enabled for a subcontracting Work Center.';
+ ILEProdActionsEnabledErr: Label 'Production actions should not be enabled for a non-subcontracting Item Ledger Entry.';
+ ILEProdActionsNotEnabledErr: Label 'Production actions should be enabled for a subcontracting Item Ledger Entry.';
+ ILEPurchActionsEnabledErr: Label 'Purchase Order action should not be enabled for a non-subcontracting Item Ledger Entry.';
+ ILEPurchActionsNotEnabledErr: Label 'Purchase Order action should be enabled for a subcontracting Item Ledger Entry.';
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcTransOrdReservTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcTransOrdReservTest.Codeunit.al
new file mode 100644
index 0000000000..79a2777217
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcTransOrdReservTest.Codeunit.al
@@ -0,0 +1,732 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Journal;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.ProductionBOM;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using System.TestLibraries.Utilities;
+
+codeunit 149915 "Subc. TransOrd. Reserv. Test"
+{
+ // [FEATURE] Subcontracting Transfer Order Reservation Integration Tests
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ var
+ SerialNoTok: Label 'SN%1', Comment = '%1 = number';
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure FullQuantityTransferTransfersAllReservationsWithoutConfirm()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO] Full quantity transfer moves all component reservations to the transfer order without showing the excess cancellation message.
+
+ // [GIVEN] A transfer-subcontracting production order with a transfer component quantity of 30 and matching component reservations
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 3, false);
+ CreateReservationOnProdOrderComp(ProdOrderComponent, 30, ProdOrderComponent."Location Code");
+
+ // [WHEN] A subcontracting purchase order is created for the full production quantity and a transfer order is created from it
+ CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 0);
+ CreateTransferOrder(PurchaseHeader);
+ FindTransferLine(TransferLine, ProductionOrder, ProdOrderComponent);
+
+ // [THEN] All reservations are transferred to the transfer line and no excess reservation confirm dialog is shown
+ TransferLine.CalcFields("Reserved Qty. Outbnd. (Base)");
+ Assert.AreEqual(30, TransferLine."Quantity (Base)", 'Transfer line base qty mismatch');
+ Assert.AreEqual(30, TransferLine."Reserved Qty. Outbnd. (Base)", 'Reserved qty outbound must match transfer qty');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting')]
+ procedure PartialQuantityWithReservationsBlocksTransferCreation()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO 634236] Creating a transfer order when the transfer quantity is less than the reserved quantity on the component must be blocked with an error.
+
+ // [GIVEN] A released production order with quantity 10, transfer component quantity per 3, and 30 reserved component quantity
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 3, false);
+ CreateReservationOnProdOrderComp(ProdOrderComponent, 30, ProdOrderComponent."Location Code");
+
+ // [WHEN] The subcontracting purchase order quantity is reduced from 10 to 6 before the transfer order is created
+ CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 6);
+ asserterror CreateTransferOrder(PurchaseHeader);
+
+ // [THEN] Transfer creation is blocked and reservations on the component remain intact
+ Assert.ExpectedError('Cancel existing reservations on the component before creating a partial transfer');
+#pragma warning disable AA0210
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ Assert.RecordIsEmpty(TransferLine);
+ Assert.AreEqual(1, CountProdOrderComponentReservations(ProdOrderComponent), 'Reservations on the component must remain intact when transfer is blocked');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting')]
+ procedure PartialQuantitySerialReservationsBlocksTransferCreation()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO 634399] Creating a transfer order with serial-tracked component reservations when the transfer quantity is less than the reserved quantity must be blocked.
+
+ // [GIVEN] A released production order with quantity 3, transfer component quantity per 2, and 6 serial-number component reservations
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 3, 2, true);
+ CreateSerialReservationsOnProdOrderComp(ProdOrderComponent, 6, ProdOrderComponent."Location Code");
+
+ // [WHEN] The subcontracting purchase order quantity is reduced from 3 to 2 before the transfer order is created
+ CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 2);
+ asserterror CreateTransferOrder(PurchaseHeader);
+
+ // [THEN] Transfer creation is blocked and serial reservations on the component remain intact
+ Assert.ExpectedError('Cancel existing reservations on the component before creating a partial transfer');
+#pragma warning disable AA0210
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ Assert.RecordIsEmpty(TransferLine);
+ Assert.AreEqual(6, CountProdOrderComponentReservations(ProdOrderComponent), 'Serial reservations on the component must remain intact when transfer is blocked');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure PartialQuantityWithoutReservationsAllowsTransferCreation()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO] Reducing the subcontracting PO quantity when the component has no reservations must allow transfer creation without error.
+
+ // [GIVEN] A released production order with quantity 10, transfer component quantity per 3, and NO reservations
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 3, false);
+
+ // [WHEN] The subcontracting purchase order quantity is reduced from 10 to 6 and a transfer order is created
+ CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 6);
+ CreateTransferOrder(PurchaseHeader);
+
+ // [THEN] Transfer line is created with the reduced quantity without error
+#pragma warning disable AA0210
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ TransferLine.FindFirst();
+ Assert.AreEqual(18, TransferLine."Quantity (Base)", 'Transfer line base qty should reflect the reduced PO qty');
+ end;
+
+ [Test]
+ procedure ExcessLotQuantityReceiptReservesOnlyComponentNeed()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO 634465] Receiving a transfer back from the subcontractor with a lot quantity that exceeds the component need must reserve only the remaining need and leave the excess as free inventory instead of failing with "Reserved quantity cannot be greater than 0".
+
+ // [GIVEN] A transfer-subcontracting released production order whose lot-tracked component needs 10
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 1, false);
+ EnableLotTrackingOnTransferComponent(Item);
+
+ // [GIVEN] 15 lot-tracked units of the component were received at the component location (5 more than needed)
+ PostComponentInventoryAsLot(ProdOrderComponent."Item No.", ProdOrderComponent."Location Code", 'LOT01', 15);
+ FindPostedComponentItemLedgerEntry(ItemLedgerEntry, ProdOrderComponent."Item No.", ProdOrderComponent."Location Code");
+
+ // [WHEN] The posted transfer receipt line is reserved against the production order component
+ BuildTransferReceiptLineForComponent(TransferReceiptLine, ProdOrderComponent, ItemLedgerEntry);
+ SubcTransferManagement.TransferReservationEntryFromPstTransferLineToProdOrderComp(TransferReceiptLine);
+
+ // [THEN] Only the component need (10) is reserved in a single capped reservation; the 5 excess units stay as free inventory
+ Assert.AreEqual(10, SubcTransferManagement.GetComponentReservedQtyBase(ProdOrderComponent), 'Only the component need must be reserved');
+ Assert.AreEqual(1, CountProdOrderComponentReservations(ProdOrderComponent), 'A single capped lot reservation must be created on the component');
+ end;
+
+ [Test]
+ procedure ExcessSerialQuantityReceiptReservesOnlyComponentNeed()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO 636820] Receiving serial-tracked units in excess of the component need must reserve only the needed serials and skip the excess serials instead of failing with "Reserved quantity cannot be greater than 0".
+
+ // [GIVEN] A transfer-subcontracting released production order whose serial-tracked component needs 3
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 3, 1, true);
+
+ // [GIVEN] 5 serial-tracked units of the component were received at the component location (2 more than needed)
+ PostComponentInventoryAsSerials(ProdOrderComponent."Item No.", ProdOrderComponent."Location Code", 5);
+ FindPostedComponentItemLedgerEntry(ItemLedgerEntry, ProdOrderComponent."Item No.", ProdOrderComponent."Location Code");
+
+ // [WHEN] The posted transfer receipt line is reserved against the production order component
+ BuildTransferReceiptLineForComponent(TransferReceiptLine, ProdOrderComponent, ItemLedgerEntry);
+ SubcTransferManagement.TransferReservationEntryFromPstTransferLineToProdOrderComp(TransferReceiptLine);
+
+ // [THEN] Exactly three serials are reserved and the two excess serials stay as free inventory
+ Assert.AreEqual(3, SubcTransferManagement.GetComponentReservedQtyBase(ProdOrderComponent), 'Only the needed serials must be reserved');
+ Assert.AreEqual(3, CountProdOrderComponentReservations(ProdOrderComponent), 'Exactly three serial reservations must be created on the component');
+ end;
+
+ [Test]
+ procedure ExcessPackageQuantityReceiptReservesOnlyComponentNeed()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO 634465] Package-tracked quantity is divisible like a lot, so receiving more than the component need must reserve only the remaining need and leave the excess as free inventory instead of failing with "Reserved quantity cannot be greater than 0".
+
+ // [GIVEN] A transfer-subcontracting released production order whose package-tracked component needs 10
+ Initialize();
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 1, false);
+ EnablePackageTrackingOnTransferComponent(Item);
+
+ // [GIVEN] 15 package-tracked units of the component were received at the component location (5 more than needed)
+ PostComponentInventoryAsPackage(ProdOrderComponent."Item No.", ProdOrderComponent."Location Code", 'PKG01', 15);
+ FindPostedComponentItemLedgerEntry(ItemLedgerEntry, ProdOrderComponent."Item No.", ProdOrderComponent."Location Code");
+
+ // [WHEN] The posted transfer receipt line is reserved against the production order component
+ BuildTransferReceiptLineForComponent(TransferReceiptLine, ProdOrderComponent, ItemLedgerEntry);
+ SubcTransferManagement.TransferReservationEntryFromPstTransferLineToProdOrderComp(TransferReceiptLine);
+
+ // [THEN] Only the component need (10) is reserved in a single capped reservation; the 5 excess units stay as free inventory
+ Assert.AreEqual(10, SubcTransferManagement.GetComponentReservedQtyBase(ProdOrderComponent), 'Only the component need must be reserved');
+ Assert.AreEqual(1, CountProdOrderComponentReservations(ProdOrderComponent), 'A single capped package reservation must be created on the component');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure CannotModifySubcTransferLineAndHeaderWhenLinkedToProdOrder()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO] Modifying key fields on subcontracting transfer lines and headers must be blocked
+ // when they are linked to a production order.
+ Initialize();
+
+ // [GIVEN] A subcontracting transfer order linked to a production order
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 3, false);
+ CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 0);
+ CreateTransferOrder(PurchaseHeader);
+ FindTransferLine(TransferLine, ProductionOrder, ProdOrderComponent);
+ TransferHeader.Get(TransferLine."Document No.");
+
+ // [THEN] CheckSubcTransferLineCanBeModified blocks modification of Item No.
+ asserterror SubcTransferManagement.CheckSubcTransferLineCanBeModified(TransferLine, TransferLine.FieldCaption("Item No."));
+ Assert.ExpectedError('You cannot change Item No. on the subcontracting transfer line');
+
+ // [THEN] CheckSubcTransferLineCanBeModified blocks modification of Quantity
+ asserterror SubcTransferManagement.CheckSubcTransferLineCanBeModified(TransferLine, TransferLine.FieldCaption(Quantity));
+ Assert.ExpectedError('You cannot change Quantity on the subcontracting transfer line');
+
+ // [THEN] CheckSubcTransferLineCanBeModified blocks modification of Variant Code
+ asserterror SubcTransferManagement.CheckSubcTransferLineCanBeModified(TransferLine, TransferLine.FieldCaption("Variant Code"));
+ Assert.ExpectedError('You cannot change Variant Code on the subcontracting transfer line');
+
+ // [THEN] CheckSubcTransferHeaderCanBeModified blocks modification of Transfer-from Code
+ asserterror SubcTransferManagement.CheckSubcTransferHeaderCanBeModified(TransferHeader, TransferHeader.FieldCaption("Transfer-from Code"));
+ Assert.ExpectedError('You cannot change Transfer-from Code on the subcontracting transfer order');
+
+ // [THEN] CheckSubcTransferHeaderCanBeModified blocks modification of Transfer-to Code
+ asserterror SubcTransferManagement.CheckSubcTransferHeaderCanBeModified(TransferHeader, TransferHeader.FieldCaption("Transfer-to Code"));
+ Assert.ExpectedError('You cannot change Transfer-to Code on the subcontracting transfer order');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure DeleteTransferOrderAndRecreateSucceeds()
+ var
+ Item: Record Item;
+ ProdOrderComponent: Record "Prod. Order Component";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ MachineCenter: array[2] of Record "Machine Center";
+ begin
+ // [SCENARIO] After creating a subcontracting transfer order and deleting it,
+ // the user must be able to re-create it from the same purchase order.
+ Initialize();
+
+ // [GIVEN] A subcontracting transfer order created from a purchase order
+ SetupTransferReservationScenario(Item, WorkCenter, MachineCenter, ProductionOrder, ProdOrderComponent, 10, 3, false);
+ CreateSubcontractingPurchaseOrderAndReduceQuantity(Item, WorkCenter[2], ProductionOrder, PurchaseHeader, PurchaseLine, 0);
+ CreateTransferOrder(PurchaseHeader);
+ FindTransferLine(TransferLine, ProductionOrder, ProdOrderComponent);
+
+ // [WHEN] The transfer order is deleted
+ TransferHeader.Get(TransferLine."Document No.");
+ TransferHeader.Delete(true);
+
+ // [VERIFY] Transfer line no longer exists
+#pragma warning disable AA0210
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ Assert.RecordIsEmpty(TransferLine);
+
+ // [WHEN] The transfer order is created again from the same purchase order
+ CreateTransferOrder(PurchaseHeader);
+
+ // [THEN] A new transfer line is created successfully
+ FindTransferLine(TransferLine, ProductionOrder, ProdOrderComponent);
+ 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");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcontractingMgmtLibrary.UpdateSubMgmtSetup_ComponentAtLocation("Components at Location"::Purchase);
+ LibraryVariableStorage.Clear();
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. TransOrd. Reserv. Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. TransOrd. Reserv. Test");
+ end;
+
+ local procedure SetupTransferReservationScenario(var Item: Record Item; var WorkCenter: array[2] of Record "Work Center"; var MachineCenter: array[2] of Record "Machine Center"; var ProductionOrder: Record "Production Order"; var ProdOrderComponent: Record "Prod. Order Component"; ProductionQty: Decimal; ComponentQtyPer: Decimal; SerialTrackedComponent: Boolean)
+ begin
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ UpdateTransferComponentQuantityPer(Item, ComponentQtyPer);
+ if SerialTrackedComponent then
+ EnableSerialTrackingOnTransferComponent(Item);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released, ProductionOrder."Source Type"::Item, Item."No.", ProductionQty);
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+ FindTransferProdOrderComponent(ProdOrderComponent, ProductionOrder);
+ end;
+
+ local procedure UpdateTransferComponentQuantityPer(Item: Record Item; ComponentQtyPer: Decimal)
+ var
+ ProductionBOMHeader: Record "Production BOM Header";
+ ProductionBOMLine: Record "Production BOM Line";
+ begin
+ ProductionBOMHeader.Get(Item."Production BOM No.");
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::New);
+ ProductionBOMHeader.Modify(true);
+
+ ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No.");
+ ProductionBOMLine.FindLast();
+ ProductionBOMLine.Validate("Quantity per", ComponentQtyPer);
+ ProductionBOMLine.Modify(true);
+
+ ProductionBOMHeader.Validate(Status, ProductionBOMHeader.Status::Certified);
+ ProductionBOMHeader.Modify(true);
+ end;
+
+ local procedure EnableSerialTrackingOnTransferComponent(Item: Record Item)
+ var
+ ComponentItem: Record Item;
+ ProductionBOMLine: Record "Production BOM Line";
+ ItemTrackingCode: Record "Item Tracking Code";
+ SerialNoSeries: Record "No. Series";
+ SerialNoSeriesLine: Record "No. Series Line";
+ begin
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+ ProductionBOMLine.FindLast();
+ ComponentItem.Get(ProductionBOMLine."No.");
+
+ 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);
+
+ ComponentItem.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ ComponentItem.Validate("Serial Nos.", SerialNoSeries.Code);
+ ComponentItem.Modify(true);
+ end;
+
+ local procedure EnableLotTrackingOnTransferComponent(Item: Record Item)
+ var
+ ComponentItem: Record Item;
+ ProductionBOMLine: Record "Production BOM Line";
+ ItemTrackingCode: Record "Item Tracking Code";
+ LotNoSeries: Record "No. Series";
+ LotNoSeriesLine: Record "No. Series Line";
+ begin
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+ ProductionBOMLine.FindLast();
+ ComponentItem.Get(ProductionBOMLine."No.");
+
+ LibraryUtility.CreateNoSeries(LotNoSeries, true, true, false);
+ LibraryUtility.CreateNoSeriesLine(LotNoSeriesLine, LotNoSeries.Code, 'L0000000001', 'L0000000999');
+ LibraryItemTracking.CreateItemTrackingCode(ItemTrackingCode, false, true, false);
+
+ ComponentItem.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ ComponentItem.Validate("Lot Nos.", LotNoSeries.Code);
+ ComponentItem.Modify(true);
+ end;
+
+ local procedure PostComponentInventoryAsLot(ItemNo: Code[20]; LocationCode: Code[10]; LotNo: Code[50]; Qty: Decimal)
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ LibraryInventory.CreateItemJournalLineInItemTemplate(ItemJournalLine, ItemNo, LocationCode, '', Qty);
+ LibraryItemTracking.CreateItemJournalLineItemTracking(ReservationEntry, ItemJournalLine, '', LotNo, '', Qty);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+
+ local procedure EnablePackageTrackingOnTransferComponent(Item: Record Item)
+ var
+ ComponentItem: Record Item;
+ ProductionBOMLine: Record "Production BOM Line";
+ ItemTrackingCode: Record "Item Tracking Code";
+ begin
+ ProductionBOMLine.SetRange("Production BOM No.", Item."Production BOM No.");
+ ProductionBOMLine.FindLast();
+ ComponentItem.Get(ProductionBOMLine."No.");
+
+ LibraryItemTracking.CreateItemTrackingCode(ItemTrackingCode, false, false, true);
+
+ ComponentItem.Validate("Item Tracking Code", ItemTrackingCode.Code);
+ ComponentItem.Modify(true);
+ end;
+
+ local procedure PostComponentInventoryAsPackage(ItemNo: Code[20]; LocationCode: Code[10]; PackageNo: Code[50]; Qty: Decimal)
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ LibraryInventory.CreateItemJournalLineInItemTemplate(ItemJournalLine, ItemNo, LocationCode, '', Qty);
+ LibraryItemTracking.CreateItemJournalLineItemTracking(ReservationEntry, ItemJournalLine, '', '', PackageNo, Qty);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+
+ local procedure PostComponentInventoryAsSerials(ItemNo: Code[20]; LocationCode: Code[10]; Qty: Integer)
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ ReservationEntry: Record "Reservation Entry";
+ i: Integer;
+ begin
+ // All serials are posted from a single journal line so the resulting item ledger entries
+ // share the same Document No. and Document Line No. (as a real transfer receipt would).
+ LibraryInventory.CreateItemJournalLineInItemTemplate(ItemJournalLine, ItemNo, LocationCode, '', Qty);
+ for i := 1 to Qty do
+ LibraryItemTracking.CreateItemJournalLineItemTracking(
+ ReservationEntry, ItemJournalLine, CopyStr(StrSubstNo(SerialNoTok, i), 1, 50), '', '', 1);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+
+ local procedure FindPostedComponentItemLedgerEntry(var ItemLedgerEntry: Record "Item Ledger Entry"; ItemNo: Code[20]; LocationCode: Code[10])
+ begin
+ ItemLedgerEntry.SetRange("Item No.", ItemNo);
+ ItemLedgerEntry.SetRange("Location Code", LocationCode);
+ ItemLedgerEntry.SetRange(Positive, true);
+ ItemLedgerEntry.FindFirst();
+ end;
+
+ local procedure BuildTransferReceiptLineForComponent(var TransferReceiptLine: Record "Transfer Receipt Line"; ProdOrderComponent: Record "Prod. Order Component"; ItemLedgerEntry: Record "Item Ledger Entry")
+ begin
+ // The reservation procedure only reads the transfer receipt line, so an in-memory record is sufficient.
+ TransferReceiptLine.Init();
+ TransferReceiptLine."Document No." := ItemLedgerEntry."Document No.";
+ TransferReceiptLine."Line No." := ItemLedgerEntry."Document Line No.";
+ TransferReceiptLine."Item No." := ProdOrderComponent."Item No.";
+ TransferReceiptLine."Transfer-to Code" := ProdOrderComponent."Location Code";
+ TransferReceiptLine."Subc. Prod. Order No." := ProdOrderComponent."Prod. Order No.";
+ TransferReceiptLine."Subc. Prod. Order Line No." := ProdOrderComponent."Prod. Order Line No.";
+ TransferReceiptLine."Subc. Prod. Ord. Comp Line No." := ProdOrderComponent."Line No.";
+ TransferReceiptLine."Subc. Operation No." := '10';
+ end;
+
+ local procedure CreateSubcontractingPurchaseOrderAndReduceQuantity(Item: Record Item; WorkCenter: Record "Work Center"; ProductionOrder: Record "Production Order"; var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; ReducedQty: Decimal)
+ begin
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter."No.");
+
+ PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order);
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ if ReducedQty <> 0 then begin
+ PurchaseLine.Validate(Quantity, ReducedQty);
+ PurchaseLine.Modify(true);
+ end;
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ end;
+
+ local procedure CreateTransferOrder(PurchaseHeader: Record "Purchase Header")
+ var
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+ PurchaseHeaderPage.Close();
+ end;
+
+ local procedure FindTransferProdOrderComponent(var ProdOrderComponent: Record "Prod. Order Component"; ProductionOrder: Record "Production Order")
+ begin
+ ProdOrderComponent.SetRange(Status, ProductionOrder.Status);
+ ProdOrderComponent.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ ProdOrderComponent.SetRange("Component Supply Method", ProdOrderComponent."Component Supply Method"::"Transfer to Vendor");
+#pragma warning restore AA0210
+ ProdOrderComponent.FindFirst();
+ end;
+
+ local procedure FindTransferLine(var TransferLine: Record "Transfer Line"; ProductionOrder: Record "Production Order"; ProdOrderComponent: Record "Prod. Order Component")
+ begin
+#pragma warning disable AA0210
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ TransferLine.SetRange("Item No.", ProdOrderComponent."Item No.");
+ TransferLine.SetRange("Subc. Prod. Ord. Comp Line No.", ProdOrderComponent."Line No.");
+ TransferLine.FindFirst();
+ end;
+
+ local procedure CreateReservationOnProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"; Qty: Decimal; LocationCode: Code[10])
+ var
+ ReservationEntry: Record "Reservation Entry";
+ EntryNo: Integer;
+ begin
+ if ReservationEntry.FindLast() then
+ EntryNo := ReservationEntry."Entry No." + 1
+ else
+ EntryNo := 1;
+
+ ReservationEntry.Init();
+ ReservationEntry."Entry No." := EntryNo;
+ ReservationEntry.Positive := false;
+ ReservationEntry."Item No." := ProdOrderComponent."Item No.";
+ ReservationEntry."Location Code" := LocationCode;
+ ReservationEntry."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+ ReservationEntry."Reservation Status" := ReservationEntry."Reservation Status"::Reservation;
+ ReservationEntry."Source Type" := Database::"Prod. Order Component";
+ ReservationEntry."Source Subtype" := ProdOrderComponent.Status.AsInteger();
+ ReservationEntry."Source ID" := ProdOrderComponent."Prod. Order No.";
+ ReservationEntry."Source Prod. Order Line" := ProdOrderComponent."Prod. Order Line No.";
+ ReservationEntry."Source Ref. No." := ProdOrderComponent."Line No.";
+ ReservationEntry.Quantity := -Qty;
+ ReservationEntry."Quantity (Base)" := -Qty;
+ ReservationEntry.Insert();
+ end;
+
+ local procedure CreateSerialReservationsOnProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"; Qty: Integer; LocationCode: Code[10])
+ var
+ i: Integer;
+ begin
+ for i := 1 to Qty do
+ CreateSerialReservationOnProdOrderComp(ProdOrderComponent, LocationCode, CopyStr(StrSubstNo(SerialNoTok, i), 1, 50));
+ end;
+
+ local procedure CreateSerialReservationOnProdOrderComp(ProdOrderComponent: Record "Prod. Order Component"; LocationCode: Code[10]; SerialNo: Code[50])
+ var
+ ReservationEntry: Record "Reservation Entry";
+ EntryNo: Integer;
+ begin
+ if ReservationEntry.FindLast() then
+ EntryNo := ReservationEntry."Entry No." + 1
+ else
+ EntryNo := 1;
+
+ ReservationEntry.Init();
+ ReservationEntry."Entry No." := EntryNo;
+ ReservationEntry.Positive := false;
+ ReservationEntry."Item No." := ProdOrderComponent."Item No.";
+ ReservationEntry."Location Code" := LocationCode;
+ ReservationEntry."Qty. per Unit of Measure" := ProdOrderComponent."Qty. per Unit of Measure";
+ ReservationEntry."Reservation Status" := ReservationEntry."Reservation Status"::Reservation;
+ ReservationEntry."Serial No." := SerialNo;
+ ReservationEntry."Source Type" := Database::"Prod. Order Component";
+ ReservationEntry."Source Subtype" := ProdOrderComponent.Status.AsInteger();
+ ReservationEntry."Source ID" := ProdOrderComponent."Prod. Order No.";
+ ReservationEntry."Source Prod. Order Line" := ProdOrderComponent."Prod. Order Line No.";
+ ReservationEntry."Source Ref. No." := ProdOrderComponent."Line No.";
+ ReservationEntry.Quantity := -1;
+ ReservationEntry."Quantity (Base)" := -1;
+ ReservationEntry.Insert();
+ end;
+
+ local procedure CountProdOrderComponentReservations(ProdOrderComponent: Record "Prod. Order Component"): Integer
+ var
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ ReservationEntry.SetRange("Reservation Status", ReservationEntry."Reservation Status"::Reservation);
+ ReservationEntry.SetRange("Source Type", Database::"Prod. Order Component");
+ ReservationEntry.SetRange("Source Subtype", ProdOrderComponent.Status.AsInteger());
+ ReservationEntry.SetRange("Source ID", ProdOrderComponent."Prod. Order No.");
+ ReservationEntry.SetRange("Source Prod. Order Line", ProdOrderComponent."Prod. Order Line No.");
+ ReservationEntry.SetRange("Source Ref. No.", ProdOrderComponent."Line No.");
+ exit(ReservationEntry.Count());
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ OpenedTransferOrderNo := CopyStr(TransfOrderPage."No.".Value(), 1, MaxStrLen(OpenedTransferOrderNo));
+ TransfOrderPage.OK().Invoke();
+ end;
+
+ [ConfirmHandler]
+ procedure DoNotConfirmShowCreatedPurchOrderForSubcontracting(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Reply := false;
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryItemTracking: Codeunit "Library - Item Tracking";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ LibraryUtility: Codeunit "Library - Utility";
+ LibraryVariableStorage: Codeunit "Library - Variable Storage";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubcTransferManagement: Codeunit "Subc. Transfer Management";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+ OpenedTransferOrderNo: Code[20];
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPAdjustmentTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPAdjustmentTest.Codeunit.al
new file mode 100644
index 0000000000..8e9c8c8827
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPAdjustmentTest.Codeunit.al
@@ -0,0 +1,457 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Subcontracting;
+
+codeunit 149914 "Subc. WIP Adjustment Test"
+{
+ // [FEATURE] WIP Adjustment for Subcontracting
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_PositiveAdjustment_CreatesCorrectEntry()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ InitialQty, NewQty : Decimal;
+ begin
+ // [SCENARIO] Entering a higher new quantity on the WIP Adjustment page creates a positive adjustment entry
+ Initialize();
+
+ // [GIVEN] A WIP ledger entry exists for a production order with an initial quantity of 5
+ InitialQty := 5;
+ CreateTestWIPSetup(WIPLedgerEntry, InitialQty, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will set the new quantity to 8 with a document reference and description
+ NewQty := 8;
+ SetHandlerValues(NewQty, 'ADJ-001', 'Positive Adjustment Test', 'Detail Info');
+
+ // [WHEN] The WIP Adjustment page is opened and the new quantity of 8 is confirmed
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.RunModal();
+
+ // [THEN] The sum of all WIP ledger quantities for the production order equals the new target quantity (8)
+ AssertWIPQuantitySum(ProdOrderNo, NewQty);
+
+ // [THEN] A positive adjustment entry is created with the correct adjustment quantity (+3) and all header fields set
+ AssertAdjustmentEntry(
+ ProdOrderNo, NewQty - InitialQty,
+ "WIP Ledger Entry Type"::"Positive Adjustment",
+ 'ADJ-001', 'Positive Adjustment Test', 'Detail Info');
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_NegativeAdjustment_CreatesCorrectEntry()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ InitialQty, NewQty : Decimal;
+ begin
+ // [SCENARIO] Entering a lower new quantity on the WIP Adjustment page creates a negative adjustment entry
+ Initialize();
+
+ // [GIVEN] A WIP ledger entry exists for a production order with an initial quantity of 8
+ InitialQty := 8;
+ CreateTestWIPSetup(WIPLedgerEntry, InitialQty, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will set the new quantity to 3 (lower than current)
+ NewQty := 3;
+ SetHandlerValues(NewQty, 'ADJ-002', 'Negative Adjustment Test', '');
+
+ // [WHEN] The WIP Adjustment page is opened and the new quantity of 3 is confirmed
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.RunModal();
+
+ // [THEN] The sum of all WIP ledger quantities for the production order equals the new target quantity (3)
+ AssertWIPQuantitySum(ProdOrderNo, NewQty);
+
+ // [THEN] A negative adjustment entry is created with the correct adjustment quantity (-5) and entry type
+ AssertAdjustmentEntry(
+ ProdOrderNo, NewQty - InitialQty,
+ "WIP Ledger Entry Type"::"Negative Adjustment",
+ 'ADJ-002', 'Negative Adjustment Test', '');
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_TwoEntriesSameKey_AggregatedBeforeAdjustment()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ Qty1, Qty2, AggregatedQty, NewQty : Decimal;
+ begin
+ // [SCENARIO] Two WIP ledger entries sharing the same routing key are aggregated on the WIP Adjustment
+ // page into a single line, and the resulting adjustment entry reflects the difference from the
+ // aggregated current quantity to the new target quantity
+ Initialize();
+
+ // [GIVEN] Two WIP ledger entries with the same production order and routing key, quantities 3 and 4
+ Qty1 := 3;
+ Qty2 := 4;
+ AggregatedQty := Qty1 + Qty2;
+ CreateTwoWIPEntriesSameKey(WIPLedgerEntry, Qty1, Qty2, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will set the new quantity to 10 (adjustment delta = 3 from aggregated 7)
+ NewQty := 10;
+ SetHandlerValues(NewQty, 'ADJ-003', 'Aggregation Test', '');
+
+ // [WHEN] The WIP Adjustment page aggregates the two entries to a single line and the new quantity 10 is confirmed
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.RunModal();
+
+ // [THEN] The sum of all WIP ledger quantities for the production order equals the new target quantity (10)
+ AssertWIPQuantitySum(ProdOrderNo, NewQty);
+
+ // [THEN] A single positive adjustment entry is created with the correct adjustment quantity (+3)
+ AssertAdjustmentEntry(
+ ProdOrderNo, NewQty - AggregatedQty,
+ "WIP Ledger Entry Type"::"Positive Adjustment",
+ 'ADJ-003', 'Aggregation Test', '');
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_NegativeQuantity_RaisesError()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ begin
+ // [SCENARIO] Entering a negative new quantity on the WIP Adjustment page raises an error
+ Initialize();
+
+ // [GIVEN] A WIP ledger entry exists for a production order with an initial quantity of 10
+ CreateTestWIPSetup(WIPLedgerEntry, 10, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will attempt to set the new quantity to -50
+ SetHandlerValues(-50, '', '', '');
+
+ // [WHEN] The WIP Adjustment page is opened and a negative quantity is entered
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+
+ // [THEN] An error is raised indicating that the new quantity must not be negative
+ asserterror WIPAdjustmentPage.RunModal();
+ Assert.ExpectedError('must not be negative');
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_QuantityExceedsProdOrderQty_RaisesError()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ begin
+ // [SCENARIO] Entering a new quantity exceeding the production order line quantity raises an error
+ Initialize();
+
+ // [GIVEN] A production order line with quantity 10 and a WIP ledger entry with initial quantity 5
+ CreateTestWIPSetupWithProdOrderQty(WIPLedgerEntry, 5, 10, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will attempt to set the new quantity to 15 (exceeds prod order qty of 10)
+ SetHandlerValues(15, '', '', '');
+
+ // [WHEN] The WIP Adjustment page is opened and a quantity exceeding the production order is entered
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+
+ // [THEN] An error is raised indicating that the new quantity must not exceed the production order line quantity
+ asserterror WIPAdjustmentPage.RunModal();
+ Assert.ExpectedError('must not exceed the production order line quantity');
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_ZeroNewQuantity_Succeeds()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ InitialQty: Decimal;
+ begin
+ // [SCENARIO] Entering zero as the new quantity succeeds (lower boundary: 0 is allowed, < 0 is not)
+ Initialize();
+
+ // [GIVEN] A WIP ledger entry with initial quantity 5 and a prod. order line quantity of 10
+ InitialQty := 5;
+ CreateTestWIPSetupWithProdOrderQty(WIPLedgerEntry, InitialQty, 10, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will set the new quantity to 0 (lower boundary)
+ SetHandlerValues(0, 'ADJ-ZRO', 'Zero Qty Test', '');
+
+ // [WHEN] The WIP Adjustment page is opened and a zero quantity is confirmed
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.RunModal();
+
+ // [THEN] A negative adjustment entry is created reducing the WIP quantity to zero
+ AssertWIPQuantitySum(ProdOrderNo, 0);
+ AssertAdjustmentEntry(
+ ProdOrderNo, -InitialQty,
+ "WIP Ledger Entry Type"::"Negative Adjustment",
+ 'ADJ-ZRO', 'Zero Qty Test', '');
+ end;
+
+ [Test]
+ [HandlerFunctions('WIPAdjustmentPageHandler')]
+ procedure WIPAdjustment_ExactProdOrderQuantity_Succeeds()
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WIPAdjustmentPage: Page "Subc. WIP Adjustment";
+ ProdOrderNo: Code[20];
+ InitialQty, ProdOrderQty : Decimal;
+ begin
+ // [SCENARIO] Entering exactly the production order line quantity succeeds (upper boundary: = is allowed, > is not)
+ Initialize();
+
+ // [GIVEN] A WIP ledger entry with initial quantity 3 and a prod. order line quantity of 10
+ InitialQty := 3;
+ ProdOrderQty := 10;
+ CreateTestWIPSetupWithProdOrderQty(WIPLedgerEntry, InitialQty, ProdOrderQty, ProdOrderNo);
+
+ // [GIVEN] The WIP Adjustment page handler will set the new quantity exactly equal to the prod. order line quantity
+ SetHandlerValues(ProdOrderQty, 'ADJ-MAX', 'Exact Prod Order Qty Test', '');
+
+ // [WHEN] The WIP Adjustment page is opened and the production order line quantity is confirmed
+ WIPAdjustmentPage.SetWIPLedgerEntry(WIPLedgerEntry);
+ WIPAdjustmentPage.RunModal();
+
+ // [THEN] A positive adjustment entry is created raising the WIP quantity to the production order line quantity
+ AssertWIPQuantitySum(ProdOrderNo, ProdOrderQty);
+ AssertAdjustmentEntry(
+ ProdOrderNo, ProdOrderQty - InitialQty,
+ "WIP Ledger Entry Type"::"Positive Adjustment",
+ 'ADJ-MAX', 'Exact Prod Order Qty Test', '');
+ end;
+
+ [ModalPageHandler]
+ procedure WIPAdjustmentPageHandler(var WIPAdjustmentPage: TestPage "Subc. WIP Adjustment")
+ begin
+ WIPAdjustmentPage."New Quantity (Base)".SetValue(HandlerNewQuantity);
+ WIPAdjustmentPage."Document No.".SetValue(HandlerDocumentNo);
+ WIPAdjustmentPage.Description.SetValue(HandlerDescription);
+ WIPAdjustmentPage."Description 2".SetValue(HandlerDescription2);
+ WIPAdjustmentPage.OK().Invoke();
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryUtility: Codeunit "Library - Utility";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ IsInitialized: Boolean;
+ HandlerNewQuantity: Decimal;
+ HandlerDocumentNo: Code[20];
+ HandlerDescription: Text[100];
+ HandlerDescription2: Text[50];
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. WIP Adjustment Test");
+ LibrarySetupStorage.Restore();
+ SubcontractingMgmtLibrary.Initialize();
+
+ if IsInitialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. WIP Adjustment Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. WIP Adjustment Test");
+ end;
+
+ ///
+ /// Creates a single WIP ledger entry with a unique production order and a fixed routing key.
+ /// Returns the production order number and leaves WIPLedgerEntry filtered to that production order.
+ ///
+ local procedure CreateTestWIPSetup(var WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; QuantityBase: Decimal; var ProdOrderNo: Code[20])
+ var
+ Item: Record Item;
+ Location: Record Location;
+ ProductionOrder: Record "Production Order";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ LibraryInventory.CreateItem(Item);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+
+ ProductionOrder."No." := CopyStr(
+ LibraryUtility.GenerateRandomCode(ProductionOrder.FieldNo("No."), Database::"Production Order"),
+ 1, MaxStrLen(ProductionOrder."No."));
+ ProdOrderLine.Status := "Production Order Status"::Released;
+ ProdOrderLine."Prod. Order No." := ProductionOrder."No.";
+ ProdOrderLine."Line No." := 10000;
+ ProdOrderLine."Quantity (Base)" := QuantityBase + 100;
+ ProdOrderLine."Unit of Measure Code" := Item."Base Unit of Measure";
+ ProdOrderLine.Insert(false);
+ ProdOrderRoutingLine."Routing No." := 'RTNG-001';
+ ProdOrderRoutingLine."Routing Reference No." := 10000;
+ ProdOrderRoutingLine."Operation No." := '10';
+
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Location.Code,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ 'WC-001', QuantityBase, false);
+
+ ProdOrderNo := ProductionOrder."No.";
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderNo);
+ end;
+
+ ///
+ /// Creates two WIP ledger entries with an identical 7-field aggregation key (same production order,
+ /// routing reference, operation, and location). SetWIPLedgerEntry will combine them into a single line.
+ /// Returns the production order number and leaves WIPLedgerEntry filtered to both entries.
+ ///
+ local procedure CreateTwoWIPEntriesSameKey(var WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; Quantity1: Decimal; Quantity2: Decimal; var ProdOrderNo: Code[20])
+ var
+ Item: Record Item;
+ Location: Record Location;
+ ProductionOrder: Record "Production Order";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ LibraryInventory.CreateItem(Item);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+
+ ProductionOrder."No." := CopyStr(
+ LibraryUtility.GenerateRandomCode(ProductionOrder.FieldNo("No."), Database::"Production Order"),
+ 1, MaxStrLen(ProductionOrder."No."));
+ ProdOrderLine.Status := "Production Order Status"::Released;
+ ProdOrderLine."Prod. Order No." := ProductionOrder."No.";
+ ProdOrderLine."Line No." := 10000;
+ ProdOrderLine."Quantity (Base)" := Quantity1 + Quantity2 + 100;
+ ProdOrderLine."Unit of Measure Code" := Item."Base Unit of Measure";
+ ProdOrderLine.Insert(false);
+ ProdOrderRoutingLine."Routing No." := 'RTNG-001';
+ ProdOrderRoutingLine."Routing Reference No." := 10000;
+ ProdOrderRoutingLine."Operation No." := '10';
+
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Location.Code,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ 'WC-001', Quantity1, false);
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Location.Code,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ 'WC-001', Quantity2, false);
+
+ ProdOrderNo := ProductionOrder."No.";
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderNo);
+ end;
+
+ ///
+ /// Creates a WIP ledger entry linked to a real Prod. Order Line with the specified quantity.
+ /// The Prod. Order Line is inserted without running triggers to allow testing the
+ /// exceeding-quantity validation without requiring a full production order setup.
+ ///
+ local procedure CreateTestWIPSetupWithProdOrderQty(var WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry"; WIPQuantityBase: Decimal; ProdOrderQty: Decimal; var ProdOrderNo: Code[20])
+ var
+ Item: Record Item;
+ Location: Record Location;
+ ProductionOrder: Record "Production Order";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ begin
+ LibraryInventory.CreateItem(Item);
+ LibraryWarehouse.CreateLocationWithInventoryPostingSetup(Location);
+
+ ProductionOrder."No." := CopyStr(
+ LibraryUtility.GenerateRandomCode(ProductionOrder.FieldNo("No."), Database::"Production Order"),
+ 1, MaxStrLen(ProductionOrder."No."));
+
+ ProdOrderLine.Status := "Production Order Status"::Released;
+ ProdOrderLine."Prod. Order No." := ProductionOrder."No.";
+ ProdOrderLine."Line No." := 10000;
+ ProdOrderLine."Quantity (Base)" := ProdOrderQty;
+ ProdOrderLine."Unit of Measure Code" := Item."Base Unit of Measure";
+ ProdOrderLine.Insert(false);
+
+ ProdOrderRoutingLine."Routing No." := 'RTNG-001';
+ ProdOrderRoutingLine."Routing Reference No." := 10000;
+ ProdOrderRoutingLine."Operation No." := '10';
+
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Location.Code,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ 'WC-001', WIPQuantityBase, false);
+
+ ProdOrderNo := ProductionOrder."No.";
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderNo);
+ end;
+
+ local procedure SetHandlerValues(NewQuantity: Decimal; DocumentNo: Code[20]; Description: Text[100]; Description2: Text[50])
+ begin
+ HandlerNewQuantity := NewQuantity;
+ HandlerDocumentNo := DocumentNo;
+ HandlerDescription := Description;
+ HandlerDescription2 := Description2;
+ end;
+
+ local procedure AssertWIPQuantitySum(ProdOrderNo: Code[20]; ExpectedSum: Decimal)
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderNo);
+ WIPLedgerEntry.CalcSums("Quantity (Base)");
+ Assert.AreEqual(
+ ExpectedSum, WIPLedgerEntry."Quantity (Base)",
+ 'The sum of WIP Ledger Entry quantities should equal the expected new target quantity');
+ end;
+
+ local procedure AssertAdjustmentEntry(ProdOrderNo: Code[20]; ExpectedQuantity: Decimal; ExpectedEntryType: Enum "WIP Ledger Entry Type"; ExpectedDocumentNo: Code[20]; ExpectedDescription: Text[100]; ExpectedDescription2: Text[50])
+ var
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ begin
+ WIPLedgerEntry.SetRange("Prod. Order No.", ProdOrderNo);
+ WIPLedgerEntry.SetRange("Document Type", "WIP Document Type"::"Adjustment (Manual)");
+ Assert.IsTrue(WIPLedgerEntry.FindFirst(), 'A WIP adjustment entry should have been created');
+ Assert.AreEqual(1, WIPLedgerEntry.Count(), 'Exactly one adjustment entry should have been created');
+ Assert.AreEqual(
+ ExpectedQuantity, WIPLedgerEntry."Quantity (Base)",
+ 'The adjustment entry quantity should equal the difference between the new and current WIP quantity');
+ Assert.AreEqual(
+ ExpectedEntryType, WIPLedgerEntry."Entry Type",
+ 'The adjustment entry type should reflect whether the quantity increased or decreased');
+ Assert.AreEqual(
+ "WIP Document Type"::"Adjustment (Manual)", WIPLedgerEntry."Document Type",
+ 'The adjustment entry document type should be Adjustment (Manual)');
+ Assert.AreEqual(
+ WorkDate(), WIPLedgerEntry."Posting Date",
+ 'The adjustment entry posting date should equal WorkDate');
+ Assert.AreEqual(
+ ExpectedDocumentNo, WIPLedgerEntry."Document No.",
+ 'The adjustment entry document number should match the value entered on the page');
+ Assert.AreEqual(
+ ExpectedDescription, WIPLedgerEntry.Description,
+ 'The adjustment entry description should match the value entered on the page');
+ Assert.AreEqual(
+ ExpectedDescription2, WIPLedgerEntry."Description 2",
+ 'The adjustment entry description 2 should match the value entered on the page');
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPTransCreateTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPTransCreateTest.Codeunit.al
new file mode 100644
index 0000000000..bb6c8c980e
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPTransCreateTest.Codeunit.al
@@ -0,0 +1,1525 @@
+// ------------------------------------------------------------------------------------------------
+// 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.UOM;
+using Microsoft.Inventory.Item;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.Family;
+using Microsoft.Manufacturing.MachineCenter;
+using Microsoft.Manufacturing.Routing;
+using Microsoft.Manufacturing.Setup;
+using Microsoft.Manufacturing.Subcontracting;
+using Microsoft.Manufacturing.WorkCenter;
+using Microsoft.Purchases.Document;
+using Microsoft.Purchases.Vendor;
+
+codeunit 149911 "Subc. WIP Trans. Create Test"
+{
+ // [FEATURE] WIP Item Transfer for Subcontracting
+ Subtype = Test;
+ TestPermissions = Disabled;
+ TestType = IntegrationTest;
+
+ trigger OnRun()
+ begin
+ IsInitialized := false;
+ end;
+
+ [Test]
+ procedure TransferWIPItemFlagFromRoutingLineToPurchaseLine()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcCalculateSubContracts: Report "Subc. Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ begin
+ // [SCENARIO] The "Transfer WIP Item" flag set on a Routing Line is propagated through the
+ // Prod. Order Routing Line to the Purchase Line when the subcontracting purchase order
+ // is created via the Subcontracting Worksheet (Calculate Subcontracts → Carry Out Action).
+
+ // [GIVEN] Complete setup of Manufacturing, Work- and Machine Centers, subcontracting item
+ Initialize();
+
+ // [GIVEN] Create work centers and machine centers
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create item with routing and prod. BOM – set "Transfer WIP Item" on routing
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Create and refresh released production order
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [GIVEN] Verify Prod. Order Routing Line carries the flag
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ Assert.IsTrue(ProdOrderRoutingLine."Transfer WIP Item",
+ 'Prod. Order Routing Line must inherit Transfer WIP Item from Routing Line.');
+
+ // [GIVEN] Setup requisition worksheet
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.CreateReqWkshTemplateAndName(ReqWkshTemplate, RequisitionWkshName);
+
+ // [WHEN] Calculate Subcontracts and Carry Out Action Msg creates Purchase Order
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ SubcCalculateSubContracts.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContracts.UseRequestPage(false);
+ SubcCalculateSubContracts.RunModal();
+
+ RequisitionLine.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+
+ // [THEN] Purchase Line carries "Transfer WIP Item" = true
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ Assert.IsTrue(PurchaseLine."Transfer WIP Item",
+ 'Purchase Line must carry "Transfer WIP Item" = true from Prod. Order Routing Line.');
+ end;
+
+ [Test]
+ procedure TransferWIPItemFlagNotSetWhenRoutingLineHasFlagOff()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseLine: Record "Purchase Line";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ WorkCenter: array[2] of Record "Work Center";
+ SubcCalculateSubContracts: Report "Subc. Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ begin
+ // [SCENARIO] When "Transfer WIP Item" is NOT set on the Routing Line,
+ // the Purchase Line must NOT have the flag set.
+
+ // [GIVEN] Setup
+ Initialize();
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ // Routing line "Transfer WIP Item" defaults to false – do NOT set it
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [GIVEN] Verify flag is false on Prod. Order Routing Line
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+ Assert.IsFalse(ProdOrderRoutingLine."Transfer WIP Item",
+ 'Prod. Order Routing Line must NOT have Transfer WIP Item when routing line does not set it.');
+
+ // [GIVEN] Setup worksheet
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+ SubcontractingMgmtLibrary.CreateReqWkshTemplateAndName(ReqWkshTemplate, RequisitionWkshName);
+
+ // [WHEN] Create Purchase Order via Subcontracting Worksheet
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ SubcCalculateSubContracts.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContracts.UseRequestPage(false);
+ SubcCalculateSubContracts.RunModal();
+
+ RequisitionLine.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ RequisitionLine.FindFirst();
+
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+
+ // [THEN] Purchase Line carries "Transfer WIP Item" = false
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ Assert.IsFalse(PurchaseLine."Transfer WIP Item",
+ 'Purchase Line must NOT carry "Transfer WIP Item" when routing line flag is off.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPAndCompTransferOrderCreatedFromSubcontrPurchOrder_SameLocation()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When a Subcontracting Purchase Order is created for a routing line with
+ // "Transfer WIP Item" = true, running "Create Transfer Order to Subcontractor" creates
+ // a WIP Transfer Line with "Transfer WIP Item" = true, correct item, quantity, and locations.
+ // Component lines and WIP Transfer Lines are created with the same locations.
+
+ // [GIVEN] Complete setup
+ Initialize();
+
+ // [GIVEN] Work centers, machine centers, item with routing + BOM
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Set "Transfer WIP Item" on the subcontracting routing line
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Set up component transfer infrastructure
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Create and refresh production order
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] A WIP Transfer Line exists with correct properties
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+ Assert.RecordCount(TransferLine, 2);
+
+ //Only One transfer header should have been created for both the component and WIP transfer lines
+ TransferLine.FindFirst();
+ TransferLine.SetFilter("Document No.", '<>%1', TransferLine."Document No.");
+ Assert.RecordCount(TransferLine, 0);
+
+ //Only one of the two lines should have the Transfer WIP Item flag set
+ TransferLine.SetRange("Document No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ Assert.RecordCount(TransferLine, 1);
+
+ TransferLine.FindFirst();
+ Assert.AreEqual(Item."No.", TransferLine."Item No.",
+ 'WIP Transfer Line must reference the production order parent item.');
+ Assert.AreEqual(ProductionOrder.Quantity, TransferLine.Quantity,
+ 'WIP Transfer Line must have the same quantity as the production order.');
+
+ // [THEN] Transfer header has correct from/to locations
+ TransferHeader.Get(TransferLine."Document No.");
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Assert.AreEqual(Vendor."Subc. Location Code", TransferHeader."Transfer-to Code",
+ 'WIP Transfer must go TO the subcontractor location.');
+
+ // [TEARDOWN]
+ TransferHeader.Delete(true);
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrders')]
+ procedure WIPAndCompTransferOrderCreatedFromSubcontrPurchOrder_DifferentLocation()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When a Subcontracting Purchase Order is created for a routing line with
+ // "Transfer WIP Item" = true, running "Create Transfer Order to Subcontractor" creates
+ // a WIP Transfer Line with "Transfer WIP Item" = true, correct item, quantity, and locations.
+ // Component lines and WIP Transfer Lines are created with different locations.
+
+ // [GIVEN] Complete setup
+ Initialize();
+
+ // [GIVEN] Work centers, machine centers, item with routing + BOM
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Set "Transfer WIP Item" on the subcontracting routing line
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Set up component transfer infrastructure
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Create and refresh production order
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No."); //different location for component transfer than WIP transfer
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order from Prod. Order Routing
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Transfer Lines exists with correct properties
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+ Assert.RecordCount(TransferLine, 2);
+
+ //two transfer headers should have been created for both the component and WIP transfer lines
+ TransferLine.FindFirst();
+ TransferLine.SetFilter("Document No.", '<>%1', TransferLine."Document No.");
+ Assert.RecordCount(TransferLine, 1);
+
+ //Only one of the two lines should have the Transfer WIP Item flag set
+ TransferLine.SetRange("Document No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ Assert.RecordCount(TransferLine, 1);
+
+ TransferLine.FindFirst();
+ Assert.AreEqual(Item."No.", TransferLine."Item No.",
+ 'WIP Transfer Line must reference the production order parent item.');
+ Assert.AreEqual(ProductionOrder.Quantity, TransferLine.Quantity,
+ 'WIP Transfer Line must have the same quantity as the production order.');
+
+ // [THEN] Transfer header has correct from/to locations
+ TransferHeader.Get(TransferLine."Document No.");
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Assert.AreEqual(Vendor."Subc. Location Code", TransferHeader."Transfer-to Code",
+ 'WIP Transfer must go TO the subcontractor location.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPTransferOrderNotCreatedWhenFlagIsOff()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When "Transfer WIP Item" flag is NOT set on the routing line, creating
+ // a Transfer Order to Subcontractor must NOT create a WIP Transfer Line.
+
+ // [GIVEN] Setup
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ // Do NOT set Transfer WIP Item on routing line
+
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ SubcontractingMgmtLibrary.UpdateProdOrderCompWithLocationCode(ProductionOrder."No.");
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [WHEN] Create Purchase Order and Transfer Order
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] No WIP Transfer Line exists
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Transfer WIP Item", true);
+ Assert.RecordIsEmpty(TransferLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure NoWIPTransferCreatedWhenExpectedEqualsPostedQuantity()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When the posted WIP quantity equals the expected quantity at the destination,
+ // the CheckCreateWIPTransfer procedure should return false, and no new WIP Transfer Order
+ // should be created when "Create Transfer Order to Subcontractor" is invoked again.
+
+ // [GIVEN] Complete setup
+ Initialize();
+
+ // [GIVEN] Work centers, machine centers, item with routing + BOM
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Set "Transfer WIP Item" on the subcontracting routing line
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Create and refresh production order
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ // [GIVEN] Get routing line information
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [GIVEN] Create first Subcontracting Purchase Order and Transfer Order
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [GIVEN] Verify first WIP Transfer Line was created
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", false);
+ Assert.RecordCount(TransferLine, 1);
+ TransferLine.FindFirst();
+
+ // [GIVEN] Simulate posting the transfer by creating WIP Ledger Entry with quantity equal to expected quantity
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Vendor."Subc. Location Code",
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ WorkCenter[2]."No.", ProductionOrder.Quantity, false);
+
+ // [GIVEN] Delete the first transfer order to allow re-creation attempt
+ TransferHeader.Get(TransferLine."Document No.");
+ TransferHeader.Delete(true);
+
+ // [WHEN] Attempt to create Transfer Order to Subcontractor again
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ asserterror PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] No WIP Transfer Line is created, and an error message indicates that there is no WIP or components to transfer
+ Assert.ExpectedError('Nothing to create. No components or WIP to transfer for the specified subcontracting order.');
+
+ // [TEARDOWN]
+ WIPLedgerEntry.DeleteAll();
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPReturnTransferOrderCreatedWithCorrectLocations()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ ProdOrderLocationCode: Code[10];
+ SubcLocationCode: Code[10];
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] After a WIP Transfer Order has been created and posted, creating
+ // a Return Transfer Order creates a WIP Return Transfer Line with reversed
+ // from/to locations compared to the original WIP Transfer Order.
+
+ // [GIVEN] Complete setup
+ Initialize();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Create and refresh production order
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ // [WHEN] Create Subcontracting Purchase Order
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Get the WIP Transfer Line and locate the subcontractor location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ SubcLocationCode := Vendor."Subc. Location Code";
+
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+ ProdOrderLocationCode := ProdOrderLine."Location Code";
+
+ // [GIVEN] Find the routing line to get operation details
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [GIVEN] Mock WIP Ledger Entry at subcontractor location (simulates posted WIP transfer)
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", SubcLocationCode,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ '', LibraryRandom.RandInt(10) + 1, false);
+
+ // [WHEN] Create Return Transfer Order from Subcontractor
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ // [THEN] A WIP Return Transfer Line exists
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", true);
+ Assert.RecordIsNotEmpty(TransferLine);
+
+ TransferLine.FindFirst();
+ Assert.AreEqual(Item."No.", TransferLine."Item No.",
+ 'WIP Return Transfer Line must reference the production order parent item.');
+ Assert.AreEqual(WIPLedgerEntry."Quantity (Base)", TransferLine.Quantity,
+ 'WIP Return Transfer Line must have the same quantity as the WIP Ledger Entry.');
+
+ // [THEN] Return Transfer Header has reversed locations (from subcontractor, to company WH)
+ TransferHeader.Get(TransferLine."Document No.");
+ Assert.AreEqual(SubcLocationCode, TransferHeader."Transfer-from Code",
+ 'Return WIP Transfer must come FROM the subcontractor location.');
+ Assert.AreEqual(ProdOrderLocationCode, TransferHeader."Transfer-to Code",
+ 'Return WIP Transfer must go TO the Prod. Order Line location (company WH).');
+
+ // [TEARDOWN]
+ WIPLedgerEntry.DeleteAll();
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPTransferOrderFromSubc1ToSubc2WhenPreviousOpIsSubcontracting()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor1: Record Vendor;
+ Vendor2: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Subc1LocationCode: Code[10];
+ Subc2LocationCode: Code[10];
+ WIPQty: Decimal;
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When a WIP item has been processed at Subcontractor 1 (the immediate previous
+ // subcontracting operation), creating a Transfer Order to Subcontractor 2 creates a WIP
+ // Transfer Line going FROM Subcontractor 1's location TO Subcontractor 2's location.
+ // The quantity on the WIP Transfer Line matches the WIP Ledger Entry at Subcontractor 1.
+
+ // [GIVEN] Complete setup
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Two subcontracting work centers, each with their own vendor and location
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+
+ // [GIVEN] Set "Transfer WIP Item" on Subcontractor 1 and Subcontractor 2 routing line (the current operation)
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[1]."No.", true);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Assign dedicated subcontractor locations to both vendors
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[1]);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Create and refresh released production order
+ SubcontractingMgmtLibrary.CreateAndRefreshProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [GIVEN] Read subcontractor location codes after all vendor updates
+ Vendor1.Get(WorkCenter[1]."Subcontractor No.");
+ Vendor2.Get(WorkCenter[2]."Subcontractor No.");
+ Subc1LocationCode := Vendor1."Subc. Location Code";
+ Subc2LocationCode := Vendor2."Subc. Location Code";
+
+ // [GIVEN] Locate the Prod. Order line and the routing line for Subcontractor 1 (previous op)
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[1]."No.");
+ ProdOrderRoutingLine.FindLast();
+
+ // [GIVEN] Insert a WIP Ledger Entry at Subcontractor 1's location, simulating that
+ // the production item has been processed and is physically at Subcontractor 1
+ WIPQty := LibraryRandom.RandInt(10) + 1;
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Subc1LocationCode,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ WorkCenter[1]."No.", WIPQty, false);
+
+ // [WHEN] Create Subcontracting Purchase Order for Subcontractor 2's operation
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor 2
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] A WIP Transfer Line exists for the production order
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", false);
+ Assert.RecordCount(TransferLine, 1);
+
+ TransferLine.FindFirst();
+ Assert.AreEqual(Item."No.", TransferLine."Item No.",
+ 'WIP Transfer Line must reference the production order parent item.');
+ Assert.AreEqual(WIPQty, TransferLine.Quantity,
+ 'WIP Transfer Line quantity must match the WIP Ledger Entry at Subcontractor 1.');
+
+ // [THEN] Transfer Header must go FROM Subcontractor 1's location TO Subcontractor 2's location
+ TransferHeader.Get(TransferLine."Document No.");
+ Assert.AreEqual(Subc1LocationCode, TransferHeader."Transfer-from Code",
+ 'WIP Transfer must come FROM Subcontractor 1''s location (previous subcontracting operation).');
+ Assert.AreEqual(Subc2LocationCode, TransferHeader."Transfer-to Code",
+ 'WIP Transfer must go TO Subcontractor 2''s location.');
+
+ // [TEARDOWN]
+ WIPLedgerEntry.DeleteAll();
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrders')]
+ procedure WIPTransferOrdersCreatedForParallelRoutingPredecessors()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ Loc30TransferFound: Boolean;
+ ProdOrderLocTransferFound: Boolean;
+ Loc30Code: Code[10];
+ Loc40Code: Code[10];
+ ProdOrderLocationCode: Code[10];
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] For a parallel routing 10 → 20 | 30 → 40 where:
+ // Creating a Transfer Order to Subcontractor for op 40's purchase order
+ // must create TWO separate WIP Transfer Orders:
+ // 1. From Prod. Order Line location (our warehouse) → Subc40 location
+ // (path through non-SC op 20; WIP remains at our warehouse)
+ // 2. From Subc30 location → Subc40 location
+ // (path through SC op 30; WIP is at Subc30's location)
+
+ // [GIVEN] Complete setup
+ Initialize();
+
+ // [GIVEN] Parallel routing with machine centers, work centers and item
+ SubcWarehouseLibrary.CreateParallelRoutingItemWithSubcontracting(
+ Item, MachineCenter, WorkCenter);
+
+ // [GIVEN] Get subcontractor location codes from work centers
+ Vendor.Get(WorkCenter[1]."Subcontractor No.");
+ Loc30Code := Vendor."Subc. Location Code";
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ Loc40Code := Vendor."Subc. Location Code";
+
+ // [GIVEN] Create released production order
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ // [GIVEN] Set the production order line location to Manufacturing "Components at Location"
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ ProdOrderLocationCode := ProductionOrder."Location Code";
+
+ // [GIVEN] Create transfer routes for both WIP paths:
+ // 1. ProdOrderLocation → Loc40 (non-SC op 20 path: WIP stays at our warehouse)
+ CreateAndUpdateTransferRoute(ProdOrderLocationCode, Loc40Code);
+
+ // 2. Loc30 → Loc40 (SC op 30 path: WIP is at Subc30 location)
+ CreateAndUpdateTransferRoute(Loc30Code, Loc40Code);
+
+ // [WHEN] Create a subcontracting purchase order for op 40 (the last SC operation)
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Transfer Orders to Subcontractor 40 — two parallel predecessors → two transfer orders
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Exactly two WIP Transfer Lines exist (one per parallel predecessor path)
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", false);
+ Assert.RecordCount(TransferLine, 2);
+
+ // [THEN] The two WIP Transfer Lines belong to two different Transfer Headers
+ TransferLine.FindFirst();
+ TransferLine.SetFilter("Document No.", '<>%1', TransferLine."Document No.");
+ Assert.RecordCount(TransferLine, 1);
+
+ // [THEN] Both WIP Transfer Lines reference the production order parent item,
+ // carry the full production order quantity (parallel routing preset),
+ // and each header delivers TO Subcontractor 40's location
+ // while coming FROM one of the two distinct source locations
+ TransferLine.SetRange("Document No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", false);
+ TransferLine.FindSet();
+ repeat
+ Assert.AreEqual(Item."No.", TransferLine."Item No.",
+ 'Each WIP Transfer Line must reference the production order parent item.');
+ Assert.AreEqual(ProductionOrder.Quantity, TransferLine.Quantity,
+ 'Each parallel WIP Transfer Line must carry the full production order quantity.');
+
+ TransferHeader.Get(TransferLine."Document No.");
+ Assert.AreEqual(Loc40Code, TransferHeader."Transfer-to Code",
+ 'Both WIP Transfer Orders must go TO Subcontractor 40''s location.');
+
+ if TransferHeader."Transfer-from Code" = ProdOrderLocationCode then
+ ProdOrderLocTransferFound := true
+ else
+ if TransferHeader."Transfer-from Code" = Loc30Code then
+ Loc30TransferFound := true;
+ until TransferLine.Next() = 0;
+
+ // [THEN] One WIP transfer originates from our warehouse (non-SC op 20 path)
+ Assert.IsTrue(ProdOrderLocTransferFound,
+ 'A WIP Transfer from the Prod. Order Line location (non-SC op 20 path) to Subc. 40 must exist.');
+
+ // [THEN] One WIP transfer originates from Subcontractor 30''s location (SC op 30 path)
+ Assert.IsTrue(Loc30TransferFound,
+ 'A WIP Transfer from Subcontractor 30''s location (SC op 30 path) to Subc. 40 must exist.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure DeleteReturnTransferOrderAndRecreateSucceeds()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ SubcLocationCode: Code[10];
+ WIPQty: Decimal;
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] After creating a WIP return transfer order and deleting it,
+ // the user must be able to re-create it from the same purchase order.
+ Initialize();
+
+ // [GIVEN] A subcontracting setup with WIP Transfer enabled
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Mock WIP Ledger Entry at subcontractor location (simulates posted WIP transfer)
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ SubcLocationCode := Vendor."Subc. Location Code";
+
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ WIPQty := LibraryRandom.RandInt(10) + 1;
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", SubcLocationCode,
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ '', WIPQty, false);
+
+ // [GIVEN] Create the first return transfer order
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", true);
+ Assert.RecordIsNotEmpty(TransferLine);
+ TransferLine.FindFirst();
+
+ // [WHEN] The return transfer order is deleted
+ TransferHeader.Get(TransferLine."Document No.");
+ TransferHeader.Delete(true);
+
+ // [VERIFY] Return transfer line no longer exists
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", true);
+ Assert.RecordIsEmpty(TransferLine);
+
+ // [WHEN] The return transfer order is created again
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateReturnFromSubcontractor.Invoke();
+
+ // [THEN] A new return transfer line is created successfully
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", true);
+ Assert.RecordIsNotEmpty(TransferLine);
+ TransferLine.FindFirst();
+ Assert.AreEqual(WIPQty, TransferLine.Quantity,
+ 'Recreated WIP return transfer line must have the same quantity as the WIP Ledger Entry.');
+
+ // [TEARDOWN]
+ WIPLedgerEntry.DeleteAll();
+ end;
+
+ [Test]
+ [HandlerFunctions('HandleTransferOrder')]
+ procedure WIPTransferCreatedPerProdOrderLineInFamilyProductionOrder()
+ var
+ Family: Record Family;
+ FamilyItem: array[2] of Record Item;
+ FamilyLine: array[2] of Record "Family Line";
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ ReqWkshTemplate: Record "Req. Wksh. Template";
+ RequisitionLine: Record "Requisition Line";
+ RequisitionWkshName: Record "Requisition Wksh. Name";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ SubcCalculateSubContracts: Report "Subc. Calculate Subcontracts";
+ CarryOutActionMsgReq: Report "Carry Out Action Msg. - Req.";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] A Production Order sourced from a Family with 2 family items shares a single
+ // subcontracting routing (Transfer WIP Item = true on the subcontracting operation).
+ // For every prod order line one purchase line is created via the subcontracting worksheet.
+ // When "Create Transfer Order to Subcontractor" is invoked on the purchase order,
+ // a separate WIP Transfer Line is created for each prod order line / family item.
+
+ // [GIVEN] Complete setup
+ Initialize();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+
+ // [GIVEN] Create subcontracting work centers and machine centers
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+
+ // [GIVEN] Create two plain items that will become the family line output items
+ LibraryInventory.CreateItem(FamilyItem[1]);
+ LibraryInventory.CreateItem(FamilyItem[2]);
+
+ // [GIVEN] Create a Production Family with both items as family lines
+ LibraryManufacturing.CreateFamily(Family);
+ LibraryManufacturing.CreateFamilyLine(FamilyLine[1], Family."No.", FamilyItem[1]."No.", 1);
+ LibraryManufacturing.CreateFamilyLine(FamilyLine[2], Family."No.", FamilyItem[2]."No.", 1);
+
+ // [GIVEN] Build a routing with the subcontracting work center and assign it to the family
+ CreateFamilyRoutingWithSubcontractingWC(Family, WorkCenter[2]."No.");
+
+ // [GIVEN] Set "Transfer WIP Item" on the subcontracting routing line
+ SetTransferWIPItemOnRoutingLine(Family."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Give WC[2]'s vendor a dedicated subcontracting location
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+
+ // [GIVEN] Create a released production order for the family
+ // → produces 2 Prod. Order Lines (one per family item), each with the family routing
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Family, Family."No.", LibraryRandom.RandInt(10) + 5);
+
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ // [GIVEN] Create a transfer route from the prod order line location to the subcontractor location
+ CreateAndUpdateTransferRoute(GetManufacturingSetupCompLocation(), Vendor."Subc. Location Code");
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ // [WHEN] Calculate Subcontracts: produces one requisition line per prod order routing line (= 2)
+ SubcontractingMgmtLibrary.CreateReqWkshTemplateAndName(ReqWkshTemplate, RequisitionWkshName);
+ RequisitionLine."Worksheet Template Name" := RequisitionWkshName."Worksheet Template Name";
+ RequisitionLine."Journal Batch Name" := RequisitionWkshName.Name;
+
+ SubcCalculateSubContracts.SetWkShLine(RequisitionLine);
+ SubcCalculateSubContracts.UseRequestPage(false);
+ SubcCalculateSubContracts.RunModal();
+
+ RequisitionLine.SetRange("Worksheet Template Name", RequisitionWkshName."Worksheet Template Name");
+ RequisitionLine.SetRange("Journal Batch Name", RequisitionWkshName.Name);
+#pragma warning disable AA0210
+ RequisitionLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning restore AA0210
+ // 2 requisition lines expected – one per family item / prod order line
+ Assert.RecordCount(RequisitionLine, 2);
+ RequisitionLine.FindFirst();
+
+ // [WHEN] Carry Out Action processes all lines in the batch → creates purchase lines
+ CarryOutActionMsgReq.SetReqWkshLine(RequisitionLine);
+ CarryOutActionMsgReq.UseRequestPage(false);
+ CarryOutActionMsgReq.RunModal();
+
+ // [THEN] 2 purchase lines exist for the production order – one per prod order line / family item
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange(Type, "Purchase Line Type"::Item);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ Assert.RecordCount(PurchaseLine, 2);
+
+ // [WHEN] Invoke "Create Transfer Order to Subcontractor" on the purchase order
+ PurchaseLine.FindFirst();
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Exactly 2 WIP Transfer Lines exist – one per family item / prod order line
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.SetRange("Subc. Return Order", false);
+ Assert.RecordCount(TransferLine, 2);
+
+ // [THEN] Each WIP Transfer Line references one of the two family items
+ TransferLine.FindSet();
+ repeat
+ Assert.IsTrue(
+ (TransferLine."Item No." = FamilyItem[1]."No.") or (TransferLine."Item No." = FamilyItem[2]."No."),
+ 'Each WIP Transfer Line must reference one of the two family items.');
+ until TransferLine.Next() = 0;
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPTransferQuantityFollowsChangedPurchaseLineQuantity()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ OriginalQty: Decimal;
+ ChangedQty: Decimal;
+ begin
+ // [SCENARIO] When the purchase line quantity is changed from the original production order
+ // quantity, the WIP transfer line must use the updated purchase line quantity, not the
+ // original production order quantity.
+
+ // [GIVEN] Complete setup with Transfer WIP Item = true on the subcontracting routing line
+ Initialize();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Set up component transfer infrastructure (needed so TransferRoute can be created)
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Create and refresh production order with the original quantity
+ OriginalQty := LibraryRandom.RandInt(5) + 2;
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", OriginalQty);
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [GIVEN] Create Subcontracting Purchase Order (purchase line qty = OriginalQty initially)
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [GIVEN] Change the purchase line quantity to a larger value
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+ ChangedQty := OriginalQty + LibraryRandom.RandInt(5) + 1;
+ PurchaseLine.Validate(Quantity, ChangedQty);
+ PurchaseLine.Modify(true);
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] WIP Transfer Line quantity equals the changed purchase line quantity (not the original prod. order qty)
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ Assert.RecordCount(TransferLine, 1);
+ TransferLine.FindFirst();
+ Assert.AreEqual(ChangedQty, TransferLine.Quantity,
+ 'WIP Transfer Line quantity must follow the changed purchase line quantity, not the original production order quantity.');
+ Assert.AreNotEqual(OriginalQty, TransferLine.Quantity,
+ 'WIP Transfer Line quantity must not equal the original production order quantity when the purchase line was changed.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPTransferUsesUnitOfMeasureFromPurchaseLine()
+ var
+ Item: Record Item;
+ ItemUnitOfMeasure: Record "Item Unit of Measure";
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferLine: Record "Transfer Line";
+ UnitOfMeasure: Record "Unit of Measure";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ BoxQtyPerPCS: Decimal;
+ ProdOrderQty: Decimal;
+ begin
+ // [SCENARIO] When the item has a non-base Purchase Unit of Measure (e.g. BOX = 10 PCS),
+ // the WIP Transfer Order line must use the purchase line UOM (BOX) and quantity,
+ // not the base UOM (PCS) from the production order line.
+ Initialize();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+ SubcWarehouseLibrary.UpdateProdBomAndRoutingWithRoutingLink(Item, WorkCenter[2]."No.");
+ SubcontractingMgmtLibrary.UpdateProdBomWithComponentSupplyMethod(Item, "Component Supply Method"::"Transfer to Vendor");
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Add a non-base UOM BOX (= 10 PCS) to the item and set it as the Purchase UOM
+ BoxQtyPerPCS := 10;
+ LibraryInventory.CreateUnitOfMeasureCode(UnitOfMeasure);
+ LibraryInventory.CreateItemUnitOfMeasure(ItemUnitOfMeasure, Item."No.", UnitOfMeasure.Code, BoxQtyPerPCS);
+ Item.Validate("Purch. Unit of Measure", UnitOfMeasure.Code);
+ Item.Modify(true);
+
+ // [GIVEN] Create and refresh production order with a quantity that is a whole multiple of BoxQtyPerPCS
+ ProdOrderQty := BoxQtyPerPCS * (LibraryRandom.RandInt(5) + 1);
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", ProdOrderQty);
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+ SubcontractingMgmtLibrary.CreateTransferRoute(WorkCenter[2], ProductionOrder);
+
+ // [GIVEN] Create Subcontracting Purchase Order — the purchase line will carry UOM = BOX
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ PurchaseLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+#pragma warning restore AA0210
+ PurchaseLine.FindFirst();
+
+ // [GIVEN] Verify the purchase line received the item's Purch. Unit of Measure (BOX)
+ PurchaseLine.Validate("Unit of Measure Code", UnitOfMeasure.Code);
+ PurchaseLine.Validate(Quantity, ProdOrderQty / BoxQtyPerPCS);
+ PurchaseLine.Modify(true);
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] The WIP Transfer Line uses the purchase line UOM (BOX), not the base UOM (PCS)
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+ #pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+ #pragma warning restore AA0210
+ Assert.RecordCount(TransferLine, 1);
+ TransferLine.FindFirst();
+ Assert.AreEqual(UnitOfMeasure.Code, TransferLine."Unit of Measure Code",
+ 'WIP Transfer Line Unit of Measure must match the purchase line UOM (BOX), not the base UOM (PCS).');
+ Assert.AreEqual(PurchaseLine.Quantity, TransferLine.Quantity,
+ 'WIP Transfer Line quantity must equal the purchase line quantity (in BOX).');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPTransferPartiallyPostedTransfersRemainingQuantity()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ FullQty: Decimal;
+ AlreadyPostedQty: Decimal;
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When part of the WIP has already been posted at the vendor location,
+ // creating a Transfer Order to Subcontractor must only transfer the remaining
+ // quantity (FullQty - AlreadyPostedQty), not the full purchase line quantity.
+
+ // [GIVEN] Complete setup with Transfer WIP Item = true on the subcontracting routing line
+ Initialize();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Create and refresh production order with a quantity that allows a partial remainder
+ FullQty := LibraryRandom.RandIntInRange(6, 10);
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", FullQty);
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ // [GIVEN] Locate the Prod. Order line and the routing line
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ // [GIVEN] Simulate that a partial quantity has already been transferred and posted at the vendor location
+ AlreadyPostedQty := LibraryRandom.RandIntInRange(1, FullQty - 1);
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Vendor."Subc. Location Code",
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ WorkCenter[2]."No.", AlreadyPostedQty, false);
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Transfer line quantity equals only the remaining quantity
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ Assert.RecordCount(TransferLine, 1);
+ TransferLine.FindFirst();
+ Assert.AreEqual(FullQty - AlreadyPostedQty, TransferLine.Quantity,
+ 'WIP Transfer Line quantity must equal the remaining quantity (full qty minus already posted), not the full quantity.');
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure WIPTransferAfterQuantityIncreaseTransfersOnlyAdditionalQuantity()
+ var
+ Item: Record Item;
+ MachineCenter: array[2] of Record "Machine Center";
+ ProdOrderLine: Record "Prod. Order Line";
+ ProdOrderRoutingLine: Record "Prod. Order Routing Line";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WorkCenter: array[2] of Record "Work Center";
+ OriginalQty: Decimal;
+ ChangedQty: Decimal;
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] When the purchase line quantity is increased after the original quantity
+ // has already been fully posted at the vendor location, creating a Transfer Order must
+ // only transfer the additional quantity (ChangedQty - OriginalQty), not the full new quantity.
+
+ // [GIVEN] Complete setup with Transfer WIP Item = true on the subcontracting routing line
+ Initialize();
+
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+
+ // [GIVEN] Create and refresh production order with the original quantity
+ OriginalQty := LibraryRandom.RandIntInRange(3, 7);
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", OriginalQty);
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ // [GIVEN] Locate the Prod. Order line and routing line
+ ProdOrderLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderLine.FindFirst();
+
+ ProdOrderRoutingLine.SetRange(Status, "Production Order Status"::Released);
+ ProdOrderRoutingLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ ProdOrderRoutingLine.SetRange("Work Center No.", WorkCenter[2]."No.");
+ ProdOrderRoutingLine.FindFirst();
+
+ // [GIVEN] Create Subcontracting Purchase Order
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+
+ // [GIVEN] Simulate that the original quantity has been fully posted at the vendor location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ SubcontractingMgmtLibrary.CreateWIPLedgerEntry(
+ WIPLedgerEntry, Item."No.", Vendor."Subc. Location Code",
+ ProductionOrder, ProdOrderLine, ProdOrderRoutingLine,
+ WorkCenter[2]."No.", OriginalQty, false);
+
+ // [GIVEN] Increase the purchase line quantity beyond the already-posted amount
+ ChangedQty := OriginalQty + LibraryRandom.RandIntInRange(1, 5);
+ PurchaseLine.Validate(Quantity, ChangedQty);
+ PurchaseLine.Modify(true);
+
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [WHEN] Create Transfer Order to Subcontractor
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ // [THEN] Transfer line quantity equals only the additional quantity (ChangedQty - OriginalQty)
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+ TransferLine.SetRange("Subc. Return Order", false);
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ Assert.RecordCount(TransferLine, 1);
+ TransferLine.FindFirst();
+ Assert.AreEqual(ChangedQty - OriginalQty, TransferLine.Quantity,
+ 'WIP Transfer Line quantity must equal only the additional quantity after the purchase line increase, not the full changed quantity.');
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ TransfOrderPage.OK().Invoke();
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrders(var TransfOrderPage: TestPage "Transfer Orders")
+ begin
+ TransfOrderPage.OK().Invoke();
+ end;
+
+ [MessageHandler]
+ procedure HandleCreateTransferOrderMsg(Message: Text[1024])
+ begin
+ end;
+
+ [ConfirmHandler]
+ procedure DoNotConfirmShowCreatedPurchOrderForSubcontracting(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Reply := false;
+ end;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. WIP Trans. Create Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ SubcontractingMgmtLibrary.UpdateSubMgmtSetup_ComponentAtLocation("Components at Location"::Purchase);
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. WIP Trans. Create Test");
+
+ SubSetupLibrary.InitSetupFields();
+ // Next lines should be uncommented if the Wizard has been moved to the Base App
+ // SubSetupLibrary.ConfigureSubManagementForNothingPresentScenario("Subc. Show/Edit Type"::Hide, "Subc. Show/Edit Type"::Hide);
+ // SubSetupLibrary.ConfigureSubManagementForBothPresentScenario("Subc. Show/Edit Type"::Hide, "Subc. Show/Edit Type"::Hide);
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. WIP Trans. Create Test");
+ end;
+
+ local procedure SetTransferWIPItemOnRoutingLine(RoutingNo: Code[20]; WorkCenterNo: Code[20]; TransferWIPItem: Boolean)
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+ RoutingHeader.Get(RoutingNo);
+ 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);
+ RoutingLine.FindFirst();
+ RoutingLine."Transfer WIP Item" := TransferWIPItem;
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+ end;
+
+ local procedure SetProdOrderLocationToCompSetupLocationAndRefresh(var ProductionOrder: Record "Production Order")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ ProductionOrder.Validate("Location Code", ManufacturingSetup."Components at Location");
+ ProductionOrder.Modify();
+
+ LibraryManufacturing.RefreshProdOrder(ProductionOrder, false, true, true, true, false);
+ end;
+
+ local procedure CreateAndUpdateTransferRoute(FromLocationCode: Code[10]; ToLocationCode: Code[10])
+ var
+ Location: Record Location;
+ TransferRoute: Record "Transfer Route";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ begin
+ LibraryWarehouse.CreateInTransitLocation(Location);
+ LibraryWarehouse.CreateAndUpdateTransferRoute(
+ TransferRoute, FromLocationCode, ToLocationCode, Location.Code, '', '');
+ end;
+
+ local procedure CreateFamilyRoutingWithSubcontractingWC(var Family: Record Family; WorkCenterNo: Code[20])
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+ // Creates a minimal serial routing containing only the subcontracting work center
+ // and assigns it to the family.
+ LibraryManufacturing.CreateRoutingHeader(RoutingHeader, RoutingHeader.Type::Serial);
+ LibraryManufacturing.CreateRoutingLine(RoutingHeader, RoutingLine, '', '10', RoutingLine.Type::"Work Center", WorkCenterNo);
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+
+ Family.Validate("Routing No.", RoutingHeader."No.");
+ Family.Modify(true);
+ end;
+
+ local procedure GetManufacturingSetupCompLocation(): Code[10]
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ exit(ManufacturingSetup."Components at Location");
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryRandom: Codeunit "Library - Random";
+ 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";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ IsInitialized: Boolean;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPTransferPostTest.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPTransferPostTest.Codeunit.al
new file mode 100644
index 0000000000..8ee05f6d1d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/Tests/SubcWIPTransferPostTest.Codeunit.al
@@ -0,0 +1,810 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Inventory.Item;
+using Microsoft.Inventory.Journal;
+using Microsoft.Inventory.Ledger;
+using Microsoft.Inventory.Location;
+using Microsoft.Inventory.Requisition;
+using Microsoft.Inventory.Tracking;
+using Microsoft.Inventory.Transfer;
+using Microsoft.Manufacturing.Document;
+using Microsoft.Manufacturing.MachineCenter;
+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.Sales.Customer;
+using Microsoft.Sales.Document;
+using Microsoft.Warehouse.Activity;
+using Microsoft.Warehouse.Document;
+using Microsoft.Warehouse.Ledger;
+using Microsoft.Warehouse.Request;
+using Microsoft.Warehouse.Setup;
+using Microsoft.Warehouse.Structure;
+
+codeunit 149910 "Subc. WIP Transfer Post Test"
+{
+ // [FEATURE] Subcontracting Warehouse Combined Scenarios Tests
+ Subtype = Test;
+ TestType = IntegrationTest;
+
+ [Test]
+ procedure CreateTransferOrderWithWIPItemFlag_CreatesSimpleWIPTransferLine()
+ var
+ FromLocation, ToLocation, InTransitCode : Record Location;
+ Item: Record Item;
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Create Transfer Order with WIP Item and validate Transfer Line fields
+ Initialize();
+ // [GIVEN] Valid From and To Locations, In-Transit Location, and WIP Item details
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(FromLocation);
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(ToLocation);
+ LibraryWarehouse.CreateInTransitLocation(InTransitCode);
+ LibraryInventory.CreateItem(Item);
+ Quantity := 5;
+
+ // [WHEN] Create Transfer Order with WIP Item
+ SubcWarehouseLibrary.CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(
+ TransferHeader, TransferLine, FromLocation.Code, ToLocation.Code, InTransitCode.Code, Item, Quantity);
+
+ // [THEN] Assert TransferLine has WIP flag and every base quantity field is 0 as expected for WIP Item
+ Assert.AreEqual(TransferLine."Transfer-from Code", FromLocation.Code, 'Transfer Line should have correct From Location');
+ Assert.AreEqual(TransferLine."Transfer-to Code", ToLocation.Code, 'Transfer Line should have correct To Location');
+ Assert.IsTrue(TransferLine."Transfer WIP Item", 'Transfer Line should have WIP Item flag set');
+ Assert.AreEqual(TransferLine.Quantity, Quantity, 'Transfer Line should have correct quantity');
+ Assert.AreEqual(TransferLine."Qty. to Ship", Quantity, 'Transfer Line should have correct Qty. to Ship');
+ Assert.AreEqual(TransferLine."Qty. to receive", 0, 'Transfer Line should have correct Qty. to receive');
+ Assert.AreEqual(TransferLine."Outstanding Quantity", Quantity, 'Transfer Line should have correct Outstanding Quantity');
+
+ Assert.AreEqual(TransferLine."Qty. per Unit of Measure", 0, 'Transfer Line should have 0 in Qty. per Unit of Measure for WIP Item');
+ Assert.AreEqual(TransferLine."Quantity (Base)", 0, 'Transfer Line should have 0 in Quantity (Base) for WIP Item');
+ Assert.AreEqual(TransferLine."Qty. to Ship (Base)", 0, 'Transfer Line should have 0 in Qty. to Ship (Base) for WIP Item');
+ Assert.AreEqual(TransferLine."Qty. to Receive (Base)", 0, 'Transfer Line should have 0 in Qty. to Receive (Base) for WIP Item');
+ Assert.AreEqual(TransferLine."Outstanding Qty. (Base)", 0, 'Transfer Line should have 0 in Outstanding Qty. (Base) for WIP Item');
+ end;
+
+ [Test]
+ procedure ToggleWIPFlag_UpdatesBaseQuantitiesCorrectly()
+ var
+ FromLocation, ToLocation, InTransitCode : Record Location;
+ Item: Record Item;
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ QtyPerUOM: Decimal;
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Set WIP-Flag, validate base quantities, then unset WIP-Flag and validate quantities again, and repeat to ensure consistent behavior
+ Initialize();
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(FromLocation);
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(ToLocation);
+ LibraryWarehouse.CreateInTransitLocation(InTransitCode);
+ Quantity := 7;
+
+ // [GIVEN] Transfer order with WIP flag
+ SubcWarehouseLibrary.CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(
+ TransferHeader, TransferLine, FromLocation.Code, ToLocation.Code, InTransitCode.Code, Item, Quantity);
+
+ // [WHEN] Remove WIP flag from Transfer Line
+ TransferLine.Validate("Transfer WIP Item", false);
+ TransferLine.Modify();
+
+ // [THEN] Base quantity fields should be calculated based on Quantity and Qty. per Unit of Measure, and WIP flag should be false
+ QtyPerUOM := 1; // Default value if not set
+ Assert.IsFalse(TransferLine."Transfer WIP Item", 'Transfer Line should not have WIP Item flag set');
+ Assert.AreEqual(TransferLine."Quantity (Base)", Quantity, 'Quantity (Base) should match Quantity');
+ Assert.AreEqual(TransferLine."Qty. to Ship (Base)", Quantity, 'Qty. to Ship (Base) should match Quantity');
+ Assert.AreEqual(TransferLine."Qty. to Receive (Base)", 0, 'Qty. to Receive (Base) should match Quantity');
+ Assert.AreEqual(TransferLine."Outstanding Qty. (Base)", Quantity, 'Outstanding Qty. (Base) should match Quantity');
+
+ // [WHEN] Set WIP flag back on Transfer Line
+ TransferLine.Validate("Transfer WIP Item", true);
+ TransferLine.Modify();
+
+ // [THEN] Base quantity fields should be reset to 0 and WIP flag should be true again
+ Assert.IsTrue(TransferLine."Transfer WIP Item", 'Transfer Line should have WIP Item flag set again');
+ Assert.AreEqual(TransferLine."Qty. per Unit of Measure", 0, 'Qty. per Unit of Measure should be 0 for WIP again');
+ Assert.AreEqual(TransferLine."Quantity (Base)", 0, 'Quantity (Base) should be 0 for WIP again');
+ Assert.AreEqual(TransferLine."Qty. to Ship (Base)", 0, 'Qty. to Ship (Base) should be 0 for WIP again');
+ Assert.AreEqual(TransferLine."Qty. to Receive (Base)", 0, 'Qty. to Receive (Base) should be 0 for WIP again');
+ Assert.AreEqual(TransferLine."Outstanding Qty. (Base)", 0, 'Outstanding Qty. (Base) should be 0 for WIP again');
+ end;
+
+ [Test]
+ procedure PostWIPTransferOrder_ShipPartialReceiveFullReceive()
+ var
+ FromLocation, ToLocation, InTransitCode : Record Location;
+ FromReceiveBin, FromPutAwayBin, ToReceiveBin, ToPutAwayBin : Record Bin;
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ TransferReceiptHeader: Record "Transfer Receipt Header";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ TransferShipmentHeader: Record "Transfer Shipment Header";
+ TransferShipmentLine: Record "Transfer Shipment Line";
+ WarehouseEntry: Record "Warehouse Entry";
+ Quantity, PartialQty : Decimal;
+ ItemLedgerEntryCountBefore, WarehouseEntryCountBefore : Integer;
+ begin
+ // [SCENARIO] Post WIP Transfer Order: first shipment only, then partial receipt, then remaining receipt
+ Initialize();
+
+ // [GIVEN] Transfer Order with WIP Item and bin handling
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(FromLocation, FromReceiveBin, FromPutAwayBin);
+ FromLocation."Require Pick" := false;
+ FromLocation."Require Put-away" := false;
+ FromLocation."Require Receive" := false;
+ FromLocation."Require Shipment" := false;
+ FromLocation.Modify();
+
+ SubcWarehouseLibrary.CreateLocationWithWarehouseHandlingAndBins(ToLocation, ToReceiveBin, ToPutAwayBin);
+ ToLocation."Require Pick" := false;
+ ToLocation."Require Put-away" := false;
+ ToLocation."Require Receive" := false;
+ ToLocation."Require Shipment" := false;
+ ToLocation.Modify();
+ LibraryWarehouse.CreateInTransitLocation(InTransitCode);
+ Quantity := 10;
+ PartialQty := 4;
+
+ LibraryInventory.CreateItem(Item);
+ CreateInventory(Item, FromLocation, FromPutAwayBin, Quantity, '');
+
+ SubcWarehouseLibrary.CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(
+ TransferHeader, TransferLine, FromLocation.Code, ToLocation.Code, InTransitCode.Code, Item, Quantity);
+ TransferLine.Validate("Transfer-from Bin Code", FromPutAwayBin.Code);
+ TransferLine.Validate("Transfer-to Bin Code", ToReceiveBin.Code);
+ TransferLine.Modify();
+
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntryCountBefore := ItemLedgerEntry.Count();
+ WarehouseEntry.SetRange("Item No.", Item."No.");
+ WarehouseEntryCountBefore := WarehouseEntry.Count();
+
+ // [WHEN] Post shipment only
+ LibraryWarehouse.PostTransferOrder(TransferHeader, true, false);
+
+ // [THEN] Transfer Shipment is created and Qty. in Transit equals full quantity. No ItemLedgerEntries should be created since it's a WIP transfer
+#pragma warning disable AA0210
+ TransferShipmentHeader.SetRange("Transfer Order No.", TransferHeader."No.");
+#pragma warning restore AA0210
+ TransferShipmentHeader.FindFirst();
+ TransferShipmentLine.Get(TransferShipmentHeader."No.", 10000);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ Assert.AreEqual(Quantity, TransferLine."Qty. in Transit", 'Qty. in Transit should equal full quantity after shipment');
+ Assert.AreEqual(0, TransferShipmentLine."Quantity (Base)", 'Quantity (Base) on Shipment Line should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferShipmentLine."Qty. per Unit of Measure", 'Qty. per Unit of Measure on Shipment Line should be 0 for WIP transfer');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after shipment of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after shipment of WIP transfer');
+
+ // [WHEN] Post partial receipt
+ TransferLine.Validate("Qty. to receive", PartialQty);
+ TransferLine.Modify();
+ LibraryWarehouse.PostTransferOrder(TransferHeader, false, true);
+
+ // [THEN] Partial Transfer Receipt is created, Quantity Received equals partial and remaining is still in transit
+ TransferReceiptHeader.SetRange("Transfer Order No.", TransferHeader."No.");
+ Assert.IsTrue(TransferReceiptHeader.FindFirst(), 'Partial Transfer Receipt should be posted');
+ TransferReceiptLine.Get(TransferReceiptHeader."No.", 10000);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ Assert.AreEqual(PartialQty, TransferLine."Quantity Received", 'Quantity Received should equal partial quantity after partial receipt');
+ Assert.AreEqual(0, TransferLine."Qty. Shipped (Base)", 'Qty. Shipped (Base) should still be 0 after partial receipt of WIP transfer');
+ Assert.AreEqual(0, TransferLine."Qty. Received (Base)", 'Qty. Received (Base) should be 0 after partial receipt of WIP transfer');
+ Assert.AreEqual(Quantity - PartialQty, TransferLine."Qty. in Transit", 'Qty. in Transit should be remaining quantity after partial receipt');
+ Assert.AreEqual(0, TransferLine."Qty. in Transit (Base)", 'Qty. in Transit (Base) should be 0 after partial receipt of WIP transfer');
+ Assert.AreEqual(0, TransferReceiptLine."Quantity (Base)", 'Quantity (Base) on Receive Line should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferReceiptLine."Qty. per Unit of Measure", 'Qty. per Unit of Measure on Receive Line should be 0 for WIP transfer');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after receive of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after receive of WIP transfer');
+
+ // [WHEN] Post remaining receipt
+ LibraryWarehouse.PostTransferOrder(TransferHeader, false, true);
+
+ // [THEN] Two Transfer Receipts exist and Transfer Order is deleted (completed)
+ Assert.AreEqual(2, TransferReceiptHeader.Count(), 'Two Transfer Receipts should be posted');
+ Assert.IsFalse(TransferHeader.Get(TransferHeader."No."), 'Transfer Order should be deleted after all receipts are posted');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after receive of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after receive of WIP transfer');
+ end;
+
+ [Test]
+ [HandlerFunctions('BinMessageHandler,DeleteWhseReceiptConfimHandler')]
+ procedure PostWIPTransferOrder_FullWhseHandling_ShipPartialReceiveFullReceive()
+ var
+ FromLocation, ToLocation, InTransitCode : Record Location;
+ FromStorageBin, ToReceiveBin, ToPutAwayBin : Record Bin;
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ TransferReceiptHeader: Record "Transfer Receipt Header";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ TransferShipmentHeader: Record "Transfer Shipment Header";
+ TransferShipmentLine: Record "Transfer Shipment Line";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseEntry: Record "Warehouse Entry";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WarehouseShipmentHeader: Record "Warehouse Shipment Header";
+ WarehouseSourceFilter: Record "Warehouse Source Filter";
+ Quantity, PartialQty : Decimal;
+ ItemLedgerEntryCountBefore, WarehouseEntryCountBefore : Integer;
+ begin
+ // [SCENARIO] Post WIP Transfer Order using warehouse documents (Require Shipment, Receive, Pick, Put-Away all true):
+ // ship via warehouse shipment, partial receive and full receive via warehouse receipts.
+ // No Warehouse Picks or Put-Aways must be created despite Require Pick and Require Put-Away being enabled.
+ Initialize();
+
+ // [GIVEN] From location with all warehouse handling flags enabled
+ LibraryWarehouse.CreateFullWMSLocation(FromLocation, 5);
+ LibraryWarehouse.CreateBin(FromStorageBin, FromLocation.Code, 'STORAGE', 'BULK', '');
+ FromLocation.Validate("Default Bin Code", FromStorageBin.Code);
+ FromLocation.Modify(true);
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, FromLocation.Code, false);
+
+ // [GIVEN] To location with all warehouse handling flags enabled
+ LibraryWarehouse.CreateFullWMSLocation(ToLocation, 5);
+ LibraryWarehouse.CreateBin(ToReceiveBin, ToLocation.Code, 'RECEIVE', 'BULK', '');
+ LibraryWarehouse.CreateBin(ToPutAwayBin, ToLocation.Code, 'PUTAWAY', 'BULK', '');
+ ToLocation.Validate("Receipt Bin Code", ToReceiveBin.Code);
+ ToLocation.Validate("Default Bin Code", ToPutAwayBin.Code);
+ ToLocation.Modify(true);
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, ToLocation.Code, true);
+
+ LibraryWarehouse.CreateInTransitLocation(InTransitCode);
+ Quantity := 10;
+ PartialQty := 4;
+
+ LibraryInventory.CreateItem(Item);
+ CreateInventory(Item, FromLocation, FromStorageBin, Quantity, '');
+
+ // [GIVEN] Transfer Order with WIP Item and bin assignment
+ SubcWarehouseLibrary.CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(
+ TransferHeader, TransferLine, FromLocation.Code, ToLocation.Code, InTransitCode.Code, Item, Quantity);
+
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntryCountBefore := ItemLedgerEntry.Count();
+ WarehouseEntry.SetRange("Item No.", Item."No.");
+ WarehouseEntryCountBefore := WarehouseEntry.Count();
+
+ // [WHEN] Release Transfer Order and create Warehouse Shipment
+ LibraryWarehouse.ReleaseTransferOrder(TransferHeader);
+ LibraryWarehouse.CreateWhseShipmentFromTO(TransferHeader);
+
+ // [THEN] No Warehouse Pick is created despite Require Pick = true (WIP transfer bypasses pick creation)
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::Pick);
+ WarehouseActivityLine.SetRange("Location Code", FromLocation.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+
+ // [WHEN] Post Warehouse Shipment
+ WarehouseShipmentHeader.SetRange("Location Code", FromLocation.Code);
+ WarehouseShipmentHeader.FindFirst();
+ LibraryWarehouse.PostWhseShipment(WarehouseShipmentHeader, false);
+
+ // [THEN] Transfer Shipment is created with WIP behavior: base quantities are 0, no item/warehouse ledger entries created
+#pragma warning disable AA0210
+ TransferShipmentHeader.SetRange("Transfer Order No.", TransferHeader."No.");
+#pragma warning restore AA0210
+ TransferShipmentHeader.FindFirst();
+ TransferShipmentLine.Get(TransferShipmentHeader."No.", 10000);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ Assert.AreEqual(Quantity, TransferLine."Qty. in Transit", 'Qty. in Transit should equal full quantity after shipment');
+ Assert.AreEqual(0, TransferShipmentLine."Quantity (Base)", 'Quantity (Base) on Shipment Line should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferShipmentLine."Qty. per Unit of Measure", 'Qty. per Unit of Measure on Shipment Line should be 0 for WIP transfer');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after shipment of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after shipment of WIP transfer');
+
+ // [WHEN] Create Warehouse Receipt for To Location and post partial receive
+ LibraryWarehouse.CreateWarehouseReceiptHeader(WarehouseReceiptHeader);
+ LibraryWarehouse.GetSourceDocumentsReceipt(WarehouseReceiptHeader, WarehouseSourceFilter, ToLocation.Code);
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+ WarehouseReceiptLine.Validate("Qty. to Receive", PartialQty);
+ WarehouseReceiptLine.Modify(true);
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ // [THEN] Partial Transfer Receipt created with WIP behavior and remaining quantity still in transit
+ TransferReceiptHeader.SetRange("Transfer Order No.", TransferHeader."No.");
+ Assert.IsTrue(TransferReceiptHeader.FindFirst(), 'Partial Transfer Receipt should be posted');
+ TransferReceiptLine.Get(TransferReceiptHeader."No.", 10000);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ Assert.AreEqual(PartialQty, TransferLine."Quantity Received", 'Quantity Received should equal partial quantity after partial receipt');
+ Assert.AreEqual(0, TransferLine."Qty. Shipped (Base)", 'Qty. Shipped (Base) should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferLine."Qty. Received (Base)", 'Qty. Received (Base) should be 0 for WIP transfer');
+ Assert.AreEqual(Quantity - PartialQty, TransferLine."Qty. in Transit", 'Qty. in Transit should be remaining quantity after partial receipt');
+ Assert.AreEqual(0, TransferLine."Qty. in Transit (Base)", 'Qty. in Transit (Base) should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferReceiptLine."Quantity (Base)", 'Quantity (Base) on Receipt Line should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferReceiptLine."Qty. per Unit of Measure", 'Qty. per Unit of Measure on Receipt Line should be 0 for WIP transfer');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after partial receive of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after partial receive of WIP transfer');
+
+ // [THEN] No Warehouse Put-Away created despite Require Put-Away = true (WIP transfer bypasses put-away creation)
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Location Code", ToLocation.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+
+ // [WHEN] Create second Warehouse Receipt for remaining quantity and post
+ WarehouseReceiptHeader.Find('=');
+ WarehouseReceiptHeader.Delete(true);
+ LibraryWarehouse.CreateWarehouseReceiptHeader(WarehouseReceiptHeader);
+ LibraryWarehouse.GetSourceDocumentsReceipt(WarehouseReceiptHeader, WarehouseSourceFilter, ToLocation.Code);
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ // [THEN] Two Transfer Receipts exist and Transfer Order is deleted (completed)
+ Assert.AreEqual(2, TransferReceiptHeader.Count(), 'Two Transfer Receipts should be posted');
+ Assert.IsFalse(TransferHeader.Get(TransferHeader."No."), 'Transfer Order should be deleted after all receipts are posted');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after full receive of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after full receive of WIP transfer');
+
+ // [THEN] Still no Warehouse Put-Away after final receive
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Location Code", ToLocation.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('BinMessageHandler,DeleteWhseReceiptConfimHandler')]
+ procedure PostWIPTransferOrder_FullWhseHandling_SerialTrackedItem()
+ var
+ FromLocation, ToLocation, InTransitCode : Record Location;
+ FromStorageBin, ToReceiveBin, ToPutAwayBin : Record Bin;
+ Item: Record Item;
+ ItemLedgerEntry: Record "Item Ledger Entry";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ TransferReceiptHeader: Record "Transfer Receipt Header";
+ TransferReceiptLine: Record "Transfer Receipt Line";
+ TransferShipmentHeader: Record "Transfer Shipment Header";
+ TransferShipmentLine: Record "Transfer Shipment Line";
+ WarehouseActivityLine: Record "Warehouse Activity Line";
+ WarehouseEmployee: Record "Warehouse Employee";
+ WarehouseEntry: Record "Warehouse Entry";
+ WarehouseReceiptHeader: Record "Warehouse Receipt Header";
+ WarehouseReceiptLine: Record "Warehouse Receipt Line";
+ WarehouseShipmentHeader: Record "Warehouse Shipment Header";
+ WarehouseSourceFilter: Record "Warehouse Source Filter";
+ NewLotNo: Code[50];
+ Quantity, PartialQty : Decimal;
+ ItemLedgerEntryCountBefore, WarehouseEntryCountBefore : Integer;
+ TransferOrder: TestPage "Transfer Order";
+ WarehouseReceiptPage: TestPage "Warehouse Receipt";
+ WarehouseShipment: TestPage "Warehouse Shipment";
+ begin
+ // [SCENARIO] Post WIP Transfer Order via full-WMS warehouse documents using a serial-tracked item.
+ // The item tracking actions on the Transfer Order Subform and the Warehouse Receipt Subform
+ // must be Enabled = false for WIP items. Posting succeeds without creating any item or warehouse
+ // ledger/tracking entries, and no Warehouse Picks or Put-Aways are generated.
+ Initialize();
+
+ // [GIVEN] From location with all warehouse handling flags enabled
+ LibraryWarehouse.CreateFullWMSLocation(FromLocation, 5);
+ LibraryWarehouse.CreateBin(FromStorageBin, FromLocation.Code, 'STORAGE', 'BULK', '');
+ FromLocation.Validate("Default Bin Code", FromStorageBin.Code);
+ FromLocation.Modify(true);
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, FromLocation.Code, false);
+
+ // [GIVEN] To location with all warehouse handling flags enabled
+ LibraryWarehouse.CreateFullWMSLocation(ToLocation, 5);
+ LibraryWarehouse.CreateBin(ToReceiveBin, ToLocation.Code, 'RECEIVE', 'BULK', '');
+ LibraryWarehouse.CreateBin(ToPutAwayBin, ToLocation.Code, 'PUTAWAY', 'BULK', '');
+ ToLocation.Validate("Receipt Bin Code", ToReceiveBin.Code);
+ ToLocation.Validate("Default Bin Code", ToPutAwayBin.Code);
+ ToLocation.Modify(true);
+ LibraryWarehouse.CreateWarehouseEmployee(WarehouseEmployee, ToLocation.Code, true);
+
+ LibraryWarehouse.CreateInTransitLocation(InTransitCode);
+ Quantity := 10;
+ PartialQty := 4;
+
+ // [GIVEN] Create lot-tracked item (Lot Warehouse Tracking enabled; Lot Specific = false so that
+ // a plain item-journal inventory adjustment can be posted without assigning lot numbers,
+ // while warehouse activities still enforce lot numbers — which are skipped for WIP transfers)
+ LibraryItemTracking.CreateLotItem(Item);
+ NewLotNo := LibraryUtility.GetNextNoFromNoSeries(LibraryUtility.GetGlobalNoSeriesCode(), WorkDate());
+
+ // [GIVEN] Create inventory at From Location
+ CreateInventory(Item, FromLocation, FromStorageBin, Quantity, NewLotNo);
+
+ // [GIVEN] Transfer Order with WIP Item flag
+ SubcWarehouseLibrary.CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(
+ TransferHeader, TransferLine, FromLocation.Code, ToLocation.Code, InTransitCode.Code, Item, Quantity);
+
+ Commit();
+ TransferOrder.OpenEdit();
+ TransferOrder.GoToRecord(TransferHeader);
+ Assert.IsFalse(TransferOrder.TransferLines.Shipment.Enabled(), 'Shipment action should be disabled on Transfer Order Subform for WIP item');
+ Assert.IsFalse(TransferOrder.TransferLines.Receipt.Enabled(), 'Receipt action should be disabled on Transfer Order Subform for WIP item');
+ Assert.IsFalse(TransferOrder.TransferLines.Reserve.Enabled(), 'Reserve action should be disabled on Transfer Order Subform for WIP item');
+ TransferOrder.Close();
+
+ ItemLedgerEntry.SetRange("Item No.", Item."No.");
+ ItemLedgerEntryCountBefore := ItemLedgerEntry.Count();
+ WarehouseEntry.SetRange("Item No.", Item."No.");
+ WarehouseEntryCountBefore := WarehouseEntry.Count();
+
+ // [WHEN] Release Transfer Order and create Warehouse Shipment
+ LibraryWarehouse.ReleaseTransferOrder(TransferHeader);
+ LibraryWarehouse.CreateWhseShipmentFromTO(TransferHeader);
+
+ // [THEN] No Warehouse Pick is created despite Require Pick = true (WIP transfer bypasses pick creation)
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::Pick);
+ WarehouseActivityLine.SetRange("Location Code", FromLocation.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+ Commit();
+
+ // [THEN] Item Tracking Lines action on Warehouse Shipment Subform should be Enabled = false for WIP item (enforced by page extension Subc. Whse Rcpt Subform Ext.)
+ WarehouseShipmentHeader.SetRange("Location Code", FromLocation.Code);
+ WarehouseShipmentHeader.FindFirst();
+ WarehouseShipment.OpenEdit();
+ WarehouseShipment.GoToRecord(WarehouseShipmentHeader);
+ Assert.IsFalse(WarehouseShipment.WhseShptLines.ItemTrackingLines.Enabled(), 'Item Tracking Lines action should be disabled on Warehouse Shipment Subform for WIP item');
+ WarehouseShipment.Close();
+
+ // [WHEN] Post Warehouse Shipment
+ LibraryWarehouse.PostWhseShipment(WarehouseShipmentHeader, false);
+
+ // [THEN] Transfer Shipment created with WIP behavior: base quantities are 0, no item/warehouse ledger entries
+#pragma warning disable AA0210
+ TransferShipmentHeader.SetRange("Transfer Order No.", TransferHeader."No.");
+#pragma warning restore AA0210
+ TransferShipmentHeader.FindFirst();
+ TransferShipmentLine.Get(TransferShipmentHeader."No.", 10000);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ Assert.AreEqual(Quantity, TransferLine."Qty. in Transit", 'Qty. in Transit should equal full quantity after shipment');
+ Assert.AreEqual(0, TransferShipmentLine."Quantity (Base)", 'Quantity (Base) on Shipment Line should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferShipmentLine."Qty. per Unit of Measure", 'Qty. per Unit of Measure on Shipment Line should be 0 for WIP transfer');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after shipment of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after shipment of WIP transfer');
+
+ // [WHEN] Create Warehouse Receipt for To Location
+ LibraryWarehouse.CreateWarehouseReceiptHeader(WarehouseReceiptHeader);
+ LibraryWarehouse.GetSourceDocumentsReceipt(WarehouseReceiptHeader, WarehouseSourceFilter, ToLocation.Code);
+ WarehouseReceiptLine.SetRange("No.", WarehouseReceiptHeader."No.");
+ WarehouseReceiptLine.FindFirst();
+
+ // [THEN] Verify via TestPage that ItemTrackingLines action is disabled on Warehouse Receipt Subform
+ WarehouseReceiptPage.OpenView();
+ WarehouseReceiptPage.GoToRecord(WarehouseReceiptHeader);
+ WarehouseReceiptPage.WhseReceiptLines.GoToRecord(WarehouseReceiptLine);
+ Assert.IsFalse(WarehouseReceiptPage.WhseReceiptLines.ItemTrackingLines.Enabled(), 'Item Tracking Lines action should be disabled on Warehouse Receipt Subform for WIP item');
+ WarehouseReceiptPage.Close();
+
+ // [WHEN] Post partial receipt
+ WarehouseReceiptLine.Validate("Qty. to Receive", PartialQty);
+ WarehouseReceiptLine.Modify(true);
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ // [THEN] Partial Transfer Receipt created with WIP behavior and remaining qty still in transit
+ TransferReceiptHeader.SetRange("Transfer Order No.", TransferHeader."No.");
+ Assert.IsTrue(TransferReceiptHeader.FindFirst(), 'Partial Transfer Receipt should be posted');
+ TransferReceiptLine.Get(TransferReceiptHeader."No.", 10000);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ Assert.AreEqual(PartialQty, TransferLine."Quantity Received", 'Quantity Received should equal partial quantity after partial receipt');
+ Assert.AreEqual(0, TransferLine."Qty. Shipped (Base)", 'Qty. Shipped (Base) should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferLine."Qty. Received (Base)", 'Qty. Received (Base) should be 0 for WIP transfer');
+ Assert.AreEqual(Quantity - PartialQty, TransferLine."Qty. in Transit", 'Qty. in Transit should be remaining quantity after partial receipt');
+ Assert.AreEqual(0, TransferLine."Qty. in Transit (Base)", 'Qty. in Transit (Base) should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferReceiptLine."Quantity (Base)", 'Quantity (Base) on Receipt Line should be 0 for WIP transfer');
+ Assert.AreEqual(0, TransferReceiptLine."Qty. per Unit of Measure", 'Qty. per Unit of Measure on Receipt Line should be 0 for WIP transfer');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after partial receive of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after partial receive of WIP transfer');
+
+ // [THEN] No Warehouse Put-Away created despite Require Put-Away = true
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Location Code", ToLocation.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+
+ // [WHEN] Delete consumed receipt and create second Warehouse Receipt for remaining quantity
+ WarehouseReceiptHeader.Find('=');
+ WarehouseReceiptHeader.Delete(true);
+ LibraryWarehouse.CreateWarehouseReceiptHeader(WarehouseReceiptHeader);
+ LibraryWarehouse.GetSourceDocumentsReceipt(WarehouseReceiptHeader, WarehouseSourceFilter, ToLocation.Code);
+ LibraryWarehouse.PostWhseReceipt(WarehouseReceiptHeader);
+
+ // [THEN] Two Transfer Receipts exist and Transfer Order is deleted (completed)
+ Assert.AreEqual(2, TransferReceiptHeader.Count(), 'Two Transfer Receipts should be posted');
+ Assert.IsFalse(TransferHeader.Get(TransferHeader."No."), 'Transfer Order should be deleted after all receipts are posted');
+ Assert.AreEqual(ItemLedgerEntryCountBefore, ItemLedgerEntry.Count(), 'No Item Ledger Entries should be created after full receive of WIP transfer');
+ Assert.AreEqual(WarehouseEntryCountBefore, WarehouseEntry.Count(), 'No Warehouse Entries should be created after full receive of WIP transfer');
+
+ // [THEN] No Warehouse Put-Away after final receipt
+ WarehouseActivityLine.SetRange("Activity Type", WarehouseActivityLine."Activity Type"::"Put-away");
+ WarehouseActivityLine.SetRange("Location Code", ToLocation.Code);
+ WarehouseActivityLine.SetRange("Item No.", Item."No.");
+ Assert.RecordIsEmpty(WarehouseActivityLine);
+ end;
+
+ [Test]
+ procedure CalcRegenPlanForPlanWksh_WIPTransferLine_NotConsideredAsDemandOrSupply()
+ var
+ Customer: Record Customer;
+ FromLocation, ToLocation, InTransitCode : Record Location;
+ Item: Record Item;
+ RequisitionLine: Record "Requisition Line";
+ SalesHeader: Record "Sales Header";
+ SalesLine: Record "Sales Line";
+ StockkeepingUnit: Record "Stockkeeping Unit";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Quantity: Decimal;
+ begin
+ // [SCENARIO] Planning Worksheet (Regenerative Plan) does not consider WIP Transfer Lines as demand or supply
+ // because all base quantity fields ("Quantity (Base)", "Outstanding Qty. (Base)", etc.) are 0.
+ // The planning uses SKU-level configuration with Replenishment System = Transfer.
+ Initialize();
+ Quantity := 10;
+
+ // [GIVEN] Locations with Transfer Route and In-Transit Location
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(FromLocation);
+ SubcWarehouseLibrary.CreateLocationWithBinMandatoryOnly(ToLocation);
+ LibraryWarehouse.CreateInTransitLocation(InTransitCode);
+
+ // [GIVEN] An item with a SKU at ToLocation configured for Transfer replenishment from FromLocation
+ LibraryInventory.CreateItem(Item);
+
+ // [GIVEN] A WIP Transfer Order for the item (base quantities = 0)
+ SubcWarehouseLibrary.CreateTransferOrderWithWIPItemFlagWithoutRoutingReference(
+ TransferHeader, TransferLine, FromLocation.Code, ToLocation.Code, InTransitCode.Code, Item, Quantity);
+
+ LibraryInventory.CreateStockkeepingUnitForLocationAndVariant(StockkeepingUnit, ToLocation.Code, Item."No.", '');
+ StockkeepingUnit.Validate("Replenishment System", StockkeepingUnit."Replenishment System"::Transfer);
+ StockkeepingUnit.Validate("Reordering Policy", StockkeepingUnit."Reordering Policy"::"Lot-for-Lot");
+ StockkeepingUnit.Validate("Transfer-from Code", FromLocation.Code);
+ StockkeepingUnit.Modify(true);
+
+ // [GIVEN] Verify the WIP Transfer Line has base quantities = 0 (precondition)
+ Assert.AreEqual(0, TransferLine."Quantity (Base)", 'Precondition: WIP Transfer Line Quantity (Base) must be 0');
+ Assert.AreEqual(0, TransferLine."Outstanding Qty. (Base)", 'Precondition: WIP Transfer Line Outstanding Qty. (Base) must be 0');
+ Assert.AreEqual(0, TransferLine."Qty. per Unit of Measure", 'Precondition: WIP Transfer Line Qty. per Unit of Measure must be 0');
+
+ // [GIVEN] A Sales Order creating real demand for the item at the To Location
+ LibrarySales.CreateCustomer(Customer);
+ LibrarySales.CreateSalesDocumentWithItem(
+ SalesHeader, SalesLine, "Sales Document Type"::Order, Customer."No.", Item."No.", Quantity, ToLocation.Code, WorkDate());
+
+ // [WHEN] Run Regenerative Plan for Planning Worksheet
+ LibraryPlanning.CalcRegenPlanForPlanWksh(Item, CalcDate('<-1M>', WorkDate()), CalcDate('<+1M>', WorkDate()));
+ Commit();
+
+ // [THEN] Requisition Lines are created for the Sales Order demand (Replenishment System = Transfer via SKU)
+ RequisitionLine.SetRange("No.", Item."No.");
+ RequisitionLine.SetRange("Ref. Order Type", RequisitionLine."Ref. Order Type"::Transfer);
+ Assert.IsTrue(RequisitionLine.FindFirst(), 'Planning should create a Requisition Line for the Sales Order demand');
+
+ // [THEN] The planned transfer has the full base quantity (from Sales demand), proving the WIP Transfer Line was NOT used as supply
+ Assert.AreEqual(RequisitionLine.Quantity, Quantity, 'Requisition Line Quantity should equal Sales Order quantity');
+ Assert.AreEqual(Quantity, RequisitionLine."Quantity (Base)", 'Requisition Line Quantity (Base) should equal Sales demand quantity, proving WIP Transfer was not counted as supply');
+
+ // [WHEN] Delete the Planning Worksheet lines, remove the WIP flag from the transfer line and run planning again
+ RequisitionLine.Reset();
+ RequisitionLine.SetRange("No.", Item."No.");
+ RequisitionLine.DeleteAll(true);
+ TransferLine.Get(TransferLine."Document No.", TransferLine."Line No.");
+ TransferLine.Validate("Transfer WIP Item", false);
+ TransferLine.Modify(true);
+ Commit();
+
+ LibraryPlanning.CalcRegenPlanForPlanWksh(Item, CalcDate('<-1M>', WorkDate()), CalcDate('<+1M>', WorkDate()));
+ Commit();
+
+ // [THEN] The transfer line is now considered by planning because it carries non-zero base quantities. No new Requisition Line should be created since the existing transfer can cover the demand.
+ RequisitionLine.Reset();
+ RequisitionLine.SetRange("No.", Item."No.");
+ Assert.RecordIsEmpty(RequisitionLine);
+ end;
+
+ [Test]
+ [HandlerFunctions('DoNotConfirmShowCreatedPurchOrderForSubcontracting,HandleTransferOrder')]
+ procedure PostWIPTransfer_WhenTransferLineHasAlternativeUOM_WIPLedgerEntryHasBaseUOM()
+ var
+ Item: Record Item;
+ ItemUOM: Record "Item Unit of Measure";
+ MachineCenter: array[2] of Record "Machine Center";
+ ProductionOrder: Record "Production Order";
+ PurchaseHeader: Record "Purchase Header";
+ PurchaseLine: Record "Purchase Line";
+ TransferHeader: Record "Transfer Header";
+ TransferLine: Record "Transfer Line";
+ Vendor: Record Vendor;
+ WIPLedgerEntry: Record "Subcontractor WIP Ledger Entry";
+ WorkCenter: array[2] of Record "Work Center";
+ PurchaseHeaderPage: TestPage "Purchase Order";
+ begin
+ // [SCENARIO] WIP Ledger Entries use the item's base unit of measure even when the
+ // subcontracting purchase order line carries a different (alternative) unit of measure.
+ Initialize();
+
+ // [GIVEN] Work centers, machine centers, item with subcontracting routing and Transfer WIP Item flag
+ SubcWarehouseLibrary.CreateAndCalculateNeededWorkAndMachineCenter(WorkCenter, MachineCenter, true);
+ SubcWarehouseLibrary.CreateItemForProductionIncludeRoutingAndProdBOM(Item, WorkCenter, MachineCenter);
+ SetTransferWIPItemOnRoutingLine(Item."Routing No.", WorkCenter[2]."No.", true);
+ SubcontractingMgmtLibrary.UpdateVendorWithSubcontractingLocationCode(WorkCenter[2]);
+
+ // [GIVEN] Item has an additional alternative UOM (6 base units each) — different from the base UOM
+ LibraryInventory.CreateItemUnitOfMeasureCode(ItemUOM, Item."No.", 6);
+
+ // [GIVEN] Released Production Order refreshed at the manufacturing components location
+ LibraryManufacturing.CreateProductionOrder(
+ ProductionOrder, "Production Order Status"::Released,
+ ProductionOrder."Source Type"::Item, Item."No.", LibraryRandom.RandInt(10) + 5);
+ SetProdOrderLocationToCompSetupLocationAndRefresh(ProductionOrder);
+
+ // [GIVEN] Subcontracting Purchase Order created via the Prod. Order Routing page
+ SubcontractingMgmtLibrary.CreateSubcontractingOrderFromProdOrderRtngPage(Item."Routing No.", WorkCenter[2]."No.");
+
+ // [GIVEN] Change the purchase line UOM to the alternative UOM to simulate a non-base-UOM document
+ PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
+ PurchaseLine.SetRange("Prod. Order No.", ProductionOrder."No.");
+ PurchaseLine.FindFirst();
+ PurchaseLine.Validate("Unit of Measure Code", ItemUOM.Code);
+ PurchaseLine.Modify(true);
+ PurchaseHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.");
+
+ // [GIVEN] Create Transfer Route from production order location to subcontractor location
+ Vendor.Get(WorkCenter[2]."Subcontractor No.");
+ CreateAndUpdateTransferRoute(ProductionOrder."Location Code", Vendor."Subc. Location Code");
+
+ // [WHEN] Create WIP Transfer Order to Subcontractor and post the shipment
+ PurchaseHeaderPage.OpenView();
+ PurchaseHeaderPage.GoToRecord(PurchaseHeader);
+ PurchaseHeaderPage.CreateTransfOrdToSubcontractor.Invoke();
+
+ TransferLine.SetRange("Subc. Prod. Order No.", ProductionOrder."No.");
+#pragma warning disable AA0210
+ TransferLine.SetRange("Transfer WIP Item", true);
+#pragma warning restore AA0210
+ TransferLine.FindFirst();
+ TransferHeader.Get(TransferLine."Document No.");
+ LibraryWarehouse.PostTransferOrder(TransferHeader, true, false);
+
+ // [THEN] WIP Ledger Entries carry the item base unit of measure, not the document UOM
+ WIPLedgerEntry.SetRange("Item No.", Item."No.");
+ Assert.IsTrue(WIPLedgerEntry.FindSet(), 'WIP Ledger Entries must be created after posting the WIP transfer shipment.');
+ repeat
+ Assert.AreEqual(Item."Base Unit of Measure", WIPLedgerEntry."Base Unit of Measure",
+ 'WIP Ledger Entry Base Unit of Measure must equal the item base UOM, not the document UOM.');
+ until WIPLedgerEntry.Next() = 0;
+ end;
+
+ [ConfirmHandler]
+ procedure DoNotConfirmShowCreatedPurchOrderForSubcontracting(Question: Text[1024]; var Reply: Boolean)
+ begin
+ Reply := false;
+ end;
+
+ [PageHandler]
+ procedure HandleTransferOrder(var TransfOrderPage: TestPage "Transfer Order")
+ begin
+ TransfOrderPage.OK().Invoke();
+ end;
+
+ [ConfirmHandler]
+ procedure DeleteWhseReceiptConfimHandler(Question: Text[1024]; var Reply: Boolean)
+ begin
+ if Question.Contains('Do you really want to delete the Whse. Receipt') then begin
+ Reply := true;
+ exit;
+ end;
+ Error('Unexpected confirmation message.');
+ end;
+
+ [MessageHandler]
+ procedure BinMessageHandler(Message: Text[1024])
+ begin
+ if Message.Contains('Transfer order') and Message.Contains('was successfully posted') then
+ exit;
+ Error('Unexpected Message.');
+ end;
+
+ var
+ Assert: Codeunit Assert;
+ LibraryERMCountryData: Codeunit "Library - ERM Country Data";
+ LibraryInventory: Codeunit "Library - Inventory";
+ LibraryItemTracking: Codeunit "Library - Item Tracking";
+ LibraryMfgManagement: Codeunit "Subc. Library Mfg. Management";
+ LibraryManufacturing: Codeunit "Library - Manufacturing";
+ LibraryRandom: Codeunit "Library - Random";
+ LibrarySetupStorage: Codeunit "Library - Setup Storage";
+ LibraryTestInitialize: Codeunit "Library - Test Initialize";
+ LibraryUtility: Codeunit "Library - Utility";
+ LibraryPlanning: Codeunit "Library - Planning";
+ LibrarySales: Codeunit "Library - Sales";
+ LibraryWarehouse: Codeunit "Library - Warehouse";
+ SubcontractingMgmtLibrary: Codeunit "Subc. Management Library";
+ SubcWarehouseLibrary: Codeunit "Subc. Warehouse Library";
+ SubSetupLibrary: Codeunit "Subc. Setup Library";
+ IsInitialized: Boolean;
+
+ local procedure Initialize()
+ begin
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Subc. WIP Transfer Post Test");
+ LibrarySetupStorage.Restore();
+
+ SubcontractingMgmtLibrary.Initialize();
+ LibraryMfgManagement.Initialize();
+
+ if IsInitialized then
+ exit;
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Subc. WIP Transfer Post Test");
+
+ SubSetupLibrary.InitSetupFields();
+ LibraryERMCountryData.CreateVATData();
+ SubSetupLibrary.InitialSetupForGenProdPostingGroup();
+ SubcontractingMgmtLibrary.UpdateManufacturingSetupWithSubcontractingLocation();
+ SubcontractingMgmtLibrary.SetupInventorySetup();
+ SubcWarehouseLibrary.UpdateSubMgmtSetupWithReqWkshTemplate();
+
+ IsInitialized := true;
+ Commit();
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Subc. WIP Transfer Post Test");
+ end;
+
+ procedure CreateInventory(Item: Record Item; Location: Record Location; Bin: Record Bin; Quantity: Decimal; LotNo: Code[50])
+ var
+ ItemJournalLine: Record "Item Journal Line";
+ ReservationEntry: Record "Reservation Entry";
+ begin
+ LibraryInventory.CreateItemJournalLineInItemTemplate(
+ ItemJournalLine, Item."No.", Location.Code, Bin.Code, Quantity);
+
+ if LotNo <> '' then
+ LibraryItemTracking.CreateItemJournalLineItemTracking(ReservationEntry, ItemJournalLine, '', LotNo, '', Quantity);
+ LibraryInventory.PostItemJournalLine(ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name");
+ end;
+
+ local procedure SetTransferWIPItemOnRoutingLine(RoutingNo: Code[20]; WorkCenterNo: Code[20]; TransferWIPItem: Boolean)
+ var
+ RoutingHeader: Record "Routing Header";
+ RoutingLine: Record "Routing Line";
+ begin
+ RoutingHeader.Get(RoutingNo);
+ 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);
+ RoutingLine.FindFirst();
+ RoutingLine."Transfer WIP Item" := TransferWIPItem;
+ RoutingLine.Modify(true);
+
+ RoutingHeader.Validate(Status, RoutingHeader.Status::Certified);
+ RoutingHeader.Modify(true);
+ end;
+
+ local procedure SetProdOrderLocationToCompSetupLocationAndRefresh(var ProductionOrder: Record "Production Order")
+ var
+ ManufacturingSetup: Record "Manufacturing Setup";
+ begin
+ ManufacturingSetup.Get();
+ ProductionOrder.Validate("Location Code", ManufacturingSetup."Components at Location");
+ ProductionOrder.Modify();
+ LibraryManufacturing.RefreshProdOrder(ProductionOrder, false, true, true, true, false);
+ end;
+
+ local procedure CreateAndUpdateTransferRoute(FromLocationCode: Code[10]; ToLocationCode: Code[10])
+ var
+ Location: Record Location;
+ TransferRoute: Record "Transfer Route";
+ begin
+ LibraryWarehouse.CreateInTransitLocation(Location);
+ LibraryWarehouse.CreateAndUpdateTransferRoute(
+ TransferRoute, FromLocationCode, ToLocationCode, Location.Code, '', '');
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseCombinedScenarios.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseCombinedScenarios.Codeunit.al
new file mode 100644
index 0000000000..8b27a9f7ff
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. Location Code" := Location.Code;
+ Vendor1."Location Code" := Location.Code;
+ Vendor1.Modify();
+
+ Vendor2.Get(WorkCenter[2]."Subcontractor No.");
+ Vendor2."Subc. 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."Subc. 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/Tests/SubcWhseDataIntegrity.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseDataIntegrity.Codeunit.al
new file mode 100644
index 0000000000..4867e85582
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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."Subc. 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."Subc. 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."Subc. 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/Tests/SubcWhseItemTracking.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseItemTracking.Codeunit.al
new file mode 100644
index 0000000000..d67460a572
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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."Subc. 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."Subc. 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/Tests/SubcWhseLocationConfig.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseLocationConfig.Codeunit.al
new file mode 100644
index 0000000000..e643caa69f
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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."Subc. 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."Subc. 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/Tests/SubcWhseNonLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseNonLastOp.Codeunit.al
new file mode 100644
index 0000000000..af7ba81c09
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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."Subc. 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."Subc. 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."Subc. 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/Tests/SubcWhsePartialLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhsePartialLastOp.Codeunit.al
new file mode 100644
index 0000000000..b269bee58d
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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."Subc. 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."Subc. 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/Tests/SubcWhsePutAwayLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhsePutAwayLastOp.Codeunit.al
new file mode 100644
index 0000000000..07344fb81c
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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/Tests/SubcWhsePutAwayWorksheet.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhsePutAwayWorksheet.Codeunit.al
new file mode 100644
index 0000000000..35e98dd575
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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/Tests/SubcWhseReceiptLastOp.Codeunit.al b/src/Apps/W1/Subcontracting/Test/Tests/SubcWhseReceiptLastOp.Codeunit.al
new file mode 100644
index 0000000000..5073b87924
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/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."Subc. 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."Subc. 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."Subc. 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."Subc. 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."Subc. 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/app.json b/src/Apps/W1/Subcontracting/Test/app.json
new file mode 100644
index 0000000000..1755427138
--- /dev/null
+++ b/src/Apps/W1/Subcontracting/Test/app.json
@@ -0,0 +1,56 @@
+{
+ "id": "32b0d4d1-eee6-4a42-9d76-86a026cd2a71",
+ "name": "Subcontracting Test",
+ "publisher": "Microsoft",
+ "brief": "Tests for the Production Subcontracting App.",
+ "description": "Contains automated tests and test libraries for validating the Production Subcontracting functionality, including production order wizards, purchase subcontracting, and location handling.",
+ "version": "28.3.0.0",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2345592",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2182906",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2345593",
+ "url": "https://go.microsoft.com/fwlink/?linkid=2345594",
+ "logo": "ExtensionLogo.png",
+ "screenshots": [],
+ "dependencies": [
+ {
+ "id": "1f32a50d-0057-4b95-b5df-cc04d7e89470",
+ "name": "Subcontracting",
+ "publisher": "Microsoft",
+ "version": "28.3.0.0"
+ },
+ {
+ "id": "5d86850b-0d76-4eca-bd7b-951ad998e997",
+ "name": "Tests-TestLibraries",
+ "publisher": "Microsoft",
+ "version": "28.3.0.0"
+ },
+ {
+ "id": "5095f467-0a01-4b99-99d1-9ff1237d286f",
+ "publisher": "Microsoft",
+ "name": "Library Variable Storage",
+ "version": "28.3.0.0"
+ }
+ ],
+ "platform": "28.0.0.0",
+ "application": "28.3.0.0",
+ "idRanges": [
+ {
+ "from": 139980,
+ "to": 140009
+ },
+ {
+ "from": 149900,
+ "to": 149930
+ }
+ ],
+ "features": [
+ "TranslationFile",
+ "ExcludeGeneratedTranslations"
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "includeSourceInSymbolFile": true,
+ "allowDownloadingSource": true
+ }
+}
\ No newline at end of file