Codeunit 1798 Data Migration Mgt., source in 29

Source29

src/Layers/W1/BaseApp/System/DataMigration/DataMigrationMgt.Codeunit.al787 lines, Copyright (c) Microsoft Corporation. MIT

namespace System.Integration;

using Microsoft.Finance.GeneralLedger.Account;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Journal;
using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Customer;
using System.Environment.Configuration;
using System.Threading;

codeunit 1798 "Data Migration Mgt."
{
    TableNo = "Job Queue Entry";

    trigger OnRun()
    var
        DataMigrationError: Record "Data Migration Error";
        DataMigrationStatus: Record "Data Migration Status";
        Retry: Boolean;
        DataCreationFailed: Boolean;
    begin
        EnableDataMigrationNotificationForAllUsers();
        DataMigrationStatus.Get(Rec."Record ID to Process");
        DataMigrationStatus.SetRange("Migration Type", DataMigrationStatus."Migration Type");
        Retry := Rec."Parameter String" = RetryTxt;

        OnBeforeMigrationStarted(DataMigrationStatus, Retry);

        if not Retry then begin
            DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::Pending);
            DataMigrationFacade.OnFillStagingTables();
            // Close the transaction here otherwise the CODEUNIT.RUN cannot be invoked
            Commit();
        end else
            DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::"Completed with Errors");

        // migrate GL accounts (delete the existing ones on a first migration and if GL accounts are migrated)
        DataMigrationStatus.SetRange("Destination Table ID", DATABASE::"G/L Account");
        if DataMigrationStatus.FindFirst() and not Retry then
            if not CODEUNIT.Run(CODEUNIT::"Data Migration Del G/L Account") then
                DataMigrationError.CreateEntryNoStagingTable(DataMigrationStatus."Migration Type", DATABASE::"G/L Account");

        if CheckAbortRequestedAndMigrateEntity(
             DataMigrationStatus, DATABASE::"G/L Account", CODEUNIT::"GL Acc. Data Migration Facade", Retry)
        then
            exit;

        // migrate customers
        if CheckAbortRequestedAndMigrateEntity(DataMigrationStatus, DATABASE::Customer, CODEUNIT::"Customer Data Migration Facade", Retry) then
            exit;

        // migrate vendor
        if CheckAbortRequestedAndMigrateEntity(DataMigrationStatus, DATABASE::Vendor, CODEUNIT::"Vendor Data Migration Facade", Retry) then
            exit;

        // migrate items
        if CheckAbortRequestedAndMigrateEntity(DataMigrationStatus, DATABASE::Item, CODEUNIT::"Item Data Migration Facade", Retry) then
            exit;

        // migrate any other tables if any
        CheckAbortAndMigrateRemainingEntities(DataMigrationStatus, Retry);
        OnCreatePostMigrationData(DataMigrationStatus, DataCreationFailed);
        OnValidateMigration(DataMigrationStatus, DataCreationFailed);
        if DataCreationFailed then
            exit;

        OnAfterMigrationFinished(DataMigrationStatus, false, StartTime, Retry);
    end;

    var
        DataMigrationStatusFacade: Codeunit "Data Migration Status Facade";
        DataMigrationFacade: Codeunit "Data Migration Facade";
        AbortRequested: Boolean;
        StartTime: DateTime;
        RetryTxt: Label 'Retry', Locked = true;
        DataMigrationNotCompletedQst: Label 'A data migration is already in progress. To see the status of the migration, go to the %1 page. Do you want to do that now?', Comment = '%1 is the caption for Data Migration Overview';
        CustomerTableNotEmptyErr: Label 'The migration has stopped because we found some customers in %1. You must delete them and then restart the migration.', Comment = '%1 product name ';
        ItemTableNotEmptyErr: Label 'The migration has stopped because we found some items in %1. You must delete them and then restart the migration.', Comment = '%1 product name ';
        VendorTableNotEmptyErr: Label 'The migration has stopped because we found some vendors in %1. You must delete them and then restart the migration.', Comment = '%1 product name ';
        DataMigrationInProgressMsg: Label 'We''re migrating data to %1.', Comment = '%1 product name ';
        DataMigrationCompletedWithErrosMsg: Label 'Data migration has stopped due to errors. Go to the %1 page to fix them.', Comment = '%1 Data Migration Overview page';
        DataMigrationEntriesToPostMsg: Label 'Data migration is complete, however, there are still a few things to do. Go to the Data Migration Overview page for more information.';
        DataMigrationFinishedMsg: Label 'Yes! The data you chose was successfully migrated.';
        DataMigrationNotificationNameTxt: Label 'Data migration notification';
        DataMigrationNotificationDescTxt: Label 'Show a warning when data migration is either in progress or has completed.';
        DontShowTxt: Label 'Don''t show again';
        MigrationStatus: Option Pending,"In Progress","Completed with errors",Completed,Stopped,Failed,"Not Started";
        GoThereNowTxt: Label 'Go there now';
        MoreInfoTxt: Label 'Learn more';
        DataMigrationHelpTopicURLTxt: Label 'https://go.microsoft.com/fwlink/?linkid=859445', Locked = true;
        CustomersEmptyListNotificationNameTxt: Label 'Show a suggestion to import customers when none exists.';
        VendorsEmptyListNotificationNameTxt: Label 'Show a suggestion to import vendors when none exists.';
        ItemsEmptyListNotificationNameTxt: Label 'Show a suggestion to import items when none exists.';
        CustomerContactNotificationNameTxt: Label 'Show a suggestion to create contacts for newly created customers.';
        VendorContactNotificationNameTxt: Label 'Show a suggestion to create contacts for newly created vendors.';
        CustContactNotificationDescTxt: Label 'Show a suggestion to create contacts for customers.';
        VendContactNotificationDescTxt: Label 'Show a suggestion to create contacts for vendors.';

    local procedure HandleEntityMigration(var DataMigrationStatus: Record "Data Migration Status"; BaseAppMigrationCodeunitToRun: Integer; Retry: Boolean)
    var
        DataMigrationError: Record "Data Migration Error";
    begin
        if DataMigrationStatus.FindFirst() then
            if DataMigrationStatus."Source Staging Table ID" > 0 then
                StagingTableEntityMigration(DataMigrationStatus, BaseAppMigrationCodeunitToRun, Retry)
            else begin
                DataMigrationStatusFacade.UpdateLineStatus(DataMigrationStatus."Migration Type",
                  DataMigrationStatus."Destination Table ID", DataMigrationStatus.Status::"In Progress");
                DataMigrationError.ClearEntryNoStagingTable(DataMigrationStatus."Migration Type",
                  DataMigrationStatus."Destination Table ID");
                Commit(); // save the dashboard before calling the extension codeunit
                if CODEUNIT.Run(DataMigrationStatus."Migration Codeunit To Run") then begin
                    DataMigrationStatus.Get(DataMigrationStatus."Migration Type", DataMigrationStatus."Destination Table ID");
                    if DataMigrationStatus."Migrated Number" = 0 then
                        DataMigrationStatusFacade.IncrementMigratedRecordCount(
                          DataMigrationStatus."Migration Type", DataMigrationStatus."Destination Table ID", DataMigrationStatus."Total Number");
                    DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::"In Progress");
                    if DataMigrationStatus.FindSet() then
                        DataMigrationStatusFacade.UpdateLineStatus(
                          DataMigrationStatus."Migration Type", DataMigrationStatus."Destination Table ID", DataMigrationStatus.Status::Completed);
                end else begin
                    DataMigrationError.CreateEntryNoStagingTable(DataMigrationStatus."Migration Type",
                      DataMigrationStatus."Destination Table ID");
                    DataMigrationStatusFacade.UpdateLineStatus(
                      DataMigrationStatus."Migration Type",
                      DataMigrationStatus."Destination Table ID",
                      DataMigrationStatus.Status::Failed);
                end;
            end;
        Commit(); // save the dashboard as the job could fail on the next task
    end;

    local procedure StagingTableEntityMigration(DataMigrationStatus: Record "Data Migration Status"; BaseAppCodeunitToRun: Integer; Retry: Boolean)
    var
        TempDataMigrationParametersBatch: Record "Data Migration Parameters" temporary;
        DummyDataMigrationStatus: Record "Data Migration Status";
        DataMigrationError: Record "Data Migration Error";
        StagingTableRecRef: RecordRef;
        "Count": Integer;
    begin
        StagingTableRecRef.Open(DataMigrationStatus."Source Staging Table ID");
        if StagingTableRecRef.FindSet() then begin
            DataMigrationStatusFacade.UpdateLineStatus(DataMigrationStatus."Migration Type",
              DataMigrationStatus."Destination Table ID", DummyDataMigrationStatus.Status::"In Progress");
            repeat
                if AbortRequested then
                    exit;

                DataMigrationError.Reset();
                if not Retry or
                   (Retry and
                    DataMigrationError.FindEntry(DataMigrationStatus."Migration Type",
                      DataMigrationStatus."Destination Table ID", StagingTableRecRef.RecordId) and
                    DataMigrationError."Scheduled For Retry" = true)
                then begin
                    Count += 1;

                    TempDataMigrationParametersBatch.Init();
                    TempDataMigrationParametersBatch.Key := Count;
                    TempDataMigrationParametersBatch."Migration Type" := DataMigrationStatus."Migration Type";
                    TempDataMigrationParametersBatch."Staging Table Migr. Codeunit" := DataMigrationStatus."Migration Codeunit To Run";
                    TempDataMigrationParametersBatch."Staging Table RecId To Process" := StagingTableRecRef.RecordId;
                    TempDataMigrationParametersBatch.Insert();

                    DataMigrationError.ClearEntry(DataMigrationStatus."Migration Type",
                      DataMigrationStatus."Destination Table ID",
                      StagingTableRecRef.RecordId);
                end;
                if Count = 100 then begin
                    // try to process batch
                    Commit(); // to save the transaction that has deleted the errors
                    ProcessBatch(DataMigrationStatus, BaseAppCodeunitToRun, TempDataMigrationParametersBatch, Count);
                    Count := 0;
                    TempDataMigrationParametersBatch.DeleteAll();
                end;
            until StagingTableRecRef.Next() = 0;

            if AbortRequested then
                exit;

            if Count > 0 then begin
                Commit(); // to save the transaction that has deleted the errors
                ProcessBatch(DataMigrationStatus, BaseAppCodeunitToRun, TempDataMigrationParametersBatch, Count);
            end;
        end;

        DataMigrationStatus.CalcFields("Error Count");
        if DataMigrationStatus."Error Count" = 0 then
            DataMigrationStatusFacade.UpdateLineStatus(
              DataMigrationStatus."Migration Type", DataMigrationStatus."Destination Table ID",
              DummyDataMigrationStatus.Status::Completed)
        else
            DataMigrationStatusFacade.UpdateLineStatus(
              DataMigrationStatus."Migration Type", DataMigrationStatus."Destination Table ID",
              DummyDataMigrationStatus.Status::"Completed with Errors");
    end;

    local procedure ProcessBatch(DataMigrationStatus: Record "Data Migration Status"; BaseAppCodeunitToRun: Integer; var TempDataMigrationParametersBatch: Record "Data Migration Parameters" temporary; "Count": Integer)
    var
        TempDataMigrationParametersSingle: Record "Data Migration Parameters" temporary;
        DataMigrationError: Record "Data Migration Error";
    begin
        // try to process batch
        if CODEUNIT.Run(BaseAppCodeunitToRun, TempDataMigrationParametersBatch) then begin
            // the batch was processed fine, update the dashboard
            DataMigrationStatusFacade.IncrementMigratedRecordCount(DataMigrationStatus."Migration Type",
              DataMigrationStatus."Destination Table ID", Count);
            Commit(); // save the dashboard status before calling the next Codeunit.RUN
        end else begin
            // the batch processing failed
            TempDataMigrationParametersBatch.FindSet();
            repeat
                // process one by one
                TempDataMigrationParametersSingle.DeleteAll();
                TempDataMigrationParametersSingle.Init();
                TempDataMigrationParametersSingle.TransferFields(TempDataMigrationParametersBatch);
                TempDataMigrationParametersSingle.Insert();

                if CODEUNIT.Run(BaseAppCodeunitToRun, TempDataMigrationParametersSingle) then begin
                    // single record processing succeeded, update dashboard
                    DataMigrationStatusFacade.IncrementMigratedRecordCount(DataMigrationStatus."Migration Type",
                      DataMigrationStatus."Destination Table ID", 1);
                    Commit(); // save the dashboard status before calling the next Codeunit.RUN
                end else begin
                    DataMigrationError.CreateEntry(DataMigrationStatus."Migration Type",
                      DataMigrationStatus."Destination Table ID", TempDataMigrationParametersSingle."Staging Table RecId To Process");
                    Commit(); // save the new errors discovered
                end;
            until TempDataMigrationParametersBatch.Next() = 0;
        end;
    end;

    procedure RunStagingTableMigrationCodeunit(CodeunitToRun: Integer; StagingTableEntityVariant: Variant): Boolean
    begin
        exit(CODEUNIT.Run(CodeunitToRun, StagingTableEntityVariant));
    end;

    [EventSubscriber(ObjectType::Page, Page::"Data Migration Overview", 'OnRequestAbort', '', false, false)]
    local procedure OnRequestAbortSubscriber()
    begin
        AbortRequested := true;
    end;

    local procedure CheckAbortRequestedAndMigrateEntity(var DataMigrationStatus: Record "Data Migration Status"; DestinationTableId: Integer; BaseAppCodeunitToRun: Integer; ReRun: Boolean): Boolean
    begin
        if AbortRequested then begin
            DataMigrationStatus.Reset();
            DataMigrationStatus.SetRange("Migration Type", DataMigrationStatus."Migration Type");
            SetAbortStatus(DataMigrationStatus);
            OnAfterMigrationFinished(DataMigrationStatus, true, StartTime, ReRun);
            exit(true);
        end;

        DataMigrationStatus.SetRange("Destination Table ID", DestinationTableId);
        HandleEntityMigration(DataMigrationStatus, BaseAppCodeunitToRun, ReRun);
    end;

    local procedure CheckAbortAndMigrateRemainingEntities(DataMigrationStatus: Record "Data Migration Status"; Retry: Boolean)
    begin
        if AbortRequested then begin
            DataMigrationStatus.Reset();
            DataMigrationStatus.SetRange("Migration Type", DataMigrationStatus."Migration Type");
            SetAbortStatus(DataMigrationStatus);
            OnAfterMigrationFinished(DataMigrationStatus, true, StartTime, Retry);
            exit;
        end;

        DataMigrationStatus.SetFilter("Destination Table ID", StrSubstNo('<>%1&<>%2&<>%3&<>%4',
            DATABASE::Item,
            DATABASE::Customer,
            DATABASE::"G/L Account",
            DATABASE::Vendor));
        DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::Pending);
        if DataMigrationStatus.FindSet() then
            repeat
                HandleEntityMigration(DataMigrationStatus, DataMigrationStatus."Migration Codeunit To Run", Retry);
            until DataMigrationStatus.Next() = 0;
    end;

    procedure SetStartTime(Value: DateTime)
    begin
        StartTime := Value;
    end;

    procedure SetAbortStatus(var DataMigrationStatus: Record "Data Migration Status")
    begin
        DataMigrationStatus.SetFilter(
          Status, StrSubstNo('%1|%2', DataMigrationStatus.Status::"In Progress", DataMigrationStatus.Status::Pending));
        if DataMigrationStatus.FindSet() then
            repeat
                DataMigrationStatus.Status := DataMigrationStatus.Status::Stopped;
                DataMigrationStatus.Modify(true);
            until DataMigrationStatus.Next() = 0;
    end;

    [IntegrationEvent(true, false)]
    [Scope('OnPrem')]
    procedure OnBeforeMigrationStarted(var DataMigrationStatus: Record "Data Migration Status"; Retry: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    procedure OnCreatePostMigrationData(var DataMigrationStatus: Record "Data Migration Status"; var DataCreationFailed: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    [Scope('OnPrem')]
    procedure OnValidateMigration(var DataMigrationStatus: Record "Data Migration Status"; var DataCreationFailed: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    [Scope('OnPrem')]
    procedure OnAfterMigrationFinished(var DataMigrationStatus: Record "Data Migration Status"; WasAborted: Boolean; StartTime: DateTime; Retry: Boolean)
    begin
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Data Migration Mgt.", 'OnBeforeMigrationStarted', '', true, true)]
    local procedure OnBeforeMigrationStartedSubscriber(var Sender: Codeunit "Data Migration Mgt."; var DataMigrationStatus: Record "Data Migration Status"; Retry: Boolean)
    var
        Message: Text;
    begin
        Sender.SetStartTime(CurrentDateTime);
        if Retry then
            Message := 'Migration started.'
        else
            Message := 'Migration restarted.';
        Session.LogMessage('00001I7', Message, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', StrSubstNo('Data Migration (%1)', DataMigrationStatus."Migration Type"));
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Data Migration Mgt.", 'OnAfterMigrationFinished', '', true, true)]
    local procedure OnAfterMigrationFinishedSubscriber(var DataMigrationStatus: Record "Data Migration Status"; WasAborted: Boolean; StartTime: DateTime; Retry: Boolean)
    var
        TotalNumberOfRecords: Integer;
        Message: Text;
        MigrationDurationAsInt: BigInteger;
    begin
        DataMigrationStatus.SetRange("Destination Table ID", DATABASE::"G/L Account");
        if DataMigrationStatus.FindFirst() then
            TotalNumberOfRecords += DataMigrationStatus."Total Number";

        DataMigrationStatus.SetRange("Destination Table ID", DATABASE::Item);
        if DataMigrationStatus.FindFirst() then
            TotalNumberOfRecords += DataMigrationStatus."Total Number";

        DataMigrationStatus.SetRange("Destination Table ID", DATABASE::Vendor);
        if DataMigrationStatus.FindFirst() then
            TotalNumberOfRecords += DataMigrationStatus."Total Number";

        DataMigrationStatus.SetRange("Destination Table ID", DATABASE::Customer);
        if DataMigrationStatus.FindFirst() then
            TotalNumberOfRecords += DataMigrationStatus."Total Number";

        MigrationDurationAsInt := CurrentDateTime - StartTime;
        if WasAborted then
            Message := StrSubstNo('Migration aborted after %1', MigrationDurationAsInt)
        else
            Message := StrSubstNo('The migration of %1 records in total took: %2.', TotalNumberOfRecords, MigrationDurationAsInt);

        if Retry then
            Message += '(Migration was restarted)';

        Session.LogMessage('00001DA', Message, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', StrSubstNo('Data Migration (%1)', DataMigrationStatus."Migration Type"));
    end;

    procedure StartMigration(MigrationType: Text[250]; Retry: Boolean)
    var
        DataMigrationError: Record "Data Migration Error";
        DataMigrationStatus: Record "Data Migration Status";
        JobQueueEntry: Record "Job Queue Entry";
        JobParameters: Text[250];
        StartNewSession: Boolean;
        CheckExistingData: Boolean;
    begin
        CheckMigrationInProgress(Retry);

        StartNewSession := false;
        CheckExistingData := true;
        OnBeforeStartMigration(StartNewSession, CheckExistingData);

        if CheckExistingData then
            CheckDataAlreadyExist(MigrationType, Retry);

        DataMigrationStatus.Reset();
        DataMigrationStatus.SetRange("Migration Type", MigrationType);
        if not Retry then begin
            DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::Pending);
            if DataMigrationStatus.FindSet() then
                repeat
                    DataMigrationError.SetRange("Migration Type", MigrationType);
                    DataMigrationError.SetRange("Destination Table ID", DataMigrationStatus."Destination Table ID");
                    DataMigrationError.DeleteAll();
                until DataMigrationStatus.Next() = 0;
        end else
            DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::"Completed with Errors");

        Commit(); // commit the dashboard changes so the OnRun call on the migration codeunit will not fail because of this uncommited transaction

        if Retry then
            JobParameters := RetryTxt;
        DataMigrationStatus.FindFirst();
        if StartNewSession then
            // run the migration in a background session
            JobQueueEntry.ScheduleJobQueueEntryWithParameters(CODEUNIT::"Data Migration Mgt.",
            DataMigrationStatus.RecordId, JobParameters)
        else begin
            JobQueueEntry."Record ID to Process" := DataMigrationStatus.RecordId;
            JobQueueEntry."Parameter String" := JobParameters;
            CODEUNIT.Run(CODEUNIT::"Data Migration Mgt.", JobQueueEntry);
        end;
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeStartMigration(var StartNewSession: Boolean; var CheckExistingData: Boolean)
    begin
    end;

    procedure CheckMigrationInProgress(Retry: Boolean)
    var
        JobQueueEntry: Record "Job Queue Entry";
        DataMigrationOverview: Page "Data Migration Overview";
        Status: Option;
    begin
        Status := GetMigrationStatus();

        JobQueueEntry.SetRange("Object ID to Run", CODEUNIT::"Data Migration Mgt.");
        JobQueueEntry.SetFilter(Status, '%1|%2|%3',
          JobQueueEntry.Status::"In Process",
          JobQueueEntry.Status::"On Hold",
          JobQueueEntry.Status::Ready);
        if (Status = MigrationStatus::Pending) and not JobQueueEntry.FindFirst() then
            exit;

        if (Status in [MigrationStatus::"Completed with errors",
                       MigrationStatus::"In Progress",
                       MigrationStatus::Pending]) and not Retry
        then begin
            if Confirm(StrSubstNo(DataMigrationNotCompletedQst, DataMigrationOverview.Caption)) then
                DataMigrationOverview.Run();
            Error('');
        end;
    end;

    procedure GetMigrationStatus(): Integer
    var
        DataMigrationStatus: Record "Data Migration Status";
    begin
        if DataMigrationStatus.IsEmpty() then
            exit(MigrationStatus::"Not Started");

        DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::"In Progress");
        if DataMigrationStatus.FindFirst() then
            exit(MigrationStatus::"In Progress");

        DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::Stopped);
        if DataMigrationStatus.FindFirst() then
            exit(MigrationStatus::Stopped);

        DataMigrationStatus.SetFilter(Status, '=%1', DataMigrationStatus.Status::Failed);
        if DataMigrationStatus.FindFirst() then
            exit(MigrationStatus::Failed);

        DataMigrationStatus.SetRange(Status, DataMigrationStatus.Status::"Completed with Errors");
        if DataMigrationStatus.FindFirst() then
            exit(MigrationStatus::"Completed with errors");

        DataMigrationStatus.SetFilter(Status, '<>%1', DataMigrationStatus.Status::Completed);
        if not DataMigrationStatus.FindFirst() then
            exit(MigrationStatus::Completed);

        exit(MigrationStatus::Pending);
    end;

    local procedure CheckDataAlreadyExist(MigrationType: Text[250]; Retry: Boolean)
    begin
        if Retry then
            exit;

        // check tables are clear. For GL accounts, we delete them automatically
        ThrowErrorIfTableNotEmpty(MigrationType, DATABASE::Customer, StrSubstNo(CustomerTableNotEmptyErr, PRODUCTNAME.Short()));
        ThrowErrorIfTableNotEmpty(MigrationType, DATABASE::Vendor, StrSubstNo(VendorTableNotEmptyErr, PRODUCTNAME.Short()));
        ThrowErrorIfTableNotEmpty(MigrationType, DATABASE::Item, StrSubstNo(ItemTableNotEmptyErr, PRODUCTNAME.Short()));
    end;

    local procedure ThrowErrorIfTableNotEmpty(MigrationType: Text[250]; TableId: Integer; ErrorMessageErr: Text)
    var
        DataMigrationStatus: Record "Data Migration Status";
        RecRef: RecordRef;
    begin
        if DataMigrationStatus.Get(MigrationType, TableId) then begin
            if DataMigrationStatus.Status <> DataMigrationStatus.Status::Pending then
                exit;
            RecRef.Open(TableId);
            if not RecRef.IsEmpty() then
                Error(ErrorMessageErr);
        end;
    end;

    procedure StartDataMigrationWizardFromNotification(Notification: Notification)
    begin
        PAGE.Run(PAGE::"Data Migration Wizard");
    end;

    procedure ShowDataMigrationRelatedGlobalNotifications()
    var
        DataMigrationStatus: Record "Data Migration Status";
        DataMigrationOverview: Page "Data Migration Overview";
        Notification: Notification;
    begin
        if not IsGlobalNotificationEnabled() then
            exit;

        if DataMigrationStatus.IsEmpty() then
            exit;

        Notification.Id(GetGlobalNotificationId());
        case GetMigrationStatus() of
            MigrationStatus::Pending,
            MigrationStatus::"In Progress":
                begin
                    Notification.Message(StrSubstNo(DataMigrationInProgressMsg, PRODUCTNAME.Short()));
                    Notification.AddAction(MoreInfoTxt, CODEUNIT::"Data Migration Mgt.", 'ShowMoreInfoPage');
                end;
            MigrationStatus::"Completed with errors",
            MigrationStatus::Failed:
                begin
                    Notification.Message(StrSubstNo(DataMigrationCompletedWithErrosMsg, DataMigrationOverview.Caption));
                    Notification.AddAction(GoThereNowTxt, CODEUNIT::"Data Migration Mgt.", 'ShowDataMigrationOverviewFromNotification');
                end;
            MigrationStatus::Completed:
                if CheckForEntitiesToBePosted() then begin
                    Notification.Message(DataMigrationEntriesToPostMsg);
                    Notification.AddAction(GoThereNowTxt, CODEUNIT::"Data Migration Mgt.", 'ShowDataMigrationOverviewFromNotification');
                end else begin
                    Notification.Message(DataMigrationFinishedMsg);
                    Notification.AddAction(DontShowTxt, CODEUNIT::"Data Migration Mgt.", 'DisableDataMigrationRelatedGlobalNotifications');
                end;
            else
                exit;
        end;

        Notification.Send();
    end;

    [Scope('OnPrem')]
    procedure GetGlobalNotificationId(): Guid
    begin
        exit('47707336-D917-4238-942F-39715F52BE4E');
    end;

    [Scope('OnPrem')]
    procedure GetCustomerContactNotificationId(): Guid
    begin
        exit('351199D7-6C9B-40F1-8E78-ff9E67C546C9');
    end;

    [Scope('OnPrem')]
    procedure GetVendorContactNotificationId(): Guid
    begin
        exit('08DB77DB-1F41-4379-8615-1B581A0225FA');
    end;

    internal procedure GetItemListEmptyNotificationId(): Guid
    begin
        exit('91ec9d2f-5328-4543-a4bb-565910ad168f');
    end;

    internal procedure GetCustomerListEmptyNotificationId(): Guid
    begin
        exit('ce4f84ea-a382-433a-a908-e2b22799321a');
    end;

    internal procedure GetVendorListEmptyNotificationId(): Guid
    begin
        exit('920c72f6-4771-4984-a223-5f941951cd40');
    end;

    local procedure IsGlobalNotificationEnabled(): Boolean
    var
        MyNotifications: Record "My Notifications";
    begin
        exit(MyNotifications.IsEnabled(GetGlobalNotificationId()));
    end;

    [EventSubscriber(ObjectType::Page, Page::"My Notifications", 'OnInitializingNotificationWithDefaultState', '', false, false)]
    local procedure OnInitializingNotificationWithDefaultState()
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(GetGlobalNotificationId(), DataMigrationNotificationNameTxt, DataMigrationNotificationDescTxt, true);
        InsertDefaultCustomerContactNotification(true);
        InsertDefaultVendorContactNotification(true);
        InsertDefaultCustomerListEmptyNotification(true);
        InsertDefaultVendorListEmptyNotification(true);
        InsertDefaultItemListEmptyNotification(true);
    end;

    procedure ShowDataMigrationOverviewFromNotification(Notification: Notification)
    begin
        PAGE.Run(PAGE::"Data Migration Overview");
    end;

    procedure IsMigrationInProgress(): Boolean
    begin
        exit(GetMigrationStatus() in [MigrationStatus::"In Progress", MigrationStatus::Pending]);
    end;

    procedure ShowMoreInfoPage(Notification: Notification)
    begin
        if PAGE.RunModal(PAGE::"Data Migration About") = ACTION::LookupOK then
            ShowDataMigrationOverviewFromNotification(Notification);
    end;

    procedure CheckForEntitiesToBePosted(): Boolean
    var
        DataMigrationStatus: Record "Data Migration Status";
        DummyCode: Code[10];
    begin
        DataMigrationStatus.SetFilter(
          Status, '%1|%2', DataMigrationStatus.Status::Completed, DataMigrationStatus.Status::"Completed with Errors");

        if not DataMigrationStatus.FindSet() then
            exit(false);
        repeat
            if DestTableHasAnyTransactions(DataMigrationStatus, DummyCode) then
                exit(true);
        until DataMigrationStatus.Next() = 0;
    end;

    procedure DestTableHasAnyTransactions(var DataMigrationStatus: Record "Data Migration Status"; var JournalBatchName: Code[10]): Boolean
    var
        GenJournalLine: Record "Gen. Journal Line";
        ItemJournalLine: Record "Item Journal Line";
    begin
        case DataMigrationStatus."Destination Table ID" of
            DATABASE::Vendor:
                begin
                    DataMigrationFacade.OnFindBatchForVendorTransactions(DataMigrationStatus."Migration Type", JournalBatchName);
                    if JournalBatchName = '' then
                        exit(false);
                    GenJournalLine.SetRange("Journal Batch Name", JournalBatchName);
                    GenJournalLine.SetRange("Account Type", GenJournalLine."Account Type"::Vendor);
                    GenJournalLine.SetFilter("Account No.", '<>%1', '');
                    exit(GenJournalLine.FindFirst());
                end;
            DATABASE::Customer:
                begin
                    DataMigrationFacade.OnFindBatchForCustomerTransactions(DataMigrationStatus."Migration Type", JournalBatchName);
                    if JournalBatchName = '' then
                        exit(false);
                    GenJournalLine.SetRange("Journal Batch Name", JournalBatchName);
                    GenJournalLine.SetRange("Account Type", GenJournalLine."Account Type"::Customer);
                    GenJournalLine.SetFilter("Account No.", '<>%1', '');
                    exit(GenJournalLine.FindFirst());
                end;
            DATABASE::Item:
                begin
                    DataMigrationFacade.OnFindBatchForItemTransactions(DataMigrationStatus."Migration Type", JournalBatchName);
                    if JournalBatchName = '' then
                        exit(false);
                    ItemJournalLine.SetRange("Journal Batch Name", JournalBatchName);
                    ItemJournalLine.SetFilter("Item No.", '<>%1', '');
                    exit(not ItemJournalLine.IsEmpty());
                end;
            else begin
                DataMigrationFacade.OnFindBatchForAccountTransactions(DataMigrationStatus, JournalBatchName);
                if JournalBatchName = '' then
                    exit(false);
                GenJournalLine.SetRange("Journal Batch Name", JournalBatchName);
                GenJournalLine.SetRange("Account Type", GenJournalLine."Account Type"::"G/L Account");
                GenJournalLine.SetFilter("Account No.", '<>%1', '');
                exit(not GenJournalLine.IsEmpty());
            end;
        end;
    end;

    [Scope('OnPrem')]
    procedure ShowHelpTopicPage(Notification: Notification)
    begin
        HyperLink(DataMigrationHelpTopicURLTxt);
    end;

    procedure GetDataMigrationHelpTopicURL(): Text
    begin
        exit(DataMigrationHelpTopicURLTxt)
    end;

    [Scope('OnPrem')]
    procedure DisableDataMigrationRelatedGlobalNotifications(Notification: Notification)
    var
        MyNotifications: Record "My Notifications";
    begin
        if not MyNotifications.Disable(GetGlobalNotificationId()) then
            MyNotifications.InsertDefault(GetGlobalNotificationId(), DataMigrationNotificationNameTxt, DataMigrationNotificationDescTxt, false);
    end;

    local procedure EnableDataMigrationNotificationForAllUsers()
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.SetRange("Notification Id", GetGlobalNotificationId());
        if MyNotifications.FindSet() then
            repeat
                MyNotifications.Enabled := true;
                MyNotifications.Modify(true);
            until MyNotifications.Next() = 0;
    end;

    procedure InsertDefaultCustomerContactNotification(Enabled: Boolean)
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(
          GetCustomerContactNotificationId(), CustomerContactNotificationNameTxt, CustContactNotificationDescTxt, Enabled);
    end;

    procedure InsertDefaultVendorContactNotification(Enabled: Boolean)
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(
          GetVendorContactNotificationId(), VendorContactNotificationNameTxt, VendContactNotificationDescTxt, Enabled);
    end;

    procedure InsertDefaultCustomerListEmptyNotification(Enabled: Boolean)
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(
          GetCustomerListEmptyNotificationId(), CustomersEmptyListNotificationNameTxt, '', Enabled);
    end;

    procedure InsertDefaultVendorListEmptyNotification(Enabled: Boolean)
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(
          GetVendorListEmptyNotificationId(), VendorsEmptyListNotificationNameTxt, '', Enabled);
    end;

    procedure InsertDefaultItemListEmptyNotification(Enabled: Boolean)
    var
        MyNotifications: Record "My Notifications";
    begin
        MyNotifications.InsertDefault(
          GetItemListEmptyNotificationId(), ItemsEmptyListNotificationNameTxt, '', Enabled);
    end;

    procedure UpdateMigrationStatus(var DataMigrationStatus: Record "Data Migration Status")
    var
        JobQueueEntry: Record "Job Queue Entry";
    begin
        if not (DataMigrationStatus.Status in [DataMigrationStatus.Status::Pending, DataMigrationStatus.Status::"In Progress"]) then
            exit;
        JobQueueEntry.SetFilter(Status, '%1|%2', JobQueueEntry.Status::Ready, JobQueueEntry.Status::"In Process");
        JobQueueEntry.SetRange("Object ID to Run", CODEUNIT::"Data Migration Mgt.");
        if JobQueueEntry.FindFirst() then
            exit;

        JobQueueEntry.SetRange(Status, JobQueueEntry.Status::Error);
        if JobQueueEntry.FindFirst() then begin
            DataMigrationStatus.Validate(Status, DataMigrationStatus.Status::Failed);
            DataMigrationStatus.Modify(true);
        end;
    end;

    procedure CheckIfMigrationIsCompleted(CurrentDataMigrationStatus: Record "Data Migration Status")
    var
        DataMigrationStatus: Record "Data Migration Status";
        DataMigrationFacade: Codeunit "Data Migration Facade";
    begin
        DataMigrationStatus.SetRange("Migration Type", CurrentDataMigrationStatus."Migration Type");
        DataMigrationStatus.SetFilter("Destination Table ID", '<>%1', CurrentDataMigrationStatus."Destination Table ID");
        DataMigrationStatus.SetFilter(
          Status,
          '%1|%2|%3',
          DataMigrationStatus.Status::"In Progress",
          DataMigrationStatus.Status::Pending,
          DataMigrationStatus.Status::"Completed with Errors");
        if DataMigrationStatus.IsEmpty() then
            DataMigrationFacade.OnMigrationCompleted(CurrentDataMigrationStatus);
    end;
}