diff --git a/Apps/US/HybridGP_US/app/Permissions/HybridGPUSObjects.PermissionSet.al b/Apps/US/HybridGP_US/app/Permissions/HybridGPUSObjects.PermissionSet.al index eba1882ab5..78a0a0f5b5 100644 --- a/Apps/US/HybridGP_US/app/Permissions/HybridGPUSObjects.PermissionSet.al +++ b/Apps/US/HybridGP_US/app/Permissions/HybridGPUSObjects.PermissionSet.al @@ -17,5 +17,6 @@ permissionset 4713 "HybridGPUS - Objects" codeunit "GP Vendor 1099 Mapping Helpers" = X, codeunit "GP IRS Form Data" = X, page "GP 1099 Migration Log" = X, - page "GP 1099 Migration Log Factbox" = X; + page "GP 1099 Migration Log Factbox" = X, + codeunit "GP IRS1099 Migration Validator" = X; } \ No newline at end of file diff --git a/Apps/US/HybridGP_US/app/src/Codeunits/GPCloudMigrationUS.Codeunit.al b/Apps/US/HybridGP_US/app/src/Codeunits/GPCloudMigrationUS.Codeunit.al index 2f9e15f05d..2ae37e5881 100644 --- a/Apps/US/HybridGP_US/app/src/Codeunits/GPCloudMigrationUS.Codeunit.al +++ b/Apps/US/HybridGP_US/app/src/Codeunits/GPCloudMigrationUS.Codeunit.al @@ -5,13 +5,12 @@ using Microsoft.Purchases.Vendor; codeunit 42004 "GP Cloud Migration US" { - - [EventSubscriber(ObjectType::Codeunit, CodeUnit::"Data Migration Mgt.", 'OnAfterMigrationFinished', '', false, false)] - local procedure OnAfterMigrationFinishedSubscriber(var DataMigrationStatus: Record "Data Migration Status"; WasAborted: Boolean; StartTime: DateTime; Retry: Boolean) + [EventSubscriber(ObjectType::Codeunit, CodeUnit::"Data Migration Mgt.", OnCreatePostMigrationData, '', false, false)] + local procedure OnCreatePostMigrationDataSubscriber(var DataMigrationStatus: Record "Data Migration Status") var HelperFunctions: Codeunit "Helper Functions"; begin - if not (DataMigrationStatus."Migration Type" = HelperFunctions.GetMigrationTypeTxt()) then + if DataMigrationStatus."Migration Type" <> HelperFunctions.GetMigrationTypeTxt() then exit; RunPostMigration(); diff --git a/Apps/US/HybridGP_US/app/src/Codeunits/GPIRS1099MigrationValidator.Codeunit.al b/Apps/US/HybridGP_US/app/src/Codeunits/GPIRS1099MigrationValidator.Codeunit.al new file mode 100644 index 0000000000..9c26b18280 --- /dev/null +++ b/Apps/US/HybridGP_US/app/src/Codeunits/GPIRS1099MigrationValidator.Codeunit.al @@ -0,0 +1,203 @@ +namespace Microsoft.DataMigration.GP; + +using Microsoft.Purchases.Vendor; +using Microsoft.Purchases.Payables; +using Microsoft.Finance.VAT.Reporting; +using Microsoft.DataMigration; + +codeunit 42006 "GP IRS1099 Migration Validator" +{ + trigger OnRun() + var + GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; + begin + if not GPCompanyAdditionalSettings.GetMigrateVendor1099Enabled() then + exit; + + ValidatorCodeLbl := GetValidatorCode(); + CompanyNameTxt := CompanyName(); + + RunVendor1099MigrationValidation(GPCompanyAdditionalSettings); + + MigrationValidation.ReportCompanyValidated(); + end; + + local procedure RunVendor1099MigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + GPPM00200: Record "GP PM00200"; + Vendor: Record Vendor; + VendorLedgerEntry: Record "Vendor Ledger Entry"; + IRS1099VendorFormBoxSetup: Record "IRS 1099 Vendor Form Box Setup"; + GPVendor1099MappingHelpers: Codeunit "GP Vendor 1099 Mapping Helpers"; + IRS1099Code: Code[10]; + ActualIRS1099Code: Code[20]; + TaxAmount: Decimal; + VendorYear1099AmountDictionary: Dictionary of [Code[10], Decimal]; + EntityType: Text[50]; + VendorNo: Code[20]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepVendor1099Lbl) then + exit; + + EntityType := Vendor1099EntityCaptionLbl; + + if GPCompanyAdditionalSettings.GetMigrateVendor1099Enabled() then begin + GPPM00200.SetRange(TEN99TYPE, 2, 5); + GPPM00200.SetFilter(VENDORID, '<>%1', ''); + if GPPM00200.FindSet() then + repeat + VendorNo := CopyStr(GPPM00200.VENDORID.TrimEnd(), 1, MaxStrLen(VendorNo)); + Vendor.SetLoadFields("No.", Name, "Federal ID No."); + if not Vendor.Get(VendorNo) then + continue; + + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, VendorNo); + IRS1099Code := GPVendor1099MappingHelpers.GetIRS1099BoxCode(System.Date2DMY(System.Today(), 3), GPPM00200.TEN99TYPE, GPPM00200.TEN99BOXNUMBER); + + Clear(ActualIRS1099Code); + if IRS1099VendorFormBoxSetup.Get(Format(GPCompanyAdditionalSettings.Get1099TaxYear()), VendorNo) then + ActualIRS1099Code := IRS1099VendorFormBoxSetup."Form Box No."; + + MigrationValidation.ValidateAreEqual(Test_VEND1099IRS1099CODE_Tok, IRS1099Code, ActualIRS1099Code, IRS1099CodeLbl); + MigrationValidation.ValidateAreEqual(Test_VEND1099FEDIDNO_Tok, CopyStr(GPPM00200.TXIDNMBR.TrimEnd(), 1, MaxStrLen(Vendor."Federal ID No.")), Vendor."Federal ID No.", FederalIdNoLbl); + + Clear(VendorYear1099AmountDictionary); + BuildVendor1099Entries(VendorNo, VendorYear1099AmountDictionary); + foreach IRS1099Code in VendorYear1099AmountDictionary.Keys() do begin + TaxAmount := VendorYear1099AmountDictionary.Get(IRS1099Code); + + if TaxAmount > 0 then begin + Clear(VendorLedgerEntry); + VendorLedgerEntry.SetLoadFields(Description, Amount); + VendorLedgerEntry.SetRange("Vendor No.", VendorNo); + VendorLedgerEntry.SetRange("Document Type", VendorLedgerEntry."Document Type"::Payment); + VendorLedgerEntry.SetRange(Description, IRS1099Code); + + if not MigrationValidation.ValidateRecordExists(Test_VEND1099TRXEXISTS_Tok, VendorLedgerEntry.FindFirst(), StrSubstNo(MissingBoxAndAmountLbl, IRS1099Code, TaxAmount)) then + continue; + + VendorLedgerEntry.CalcFields(Amount); + + MigrationValidation.ValidateAreEqual(Test_VEND1099TEN99BOX_Tok, IRS1099Code, VendorLedgerEntry.Description, Vendor1099BoxLbl); + MigrationValidation.ValidateAreEqual(Test_VEND1099TEN99TRXAMT_Tok, TaxAmount, VendorLedgerEntry.Amount, Vendor1099BoxAmountLbl); + end; + end; + + until GPPM00200.Next() = 0; + end; + + LogValidationProgress(ValidationStepVendor1099Lbl); + Commit(); + end; + + local procedure BuildVendor1099Entries(VendorNo: Code[20]; var VendorYear1099AmountDictionary: Dictionary of [Code[10], Decimal]) + var + GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; + GPPM00204: Record "GP PM00204"; + GPVendor1099MappingHelpers: Codeunit "GP Vendor 1099 Mapping Helpers"; + IRS1099Code: Code[10]; + TaxAmount: Decimal; + TaxYear: Integer; + begin + TaxYear := GPCompanyAdditionalSettings.Get1099TaxYear(); + GPPM00204.SetRange(VENDORID, VendorNo); + GPPM00204.SetRange(YEAR1, TaxYear); + GPPM00204.SetFilter(TEN99AMNT, '>0'); + if GPPM00204.FindSet() then + repeat + IRS1099Code := GPVendor1099MappingHelpers.GetIRS1099BoxCode(TaxYear, GPPM00204.TEN99TYPE, GPPM00204.TEN99BOXNUMBER); + if IRS1099Code <> '' then + if VendorYear1099AmountDictionary.Get(IRS1099Code, TaxAmount) then + VendorYear1099AmountDictionary.Set(IRS1099Code, TaxAmount + GPPM00204.TEN99AMNT) + else + VendorYear1099AmountDictionary.Add(IRS1099Code, GPPM00204.TEN99AMNT); + until GPPM00204.Next() = 0; + end; + + local procedure LogValidationProgress(ValidationStep: Code[20]) + begin + Clear(CompanyValidationProgress); + CompanyValidationProgress.Validate("Company Name", CompanyNameTxt); + CompanyValidationProgress.Validate("Validator Code", ValidatorCodeLbl); + CompanyValidationProgress.Validate("Validation Step", ValidationStep); + CompanyValidationProgress.Insert(true); + end; + + internal procedure GetValidatorCode(): Code[20] + begin + exit('GP-US'); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Hybrid Cloud Management", OnPrepareMigrationValidation, '', false, false)] + local procedure OnPrepareMigrationValidation(ProductID: Text[250]) + var + HybridGPWizard: Codeunit "Hybrid GP Wizard"; + begin + if ProductID <> HybridGPWizard.ProductId() then + exit; + + RegisterValidator(); + + AddTest(Test_VEND1099IRS1099CODE_Tok, Vendor1099EntityCaptionLbl, IRS1099CodeLbl); + AddTest(Test_VEND1099FEDIDNO_Tok, Vendor1099EntityCaptionLbl, FederalIdNoLbl); + AddTest(Test_VEND1099TRXEXISTS_Tok, Vendor1099EntityCaptionLbl, Vendor1099MissingTrxLbl); + AddTest(Test_VEND1099TEN99BOX_Tok, Vendor1099EntityCaptionLbl, Vendor1099TrxBoxNoLbl); + AddTest(Test_VEND1099TEN99TRXAMT_Tok, Vendor1099EntityCaptionLbl, Vendor1099TrxAmtLbl); + end; + + local procedure RegisterValidator() + var + MigrationValidatorRegistry: Record "Migration Validator Registry"; + HybridGPWizard: Codeunit "Hybrid GP Wizard"; + ValidatorCode: Code[20]; + MigrationType: Text[250]; + ValidatorCodeunitId: Integer; + begin + ValidatorCode := GetValidatorCode(); + MigrationType := HybridGPWizard.ProductId(); + ValidatorCodeunitId := Codeunit::"GP IRS1099 Migration Validator"; + if not MigrationValidatorRegistry.Get(ValidatorCode) then begin + MigrationValidatorRegistry.Validate("Validator Code", ValidatorCode); + MigrationValidatorRegistry.Validate("Migration Type", MigrationType); + MigrationValidatorRegistry.Validate(Description, ValidatorDescriptionLbl); + MigrationValidatorRegistry.Validate("Codeunit Id", ValidatorCodeunitId); + MigrationValidatorRegistry.Validate(Automatic, false); + MigrationValidatorRegistry.Insert(true); + end; + end; + + local procedure AddTest(Code: Code[30]; Entity: Text[50]; Description: Text) + var + MigrationValidationTest: Record "Migration Validation Test"; + begin + if not MigrationValidationTest.Get(Code, GetValidatorCode()) then begin + MigrationValidationTest.Validate(Code, Code); + MigrationValidationTest.Validate("Validator Code", GetValidatorCode()); + MigrationValidationTest.Validate(Entity, Entity); + MigrationValidationTest.Validate("Test Description", Description); + MigrationValidationTest.Insert(true); + end; + end; + + var + CompanyValidationProgress: Record "Company Validation Progress"; + MigrationValidation: Codeunit "Migration Validation"; + ValidatorCodeLbl: Code[20]; + CompanyNameTxt: Text; + FederalIdNoLbl: Label 'Federal ID No.'; + IRS1099CodeLbl: Label 'IRS 1099 Code'; + MissingBoxAndAmountLbl: Label 'Missing 1099 Box Payment. 1099 Box = %1, Amount = %2', Comment = '%1 = 1099 Box Code, %2 = Amount of the payment'; + Vendor1099BoxLbl: Label '1099 Box'; + Vendor1099BoxAmountLbl: Label '1099 Box Amount'; + Vendor1099MissingTrxLbl: Label 'Missing 1099 transaction'; + Vendor1099TrxBoxNoLbl: Label '1099 transaction Box No/Description'; + Vendor1099TrxAmtLbl: Label '1099 transaction amount'; + Vendor1099EntityCaptionLbl: Label 'Vendor 1099', MaxLength = 50; + ValidationStepVendor1099Lbl: Label 'VENDOR1099', MaxLength = 20; + ValidatorDescriptionLbl: Label 'GP IRS 1099 migration validator', MaxLength = 250; + Test_VEND1099IRS1099CODE_Tok: Label 'VEND1099IRS1099CODE', Locked = true; + Test_VEND1099FEDIDNO_Tok: Label 'VEND1099FEDIDNO', Locked = true; + Test_VEND1099TRXEXISTS_Tok: Label 'VEND1099TRXEXISTS', Locked = true; + Test_VEND1099TEN99BOX_Tok: Label 'VEND1099TEN99BOX', Locked = true; + Test_VEND1099TEN99TRXAMT_Tok: Label 'VEND1099TEN99TRXAMT', Locked = true; +} \ No newline at end of file diff --git a/Apps/US/HybridGP_US/app/src/PageExt/GPUpgradeSettingsExt.PageExt.al b/Apps/US/HybridGP_US/app/src/PageExt/GPUpgradeSettingsExt.PageExt.al new file mode 100644 index 0000000000..9a2be1df0c --- /dev/null +++ b/Apps/US/HybridGP_US/app/src/PageExt/GPUpgradeSettingsExt.PageExt.al @@ -0,0 +1,43 @@ +namespace Microsoft.DataMigration.GP; + +using Microsoft.DataMigration; + +pageextension 41105 "GP Upgrade Settings Ext" extends "GP Upgrade Settings" +{ + layout + { + addafter(GPAutomaticValidation) + { + field(GPUSAutomaticValidation; GPIRS1099AutoValidation) + { + ApplicationArea = All; + Caption = 'GP-US (1099)'; + ToolTip = 'Specifies whether automatic validation is enabled for the GP-US (1099) migration.'; + + trigger OnValidate() + var + MigrationValidationRegistry: Record "Migration Validator Registry"; + GPIRS1099MigrtionValidator: Codeunit "GP IRS1099 Migration Validator"; + begin + if MigrationValidationRegistry.Get(GPIRS1099MigrtionValidator.GetValidatorCode()) then begin + MigrationValidationRegistry.Validate(Automatic, GPIRS1099AutoValidation); + MigrationValidationRegistry.Modify(true); + end; + end; + } + } + } + + trigger OnOpenPage() + var + MigrationValidationRegistry: Record "Migration Validator Registry"; + GPIRS1099MigrtionValidator: Codeunit "GP IRS1099 Migration Validator"; + begin + if MigrationValidationRegistry.Get(GPIRS1099MigrtionValidator.GetValidatorCode()) then + GPIRS1099AutoValidation := MigrationValidationRegistry.Automatic; + + end; + + var + GPIRS1099AutoValidation: Boolean; +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/Permissions/HBDObjects.PermissionSet.al b/Apps/W1/HybridBaseDeployment/app/Permissions/HBDObjects.PermissionSet.al index 1e77694adb..57717e3328 100644 --- a/Apps/W1/HybridBaseDeployment/app/Permissions/HBDObjects.PermissionSet.al +++ b/Apps/W1/HybridBaseDeployment/app/Permissions/HBDObjects.PermissionSet.al @@ -9,5 +9,14 @@ permissionset 4006 "HBD - Objects" Permissions = page "Hybrid DA Approval" = X, page "Add Migration Table Mappings" = X, table "Hybrid Company Status" = X, - table "Hybrid DA Approval" = X; + table "Hybrid DA Approval" = X, + table "Migration Validation Error" = X, + page "Migration Validation Errors" = X, + codeunit "Migration Validation" = X, + page "Company Migration Status" = X, + table "Migration Validation Test" = X, + page "Migration Validation Results" = X, + table "Company Validation Progress" = X, + table "Migration Validation Buffer" = X, + codeunit "Migration Validator Warning" = X; } diff --git a/Apps/W1/HybridBaseDeployment/app/Permissions/INTELLIGENTCLOUDHBD.PermissionSetExt.al b/Apps/W1/HybridBaseDeployment/app/Permissions/INTELLIGENTCLOUDHBD.PermissionSetExt.al index fef39ac989..ac434d385f 100644 --- a/Apps/W1/HybridBaseDeployment/app/Permissions/INTELLIGENTCLOUDHBD.PermissionSetExt.al +++ b/Apps/W1/HybridBaseDeployment/app/Permissions/INTELLIGENTCLOUDHBD.PermissionSetExt.al @@ -18,5 +18,10 @@ permissionsetextension 4003 "INTELLIGENT CLOUD - HBD" extends "INTELLIGENT CLOUD tabledata "Hybrid DA Approval" = rmi, tabledata "Replication Record Link Buffer" = RIMD, tabledata "Record Link Mapping" = RIMD, - tabledata "Cloud Migration Warning" = RIMD; + tabledata "Cloud Migration Warning" = RIMD, + tabledata "Migration Validator Registry" = RIMD, + tabledata "Migration Validation Error" = RIMD, + tabledata "Migration Validation Test" = RIMD, + tabledata "Company Validation Progress" = RIMD, + tabledata "Migration Validation Buffer" = RIMD; } \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicHBD.permissionsetext.al b/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicHBD.permissionsetext.al index 58ca3aad49..b2bdc6a6b5 100644 --- a/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicHBD.permissionsetext.al +++ b/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicHBD.permissionsetext.al @@ -18,5 +18,10 @@ permissionsetextension 4000 "D365 BASIC - HBD" extends "D365 BASIC" tabledata "Replication Run Completed Arg" = RIMD, tabledata "Replication Record Link Buffer" = RIMD, tabledata "Record Link Mapping" = RIMD, - tabledata "Cloud Migration Warning" = RIMD; + tabledata "Cloud Migration Warning" = RIMD, + tabledata "Migration Validator Registry" = RIMD, + tabledata "Migration Validation Error" = RIMD, + tabledata "Migration Validation Test" = RIMD, + tabledata "Company Validation Progress" = RIMD, + tabledata "Migration Validation Buffer" = RIMD; } \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicisvHBD.permissionsetext.al b/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicisvHBD.permissionsetext.al index da1e3cb429..f2cf5e4f9e 100644 --- a/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicisvHBD.permissionsetext.al +++ b/Apps/W1/HybridBaseDeployment/app/Permissions/d365basicisvHBD.permissionsetext.al @@ -19,5 +19,10 @@ permissionsetextension 4001 "D365 BASIC ISV - HBD" extends "D365 BASIC ISV" tabledata "Hybrid DA Approval" = rmi, tabledata "Replication Record Link Buffer" = RIMD, tabledata "Record Link Mapping" = RIMD, - tabledata "Cloud Migration Warning" = RIMD; + tabledata "Cloud Migration Warning" = RIMD, + tabledata "Migration Validator Registry" = RIMD, + tabledata "Migration Validation Error" = RIMD, + tabledata "Migration Validation Test" = RIMD, + tabledata "Company Validation Progress" = RIMD, + tabledata "Migration Validation Buffer" = RIMD; } \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/Permissions/d365teammemberHBD.permissionsetext.al b/Apps/W1/HybridBaseDeployment/app/Permissions/d365teammemberHBD.permissionsetext.al index fb4a7b0b25..7432543007 100644 --- a/Apps/W1/HybridBaseDeployment/app/Permissions/d365teammemberHBD.permissionsetext.al +++ b/Apps/W1/HybridBaseDeployment/app/Permissions/d365teammemberHBD.permissionsetext.al @@ -17,5 +17,10 @@ permissionsetextension 4002 "D365 TEAM MEMBER - HBD" extends "D365 TEAM MEMBER" tabledata "Replication Run Completed Arg" = RIMD, tabledata "Replication Record Link Buffer" = RIMD, tabledata "Record Link Mapping" = RIMD, - tabledata "Cloud Migration Warning" = RIMD; + tabledata "Cloud Migration Warning" = RIMD, + tabledata "Migration Validator Registry" = RIMD, + tabledata "Migration Validation Error" = RIMD, + tabledata "Migration Validation Test" = RIMD, + tabledata "Company Validation Progress" = RIMD, + tabledata "Migration Validation Buffer" = RIMD; } \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/codeunits/HybridCloudManagement.Codeunit.al b/Apps/W1/HybridBaseDeployment/app/src/codeunits/HybridCloudManagement.Codeunit.al index f3bfa762df..ecb6a68f0d 100644 --- a/Apps/W1/HybridBaseDeployment/app/src/codeunits/HybridCloudManagement.Codeunit.al +++ b/Apps/W1/HybridBaseDeployment/app/src/codeunits/HybridCloudManagement.Codeunit.al @@ -649,6 +649,7 @@ codeunit 4001 "Hybrid Cloud Management" IntelligentCloudSetup.Validate("Replication User", UserId()); IntelligentCloudSetup.Modify(); RestoreDefaultMigrationTableMappings(false); + PrepareMigrationValidation(); RefreshIntelligentCloudStatusTable(); CreateCompanies(); @@ -941,6 +942,14 @@ codeunit 4001 "Hybrid Cloud Management" OnInsertDefaultTableMappings(IntelligentCloudSetup."Product ID", DeleteExisting); end; + procedure PrepareMigrationValidation() + IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + begin + if IntelligentCloudSetup.Get() then; + + OnPrepareMigrationValidation(IntelligentCloudSetup."Product ID"); + end; + procedure CompleteCloudMigration() var IntelligentCloudSetup: Record "Intelligent Cloud Setup"; @@ -2223,6 +2232,23 @@ codeunit 4001 "Hybrid Cloud Management" RecordLink.SetRange("To User ID", OldUserName); RecordLink.ModifyAll("To User ID", NewUserName); end; + + local procedure ClearCompanyMigrationValidation(MigrationType: Text[250]) + var + MigrationValidationError: Record "Migration Validation Error"; + CompanyValidationProgress: Record "Company Validation Progress"; + begin + if MigrationType <> '' then + MigrationValidationError.SetRange("Migration Type", MigrationType); + + MigrationValidationError.SetRange("Company Name", CompanyName()); + if not MigrationValidationError.IsEmpty() then + MigrationValidationError.DeleteAll(); + + CompanyValidationProgress.SetRange("Company Name", CompanyName()); + if not CompanyValidationProgress.IsEmpty() then + CompanyValidationProgress.DeleteAll(); + end; [EventSubscriber(ObjectType::Page, Page::Companies, 'OnOpenPageEvent', '', false, false)] local procedure WarnNotToManageCompaniesManually(var Rec: Record Company) @@ -2245,6 +2271,48 @@ codeunit 4001 "Hybrid Cloud Management" SendSetupWebhooksNotification.AddAction(DontShowAgainMsg, Codeunit::"Hybrid Cloud Management", 'DontShowCompaniesWarningNotification'); SendSetupWebhooksNotification.Send(); end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Data Migration Mgt.", OnBeforeMigrationStarted, '', false, false)] + local procedure BeforeMigrationStarted(var DataMigrationStatus: Record "Data Migration Status"; Retry: Boolean) + begin + ClearCompanyMigrationValidation(DataMigrationStatus."Migration Type"); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Data Migration Mgt.", OnValidateMigration, '', false, false)] + local procedure StartMigrationValidation(var DataCreationFailed: Boolean) + begin + StartMigrationValidationImp(DataCreationFailed); + end; + + internal procedure StartMigrationValidationImp(var DataCreationFailed: Boolean) + var + IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + MigrationValidationError: Record "Migration Validation Error"; + MigrationValidation: Codeunit "Migration Validation"; + begin + if DataCreationFailed then + exit; + + if not IntelligentCloudSetup.Get() then + exit; + + MigrationValidation.StartValidation(IntelligentCloudSetup."Product ID", true); + + MigrationValidationError.SetRange("Migration Type", IntelligentCloudSetup."Product ID"); + MigrationValidationError.SetRange("Company Name", CompanyName()); + MigrationValidationError.SetRange("Is Warning", false); + if not MigrationValidationError.IsEmpty() then + DataCreationFailed := true; + end; + + [EventSubscriber(ObjectType::Table, Database::"Company", OnAfterDeleteEvent, '', false, false)] + local procedure CleanupAfterCompanyDelete(var Rec: Record Company; RunTrigger: Boolean) + begin + if Rec.IsTemporary() then + exit; + + ClearCompanyMigrationValidation(''); + end; [IntegrationEvent(false, false)] local procedure OnBackupDataPerDatabase(ProductID: Text[250]; var Handled: Boolean) @@ -2271,6 +2339,11 @@ codeunit 4001 "Hybrid Cloud Management" begin end; + [IntegrationEvent(false, false)] + local procedure OnPrepareMigrationValidation(ProductID: Text[250]) + begin + end; + [IntegrationEvent(false, false)] local procedure OnHandleRunReplication(var Handled: Boolean; var RunId: Text; ReplicationType: Option) begin diff --git a/Apps/W1/HybridBaseDeployment/app/src/codeunits/MigrationValidation.Codeunit.al b/Apps/W1/HybridBaseDeployment/app/src/codeunits/MigrationValidation.Codeunit.al new file mode 100644 index 0000000000..2170f743d6 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/codeunits/MigrationValidation.Codeunit.al @@ -0,0 +1,509 @@ +namespace Microsoft.DataMigration; + +using System.Reflection; +using System.Threading; +using System.Security.User; + +codeunit 40032 "Migration Validation" +{ + trigger OnRun() + var + IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + begin + if not IntelligentCloudSetup.Get() then + exit; + + StartValidation(IntelligentCloudSetup."Product ID", false); + end; + + /// + /// Start migration validation for the current company + /// + /// The type of migration + /// Should only run validators that are set to run automatically + procedure StartValidation(MigrationType: Text; RunOnlyAutomatic: Boolean) + var + MigrationValidatorRegistry: Record "Migration Validator Registry"; + CloudMigrationWarning: Record "Cloud Migration Warning"; + begin + MigrationValidatorRegistry.SetRange("Migration Type", MigrationType); + + if RunOnlyAutomatic then + MigrationValidatorRegistry.SetRange(Automatic, true); + + if not MigrationValidatorRegistry.FindSet() then + exit; + + repeat + Commit(); + if not Codeunit.Run(MigrationValidatorRegistry."Codeunit Id") then begin + Clear(CloudMigrationWarning); + CloudMigrationWarning."Entry No." := 0; + CloudMigrationWarning."Warning Type" := CloudMigrationWarning."Warning Type"::"Migration Validator"; + CloudMigrationWarning.Message := CopyStr(StrSubstNo(CloudMigrationWarningErr, MigrationValidatorRegistry."Validator Code", GetLastErrorText()), 1, MaxStrLen(CloudMigrationWarning.Message)); + CloudMigrationWarning.Insert(); + end; + until MigrationValidatorRegistry.Next() = 0; + + OnMigrationValidated(); + end; + + /// + /// Report that the Company has had validation tests run. + /// + procedure ReportCompanyValidated() + var + HybridCompanyStatus: Record "Hybrid Company Status"; + begin + if HybridCompanyStatus.Get(CompanyName()) then + if not HybridCompanyStatus.Validated then begin + HybridCompanyStatus.Validate(Validated, true); + HybridCompanyStatus.Modify(true); + end; + end; + + /// + /// Set the context for this series of migration validation tests. + /// + /// The validator executing the validation tests. + /// The entity type being tested. + /// The entity Id context. + procedure SetContext(EntryValidatorCode: Code[20]; EntryEntityType: Text[50]; EntryContext: Text[250]) + var + MigrationValidatorRegistry: Record "Migration Validator Registry"; + begin + ContextIsSet := false; + + EntityType := EntryEntityType; + Context := EntryContext; + + if ValidatorCode <> EntryValidatorCode then begin + ValidatorCode := EntryValidatorCode; + + MigrationValidatorRegistry.Get(ValidatorCode); + ContextMigrationType := MigrationValidatorRegistry."Migration Type"; + end; + + if ContextMigrationType = '' then + exit; + + if EntityType = '' then + exit; + + if Context = '' then + exit; + + ContextIsSet := true; + end; + + /// + /// Validate that the actual value matches the expected value. + /// + /// The expected value for the test. + /// The actual value being tested. + /// Description of the test. + procedure ValidateAreEqual(TestCode: Code[30]; Expected: Variant; Actual: Variant; TestDescription: Text[250]): Boolean + begin + exit(ValidateAreEqual(TestCode, Expected, Actual, TestDescription, false)); + end; + + /// + /// Validate that the actual value matches the expected value. + /// + /// The expected value for the test. + /// The actual value being tested. + /// Description of the test. + /// Should the test be handled as a warning. + procedure ValidateAreEqual(TestCode: Code[30]; Expected: Variant; Actual: Variant; TestDescription: Text[250]; ShouldBeWarning: Boolean): Boolean + begin + exit(ValidateAreEqual(TestCode, Expected, Actual, TestDescription, ShouldBeWarning, false)); + end; + + /// + /// Validate that the actual value matches the expected value. + /// + /// The expected value for the test. + /// The actual value being tested. + /// Description of the test. + /// Should the test be handled as a warning. + /// Should the Expected and Actual values be redacted when logged as a validation error. + procedure ValidateAreEqual(TestCode: Code[30]; Expected: Variant; Actual: Variant; TestDescription: Text[250]; ShouldBeWarning: Boolean; ShouldRedact: Boolean): Boolean + begin + AssertContextIsSet(); + + if Equal(Expected, Actual) then + exit(true); + + CreateValidationError(TestCode, Expected, Actual, TestDescription, ShouldBeWarning, ShouldRedact); + end; + + /// + /// Validate that a record exists. + /// + /// Return value of the Get() call on the record being validated. + /// Description of the test. + procedure ValidateRecordExists(TestCode: Code[30]; GetReturnValue: Boolean; TestDescription: Text[250]): Boolean + begin + AssertContextIsSet(); + + if GetReturnValue then + exit(true); + + CreateValidationError(TestCode, MissingExpectedLbl, MissingActualLbl, TestDescription, false, false); + end; + + /// + /// Validate that a record exists. + /// + /// Return value of the Get() call on the record being validated. + /// Description of the test. + /// Should the test be handled as a warning. + procedure ValidateRecordExists(TestCode: Code[30]; GetReturnValue: Boolean; TestDescription: Text[250]; ShouldBeWarning: Boolean): Boolean + begin + AssertContextIsSet(); + + if GetReturnValue then + exit(true); + + CreateValidationError(TestCode, MissingExpectedLbl, MissingActualLbl, TestDescription, ShouldBeWarning, false); + end; + + local procedure CreateValidationError(TestCode: Code[30]; Expected: Variant; Actual: Variant; TestDescription: Text[250]; ShouldBeWarning: Boolean; ShouldRedact: Boolean) + var + MigrationValidationError: Record "Migration Validation Error"; + MigrationValidationTest: record "Migration Validation Test"; + OverrideIsWarning: Boolean; + OverrideShouldRedact: Boolean; + ActualIsWarning: Boolean; + ActualShouldRedact: Boolean; + begin + MigrationValidationTest.SetRange(Code, TestCode); + MigrationValidationTest.SetRange("Validator Code", ValidatorCode); + MigrationValidationTest.SetRange(Ignore, true); + if not MigrationValidationTest.IsEmpty() then + exit; + + ActualIsWarning := ShouldBeWarning; + OverrideIsWarning := ShouldBeWarning; + ActualShouldRedact := ShouldRedact; + OverrideShouldRedact := ShouldRedact; + + OnTestOverrideWarning(ValidatorCode, TestCode, Context, ShouldBeWarning, OverrideIsWarning); + ActualIsWarning := OverrideIsWarning; + + OnTestOverrideShouldRedact(ValidatorCode, TestCode, Context, ShouldRedact, OverrideShouldRedact); + ActualShouldRedact := OverrideShouldRedact; + + if ActualShouldRedact then begin + Expected := RedactedLbl; + Actual := RedactedLbl; + end; + + MigrationValidationError."Entry No." := 0; + MigrationValidationError.Validate("Company Name", CompanyName()); + MigrationValidationError.Validate("Test Code", TestCode); + MigrationValidationError.Validate("Validator Code", ValidatorCode); + MigrationValidationError.Validate("Migration Type", ContextMigrationType); + MigrationValidationError.Validate("Entity Type", EntityType); + MigrationValidationError.Validate(Context, Context); + MigrationValidationError.Validate("Test Description", TestDescription); + MigrationValidationError.Validate(Expected, Expected); + MigrationValidationError.Validate(Actual, Actual); + MigrationValidationError.Validate("Is Warning", ActualIsWarning); + MigrationValidationError.Insert(true); + end; + + /// + /// Delete the past validation errors and other entries for the current Company. + /// + /// The Company that needs the migration validation errors deleted. + procedure DeleteMigrationValidationEntriesForCompany() + begin + DeleteMigrationValidationEntriesForCompany(CompanyName()); + end; + + /// + /// Delete the past validation errors and other entries for the specified Company. + /// + /// The Company that needs the migration validation errors deleted. + procedure DeleteMigrationValidationEntriesForCompany(Company: Text) + var + MigrationValidationError: Record "Migration Validation Error"; + CompanyValidationProgress: Record "Company Validation Progress"; + HybridCompanyStatus: Record "Hybrid Company Status"; + begin + MigrationValidationError.SetRange("Company Name", Company); + if not MigrationValidationError.IsEmpty() then + MigrationValidationError.DeleteAll(true); + + CompanyValidationProgress.SetRange("Company Name", Company); + if not CompanyValidationProgress.IsEmpty() then + CompanyValidationProgress.DeleteAll(true); + + if not HybridCompanyStatus.Get(Company) then + exit; + + HybridCompanyStatus.Validate(Validated, false); + HybridCompanyStatus.Modify(true); + end; + + /// + /// Schedule validation for a Company. + /// + /// The Company context to schedule validation testing. + /// The index number for this scheduled validation. Used to calculate the start time for the scheduled job. + procedure ScheduleCompanyValidation(Company: Text; ScheduledEntryNumber: Integer) + var + TimeoutDuration: Duration; + MaxAttempts: Integer; + TimeBetweenScheduledJobs: Integer; + QueueCategory: Code[10]; + IsHandled: Boolean; + OverrideTimeoutDuration: Duration; + OverrideMaxAttempts: Integer; + OverrideTimeBetweenScheduledJobs: Integer; + StartDateTime: DateTime; + FailoverToSession: Boolean; + SessionId: Integer; + begin + TimeoutDuration := GetDefaultJobTimeout(); + MaxAttempts := GetDefaultJobMaxAttempts(); + TimeBetweenScheduledJobs := GetDefaultTimeBetweenScheduledJobs(); + OverrideTimeoutDuration := TimeoutDuration; + OverrideMaxAttempts := MaxAttempts; + OverrideTimeBetweenScheduledJobs := TimeBetweenScheduledJobs; + QueueCategory := GetJobQueueCategory(); + + OnBeforeCreateMigrationValidationJob(IsHandled, OverrideTimeoutDuration, OverrideMaxAttempts, OverrideTimeBetweenScheduledJobs); + if IsHandled then begin + TimeoutDuration := OverrideTimeoutDuration; + MaxAttempts := OverrideMaxAttempts; + TimeBetweenScheduledJobs := OverrideTimeBetweenScheduledJobs; + end; + + FailoverToSession := not CanStartBackgroundJob(); + + if not FailoverToSession then begin + SendStartValidationResultMessage('', StrSubstNo(TelemetryValidationToBeScheduledMsg, JobQueueLbl), false, false); + + StartDateTime := CurrentDateTime(); + if ScheduledEntryNumber > 1 then + StartDateTime += (TimeBetweenScheduledJobs * (ScheduledEntryNumber - 1)); + + CreateAndScheduleBackgroundJob(Company, + Codeunit::"Migration Validation", + TimeoutDuration, + MaxAttempts, + QueueCategory, + MigrationValidationJobDescriptionTxt, + StartDateTime); + + SendStartValidationResultMessage('', StrSubstNo(TelemetryValidationScheduledMsg, JobQueueLbl), false, true); + end; + + if FailoverToSession then begin + SendStartValidationResultMessage('', StrSubstNo(TelemetryValidationToBeScheduledMsg, SessionLbl), false, false); + + if Session.StartSession(SessionId, Codeunit::"Migration Validation", Company) then + SendStartValidationResultMessage('', StrSubstNo(TelemetryValidationScheduledMsg, SessionLbl), false, true) + else + SendStartValidationResultMessage('', TelemetryValidationFailedToStartSessionMsg, true, true); + end; + end; + + local procedure Equal(Left: Variant; Right: Variant): Boolean + begin + if IsNumber(Left) and IsNumber(Right) then + exit(EqualNumbers(Left, Right)); + + if Left.IsDotNet or Right.IsDotNet then + exit((Format(Left, 0, 2) = Format(Right, 0, 2))); + + exit((TypeOf(Left) = TypeOf(Right)) and (Format(Left, 0, 2) = Format(Right, 0, 2))) + end; + + local procedure EqualNumbers(Left: Decimal; Right: Decimal): Boolean + begin + exit(Left = Right) + end; + + local procedure IsNumber(Value: Variant): Boolean + begin + exit(Value.IsDecimal or Value.IsInteger or Value.IsChar) + end; + + local procedure TypeOf(Value: Variant): Integer + var + "Field": Record "Field"; + begin + case true of + Value.IsBoolean: + exit(Field.Type::Boolean); + Value.IsOption or Value.IsInteger or Value.IsByte: + exit(Field.Type::Integer); + Value.IsBigInteger: + exit(Field.Type::BigInteger); + Value.IsDecimal: + exit(Field.Type::Decimal); + Value.IsText or Value.IsCode or Value.IsChar or Value.IsTextConstant: + exit(Field.Type::Text); + Value.IsDate: + exit(Field.Type::Date); + Value.IsTime: + exit(Field.Type::Time); + Value.IsDuration: + exit(Field.Type::Duration); + Value.IsDateTime: + exit(Field.Type::DateTime); + Value.IsDateFormula: + exit(Field.Type::DateFormula); + Value.IsGuid: + exit(Field.Type::GUID); + Value.IsRecordId: + exit(Field.Type::RecordID); + else + Error(UnsupportedTypeErr, UnsupportedTypeName(Value)) + end + end; + + local procedure UnsupportedTypeName(Value: Variant): Text + begin + case true of + Value.IsRecord: + exit('Record'); + Value.IsRecordRef: + exit('RecordRef'); + Value.IsFieldRef: + exit('FieldRef'); + Value.IsCodeunit: + exit('Codeunit'); + Value.IsAutomation: + exit('Automation'); + Value.IsFile: + exit('File'); + end; + exit('Unsupported Type'); + end; + + local procedure AssertContextIsSet() + begin + if not ContextIsSet then + Error(SetContextErr); + end; + + local procedure CanStartBackgroundJob(): Boolean + var + JobQueueEntry: Record "Job Queue Entry"; + UserPermissions: Codeunit "User Permissions"; + begin + if not UserPermissions.IsSuper(UserSecurityId()) then + exit(false); + + if not TaskScheduler.CanCreateTask() then + exit(false); + + if not JobQueueEntry.WritePermission then + exit(false); + + exit(true); + end; + + local procedure SendStartValidationResultMessage(TelemetryEventId: Text; MessageText: Text; IsError: Boolean; ShouldShowMessage: Boolean) + begin + if IsError then + Session.LogMessage(TelemetryEventId, MessageText, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CloudMigrationTok) + else + Session.LogMessage(TelemetryEventId, MessageText, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CloudMigrationTok); + + if ShouldShowMessage and GuiAllowed() then + Message(MessageText); + end; + + local procedure CreateAndScheduleBackgroundJob(Company: Text; ObjectIdToRun: Integer; TimeoutDuration: Duration; MaxAttempts: Integer; CategoryCode: Code[10]; Description: Text[250]; StartDateTime: DateTime): Guid + var + JobQueueEntry: Record "Job Queue Entry"; + JobQueueEntryBuffer: Record "Job Queue Entry Buffer"; + begin + JobQueueEntry.ChangeCompany(Company); + JobQueueEntryBuffer.ChangeCompany(Company); + + JobQueueEntry.Init(); + JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit; + JobQueueEntry."Object ID to Run" := ObjectIdToRun; + JobQueueEntry."Maximum No. of Attempts to Run" := MaxAttempts; + JobQueueEntry."Job Queue Category Code" := CategoryCode; + JobQueueEntry.Description := Description; + JobQueueEntry."Job Timeout" := TimeoutDuration; + JobQueueEntry."Earliest Start Date/Time" := StartDateTime; + Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry); + + JobQueueEntryBuffer.Init(); + JobQueueEntryBuffer.TransferFields(JobQueueEntry); + JobQueueEntryBuffer."Job Queue Entry ID" := JobQueueEntry.SystemId; + JobQueueEntryBuffer."Start Date/Time" := StartDateTime; + JobQueueEntryBuffer.Insert(); + + exit(JobQueueEntryBuffer.SystemId); + end; + + local procedure GetDefaultJobMaxAttempts(): Integer + begin + exit(10); + end; + + local procedure GetDefaultJobTimeout(): Duration + begin + exit(48 * 60 * 60 * 1000); // 48 hours + end; + + internal procedure GetDefaultTimeBetweenScheduledJobs(): Integer + begin + exit(60 * 1000 * 2); // 2 minutes + end; + + local procedure GetJobQueueCategory(): Code[10] + begin + exit(JobQueueCategoryTok); + end; + + var + ValidatorCode: Code[20]; + ContextMigrationType: Text[250]; + EntityType: Text[50]; + Context: Text[250]; + MissingExpectedLbl: Label 'Expected to exist'; + MissingActualLbl: Label 'Does not exist'; + RedactedLbl: Label ''; + ContextIsSet: Boolean; + SetContextErr: Label 'Context must be set before calling this procedure.'; + UnsupportedTypeErr: Label 'Equality assertions only support Boolean, Option, Integer, BigInteger, Decimal, Code, Text, Date, DateFormula, Time, Duration, and DateTime values. Current value:%1.', Comment = '%1 = The unsupported variant type.'; + CloudMigrationWarningErr: Label '%1 - %2', Comment = '%1 = Validator Code, %2 = Error message'; + TelemetryValidationToBeScheduledMsg: Label 'Migration validation is about to be scheduled. Mode: %1', Comment = '%1 is the mode.', Locked = true; + TelemetryValidationScheduledMsg: Label 'Migration validation is now scheduled. Mode: %1', Comment = '%1 is the mode.', Locked = true; + TelemetryValidationFailedToStartSessionMsg: Label 'Migration validation could not start a new Session.', Locked = true; + MigrationValidationJobDescriptionTxt: Label 'Migration Validation'; + JobQueueLbl: Label 'Job Queue', Locked = true; + SessionLbl: Label 'Session', Locked = true; + JobQueueCategoryTok: Label 'VALIDATION', Locked = true; + CloudMigrationTok: Label 'CloudMigration', Locked = true; + + [IntegrationEvent(false, false)] + local procedure OnTestOverrideWarning(Validator: Code[20]; Test: Code[30]; TestContext: Text[250]; EntryIsWarning: Boolean; var OverrideIsWarning: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnTestOverrideShouldRedact(Validator: Code[20]; Test: Code[30]; TestContext: Text[250]; EntryShouldRedact: Boolean; var OverrideShouldRedact: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeCreateMigrationValidationJob(var IsHandled: Boolean; var TimeoutDuration: Duration; var MaxAttempts: Integer; var TimeBetweenScheduledJobs: Integer) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnMigrationValidated() + begin + end; +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/codeunits/MigrationValidatorWarning.Codeunit.al b/Apps/W1/HybridBaseDeployment/app/src/codeunits/MigrationValidatorWarning.Codeunit.al new file mode 100644 index 0000000000..fc1b98b1d8 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/codeunits/MigrationValidatorWarning.Codeunit.al @@ -0,0 +1,60 @@ +namespace Microsoft.DataMigration; + +codeunit 40031 "Migration Validator Warning" implements "Cloud Migration Warning" +{ + var + MigrationValidatorWarningMsg: Label 'Number of migration validators that had an error during the last migration.'; + + procedure CheckWarning(): Boolean + begin + exit(GetWarningCount() > 0); + end; + + procedure FixWarning() + begin + // Not sure what to put here. + // The migration validators should not fail, and if one does it cannot be fixed by the one running the migration. + // The issue should be reported. + end; + + procedure ShowWarning(var CloudMigrationWarning: Record "Cloud Migration Warning"): Text + var + SearchCloudMigrationWarning: Record "Cloud Migration Warning"; + HybridReplicationSummary: Record "Hybrid Replication Summary"; + FilterTxt: Text; + begin + SearchCloudMigrationWarning.SetRange("Warning Type", SearchCloudMigrationWarning."Warning Type"::"Migration Validator"); + HybridReplicationSummary.SetCurrentKey("Start Time"); + if HybridReplicationSummary.FindLast() then + SearchCloudMigrationWarning.SetFilter(SystemCreatedAt, '>%1', HybridReplicationSummary."End Time"); + + if not SearchCloudMigrationWarning.FindSet() then + exit; + + repeat + FilterTxt := FilterTxt + Format(SearchCloudMigrationWarning."Entry No.") + '|' + until SearchCloudMigrationWarning.Next() = 0; + FilterTxt := FilterTxt.TrimEnd('|'); + + exit(FilterTxt); + end; + + procedure GetWarningMessage(): Text[1024] + begin + exit(MigrationValidatorWarningMsg); + end; + + procedure GetWarningCount(): Integer + var + CloudMigrationWarning: Record "Cloud Migration Warning"; + HybridReplicationSummary: Record "Hybrid Replication Summary"; + begin + CloudMigrationWarning.SetRange("Warning Type", CloudMigrationWarning."Warning Type"::"Migration Validator"); + CloudMigrationWarning.SetRange(Ignored, false); + HybridReplicationSummary.SetCurrentKey("Start Time"); + if HybridReplicationSummary.FindLast() then + CloudMigrationWarning.SetFilter(SystemCreatedAt, '>%1', HybridReplicationSummary."End Time"); + + exit(CloudMigrationWarning.Count()); + end; +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/enums/CloudMigrationWarningType.Enum.al b/Apps/W1/HybridBaseDeployment/app/src/enums/CloudMigrationWarningType.Enum.al index 19f6651f19..777e494f5c 100644 --- a/Apps/W1/HybridBaseDeployment/app/src/enums/CloudMigrationWarningType.Enum.al +++ b/Apps/W1/HybridBaseDeployment/app/src/enums/CloudMigrationWarningType.Enum.al @@ -14,4 +14,9 @@ enum 40010 "Cloud Migration Warning Type" implements "Cloud Migration Warning" Caption = 'Tenant Media'; Implementation = "Cloud Migration Warning" = "Tenant Media Warning"; } + value(3; "Migration Validator") + { + Caption = 'Migration Validator'; + Implementation = "Cloud Migration Warning" = "Migration Validator Warning"; + } } \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/pages/CloudMigrationManagement.Page.al b/Apps/W1/HybridBaseDeployment/app/src/pages/CloudMigrationManagement.Page.al index 26e71a7ca2..0417667729 100644 --- a/Apps/W1/HybridBaseDeployment/app/src/pages/CloudMigrationManagement.Page.al +++ b/Apps/W1/HybridBaseDeployment/app/src/pages/CloudMigrationManagement.Page.al @@ -187,6 +187,24 @@ page 40063 "Cloud Migration Management" end; } } + group(MigrationValidationErrors) + { + ShowCaption = false; + + field("Validation Errors"; ValidationErrors) + { + ApplicationArea = All; + Caption = 'Validation Errors'; + Style = Unfavorable; + StyleExpr = (ValidationErrors > 0); + ToolTip = 'Indicates the total number of failed post migration validation tests, for all migrated companies.'; + + trigger OnDrillDown() + begin + Page.Run(Page::"Migration Validation Errors"); + end; + } + } } } } @@ -733,6 +751,7 @@ page 40063 "Cloud Migration Management" var HybridReplicationSummary: Record "Hybrid Replication Summary"; HybridReplicationDetail: Record "Hybrid Replication Detail"; + MigrationValidationError: Record "Migration Validation Error"; TempHybridReplicationDetail: Record "Hybrid Replication Detail" temporary; HybridReplicationStatistics: Codeunit "Hybrid Replication Statistics"; HybridCloudManagement: Codeunit "Hybrid Cloud Management"; @@ -756,6 +775,8 @@ page 40063 "Cloud Migration Management" HybridReplicationSummary.CalcFields("Companies Not Initialized"); NotInitializedCompaniesCount := HybridReplicationSummary."Companies Not Initialized"; end; + + ValidationErrors := MigrationValidationError.Count(); end; local procedure UpdateControlProperties() @@ -987,4 +1008,5 @@ page 40063 "Cloud Migration Management" CustomTablesEnabled: Boolean; LastRefresh: DateTime; RecordLinkBufferNotEmpty: Boolean; + ValidationErrors: Integer; } diff --git a/Apps/W1/HybridBaseDeployment/app/src/pages/CompanyMigrationStatus.Page.al b/Apps/W1/HybridBaseDeployment/app/src/pages/CompanyMigrationStatus.Page.al new file mode 100644 index 0000000000..379e2f54f9 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/pages/CompanyMigrationStatus.Page.al @@ -0,0 +1,97 @@ +namespace Microsoft.DataMigration; + +page 40066 "Company Migration Status" +{ + ApplicationArea = All; + Caption = 'Company Migration Status'; + PageType = List; + SourceTable = "Hybrid Company Status"; + UsageCategory = None; + InsertAllowed = false; + Editable = false; + DeleteAllowed = false; + + layout + { + area(Content) + { + repeater(General) + { + field(Name; Rec.Name) + { + ToolTip = 'Specifies the value of the Name field.'; + } + field("Upgrade Status"; Rec."Upgrade Status") + { + ToolTip = 'Specifies the value of the Upgrade Status field.'; + } + field(Validated; Rec.Validated) + { + ToolTip = 'Indicates if the company has been validated.'; + } + } + } + } + + actions + { + area(Promoted) + { + actionref(RunAllValidation_Promoted; RunAllValidation) + { + } + } + + area(Processing) + { + action(RunAllValidation) + { + ApplicationArea = All; + Caption = 'Run All Validation'; + ToolTip = 'Run validation on all migrated companies that have yet to be validated.'; + Image = Process; + + trigger OnAction() + var + IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + HybridCompanyStatus: Record "Hybrid Company Status"; + MigrationValidation: Codeunit "Migration Validation"; + ScheduledEntryNumber: Integer; + ForceRun: Boolean; + begin + if not IntelligentCloudSetup.Get() then + exit; + + ForceRun := Dialog.Confirm(ShouldForceValidateQst, false); + + HybridCompanyStatus.SetFilter(Name, '<>%1', ''); + + if not ForceRun then + HybridCompanyStatus.SetRange(Validated, false); + + if not HybridCompanyStatus.FindSet() then begin + Message(NoCompaniesToValidateMsg); + exit; + end; + + ScheduledEntryNumber := 1; + repeat + if ForceRun then + MigrationValidation.DeleteMigrationValidationEntriesForCompany(HybridCompanyStatus.Name); + + MigrationValidation.ScheduleCompanyValidation(HybridCompanyStatus.Name, ScheduledEntryNumber); + + ScheduledEntryNumber += 1; + until HybridCompanyStatus.Next() = 0; + + Message(ValidationScheduledMsg); + end; + } + } + } + + var + ShouldForceValidateQst: Label 'Do you want to force validation on all companies, even if it was previously validated?'; + NoCompaniesToValidateMsg: Label 'No companies need to be validated.'; + ValidationScheduledMsg: Label 'Validation is scheduled.'; +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudManagement.Page.al b/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudManagement.Page.al index e0b3544445..087b72c330 100644 --- a/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudManagement.Page.al +++ b/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudManagement.Page.al @@ -416,6 +416,22 @@ page 4003 "Intelligent Cloud Management" HybridCloudManagement.ChangeRemovePermissionsFromUsers(); end; } + action(ValidationStatus) + { + ApplicationArea = Basic, Suite; + Caption = 'Validation Status'; + Image = Process; + ToolTip = 'View the Company migration status page to manually run validation.'; + Visible = (NumberOfRegisteredValidators > 0); + + trigger OnAction() + var + CompanyMigrationStatus: Page "Company Migration Status"; + begin + CompanyMigrationStatus.RunModal(); + CurrPage.Update(false); + end; + } } } @@ -427,6 +443,7 @@ page 4003 "Intelligent Cloud Management" trigger OnOpenPage() var IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + MigrationValidatorRegistry: Record "Migration Validator Registry"; PermissionManager: Codeunit "Permission Manager"; UserPermissions: Codeunit "User Permissions"; EnvironmentInformation: Codeunit "Environment Information"; @@ -458,9 +475,13 @@ page 4003 "Intelligent Cloud Management" CanShowUpdateReplicationCompanies(UpdateReplicationCompaniesEnabled); CanMapCustomTables(CustomTablesEnabled); - if IntelligentCloudSetup.Get() then + if IntelligentCloudSetup.Get() then begin HybridDeployment.Initialize(IntelligentCloudSetup."Product ID"); + MigrationValidatorRegistry.SetRange("Migration Type", IntelligentCloudSetup."Product ID"); + NumberOfRegisteredValidators := MigrationValidatorRegistry.Count(); + end; + IntelligentCloudNotifier.ShowICUpdateNotification(); WarnAboutNonInitializedCompanies(); @@ -663,4 +684,5 @@ page 4003 "Intelligent Cloud Management" IntelligentCloudNotSetupMsg: Label 'Cloud migration was not set up. To migrate data to the cloud, complete the wizard.'; RunReplicationConfirmQst: Label 'Are you sure you want to trigger migration?'; DataRepairNotCompletedMsg: Label 'Data repair has not completed. Before you complete the cloud migration or trigger an upgrade, invoke the ''Repair Companion Table Records'' action'; + NumberOfRegisteredValidators: Integer; } diff --git a/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudStatFactbox.Page.al b/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudStatFactbox.Page.al index f9bc242922..47575b3089 100644 --- a/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudStatFactbox.Page.al +++ b/Apps/W1/HybridBaseDeployment/app/src/pages/IntelligentCloudStatFactbox.Page.al @@ -180,6 +180,54 @@ page 4008 "Intelligent Cloud Stat Factbox" Page.Run(Page::"Hybrid Companies List"); end; } + + field(Warnings; NumberOfWarnings) + { + ApplicationArea = All; + Editable = false; + Caption = 'Warnings'; + ToolTip = 'Specifies the number of warnings for the selected migration.'; + Style = Unfavorable; + StyleExpr = (NumberOfWarnings > 0); + + trigger OnDrillDown() + begin + Page.Run(Page::"Cloud Migration Warnings"); + end; + } + + field("Validation Errors"; ValidationErrors) + { + ApplicationArea = All; + Caption = 'Validation Errors'; + Style = Unfavorable; + StyleExpr = (ValidationErrors > 0); + ToolTip = 'Indicates the total number of failed post migration validation tests, for all migrated companies.'; + Visible = (NumberOfRegisteredValidators > 0); + + trigger OnDrillDown() + var + MigrationValidationRegistery: Record "Migration Validator Registry"; + MigrationValidationTest: Record "Migration Validation Test"; + MigrationValidationResults: Page "Migration Validation Results"; + ValidatorFilter: Text; + SeparatorChar: Text; + begin + MigrationValidationRegistery.SetRange("Migration Type", MigrationType); + if MigrationValidationRegistery.FindSet() then + repeat + if ValidatorFilter <> '' then + SeparatorChar := '|'; + + ValidatorFilter := SeparatorChar + ValidatorFilter; + until MigrationValidationRegistery.Next() = 0; + + MigrationValidationTest.SetFilter("Validator Code", ValidatorFilter); + MigrationValidationResults.SetTableView(MigrationValidationTest); + MigrationValidationResults.RunModal(); + RefreshStats(); + end; + } } field(Spacer3; '') @@ -194,17 +242,26 @@ page 4008 "Intelligent Cloud Stat Factbox" } } - trigger OnOpenPage() + trigger OnAfterGetRecord() + begin + RefreshStats(); + end; + + procedure RefreshStats() var IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + MigrationValidatorRegistry: Record "Migration Validator Registry"; + MigrationValidationError: Record "Migration Validation Error"; HybridCloudManagement: Codeunit "Hybrid Cloud Management"; begin CanShowTablesNotMigrated(TablesNotMigratedEnabled); if TablesNotMigratedEnabled then TotalTablesNotMigrated := HybridCloudManagement.GetTotalTablesNotMigrated(); - if IntelligentCloudSetup.Get() then + if IntelligentCloudSetup.Get() then begin NextScheduledRun := IntelligentCloudSetup.GetNextScheduledRunDateTime(CurrentDateTime()); + MigrationType := IntelligentCloudSetup."Product ID"; + end; ShowNextScheduled := NextScheduledRun <> 0DT; @@ -213,6 +270,14 @@ page 4008 "Intelligent Cloud Stat Factbox" TotalTablesNotMigrated := HybridCloudManagement.GetTotalTablesNotMigrated(); SourceProduct := HybridCloudManagement.GetChosenProductName(); end; + + UpdateWarningCounts(); + + MigrationValidatorRegistry.SetRange("Migration Type", MigrationType); + NumberOfRegisteredValidators := MigrationValidatorRegistry.Count(); + + MigrationValidationError.SetRange("Migration Type", MigrationType); + ValidationErrors := MigrationValidationError.Count(); end; local procedure ShowTablesNotMigrated() @@ -262,6 +327,21 @@ page 4008 "Intelligent Cloud Stat Factbox" Page.Run(4019, TempIntelligentCloudNotMigrated); end; + local procedure UpdateWarningCounts() + var + ICloudMigrationWarning: Interface "Cloud Migration Warning"; + CloudMigrationWarningType: Enum "Cloud Migration Warning Type"; + WarningImplementations: List of [Integer]; + WarningImplementation: Integer; + begin + NumberOfWarnings := 0; + WarningImplementations := CloudMigrationWarningType.Ordinals(); + foreach WarningImplementation in WarningImplementations do begin + ICloudMigrationWarning := "Cloud Migration Warning Type".FromInteger(WarningImplementation); + NumberOfWarnings += ICloudMigrationWarning.GetWarningCount(); + end; + end; + [IntegrationEvent(false, false)] local procedure CanShowTablesNotMigrated(var Enabled: Boolean) begin @@ -274,5 +354,9 @@ page 4008 "Intelligent Cloud Stat Factbox" TotalTablesNotMigrated: Integer; ShowNextScheduled: Boolean; TablesNotMigratedEnabled: Boolean; + MigrationType: Text; + NumberOfWarnings: Integer; + NumberOfRegisteredValidators: Integer; + ValidationErrors: Integer; } diff --git a/Apps/W1/HybridBaseDeployment/app/src/pages/MigrationValidationErrors.Page.al b/Apps/W1/HybridBaseDeployment/app/src/pages/MigrationValidationErrors.Page.al new file mode 100644 index 0000000000..a9af74ac4d --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/pages/MigrationValidationErrors.Page.al @@ -0,0 +1,86 @@ +namespace Microsoft.DataMigration; + +page 40065 "Migration Validation Errors" +{ + ApplicationArea = All; + Caption = 'Migration Validation Errors'; + PageType = List; + SourceTable = "Migration Validation Error"; + UsageCategory = Lists; + Editable = false; + DeleteAllowed = false; + InsertAllowed = false; + + layout + { + area(Content) + { + repeater(General) + { + field("Company Name"; Rec."Company Name") + { + ToolTip = 'Specifies the value of the Company Name field.'; + } + field("Entity Type"; Rec."Entity Type") + { + ToolTip = 'Specifies the value of the Entity Type field.'; + } + field(Context; Rec.Context) + { + ToolTip = 'Specifies the value of the Context field.'; + } + field("Test Description"; Rec."Test Description") + { + ToolTip = 'Specifies the value of the Test Description field.'; + } + field(Expected; Rec.Expected) + { + ToolTip = 'Specifies the value of the Expected field.'; + } + field(Actual; Rec.Actual) + { + ToolTip = 'Specifies the value of the Actual field.'; + } + field("Is Warning"; Rec."Is Warning") + { + ToolTip = 'Specifies if the failed validation test should be considered just a warning.'; + } + field("Migration Type"; Rec."Migration Type") + { + ToolTip = 'Specifies the value of the Migration Type field.'; + Visible = false; + } + field("Validator Code"; Rec."Validator Code") + { + ToolTip = 'Specifies the value of the Validator Code field.'; + Visible = false; + } + } + } + } + + actions + { + area(Promoted) + { + actionref(CompanyMigrationStatus_Promoted; CompanyMigrationStatus) + { + } + } + area(Navigation) + { + action(CompanyMigrationStatus) + { + ApplicationArea = All; + Caption = 'Company Migration Status'; + Image = Navigate; + ToolTip = 'Open the Company Migration Status page.'; + + trigger OnAction() + begin + Page.Run(Page::"Company Migration Status"); + end; + } + } + } +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/pages/MigrationValidationResults.Page.al b/Apps/W1/HybridBaseDeployment/app/src/pages/MigrationValidationResults.Page.al new file mode 100644 index 0000000000..e29528dad9 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/pages/MigrationValidationResults.Page.al @@ -0,0 +1,149 @@ +namespace Microsoft.DataMigration; + +page 40067 "Migration Validation Results" +{ + ApplicationArea = All; + Caption = 'Migration Validation Results'; + PageType = Worksheet; + SourceTable = "Migration Validation Test"; + UsageCategory = Lists; + DeleteAllowed = false; + InsertAllowed = false; + + layout + { + area(Content) + { + group(Filters) + { + Caption = 'Filters'; + + field(CurrentCompany; CurrentCompanyFilter) + { + Caption = 'Current company only'; + Visible = false; + ToolTip = 'Filter on the current company only or show tests results from all validated companies.'; + + trigger OnValidate() + begin + CurrPage.Update(false); + end; + } + + field(HidePassingTests; HidePassingTestsFilter) + { + Caption = 'Hide passing tests'; + ToolTip = 'Hide test records that do not have any issues found from any validated company.'; + + trigger OnValidate() + begin + ApplyFilterHidePassingTests(); + CurrPage.Update(false); + end; + } + } + + repeater(General) + { + field(FailCount; Rec."Fail Count") + { + Caption = 'Failed'; + ToolTip = 'Specifies the number of failures within the filter criteria.'; + + trigger OnDrillDown() + var + MigrationValidationError: Record "Migration Validation Error"; + MigrationValidationErrors: Page "Migration Validation Errors"; + begin + MigrationValidationError.SetRange("Validator Code", Rec."Validator Code"); + MigrationValidationError.SetRange("Test Code", Rec.Code); + + MigrationValidationErrors.SetTableView(MigrationValidationError); + MigrationValidationErrors.Run(); + end; + } + field(Entity; Rec.Entity) + { + Caption = 'Entity'; + Editable = false; + ToolTip = 'Specifies the value of the Test Description field.'; + } + field("Test Description"; Rec."Test Description") + { + Caption = 'Test Description'; + Editable = false; + ToolTip = 'Specifies the value of the Test Description field.'; + } + field("Code"; Rec."Code") + { + Caption = 'Code'; + Editable = false; + ToolTip = 'Specifies the value of the Code field.'; + } + field("Validator Code"; Rec."Validator Code") + { + Caption = 'Validator'; + Editable = false; + ToolTip = 'Specifies the value of the Validator Code field.'; + } + field(Ignore; Rec.Ignore) + { + ToolTip = 'Specifies the value of the Ignore field.'; + } + } + } + } + + actions + { + area(Promoted) + { + actionref(CompanyMigrationStatus_Promoted; CompanyMigrationStatus) + { + } + } + + area(Navigation) + { + action(CompanyMigrationStatus) + { + ApplicationArea = All; + Caption = 'Company Migration Status'; + Image = Process; + ToolTip = 'Open the Company Migration Status page.'; + + trigger OnAction() + var + CompanyMigrationStatus: Page "Company Migration Status"; + begin + CompanyMigrationStatus.RunModal(); + CurrPage.Update(false); + end; + } + } + } + + trigger OnOpenPage() + begin + HidePassingTestsFilter := true; + + ApplyFilterHidePassingTests(); + Rec.CalcFields("Fail Count"); + Rec.SetCurrentKey("Fail Count"); + Rec.Ascending(false); + + CurrPage.SetTableView(Rec); + end; + + local procedure ApplyFilterHidePassingTests() + begin + if HidePassingTestsFilter then + Rec.SetFilter("Fail Count", '>0') + else + Rec.SetRange("Fail Count"); + end; + + var + CurrentCompanyFilter: Boolean; + HidePassingTestsFilter: Boolean; +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/tables/CompanyValidationProgress.Table.al b/Apps/W1/HybridBaseDeployment/app/src/tables/CompanyValidationProgress.Table.al new file mode 100644 index 0000000000..ebe86c357e --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/tables/CompanyValidationProgress.Table.al @@ -0,0 +1,31 @@ +namespace Microsoft.DataMigration; + +table 40045 "Company Validation Progress" +{ + Caption = 'Company Validation Progress'; + DataClassification = CustomerContent; + DataPerCompany = false; + + fields + { + field(1; "Company Name"; Text[30]) + { + Caption = 'Company Name'; + } + field(2; "Validator Code"; Code[20]) + { + Caption = 'Validator Code'; + } + field(3; "Validation Step"; Code[20]) + { + Caption = 'Validation Step'; + } + } + keys + { + key(PK; "Company Name", "Validator Code", "Validation Step") + { + Clustered = true; + } + } +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/tables/HybridCompanyStatus.Table.al b/Apps/W1/HybridBaseDeployment/app/src/tables/HybridCompanyStatus.Table.al index 1f726f0b1b..488e764813 100644 --- a/Apps/W1/HybridBaseDeployment/app/src/tables/HybridCompanyStatus.Table.al +++ b/Apps/W1/HybridBaseDeployment/app/src/tables/HybridCompanyStatus.Table.al @@ -60,6 +60,10 @@ table 40027 "Hybrid Company Status" Description = 'Tenant Media Count'; DataClassification = SystemMetadata; } + field(10; Validated; Boolean) + { + Description = 'Indicates if the company has been validated.'; + } } keys diff --git a/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationBuffer.Table.al b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationBuffer.Table.al new file mode 100644 index 0000000000..51a586f85f --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationBuffer.Table.al @@ -0,0 +1,111 @@ +namespace Microsoft.DataMigration; + +table 40046 "Migration Validation Buffer" +{ + Caption = 'Migration Validation Buffer'; + DataClassification = CustomerContent; + TableType = Temporary; + + fields + { + field(1; "No."; Text[100]) + { + Caption = 'No.'; + NotBlank = true; + } + field(2; "Parent No."; Text[100]) + { + Caption = 'Parent No.'; + } + field(3; "Text 1"; Text[250]) + { + Caption = 'Text 1'; + } + field(4; "Text 2"; Text[250]) + { + Caption = 'Text 2'; + } + field(5; "Text 3"; Text[250]) + { + Caption = 'Text 3'; + } + field(6; "Text 4"; Text[250]) + { + Caption = 'Text 4'; + } + field(7; "Integer 1"; Integer) + { + Caption = 'Integer 1'; + } + field(8; "Integer 2"; Integer) + { + Caption = 'Integer 2'; + } + field(9; "Integer 3"; Integer) + { + Caption = 'Integer 3'; + } + field(10; "Integer 4"; Integer) + { + Caption = 'Integer 4'; + } + field(11; "Boolean 1"; Boolean) + { + Caption = 'Boolean 1'; + } + field(12; "Boolean 2"; Boolean) + { + Caption = 'Boolean 2'; + } + field(13; "Boolean 3"; Boolean) + { + Caption = 'Boolean 3'; + } + field(14; "Boolean 4"; Boolean) + { + Caption = 'Boolean 4'; + } + field(15; "Decimal 1"; Decimal) + { + Caption = 'Decimal 1'; + } + field(16; "Decimal 2"; Decimal) + { + Caption = 'Decimal 2'; + } + field(17; "Decimal 3"; Decimal) + { + Caption = 'Decimal 3'; + } + field(18; "Decimal 4"; Decimal) + { + Caption = 'Decimal 4'; + } + field(19; "Date 1"; Date) + { + Caption = 'Date 1'; + } + field(20; "Date 2"; Date) + { + Caption = 'Date 2'; + } + field(21; "Date 3"; Date) + { + Caption = 'Date 3'; + } + field(22; "Date 4"; Date) + { + Caption = 'Date 4'; + } + } + keys + { + key(PK; "No.") + { + Clustered = true; + } + key(Key2; "Parent No.") + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationError.Table.al b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationError.Table.al new file mode 100644 index 0000000000..0128eb84a5 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationError.Table.al @@ -0,0 +1,70 @@ +namespace Microsoft.DataMigration; + +table 40043 "Migration Validation Error" +{ + Caption = 'Migration Validation Error'; + DataClassification = CustomerContent; + DataPerCompany = false; + + fields + { + field(1; "Entry No."; Integer) + { + Caption = 'Entry No.'; + AutoIncrement = true; + } + field(2; "Company Name"; Text[30]) + { + Caption = 'Company Name'; + NotBlank = true; + } + field(3; "Test Code"; Code[30]) + { + Caption = 'Test Code'; + NotBlank = true; + TableRelation = "Migration Validation Test"; + } + field(4; "Validator Code"; Code[20]) + { + Caption = 'Validator Code'; + NotBlank = true; + TableRelation = "Migration Validator Registry"; + } + field(5; "Migration Type"; Text[250]) + { + Caption = 'Migration Type'; + } + field(6; "Entity Type"; Text[50]) + { + Caption = 'Entity Type'; + NotBlank = true; + } + field(7; Context; Text[250]) + { + Caption = 'Context'; + } + field(8; "Test Description"; Text[250]) + { + Caption = 'Test Description'; + } + field(9; Expected; Text[250]) + { + Caption = 'Expected'; + } + field(10; Actual; Text[250]) + { + Caption = 'Actual'; + } + field(11; "Is Warning"; Boolean) + { + Caption = 'Is Warning'; + } + } + keys + { + key(PK; "Entry No.") + { + Clustered = true; + } + } +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationTest.Table.al b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationTest.Table.al new file mode 100644 index 0000000000..6e18704e47 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidationTest.Table.al @@ -0,0 +1,48 @@ +namespace Microsoft.DataMigration; + +table 40044 "Migration Validation Test" +{ + Caption = 'Migration Validation Test'; + DataClassification = SystemMetadata; + DataPerCompany = false; + + fields + { + field(1; "Code"; Code[30]) + { + Caption = 'Code'; + } + field(2; "Validator Code"; Code[20]) + { + Caption = 'Validator Code'; + } + field(3; Entity; Text[50]) + { + Caption = 'Test Description'; + } + field(4; "Test Description"; Text[2048]) + { + Caption = 'Test Description'; + } + field(5; "Fail Count"; Integer) + { + Caption = 'Fail Count'; + FieldClass = FlowField; + CalcFormula = count("Migration Validation Error" where("Validator Code" = field("Validator Code"), "Test Code" = field(Code))); + } + field(6; Ignore; Boolean) + { + Caption = 'Ignore'; + } + } + keys + { + key(PK; "Code", "Validator Code") + { + Clustered = true; + } + key(Key2; Ignore) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidatorRegistry.Table.al b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidatorRegistry.Table.al new file mode 100644 index 0000000000..937b881e0e --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/app/src/tables/MigrationValidatorRegistry.Table.al @@ -0,0 +1,40 @@ +namespace Microsoft.DataMigration; + +table 40042 "Migration Validator Registry" +{ + Caption = 'Migration Validator Registry'; + DataClassification = SystemMetadata; + DataPerCompany = false; + + fields + { + field(1; "Validator Code"; Code[20]) + { + Caption = 'Validator Code'; + } + field(2; "Migration Type"; Text[250]) + { + Caption = 'Migration Type'; + } + field(3; "Codeunit Id"; Integer) + { + Caption = 'Codeunit Id'; + } + field(5; Description; Text[2048]) + { + Caption = 'Description'; + } + field(6; Automatic; Boolean) + { + Caption = 'Automatic'; + InitValue = true; + } + } + keys + { + key(PK; "Validator Code") + { + Clustered = true; + } + } +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/test/src/DummyMigrationValidator.Codeunit.al b/Apps/W1/HybridBaseDeployment/test/src/DummyMigrationValidator.Codeunit.al new file mode 100644 index 0000000000..45d6b771a3 --- /dev/null +++ b/Apps/W1/HybridBaseDeployment/test/src/DummyMigrationValidator.Codeunit.al @@ -0,0 +1,96 @@ +codeunit 139501 "Dummy Migration Validator" +{ + trigger OnRun() + begin + RunCustomerMigrationValidation(); + + MigrationValidation.ReportCompanyValidated(); + end; + + local procedure RunCustomerMigrationValidation() + var + Customer: Record Customer; + begin + // [Customer: Test 1] + + // Set the context of this set of tests + MigrationValidation.SetContext(GetValidatorCode(), 'Customer', 'TEST-1'); + + // Check for the entity record by the key + if not MigrationValidation.ValidateRecordExists(Test_CUSTOMEREXISTS_Tok, Customer.Get('TEST-1'), 'Missing TEST-1') then + exit; + + // This is a test that is not a warning, and would fail the migration + MigrationValidation.ValidateAreEqual(Test_CUSTOMERNAME_Tok, 'Test 1', Customer.Name, 'Name'); + + // This is a test that would be just a warning + MigrationValidation.ValidateAreEqual(Test_CUSTOMERNAME2_Tok, 'Test name 2', Customer."Name 2", 'Name 2', true); + + // [Customer: Test 2] + + // Set the context of this set of tests + MigrationValidation.SetContext(GetValidatorCode(), 'Customer', 'TEST-2'); + + // Check for the entity record by the key + if not MigrationValidation.ValidateRecordExists(Test_CUSTOMEREXISTS_Tok, Customer.Get('TEST-2'), 'Missing TEST-2') then + exit; + + // This is a test that is not a warning, and would fail the migration + MigrationValidation.ValidateAreEqual(Test_CUSTOMERNAME_Tok, 'Test 2', Customer.Name, 'Name'); + + // This is a test that would be just a warning + MigrationValidation.ValidateAreEqual(Test_CUSTOMERNAME2_Tok, 'Test name 2', Customer."Name 2", 'Name 2', true); + end; + + internal procedure GetValidatorCode(): Code[20] + begin + exit('TEST'); + end; + + // Normally initialized by this event, but called directly for testing. + //[EventSubscriber(ObjectType::Codeunit, Codeunit::"Hybrid Cloud Management", OnPrepareMigrationValidation, '', false, false)] + internal procedure OnPrepareMigrationValidation(ProductID: Text[250]) + begin + RegisterValidator(ProductID); + + AddTest(Test_CUSTOMEREXISTS_Tok, 'Customer', 'Missing Customer'); + AddTest(Test_CUSTOMERNAME_Tok, 'Customer', 'Name'); + AddTest(Test_CUSTOMERNAME2_Tok, 'Customer', 'Name 2'); + end; + + local procedure RegisterValidator(ProductID: Text[250]) + var + MigrationValidatorRegistry: Record "Migration Validator Registry"; + ValidatorCodeunitId: Integer; + begin + ValidatorCodeunitId := Codeunit::"Dummy Migration Validator"; + if not MigrationValidatorRegistry.Get(GetValidatorCode()) then begin + MigrationValidatorRegistry.Validate("Validator Code", GetValidatorCode()); + MigrationValidatorRegistry.Validate("Migration Type", ProductID); + MigrationValidatorRegistry.Validate(Description, ValidatorDescriptionLbl); + MigrationValidatorRegistry.Validate("Codeunit Id", ValidatorCodeunitId); + MigrationValidatorRegistry.Validate(Automatic, true); + MigrationValidatorRegistry.Insert(true); + end; + end; + + local procedure AddTest(Code: Code[30]; Entity: Text[50]; Description: Text) + var + MigrationValidationTest: Record "Migration Validation Test"; + begin + if not MigrationValidationTest.Get(Code, GetValidatorCode()) then begin + MigrationValidationTest.Validate(Code, Code); + MigrationValidationTest.Validate("Validator Code", GetValidatorCode()); + MigrationValidationTest.Validate(Entity, Entity); + MigrationValidationTest.Validate("Test Description", Description); + MigrationValidationTest.Insert(true); + end; + end; + + var + MigrationValidation: Codeunit "Migration Validation"; + ValidatorDescriptionLbl: Label 'Dummy migration validator', MaxLength = 250; + Test_CUSTOMEREXISTS_Tok: Label 'CUSTOMEREXISTS', Locked = true; + Test_CUSTOMERNAME_Tok: Label 'CUSTOMERNAME', Locked = true; + Test_CUSTOMERNAME2_Tok: Label 'CUSTOMERNAME2', Locked = true; +} \ No newline at end of file diff --git a/Apps/W1/HybridBaseDeployment/test/src/HybridCloudManagementTests.Codeunit.al b/Apps/W1/HybridBaseDeployment/test/src/HybridCloudManagementTests.Codeunit.al index 8d94c25712..6dc9b9bfc9 100644 --- a/Apps/W1/HybridBaseDeployment/test/src/HybridCloudManagementTests.Codeunit.al +++ b/Apps/W1/HybridBaseDeployment/test/src/HybridCloudManagementTests.Codeunit.al @@ -706,6 +706,195 @@ codeunit 139656 "Hybrid Cloud Management Tests" Assert.IsTrue(HybridCompanyStatus."Record Link Move Completed", 'Record links migration status not updated'); end; + [Test] + procedure TestMigrationValidation() + var + MigrationValidationError: Record "Migration Validation Error"; + Customer: Record Customer; + HybridCompanyStatus: Record "Hybrid Company Status"; + MigrationValidation: Codeunit "Migration Validation"; + HybridCloudManagement: Codeunit "Hybrid Cloud Management"; + DataCreationFailed: Boolean; + begin + // [GIVEN] A company migration is being validated + InitMigrationValidationTests(); + + DataCreationFailed := false; + + // [WHEN] No customers were migrated, but were expected + HybridCloudManagement.StartMigrationValidationImp(DataCreationFailed); + + // [THEN] The migration will fail, and there will be corresponding validation error entries + HybridCompanyStatus.Get(CompanyName()); + Assert.IsTrue(HybridCompanyStatus.Validated, 'The company should have been validated.'); + Assert.RecordCount(MigrationValidationError, 1); + MigrationValidationError.FindFirst(); + Assert.AreEqual('Missing TEST-1', MigrationValidationError."Test Description", 'Incorrect test description'); + Assert.AreEqual(false, MigrationValidationError."Is Warning", 'Incorrect value for Is Warning'); + Assert.IsTrue(DataCreationFailed, 'The migration should be in a failed state.'); + + // Reset + DataCreationFailed := false; + MigrationValidation.DeleteMigrationValidationEntriesForCompany(); + + // [WHEN] Some of the customers were created + // Create Customer TEST-1 + InitMigrationValidationTest_CustomerTest1(); + HybridCloudManagement.StartMigrationValidationImp(DataCreationFailed); + + // [THEN] The migration will fail, and there will be corresponding validation error entries + Assert.IsTrue(DataCreationFailed, 'The migration should be in a failed state.'); + Assert.RecordCount(MigrationValidationError, 1); + MigrationValidationError.FindFirst(); + Assert.AreEqual('Missing TEST-2', MigrationValidationError."Test Description", 'Incorrect test description'); + Assert.AreEqual(false, MigrationValidationError."Is Warning", 'Incorrect value for Is Warning'); + + // Reset + DataCreationFailed := false; + MigrationValidation.DeleteMigrationValidationEntriesForCompany(); + + // [WHEN] All the customers were created and correct + InitMigrationValidationTest_CustomerTest1(); + InitMigrationValidationTest_CustomerTest2(); + HybridCloudManagement.StartMigrationValidationImp(DataCreationFailed); + + // [THEN] The migration will be successful, and there won't be any validation error entries + Assert.IsFalse(DataCreationFailed, 'The migration should be in a failed state.'); + Assert.RecordCount(MigrationValidationError, 0); + + // Reset + DataCreationFailed := false; + MigrationValidation.DeleteMigrationValidationEntriesForCompany(); + + // [WHEN] Some values are unexpected + Customer.GET('TEST-1'); + Customer.Name := 'Wrong name'; + Customer."Name 2" := 'Wrong name 2'; + Customer.Modify(); + + HybridCloudManagement.StartMigrationValidationImp(DataCreationFailed); + + // [TEST] The correct validation error records will be added + // The migration will be in a failed state because there is an entry that isn't a warning + Assert.RecordCount(MigrationValidationError, 2); + Assert.IsTrue(DataCreationFailed, 'The migration should be in a failed state.'); + + MigrationValidationError.FindSet(); + Assert.AreEqual('Name', MigrationValidationError."Test Description", 'Incorrect test description'); + Assert.AreEqual('Test 1', MigrationValidationError.Expected, 'Incorrect Expected value'); + Assert.AreEqual('Wrong name', MigrationValidationError.Actual, 'Incorrect Actual value'); + Assert.AreEqual(false, MigrationValidationError."Is Warning", 'Incorrect value for Is Warning'); + + MigrationValidationError.Next(); + Assert.AreEqual('Name 2', MigrationValidationError."Test Description", 'Incorrect test description'); + Assert.AreEqual('Test name 2', MigrationValidationError.Expected, 'Incorrect Expected value'); + Assert.AreEqual('Wrong name 2', MigrationValidationError.Actual, 'Incorrect Actual value'); + Assert.AreEqual(true, MigrationValidationError."Is Warning", 'Incorrect value for Is Warning'); + + // Reset + DataCreationFailed := false; + MigrationValidation.DeleteMigrationValidationEntriesForCompany(); + + // [WHEN] Some values are unexpected, but nothing considered major + Customer.GET('TEST-1'); + Customer.Name := 'Test 1'; // Back to expected value + Customer."Name 2" := 'Wrong name 2'; + Customer.Modify(); + + HybridCloudManagement.StartMigrationValidationImp(DataCreationFailed); + Assert.RecordCount(MigrationValidationError, 1); + Assert.IsFalse(DataCreationFailed, 'The migration should NOT be in a failed state.'); + end; + + local procedure InitMigrationValidationTests() + var + MigrationValidatorRegistry: Record "Migration Validator Registry"; + MigrationValidationError: Record "Migration Validation Error"; + DataMigrationStatus: Record "Data Migration Status"; + HybridCompany: Record "Hybrid Company"; + HybridCompanyStatus: Record "Hybrid Company Status"; + IntelligentCloudSetup: Record "Intelligent Cloud Setup"; + DummyMigrationValidator: Codeunit "Dummy Migration Validator"; + ValidatorCode: Code[20]; + MigrationType: Text[250]; + ValidatorCodeunitId: Integer; + begin + ValidatorCode := DummyMigrationValidator.GetValidatorCode(); + ValidatorCodeunitId := Codeunit::"Dummy Migration Validator"; + + if not IntelligentCloudSetup.Get() then begin + IntelligentCloudSetup."Product ID" := GetDefaultTestMigrationType(); + IntelligentCloudSetup.Insert(); + end; + + MigrationType := IntelligentCloudSetup."Product ID"; + + if not DataMigrationStatus.IsEmpty() then + DataMigrationStatus.DeleteAll(); + + if not MigrationValidationError.IsEmpty() then + MigrationValidationError.DeleteAll(); + + if not MigrationValidatorRegistry.IsEmpty() then + MigrationValidatorRegistry.DeleteAll(); + + if not MigrationValidatorRegistry.Get(ValidatorCode) then begin + MigrationValidatorRegistry.Validate("Validator Code", ValidatorCode); + MigrationValidatorRegistry.Validate("Migration Type", MigrationType); + MigrationValidatorRegistry.Validate("Codeunit Id", ValidatorCodeunitId); + MigrationValidatorRegistry.Insert(); + end; + + if not HybridCompany.Get(CompanyName()) then begin + HybridCompany.Name := CopyStr(CompanyName(), 1, MaxStrLen(HybridCompany.Name)); + HybridCompany.Insert(); + end; + + if not HybridCompanyStatus.Get(CompanyName()) then begin + HybridCompanyStatus.Name := CopyStr(CompanyName(), 1, MaxStrLen(HybridCompanyStatus.Name)); + HybridCompanyStatus.Insert(); + end; + + HybridCompanyStatus.Validated := false; + HybridCompany.Modify(); + + Clear(DataMigrationStatus); + DataMigrationStatus."Migration Type" := IntelligentCloudSetup."Product ID"; + DataMigrationStatus.Status := DataMigrationStatus.Status::"In Progress"; + DataMigrationStatus.Insert(true); + + DummyMigrationValidator.OnPrepareMigrationValidation(MigrationType); + end; + + local procedure InitMigrationValidationTest_CustomerTest1() + var + Customer: Record Customer; + begin + if not Customer.Get('TEST-1') then begin + Customer."No." := 'TEST-1'; + Customer.Name := 'Test 1'; + Customer."Name 2" := 'Test name 2'; + Customer.Insert(); + end; + end; + + local procedure InitMigrationValidationTest_CustomerTest2() + var + Customer: Record Customer; + begin + if not Customer.Get('TEST-2') then begin + Customer."No." := 'TEST-2'; + Customer.Name := 'Test 2'; + Customer."Name 2" := 'Test name 2'; + Customer.Insert(); + end; + end; + + local procedure GetDefaultTestMigrationType(): Code[20] + begin + exit('TEST'); + end; + local procedure OpenCloudMigSelectTablesPage(var CloudMigSelectTables: TestPage "Cloud Mig - Select Tables") var IntelligentCloudStatus: Record "Intelligent Cloud Status"; diff --git a/Apps/W1/HybridGP/app/Permissions/HybridGPObjects.PermissionSet.al b/Apps/W1/HybridGP/app/Permissions/HybridGPObjects.PermissionSet.al index af444572db..05efe5e7e8 100644 --- a/Apps/W1/HybridGP/app/Permissions/HybridGPObjects.PermissionSet.al +++ b/Apps/W1/HybridGP/app/Permissions/HybridGPObjects.PermissionSet.al @@ -157,5 +157,6 @@ permissionset 4029 "HybridGP - Objects" page "GP Payment Terms" = X, table "GP IV00104" = X, table "GP PM00101" = X, - table "GP PM00203" = X; + table "GP PM00203" = X, + codeunit "GP Migration Validator" = X; } \ No newline at end of file diff --git a/Apps/W1/HybridGP/app/src/Migration/GPTables/GPPopulateCombinedTables.Codeunit.al b/Apps/W1/HybridGP/app/src/Migration/GPTables/GPPopulateCombinedTables.Codeunit.al index be96885d3c..805b3e3a10 100644 --- a/Apps/W1/HybridGP/app/src/Migration/GPTables/GPPopulateCombinedTables.Codeunit.al +++ b/Apps/W1/HybridGP/app/src/Migration/GPTables/GPPopulateCombinedTables.Codeunit.al @@ -329,8 +329,6 @@ codeunit 40125 "GP Populate Combined Tables" var GPCustomer: Record "GP Customer"; GPRM00101: Record "GP RM00101"; - GPRM00103: Record "GP RM00103"; - GPSY01200: Record "GP SY01200"; GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; begin GPRM00101.SetRange(INACTIVE, false); @@ -342,53 +340,58 @@ codeunit 40125 "GP Populate Combined Tables" repeat Clear(GPCustomer); + SetGPCustomerFields(GPCustomer, GPRM00101); + GPCustomer.Insert(); + until GPRM00101.Next() = 0; + end; + + internal procedure SetGPCustomerFields(var GPCustomer: Record "GP Customer"; var GPRM00101: Record "GP RM00101") + var + GPRM00103: Record "GP RM00103"; + GPSY01200: Record "GP SY01200"; + begin #pragma warning disable AA0139 - GPCustomer.CUSTNMBR := GPRM00101.CUSTNMBR.Trim(); - GPCustomer.CUSTNAME := GPRM00101.CUSTNAME.Trim(); - GPCustomer.STMTNAME := GPRM00101.STMTNAME.Trim(); - GPCustomer.ADDRESS1 := GPRM00101.ADDRESS1.Trim(); - GPCustomer.ADDRESS2 := GPRM00101.ADDRESS2.Trim(); - GPCustomer.CITY := GPRM00101.CITY.Trim(); - GPCustomer.CNTCPRSN := GPRM00101.CNTCPRSN.Trim(); - GPCustomer.SALSTERR := GPRM00101.SALSTERR.Trim(); - GPCustomer.CRLMTAMT := GPRM00101.CRLMTAMT; - GPCustomer.PYMTRMID := GPRM00101.PYMTRMID.Trim(); - GPCustomer.SLPRSNID := GPRM00101.SLPRSNID.Trim(); - GPCustomer.SHIPMTHD := GPRM00101.SHIPMTHD.Trim(); - GPCustomer.COUNTRY := GPRM00101.COUNTRY.Trim(); - GPCustomer.STMTCYCL := GPRM00101.STMTCYCL <> 0; - GPCustomer.ZIPCODE := GPRM00101.ZIP.Trim(); - GPCustomer.STATE := GPRM00101.STATE.Trim(); - GPCustomer.TAXSCHID := GPRM00101.TAXSCHID.Trim(); - GPCustomer.UPSZONE := GPRM00101.UPSZONE.Trim(); - GPCustomer.TAXEXMT1 := GPRM00101.TAXEXMT1.Trim(); - GPCustomer.CUSTCLAS := GPRM00101.CUSTCLAS.Trim(); - GPCustomer.RMSLSACC := GPRM00101.RMSLSACC; + GPCustomer.CUSTNMBR := GPRM00101.CUSTNMBR.Trim(); + GPCustomer.CUSTNAME := GPRM00101.CUSTNAME.Trim(); + GPCustomer.STMTNAME := GPRM00101.STMTNAME.Trim(); + GPCustomer.ADDRESS1 := GPRM00101.ADDRESS1.Trim(); + GPCustomer.ADDRESS2 := GPRM00101.ADDRESS2.Trim(); + GPCustomer.CITY := GPRM00101.CITY.Trim(); + GPCustomer.CNTCPRSN := GPRM00101.CNTCPRSN.Trim(); + GPCustomer.SALSTERR := GPRM00101.SALSTERR.Trim(); + GPCustomer.CRLMTAMT := GPRM00101.CRLMTAMT; + GPCustomer.PYMTRMID := GPRM00101.PYMTRMID.Trim(); + GPCustomer.SLPRSNID := GPRM00101.SLPRSNID.Trim(); + GPCustomer.SHIPMTHD := GPRM00101.SHIPMTHD.Trim(); + GPCustomer.COUNTRY := GPRM00101.COUNTRY.Trim(); + GPCustomer.STMTCYCL := GPRM00101.STMTCYCL <> 0; + GPCustomer.ZIPCODE := GPRM00101.ZIP.Trim(); + GPCustomer.STATE := GPRM00101.STATE.Trim(); + GPCustomer.TAXSCHID := GPRM00101.TAXSCHID.Trim(); + GPCustomer.UPSZONE := GPRM00101.UPSZONE.Trim(); + GPCustomer.TAXEXMT1 := GPRM00101.TAXEXMT1.Trim(); #pragma warning restore AA0139 - if GPRM00101.PHONE1.Contains('E+') then - GPCustomer.PHONE1 := '00000000000000' - else - GPCustomer.PHONE1 := CopyStr(GPRM00101.PHONE1.Trim(), 1, MaxStrLen(GPCustomer.PHONE1)); + if GPRM00101.PHONE1.Contains('E+') then + GPCustomer.PHONE1 := '00000000000000' + else + GPCustomer.PHONE1 := CopyStr(GPRM00101.PHONE1.Trim(), 1, MaxStrLen(GPCustomer.PHONE1)); - if GPRM00101.FAX.Contains('E+') then - GPCustomer.FAX := '00000000000000' - else - GPCustomer.FAX := CopyStr(GPRM00101.FAX.Trim(), 1, MaxStrLen(GPCustomer.FAX)); + if GPRM00101.FAX.Contains('E+') then + GPCustomer.FAX := '00000000000000' + else + GPCustomer.FAX := CopyStr(GPRM00101.FAX.Trim(), 1, MaxStrLen(GPCustomer.FAX)); - if GPRM00103.Get(GPRM00101.CUSTNMBR) then - GPCustomer.AMOUNT := GPRM00103.CUSTBLNC; + if GPRM00103.Get(GPRM00101.CUSTNMBR) then + GPCustomer.AMOUNT := GPRM00103.CUSTBLNC; - if GPSY01200.Get('CUS', GPRM00101.CUSTNMBR, GPRM00101.ADRSCODE) then begin - if ((GPSY01200.INET1 <> '') and (GPSY01200.INET1.Contains('@'))) then - GPCustomer.INET1 := CopyStr(GPSY01200.INET1.Trim(), 1, MaxStrLen(GPCustomer.INET1)); + if GPSY01200.Get('CUS', GPRM00101.CUSTNMBR, GPRM00101.ADRSCODE) then begin + if ((GPSY01200.INET1 <> '') and (GPSY01200.INET1.Contains('@'))) then + GPCustomer.INET1 := CopyStr(GPSY01200.INET1.Trim(), 1, MaxStrLen(GPCustomer.INET1)); - if ((GPSY01200.INET2 <> '') and (GPSY01200.INET2.Contains('@'))) then - GPCustomer.INET2 := CopyStr(GPSY01200.INET2.Trim(), 1, MaxStrLen(GPCustomer.INET2)); - end; - - GPCustomer.Insert(); - until GPRM00101.Next() = 0; + if ((GPSY01200.INET2 <> '') and (GPSY01200.INET2.Contains('@'))) then + GPCustomer.INET2 := CopyStr(GPSY01200.INET2.Trim(), 1, MaxStrLen(GPCustomer.INET2)); + end; end; internal procedure PopulateGPCustomerTransactions() @@ -527,66 +530,69 @@ codeunit 40125 "GP Populate Combined Tables" internal procedure PopulateGPVendors() var GPPM00200Vendor: Record "GP PM00200"; - GPPM00201VendorSum: Record "GP PM00201"; - GPSY01200NetAddresses: Record "GP SY01200"; GPVendor: Record "GP Vendor"; - GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; + GPCompanyMigrationSettings: Record "GP Company Migration Settings"; begin GPPM00200Vendor.SetFilter(VENDSTTS, '1|3'); - if GPCompanyAdditionalSettings.GetMigrateInactiveVendors() then - GPPM00200Vendor.SetRange(VENDSTTS); + if GPCompanyMigrationSettings.Get(CompanyName()) then + if GPCompanyMigrationSettings."Migrate Inactive Vendors" then + GPPM00200Vendor.SetRange(VENDSTTS); if not GPPM00200Vendor.FindSet() then exit; repeat Clear(GPVendor); + SetGPVendorFields(GPVendor, GPPM00200Vendor); + GPVendor.Insert(); + until GPPM00200Vendor.Next() = 0; + end; + + internal procedure SetGPVendorFields(var GPVendor: Record "GP Vendor"; GPPM00200Vendor: Record "GP PM00200") + var + GPPM00201VendorSum: Record "GP PM00201"; + GPSY01200NetAddresses: Record "GP SY01200"; + begin #pragma warning disable AA0139 - GPVendor.VENDORID := GPPM00200Vendor.VENDORID.TrimEnd(); - GPVendor.VENDNAME := GPPM00200Vendor.VENDNAME.TrimEnd(); - GPVendor.SEARCHNAME := GPPM00200Vendor.VENDNAME.TrimEnd(); - GPVendor.VNDCHKNM := GPPM00200Vendor.VNDCHKNM.TrimEnd(); - GPVendor.ADDRESS1 := GPPM00200Vendor.ADDRESS1.TrimEnd(); - GPVendor.ADDRESS2 := GPPM00200Vendor.ADDRESS2.TrimEnd(); - GPVendor.CITY := GPPM00200Vendor.CITY.TrimEnd(); - GPVendor.VNDCNTCT := GPPM00200Vendor.VNDCNTCT.TrimEnd(); - GPVendor.PYMTRMID := GPPM00200Vendor.PYMTRMID.TrimEnd(); - GPVendor.SHIPMTHD := GPPM00200Vendor.SHIPMTHD.TrimEnd(); - GPVendor.COUNTRY := GPPM00200Vendor.COUNTRY.TrimEnd(); - GPVendor.PYMNTPRI := GPPM00200Vendor.PYMNTPRI.TrimEnd(); - GPVendor.ZIPCODE := GPPM00200Vendor.ZIPCODE.TrimEnd(); - GPVendor.STATE := GPPM00200Vendor.STATE.TrimEnd(); - GPVendor.TAXSCHID := GPPM00200Vendor.TAXSCHID.TrimEnd(); - GPVendor.UPSZONE := GPPM00200Vendor.UPSZONE.TrimEnd(); - GPVendor.TXIDNMBR := GPPM00200Vendor.TXIDNMBR.TrimEnd(); - GPVendor.VNDCLSID := GPPM00200Vendor.VNDCLSID.TrimEnd(); + GPVendor.VENDORID := GPPM00200Vendor.VENDORID.TrimEnd(); + GPVendor.VENDNAME := GPPM00200Vendor.VENDNAME.TrimEnd(); + GPVendor.SEARCHNAME := GPPM00200Vendor.VENDNAME.TrimEnd(); + GPVendor.VNDCHKNM := GPPM00200Vendor.VNDCHKNM.TrimEnd(); + GPVendor.ADDRESS1 := GPPM00200Vendor.ADDRESS1.TrimEnd(); + GPVendor.ADDRESS2 := GPPM00200Vendor.ADDRESS2.TrimEnd(); + GPVendor.CITY := GPPM00200Vendor.CITY.TrimEnd(); + GPVendor.VNDCNTCT := GPPM00200Vendor.VNDCNTCT.TrimEnd(); + GPVendor.PYMTRMID := GPPM00200Vendor.PYMTRMID.TrimEnd(); + GPVendor.SHIPMTHD := GPPM00200Vendor.SHIPMTHD.TrimEnd(); + GPVendor.COUNTRY := GPPM00200Vendor.COUNTRY.TrimEnd(); + GPVendor.PYMNTPRI := GPPM00200Vendor.PYMNTPRI.TrimEnd(); + GPVendor.ZIPCODE := GPPM00200Vendor.ZIPCODE.TrimEnd(); + GPVendor.STATE := GPPM00200Vendor.STATE.TrimEnd(); + GPVendor.TAXSCHID := GPPM00200Vendor.TAXSCHID.TrimEnd(); + GPVendor.UPSZONE := GPPM00200Vendor.UPSZONE.TrimEnd(); + GPVendor.TXIDNMBR := GPPM00200Vendor.TXIDNMBR.TrimEnd(); #pragma warning restore AA0139 - GPVendor.PMPRCHIX := GPPM00200Vendor.PMPRCHIX; - - if GPPM00200Vendor.PHNUMBR1.Contains('E+') then - GPVendor.PHNUMBR1 := '00000000000000' - else - GPVendor.PHNUMBR1 := CopyStr(GPPM00200Vendor.PHNUMBR1.Trim(), 1, MaxStrLen(GPVendor.PHNUMBR1)); - - if GPPM00200Vendor.FAXNUMBR.Contains('E+') then - GPVendor.FAXNUMBR := '00000000000000' - else - GPVendor.FAXNUMBR := CopyStr(GPPM00200Vendor.FAXNUMBR.Trim(), 1, MaxStrLen(GPVendor.FAXNUMBR)); + if GPPM00200Vendor.PHNUMBR1.Contains('E+') then + GPVendor.PHNUMBR1 := '00000000000000' + else + GPVendor.PHNUMBR1 := CopyStr(GPPM00200Vendor.PHNUMBR1.Trim(), 1, MaxStrLen(GPVendor.PHNUMBR1)); - if GPPM00201VendorSum.Get(GPPM00200Vendor.VENDORID) then - GPVendor.AMOUNT := GPPM00201VendorSum.CURRBLNC; + if GPPM00200Vendor.FAXNUMBR.Contains('E+') then + GPVendor.FAXNUMBR := '00000000000000' + else + GPVendor.FAXNUMBR := CopyStr(GPPM00200Vendor.FAXNUMBR.Trim(), 1, MaxStrLen(GPVendor.FAXNUMBR)); - if GPSY01200NetAddresses.Get('VEN', GPPM00200Vendor.VENDORID, GPPM00200Vendor.VADDCDPR) then begin - if ((GPSY01200NetAddresses.INET1 <> '') and (GPSY01200NetAddresses.INET1.Contains('@'))) then - GPVendor.INET1 := CopyStr(GPSY01200NetAddresses.INET1.Trim(), 1, MaxStrLen(GPVendor.INET1)); + if GPPM00201VendorSum.Get(GPPM00200Vendor.VENDORID) then + GPVendor.AMOUNT := GPPM00201VendorSum.CURRBLNC; - if ((GPSY01200NetAddresses.INET2 <> '') and (GPSY01200NetAddresses.INET2.Contains('@'))) then - GPVendor.INET2 := CopyStr(GPSY01200NetAddresses.INET2.Trim(), 1, MaxStrLen(GPVendor.INET2)); - end; + if GPSY01200NetAddresses.Get('VEN', GPPM00200Vendor.VENDORID, GPPM00200Vendor.VADDCDPR) then begin + if ((GPSY01200NetAddresses.INET1 <> '') and (GPSY01200NetAddresses.INET1.Contains('@'))) then + GPVendor.INET1 := CopyStr(GPSY01200NetAddresses.INET1.Trim(), 1, MaxStrLen(GPVendor.INET1)); - GPVendor.Insert(); - until GPPM00200Vendor.Next() = 0; + if ((GPSY01200NetAddresses.INET2 <> '') and (GPSY01200NetAddresses.INET2.Contains('@'))) then + GPVendor.INET2 := CopyStr(GPSY01200NetAddresses.INET2.Trim(), 1, MaxStrLen(GPVendor.INET2)); + end; end; internal procedure PopulateGPVendorTransactions() @@ -667,12 +673,6 @@ codeunit 40125 "GP Populate Combined Tables" var GPItem: Record "GP Item"; GPIV00101Inventory: Record "GP IV00101"; - GPIV00102InventoryQty: Record "GP IV00102"; - GPIV00105inventoryCurr: Record "GP IV00105"; - GPIV40201InventoryUom: Record "GP IV40201"; - DummyItem: Record Item; - GPMC40000: Record "GP MC40000"; - FoundCurrency: Boolean; begin UpdateGLSetupUnitRoundingPrecisionIfNeeded(); @@ -682,79 +682,90 @@ codeunit 40125 "GP Populate Combined Tables" repeat Clear(GPItem); if ShouldAddItemToStagingTable(GPIV00101Inventory) then begin - GPItem.No := CopyStr(GPIV00101Inventory.ITEMNMBR.TrimEnd(), 1, MaxStrLen(DummyItem."No.")); - GPItem.Description := CopyStr(GPIV00101Inventory.ITEMDESC.TrimEnd(), 1, MaxStrLen(GPItem.Description)); - GPItem.SearchDescription := CopyStr(GPIV00101Inventory.ITEMDESC.TrimEnd(), 1, MaxStrLen(GPItem.SearchDescription)); + SetGPItemFields(GPItem, GPIV00101Inventory); + GPItem.Insert(); + end; + until GPIV00101Inventory.Next() = 0; + end; + + internal procedure SetGPItemFields(var GPItem: Record "GP Item"; var GPIV00101Inventory: Record "GP IV00101") + var + GPIV00102InventoryQty: Record "GP IV00102"; + GPIV00105inventoryCurr: Record "GP IV00105"; + GPIV40201InventoryUom: Record "GP IV40201"; + DummyItem: Record Item; + GPMC40000: Record "GP MC40000"; + FoundCurrency: Boolean; + begin + GPItem.No := CopyStr(GPIV00101Inventory.ITEMNMBR.TrimEnd(), 1, MaxStrLen(DummyItem."No.")); + GPItem.Description := CopyStr(GPIV00101Inventory.ITEMDESC.TrimEnd(), 1, MaxStrLen(GPItem.Description)); + GPItem.SearchDescription := CopyStr(GPIV00101Inventory.ITEMDESC.TrimEnd(), 1, MaxStrLen(GPItem.SearchDescription)); #pragma warning disable AA0139 - GPItem.ShortName := GPIV00101Inventory.ITEMNMBR.TrimEnd(); + GPItem.ShortName := GPIV00101Inventory.ITEMNMBR.TrimEnd(); #pragma warning restore AA0139 - case GPIV00101Inventory.ITEMTYPE of - 1, 2: - GPItem.ItemType := 0; - 4, 5, 6: - GPItem.ItemType := 1; - 3: - GPItem.ItemType := 2; - end; + case GPIV00101Inventory.ITEMTYPE of + 1, 2: + GPItem.ItemType := 0; + 4, 5, 6: + GPItem.ItemType := 1; + 3: + GPItem.ItemType := 2; + end; - case GPIV00101Inventory.VCTNMTHD of - 1: - GPItem.CostingMethod := Format(0); - 2: - GPItem.CostingMethod := Format(1); - 3: - GPItem.CostingMethod := Format(3); - 4, 5: - GPItem.CostingMethod := Format(4); - else - GPItem.CostingMethod := Format(0); - end; + case GPIV00101Inventory.VCTNMTHD of + 1: + GPItem.CostingMethod := Format(0); + 2: + GPItem.CostingMethod := Format(1); + 3: + GPItem.CostingMethod := Format(3); + 4, 5: + GPItem.CostingMethod := Format(4); + else + GPItem.CostingMethod := Format(0); + end; - GPItem.CurrentCost := GPIV00101Inventory.CURRCOST; - GPItem.StandardCost := GPIV00101Inventory.STNDCOST; + GPItem.CurrentCost := GPIV00101Inventory.CURRCOST; + GPItem.StandardCost := GPIV00101Inventory.STNDCOST; #pragma warning disable AA0139 - GPItem.SalesUnitOfMeasure := GPIV00101Inventory.SELNGUOM.Trim(); - GPItem.PurchUnitOfMeasure := GPIV00101Inventory.PRCHSUOM.Trim(); - GPItem.SalesUnitOfMeasure := GPIV00101Inventory.SELNGUOM.Trim(); - GPItem.SalesUnitOfMeasure := GPIV00101Inventory.SELNGUOM.Trim(); + GPItem.SalesUnitOfMeasure := GPIV00101Inventory.SELNGUOM.Trim(); + GPItem.PurchUnitOfMeasure := GPIV00101Inventory.PRCHSUOM.Trim(); + GPItem.SalesUnitOfMeasure := GPIV00101Inventory.SELNGUOM.Trim(); + GPItem.SalesUnitOfMeasure := GPIV00101Inventory.SELNGUOM.Trim(); #pragma warning restore AA0139 - case GPIV00101Inventory.ITMTRKOP of - 2: - GPItem.ItemTrackingCode := ItemTrackingCodeSERIALLbl; - 3: - GPItem.ItemTrackingCode := ItemTrackingCodeLOTLbl; - end; + case GPIV00101Inventory.ITMTRKOP of + 2: + GPItem.ItemTrackingCode := ItemTrackingCodeSERIALLbl; + 3: + GPItem.ItemTrackingCode := ItemTrackingCodeLOTLbl; + end; - GPItem.ShipWeight := GPIV00101Inventory.ITEMSHWT / 100; - if GPIV00101Inventory.ITEMTYPE = 2 then - GPItem.InActive := true - else - GPItem.InActive := GPIV00101Inventory.INACTIVE; + GPItem.ShipWeight := GPIV00101Inventory.ITEMSHWT / 100; + if GPIV00101Inventory.ITEMTYPE = 2 then + GPItem.InActive := true + else + GPItem.InActive := GPIV00101Inventory.INACTIVE; - GPIV40201InventoryUom.SetRange(UOMSCHDL, GPIV00101Inventory.UOMSCHDL); - if GPIV40201InventoryUom.FindFirst() then + GPIV40201InventoryUom.SetRange(UOMSCHDL, GPIV00101Inventory.UOMSCHDL); + if GPIV40201InventoryUom.FindFirst() then #pragma warning disable AA0139 - GPItem.BaseUnitOfMeasure := GPIV40201InventoryUom.BASEUOFM.Trim(); + GPItem.BaseUnitOfMeasure := GPIV40201InventoryUom.BASEUOFM.Trim(); #pragma warning restore AA0139 - GPIV00102InventoryQty.SetRange(ITEMNMBR, GPIV00101Inventory.ITEMNMBR); - GPIV00102InventoryQty.SetRange(LOCNCODE, ''); - GPIV00102InventoryQty.SetRange(RCRDTYPE, 1); - if GPIV00102InventoryQty.FindFirst() then - GPItem.QuantityOnHand := GPIV00102InventoryQty.QTYONHND; - - GPIV00105inventoryCurr.SetRange(ITEMNMBR, GPIV00101Inventory.ITEMNMBR); - if GPIV00105inventoryCurr.FindSet() then - repeat - FoundCurrency := GPMC40000.Get(GPIV00105inventoryCurr.CURNCYID); - until (GPIV00105inventoryCurr.Next() = 0) or FoundCurrency; + GPIV00102InventoryQty.SetRange(ITEMNMBR, GPIV00101Inventory.ITEMNMBR); + GPIV00102InventoryQty.SetRange(LOCNCODE, ''); + GPIV00102InventoryQty.SetRange(RCRDTYPE, 1); + if GPIV00102InventoryQty.FindFirst() then + GPItem.QuantityOnHand := GPIV00102InventoryQty.QTYONHND; - if FoundCurrency then - GPItem.UnitListPrice := GPIV00105inventoryCurr.LISTPRCE; + GPIV00105inventoryCurr.SetRange(ITEMNMBR, GPIV00101Inventory.ITEMNMBR); + if GPIV00105inventoryCurr.FindSet() then + repeat + FoundCurrency := GPMC40000.Get(GPIV00105inventoryCurr.CURNCYID); + until (GPIV00105inventoryCurr.Next() = 0) or FoundCurrency; - GPItem.Insert(); - end; - until GPIV00101Inventory.Next() = 0; + if FoundCurrency then + GPItem.UnitListPrice := GPIV00105inventoryCurr.LISTPRCE; end; local procedure UpdateGLSetupUnitRoundingPrecisionIfNeeded() diff --git a/Apps/W1/HybridGP/app/src/Migration/Support/CheckBooks/GPCheckbookTransactions.table.al b/Apps/W1/HybridGP/app/src/Migration/Support/CheckBooks/GPCheckbookTransactions.table.al index bd805e1af7..75a6e52702 100644 --- a/Apps/W1/HybridGP/app/src/Migration/Support/CheckBooks/GPCheckbookTransactions.table.al +++ b/Apps/W1/HybridGP/app/src/Migration/Support/CheckBooks/GPCheckbookTransactions.table.al @@ -220,4 +220,9 @@ table 40101 "GP Checkbook Transactions" Clustered = true; } } + + internal procedure ShouldFlipSign(): Boolean + begin + exit((Rec.CMTrxType = 3) or (Rec.CMTrxType = 4) or (Rec.CMTrxType = 6)); + end; } \ No newline at end of file diff --git a/Apps/W1/HybridGP/app/src/Migration/Support/HelperFunctions.codeunit.al b/Apps/W1/HybridGP/app/src/Migration/Support/HelperFunctions.codeunit.al index f39e6ee911..e175bac1af 100644 --- a/Apps/W1/HybridGP/app/src/Migration/Support/HelperFunctions.codeunit.al +++ b/Apps/W1/HybridGP/app/src/Migration/Support/HelperFunctions.codeunit.al @@ -1071,7 +1071,7 @@ codeunit 4037 "Helper Functions" if GPVendor.FindSet() then repeat if GPVendorMigrator.ShouldMigrateVendor(GPVendor.VENDORID, IsTemporaryVendor, HasOpenPurchaseOrders, HasOpenTransactions) then - VendorCount := VendorCount + 1; + VendorCount += 1; until GPVendor.Next() = 0; @@ -1154,7 +1154,7 @@ codeunit 4037 "Helper Functions" GenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); GenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); if not GenJournalLine.IsEmpty() then - UnpostedBatchCount := UnpostedBatchCount + 1; + UnpostedBatchCount += 1; until GenJournalBatch.Next() = 0; exit(UnpostedBatchCount); @@ -1177,7 +1177,7 @@ codeunit 4037 "Helper Functions" repeat ItemJournalLine.SetRange("Journal Batch Name", ItemJournalBatch.Name); if not ItemJournalLine.IsEmpty() then - UnpostedBatchCount += UnpostedBatchCount + 1; + UnpostedBatchCount += 1; until ItemJournalBatch.Next() = 0; exit(UnpostedBatchCount); @@ -1200,20 +1200,23 @@ codeunit 4037 "Helper Functions" repeat StatisticalAccJournalLine.SetRange("Journal Batch Name", StatisticalAccJournalBatch.Name); if not StatisticalAccJournalLine.IsEmpty() then - UnpostedBatchCount += UnpostedBatchCount + 1; + UnpostedBatchCount += 1; until StatisticalAccJournalBatch.Next() = 0; exit(UnpostedBatchCount); end; - internal procedure GetUnpostedBatchCountForCompany(CompanyNameTxt: Text; var TotalGLBatchCount: Integer; var TotalStatisticalBatchCount: Integer; var TotalItemBatchCount: Integer) + internal procedure GetUnpostedBatchCountForCompany(CompanyNameTxt: Text; var TotalGLBatchCount: Integer; var TotalStatisticalBatchCount: Integer; var TotalBankBatchCount: Integer; var TotalCustomerBatchCount: Integer; var TotalItemBatchCount: Integer; var TotalVendorBatchCount: Integer) var HybridCompanyStatus: Record "Hybrid Company Status"; GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; begin TotalGLBatchCount := 0; TotalStatisticalBatchCount := 0; + TotalBankBatchCount := 0; + TotalCustomerBatchCount := 0; TotalItemBatchCount := 0; + TotalVendorBatchCount := 0; if not HybridCompanyStatus.Get(CompanyNameTxt) then exit; @@ -1231,16 +1234,18 @@ codeunit 4037 "Helper Functions" end; if not GPCompanyAdditionalSettings."Skip Posting Customer Batches" then - TotalGLBatchCount += GetGLBatchCountWithUnpostedLinesForCompany(CompanyNameTxt, GeneralTemplateNameTxt, CustomerBatchNameTxt); + TotalCustomerBatchCount += GetGLBatchCountWithUnpostedLinesForCompany(CompanyNameTxt, GeneralTemplateNameTxt, CustomerBatchNameTxt); if not GPCompanyAdditionalSettings."Skip Posting Vendor Batches" then - TotalGLBatchCount += GetGLBatchCountWithUnpostedLinesForCompany(CompanyNameTxt, GeneralTemplateNameTxt, VendorBatchNameTxt); + TotalVendorBatchCount += GetGLBatchCountWithUnpostedLinesForCompany(CompanyNameTxt, GeneralTemplateNameTxt, VendorBatchNameTxt); if not GPCompanyAdditionalSettings."Skip Posting Bank Batches" then - TotalGLBatchCount += GetGLBatchCountWithUnpostedLinesForCompany(CompanyNameTxt, GeneralTemplateNameTxt, BankBatchNameTxt); + TotalBankBatchCount += GetGLBatchCountWithUnpostedLinesForCompany(CompanyNameTxt, GeneralTemplateNameTxt, BankBatchNameTxt); if not GPCompanyAdditionalSettings."Skip Posting Item Batches" then TotalItemBatchCount := GetItemBatchCountWithUnpostedLinesForCompany(CompanyNameTxt); + + TotalGLBatchCount += TotalGLBatchCount + TotalBankBatchCount + TotalCustomerBatchCount + TotalVendorBatchCount; end; procedure PostGLTransactions() diff --git a/Apps/W1/HybridGP/app/src/codeunits/GPMigrationValidator.Codeunit.al b/Apps/W1/HybridGP/app/src/codeunits/GPMigrationValidator.Codeunit.al new file mode 100644 index 0000000000..b446859850 --- /dev/null +++ b/Apps/W1/HybridGP/app/src/codeunits/GPMigrationValidator.Codeunit.al @@ -0,0 +1,1583 @@ +namespace Microsoft.DataMigration.GP; + +using Microsoft.DataMigration; +using Microsoft.Finance.GeneralLedger.Account; +using Microsoft.Finance.Analysis.StatisticalAccount; +using Microsoft.Bank.BankAccount; +using Microsoft.Sales.Customer; +using Microsoft.Inventory.Item; +using Microsoft.Purchases.Vendor; +using Microsoft.Purchases.Document; +using Microsoft.Finance.GeneralLedger.Setup; +using Microsoft.Purchases.Remittance; +using Microsoft.Finance.Currency; + +codeunit 40903 "GP Migration Validator" +{ + trigger OnRun() + var + GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; + begin + if not GPCompanyAdditionalSettings.Get(CompanyName()) then + exit; + + ValidatorCodeLbl := GetValidatorCode(); + CompanyNameTxt := CompanyName(); + DefaultCurrency.InitRoundingPrecision(); + + GetUnpostedBatchCounts(); + + RunGLAccountMigrationValidation(GPCompanyAdditionalSettings); + RunStatisticalAccountMigrationValidation(GPCompanyAdditionalSettings); + RunBankAccountMigrationValidation(GPCompanyAdditionalSettings); + RunCustomerMigrationValidation(GPCompanyAdditionalSettings); + RunItemMigrationValidation(GPCompanyAdditionalSettings); + RunPurchaseOrderMigrationValidation(GPCompanyAdditionalSettings); + RunVendorMigrationValidation(GPCompanyAdditionalSettings); + + MigrationValidation.ReportCompanyValidated(); + end; + + local procedure GetUnpostedBatchCounts() + var + HelperFunctions: Codeunit "Helper Functions"; + begin + TotalUnpostedGLBatchCount := 0; + TotalUnpostedStatisticalBatchCount := 0; + TotalUnpostedBankBatchCount := 0; + TotalUnpostedCustomerBatchCount := 0; + TotalUnpostedItemBatchCount := 0; + TotalUnpostedVendorBatchCount := 0; + + HelperFunctions.GetUnpostedBatchCountForCompany(CompanyName(), TotalUnpostedGLBatchCount, TotalUnpostedStatisticalBatchCount, TotalUnpostedBankBatchCount, TotalUnpostedCustomerBatchCount, TotalUnpostedItemBatchCount, TotalUnpostedVendorBatchCount); + end; + + local procedure RunGLAccountMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + GLAccount: Record "G/L Account"; + GPAccount: Record "GP Account"; + FirstAccount: Record "GP GL00100"; + GPGL00100: Record "GP GL00100"; + GPGL10111: Record "GP GL10111"; + GPGL40200: Record "GP GL40200"; + GPSY00300: Record "GP SY00300"; + GPGLTransactions: Record "GP GLTransactions"; + GPFiscalPeriods: Record "GP Fiscal Periods"; + HelperFunctions: Codeunit "Helper Functions"; + BalanceFailureShouldBeWarning: Boolean; + GPAccountNo: Code[20]; + GPAccountBeginningBalance: Decimal; + AccountFilter: Text; + EntityType: Text[50]; + GPAccountDescription: Text[100]; + ValidatedAccountNos: List of [Text]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepGLAccountLbl) then + exit; + + EntityType := GlAccountEntityCaptionLbl; + BalanceFailureShouldBeWarning := (TotalUnpostedGLBatchCount > 0); + + // GP + if GPCompanyAdditionalSettings.GetGLModuleEnabled() then begin + GPGL00100.SetRange(ACCTTYPE, 1); + GPGL00100.SetFilter(MNACSGMT, '<>%1', ''); + if GPGL00100.FindSet() then + repeat + GPAccountBeginningBalance := 0; + GPAccountNo := CopyStr(GPGL00100.MNACSGMT.TrimEnd(), 1, MaxStrLen(GPAccountNo)); + if ValidatedAccountNos.Contains(GPAccountNo) then + continue; + + ValidatedAccountNos.Add(GPAccountNo); + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, GPAccountNo); + + GPSY00300.SetRange(MNSEGIND, true); + if GPSY00300.FindFirst() then begin + GPGL40200.SetRange(SGMNTID, GPGL00100.MNACSGMT); + GPGL40200.SetRange(SGMTNUMB, GPSY00300.SGMTNUMB); + if GPGL40200.FindFirst() then + GPAccountDescription := CopyStr(GPGL40200.DSCRIPTN.TrimEnd(), 1, MaxStrLen(GPAccountDescription)); + end; + + if GPAccountDescription = '' then begin + FirstAccount.SetCurrentKey(ACTINDX); + FirstAccount.SetRange(MNACSGMT, GPGL00100.MNACSGMT); + FirstAccount.SetRange(ACCTTYPE, 1); + if FirstAccount.FindFirst() then + GPAccountDescription := CopyStr(FirstAccount.ACTDESCR.TrimEnd(), 1, MaxStrLen(GPAccountDescription)); + end; + + Clear(GPAccount); + GPAccount.AcctNum := GPAccountNo; + GPAccount.AcctIndex := GPGL00100.ACTINDX; + GPAccount.Name := CopyStr(GPAccountDescription.Trim(), 1, MaxStrLen(GLAccount.Name)); + GPAccount.SearchName := GPAccount.Name; + GPAccount.AccountCategory := GPGL00100.ACCATNUM; + GPAccount.IncomeBalance := GPGL00100.PSTNGTYP = 1; + GPAccount.DebitCredit := GPGL00100.TPCLBLNC; + GPAccount.Active := GPGL00100.ACTIVE; + GPAccount.DirectPosting := GPGL00100.ACCTENTR; + GPAccount.AccountSubcategoryEntryNo := GPGL00100.ACCATNUM; + GPAccount.AccountType := GPGL00100.ACCTTYPE; + + if not GPCompanyAdditionalSettings.GetMigrateOnlyGLMaster() then + if not GPCompanyAdditionalSettings.GetSkipPostingAccountBatches() then + if GPGL00100.PSTNGTYP = 0 then begin + AccountFilter := GetAccountFilter(GPAccountNo, 1); + + if AccountFilter <> '' then begin + + // Beginning Balance + GPGL10111.SetFilter(ACTINDX, AccountFilter); + GPGL10111.SetRange(PERIODID, 0); + GPGL10111.SetRange(YEAR1, GPCompanyAdditionalSettings."Oldest GL Year to Migrate"); + if GPGL10111.FindSet() then + repeat + GPAccountBeginningBalance += RoundWithSpecPrecision(GPGL10111.PERDBLNC); + until GPGL10111.Next() = 0; + + // Trx summary + GPGLTransactions.SetCurrentKey(YEAR1, PERIODID, ACTINDX); + GPGLTransactions.SetFilter(ACTINDX, AccountFilter); + + if GPCompanyAdditionalSettings."Oldest GL Year to Migrate" > 0 then + GPGLTransactions.SetFilter(YEAR1, '>= %1', GPCompanyAdditionalSettings."Oldest GL Year to Migrate"); + + if GPGLTransactions.FindSet() then + repeat + if GPFiscalPeriods.Get(GPGLTransactions.PERIODID, GPGLTransactions.YEAR1) then + GPAccountBeginningBalance += RoundWithSpecPrecision(GPGLTransactions.PERDBLNC); + until GPGLTransactions.Next() = 0; + end; + end; + + Clear(GLAccount); + GLAccount.SetLoadFields("No.", Name, "Account Type", "Account Category", "Debit/Credit", "Account Subcategory Entry No.", "Income/Balance", Balance); + if not MigrationValidation.ValidateRecordExists(Test_ACCOUNTEXISTS_Tok, GLAccount.Get(GPAccount.AcctNum), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + GLAccount.CalcFields(Balance); + + MigrationValidation.ValidateAreEqual(Test_ACCOUNTNAME_Tok, GPAccount.Name, GLAccount.Name, AccountNameLbl, true); + MigrationValidation.ValidateAreEqual(Test_ACCOUNTTYPE_Tok, Format(GLAccount."Account Type"::Posting), Format(GLAccount."Account Type"), AccountTypeLbl); + MigrationValidation.ValidateAreEqual(Test_ACCOUNTCATEGORY_Tok, HelperFunctions.ConvertAccountCategory(GPAccount), GLAccount."Account Category".AsInteger(), AccountCategoryLbl); + MigrationValidation.ValidateAreEqual(Test_ACCOUNTDEBCRED_Tok, HelperFunctions.ConvertDebitCreditType(GPAccount), GLAccount."Debit/Credit", AccountDebitCreditLbl); + MigrationValidation.ValidateAreEqual(Test_ACCOUNTSUBCATEGORY_Tok, HelperFunctions.AssignSubAccountCategory(GPAccount), GLAccount."Account Subcategory Entry No.", AccountSubcategoryLbl); + MigrationValidation.ValidateAreEqual(Test_ACCOUNTINCBAL_Tok, HelperFunctions.ConvertIncomeBalanceType(GPAccount), GLAccount."Income/Balance".AsInteger(), AccountIncomeBalanceLbl); + MigrationValidation.ValidateAreEqual(Test_ACCOUNTBALANCE_Tok, GPAccountBeginningBalance, GLAccount.Balance, BeginningBalanceLbl, BalanceFailureShouldBeWarning); + until GPGL00100.Next() = 0; + end; + + LogValidationProgress(ValidationStepGLAccountLbl); + Commit(); + end; + + local procedure RunStatisticalAccountMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + GeneralLedgerSetup: Record "General Ledger Setup"; + FirstAccount: Record "GP GL00100"; + GPGL00100: Record "GP GL00100"; + GPGL10111: Record "GP GL10111"; + GPGL40200: Record "GP GL40200"; + GPSY00300: Record "GP SY00300"; + GPGLTransactions: Record "GP GLTransactions"; + GPFiscalPeriods: Record "GP Fiscal Periods"; + StatisticalAccount: Record "Statistical Account"; + BalanceFailureShouldBeWarning: Boolean; + DimensionCode1: Code[20]; + DimensionCode2: Code[20]; + GPAccountNo: Code[20]; + GPAccountBeginningBalance: Decimal; + AccountFilter: Text; + EntityType: Text[50]; + GPAccountDescription: Text[100]; + ValidatedAccountNos: List of [Text]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepStatAccountLbl) then + exit; + + EntityType := StatisticalAccountEntityCaptionLbl; + BalanceFailureShouldBeWarning := (TotalUnpostedStatisticalBatchCount > 0); + + if GeneralLedgerSetup.Get() then begin + DimensionCode1 := GeneralLedgerSetup."Global Dimension 1 Code"; + DimensionCode2 := GeneralLedgerSetup."Global Dimension 2 Code"; + end; + + if GPCompanyAdditionalSettings.GetGLModuleEnabled() then begin + GPGL00100.SetRange(ACCTTYPE, 2); + GPGL00100.SetFilter(MNACSGMT, '<>%1', ''); + if GPGL00100.FindSet() then + repeat + GPAccountBeginningBalance := 0; + GPAccountNo := CopyStr(GPGL00100.MNACSGMT.TrimEnd(), 1, MaxStrLen(GPAccountNo)); + if ValidatedAccountNos.Contains(GPAccountNo) then + continue; + + ValidatedAccountNos.Add(GPAccountNo); + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, GPAccountNo); + + GPSY00300.SetRange(MNSEGIND, true); + if GPSY00300.FindFirst() then begin + GPGL40200.SetRange(SGMNTID, GPGL00100.MNACSGMT); + GPGL40200.SetRange(SGMTNUMB, GPSY00300.SGMTNUMB); + if GPGL40200.FindFirst() then + GPAccountDescription := CopyStr(GPGL40200.DSCRIPTN.TrimEnd(), 1, MaxStrLen(GPAccountDescription)); + end; + + if GPAccountDescription = '' then begin + FirstAccount.SetCurrentKey(ACTINDX); + FirstAccount.SetRange(MNACSGMT, GPGL00100.MNACSGMT); + FirstAccount.SetRange(ACCTTYPE, 2); + if FirstAccount.FindFirst() then + GPAccountDescription := CopyStr(FirstAccount.ACTDESCR.TrimEnd(), 1, MaxStrLen(GPAccountDescription)); + end; + + if not GPCompanyAdditionalSettings.GetMigrateOnlyGLMaster() then + if not GPCompanyAdditionalSettings.GetSkipPostingAccountBatches() then begin + Clear(GPGL10111); + AccountFilter := GetAccountFilter(GPAccountNo, 2); + + if AccountFilter <> '' then begin + + // Beginning Balance + GPGL10111.SetFilter(ACTINDX, AccountFilter); + GPGL10111.SetRange(PERIODID, 0); + GPGL10111.SetRange(YEAR1, GPCompanyAdditionalSettings."Oldest GL Year to Migrate"); + if GPGL10111.FindSet() then + repeat + GPAccountBeginningBalance += RoundWithSpecPrecision(GPGL10111.PERDBLNC); + until GPGL10111.Next() = 0; + + // Trx summary + GPGLTransactions.SetCurrentKey(YEAR1, PERIODID, ACTINDX); + GPGLTransactions.SetFilter(ACTINDX, AccountFilter); + + if GPCompanyAdditionalSettings."Oldest GL Year to Migrate" > 0 then + GPGLTransactions.SetFilter(YEAR1, '>= %1', GPCompanyAdditionalSettings."Oldest GL Year to Migrate"); + + if GPGLTransactions.FindSet() then + repeat + if GPFiscalPeriods.Get(GPGLTransactions.PERIODID, GPGLTransactions.YEAR1) then + GPAccountBeginningBalance += RoundWithSpecPrecision(GPGLTransactions.PERDBLNC); + until GPGLTransactions.Next() = 0; + end; + end; + + Clear(StatisticalAccount); + StatisticalAccount.SetLoadFields("No.", Name, "Global Dimension 1 Code", "Global Dimension 2 Code", Balance); + if not MigrationValidation.ValidateRecordExists(Test_STATACCOUNTEXISTS_Tok, StatisticalAccount.Get(GPAccountNo), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + StatisticalAccount.CalcFields(Balance); + + MigrationValidation.ValidateAreEqual(Test_STATACCOUNTNAME_Tok, GPAccountDescription, StatisticalAccount.Name, AccountNameLbl, true); + MigrationValidation.ValidateAreEqual(Test_STATACCOUNTDIM1_Tok, DimensionCode1, StatisticalAccount."Global Dimension 1 Code", Dimension1Lbl); + MigrationValidation.ValidateAreEqual(Test_STATACCOUNTDIM2_Tok, DimensionCode2, StatisticalAccount."Global Dimension 2 Code", Dimension2Lbl); + MigrationValidation.ValidateAreEqual(Test_STATACCOUNTBALANCE_Tok, GPAccountBeginningBalance, StatisticalAccount.Balance, BeginningBalanceLbl, BalanceFailureShouldBeWarning); + until GPGL00100.Next() = 0; + end; + + LogValidationProgress(ValidationStepStatAccountLbl); + Commit(); + end; + + local procedure RunBankAccountMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + BankAccount: Record "Bank Account"; + GPBankMSTR: Record "GP Bank MSTR"; + GPCheckbookMSTR: Record "GP Checkbook MSTR"; + GPCheckbookTransactions: Record "GP Checkbook Transactions"; + GPCM20600: Record "GP CM20600"; + BalanceFailureShouldBeWarning: Boolean; + ShouldFlipSign: Boolean; + ShouldInclude: Boolean; + GPAccountNo: Code[20]; + GPAccountBalance: Decimal; + EntityType: Text[50]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepBankAccountLbl) then + exit; + + EntityType := BankAccountEntityCaptionLbl; + BalanceFailureShouldBeWarning := (TotalUnpostedBankBatchCount > 0); + + if GPCompanyAdditionalSettings.GetBankModuleEnabled() then + if GPCheckbookMSTR.FindSet() then + repeat + GPAccountNo := CopyStr(GPCheckbookMSTR.CHEKBKID.TrimEnd(), 1, MaxStrLen(GPAccountNo)); + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, GPAccountNo); + ShouldInclude := true; + GPAccountBalance := 0; + + if not GPCompanyAdditionalSettings.GetMigrateInactiveCheckbooks() then + if GPCheckbookMSTR.INACTIVE then + ShouldInclude := false; + + if ShouldInclude then begin + + // Balance + if not GPCompanyAdditionalSettings.GetMigrateOnlyBankMaster() then + if not GPCompanyAdditionalSettings.GetSkipPostingBankBatches() then begin + GPAccountBalance := GPCheckbookMSTR.Last_Reconciled_Balance; + + GPCheckbookTransactions.SetRange(CHEKBKID, GPCheckbookMSTR.CHEKBKID); + GPCheckbookTransactions.SetRange(Recond, false); + GPCheckbookTransactions.SetFilter(TRXAMNT, '<>%1', 0); + if GPCheckbookTransactions.FindSet() then + repeat + ShouldFlipSign := false; + + if GPCheckbookTransactions.ShouldFlipSign() then + ShouldFlipSign := true; + + if GPCheckbookTransactions.CMTrxType = 7 then begin + GPCM20600.SetRange(CMXFRNUM, GPCheckbookTransactions.CMTrxNum); + GPCM20600.SetRange(CMFRMRECNUM, GPCheckbookTransactions.CMRECNUM); + if GPCM20600.FindFirst() then + if GPCM20600.Xfr_Record_Number > 0 then + ShouldFlipSign := true; + end; + + if ShouldFlipSign then + GPCheckbookTransactions.TRXAMNT := GPCheckbookTransactions.TRXAMNT * -1; + + GPAccountBalance := GPAccountBalance + RoundWithSpecPrecision(GPCheckbookTransactions.TRXAMNT); + until GPCheckbookTransactions.Next() = 0; + end; + + Clear(BankAccount); + BankAccount.SetLoadFields("No.", Name, "Bank Account No.", Balance, Address, "Address 2", City, "Phone No.", "Transit No.", "Fax No.", County, "Post Code", "Bank Branch No."); + if not MigrationValidation.ValidateRecordExists(Test_BANKACCOUNTEXISTS_Tok, BankAccount.Get(GPAccountNo), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + BankAccount.CalcFields(Balance); + + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTNAME_Tok, CopyStr(GPCheckbookMSTR.DSCRIPTN.TrimEnd(), 1, MaxStrLen(BankAccount.Name)), BankAccount.Name, NameLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTNO_Tok, CopyStr(GPCheckbookMSTR.BNKACTNM.TrimEnd(), 1, MaxStrLen(BankAccount."Bank Account No.")), BankAccount."Bank Account No.", BankAccountNumberLbl, false, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTADDR_Tok, CopyStr(GPBankMSTR.ADDRESS1.TrimEnd(), 1, MaxStrLen(BankAccount.Address)), BankAccount.Address, AddressLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTADDR2_Tok, CopyStr(GPBankMSTR.ADDRESS2.TrimEnd(), 1, MaxStrLen(BankAccount."Address 2")), BankAccount."Address 2", Address2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTCITY_Tok, CopyStr(GPBankMSTR.CITY.TrimEnd(), 1, MaxStrLen(BankAccount.City)), BankAccount.City, CityLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTCOUNTY_Tok, CopyStr(GPBankMSTR.STATE.TrimEnd(), 1, MaxStrLen(BankAccount.County)), BankAccount.County, CountyLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTPOSTCODE_Tok, CopyStr(GPBankMSTR.ZIPCODE.TrimEnd(), 1, MaxStrLen(BankAccount."Post Code")), BankAccount."Post Code", PostCodeLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTPHN_Tok, CopyStr(GPBankMSTR.PHNUMBR1.TrimEnd(), 1, MaxStrLen(BankAccount."Phone No.")), BankAccount."Phone No.", PhoneLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTFAX_Tok, CopyStr(GPBankMSTR.FAXNUMBR.TrimEnd(), 1, MaxStrLen(BankAccount."Fax No.")), BankAccount."Fax No.", FaxLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTTRANSITNO_Tok, CopyStr(GPBankMSTR.TRNSTNBR.TrimEnd(), 1, MaxStrLen(BankAccount."Transit No.")), BankAccount."Transit No.", TransitNoLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTBRANCHNO_Tok, CopyStr(GPBankMSTR.BNKBRNCH.TrimEnd(), 1, MaxStrLen(BankAccount."Bank Branch No.")), BankAccount."Bank Branch No.", BankBranchNoLbl, true); + MigrationValidation.ValidateAreEqual(Test_BANKACCOUNTBALANCE_Tok, GPAccountBalance, BankAccount.Balance, BalanceLbl, BalanceFailureShouldBeWarning); + end; + until GPCheckbookMSTR.Next() = 0; + LogValidationProgress(ValidationStepBankAccountLbl); + Commit(); + end; + + local procedure RunCustomerMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + Customer: Record Customer; + GPCustomer: Record "GP Customer"; + GPRM00101: Record "GP RM00101"; + GPRM20101: record "GP RM20101"; + GPPaymentTerms: Record "GP Payment Terms"; + GPPopulateCombinedTables: Codeunit "GP Populate Combined Tables"; + HelperFunctions: Codeunit "Helper Functions"; + BalanceFailureShouldBeWarning: Boolean; + ShouldInclude: Boolean; + ClassName: Code[20]; + CustomerNo: Code[20]; + TaxLiable: Boolean; + PhoneNo: Text[30]; + FaxNo: Text[30]; + PaymentTerms: Code[10]; + GPCustomerBalance: Decimal; + EntityType: Text[50]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepCustomerLbl) then + exit; + + EntityType := CustomerEntityCaptionLbl; + BalanceFailureShouldBeWarning := (TotalUnpostedCustomerBatchCount > 0); + + if GPCompanyAdditionalSettings.GetReceivablesModuleEnabled() then begin + GPRM00101.SetFilter(CUSTNMBR, '<>%1', ''); + if GPRM00101.FindSet() then + repeat + CustomerNo := CopyStr(GPRM00101.CUSTNMBR.TrimEnd(), 1, MaxStrLen(CustomerNo)); + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, CustomerNo); + GPCustomerBalance := 0; + ShouldInclude := true; + + if not GPCompanyAdditionalSettings.GetMigrateInactiveCustomers() then + if GPRM00101.INACTIVE then + ShouldInclude := false; + + if ShouldInclude then begin + if GPCompanyAdditionalSettings.GetMigrateCustomerClasses() then + ClassName := CopyStr(GPRM00101.CUSTCLAS.TrimEnd(), 1, MaxStrLen(ClassName)); + + if ClassName = '' then + ClassName := DefaultClassNameTok; + + Clear(GPCustomer); + GPPopulateCombinedTables.SetGPCustomerFields(GPCustomer, GPRM00101); + + GPCustomer.PHONE1 := HelperFunctions.CleanGPPhoneOrFaxNumber(GPCustomer.PHONE1); + GPCustomer.FAX := HelperFunctions.CleanGPPhoneOrFaxNumber(GPCustomer.FAX); + + if not GPCompanyAdditionalSettings.GetMigrateOnlyReceivablesMaster() then + if not GPCompanyAdditionalSettings.GetSkipPostingCustomerBatches() then begin + GPRM20101.SetRange(CUSTNMBR, GPRM00101.CUSTNMBR); + GPRM20101.SetRange(RMDTYPAL, 1, 9); + GPRM20101.SetRange(VOIDSTTS, 0); + GPRM20101.SetFilter(CURTRXAM, '>=0.01'); + if GPRM20101.FindSet() then + repeat + if GPRM20101.RMDTYPAL < 7 then + GPCustomerBalance := RoundWithSpecPrecision(GPCustomerBalance + GPRM20101.CURTRXAM) + else + GPCustomerBalance := GPCustomerBalance + RoundWithSpecPrecision(GPRM20101.CURTRXAM * -1); + until GPRM20101.Next() = 0; + end; + + if not MigrationValidation.ValidateRecordExists(Test_CUSTOMEREXISTS_Tok, Customer.Get(CustomerNo), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + Customer.CalcFields(Balance); + + MigrationValidation.ValidateAreEqual(Test_CUSTOMERNAME_Tok, CopyStr(GPRM00101.CUSTNAME.TrimEnd(), 1, MaxStrLen(Customer.Name)), Customer.Name, NameLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERPOSTINGGROUP_Tok, ClassName, Customer."Customer Posting Group", CustomerPostingGroupLbl); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERADDR_Tok, CopyStr(GPCustomer.ADDRESS1, 1, MaxStrLen(Customer.Address)), Customer.Address, AddressLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERADDR2_Tok, CopyStr(GPCustomer.ADDRESS2, 1, MaxStrLen(Customer."Address 2")), Customer."Address 2", Address2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERCITY_Tok, CopyStr(GPCustomer.CITY, 1, MaxStrLen(Customer.City)), Customer.City, CityLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERNAME2_Tok, CopyStr(GPCustomer.STMTNAME, 1, MaxStrLen(Customer."Name 2")), Customer."Name 2", Name2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERCREDITLMT_Tok, GPCustomer.CRLMTAMT, Customer."Credit Limit (LCY)", CreditLimitLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERCONTACT_Tok, CopyStr(GPCustomer.CNTCPRSN, 1, MaxStrLen(Customer.Contact)), Customer.Contact, ContactLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERSALESPERSON_Tok, UpperCase(GPCustomer.SLPRSNID.TrimEnd()), Customer."Salesperson Code", SalesPersonLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERSHIPMETHOD_Tok, UpperCase(CopyStr(GPCustomer.SHIPMTHD, 1, MaxStrLen(Customer."Shipment Method Code")).TrimEnd()), Customer."Shipment Method Code", ShipmentMethodLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERTERRITORY_Tok, UpperCase(CopyStr(GPCustomer.SALSTERR, 1, MaxStrLen(Customer."Territory Code")).TrimEnd()), Customer."Territory Code", TerritoryLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERTAXAREA_Tok, UpperCase(GPCustomer.TAXSCHID.TrimEnd()), Customer."Tax Area Code", TaxAreaLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERBALANCE_Tok, GPCustomerBalance, Customer.Balance, BalanceLbl, BalanceFailureShouldBeWarning); + + TaxLiable := (GPCustomer.TAXSCHID <> ''); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERTAXLIABLE_Tok, TaxLiable, Customer."Tax Liable", TaxLiableLbl, true); + + PhoneNo := ''; + if GPCustomer.PHONE1 <> '' then + if not HelperFunctions.ContainsAlphaChars(GPCustomer.PHONE1) then + PhoneNo := GPCustomer.PHONE1; + + FaxNo := ''; + if GPCustomer.FAX <> '' then + if not HelperFunctions.ContainsAlphaChars(GPCustomer.FAX) then + FaxNo := GPCustomer.FAX; + + MigrationValidation.ValidateAreEqual(Test_CUSTOMERPHN_Tok, PhoneNo, Customer."Phone No.", PhoneLbl, true); + MigrationValidation.ValidateAreEqual(Test_CUSTOMERFAX_Tok, FaxNo, Customer."Fax No.", FaxLbl, true); + + PaymentTerms := ''; + if GPCustomer.PYMTRMID <> '' then + if GPPaymentTerms.Get(GPCustomer.PYMTRMID) then begin + PaymentTerms := CopyStr(GPCustomer.PYMTRMID, 1, MaxStrLen(Customer."Payment Terms Code")); + if GPPaymentTerms.PYMTRMID_New <> '' then + PaymentTerms := GPPaymentTerms.PYMTRMID_New; + end; + + MigrationValidation.ValidateAreEqual(Test_CUSTOMERPMTTERMS_Tok, PaymentTerms, Customer."Payment Terms Code", PaymentTermsLbl, true); + + ValidateCustomerShipToAddresses(Customer); + end; + until GPRM00101.Next() = 0; + end; + + LogValidationProgress(ValidationStepCustomerLbl); + Commit(); + end; + + local procedure ValidateCustomerShipToAddresses(var Customer: Record Customer) + var + GPCustomerAddress: Record "GP Customer Address"; + ShipToAddress: Record "Ship-to Address"; + AddressCode: Code[10]; + EntityType: Text[50]; + ContextCode: Text[250]; + begin + EntityType := CustomerAddressEntityCaptionLbl; + + GPCustomerAddress.SetRange(CUSTNMBR, Customer."No."); + if GPCustomerAddress.FindSet() then + repeat + AddressCode := CopyStr(GPCustomerAddress.ADRSCODE, 1, MaxStrLen(AddressCode)); + ContextCode := Customer."No." + '-' + AddressCode; + + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, ContextCode); + + if not MigrationValidation.ValidateRecordExists(Test_SHIPADDREXISTS_Tok, ShipToAddress.Get(Customer."No.", AddressCode), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + if (CopyStr(GPCustomerAddress.PHONE1, 1, 14) = '00000000000000') then + GPCustomerAddress.PHONE1 := ''; + + if (CopyStr(GPCustomerAddress.FAX, 1, 14) = '00000000000000') then + GPCustomerAddress.FAX := ''; + + MigrationValidation.ValidateAreEqual(Test_SHIPADDRNAME_Tok, Customer.Name, ShipToAddress.Name, NameLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRADDR_Tok, GPCustomerAddress.ADDRESS1.TrimEnd(), ShipToAddress.Address, AddressLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRADDR2_Tok, CopyStr(GPCustomerAddress.ADDRESS2.TrimEnd(), 1, MaxStrLen(ShipToAddress."Address 2")), ShipToAddress."Address 2", Address2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRCITY_Tok, CopyStr(GPCustomerAddress.CITY.TrimEnd(), 1, MaxStrLen(ShipToAddress.City)), ShipToAddress.City, CityLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRPOSTCODE_Tok, UpperCase(GPCustomerAddress.ZIP.TrimEnd()), ShipToAddress."Post Code", PostCodeLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRPHN_Tok, GPCustomerAddress.PHONE1.TrimEnd(), ShipToAddress."Phone No.", PhoneLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRFAX_Tok, GPCustomerAddress.FAX.TrimEnd(), ShipToAddress."Fax No.", FaxLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRCONTACT_Tok, GPCustomerAddress.CNTCPRSN.TrimEnd(), ShipToAddress.Contact, ContactLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRSHIPMETHOD_Tok, CopyStr(GPCustomerAddress.SHIPMTHD.TrimEnd(), 1, MaxStrLen(ShipToAddress."Shipment Method Code")), ShipToAddress."Shipment Method Code", ShipmentMethodLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRCOUNTY_Tok, GPCustomerAddress.STATE.TrimEnd(), ShipToAddress.County, CountyLbl, true); + MigrationValidation.ValidateAreEqual(Test_SHIPADDRTAXAREA_Tok, GPCustomerAddress.TAXSCHID.TrimEnd(), ShipToAddress."Tax Area Code", TaxAreaLbl, true); + + until GPCustomerAddress.Next() = 0; + end; + + local procedure RunItemMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + GPItem: Record "GP Item"; + GPIV00101: Record "GP IV00101"; + GPIV00200: Record "GP IV00200"; + GPIV00300: Record "GP IV00300"; + GPIV10200: Record "GP IV10200"; + GPIV00104: Record "GP IV00104"; + Item: Record Item; + GPPopulateCombinedTables: Codeunit "GP Populate Combined Tables"; + IsDiscontinued: Boolean; + IsInactive: Boolean; + IsInventoryOrDiscontinued: Boolean; + QuantityFailureShouldBeWarning: Boolean; + ShouldInclude: Boolean; + ClassName: Code[20]; + ItemType: Enum "Item Type"; + CostingMethod: Enum "Costing Method"; + ItemNo: Code[20]; + Quantity: Decimal; + KitItemNo: Code[20]; + Kits: List of [Code[20]]; + EntityType: Text[50]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepItemLbl) then + exit; + + EntityType := ItemEntityCaptionLbl; + QuantityFailureShouldBeWarning := (TotalUnpostedItemBatchCount > 0); + + if GPCompanyAdditionalSettings.GetInventoryModuleEnabled() then begin + if GPCompanyAdditionalSettings.GetMigrateKitItems() then + if GPIV00104.FindSet() then + repeat + KitItemNo := CopyStr(GPIV00104.CMPTITNM.TrimEnd(), 1, MaxStrLen(KitItemNo)); + if not Kits.Contains(KitItemNo) then + Kits.Add(KitItemNo); + until GPIV00104.Next() = 0; + + GPIV00101.SetFilter(ITEMNMBR, '<>%1', ''); + if not GPCompanyAdditionalSettings.GetMigrateKitItems() then + GPIV00101.SetFilter(ITEMTYPE, '<>%1', 3); + + if GPIV00101.FindSet() then + repeat + ItemNo := CopyStr(GPIV00101.ITEMNMBR.TrimEnd(), 1, MaxStrLen(ItemNo)); + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, ItemNo); + + Quantity := 0; + ClassName := ''; + ShouldInclude := true; + IsInventoryOrDiscontinued := (GPIV00101.ITEMTYPE < 4); + IsInactive := (GPIV00101.ITEMTYPE = 2) or (GPIV00101.INACTIVE); + IsDiscontinued := GPIV00101.ITEMTYPE = 2; + + if not GPCompanyAdditionalSettings.GetMigrateInactiveItems() then + if IsInactive then + ShouldInclude := false; + + if ShouldInclude then + if not GPCompanyAdditionalSettings.GetMigrateDiscontinuedItems() then + if IsDiscontinued then + ShouldInclude := false; + + if ShouldInclude then begin + if IsInventoryOrDiscontinued then + if GPCompanyAdditionalSettings.GetMigrateItemClasses() then begin + ClassName := CopyStr(GPIV00101.ITMCLSCD.TrimEnd(), 1, MaxStrLen(ClassName)); + + if ClassName = '' then + ClassName := DefaultClassNameTok; + end else + ClassName := DefaultClassNameTok; + + if not GPCompanyAdditionalSettings.GetSkipPostingItemBatches() then + if not GPCompanyAdditionalSettings.GetMigrateOnlyInventoryMaster() then begin + GPIV10200.SetRange(ITEMNMBR, GPIV00101.ITEMNMBR); + GPIV10200.SetRange(RCPTSOLD, false); + GPIV10200.SetRange(QTYTYPE, 1); + if GPIV10200.FindSet() then + repeat + // Serial + if GPIV00101.ITMTRKOP = 2 then begin + GPIV00200.SetRange(ITEMNMBR, GPIV10200.ITEMNMBR); + GPIV00200.SetRange(LOCNCODE, GPIV10200.TRXLOCTN); + GPIV00200.SetRange(DATERECD, GPIV10200.DATERECD); + GPIV00200.SetRange(RCTSEQNM, GPIV10200.RCTSEQNM); + GPIV00200.SetRange(QTYTYPE, 1); + Quantity := Quantity + GPIV00200.Count(); + end; + + // Lot + if GPIV00101.ITMTRKOP = 3 then begin + GPIV00300.SetRange(ITEMNMBR, GPIV00101.ITEMNMBR); + GPIV00300.SetRange(LOCNCODE, GPIV10200.TRXLOCTN); + GPIV00300.SetRange(DATERECD, GPIV10200.DATERECD); + GPIV00300.SetRange(RCTSEQNM, GPIV10200.RCTSEQNM); + GPIV00300.SetRange(QTYTYPE, 1); + if GPIV00300.FindSet() then + repeat + Quantity := Quantity + (GPIV00300.QTYRECVD - GPIV00300.QTYSOLD); + until GPIV00300.Next() = 0; + end; + + if (GPIV00101.ITMTRKOP <> 2) and (GPIV00101.ITMTRKOP <> 3) then + Quantity := Quantity + (GPIV10200.QTYRECVD - GPIV10200.QTYSOLD); + until GPIV10200.Next() = 0; + end; + + Clear(GPItem); + GPPopulateCombinedTables.SetGPItemFields(GPItem, GPIV00101); + + if not MigrationValidation.ValidateRecordExists(Test_ITEMEXISTS_Tok, Item.Get(ItemNo), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + Item.CalcFields(Inventory); + + MigrationValidation.ValidateAreEqual(Test_ITEMDESC_Tok, GPItem.Description, Item.Description, DescriptionLbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMDESC2_Tok, GPItem.ShortName, Item."Description 2", Description2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMSEARCHDESC_Tok, GPItem.SearchDescription, Item."Search Description", SearchDescription2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMPOSTINGGROUP_Tok, ClassName, Item."Inventory Posting Group", ItemPostingGroupLbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMUNITLISTPRICE_Tok, GPItem.UnitListPrice, Item."Unit List Price", UnitListPriceLbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMBASEUOFM_Tok, GPItem.BaseUnitOfMeasure, Item."Base Unit of Measure", BaseUofMLbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMPURCHUOFM_Tok, GPItem.PurchUnitOfMeasure, Item."Purch. Unit of Measure", PurchUofMLbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMTRACKINGCODE_Tok, GPItem.ItemTrackingCode, Item."Item Tracking Code", ItemTrackingCodeLbl, true); + MigrationValidation.ValidateAreEqual(Test_ITEMINVENTORY_Tok, Quantity, Item.Inventory, QuantityLbl, QuantityFailureShouldBeWarning); + + if (GPItem.ItemType in [0, 2]) then + ItemType := ItemType::Inventory + else + if not Kits.Contains(ItemNo) then + ItemType := ItemType::Service + else + ItemType := ItemType::"Non-Inventory"; + + if ItemType = ItemType::Service then + CostingMethod := CostingMethod::FIFO + else + case GPItem.CostingMethod of + '0': + CostingMethod := CostingMethod::FIFO; + '1': + CostingMethod := CostingMethod::LIFO; + '2': + CostingMethod := CostingMethod::Specific; + '3': + CostingMethod := CostingMethod::Average; + '4': + CostingMethod := CostingMethod::Standard; + end; + + MigrationValidation.ValidateAreEqual(Test_ITEMTYPE_Tok, ItemType, Item.Type, TypeLbl); + MigrationValidation.ValidateAreEqual(Test_ITEMCOSTMETHOD_Tok, CostingMethod, Item."Costing Method", CostingMethodLbl, true); + end; + until GPIV00101.Next() = 0; + end; + + LogValidationProgress(ValidationStepItemLbl); + Commit(); + end; + + local procedure RunPurchaseOrderMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + GPPOP10100: Record "GP POP10100"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + Vendor: Record Vendor; + GPPOHeaderValidationBuffer: Record "Migration Validation Buffer"; + GPPOLineValidationBuffer: Record "Migration Validation Buffer"; + PONumber: Code[20]; + EntityType: Text[50]; + LineEntityType: Text[50]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepPurchaseOrderLbl) then + exit; + + EntityType := 'Purchase Order'; + LineEntityType := 'Purchase Line'; + + // GP + if GPCompanyAdditionalSettings.GetMigrateOpenPOs() then begin + GPPOP10100.SetRange(POTYPE, GPPOP10100.POTYPE::Standard); + GPPOP10100.SetRange(POSTATUS, 1, 4); + GPPOP10100.SetFilter(VENDORID, '<>%1', ''); + if GPPOP10100.FindSet() then + repeat + PONumber := CopyStr(GPPOP10100.PONUMBER.TrimEnd(), 1, MaxStrLen(PurchaseHeader."No.")); + if Vendor.Get(GPPOP10100.VENDORID) then + if not GPPOHeaderValidationBuffer.Get(PONumber) then begin + GPPOHeaderValidationBuffer."No." := PONumber; + GPPOHeaderValidationBuffer."Text 1" := CopyStr(GPPOP10100.VENDORID.TrimEnd(), 1, MaxStrLen(Vendor."No.")); + GPPOHeaderValidationBuffer."Date 1" := GPPOP10100.DOCDATE; + GPPOHeaderValidationBuffer.Insert(); + + if not PopulatePOLineBuffer(PONumber, GPPOLineValidationBuffer) then + GPPOHeaderValidationBuffer.Delete(); + end; + until GPPOP10100.Next() = 0; + end; + + // Validate - Purchase Orders + if GPPOHeaderValidationBuffer.FindSet() then + repeat + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, GPPOHeaderValidationBuffer."No."); + + if not MigrationValidation.ValidateRecordExists(Test_POEXISTS_Tok, PurchaseHeader.Get("Purchase Document Type"::Order, GPPOHeaderValidationBuffer."No."), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + MigrationValidation.ValidateAreEqual(Test_POBUYFROMVEND_Tok, GPPOHeaderValidationBuffer."Text 1", PurchaseHeader."Buy-from Vendor No.", PurchaseOrderBuyFromVendorNoLbl); + MigrationValidation.ValidateAreEqual(Test_POPAYTOVEND_Tok, GPPOHeaderValidationBuffer."Text 1", PurchaseHeader."Pay-to Vendor No.", PurchaseOrderPayToVendorNoLbl); + MigrationValidation.ValidateAreEqual(Test_PODOCDATE_Tok, GPPOHeaderValidationBuffer."Date 1", PurchaseHeader."Document Date", DocumentDateLbl); + + // Lines + GPPOLineValidationBuffer.Reset(); + GPPOLineValidationBuffer.SetRange("Parent No.", GPPOHeaderValidationBuffer."No."); + if GPPOLineValidationBuffer.FindSet() then + repeat + MigrationValidation.SetContext(ValidatorCodeLbl, LineEntityType, GPPOLineValidationBuffer."No."); + + PurchaseLine.SetRange("Document Type", "Purchase Document Type"::Order); + PurchaseLine.SetRange("Document No.", GPPOHeaderValidationBuffer."No."); + PurchaseLine.SetRange("No.", GPPOLineValidationBuffer."Text 1"); + if not MigrationValidation.ValidateRecordExists(Test_POLINEEXISTS_Tok, PurchaseLine.FindFirst(), StrSubstNo(MissingEntityTok, LineEntityType)) then + continue; + + MigrationValidation.ValidateAreEqual(Test_POLINEQTY_Tok, GPPOLineValidationBuffer."Decimal 1", PurchaseLine.Quantity, QuantityLbl, true); + MigrationValidation.ValidateAreEqual(Test_POLINEQTYRECV_Tok, GPPOLineValidationBuffer."Decimal 2", PurchaseLine."Quantity Received", QuantityRecLbl, true); + until GPPOLineValidationBuffer.Next() = 0; + until GPPOHeaderValidationBuffer.Next() = 0; + + LogValidationProgress(ValidationStepPurchaseOrderLbl); + Commit(); + end; + + local procedure PopulatePOLineBuffer(PONumber: Code[20]; var LineBuffer: Record "Migration Validation Buffer"): Boolean + var + GPPOP10110: Record "GP POP10110"; + GPPOPReceiptApply: Record GPPOPReceiptApply; + GPPOPReceiptApplyLineUnitCost: Record GPPOPReceiptApply; + Item: Record Item; + HasLines: Boolean; + ShouldCreateLine: Boolean; + LocationCode: Code[10]; + LastLineUnitCost: Decimal; + LineQtyInvoicedByUnitCost: Decimal; + LineQtyReceivedByUnitCost: Decimal; + LineQuantityRemaining: Decimal; + ItemNo: Text; + LastLocation: Text[12]; + begin + GPPOP10110.SetRange(PONUMBER, PONumber); + if not GPPOP10110.FindSet() then + exit; + + repeat + LastLocation := ''; + LastLineUnitCost := 0; + ShouldCreateLine := true; + + ItemNo := CopyStr(GPPOP10110.ITEMNMBR.Trim(), 1, MaxStrLen(Item."No.")); + Item.SetLoadFields(Blocked); + if Item.Get(ItemNo) then begin + if Item.Blocked then + ShouldCreateLine := false + end else + if GPPOP10110.NONINVEN = 0 then + ShouldCreateLine := false; + + if ShouldCreateLine then begin + LineQuantityRemaining := GPPOP10110.QTYORDER - GPPOP10110.QTYCANCE; + if LineQuantityRemaining > 0 then begin + GPPOPReceiptApplyLineUnitCost.SetLoadFields(TRXLOCTN, PCHRPTCT, UOFM); + GPPOPReceiptApplyLineUnitCost.SetCurrentKey(TRXLOCTN, PCHRPTCT); + GPPOPReceiptApplyLineUnitCost.SetRange(PONUMBER, GPPOP10110.PONUMBER); + GPPOPReceiptApplyLineUnitCost.SetRange(POLNENUM, GPPOP10110.ORD); + GPPOPReceiptApplyLineUnitCost.SetRange(Status, GPPOPReceiptApplyLineUnitCost.Status::Posted); + GPPOPReceiptApplyLineUnitCost.SetFilter(POPTYPE, '1|3'); + GPPOPReceiptApplyLineUnitCost.SetFilter(QTYSHPPD, '>%1', 0); + GPPOPReceiptApplyLineUnitCost.SetFilter(PCHRPTCT, '>%1', 0); + + if GPPOPReceiptApplyLineUnitCost.FindSet() then + repeat + if ((LastLocation <> GPPOPReceiptApplyLineUnitCost.TRXLOCTN) or (LastLineUnitCost <> GPPOPReceiptApplyLineUnitCost.PCHRPTCT)) then begin + LocationCode := CopyStr(GPPOPReceiptApplyLineUnitCost.TRXLOCTN, 1, MaxStrLen(LocationCode)); + LineQtyReceivedByUnitCost := GPPOPReceiptApply.GetSumQtyShippedByUnitCost(GPPOP10110.PONUMBER, GPPOP10110.ORD, LocationCode, GPPOPReceiptApplyLineUnitCost.PCHRPTCT); + LineQtyInvoicedByUnitCost := GPPOPReceiptApply.GetSumQtyInvoicedByUnitCost(GPPOP10110.PONUMBER, GPPOP10110.ORD, LocationCode, GPPOPReceiptApplyLineUnitCost.PCHRPTCT); + + if (LineQtyReceivedByUnitCost > LineQtyInvoicedByUnitCost) then + InsertPOLine(PONumber, GPPOP10110, LineQuantityRemaining, LineQtyReceivedByUnitCost, LineQtyInvoicedByUnitCost, HasLines, LineBuffer) + else + LineQuantityRemaining := LineQuantityRemaining - LineQtyReceivedByUnitCost; + + LastLocation := GPPOPReceiptApplyLineUnitCost.TRXLOCTN; + LastLineUnitCost := GPPOPReceiptApplyLineUnitCost.PCHRPTCT; + end; + until GPPOPReceiptApplyLineUnitCost.Next() = 0; + + if LineQuantityRemaining > 0 then + InsertPOLine(PONumber, GPPOP10110, LineQuantityRemaining, 0, 0, HasLines, LineBuffer); + end; + end; + until GPPOP10110.Next() = 0; + + exit(HasLines); + end; + + local procedure InsertPOLine(PONumber: Code[20]; var GPPOP10110: Record "GP POP10110"; var LineQuantityRemaining: Decimal; QuantityReceived: Decimal; QuantityInvoiced: Decimal; var HasLines: Boolean; var LineBuffer: Record "Migration Validation Buffer") + var + ItemNo: Code[20]; + AdjustedQuantity: Decimal; + AdjustedQuantityReceived: Decimal; + QuantityOverReceipt: Decimal; + POLineIdTxt: Text[50]; + begin + AdjustedQuantityReceived := SubtractAndZeroIfNegative(QuantityReceived, QuantityInvoiced); + if AdjustedQuantityReceived > 0 then + AdjustedQuantity := SubtractAndZeroIfNegative(QuantityReceived, QuantityInvoiced) + else + AdjustedQuantity := SubtractAndZeroIfNegative(LineQuantityRemaining, QuantityInvoiced); + + QuantityOverReceipt := SubtractAndZeroIfNegative(AdjustedQuantityReceived, AdjustedQuantity); + + if QuantityOverReceipt > 0 then + AdjustedQuantity := AdjustedQuantityReceived; + + if AdjustedQuantity > 0 then begin + POLineIdTxt := CopyStr(PONumber + '_' + CopyStr(GPPOP10110.ITEMNMBR.TrimEnd(), 1, MaxStrLen(ItemNo)), 1, MaxStrLen(POLineIdTxt)); + LineBuffer.SetRange("No.", POLineIdTxt); + if LineBuffer.FindFirst() then begin + LineBuffer."Decimal 1" := LineBuffer."Decimal 1" + AdjustedQuantity; + LineBuffer."Decimal 2" := LineBuffer."Decimal 2" + AdjustedQuantityReceived; + LineBuffer.Modify(); + end else begin + LineBuffer."No." := POLineIdTxt; + LineBuffer."Parent No." := PONumber; + LineBuffer."Text 1" := CopyStr(CopyStr(GPPOP10110.ITEMNMBR.TrimEnd(), 1, MaxStrLen(ItemNo)), 1, MaxStrLen(LineBuffer."Text 1")); + LineBuffer."Decimal 1" := AdjustedQuantity; + LineBuffer."Decimal 2" := AdjustedQuantityReceived; + LineBuffer.Insert(); + end; + + HasLines := true; + end; + LineQuantityRemaining := LineQuantityRemaining - QuantityReceived; + end; + + local procedure SubtractAndZeroIfNegative(Minuend: Decimal; Subtrahend: Decimal): Decimal + var + Difference: Decimal; + begin + Difference := Minuend - Subtrahend; + + if Difference < 0 then + Difference := 0; + + exit(Difference); + end; + + local procedure RunVendorMigrationValidation(var GPCompanyAdditionalSettings: Record "GP Company Additional Settings") + var + GPPM00200: Record "GP PM00200"; + GPPM20000: Record "GP PM20000"; + GPVendor: Record "GP Vendor"; + GPPaymentTerms: Record "GP Payment Terms"; + Vendor: Record Vendor; + GPPopulateCombinedTables: Codeunit "GP Populate Combined Tables"; + HelperFunctions: Codeunit "Helper Functions"; + BalanceFailureShouldBeWarning: Boolean; + IsActive: Boolean; + ShouldInclude: Boolean; + ClassName: Code[20]; + VendorNo: Code[20]; + Balance: Decimal; + EntityType: Text[50]; + VendorName2: Text[50]; + PaymentTerms: Code[10]; + TaxLiable: Boolean; + PhoneNo: Text[30]; + FaxNo: Text[30]; + begin + if CompanyValidationProgress.Get(CompanyNameTxt, ValidatorCodeLbl, ValidationStepVendorLbl) then + exit; + + EntityType := VendorEntityCaptionLbl; + BalanceFailureShouldBeWarning := (TotalUnpostedVendorBatchCount > 0); + + if GPCompanyAdditionalSettings.GetPayablesModuleEnabled() then begin + GPPM00200.SetFilter(VENDORID, '<>%1', ''); + if GPPM00200.FindSet() then + repeat + VendorNo := CopyStr(GPPM00200.VENDORID.TrimEnd(), 1, MaxStrLen(VendorNo)); + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, VendorNo); + + Balance := 0; + ShouldInclude := true; + IsActive := (GPPM00200.VENDSTTS = 1) or (GPPM00200.VENDSTTS = 3); + + if not GPCompanyAdditionalSettings.GetMigrateInactiveVendors() then + if not IsActive then + ShouldInclude := false; + + if ShouldInclude then + ShouldInclude := ShouldMigrateTemporaryVendor(GPPM00200.VENDORID); + + if ShouldInclude then begin + if GPCompanyAdditionalSettings.GetMigrateVendorClasses() then + ClassName := CopyStr(GPPM00200.VNDCLSID.TrimEnd(), 1, MaxStrLen(ClassName)); + + if ClassName = '' then + ClassName := DefaultClassNameTok; + + Clear(GPVendor); + GPPopulateCombinedTables.SetGPVendorFields(GPVendor, GPPM00200); + + GPVendor.PHNUMBR1 := HelperFunctions.CleanGPPhoneOrFaxNumber(GPVendor.PHNUMBR1); + GPVendor.FAXNUMBR := HelperFunctions.CleanGPPhoneOrFaxNumber(GPVendor.FAXNUMBR); + + if not GPCompanyAdditionalSettings.GetMigrateOnlyPayablesMaster() then + if not GPCompanyAdditionalSettings.GetSkipPostingVendorBatches() then begin + GPPM20000.SetRange(VENDORID, GPPM00200.VENDORID); + GPPM20000.SetFilter(DOCTYPE, '<=7'); + GPPM20000.SetFilter(CURTRXAM, '>=0.01'); + GPPM20000.SetRange(VOIDED, false); + if GPPM20000.FindSet() then + repeat + if GPPM20000.DOCTYPE < 4 then + Balance := Balance + RoundWithSpecPrecision(GPPM20000.CURTRXAM) + else + Balance := Balance + RoundWithSpecPrecision(GPPM20000.CURTRXAM * -1); + until GPPM20000.Next() = 0; + end; + + if not MigrationValidation.ValidateRecordExists(Test_VENDOREXISTS_Tok, Vendor.Get(VendorNo), StrSubstNo(MissingEntityTok, EntityType)) then + continue; + + Vendor.CalcFields(Balance); + + MigrationValidation.ValidateAreEqual(Test_VENDORNAME_Tok, CopyStr(GPVendor.VENDNAME.TrimEnd(), 1, MaxStrLen(Vendor.Name)), Vendor.Name, NameLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORPOSTINGGROUP_Tok, ClassName, Vendor."Vendor Posting Group", VendorPostingGroupLbl); + MigrationValidation.ValidateAreEqual(Test_VENDORPREFBANKACCT_Tok, GetPreferredGPVendorBankCode(VendorNo), Vendor."Preferred Bank Account Code", PreferredBankAccountLbl); + MigrationValidation.ValidateAreEqual(Test_VENDORADDR_Tok, CopyStr(GPVendor.ADDRESS1, 1, MaxStrLen(Vendor.Address)), Vendor.Address, AddressLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORADDR2_Tok, CopyStr(GPVendor.ADDRESS2, 1, MaxStrLen(Vendor."Address 2")), Vendor."Address 2", Address2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORCITY_Tok, CopyStr(GPVendor.CITY, 1, MaxStrLen(Vendor.City)), Vendor.City, CityLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORCONTACT_Tok, CopyStr(GPVendor.VNDCNTCT, 1, MaxStrLen(Vendor.Contact)), Vendor.Contact, ContactLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORSHIPMETHOD_Tok, UpperCase(CopyStr(GPVendor.SHIPMTHD, 1, MaxStrLen(Vendor."Shipment Method Code")).TrimEnd()), Vendor."Shipment Method Code", ShipmentMethodLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORTAXAREA_Tok, UpperCase(GPVendor.TAXSCHID.TrimEnd()), Vendor."Tax Area Code", TaxAreaLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORBALANCE_Tok, Balance, Vendor.Balance, BalanceLbl, BalanceFailureShouldBeWarning); + + TaxLiable := (GPVendor.TAXSCHID <> ''); + MigrationValidation.ValidateAreEqual(Test_VENDORTAXLIABLE_Tok, TaxLiable, Vendor."Tax Liable", TaxLiableLbl, true); + + VendorName2 := CopyStr(GPVendor.VNDCHKNM.TrimEnd(), 1, MaxStrLen(Vendor."Name 2")); + if HelperFunctions.StringEqualsCaseInsensitive(VendorName2, Vendor.Name) then + VendorName2 := ''; + + MigrationValidation.ValidateAreEqual(Test_VENDORNAME2_Tok, VendorName2, Vendor."Name 2", Name2Lbl, true); + + PhoneNo := ''; + if GPVendor.PHNUMBR1 <> '' then + if not HelperFunctions.ContainsAlphaChars(GPVendor.PHNUMBR1) then + PhoneNo := GPVendor.PHNUMBR1; + + FaxNo := ''; + if GPVendor.FAXNUMBR <> '' then + if not HelperFunctions.ContainsAlphaChars(GPVendor.FAXNUMBR) then + FaxNo := GPVendor.FAXNUMBR; + + MigrationValidation.ValidateAreEqual(Test_VENDORPHN_Tok, PhoneNo, Vendor."Phone No.", PhoneLbl, true); + MigrationValidation.ValidateAreEqual(Test_VENDORFAX_Tok, FaxNo, Vendor."Fax No.", FaxLbl, true); + + PaymentTerms := ''; + if GPVendor.PYMTRMID <> '' then + if GPPaymentTerms.Get(GPVendor.PYMTRMID) then begin + PaymentTerms := CopyStr(GPVendor.PYMTRMID, 1, MaxStrLen(Vendor."Payment Terms Code")); + if GPPaymentTerms.PYMTRMID_New <> '' then + PaymentTerms := GPPaymentTerms.PYMTRMID_New; + end; + + MigrationValidation.ValidateAreEqual(Test_VENDORPMTTERMS_Tok, PaymentTerms, Vendor."Payment Terms Code", PaymentTermsLbl, true); + + ValidateVendorAddresses(GPVendor); + end; + until GPPM00200.Next() = 0; + end; + + LogValidationProgress(ValidationStepVendorLbl); + Commit(); + end; + + local procedure ValidateVendorAddresses(var GPVendor: Record "GP Vendor") + var + GPPM00200: Record "GP PM00200"; + GPVendorAddress: Record "GP Vendor Address"; + Vendor: Record Vendor; + RemitAddress: Record "Remit Address"; + OrderAddress: Record "Order Address"; + HelperFunctions: Codeunit "Helper Functions"; + AddressCode: Code[10]; + AssignedPrimaryAddressCode: Code[10]; + AssignedRemitToAddressCode: Code[10]; + EntityType: Text[50]; + ContextCode: Text[250]; + begin + if not Vendor.Get(GPVendor.VENDORID) then + exit; + + if GPPM00200.Get(GPVendor.VENDORID) then begin + AssignedPrimaryAddressCode := CopyStr(GPPM00200.VADDCDPR.Trim(), 1, MaxStrLen(AssignedPrimaryAddressCode)); + AssignedRemitToAddressCode := CopyStr(GPPM00200.VADCDTRO.Trim(), 1, MaxStrLen(AssignedRemitToAddressCode)); + end; + + GPVendorAddress.SetRange(VENDORID, Vendor."No."); + if GPVendorAddress.FindSet() then + repeat + AddressCode := CopyStr(GPVendorAddress.ADRSCODE.Trim(), 1, MaxStrLen(AddressCode)); + ContextCode := Vendor."No." + '-' + AddressCode; + + if AddressCode = AssignedRemitToAddressCode then begin + EntityType := VendorRemitAddressEntityCaptionLbl; + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, ContextCode); + + if MigrationValidation.ValidateRecordExists(Test_REMITADDREXISTS_Tok, RemitAddress.Get(AddressCode, Vendor."No."), StrSubstNo(MissingEntityTok, EntityType)) then begin + MigrationValidation.ValidateAreEqual(Test_REMITADDRNAME_Tok, Vendor.Name, RemitAddress.Name, NameLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRADDR_Tok, GPVendorAddress.ADDRESS1.TrimEnd(), RemitAddress.Address, AddressLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRADDR2_Tok, CopyStr(GPVendorAddress.ADDRESS2.TrimEnd(), 1, MaxStrLen(RemitAddress."Address 2")), RemitAddress."Address 2", Address2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRCITY_Tok, CopyStr(GPVendorAddress.CITY.TrimEnd(), 1, MaxStrLen(RemitAddress.City)), RemitAddress.City, CityLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRPOSTCODE_Tok, UpperCase(GPVendorAddress.ZIPCODE.TrimEnd()), RemitAddress."Post Code", PostCodeLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRPHN_Tok, HelperFunctions.CleanGPPhoneOrFaxNumber(GPVendorAddress.PHNUMBR1), RemitAddress."Phone No.", PhoneLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRFAX_Tok, HelperFunctions.CleanGPPhoneOrFaxNumber(GPVendorAddress.FAXNUMBR), RemitAddress."Fax No.", FaxLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRCOUNTY_Tok, GPVendorAddress.STATE.TrimEnd(), RemitAddress.County, CountyLbl, true); + MigrationValidation.ValidateAreEqual(Test_REMITADDRCONTACT_Tok, GPVendorAddress.VNDCNTCT.TrimEnd(), RemitAddress.Contact, ContactLbl, true); + end; + end; + + if (AddressCode = AssignedPrimaryAddressCode) or (AddressCode <> AssignedRemitToAddressCode) then begin + EntityType := VendorOrderAddressEntityCaptionLbl; + MigrationValidation.SetContext(ValidatorCodeLbl, EntityType, ContextCode); + + if MigrationValidation.ValidateRecordExists(Test_ORDERADDREXISTS_Tok, OrderAddress.Get(Vendor."No.", AddressCode), StrSubstNo(MissingEntityTok, EntityType)) then begin + MigrationValidation.ValidateAreEqual(Test_ORDERADDRNAME_Tok, Vendor.Name, OrderAddress.Name, NameLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRADDR_Tok, GPVendorAddress.ADDRESS1.TrimEnd(), OrderAddress.Address, AddressLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRADDR2_Tok, CopyStr(GPVendorAddress.ADDRESS2.TrimEnd(), 1, MaxStrLen(OrderAddress."Address 2")), OrderAddress."Address 2", Address2Lbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRCITY_Tok, CopyStr(GPVendorAddress.CITY.TrimEnd(), 1, MaxStrLen(OrderAddress.City)), OrderAddress.City, CityLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRPOSTCODE_Tok, UpperCase(GPVendorAddress.ZIPCODE.TrimEnd()), OrderAddress."Post Code", PostCodeLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRPHN_Tok, HelperFunctions.CleanGPPhoneOrFaxNumber(GPVendorAddress.PHNUMBR1), OrderAddress."Phone No.", PhoneLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRFAX_Tok, HelperFunctions.CleanGPPhoneOrFaxNumber(GPVendorAddress.FAXNUMBR), OrderAddress."Fax No.", FaxLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRCOUNTY_Tok, GPVendorAddress.STATE.TrimEnd(), OrderAddress.County, CountyLbl, true); + MigrationValidation.ValidateAreEqual(Test_ORDERADDRCONTACT_Tok, GPVendorAddress.VNDCNTCT.TrimEnd(), OrderAddress.Contact, ContactLbl, true); + end; + end + until GPVendorAddress.Next() = 0; + end; + + local procedure ShouldMigrateTemporaryVendor(VendorNo: Text[75]): Boolean + var + GPCompanyAdditionalSettings: Record "GP Company Additional Settings"; + GPPM00200: Record "GP PM00200"; + GPPOP10100: Record "GP POP10100"; + GPVendorTransactions: Record "GP Vendor Transactions"; + HasOpenPurchaseOrders: Boolean; + HasOpenTransactions: Boolean; + IsTemporaryVendor: Boolean; + begin + if GPCompanyAdditionalSettings.GetMigrateTemporaryVendors() then + exit(true); + + GPPM00200.SetLoadFields(VENDSTTS); + if GPPM00200.Get(VendorNo) then + IsTemporaryVendor := GPPM00200.VENDSTTS = 3; + + if not IsTemporaryVendor then + exit(true); + + // Check for open POs + GPPOP10100.SetRange(POTYPE, GPPOP10100.POTYPE::Standard); + GPPOP10100.SetRange(POSTATUS, 1, 4); + GPPOP10100.SetRange(VENDORID, VendorNo); + HasOpenPurchaseOrders := GPPOP10100.Count() > 0; + + // Check for open AP transactions + GPVendorTransactions.SetRange(VENDORID, VendorNo); + HasOpenTransactions := GPVendorTransactions.Count() > 0; + + if not HasOpenPurchaseOrders then + if not HasOpenTransactions then + exit(false); + + exit(true); + end; + + local procedure GetPreferredGPVendorBankCode(VendorNo: Code[20]): Code[20] + var + GPPM00200: Record "GP PM00200"; + GPSY06000: Record "GP SY06000"; + SearchGPSY06000: Record "GP SY06000"; + AddressCode: Code[10]; + PrimaryAddressCode: Code[10]; + RemitToAddressCode: Code[10]; + GeneratedBankCode: Code[20]; + PreferredBankCode: Code[20]; + BankAccountCounter: Integer; + MaxSupportedVendorNoLength: Integer; + NumberOfAccounts: Integer; + begin + PreferredBankCode := ''; + BankAccountCounter := 0; + + GPPM00200.SetLoadFields(VADDCDPR, VADCDTRO); + if not GPPM00200.Get(VendorNo) then + exit; + + GPSY06000.SetLoadFields(CustomerVendor_ID); + GPSY06000.SetRange(CustomerVendor_ID, VendorNo); + GPSY06000.SetRange(INACTIVE, false); + NumberOfAccounts := GPSY06000.Count(); + + if NumberOfAccounts = 0 then + exit; + + // Single bank account, then return the VendorNo as the bank code + if NumberOfAccounts = 1 then + exit(VendorNo); + + // Multiple bank accounts, duplicate migration logic for multiple account handling + PrimaryAddressCode := CopyStr(GPPM00200.VADDCDPR.Trim(), 1, MaxStrLen(PrimaryAddressCode)); + RemitToAddressCode := CopyStr(GPPM00200.VADCDTRO.Trim(), 1, MaxStrLen(RemitToAddressCode)); + MaxSupportedVendorNoLength := MaxStrLen(GeneratedBankCode) - StrLen(Format(NumberOfAccounts)) - 1; + + if StrLen(VendorNo) > MaxSupportedVendorNoLength then +#pragma warning disable AA0139 + VendorNo := CopyStr(VendorNo, 1, MaxSupportedVendorNoLength); +#pragma warning restore AA0139 + + GPSY06000.SetLoadFields(ADRSCODE); + if GPSY06000.FindSet() then + repeat + BankAccountCounter := BankAccountCounter + 1; + GeneratedBankCode := CopyStr(VendorNo + '-' + Format(BankAccountCounter), 1, MaxStrLen(GeneratedBankCode)); + AddressCode := CopyStr(GPSY06000.ADRSCODE.TrimEnd(), 1, MaxStrLen(AddressCode)); + + if AddressCode = RemitToAddressCode then + PreferredBankCode := GeneratedBankCode + else + if AddressCode = PrimaryAddressCode then begin + SearchGPSY06000.SetRange("CustomerVendor_ID", VendorNo); + SearchGPSY06000.SetRange("ADRSCODE", RemitToAddressCode); + SearchGPSY06000.SetRange("INACTIVE", false); + if SearchGPSY06000.IsEmpty() then + PreferredBankCode := GeneratedBankCode; + end; + until GPSY06000.Next() = 0; + + exit(PreferredBankCode); + end; + + local procedure GetAccountFilter(AccountNo: Text[50]; ACCTYPE: Integer): Text + var + GPGL00100: Record "GP GL00100"; + FilterText: Text; + begin + GPGL00100.SetLoadFields(ACTINDX); + GPGL00100.SetRange(ACCTTYPE, ACCTYPE); + GPGL00100.SetRange(MNACSGMT, AccountNo); + GPGL00100.SetRange(PSTNGTYP, 0); + GPGL00100.SetRange(Clear_Balance, false); + if GPGL00100.FindSet() then + repeat + if FilterText <> '' then + FilterText += '|' + Format(GPGL00100.ACTINDX) + else + FilterText := Format(GPGL00100.ACTINDX); + until GPGL00100.Next() = 0; + + exit(FilterText); + end; + + internal procedure RoundWithSpecPrecision(Amount: Decimal): Decimal + begin + exit(Round(Amount, DefaultCurrency."Amount Rounding Precision")); + end; + + local procedure LogValidationProgress(ValidationStep: Code[20]) + begin + Clear(CompanyValidationProgress); + CompanyValidationProgress.Validate("Company Name", CompanyNameTxt); + CompanyValidationProgress.Validate("Validator Code", ValidatorCodeLbl); + CompanyValidationProgress.Validate("Validation Step", ValidationStep); + CompanyValidationProgress.Insert(true); + end; + + internal procedure GetValidatorCode(): Code[20]; + begin + exit('GP'); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Hybrid Cloud Management", OnPrepareMigrationValidation, '', false, false)] + local procedure OnPrepareMigrationValidation(ProductID: Text[250]) + var + HybridGPWizard: Codeunit "Hybrid GP Wizard"; + begin + if ProductID <> HybridGPWizard.ProductId() then + exit; + + RegisterValidator(); + + AddTest(Test_ACCOUNTEXISTS_Tok, 'G/L Account', 'Missing Account'); + AddTest(Test_ACCOUNTNAME_Tok, 'G/L Account', 'Name'); + AddTest(Test_ACCOUNTTYPE_Tok, 'G/L Account', 'Account Type'); + AddTest(Test_ACCOUNTCATEGORY_Tok, 'G/L Account', 'Account Category'); + AddTest(Test_ACCOUNTDEBCRED_Tok, 'G/L Account', 'Debit/Credit'); + AddTest(Test_ACCOUNTSUBCATEGORY_Tok, 'G/L Account', 'Account Subcategory'); + AddTest(Test_ACCOUNTINCBAL_Tok, 'G/L Account', 'Income/Balance'); + AddTest(Test_ACCOUNTBALANCE_Tok, 'G/L Account', 'Balance'); + AddTest(Test_STATACCOUNTEXISTS_Tok, 'Statistical Account', 'Missing Account'); + AddTest(Test_STATACCOUNTNAME_Tok, 'Statistical Account', 'Name'); + AddTest(Test_STATACCOUNTDIM1_Tok, 'Statistical Account', 'Dimension 1'); + AddTest(Test_STATACCOUNTDIM2_Tok, 'Statistical Account', 'Dimension 2'); + AddTest(Test_STATACCOUNTBALANCE_Tok, 'Statistical Account', 'Balance'); + AddTest(Test_BANKACCOUNTEXISTS_Tok, 'Bank Account', 'Missing Bank Account'); + AddTest(Test_BANKACCOUNTNAME_Tok, 'Bank Account', 'Name'); + AddTest(Test_BANKACCOUNTNO_Tok, 'Bank Account', 'Bank Account No.'); + AddTest(Test_BANKACCOUNTADDR_Tok, 'Bank Account', 'Address'); + AddTest(Test_BANKACCOUNTADDR2_Tok, 'Bank Account', 'Address 2'); + AddTest(Test_BANKACCOUNTCITY_Tok, 'Bank Account', 'City'); + AddTest(Test_BANKACCOUNTCOUNTY_Tok, 'Bank Account', 'County (State)'); + AddTest(Test_BANKACCOUNTPOSTCODE_Tok, 'Bank Account', 'Post Code'); + AddTest(Test_BANKACCOUNTPHN_Tok, 'Bank Account', 'Phone'); + AddTest(Test_BANKACCOUNTFAX_Tok, 'Bank Account', 'Fax'); + AddTest(Test_BANKACCOUNTTRANSITNO_Tok, 'Bank Account', 'Transit No.'); + AddTest(Test_BANKACCOUNTBRANCHNO_Tok, 'Bank Account', 'Bank Branch No.'); + AddTest(Test_BANKACCOUNTBALANCE_Tok, 'Bank Account', 'Balance'); + AddTest(Test_CUSTOMEREXISTS_Tok, 'Customer', 'Missing Customer'); + AddTest(Test_CUSTOMERNAME_Tok, 'Customer', 'Name'); + AddTest(Test_CUSTOMERPOSTINGGROUP_Tok, 'Customer', 'Customer Posting Group'); + AddTest(Test_CUSTOMERADDR_Tok, 'Customer', 'Address'); + AddTest(Test_CUSTOMERADDR2_Tok, 'Customer', 'Address 2'); + AddTest(Test_CUSTOMERCITY_Tok, 'Customer', 'City'); + AddTest(Test_CUSTOMERPHN_Tok, 'Customer', 'Phone'); + AddTest(Test_CUSTOMERFAX_Tok, 'Customer', 'Fax'); + AddTest(Test_CUSTOMERNAME2_Tok, 'Customer', 'Name 2'); + AddTest(Test_CUSTOMERCREDITLMT_Tok, 'Customer', 'Credit Limit'); + AddTest(Test_CUSTOMERCONTACT_Tok, 'Customer', 'Contact'); + AddTest(Test_CUSTOMERSALESPERSON_Tok, 'Customer', 'Sales Person'); + AddTest(Test_CUSTOMERSHIPMETHOD_Tok, 'Customer', 'Shipment Method'); + AddTest(Test_CUSTOMERPMTTERMS_Tok, 'Customer', 'Payment Terms'); + AddTest(Test_CUSTOMERTERRITORY_Tok, 'Customer', 'Territory'); + AddTest(Test_CUSTOMERTAXAREA_Tok, 'Customer', 'Tax Area'); + AddTest(Test_CUSTOMERTAXLIABLE_Tok, 'Customer', 'Tax Liable'); + AddTest(Test_CUSTOMERBALANCE_Tok, 'Customer', 'Balance'); + AddTest(Test_SHIPADDREXISTS_Tok, 'Customer - Ship-to Address', 'Missing address'); + AddTest(Test_SHIPADDRNAME_Tok, 'Customer - Ship-to Address', 'Name'); + AddTest(Test_SHIPADDRADDR_Tok, 'Customer - Ship-to Address', 'Address'); + AddTest(Test_SHIPADDRADDR2_Tok, 'Customer - Ship-to Address', 'Address 2'); + AddTest(Test_SHIPADDRCITY_Tok, 'Customer - Ship-to Address', 'City'); + AddTest(Test_SHIPADDRPOSTCODE_Tok, 'Customer - Ship-to Address', 'Post Code'); + AddTest(Test_SHIPADDRPHN_Tok, 'Customer - Ship-to Address', 'Phone'); + AddTest(Test_SHIPADDRFAX_Tok, 'Customer - Ship-to Address', 'Fax'); + AddTest(Test_SHIPADDRCONTACT_Tok, 'Customer - Ship-to Address', 'Contact'); + AddTest(Test_SHIPADDRSHIPMETHOD_Tok, 'Customer - Ship-to Address', 'Shipment Method'); + AddTest(Test_SHIPADDRCOUNTY_Tok, 'Customer - Ship-to Address', 'County (State)'); + AddTest(Test_SHIPADDRTAXAREA_Tok, 'Customer - Ship-to Address', 'Tax Area'); + AddTest(Test_ITEMEXISTS_Tok, 'Item', 'Missing Item'); + AddTest(Test_ITEMTYPE_Tok, 'Item', 'Type'); + AddTest(Test_ITEMDESC_Tok, 'Item', 'Description'); + AddTest(Test_ITEMDESC2_Tok, 'Item', 'Description 2'); + AddTest(Test_ITEMSEARCHDESC_Tok, 'Item', 'Search Description'); + AddTest(Test_ITEMPOSTINGGROUP_Tok, 'Item', 'Inventory Posting Group'); + AddTest(Test_ITEMUNITLISTPRICE_Tok, 'Item', 'Unit List Price'); + AddTest(Test_ITEMCOSTMETHOD_Tok, 'Item', 'Costing Method'); + AddTest(Test_ITEMBASEUOFM_Tok, 'Item', 'Base Unit of Measure'); + AddTest(Test_ITEMPURCHUOFM_Tok, 'Item', 'Purch. Unit of Measure'); + AddTest(Test_ITEMTRACKINGCODE_Tok, 'Item', 'Item Tracking Code'); + AddTest(Test_ITEMINVENTORY_Tok, 'Item', 'Inventory'); + AddTest(Test_POEXISTS_Tok, 'Purchase Order', 'Missing Purchase Order'); + AddTest(Test_POBUYFROMVEND_Tok, 'Purchase Order', 'Buy-from Vendor No.'); + AddTest(Test_POPAYTOVEND_Tok, 'Purchase Order', 'Pay-to Vendor No.'); + AddTest(Test_PODOCDATE_Tok, 'Purchase Order', 'Document Date'); + AddTest(Test_POLINEEXISTS_Tok, 'Purchase Order - Line', 'Missing PO Line'); + AddTest(Test_POLINEQTY_Tok, 'Purchase Order - Line', 'Quantity'); + AddTest(Test_POLINEQTYRECV_Tok, 'Purchase Order - Line', 'Quantity Received'); + AddTest(Test_VENDOREXISTS_Tok, 'Vendor', 'Missing Vendor'); + AddTest(Test_VENDORNAME_Tok, 'Vendor', 'Name'); + AddTest(Test_VENDORNAME2_Tok, 'Vendor', 'Name 2'); + AddTest(Test_VENDORPOSTINGGROUP_Tok, 'Vendor', 'Vendor Posting Group'); + AddTest(Test_VENDORPREFBANKACCT_Tok, 'Vendor', 'Preferred Bank Account'); + AddTest(Test_VENDORADDR_Tok, 'Vendor', 'Address'); + AddTest(Test_VENDORADDR2_Tok, 'Vendor', 'Address 2'); + AddTest(Test_VENDORCITY_Tok, 'Vendor', 'City'); + AddTest(Test_VENDORPHN_Tok, 'Vendor', 'Phone'); + AddTest(Test_VENDORFAX_Tok, 'Vendor', 'Fax'); + AddTest(Test_VENDORCONTACT_Tok, 'Vendor', 'Contact'); + AddTest(Test_VENDORSHIPMETHOD_Tok, 'Vendor', 'Shipment Method'); + AddTest(Test_VENDORPMTTERMS_Tok, 'Vendor', 'Payment Terms'); + AddTest(Test_VENDORTAXAREA_Tok, 'Vendor', 'Tax Area'); + AddTest(Test_VENDORTAXLIABLE_Tok, 'Vendor', 'Tax Liable'); + AddTest(Test_VENDORBALANCE_Tok, 'Vendor', 'Balance'); + AddTest(Test_ORDERADDREXISTS_Tok, 'Vendor - Order Address', 'Missing address'); + AddTest(Test_ORDERADDRNAME_Tok, 'Vendor - Order Address', 'Name'); + AddTest(Test_ORDERADDRADDR_Tok, 'Vendor - Order Address', 'Address'); + AddTest(Test_ORDERADDRADDR2_Tok, 'Vendor - Order Address', 'Address 2'); + AddTest(Test_ORDERADDRCITY_Tok, 'Vendor - Order Address', 'City'); + AddTest(Test_ORDERADDRPOSTCODE_Tok, 'Vendor - Order Address', 'Post Code'); + AddTest(Test_ORDERADDRPHN_Tok, 'Vendor - Order Address', 'Phone'); + AddTest(Test_ORDERADDRFAX_Tok, 'Vendor - Order Address', 'Fax'); + AddTest(Test_ORDERADDRCOUNTY_Tok, 'Vendor - Order Address', 'County (State)'); + AddTest(Test_ORDERADDRCONTACT_Tok, 'Vendor - Order Address', 'Contact'); + AddTest(Test_REMITADDREXISTS_Tok, 'Vendor - Remit Address', 'Missing address'); + AddTest(Test_REMITADDRNAME_Tok, 'Vendor - Remit Address', 'Name'); + AddTest(Test_REMITADDRADDR_Tok, 'Vendor - Remit Address', 'Address'); + AddTest(Test_REMITADDRADDR2_Tok, 'Vendor - Remit Address', 'Address 2'); + AddTest(Test_REMITADDRCITY_Tok, 'Vendor - Remit Address', 'City'); + AddTest(Test_REMITADDRPOSTCODE_Tok, 'Vendor - Remit Address', 'Post Code'); + AddTest(Test_REMITADDRPHN_Tok, 'Vendor - Remit Address', 'Phone'); + AddTest(Test_REMITADDRFAX_Tok, 'Vendor - Remit Address', 'Fax'); + AddTest(Test_REMITADDRCOUNTY_Tok, 'Vendor - Remit Address', 'County (State)'); + AddTest(Test_REMITADDRCONTACT_Tok, 'Vendor - Remit Address', 'Contact'); + end; + + local procedure RegisterValidator() + var + MigrationValidatorRegistry: Record "Migration Validator Registry"; + GPMigrationValidator: Codeunit "GP Migration Validator"; + HybridGPWizard: Codeunit "Hybrid GP Wizard"; + ValidatorCode: Code[20]; + MigrationType: Text[250]; + ValidatorCodeunitId: Integer; + begin + ValidatorCode := GPMigrationValidator.GetValidatorCode(); + MigrationType := HybridGPWizard.ProductId(); + ValidatorCodeunitId := Codeunit::"GP Migration Validator"; + if not MigrationValidatorRegistry.Get(ValidatorCode) then begin + MigrationValidatorRegistry.Validate("Validator Code", ValidatorCode); + MigrationValidatorRegistry.Validate("Migration Type", MigrationType); + MigrationValidatorRegistry.Validate(Description, ValidatorDescriptionLbl); + MigrationValidatorRegistry.Validate("Codeunit Id", ValidatorCodeunitId); + MigrationValidatorRegistry.Validate(Automatic, false); + MigrationValidatorRegistry.Insert(true); + end; + end; + + local procedure AddTest(Code: Code[30]; Entity: Text[50]; Description: Text) + var + MigrationValidationTest: Record "Migration Validation Test"; + GPMigrationValidator: Codeunit "GP Migration Validator"; + begin + if not MigrationValidationTest.Get(Code, GPMigrationValidator.GetValidatorCode()) then begin + MigrationValidationTest.Validate(Code, Code); + MigrationValidationTest.Validate("Validator Code", GPMigrationValidator.GetValidatorCode()); + MigrationValidationTest.Validate(Entity, Entity); + MigrationValidationTest.Validate("Test Description", Description); + MigrationValidationTest.Insert(true); + end; + end; + + var + DefaultCurrency: Record Currency; + CompanyValidationProgress: Record "Company Validation Progress"; + MigrationValidation: Codeunit "Migration Validation"; + ValidatorCodeLbl: Code[20]; + CompanyNameTxt: Text; + TotalUnpostedBankBatchCount: Integer; + TotalUnpostedCustomerBatchCount: Integer; + TotalUnpostedGLBatchCount: Integer; + TotalUnpostedItemBatchCount: Integer; + TotalUnpostedStatisticalBatchCount: Integer; + TotalUnpostedVendorBatchCount: Integer; + AccountCategoryLbl: Label 'Account Category'; + AccountDebitCreditLbl: Label 'Debit/Credit'; + AccountIncomeBalanceLbl: Label 'Income/Balance'; + AccountNameLbl: Label 'Name'; + AccountSubcategoryLbl: Label 'Account Subcategory'; + AccountTypeLbl: Label 'Account Type'; + Address2Lbl: Label 'Address 2'; + AddressLbl: Label 'Address'; + BalanceLbl: Label 'Balance'; + BankAccountEntityCaptionLbl: Label 'Bank Account', MaxLength = 50; + BankAccountNumberLbl: Label 'Account Number'; + BankBranchNoLbl: Label 'Bank Branch No.'; + BaseUofMLbl: Label 'Base UofM'; + BeginningBalanceLbl: Label 'Beginning Balance'; + CityLbl: Label 'City'; + ContactLbl: Label 'Contact'; + CostingMethodLbl: Label 'Costing Method'; + CountyLbl: Label 'County'; + CreditLimitLbl: Label 'Credit Limit'; + CustomerAddressEntityCaptionLbl: Label 'Customer Ship-to Address', MaxLength = 50; + CustomerEntityCaptionLbl: Label 'Customer', MaxLength = 50; + CustomerPostingGroupLbl: Label 'Customer Posting Group'; + DefaultClassNameTok: Label 'GP', MaxLength = 20, Locked = true; + Description2Lbl: Label 'Description 2'; + DescriptionLbl: Label 'Description'; + Dimension1Lbl: Label 'Dimension 1'; + Dimension2Lbl: Label 'Dimension 2'; + DocumentDateLbl: Label 'Document Date'; + FaxLbl: Label 'Fax'; + GlAccountEntityCaptionLbl: Label 'G/L Account', MaxLength = 50; + ItemEntityCaptionLbl: Label 'Item', MaxLength = 50; + ItemPostingGroupLbl: Label 'Item Posting Group'; + ItemTrackingCodeLbl: Label 'Item Tracking Code'; + MissingEntityTok: Label 'Missing %1', Comment = '%1 = the entity being validated'; + Name2Lbl: Label 'Name 2'; + NameLbl: Label 'Name'; + PaymentTermsLbl: Label 'Payment Terms'; + PhoneLbl: Label 'Phone'; + PostCodeLbl: Label 'Post Code'; + PreferredBankAccountLbl: Label 'Preferred Bank Account'; + PurchaseOrderBuyFromVendorNoLbl: Label 'Buy-from Vendor No.'; + PurchaseOrderPayToVendorNoLbl: Label 'Pay-to Vendor No.'; + PurchUofMLbl: Label 'Purch. UofM'; + QuantityLbl: Label 'Quantity'; + QuantityRecLbl: Label 'Quantity Received'; + SalesPersonLbl: Label 'Sales Person'; + SearchDescription2Lbl: Label 'Search Description'; + ShipmentMethodLbl: Label 'Shipment Method'; + StatisticalAccountEntityCaptionLbl: Label 'Statistical Account', MaxLength = 50; + TaxAreaLbl: Label 'Tax Area'; + TaxLiableLbl: Label 'Tax Liable'; + TerritoryLbl: Label 'Territory'; + TransitNoLbl: Label 'Transit No.'; + TypeLbl: Label 'Type'; + UnitListPriceLbl: Label 'Unit List Price'; + VendorEntityCaptionLbl: Label 'Vendor', MaxLength = 50; + VendorOrderAddressEntityCaptionLbl: Label 'Vendor Order Address', MaxLength = 50; + VendorPostingGroupLbl: Label 'Vendor Posting Group'; + VendorRemitAddressEntityCaptionLbl: Label 'Vendor Remit Address', MaxLength = 50; + ValidationStepGLAccountLbl: Label 'GLACCOUNT', MaxLength = 20; + ValidationStepStatAccountLbl: Label 'STATACCOUNT', MaxLength = 20; + ValidationStepBankAccountLbl: Label 'BANKACCOUNT', MaxLength = 20; + ValidationStepCustomerLbl: Label 'CUSTOMER', MaxLength = 20; + ValidationStepItemLbl: Label 'ITEM', MaxLength = 20; + ValidationStepPurchaseOrderLbl: Label 'PURCHASEORDER', MaxLength = 20; + ValidationStepVendorLbl: Label 'VENDOR', MaxLength = 20; + ValidatorDescriptionLbl: Label 'GP migration validator', MaxLength = 250; + Test_ACCOUNTEXISTS_Tok: Label 'ACCOUNTEXISTS', Locked = true; + Test_ACCOUNTNAME_Tok: Label 'ACCOUNTNAME', Locked = true; + Test_ACCOUNTTYPE_Tok: Label 'ACCOUNTTYPE', Locked = true; + Test_ACCOUNTCATEGORY_Tok: Label 'ACCOUNTCATEGORY', Locked = true; + Test_ACCOUNTDEBCRED_Tok: Label 'ACCOUNTDEBCRED', Locked = true; + Test_ACCOUNTSUBCATEGORY_Tok: Label 'ACCOUNTSUBCATEGORY', Locked = true; + Test_ACCOUNTINCBAL_Tok: Label 'ACCOUNTINCBAL', Locked = true; + Test_ACCOUNTBALANCE_Tok: Label 'ACCOUNTBALANCE', Locked = true; + Test_STATACCOUNTEXISTS_Tok: Label 'STATACCOUNTEXISTS', Locked = true; + Test_STATACCOUNTNAME_Tok: Label 'STATACCOUNTNAME', Locked = true; + Test_STATACCOUNTDIM1_Tok: Label 'STATACCOUNTDIM1', Locked = true; + Test_STATACCOUNTDIM2_Tok: Label 'STATACCOUNTDIM2', Locked = true; + Test_STATACCOUNTBALANCE_Tok: Label 'STATACCOUNTBALANCE', Locked = true; + Test_BANKACCOUNTEXISTS_Tok: Label 'BANKACCOUNTEXISTS', Locked = true; + Test_BANKACCOUNTNAME_Tok: Label 'BANKACCOUNTNAME', Locked = true; + Test_BANKACCOUNTNO_Tok: Label 'BANKACCOUNTNO', Locked = true; + Test_BANKACCOUNTADDR_Tok: Label 'BANKACCOUNTADDR', Locked = true; + Test_BANKACCOUNTADDR2_Tok: Label 'BANKACCOUNTADDR2', Locked = true; + Test_BANKACCOUNTCITY_Tok: Label 'BANKACCOUNTCITY', Locked = true; + Test_BANKACCOUNTCOUNTY_Tok: Label 'BANKACCOUNTCOUNTY', Locked = true; + Test_BANKACCOUNTPOSTCODE_Tok: Label 'BANKACCOUNTPOSTCODE', Locked = true; + Test_BANKACCOUNTPHN_Tok: Label 'BANKACCOUNTPHN', Locked = true; + Test_BANKACCOUNTFAX_Tok: Label 'BANKACCOUNTFAX', Locked = true; + Test_BANKACCOUNTTRANSITNO_Tok: Label 'BANKACCOUNTTRANSITNO', Locked = true; + Test_BANKACCOUNTBRANCHNO_Tok: Label 'BANKACCOUNTBRANCHNO', Locked = true; + Test_BANKACCOUNTBALANCE_Tok: Label 'BANKACCOUNTBALANCE', Locked = true; + Test_CUSTOMEREXISTS_Tok: Label 'CUSTOMEREXISTS', Locked = true; + Test_CUSTOMERNAME_Tok: Label 'CUSTOMERNAME', Locked = true; + Test_CUSTOMERPOSTINGGROUP_Tok: Label 'CUSTOMERPOSTINGGROUP', Locked = true; + Test_CUSTOMERADDR_Tok: Label 'CUSTOMERADDR', Locked = true; + Test_CUSTOMERADDR2_Tok: Label 'CUSTOMERADDR2', Locked = true; + Test_CUSTOMERCITY_Tok: Label 'CUSTOMERCITY', Locked = true; + Test_CUSTOMERPHN_Tok: Label 'CUSTOMERPHN', Locked = true; + Test_CUSTOMERFAX_Tok: Label 'CUSTOMERFAX', Locked = true; + Test_CUSTOMERNAME2_Tok: Label 'CUSTOMERNAME2', Locked = true; + Test_CUSTOMERCREDITLMT_Tok: Label 'CUSTOMERCREDITLMT', Locked = true; + Test_CUSTOMERCONTACT_Tok: Label 'CUSTOMERCONTACT', Locked = true; + Test_CUSTOMERSALESPERSON_Tok: Label 'CUSTOMERSALESPERSON', Locked = true; + Test_CUSTOMERSHIPMETHOD_Tok: Label 'CUSTOMERSHIPMETHOD', Locked = true; + Test_CUSTOMERPMTTERMS_Tok: Label 'CUSTOMERPMTTERMS', Locked = true; + Test_CUSTOMERTERRITORY_Tok: Label 'CUSTOMERTERRITORY', Locked = true; + Test_CUSTOMERTAXAREA_Tok: Label 'CUSTOMERTAXAREA', Locked = true; + Test_CUSTOMERTAXLIABLE_Tok: Label 'CUSTOMERTAXLIABLE', Locked = true; + Test_CUSTOMERBALANCE_Tok: Label 'CUSTOMERBALANCE', Locked = true; + Test_SHIPADDREXISTS_Tok: Label 'SHIPADDREXISTS', Locked = true; + Test_SHIPADDRNAME_Tok: Label 'SHIPADDRNAME', Locked = true; + Test_SHIPADDRADDR_Tok: Label 'SHIPADDRADDR', Locked = true; + Test_SHIPADDRADDR2_Tok: Label 'SHIPADDRADDR2', Locked = true; + Test_SHIPADDRCITY_Tok: Label 'SHIPADDRCITY', Locked = true; + Test_SHIPADDRPOSTCODE_Tok: Label 'SHIPADDRPOSTCODE', Locked = true; + Test_SHIPADDRPHN_Tok: Label 'SHIPADDRPHN', Locked = true; + Test_SHIPADDRFAX_Tok: Label 'SHIPADDRFAX', Locked = true; + Test_SHIPADDRCONTACT_Tok: Label 'SHIPADDRCONTACT', Locked = true; + Test_SHIPADDRSHIPMETHOD_Tok: Label 'SHIPADDRSHIPMETHOD', Locked = true; + Test_SHIPADDRCOUNTY_Tok: Label 'SHIPADDRCOUNTY', Locked = true; + Test_SHIPADDRTAXAREA_Tok: Label 'SHIPADDRTAXAREA', Locked = true; + Test_ITEMEXISTS_Tok: Label 'ITEMEXISTS', Locked = true; + Test_ITEMTYPE_Tok: Label 'ITEMTYPE', Locked = true; + Test_ITEMDESC_Tok: Label 'ITEMDESC', Locked = true; + Test_ITEMDESC2_Tok: Label 'ITEMDESC2', Locked = true; + Test_ITEMSEARCHDESC_Tok: Label 'ITEMSEARCHDESC', Locked = true; + Test_ITEMPOSTINGGROUP_Tok: Label 'ITEMPOSTINGGROUP', Locked = true; + Test_ITEMUNITLISTPRICE_Tok: Label 'ITEMUNITLISTPRICE', Locked = true; + Test_ITEMCOSTMETHOD_Tok: Label 'ITEMCOSTMETHOD', Locked = true; + Test_ITEMBASEUOFM_Tok: Label 'ITEMBASEUOFM', Locked = true; + Test_ITEMPURCHUOFM_Tok: Label 'ITEMPURCHUOFM', Locked = true; + Test_ITEMTRACKINGCODE_Tok: Label 'ITEMTRACKINGCODE', Locked = true; + Test_ITEMINVENTORY_Tok: Label 'ITEMINVENTORY', Locked = true; + Test_POEXISTS_Tok: Label 'POEXISTS', Locked = true; + Test_POBUYFROMVEND_Tok: Label 'POBUYFROMVEND', Locked = true; + Test_POPAYTOVEND_Tok: Label 'POPAYTOVEND', Locked = true; + Test_PODOCDATE_Tok: Label 'PODOCDATE', Locked = true; + Test_POLINEEXISTS_Tok: Label 'POLINEEXISTS', Locked = true; + Test_POLINEQTY_Tok: Label 'POLINEQTY', Locked = true; + Test_POLINEQTYRECV_Tok: Label 'POLINEQTYRECV', Locked = true; + Test_VENDOREXISTS_Tok: Label 'VENDOREXISTS', Locked = true; + Test_VENDORNAME_Tok: Label 'VENDORNAME', Locked = true; + Test_VENDORNAME2_Tok: Label 'VENDORNAME2', Locked = true; + Test_VENDORPOSTINGGROUP_Tok: Label 'VENDORPOSTINGGROUP', Locked = true; + Test_VENDORPREFBANKACCT_Tok: Label 'VENDORPREFBANKACCT', Locked = true; + Test_VENDORADDR_Tok: Label 'VENDORADDR', Locked = true; + Test_VENDORADDR2_Tok: Label 'VENDORADDR2', Locked = true; + Test_VENDORCITY_Tok: Label 'VENDORCITY', Locked = true; + Test_VENDORPHN_Tok: Label 'VENDORPHN', Locked = true; + Test_VENDORFAX_Tok: Label 'VENDORFAX', Locked = true; + Test_VENDORCONTACT_Tok: Label 'VENDORCONTACT', Locked = true; + Test_VENDORSHIPMETHOD_Tok: Label 'VENDORSHIPMETHOD', Locked = true; + Test_VENDORPMTTERMS_Tok: Label 'VENDORPMTTERMS', Locked = true; + Test_VENDORTAXAREA_Tok: Label 'VENDORTAXAREA', Locked = true; + Test_VENDORTAXLIABLE_Tok: Label 'VENDORTAXLIABLE', Locked = true; + Test_VENDORBALANCE_Tok: Label 'VENDORBALANCE', Locked = true; + Test_ORDERADDREXISTS_Tok: Label 'ORDERADDREXISTS', Locked = true; + Test_ORDERADDRNAME_Tok: Label 'ORDERADDRNAME', Locked = true; + Test_ORDERADDRADDR_Tok: Label 'ORDERADDRADDR', Locked = true; + Test_ORDERADDRADDR2_Tok: Label 'ORDERADDRADDR2', Locked = true; + Test_ORDERADDRCITY_Tok: Label 'ORDERADDRCITY', Locked = true; + Test_ORDERADDRPOSTCODE_Tok: Label 'ORDERADDRPOSTCODE', Locked = true; + Test_ORDERADDRPHN_Tok: Label 'ORDERADDRPHN', Locked = true; + Test_ORDERADDRFAX_Tok: Label 'ORDERADDRFAX', Locked = true; + Test_ORDERADDRCOUNTY_Tok: Label 'ORDERADDRCOUNTY', Locked = true; + Test_ORDERADDRCONTACT_Tok: Label 'ORDERADDRCONTACT', Locked = true; + Test_REMITADDREXISTS_Tok: Label 'REMITADDREXISTS', Locked = true; + Test_REMITADDRNAME_Tok: Label 'REMITADDRNAME', Locked = true; + Test_REMITADDRADDR_Tok: Label 'REMITADDRADDR', Locked = true; + Test_REMITADDRADDR2_Tok: Label 'REMITADDRADDR2', Locked = true; + Test_REMITADDRCITY_Tok: Label 'REMITADDRCITY', Locked = true; + Test_REMITADDRPOSTCODE_Tok: Label 'REMITADDRPOSTCODE', Locked = true; + Test_REMITADDRPHN_Tok: Label 'REMITADDRPHN', Locked = true; + Test_REMITADDRFAX_Tok: Label 'REMITADDRFAX', Locked = true; + Test_REMITADDRCOUNTY_Tok: Label 'REMITADDRCOUNTY', Locked = true; + Test_REMITADDRCONTACT_Tok: Label 'REMITADDRCONTACT', Locked = true; + +} \ No newline at end of file diff --git a/Apps/W1/HybridGP/app/src/codeunits/HybridGPWizard.codeunit.al b/Apps/W1/HybridGP/app/src/codeunits/HybridGPWizard.codeunit.al index c8e4926357..4604bf594a 100644 --- a/Apps/W1/HybridGP/app/src/codeunits/HybridGPWizard.codeunit.al +++ b/Apps/W1/HybridGP/app/src/codeunits/HybridGPWizard.codeunit.al @@ -136,6 +136,7 @@ codeunit 4015 "Hybrid GP Wizard" HybridReplicationDetail: Record "Hybrid Replication Detail"; GPMigrationErrorOverview: Record "GP Migration Error Overview"; GPMigrationWarnings: Record "GP Migration Warnings"; + MigrationValidationError: Record "Migration Validation Error"; begin GPCompanyMigrationSettings.Reset(); if GPCompanyMigrationSettings.FindSet() then @@ -158,6 +159,9 @@ codeunit 4015 "Hybrid GP Wizard" if not GPMigrationWarnings.IsEmpty() then GPMigrationWarnings.DeleteAll(); + + if not MigrationValidationError.IsEmpty() then + MigrationValidationError.DeleteAll(); end; [EventSubscriber(ObjectType::Table, Database::"Company", 'OnAfterDeleteEvent', '', false, false)] @@ -170,6 +174,7 @@ codeunit 4015 "Hybrid GP Wizard" HybridReplicationDetail: Record "Hybrid Replication Detail"; GPMigrationErrorOverview: Record "GP Migration Error Overview"; GPMigrationWarnings: Record "GP Migration Warnings"; + MigrationValidationError: Record "Migration Validation Error"; begin if Rec.IsTemporary() then exit; @@ -197,6 +202,10 @@ codeunit 4015 "Hybrid GP Wizard" GPMigrationWarnings.SetRange("Company Name", Rec.Name); if not GPMigrationWarnings.IsEmpty() then GPMigrationWarnings.DeleteAll(); + + MigrationValidationError.SetRange("Company Name", Rec.Name); + if not MigrationValidationError.IsEmpty() then + MigrationValidationError.DeleteAll(); end; local procedure ProcessesAreRunning(): Boolean diff --git a/Apps/W1/HybridGP/app/src/pages/GPUpgradeSettings.Page.al b/Apps/W1/HybridGP/app/src/pages/GPUpgradeSettings.Page.al index 499c2bbc52..fe393bce61 100644 --- a/Apps/W1/HybridGP/app/src/pages/GPUpgradeSettings.Page.al +++ b/Apps/W1/HybridGP/app/src/pages/GPUpgradeSettings.Page.al @@ -1,4 +1,5 @@ namespace Microsoft.DataMigration.GP; +using Microsoft.DataMigration; page 40043 "GP Upgrade Settings" { @@ -12,6 +13,29 @@ page 40043 "GP Upgrade Settings" { area(Content) { + group(AutomaticValidation) + { + Caption = 'Automatic Validation'; + + field(GPAutomaticValidation; GPAutoValidation) + { + ApplicationArea = All; + Caption = 'GP'; + ToolTip = 'Specifies whether automatic validation is enabled for the primary GP migration.'; + + trigger OnValidate() + var + MigrationValidationRegistry: Record "Migration Validator Registry"; + GPMigrtionValidator: Codeunit "GP Migration Validator"; + begin + if MigrationValidationRegistry.Get(GPMigrtionValidator.GetValidatorCode()) then begin + MigrationValidationRegistry.Validate(Automatic, GPAutoValidation); + MigrationValidationRegistry.Modify(true); + end; + end; + } + } + group(ErrorHandling) { Caption = 'Error Handling'; @@ -49,7 +73,16 @@ page 40043 "GP Upgrade Settings" } trigger OnOpenPage() + var + MigrationValidationRegistry: Record "Migration Validator Registry"; + GPMigrtionValidator: Codeunit "GP Migration Validator"; begin + if MigrationValidationRegistry.Get(GPMigrtionValidator.GetValidatorCode()) then + GPAutoValidation := MigrationValidationRegistry.Automatic; + Rec.GetonInsertGPUpgradeSettings(Rec); end; + + var + GPAutoValidation: Boolean; } \ No newline at end of file diff --git a/Apps/W1/HybridGP/app/src/pages/HybridGPOverviewFb.Page.al b/Apps/W1/HybridGP/app/src/pages/HybridGPOverviewFb.Page.al index df505e1037..e131808f24 100644 --- a/Apps/W1/HybridGP/app/src/pages/HybridGPOverviewFb.Page.al +++ b/Apps/W1/HybridGP/app/src/pages/HybridGPOverviewFb.Page.al @@ -94,7 +94,10 @@ page 40125 "Hybrid GP Overview Fb" HelperFunctions: Codeunit "Helper Functions"; TotalGLBatchCount: Integer; TotalStatisticalBatchCount: Integer; + TotalBankBatchCount: Integer; + TotalCustomerBatchCount: Integer; TotalItemBatchCount: Integer; + TotalVendorBatchCount: Integer; CompanyHasFailedBatches: Boolean; FailedBatchMsgBuilder: TextBuilder; AddComma: Boolean; @@ -113,9 +116,12 @@ page 40125 "Hybrid GP Overview Fb" repeat TotalGLBatchCount := 0; TotalStatisticalBatchCount := 0; + TotalBankBatchCount := 0; + TotalCustomerBatchCount := 0; TotalItemBatchCount := 0; + TotalVendorBatchCount := 0; - HelperFunctions.GetUnpostedBatchCountForCompany(HybridCompanyStatus.Name, TotalGLBatchCount, TotalStatisticalBatchCount, TotalItemBatchCount); + HelperFunctions.GetUnpostedBatchCountForCompany(HybridCompanyStatus.Name, TotalGLBatchCount, TotalStatisticalBatchCount, TotalBankBatchCount, TotalCustomerBatchCount, TotalItemBatchCount, TotalVendorBatchCount); FailedBatchCount := FailedBatchCount + TotalGLBatchCount + TotalStatisticalBatchCount + TotalItemBatchCount; CompanyHasFailedBatches := (TotalGLBatchCount > 0) or (TotalItemBatchCount > 0); if CompanyHasFailedBatches then begin diff --git a/Apps/W1/HybridGP/app/src/pages/IntelligentCloudExtension.PageExt.al b/Apps/W1/HybridGP/app/src/pages/IntelligentCloudExtension.PageExt.al index 6c124ca695..079ea3f292 100644 --- a/Apps/W1/HybridGP/app/src/pages/IntelligentCloudExtension.PageExt.al +++ b/Apps/W1/HybridGP/app/src/pages/IntelligentCloudExtension.PageExt.al @@ -52,8 +52,11 @@ pageextension 4015 "Intelligent Cloud Extension" extends "Intelligent Cloud Mana trigger OnAction() var + HybridCloudManagement: Codeunit "Hybrid Cloud Management"; GPMigrationConfiguration: Page "GP Migration Configuration"; begin + HybridCloudManagement.PrepareMigrationValidation(); // TODO: REMOVE AFTER LOCAL TESTING + GPMigrationConfiguration.ShouldShowManagementPromptOnClose(false); GPMigrationConfiguration.Run(); end;