Codeunit 5364 Int. Option Synch. Invoke, source in 29
Source29
src/Layers/W1/BaseApp/Integration/SynchEngine/IntOptionSynchInvoke.Codeunit.al429 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.Foundation.PaymentTerms;
using Microsoft.Foundation.Shipping;
using Microsoft.Integration.D365Sales;
using Microsoft.Integration.Dataverse;
codeunit 5364 "Int. Option Synch. Invoke"
{
trigger OnRun()
begin
CheckContext();
SynchOption(
IntegrationTableMappingContext, SourceRecordRefContext, DestinationRecordRefContext,
SynchActionContext, IgnoreSynchOnlyCoupledRecordsContext, JobIdContext);
end;
var
IntegrationTableMappingContext: Record "Integration Table Mapping";
SourceRecordRefContext: RecordRef;
DestinationRecordRefContext: RecordRef;
JobIdContext: Guid;
SynchActionType: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete,Uncouple,Couple;
ModifyFailedErr: Label 'Modifying %1 failed because of the following error: %2.', Comment = '%1 = Table Caption, %2 = Error from modify process.';
ModifyFailedSimpleErr: Label 'Modifying %1 failed.', Comment = '%1 = Table Caption';
CoupledRecordIsDeletedErr: Label 'The %1 record cannot be updated because it is coupled to a deleted record.', Comment = '%1 = Source Table Caption';
CopyDataErr: Label 'The data could not be updated because of the following error: %1.', Comment = '%1 = Error message from transferdata process.';
SynchActionContext: Option;
IgnoreSynchOnlyCoupledRecordsContext: Boolean;
IsContextInitialized: Boolean;
ContextErr: Label 'The integration record synchronization context has not been initialized.';
procedure SetContext(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; SynchAction: Option; IgnoreSynchOnlyCoupledRecords: Boolean; JobId: Guid)
begin
IntegrationTableMappingContext := IntegrationTableMapping;
SourceRecordRefContext := SourceRecordRef;
DestinationRecordRefContext := DestinationRecordRef;
SynchActionContext := SynchAction;
IgnoreSynchOnlyCoupledRecordsContext := IgnoreSynchOnlyCoupledRecords;
JobIdContext := JobId;
IsContextInitialized := true;
end;
procedure GetContext(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var SynchAction: Option)
begin
CheckContext();
IntegrationTableMapping := IntegrationTableMappingContext;
SourceRecordRef := SourceRecordRefContext;
DestinationRecordRef := DestinationRecordRefContext;
SynchAction := SynchActionContext;
end;
procedure WasModifiedAfterLastSynch(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef): Boolean
var
CRMOptionMapping: Record "CRM Option Mapping";
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationFieldMapping: Record "Integration Field Mapping";
DestinationFieldRef: FieldRef;
LastModifiedOn: DateTime;
begin
if IntegrationTableMapping."Table ID" = SourceRecordRef.Number() then begin
LastModifiedOn := GetRowLastModifiedOn(IntegrationTableMapping, SourceRecordRef);
exit(CRMIntegrationRecord.IsModifiedAfterLastSynchronizedRecord(SourceRecordRef, LastModifiedOn));
end else begin
IntegrationFieldMapping.SetRange("Integration Table Mapping Name", IntegrationTableMapping.Name);
if IntegrationFieldMapping.FindFirst() then begin
DestinationFieldRef := DestinationRecordRef.Field(IntegrationFieldMapping."Field No.");
exit(UpperCase(Format(DestinationFieldRef.Value())) <> UpperCase(CopyStr(CRMOptionMapping.GetRecordRefOptionValue(SourceRecordRef), 1, DestinationFieldRef.Length)));
end;
end;
end;
local procedure CheckContext()
begin
if not IsContextInitialized then
Error(ContextErr);
end;
procedure GetRowLastModifiedOn(IntegrationTableMapping: Record "Integration Table Mapping"; FromRecordRef: RecordRef): DateTime
var
ModifiedFieldRef: FieldRef;
begin
ModifiedFieldRef := FromRecordRef.Field(FromRecordRef.SystemModifiedAtNo());
exit(ModifiedFieldRef.Value());
end;
local procedure SynchOption(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var SynchAction: Option; IgnoreSynchOnlyCoupledRecords: Boolean; JobId: Guid)
var
SourceWasChanged: Boolean;
RecordState: Option NotFound,Coupled,Decoupled;
IsDestinationDeleted: Boolean;
NewPK: Variant;
begin
// Find the mapped record or prepare a new one
RecordState := GetMappedRecord(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, SynchAction, IsDestinationDeleted, JobId);
if RecordState = RecordState::NotFound then begin
if SynchAction = SynchActionType::Fail then
exit;
if IsDestinationDeleted or (IntegrationTableMapping."Synch. Only Coupled Records" and not IgnoreSynchOnlyCoupledRecords) then begin
SynchAction := SynchActionType::Skip;
exit;
end;
PrepareNewDestination(IntegrationTableMapping, DestinationRecordRef);
SynchAction := SynchActionType::Insert;
end;
if SynchAction = SynchActionType::Insert then
SourceWasChanged := true
else begin
SourceWasChanged := WasModifiedAfterLastSynch(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
if SynchAction <> SynchActionType::ForceModify then
if not SourceWasChanged then
SynchAction := SynchActionType::IgnoreUnchanged;
end;
if not (SynchAction in [SynchActionType::Insert, SynchActionType::Modify, SynchActionType::ForceModify]) then
exit;
if SourceWasChanged or (SynchAction = SynchActionType::ForceModify) then
TransferFields(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, SynchAction, JobId, NewPK);
case SynchAction of
SynchActionType::Insert:
InsertRecord(
IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, SynchAction, JobId);
SynchActionType::Modify,
SynchActionType::ForceModify:
ModifyRecord(
IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, SynchAction, JobId, NewPK);
end;
end;
local procedure InsertRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var SynchAction: Option; JobId: Guid)
var
CRMOptionMapping: Record "CRM Option Mapping";
NewOptionId: Integer;
begin
OnBeforeInsertRecord(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
if IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::FromIntegrationTable then
DestinationRecordRef.Insert(true)
else begin
NewOptionId := InsertCRMOption(DestinationRecordRef);
DestinationRecordRef.Field(1).Value := NewOptionId;
end;
if SynchAction <> SynchActionType::Fail then begin
CRMOptionMapping.UpdateOptionMapping(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, JobId);
if IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::FromIntegrationTable then begin
OnAfterInsertRecord(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
if DestinationRecordRef.Number() = IntegrationTableMapping."Table ID" then
// refetch the local record as subscribers to the OnAfterInsertRecord above could update it
DestinationRecordRef.GetBySystemId(DestinationRecordRef.Field(DestinationRecordRef.SystemIdNo).Value());
end;
OnAfterInsertOption(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
end;
Commit();
end;
local procedure InsertCRMOption(DestinationRecordRef: RecordRef): Integer
var
CRMOptionMapping: Record "CRM Option Mapping";
CDSIntegrationMgt: Codeunit "CDS Integration Mgt.";
EntityName: Text;
FieldName: Text;
begin
CRMOptionMapping.GetMetadataInfo(DestinationRecordRef, EntityName, FieldName);
exit(CDSIntegrationMgt.InsertOptionSetMetadata(EntityName, FieldName, CRMOptionMapping.GetRecordRefOptionValue(DestinationRecordRef)));
end;
local procedure ModifyRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var SynchAction: Option; JobId: Guid; NewPK: Variant)
var
CRMOptionMapping: Record "CRM Option Mapping";
LastError: Text;
LogError: Text;
Failed: Boolean;
begin
OnBeforeModifyRecord(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
if IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::FromIntegrationTable then
if DestinationRecordRef.Rename(NewPK) then begin
CRMOptionMapping.UpdateOptionMapping(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, JobId);
OnAfterModifyRecord(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
if DestinationRecordRef.Number() = IntegrationTableMapping."Table ID" then
// refetch the local record as subscribers to the OnAfterModifyRecord above could update it
DestinationRecordRef.GetBySystemId(DestinationRecordRef.Field(DestinationRecordRef.SystemIdNo).Value());
end else
Failed := true
else
if ModifyCRMOption(DestinationRecordRef) then
CRMOptionMapping.UpdateOptionMapping(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, JobId)
else
Failed := true;
OnAfterModifyOption(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
if Failed then begin
LastError := GetLastErrorText();
OnErrorWhenModifyingRecord(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef);
SynchAction := SynchActionType::Fail;
if LastError <> '' then
LogError := StrSubstNo(ModifyFailedErr, DestinationRecordRef.Caption(), RemoveTrailingDots(LastError))
else
LogError := StrSubstNo(ModifyFailedSimpleErr, DestinationRecordRef.Caption());
LogSynchError(SourceRecordRef, DestinationRecordRef, LogError, JobId);
MarkOptionMappingAsFailed(IntegrationTableMapping, SourceRecordRef, JobId, SynchAction);
end;
Commit();
end;
[TryFunction]
local procedure ModifyCRMOption(DestinationRecordRef: RecordRef)
var
CRMOptionMapping: Record "CRM Option Mapping";
CDSIntegrationMgt: Codeunit "CDS Integration Mgt.";
EntityName: Text;
FieldName: Text;
begin
CRMOptionMapping.GetMetadataInfo(DestinationRecordRef, EntityName, FieldName);
CDSIntegrationMgt.UpdateOptionSetMetadata(EntityName, FieldName, CRMOptionMapping.GetRecordRefOptionId(DestinationRecordRef), CRMOptionMapping.GetRecordRefOptionValue(DestinationRecordRef));
end;
local procedure GetMappedRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; var RecordRef: RecordRef; var CoupledRecordRef: RecordRef; var SynchAction: Option; var IsDestinationMarkedAsDeleted: Boolean; JobId: Guid): Integer
var
CRMOptionMapping: Record "CRM Option Mapping";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
DeletionConflictHandled: Boolean;
RecordState: Option NotFound,Coupled,Decoupled;
begin
IsDestinationMarkedAsDeleted := false;
if RecordRef.Number = IntegrationTableMapping."Table ID" then begin
CRMOptionMapping.SetRange("Record ID", RecordRef.RecordId);
if CRMOptionMapping.FindFirst() then begin
RecordState := RecordState::Coupled;
CRMIntegrationTableSynch.LoadCRMOption(CoupledRecordRef, IntegrationTableMapping);
CoupledRecordRef.Field(CoupledRecordRef.KeyIndex(1).FieldIndex(1).Number).SetRange(CRMOptionMapping."Option Value");
IsDestinationMarkedAsDeleted := not CoupledRecordRef.FindFirst();
end else
RecordState := RecordState::NotFound;
end else
if CRMOptionMapping.IsCRMRecordRefMapped(RecordRef, CRMOptionMapping) then begin
RecordState := RecordState::Coupled;
IsDestinationMarkedAsDeleted := not CoupledRecordRef.Get(CRMOptionMapping."Record ID")
end else
RecordState := RecordState::NotFound;
if RecordState <> RecordState::NotFound then
if not IsDestinationMarkedAsDeleted then begin
if SynchAction <> SynchActionType::ForceModify then
SynchAction := SynchActionType::Modify;
exit(RecordState);
end;
if SynchAction <> SynchActionType::ForceModify then
if RecordState = RecordState::Coupled then begin
OnDeletionConflictDetected(IntegrationTableMapping, RecordRef, DeletionConflictHandled);
if not DeletionConflictHandled then begin
RecordState := RecordState::NotFound;
SynchAction := SynchActionType::Fail;
LogSynchError(RecordRef, CoupledRecordRef, StrSubstNo(CoupledRecordIsDeletedErr, RecordRef.Caption), JobId);
MarkOptionMappingAsFailed(IntegrationTableMapping, RecordRef, JobId, SynchAction);
end else
case IntegrationTableMapping."Deletion-Conflict Resolution" of
IntegrationTableMapping."Deletion-Conflict Resolution"::"Restore Records":
begin
PrepareNewDestination(IntegrationTableMapping, CoupledRecordRef);
RecordState := RecordState::Coupled;
SynchAction := SynchActionType::Insert;
end;
IntegrationTableMapping."Deletion-Conflict Resolution"::"Remove Coupling":
begin
RecordState := RecordState::Decoupled;
SynchAction := SynchActionType::None;
end;
end;
end;
exit(RecordState);
end;
local procedure PrepareNewDestination(var IntegrationTableMapping: Record "Integration Table Mapping"; var CoupledRecordRef: RecordRef)
begin
CoupledRecordRef.Close();
if IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::FromIntegrationTable then
CoupledRecordRef.Open(IntegrationTableMapping."Table ID")
else begin
case IntegrationTableMapping."Table ID" of
Database::"Payment Terms":
CoupledRecordRef.Open(Database::"CRM Payment Terms");
Database::"Shipment Method":
CoupledRecordRef.Open(Database::"CRM Freight Terms");
Database::"Shipping Agent":
CoupledRecordRef.Open(Database::"CRM Shipping Method");
end;
OnPrepareNewDestination(IntegrationTableMapping, CoupledRecordRef);
end;
CoupledRecordRef.Init();
end;
local procedure LogSynchError(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; ErrorMessage: Text; JobId: Guid)
var
IntegrationSynchJobErrors: Record "Integration Synch. Job Errors";
EmptyRecordID: RecordID;
begin
if DestinationRecordRef.Number = 0 then begin
EmptyRecordID := SourceRecordRef.RecordId();
Clear(EmptyRecordID);
IntegrationSynchJobErrors.LogSynchError(JobId, SourceRecordRef.RecordId(), EmptyRecordID, ErrorMessage)
end else begin
IntegrationSynchJobErrors.LogSynchError(JobId, SourceRecordRef.RecordId(), DestinationRecordRef.RecordId(), ErrorMessage);
// Close destination - it is in error state and can no longer be used.
DestinationRecordRef.Close();
end;
end;
local procedure TransferFields(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var SynchAction: Option; JobId: Guid; var NewPK: Variant)
begin
OnBeforeTransferRecordFields(SourceRecordRef, DestinationRecordRef);
if not TransferField(IntegrationTableMapping, SourceRecordRef, DestinationRecordRef, SynchAction, NewPK) then begin
SynchAction := SynchActionType::Fail;
LogSynchError(
SourceRecordRef, DestinationRecordRef,
StrSubstNo(CopyDataErr, RemoveTrailingDots(GetLastErrorText)), JobId);
MarkOptionMappingAsFailed(IntegrationTableMappingContext, SourceRecordRef, JobId, SynchAction);
Commit();
end;
end;
[TryFunction]
local procedure TransferField(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var SyncAction: Option; var NewPK: Variant)
var
CRMOptionMapping: Record "CRM Option Mapping";
IntegrationFieldMapping: Record "Integration Field Mapping";
DestinationFieldRef: FieldRef;
begin
IntegrationFieldMapping.SetRange("Integration Table Mapping Name", IntegrationTableMapping.Name);
if IntegrationFieldMapping.FindFirst() then
if IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::FromIntegrationTable then begin
DestinationFieldRef := DestinationRecordRef.Field(IntegrationFieldMapping."Field No.");
if not (SyncAction in [SynchActionType::Modify, SynchActionType::ForceModify]) then begin
DestinationFieldRef.Value := CopyStr(CRMOptionMapping.GetRecordRefOptionValue(SourceRecordRef), 1, DestinationFieldRef.Length);
if IntegrationFieldMapping."Validate Field" then
DestinationFieldRef.Validate();
end else
NewPK := CopyStr(CRMOptionMapping.GetRecordRefOptionValue(SourceRecordRef), 1, DestinationFieldRef.Length);
end else begin
DestinationFieldRef := DestinationRecordRef.Field(2);
DestinationFieldRef.Value := SourceRecordRef.Field(IntegrationFieldMapping."Field No.");
end;
end;
procedure MarkOptionMappingAsFailed(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; JobId: Guid; var SyncAction: Option)
var
CRMOptionMapping: Record "CRM Option Mapping";
DirectionToIntTable: Boolean;
MarkedAsSkipped: Boolean;
begin
DirectionToIntTable := IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::ToIntegrationTable;
MarkedAsSkipped := SyncAction = SynchActionType::Skip;
CRMOptionMapping.MarkLastSynchAsFailure(SourceRecordRef, DirectionToIntTable, JobId, MarkedAsSkipped);
if MarkedAsSkipped then
SyncAction := SynchActionType::Skip;
end;
local procedure RemoveTrailingDots(Message: Text): Text
begin
exit(DelChr(Message, '>', '.'));
end;
[IntegrationEvent(false, false)]
local procedure OnDeletionConflictDetected(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DeletionConflictHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterModifyOption(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnErrorWhenModifyingRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeInsertRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterInsertRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterInsertOption(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnPrepareNewDestination(IntegrationTableMapping: Record "Integration Table Mapping"; var CoupledRecordRef: RecordRef)
begin
end;
}