Codeunit 5357 Int. Rec. Uncouple Invoke

App
Base Application
Namespace
Microsoft.Integration.SyncEngine
Versions
17-28

Procedures, 3Events, 4

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Integration/SynchEngine/IntRecUncoupleInvoke.Codeunit.al225 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Integration.SyncEngine;

using Microsoft.Integration.Dataverse;
using System.Reflection;

codeunit 5357 "Int. Rec. Uncouple Invoke"
{

    trigger OnRun()
    begin
        CheckContext();
        UncoupleRecord(
            IntegrationTableMappingContext, LocalRecordRefContext, IntegrationRecordRefContext,
            SynchActionContext, LocalRecordModifiedContext, IntegrationRecordModifiedContext,
            JobIdContext, IntegrationTableConnectionTypeContext);
        if not IsNullGuid(JobIdContext) then
            Commit();
    end;

    var
        IntegrationTableMappingContext: Record "Integration Table Mapping";
        LocalRecordRefContext: RecordRef;
        IntegrationRecordRefContext: RecordRef;
        LocalRecordModifiedContext: Boolean;
        IntegrationRecordModifiedContext: Boolean;
        IntegrationTableConnectionTypeContext: TableConnectionType;
        JobIdContext: Guid;
        SynchActionType: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete,Uncouple;
        SynchActionContext: Option;
        IsContextInitialized: Boolean;
        ContextErr: Label 'The integration record synchronization context has not been initialized.';
        UncoupleFailedErr: Label 'Uncoupling %1 failed because of the following error: %2.', Comment = '%1 = Table Caption, %2 = Error from modify process.';
        UnexpectedRecordStateTxt: Label 'Uncoupling %1 was skipped because of record state differs from the expected one. Actual state: %2, expected state: %3.', Locked = true;
        UnexpectedSyncActionTxt: Label 'Uncoupling %1 was skipped because of sync action differs the expected one. Actual action: %2, expected action: %3.', Locked = true;
        CategoryTok: Label 'AL Dataverse Integration', Locked = true;

    procedure SetContext(IntegrationTableMapping: Record "Integration Table Mapping"; LocalRecordRef: RecordRef; IntegrationRecordRef: RecordRef; SynchAction: Option; LocalRecordModified: Boolean; IntegrationRecordModified: Boolean; JobId: Guid; IntegrationTableConnectionType: TableConnectionType)
    begin
        IntegrationTableMappingContext := IntegrationTableMapping;
        LocalRecordRefContext := LocalRecordRef;
        IntegrationRecordRefContext := IntegrationRecordRef;
        SynchActionContext := SynchAction;
        LocalRecordModifiedContext := LocalRecordModified;
        IntegrationRecordModifiedContext := IntegrationRecordModified;
        IntegrationTableConnectionTypeContext := IntegrationTableConnectionType;
        JobIdContext := JobId;
        IsContextInitialized := true;
    end;

    procedure GetContext(var IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef; var SynchAction: Option; var LocalRecordModified: Boolean; var IntegrationRecordModified: Boolean)
    begin
        CheckContext();
        IntegrationTableMapping := IntegrationTableMappingContext;
        LocalRecordRef := LocalRecordRefContext;
        IntegrationRecordRef := IntegrationRecordRefContext;
        SynchAction := SynchActionContext;
        LocalRecordModified := LocalRecordModifiedContext;
        IntegrationRecordModified := IntegrationRecordModifiedContext;
    end;

    local procedure CheckContext()
    begin
        if not IsContextInitialized then
            Error(ContextErr);
    end;

    local procedure UncoupleRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef; var SynchAction: Option; var LocalRecordModified: Boolean; var IntegrationRecordModified: Boolean; JobId: Guid; IntegrationTableConnectionType: TableConnectionType)
    var
        IntegrationRecSynchInvoke: Codeunit "Integration Rec. Synch. Invoke";
        RecordState: Option NotFound,Coupled,Uncoupled;
        LocalRecordDeleted: Boolean;
        IntegrationRecordDeleted: Boolean;
        IntegrationRecordLastModifiedTime: DateTime;
    begin
        if SynchAction <> SynchActionType::Uncouple then begin
            SynchAction := SynchActionType::Skip;
            Session.LogMessage('0000DEB', StrSubstNo(UnexpectedSyncActionTxt, GetTableCaption(IntegrationTableMapping."Integration Table ID"), SynchAction, SynchActionType::Skip), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
            exit;
        end;

        if LocalRecordRef.Number() <> 0 then
            RecordState := IntegrationRecSynchInvoke.FindCoupledRecord(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef, IntegrationRecordDeleted, IntegrationTableConnectionType)
        else
            RecordState := IntegrationRecSynchInvoke.FindCoupledRecord(IntegrationTableMapping, IntegrationRecordRef, LocalRecordRef, LocalRecordDeleted, IntegrationTableConnectionType);
        if RecordState <> RecordState::Coupled then begin
            SynchAction := SynchActionType::Skip;
            Session.LogMessage('0000DEC', StrSubstNo(UnexpectedRecordStateTxt, GetTableCaption(IntegrationTableMapping."Integration Table ID"), RecordState, RecordState::Coupled), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
            exit;
        end;

        if IntegrationTableMapping.Direction in [IntegrationTableMapping.Direction::ToIntegrationTable, IntegrationTableMapping.Direction::Bidirectional] then
            if not IntegrationRecordDeleted then
                if IntegrationRecordRef.Number() <> 0 then
                    IntegrationRecordLastModifiedTime := GetIntegrationRecordLastModifiedTime(IntegrationTableMapping, IntegrationRecordRef);

        OnBeforeUncoupleRecord(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef);

        if IntegrationTableMapping.Direction in [IntegrationTableMapping.Direction::FromIntegrationTable, IntegrationTableMapping.Direction::Bidirectional] then
            if not LocalRecordDeleted then
                if LocalRecordRef.Number() <> 0 then
                    if LocalRecordRef.IsDirty() then
                        if LocalRecordRef.Modify(true) then
                            LocalRecordModified := true
                        else
                            SynchAction := SynchActionType::Fail;

        if IntegrationTableMapping.Direction in [IntegrationTableMapping.Direction::ToIntegrationTable, IntegrationTableMapping.Direction::Bidirectional] then
            if not IntegrationRecordDeleted then
                if IntegrationRecordRef.Number() <> 0 then
                    if not IntegrationRecordRef.IsDirty() then
                        IntegrationRecordModified := GetIntegrationRecordLastModifiedTime(IntegrationTableMapping, IntegrationRecordRef) > IntegrationRecordLastModifiedTime
                    else
                        if IntegrationRecordRef.Modify(true) then
                            IntegrationRecordModified := true
                        else
                            SynchAction := SynchActionType::Fail;

        if SynchAction = SynchActionType::Fail then begin
            OnErrorWhenUncouplingRecord(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef);
            LogSynchError(
                LocalRecordRef, IntegrationRecordRef,
                StrSubstNo(UncoupleFailedErr, GetTableCaption(IntegrationTableMapping."Integration Table ID"), RemoveTrailingDots(GetLastErrorText())), JobId);
            exit;
        end;

        if not RemoveIntegrationRecordCoupling(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef, IntegrationTableConnectionType) then
            SynchAction := SynchActionType::"None";
        OnAfterUncoupleRecord(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef);
    end;

    local procedure RemoveIntegrationRecordCoupling(IntegrationTableMapping: Record "Integration Table Mapping"; LocalRecordRef: RecordRef; IntegrationRecordRef: RecordRef; IntegrationTableConnectionType: TableConnectionType): Boolean
    var
        CRMIntegrationManagement: Codeunit "CRM Integration Management";
        IntegrationRecordManagement: Codeunit "Integration Record Management";
        IntegrationTableUidFieldRef: FieldRef;
        IntegrationTableUid: Variant;
        DestinationTableID: Integer;
        Removed: Boolean;
        IsHandled: Boolean;
    begin
        OnRemoveIntegrationTableCoupling(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef, IntegrationTableConnectionType, IsHandled, Removed);
        if IsHandled then
            exit(Removed);

        if IntegrationTableMapping.Type = IntegrationTableMapping.Type::Dataverse then
            if CRMIntegrationManagement.IsIntegrationRecordChild(IntegrationTableMapping."Table ID") then
                exit(false);

        IntegrationTableUidFieldRef := IntegrationRecordRef.Field(IntegrationTableMapping."Integration Table UID Fld. No.");
        IntegrationTableUid := IntegrationTableUidFieldRef.Value();
        DestinationTableID := IntegrationTableMapping."Table ID";

        IntegrationRecordManagement.RemoveIntegrationTableCoupling(
          IntegrationTableConnectionType, IntegrationTableUid, DestinationTableID, LocalRecordRef, Removed);
        exit(Removed);
    end;

    procedure GetIntegrationRecordLastModifiedTime(var IntegrationTableMapping: Record "Integration Table Mapping"; var IntegrationRecordRef: RecordRef): DateTime
    var
        ModifiedFieldRef: FieldRef;
    begin
        ModifiedFieldRef := IntegrationRecordRef.Field(IntegrationTableMapping."Int. Tbl. Modified On Fld. No.");
        exit(ModifiedFieldRef.Value());
    end;

    local procedure RemoveTrailingDots(Message: Text): Text
    begin
        exit(DelChr(Message, '>', '.'));
    end;

    local procedure LogSynchError(var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef; ErrorMessage: Text; JobId: Guid)
    var
        IntegrationSynchJobErrors: Record "Integration Synch. Job Errors";
        EmptyRecordID: RecordID;
    begin
        if IsNullGuid(JobId) then
            exit;

        if IntegrationRecordRef.Number = 0 then begin
            EmptyRecordID := LocalRecordRef.RecordId();
            Clear(EmptyRecordID);
            IntegrationSynchJobErrors.LogSynchError(JobId, LocalRecordRef.RecordId(), EmptyRecordID, ErrorMessage)
        end else begin
            IntegrationSynchJobErrors.LogSynchError(JobId, LocalRecordRef.RecordId(), IntegrationRecordRef.RecordId(), ErrorMessage);

            // Close destination - it is in error state and can no longer be used.
            IntegrationRecordRef.Close();
        end;
    end;

    local procedure GetTableCaption(TableID: Integer): Text
    var
        TableMetadata: Record "Table Metadata";
    begin
        if TableMetadata.Get(TableID) then
            exit(TableMetadata.Caption);
        exit('');
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeUncoupleRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterUncoupleRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnErrorWhenUncouplingRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnRemoveIntegrationTableCoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef; var IntegrationTableConnectionType: TableConnectionType; var IsHandled: Boolean; var Removed: Boolean)
    begin
    end;
}