Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#if not CLEAN28
namespace Microsoft.Manufacturing.Subcontracting.Migration;

using Microsoft.Inventory.Location;
using Microsoft.Inventory.Transfer;
using Microsoft.Manufacturing.Document;
using Microsoft.Manufacturing.Routing;
Expand Down Expand Up @@ -41,6 +42,8 @@ codeunit 149951 "IT Subc. Migration"
#if not CLEAN28
LegacySubcFeatureHandler.CheckCanDisableLegacySubcontracting();
#endif
// Validate before confirmation for prompt feedback, then repeat under locks to prevent concurrent changes.
CheckSubcontractingLocations();
Comment thread
ChethanT marked this conversation as resolved.
UIAllowed := ShowDialog and GuiAllowed();
if UIAllowed then begin
ConfirmDisableLegacySubcontracting();
Expand All @@ -49,6 +52,8 @@ codeunit 149951 "IT Subc. Migration"

LockTables();
Clear(PreMigrationCounts);
// This authoritative validation covers changes made by other sessions while confirmation was pending.
CheckSubcontractingLocations();
Comment thread
ChethanT marked this conversation as resolved.
Comment thread
ChethanT marked this conversation as resolved.
RunMigration();

if UIAllowed then
Expand Down Expand Up @@ -441,6 +446,7 @@ codeunit 149951 "IT Subc. Migration"
RoutingLine: Record "Routing Line";
Vendor: Record Vendor;
PurchaseHeader: Record "Purchase Header";
Location: Record Location;
#pragma warning disable AL0432
LegacySubcontractorPrice: Record "Subcontractor Prices";
#pragma warning restore AL0432
Expand All @@ -455,6 +461,7 @@ codeunit 149951 "IT Subc. Migration"
RoutingLine.LockTable();
Vendor.LockTable();
PurchaseHeader.LockTable();
Location.LockTable();
Comment thread
ChethanT marked this conversation as resolved.
LegacySubcontractorPrice.LockTable();
SubcontractorPrice.LockTable();
ManufacturingSetup.LockTable();
Expand Down Expand Up @@ -573,6 +580,89 @@ codeunit 149951 "IT Subc. Migration"
PurchaseHeader.SetFilter("Subcontracting Location Code", '<>%1', '');
end;

[ErrorBehavior(ErrorBehavior::Collect)]
Comment thread
ChethanT marked this conversation as resolved.
internal procedure CheckSubcontractingLocations()
Comment thread
ChethanT marked this conversation as resolved.
var
Vendor: Record Vendor;
PurchaseHeader: Record "Purchase Header";
Location: Record Location;
LegacySubcontractingLocations: Dictionary of [Code[10], Boolean];
LocationCode: Code[10];
UnsupportedWarehouseSettings: Text;
CollectedErrors: List of [ErrorInfo];
CollectedError: ErrorInfo;
BlockingError: ErrorInfo;
BlockingErrorTextBuilder: TextBuilder;
begin
SetVendorMigrationFilters(Vendor);
Vendor.SetLoadFields("Subcontracting Location Code");
if Vendor.FindSet() then
repeat
AddLegacySubcontractingLocation(LegacySubcontractingLocations, Vendor."Subcontracting Location Code");
until Vendor.Next() = 0;

SetPurchaseHeaderMigrationFilters(PurchaseHeader);
PurchaseHeader.SetLoadFields("Subcontracting Location Code");
if PurchaseHeader.FindSet() then
repeat
AddLegacySubcontractingLocation(LegacySubcontractingLocations, PurchaseHeader."Subcontracting Location Code");
until PurchaseHeader.Next() = 0;

Location.SetLoadFields(
"Bin Mandatory",
"Require Pick",
"Require Put-away",
"Require Receive",
"Require Shipment");
foreach LocationCode in LegacySubcontractingLocations.Keys() do
if not Location.Get(LocationCode) then
Comment thread
ChethanT marked this conversation as resolved.
Error(ErrorInfo.Create(StrSubstNo(MissingSubcontractingLocationErr, LocationCode), true))
else begin
UnsupportedWarehouseSettings := GetUnsupportedWarehouseSettings(Location);
if UnsupportedWarehouseSettings <> '' then
Error(ErrorInfo.Create(StrSubstNo(UnsupportedSubcontractingLocationErr, Location.Code, UnsupportedWarehouseSettings), true));
end;

if HasCollectedErrors() then begin
Comment thread
ChethanT marked this conversation as resolved.
CollectedErrors := GetCollectedErrors(true);
foreach CollectedError in CollectedErrors do
BlockingErrorTextBuilder.AppendLine(CollectedError.Message());
BlockingError.Message := StrSubstNo(SubcontractingLocationsBlockedErr, BlockingErrorTextBuilder.ToText());
BlockingError.DataClassification := DataClassification::CustomerContent;
BlockingError.ErrorType := ErrorType::Client;
BlockingError.Collectible := false;
Error(BlockingError);
end;
end;

local procedure AddLegacySubcontractingLocation(var LegacySubcontractingLocations: Dictionary of [Code[10], Boolean]; LocationCode: Code[10])
begin
if not LegacySubcontractingLocations.ContainsKey(LocationCode) then
LegacySubcontractingLocations.Add(LocationCode, true);
end;

local procedure GetUnsupportedWarehouseSettings(Location: Record Location): Text
var
UnsupportedWarehouseSettings: Text;
begin
AddUnsupportedWarehouseSetting(UnsupportedWarehouseSettings, Location."Bin Mandatory", Location.FieldCaption("Bin Mandatory"));
AddUnsupportedWarehouseSetting(UnsupportedWarehouseSettings, Location."Require Pick", Location.FieldCaption("Require Pick"));
AddUnsupportedWarehouseSetting(UnsupportedWarehouseSettings, Location."Require Put-away", Location.FieldCaption("Require Put-away"));
AddUnsupportedWarehouseSetting(UnsupportedWarehouseSettings, Location."Require Receive", Location.FieldCaption("Require Receive"));
AddUnsupportedWarehouseSetting(UnsupportedWarehouseSettings, Location."Require Shipment", Location.FieldCaption("Require Shipment"));
exit(UnsupportedWarehouseSettings);
end;

local procedure AddUnsupportedWarehouseSetting(var UnsupportedWarehouseSettings: Text; IsEnabled: Boolean; WarehouseSettingCaption: Text)
begin
if not IsEnabled then
exit;

if UnsupportedWarehouseSettings <> '' then
UnsupportedWarehouseSettings += ', ';
UnsupportedWarehouseSettings += WarehouseSettingCaption;
end;

local procedure VerifyMigration()
var
TransferLine: Record "Transfer Line";
Expand Down Expand Up @@ -658,6 +748,9 @@ codeunit 149951 "IT Subc. Migration"
VerifyingPhaseLbl: Label 'Verifying migration...';
VerifyingProgressEntityLbl: Label 'Verification step';
MigrationVerificationFailedErr: Label 'Migration verification failed for %1: expected %2 record(s) but found %3 after migration.', Comment = '%1 = entity name, %2 = pre-migration count, %3 = post-migration count';
SubcontractingLocationsBlockedErr: Label 'Migration can''t start because one or more subcontracting locations are invalid. Resolve the following issues and run the precheck again:\%1', Comment = '%1 = detailed location validation errors';
UnsupportedSubcontractingLocationErr: Label 'Migration can''t start because subcontracting location %1 uses unsupported warehouse settings: %2. Update the location or subcontracting setup, and then run the precheck again.', Comment = '%1 = location code, %2 = unsupported warehouse settings';
MissingSubcontractingLocationErr: Label 'Migration can''t start because legacy subcontracting data references location %1, but that location doesn''t exist. Update the legacy vendor or purchase document, and then run the precheck again.', Comment = '%1 = location code';

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Legacy Subc. Feature Handler", 'OnMigrationSubcontractingData', '', false, false)]
local procedure MigrateSubconOnMigrationSubcontractingData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ codeunit 149956 "IT Subc. Migration Tests"
LibraryWarehouse: Codeunit "Library - Warehouse";
LibraryUtility: Codeunit "Library - Utility";
Initialized: Boolean;
SubcontractingLocationsBlockedErr: Label 'Migration can''t start because one or more subcontracting locations are invalid.';
Comment thread
ChethanT marked this conversation as resolved.
UnsupportedSubcontractingLocationErr: Label 'Migration can''t start because subcontracting location %1 uses unsupported warehouse settings: %2. Update the location or subcontracting setup, and then run the precheck again.', Comment = '%1 = location code, %2 = unsupported warehouse settings';
MissingSubcontractingLocationErr: Label 'Migration can''t start because legacy subcontracting data references location %1, but that location doesn''t exist. Update the legacy vendor or purchase document, and then run the precheck again.', Comment = '%1 = location code';

[Test]
[Scope('OnPrem')]
Expand Down Expand Up @@ -828,6 +831,160 @@ codeunit 149956 "IT Subc. Migration Tests"
'Routing line "Transfer WIP Item" should be migrated.');
end;

[Test]
[Scope('OnPrem')]
procedure CheckSubcontractingLocations_ReportsEveryLocationAndSetting()
var
Vendor: Record Vendor;
PurchaseHeader: Record "Purchase Header";
VendorLocation: Record Location;
PurchaseLocation: Record Location;
ITSubcMigration: Codeunit "IT Subc. Migration";
BlockingError: Text;
begin
// [SCENARIO] The migration precheck reports every incompatible location and its unsupported warehouse settings
Initialize();

// [GIVEN] A vendor whose legacy subcontracting location requires bins and picks
LibraryWarehouse.CreateLocation(VendorLocation);
VendorLocation."Bin Mandatory" := true;
VendorLocation."Require Pick" := true;
VendorLocation.Modify(false);
LibraryPurchase.CreateVendor(Vendor);
Vendor."Subcontracting Location Code" := VendorLocation.Code;
Comment thread
ChethanT marked this conversation as resolved.
Vendor.Modify(false);

// [GIVEN] A purchase header with a different legacy location that requires warehouse handling
LibraryWarehouse.CreateLocation(PurchaseLocation);
PurchaseLocation."Require Put-away" := true;
PurchaseLocation."Require Receive" := true;
PurchaseLocation."Require Shipment" := true;
PurchaseLocation.Modify(false);
LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No.");
PurchaseHeader."Subcontracting Location Code" := PurchaseLocation.Code;
PurchaseHeader.Modify(false);

// [WHEN] The subcontracting location precheck runs
asserterror ITSubcMigration.CheckSubcontractingLocations();
Comment thread
ChethanT marked this conversation as resolved.
Assert.ExpectedError(SubcontractingLocationsBlockedErr);

// [THEN] The blocking error reports every incompatible location and its unsupported settings
BlockingError := GetLastErrorText();
Assert.IsTrue(
BlockingError.Contains(
StrSubstNo(
UnsupportedSubcontractingLocationErr,
VendorLocation.Code,
VendorLocation.FieldCaption("Bin Mandatory") + ', ' + VendorLocation.FieldCaption("Require Pick"))),
'The precheck should report the vendor location and its unsupported settings.');
Assert.IsTrue(
BlockingError.Contains(
StrSubstNo(
UnsupportedSubcontractingLocationErr,
PurchaseLocation.Code,
PurchaseLocation.FieldCaption("Require Put-away") + ', ' +
PurchaseLocation.FieldCaption("Require Receive") + ', ' +
PurchaseLocation.FieldCaption("Require Shipment"))),
'The precheck should report the purchase location and its unsupported settings.');
end;

[Test]
[Scope('OnPrem')]
procedure CheckSubcontractingLocations_ReportsMissingAndUnsupportedLegacyLocations()
var
Vendor: Record Vendor;
PurchaseHeader: Record "Purchase Header";
MissingLocation: Record Location;
UnsupportedLocation: Record Location;
ITSubcMigration: Codeunit "IT Subc. Migration";
BlockingError: Text;
begin
// [SCENARIO] The migration precheck aggregates missing and unsupported legacy subcontracting locations
Initialize();

// [GIVEN] A vendor whose legacy subcontracting location references a deleted location
LibraryWarehouse.CreateLocation(MissingLocation);
LibraryPurchase.CreateVendor(Vendor);
Vendor."Subcontracting Location Code" := MissingLocation.Code;
Vendor.Modify(false);
MissingLocation.Delete(false);

// [GIVEN] A purchase header whose legacy subcontracting location requires bins
LibraryWarehouse.CreateLocation(UnsupportedLocation);
UnsupportedLocation."Bin Mandatory" := true;
UnsupportedLocation.Modify(false);
LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No.");
PurchaseHeader."Subcontracting Location Code" := UnsupportedLocation.Code;
PurchaseHeader.Modify(false);
Commit();

// [WHEN] The subcontracting location precheck runs
asserterror ITSubcMigration.CheckSubcontractingLocations();
Comment thread
ChethanT marked this conversation as resolved.
Assert.ExpectedError(SubcontractingLocationsBlockedErr);

// [THEN] The blocking error reports both legacy location problems
BlockingError := GetLastErrorText();
Assert.IsTrue(
BlockingError.Contains(StrSubstNo(MissingSubcontractingLocationErr, MissingLocation.Code)),
'The precheck should report the missing legacy location.');
Assert.IsTrue(
BlockingError.Contains(
StrSubstNo(
UnsupportedSubcontractingLocationErr,
UnsupportedLocation.Code,
UnsupportedLocation.FieldCaption("Bin Mandatory"))),
'The precheck should continue and report the unsupported legacy location.');
end;

[Test]
[Scope('OnPrem')]
procedure StartDisableLegacySubcontracting_BlocksUnsupportedLocationBeforeMigration()
var
Vendor: Record Vendor;
Location: Record Location;
TransferLine: Record "Transfer Line";
PurchaseLine: Record "Purchase Line";
ITSubcMigration: Codeunit "IT Subc. Migration";
begin
// [SCENARIO] Disabling legacy subcontracting stops before migration when a location has unsupported warehouse settings
Initialize();

// [GIVEN] No open WIP transfers or purchase orders
TransferLine.SetRange("WIP Item", true);
if not TransferLine.IsEmpty() then
TransferLine.DeleteAll();
PurchaseLine.SetRange("Document Type", PurchaseLine."Document Type"::Order);
#pragma warning disable AL0432
PurchaseLine.SetRange("WIP Item", true);
#pragma warning restore AL0432
if not PurchaseLine.IsEmpty() then
PurchaseLine.DeleteAll();

// [GIVEN] A vendor with an unmigrated legacy subcontracting location that requires bins
LibraryWarehouse.CreateLocation(Location);
Location."Bin Mandatory" := true;
Location.Modify(false);
LibraryPurchase.CreateVendor(Vendor);
Vendor."Subcontracting Location Code" := Location.Code;
Vendor."Subc. Location Code" := '';
Vendor.Modify(false);
Commit();

// [WHEN] Legacy subcontracting is disabled
asserterror ITSubcMigration.StartDisableLegacySubcontracting(false);
Comment thread
ChethanT marked this conversation as resolved.

// [THEN] The precheck reports the incompatible location
Assert.ExpectedError(
StrSubstNo(
UnsupportedSubcontractingLocationErr,
Location.Code,
Location.FieldCaption("Bin Mandatory")));

// [THEN] Migration has not changed the vendor
Vendor.Get(Vendor."No.");
Assert.AreEqual('', Vendor."Subc. Location Code", 'The vendor must not be migrated when the precheck fails.');
end;

[Test]
[HandlerFunctions('ConfirmHandlerReturnFalse')]
[Scope('OnPrem')]
Expand Down
Loading