Codeunit 5341 CRM Int. Table. Subscriber
- App
- Base Application
- Namespace
- Microsoft.Integration.D365Sales
- Versions
- 17-28
Versions171819202122232425262728
Source29
Source in 29
src/Layers/W1/BaseApp/Integration/D365Sales/CRMIntTableSubscriber.Codeunit.al3330 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.D365Sales;
using Microsoft.CRM.BusinessRelation;
using Microsoft.CRM.Contact;
using Microsoft.CRM.Opportunity;
using Microsoft.CRM.Team;
using Microsoft.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.ReceivablesPayables;
using Microsoft.Foundation.Shipping;
using Microsoft.Foundation.UOM;
using Microsoft.Integration.Dataverse;
using Microsoft.Integration.SyncEngine;
using Microsoft.Inventory.Item;
using Microsoft.Pricing.Asset;
using Microsoft.Pricing.Calculation;
using Microsoft.Pricing.PriceList;
using Microsoft.Projects.Resources.Resource;
using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Archive;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Document;
using Microsoft.Sales.History;
using Microsoft.Sales.Posting;
using Microsoft.Sales.Pricing;
using Microsoft.Sales.Setup;
using Microsoft.Utilities;
using System.Environment.Configuration;
using System.Telemetry;
using System.Threading;
using System.Utilities;
codeunit 5341 "CRM Int. Table. Subscriber"
{
SingleInstance = true;
trigger OnRun()
begin
end;
var
CRMSynchHelper: Codeunit "CRM Synch. Helper";
CRMProductName: Codeunit "CRM Product Name";
CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
CRMIntegrationManagement: Codeunit "CRM Integration Management";
PrepaymentMgt: Codeunit "Prepayment Mgt.";
CannotFindSyncedProductErr: Label 'Cannot find a synchronized product for %1.', Comment = '%1=product identifier';
CannotSynchOnlyLinesErr: Label 'Cannot synchronize invoice lines separately.';
CannotSynchProductErr: Label 'Cannot synchronize the product %1.', Comment = '%1=product identification';
CannotSynchResourceErr: Label 'Cannot synchronize the resource %1.', Comment = '%1=resource identification';
RecordNotFoundErr: Label 'Cannot find %1 in table %2.', Comment = '%1 = The lookup value when searching for the source record, %2 = Source table caption';
ContactsMustBeRelatedToCompanyErr: Label 'The contact %1 must have a contact company that has a business relation to a customer.', Comment = '%1 = Contact No.';
ContactMissingCompanyErr: Label 'The contact cannot be synchronized because the company does not exist.';
CRMUnitGroupExistsAndIsInactiveErr: Label 'The %1 %2 already exists in %3, but it cannot be synchronized, because it is inactive.', Comment = '%1=table caption: Unit Group,%2=The name of the indicated Unit Group;%3=product name';
CRMUnitGroupContainsMoreThanOneUoMErr: Label 'The %4 %1 %2 contains more than one %3. This setup cannot be used for synchronization.', Comment = '%1=table caption: Unit Group,%2=The name of the indicated Unit Group,%3=table caption: Unit., %4 = Dataverse service name';
CustomerHasChangedErr: Label 'Cannot create the invoice in %2. The customer from the original %2 sales order %1 was changed or is no longer coupled.', Comment = '%1=CRM sales order number, %2 = Dataverse service name';
ItemUnitOfMeasureDoesNotExistErr: Label 'Cannot create the invoice in %1. The item unit of measure %2 does not exist.', Comment = '%1= Dataverse service name, %2=item unit of measure code';
NotCoupledItemUoMErr: Label 'Cannot create the invoice in %1. The item unit of measure %2 is not coupled to a %1 unit.', Comment = '%1= ataverse service name", %2=item unit of measure code';
ResourceUnitOfMeasureDoesNotExistErr: Label 'Cannot create the invoice in %1. The resource unit of measure %2 does not exist.', Comment = '%1= Dataverse service name, %2=resource unit of measure code';
NotCoupledResourceUoMErr: Label 'Cannot create the invoice in %1. The resource unit of measure %2 is not coupled to a %1 unit.', Comment = '%1= ataverse service name", %2=resource unit of measure code';
NoCoupledSalesInvoiceHeaderErr: Label 'Cannot find the coupled %1 invoice header.', Comment = '%1 = Dataverse service name';
RecordMustBeCoupledErr: Label '%1 %2 must be coupled to a record in %3.', Comment = '%1 =field caption, %2 = field value, %3 - product name ';
CurrencyExchangeRateMissingErr: Label 'Cannot create or update the currency %1 in %2, because there is no exchange rate defined for it.', Comment = '%1 - currency code, %2 - Dataverse service name';
NewCodePatternTxt: Label 'SP NO. %1', Locked = true;
SalespersonPurchaserCodeFilterLbl: Label 'SP NO. 0*', Locked = true;
SourceDestCodePatternTxt: Label '%1-%2', Locked = true;
CategoryTok: Label 'AL Dataverse Integration', Locked = true;
UpdateContactParentCompanyTxt: Label 'Updating contact parent company.', Locked = true;
UpdateContactParentCompanyFailedTxt: Label 'Updating contact parent company failed. Parent Customer ID: %1', Locked = true, Comment = '%1 - parent customer id';
UpdateContactParentCompanySuccessfulTxt: Label 'Updated contact parent company successfully.', Locked = true;
ItemUnitGroupNotFoundErr: Label 'Item Unit Group for Item %1 is not found.', Comment = '%1 - item number';
ResourceUnitGroupNotFoundErr: Label 'Resource Unit Group for Resource %1 is not found.', Comment = '%1 - resource number';
CRMUnitGroupNotFoundErr: Label 'CRM Unit Group %1 does not exist.', Comment = '%1 - unit group name';
CRMUnitNotFoundErr: Label 'CRM Unit %1 with Unit Group Id %2 does not exist.', Comment = '%1 - unit name, %2 - unit group id';
SynchingSalesSpecificEntityTxt: Label 'Synching a %1 specific entity.', Locked = true;
FailedToGetPostedSalesInvoiceTxt: Label 'Failed to get posted sales invoice %1 from SQL database.', Locked = true;
FailedToGetPostedSalesInvoiceLinesTxt: Label 'Failed to get lines for posted sales invoice %1 from SQL database.', Locked = true;
SalesInvoiceNotCommittedErr: Label 'Posted sales invoice %1 is not committed in the SQL database yet. It will be synchronized by the next scheduled synchronization run.', Comment = '%1 - invoice number';
SalesInvoiceLinesNotCommittedErr: Label 'The lines of posted sales invoice %1 are not committed in the SQL database yet. The invoice will be synchronized by the next scheduled synchronization run.', Comment = '%1 - invoice number';
NotCoupledCRMUomErr: Label 'The %2 unit %1 is not coupled to a unit of measure.', Comment = '%1 = Unit name, %2 = Dataverse service name';
OrderPriceListLbl: Label 'Business Central Order %1 Price List', Locked = true, Comment = '%1 - Order No.';
SalesHeaderNotCoupledErr: Label 'Sales header is not coupled.';
WriteInProductErr: Label 'Sales line contains a write-in product. You must choose the default write-in product in Sales & Receivables Setup window.';
UnitOfMeasureNotCoupledErr: Label 'Unit of measure %1 is not coupled.', Comment = '%1 - unit of measure code';
ItemUnitOfMeasureNotCoupledErr: Label 'Item unit of measure %1 is not coupled.', Comment = '%1 - unit of measure code';
ResourceUnitOfMeasureNotCoupledErr: Label 'Resource unit of measure %1 is not coupled.', Comment = '%1 - unit of measure code';
ItemUomDoesNotExistErr: Label 'The item unit of measure %1 does not exist.', Comment = '%1= item unit of measure code';
ResourceUomDoesNotExistErr: Label 'The resource unit of measure %1 does not exist.', Comment = '%1= resource unit of measure code';
procedure ClearCache()
begin
CRMSynchHelper.ClearCache();
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Job Queue Start Codeunit", 'OnAfterRun', '', false, false)]
local procedure OnAfterJobQueueEntryRun(var JobQueueEntry: Record "Job Queue Entry")
var
IntegrationSynchJob: Record "Integration Synch. Job";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
if IsJobQueueEntryCRMIntegrationJob(JobQueueEntry, IntegrationTableMapping) then
if IntegrationSynchJob.HaveJobsBeenIdle(JobQueueEntry.GetLastLogEntryNo()) then begin
if JobQueueEntry."Recurring Job" then
JobQueueEntry.Status := JobQueueEntry.Status::"On Hold with Inactivity Timeout"
end else
JobQueueEntry.Status := JobQueueEntry.Status::Ready;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterDeleteAfterPosting', '', false, false)]
local procedure DeleteCouplingOnAfterDeleteAfterPosting(SalesHeader: Record "Sales Header"; SalesInvoiceHeader: Record "Sales Invoice Header"; SalesCrMemoHeader: Record "Sales Cr.Memo Header"; CommitIsSuppressed: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMIntegrationRecord: Record "CRM Integration Record";
begin
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
exit;
if IsNullGuid(SalesHeader.SystemId) then
exit;
CRMIntegrationRecord.SetRange("Integration ID", SalesHeader.SystemId);
CRMIntegrationRecord.SetRange("Table ID", Database::"Sales Header");
if not CRMIntegrationRecord.IsEmpty() then
CRMIntegrationRecord.DeleteAll();
end;
[EventSubscriber(ObjectType::Table, Database::"Job Queue Entry", 'OnFindingIfJobNeedsToBeRun', '', false, false)]
local procedure OnFindingIfJobNeedsToBeRun(var Sender: Record "Job Queue Entry"; var Result: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
IntegrationTableMapping: Record "Integration Table Mapping";
JobQueueEntry: Record "Job Queue Entry";
RecRef: RecordRef;
ConnectionID: Guid;
begin
if Result then
exit;
if IsJobQueueEntryCRMIntegrationJob(Sender, IntegrationTableMapping) and
CRMIntegrationManagement.IsCRMTable(IntegrationTableMapping."Integration Table ID")
then begin
CRMConnectionSetup.Get();
if CRMConnectionSetup."Is Enabled" or CDSIntegrationImpl.IsIntegrationEnabled() then begin
JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId());
JobQueueEntry.SetRange(Status, JobQueueEntry.Status::"In Process");
if not JobQueueEntry.IsEmpty() then begin
Result := false;
exit;
end;
ConnectionID := Format(CreateGuid());
if CDSIntegrationImpl.IsIntegrationEnabled() then begin
CDSIntegrationImpl.RegisterConnection(ConnectionID);
SetDefaultTableConnection(TABLECONNECTIONTYPE::CRM, ConnectionID, false)
end else
CRMConnectionSetup.RegisterConnectionWithName(ConnectionID);
RecRef.Open(IntegrationTableMapping."Integration Table ID");
IntegrationTableMapping.SetIntRecordRefFilter(RecRef);
if not RecRef.IsEmpty() then
Result := true;
RecRef.Close();
if CDSIntegrationImpl.IsIntegrationEnabled() then
CDSIntegrationImpl.UnregisterConnection(ConnectionID)
else
CRMConnectionSetup.UnregisterConnectionWithName(ConnectionID);
end;
end;
end;
[EventSubscriber(ObjectType::Page, Page::"Job Queue Log Entries", 'OnShowDetails', '', false, false)]
local procedure OnShowDetailedLog(JobQueueLogEntry: Record "Job Queue Log Entry")
var
IntegrationSynchJob: Record "Integration Synch. Job";
begin
if (JobQueueLogEntry."Object Type to Run" = JobQueueLogEntry."Object Type to Run"::Codeunit) and
(JobQueueLogEntry."Object ID to Run" in [CODEUNIT::"Integration Synch. Job Runner", CODEUNIT::"CRM Statistics Job", CODEUNIT::"Int. Uncouple Job Runner", CODEUNIT::"Int. Coupling Job Runner"])
then begin
IntegrationSynchJob.SetRange("Job Queue Log Entry No.", JobQueueLogEntry."Entry No.");
PAGE.RunModal(PAGE::"Integration Synch. Job List", IntegrationSynchJob);
end;
end;
local procedure IsJobQueueEntryCRMIntegrationJob(JobQueueEntry: Record "Job Queue Entry"; var IntegrationTableMapping: Record "Integration Table Mapping"): Boolean
var
RecRef: RecordRef;
begin
Clear(IntegrationTableMapping);
if JobQueueEntry."Object Type to Run" <> JobQueueEntry."Object Type to Run"::Codeunit then
exit(false);
case JobQueueEntry."Object ID to Run" of
CODEUNIT::"CRM Statistics Job":
exit(true);
CODEUNIT::"Integration Synch. Job Runner":
begin
if not RecRef.Get(JobQueueEntry."Record ID to Process") then
exit(false);
if RecRef.Number() = DATABASE::"Integration Table Mapping" then begin
RecRef.SetTable(IntegrationTableMapping);
exit(IntegrationTableMapping."Synch. Codeunit ID" = CODEUNIT::"CRM Integration Table Synch.");
end;
end;
CODEUNIT::"Int. Uncouple Job Runner":
begin
if not RecRef.Get(JobQueueEntry."Record ID to Process") then
exit(false);
if RecRef.Number() = DATABASE::"Integration Table Mapping" then begin
RecRef.SetTable(IntegrationTableMapping);
exit(IntegrationTableMapping."Uncouple Codeunit ID" = Codeunit::"CDS Int. Table Uncouple");
end;
end;
CODEUNIT::"Int. Coupling Job Runner":
begin
if not RecRef.Get(JobQueueEntry."Record ID to Process") then
exit(false);
if RecRef.Number() = DATABASE::"Integration Table Mapping" then begin
RecRef.SetTable(IntegrationTableMapping);
exit(IntegrationTableMapping."Coupling Codeunit ID" = Codeunit::"CDS Int. Table Couple");
end;
end;
Codeunit::"CRM Archived Sales Orders Job":
exit(true);
end;
end;
[EventSubscriber(ObjectType::Table, Database::"Job Queue Entry", 'OnAfterDeleteEvent', '', false, false)]
local procedure OnCleanupAfterJobExecution(var Rec: Record "Job Queue Entry"; RunTrigger: Boolean)
var
IntegrationTableMapping: Record "Integration Table Mapping";
RecordID: RecordID;
RecRef: RecordRef;
begin
if Rec.IsTemporary() then
exit;
RecordID := Rec."Record ID to Process";
if RecordID.TableNo() = DATABASE::"Integration Table Mapping" then begin
RecRef := RecordID.GetRecord();
RecRef.SetTable(IntegrationTableMapping);
if IntegrationTableMapping.Get(IntegrationTableMapping.Name) then
if IntegrationTableMapping."Delete After Synchronization" then
IntegrationTableMapping.Delete(true);
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeTransferRecordFields', '', false, false)]
procedure OnBeforeTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
CRMConnectionSetup: Record "CRM Connection Setup";
begin
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'Sales Invoice Header-CRM Invoice':
CheckItemOrResourceIsNotBlocked(SourceRecordRef);
'Sales Line-CRM Salesorderdetail':
AddSalesOrderIdToCRMSalesorderdetail(SourceRecordRef, DestinationRecordRef);
'CRM Salesorderdetail-Sales Line':
begin
AddDocumentNoToSalesOrderLine(SourceRecordRef, DestinationRecordRef);
AddTypeToSalesOrderLine(SourceRecordRef, DestinationRecordRef);
end;
'CRM Salesorder-Sales Header':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
DestinationRecordRef.SetTable(SalesHeader);
SalesHeader.Status := SalesHeader.Status::Open;
DestinationRecordRef.GetTable(SalesHeader);
end;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Record Synch.", 'OnTransferFieldData', '', false, false)]
procedure OnTransferFieldData(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean; var NeedsConversion: Boolean)
var
CDSConnectionSetup: Record "CDS Connection Setup";
CRMConnectionSetup: Record "CRM Connection Setup";
IntegrationTableMapping: Record "Integration Table Mapping";
Item: Record Item;
Resource: Record Resource;
PriceListLine: Record "Price List Line";
SalesLine: Record "Sales Line";
CRMProduct: Record "CRM Product";
GeneralLedgerSetup: Record "General Ledger Setup";
CRMTransactionCurrency: Record "CRM Transactioncurrency";
OptionValue: Integer;
TableValue: Text;
TransactionCurrencyId: Guid;
begin
if IsValueFound then
exit;
if not (CRMIntegrationManagement.IsCDSIntegrationEnabled() or CRMIntegrationManagement.IsCRMIntegrationEnabled()) then
exit;
if SourceFieldRef.Number() = DestinationFieldRef.Number() then
if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then
exit;
if CRMIntegrationManagement.IsCDSIntegrationEnabled() then
if (SourceFieldRef.Record().Number() in [Database::Customer, Database::Vendor, Database::Currency, Database::Contact, Database::"Salesperson/Purchaser"]) or
(DestinationFieldRef.Record().Number() in [Database::Customer, Database::Vendor, Database::Currency, Database::Contact, Database::"Salesperson/Purchaser"]) then
exit;
if (SourceFieldRef.Record().Number() = Database::"CRM Salesorderdetail") and (DestinationFieldRef.Record().Number() = Database::"Sales Line") and (DestinationFieldRef.Number() = SalesLine.FieldNo("No.")) then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
if AddWriteInProductNo(SourceFieldRef, NewValue) then begin
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if (SourceFieldRef.Name() = SalesLine.FieldName(SalesLine."Currency Code")) and (DestinationFieldRef.Name() = CRMTransactionCurrency.FieldName(TransactionCurrencyId)) then
if Format(SourceFieldRef.Value()) = '' then
if CDSConnectionSetup.Get() then
if CDSConnectionSetup.BaseCurrencyCode <> '' then begin
GeneralLedgerSetup.Get();
if GeneralLedgerSetup."LCY Code" <> CDSConnectionSetup.BaseCurrencyCode then begin
CRMTransactionCurrency.SetRange(ISOCurrencyCode, CopyStr(GeneralLedgerSetup."LCY Code", 1, MaxStrLen(CRMTransactionCurrency.ISOCurrencyCode)));
if CRMTransactionCurrency.FindFirst() then begin
NewValue := CRMTransactionCurrency.TransactionCurrencyId;
IsValueFound := true;
NeedsConversion := false;
exit;
end;
end;
end;
if (SourceFieldRef.Name() = CRMTransactionCurrency.FieldName(TransactionCurrencyId)) and (DestinationFieldRef.Name() = SalesLine.FieldName(SalesLine."Currency Code")) then
if CDSConnectionSetup.Get() then
if CDSConnectionSetup.BaseCurrencyCode <> '' then begin
GeneralLedgerSetup.Get();
if GeneralLedgerSetup."LCY Code" <> CDSConnectionSetup.BaseCurrencyCode then begin
Evaluate(TransactionCurrencyId, Format(SourceFieldRef.Value()));
if CRMTransactionCurrency.Get(TransactionCurrencyId) then
if Format(CRMTransactionCurrency.ISOCurrencyCode) = Format(GeneralLedgerSetup."LCY Code") then begin
NewValue := '';
IsValueFound := true;
NeedsConversion := false;
exit;
end;
end;
end;
if (DestinationFieldRef.Record().Number() = Database::"CRM Salesorderdetail") and (SourceFieldRef.Number() = SalesLine.FieldNo("No.")) then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
if AddWriteInSalesorderdetail(SourceFieldRef, NewValue) then begin
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if (DestinationFieldRef.Record().Number() = Database::"CRM Salesorder") and (DestinationFieldRef.Name() = 'OwnerId') then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
CDSConnectionSetup.Get();
case CDSConnectionSetup."Ownership Model" of
CDSConnectionSetup."Ownership Model"::Team:
begin
// in case of field mapping to OwnerId, if ownership model is Team, we don't change the value if Salesperson changes
NewValue := DestinationFieldRef.Value();
IsValueFound := true;
NeedsConversion := false;
exit;
end;
CDSConnectionSetup."Ownership Model"::Person:
begin
// in case of field mapping to OwnerId, if ownership model is Person, we should find the user mapped to the Salesperson/Purchaser
NewValue := CRMSynchHelper.GetCoupledCDSUserId(SourceFieldRef.Record());
IsValueFound := true;
NeedsConversion := false;
exit;
end;
end;
end;
if CRMSynchHelper.ConvertTableToOption(SourceFieldRef, DestinationFieldRef, OptionValue) then begin
NewValue := OptionValue;
IsValueFound := true;
NeedsConversion := true;
exit;
end;
if CRMSynchHelper.ConvertOptionToTable(SourceFieldRef, DestinationFieldRef, TableValue) then begin
NewValue := TableValue;
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if ShouldKeepOldValue(DestinationFieldRef) then begin
NewValue := DestinationFieldRef.Value();
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if CRMSynchHelper.FindNewValueForSpecialMapping(SourceFieldRef, DestinationFieldRef, NewValue) then begin
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if CRMIntegrationManagement.IsUnitGroupMappingEnabled() then begin
if ((SourceFieldRef.Record().Number() = Database::Item) and (SourceFieldRef.Number() = Item.FieldNo("Base Unit of Measure"))) or
((SourceFieldRef.Record().Number() = Database::Resource) and (SourceFieldRef.Number() = Resource.FieldNo("Base Unit of Measure"))) or
((SourceFieldRef.Record().Number() = Database::"Price List Line") and (SourceFieldRef.Number() = PriceListLine.FieldNo("Unit of Measure Code"))) then begin
CRMSynchHelper.ConvertBaseUnitOfMeasureToUomId(SourceFieldRef, DestinationFieldRef, NewValue);
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if (SourceFieldRef.Record().Number() = Database::"CRM Product") and (SourceFieldRef.Number() = CRMProduct.FieldNo(DefaultUoMId)) then begin
CRMSynchHelper.ConvertUomIdToBaseUnitOfMeasure(SourceFieldRef, DestinationFieldRef, NewValue);
IsValueFound := true;
NeedsConversion := false;
exit;
end;
if SourceFieldRef.Record().Number() = Database::"Unit Group" then begin
CRMSynchHelper.PrefixUnitGroupCode(SourceFieldRef, NewValue);
IsValueFound := true;
NeedsConversion := false;
end;
end;
if CRMSynchHelper.AreFieldsRelatedToMappedTables(SourceFieldRef, DestinationFieldRef, IntegrationTableMapping) then
if FindNewValueForCoupledRecordPK(IntegrationTableMapping, SourceFieldRef, DestinationFieldRef, NewValue) then begin
IsValueFound := true;
NeedsConversion := false;
end;
end;
local procedure ShouldKeepOldValue(DestinationFieldRef: FieldRef): Boolean
var
CDSConnectionSetup: Record "CDS Connection Setup";
TempCRMOpportunity: Record "CRM Opportunity" temporary;
begin
// in case of field mapping to OwnerId, if ownership model is Team, we don't change the value if Salesperson changes
if DestinationFieldRef.Name() = TempCRMOpportunity.FieldName(OwnerId) then
if CRMIntegrationManagement.IsCDSIntegrationEnabled() then
if CRMIntegrationManagement.IsCRMTable(DestinationFieldRef.Record().Number()) then begin
CDSConnectionSetup.Get();
exit(CDSConnectionSetup."Ownership Model" = CDSConnectionSetup."Ownership Model"::Team);
end;
exit(false);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterTransferRecordFields', '', false, false)]
procedure OnAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var AdditionalFieldsWereModified: Boolean; DestinationIsInserted: Boolean)
var
IsHandled: Boolean;
begin
OnBeforeGetSourceDestCodeOnAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef, AdditionalFieldsWereModified, DestinationIsInserted, IsHandled);
if IsHandled then
exit;
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'CRM Account-Customer':
if UpdateCustomerBlocked(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Sales Price-CRM Productpricelevel':
if UpdateCRMProductPricelevelAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Price List Line-CRM Productpricelevel':
if UpdateCRMProductPricelevelAfterTransferRecordFieldsPriceListLine(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Currency-CRM Transactioncurrency':
if UpdateCRMTransactionCurrencyAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Item-CRM Product',
'Resource-CRM Product':
if UpdateCRMProductAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef, DestinationIsInserted) then
AdditionalFieldsWereModified := true;
'CRM Product-Item':
if UpdateItemAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'CRM Product-Resource':
if UpdateResourceAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Unit of Measure-CRM Uomschedule':
if UpdateCRMUoMScheduleAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Unit Group-CRM Uomschedule':
CheckCRMUoMScheduleAfterTransferRecordFields(DestinationRecordRef);
'Item Unit of Measure-CRM Uom':
if UpdateCRMUomFromItemAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Resource Unit of Measure-CRM Uom':
if UpdateCRMUomFromResourceAfterTransferRecordFields(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'Sales Line-CRM Salesorderdetail':
if UpdateCRMSalesorderdetailUom(SourceRecordRef, DestinationRecordRef) then
AdditionalFieldsWereModified := true;
'CRM Salesorderdetail-Sales Line':
if UpdateSalesLineUnitOfMeasure(SourceRecordRef, DestinationRecordRef) then begin
UpdateSalesLinePriceOverride(SourceRecordRef, DestinationRecordRef);
AdditionalFieldsWereModified := true;
end;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeInsertRecord', '', false, false)]
procedure OnBeforeInsertRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CDSIntTableSubscriber: Codeunit "CDS Int. Table. Subscriber";
begin
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'CRM Contact-Contact':
UpdateContactParentCompany(SourceRecordRef, DestinationRecordRef);
'Contact-CRM Contact':
UpdateCRMContactParentCustomerId(SourceRecordRef, DestinationRecordRef);
'Currency-CRM Transactioncurrency':
UpdateCRMTransactionCurrencyBeforeInsertRecord(DestinationRecordRef);
'Customer Price Group-CRM Pricelevel':
UpdateCRMPricelevelBeforeInsertRecord(SourceRecordRef, DestinationRecordRef);
'Price List Header-CRM Pricelevel':
UpdateCRMPricelevelBeforeInsertPriceListHeader(SourceRecordRef, DestinationRecordRef);
'Item-CRM Product',
'Resource-CRM Product':
UpdateCRMProductBeforeInsertRecord(DestinationRecordRef);
'Sales Invoice Header-CRM Invoice':
begin
CheckSalesInvoiceLineItemsAreCoupled(SourceRecordRef);
UpdateCRMInvoiceBeforeInsertRecord(SourceRecordRef, DestinationRecordRef);
end;
'Opportunity-CRM Opportunity':
UpdateCRMOpportunityBeforeInsertRecord(DestinationRecordRef);
'Sales Invoice Line-CRM Invoicedetail':
UpdateCRMInvoiceDetailsBeforeInsertRecord(SourceRecordRef, DestinationRecordRef);
'Sales Header-CRM Salesorder':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
UpdateCRMSalesOrderPriceList(SourceRecordRef, DestinationRecordRef);
SetCompanyId(DestinationRecordRef);
SetCRMOrderName(SourceRecordRef, DestinationRecordRef);
SetDocOccurenceNumber(SourceRecordRef, DestinationRecordRef);
CDSIntTableSubscriber.SetOwnerId(SourceRecordRef, DestinationRecordRef);
end;
'CRM Salesorder-Sales Header':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
UpdateSalesOrderQuoteNo(SourceRecordRef, DestinationRecordRef);
'Sales Line-CRM Salesorderdetail':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
SetWriteInProduct(SourceRecordRef, DestinationRecordRef);
ApplySalesLineTax(SourceRecordRef, DestinationRecordRef);
end;
end;
case DestinationRecordRef.Number() of
Database::"Salesperson/Purchaser":
UpdateSalesPersOnBeforeInsertRecord(DestinationRecordRef);
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterInsertRecord', '', false, false)]
procedure OnAfterInsertRecord(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
SourceDestCode: Text;
begin
SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef);
case SourceDestCode of
'Customer Price Group-CRM Pricelevel':
ResetCRMProductpricelevelFromCustomerPriceGroup(SourceRecordRef);
'Price List Header-CRM Pricelevel':
ResetCRMProductpricelevelFromPriceListHeader(SourceRecordRef);
'Item-CRM Product',
'Resource-CRM Product':
UpdateCRMProductAfterInsertRecord(DestinationRecordRef);
'Sales Invoice Header-CRM Invoice':
begin
UpdateCRMInvoiceAfterInsertRecord(SourceRecordRef, DestinationRecordRef);
UpdatePricesIncludeVATRounding(SourceRecordRef, DestinationRecordRef);
end;
'Sales Invoice Line-CRM Invoicedetail':
UpdateCRMInvoiceDetailsAfterInsertRecord(SourceRecordRef, DestinationRecordRef);
'Item Unit of Measure-CRM Uom':
UpdateCRMUomFromItemAfterInsertRecord(SourceRecordRef, DestinationRecordRef);
'Resource Unit of Measure-CRM Uom':
UpdateCRMUomFromResourceAfterInsertRecord(SourceRecordRef, DestinationRecordRef);
'Sales Header-CRM Salesorder':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
ResetCRMSalesorderdetailFromSalesOrderLine(SourceRecordRef, DestinationRecordRef);
SetCRMSalesOrderStateCode(SourceRecordRef, DestinationRecordRef);
end;
'CRM Salesorder-Sales Header':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
ResetSalesOrderLineFromCRMSalesorderdetail(SourceRecordRef, DestinationRecordRef);
ApplySalesOrderDiscounts(SourceRecordRef, DestinationRecordRef);
CreateFreightLines(SourceRecordRef, DestinationRecordRef);
SetSalesOrderStatus(DestinationRecordRef);
SetOrderNumberAndDocOccurenceNumber(SourceRecordRef, DestinationRecordRef);
CreateSalesOrderNotes(SourceRecordRef, DestinationRecordRef);
end;
'CRM Product-Item':
CreateUnitGroupAndItemUnitOfMeasure(SourceRecordRef, DestinationRecordRef);
'CRM Product-Resource':
CreateUnitGroupAndResourceUnitOfMeasure(SourceRecordRef, DestinationRecordRef);
'CRM Salesorderdetail-Sales Line':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
AutoReserveSalesLine(DestinationRecordRef);
end;
end;
local procedure CreateUnitGroupAndItemUnitOfMeasure(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
CRMProduct: Record "CRM Product";
Item: Record Item;
UnitGroup: Record "Unit Group";
ItemUnitOfMeasure: Record "Item Unit of Measure";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
UnitGroupRecordRef: RecordRef;
ItemUnitOfMeasureRecordRef: RecordRef;
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Table ID", Database::"Unit Group");
IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uomschedule");
if IntegrationTableMapping.FindFirst() then
if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
IntegrationTableMapping.SetRange("Table ID", Database::"Item Unit of Measure");
IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uom");
if IntegrationTableMapping.FindFirst() then
if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
SourceRecordRef.SetTable(CRMProduct);
DestinationRecordRef.SetTable(Item);
if UnitGroup.Get(UnitGroup."Source Type"::Item, Item.SystemId) then begin
CRMIntegrationRecord.CoupleRecordIdToCRMID(UnitGroup.RecordId, CRMProduct.DefaultUoMScheduleId);
UnitGroup.SetRange(SystemId, UnitGroup.SystemId);
UnitGroupRecordRef.GetTable(UnitGroup);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(UnitGroupRecordRef, false, false);
end;
if not ItemUnitOfMeasure.Get(Item."No.", Item."Base Unit of Measure") then begin
ItemUnitOfMeasure.Init();
ItemUnitOfMeasure.Validate("Item No.", Item."No.");
ItemUnitOfMeasure.Validate(Code, Item."Base Unit of Measure");
ItemUnitOfMeasure."Qty. per Unit of Measure" := 1;
ItemUnitOfMeasure.Insert();
CRMIntegrationRecord.CoupleRecordIdToCRMID(ItemUnitOfMeasure.RecordId, CRMProduct.DefaultUoMId);
ItemUnitOfMeasure.SetRange(SystemId, ItemUnitOfMeasure.SystemId);
ItemUnitOfMeasureRecordRef.GetTable(ItemUnitOfMeasure);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(ItemUnitOfMeasureRecordRef, false, false);
end;
end;
end;
end;
local procedure CreateUnitGroupAndResourceUnitOfMeasure(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
CRMProduct: Record "CRM Product";
Resource: Record Resource;
UnitGroup: Record "Unit Group";
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
UnitGroupRecordRef: RecordRef;
ResourceUnitOfMeasureRecordRef: RecordRef;
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Table ID", Database::"Unit Group");
IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uomschedule");
if IntegrationTableMapping.FindFirst() then
if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
IntegrationTableMapping.SetRange("Table ID", Database::"Resource Unit of Measure");
IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uom");
if IntegrationTableMapping.FindFirst() then
if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
SourceRecordRef.SetTable(CRMProduct);
DestinationRecordRef.SetTable(Resource);
if UnitGroup.Get(UnitGroup."Source Type"::Resource, Resource.SystemId) then begin
CRMIntegrationRecord.CoupleRecordIdToCRMID(UnitGroup.RecordId, CRMProduct.DefaultUoMScheduleId);
UnitGroup.SetRange(SystemId, UnitGroup.SystemId);
UnitGroupRecordRef.GetTable(UnitGroup);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(UnitGroupRecordRef, false, false);
end;
if not ResourceUnitOfMeasure.Get(Resource."No.", Resource."Base Unit of Measure") then begin
ResourceUnitOfMeasure.Init();
ResourceUnitOfMeasure.Validate("Resource No.", Resource."No.");
ResourceUnitOfMeasure.Validate(Code, Resource."Base Unit of Measure");
ResourceUnitOfMeasure."Qty. per Unit of Measure" := 1;
ResourceUnitOfMeasure.Insert();
CRMIntegrationRecord.CoupleRecordIdToCRMID(ResourceUnitOfMeasure.RecordId, CRMProduct.DefaultUoMId);
ResourceUnitOfMeasure.SetRange(SystemId, ResourceUnitOfMeasure.SystemId);
ResourceUnitOfMeasureRecordRef.GetTable(ResourceUnitOfMeasure);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(ResourceUnitOfMeasureRecordRef, false, false);
end;
end;
end;
end;
local procedure ChangeSalesOrderStateCode(var SourceRecordRef: RecordRef; NewStateCode: Option)
var
CRMSalesorder: Record "CRM Salesorder";
ChangedCRMSalesOrder: Record "CRM Salesorder";
begin
if not CRMIntegrationManagement.IsCRMIntegrationEnabled() then
exit;
SourceRecordRef.SetTable(CRMSalesorder);
ChangedCRMSalesOrder.SetAutoCalcFields(CreatedByName, ModifiedByName, TransactionCurrencyIdName);
ChangedCRMSalesOrder.Get(CRMSalesorder.SalesOrderId);
if ChangedCRMSalesOrder.StateCode = NewStateCode then
exit;
ChangedCRMSalesOrder.StateCode := NewStateCode;
ChangedCRMSalesOrder.Modify();
end;
local procedure ChangeSalesOrderStatus(var SourceRecordRef: RecordRef; NewStatus: Enum "Sales Document Status")
var
SalesHeader: Record "Sales Header";
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if not CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
exit;
SalesHeader.GetBySystemId(SourceRecordRef.Field(SourceRecordRef.SystemIdNo).Value());
OnChangeSalesOrderStatusOnBeforeCompareStatus(SalesHeader, NewStatus);
if SalesHeader.Status = NewStatus then
exit;
SalesHeader.Status := NewStatus;
SalesHeader.Modify();
SourceRecordRef.GetTable(SalesHeader);
end;
local procedure SetSalesOrderStatus(var SourceRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
NewStatus: Enum "Sales Document Status";
begin
SourceRecordRef.SetTable(SalesHeader);
if PrepaymentMgt.TestSalesPrepayment(SalesHeader) then
NewStatus := SalesHeader.Status::Open
else
NewStatus := SalesHeader.Status::Released;
ChangeValidateSalesOrderStatus(SourceRecordRef, NewStatus);
end;
local procedure SetCRMSalesOrderStateCode(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
CRMSalesorder: Record "CRM Salesorder";
begin
SourceRecordRef.SetTable(SalesHeader);
DestinationRecordRef.SetTable(CRMSalesorder);
SalesHeader.CalcFields("Completely Shipped");
if SalesHeader."Completely Shipped" then
CRMSynchHelper.FulfillSalesOrder(CRMSalesorder.SalesOrderId)
else
ChangeSalesOrderStateCode(DestinationRecordRef, CRMSalesorder.StateCode::Submitted);
end;
local procedure SubmitOrInvoiceSalesOrder(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
CRMSalesorder: Record "CRM Salesorder";
CRMSalesDocumentPostingMgt: Codeunit "CRM Sales Document Posting Mgt";
begin
SourceRecordRef.SetTable(SalesHeader);
if CRMSalesDocumentPostingMgt.IsSalesOrderFullyInvoiced(SalesHeader) then
ChangeSalesOrderStateCode(DestinationRecordRef, CRMSalesorder.StateCode::Invoiced)
else
ChangeSalesOrderStateCode(DestinationRecordRef, CRMSalesorder.StateCode::Submitted);
end;
local procedure ChangeValidateSalesOrderStatus(var SourceRecordRef: RecordRef; NewStatus: Enum "Sales Document Status")
var
SalesHeader: Record "Sales Header";
CRMConnectionSetup: Record "CRM Connection Setup";
ReleaseSalesDocument: Codeunit "Release Sales Document";
begin
if not CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
exit;
SalesHeader.GetBySystemId(SourceRecordRef.Field(SourceRecordRef.SystemIdNo).Value());
OnChangeSalesOrderStatusOnBeforeCompareStatus(SalesHeader, NewStatus);
if SalesHeader.Status = NewStatus then
exit;
if NewStatus = SalesHeader.Status::Released then
ReleaseSalesDocument.PerformManualRelease(SalesHeader)
else
ReleaseSalesDocument.PerformManualReopen(SalesHeader);
SourceRecordRef.GetTable(SalesHeader);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeModifyRecord', '', false, false)]
procedure OnBeforeModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMSalesOrder: Record "CRM Salesorder";
CRMConnectionSetup: Record "CRM Connection Setup";
begin
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'CRM Contact-Contact':
UpdateContactParentCompany(SourceRecordRef, DestinationRecordRef);
'Contact-CRM Contact':
UpdateCRMContactParentCustomerId(SourceRecordRef, DestinationRecordRef);
'Customer Price Group-CRM Pricelevel':
UpdateCRMPricelevelBeforeModifyRecord(SourceRecordRef, DestinationRecordRef);
'Price List Header-CRM Pricelevel':
UpdateCRMPricelevelBeforeModifyPriceListHeader(SourceRecordRef, DestinationRecordRef);
'Item-CRM Product',
'Resource-CRM Product',
'Opportunity-CRM Opportunity',
'Sales Invoice Header-CRM Invoice':
SetCompanyId(DestinationRecordRef);
'Sales Header-CRM Salesorder':
begin
if not CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
ChangeSalesOrderStateCode(DestinationRecordRef, CRMSalesOrder.StateCode::Active)
else
UpdateCRMSalesOrderPriceList(SourceRecordRef, DestinationRecordRef);
SetCompanyId(DestinationRecordRef);
end;
'Sales Line-CRM Salesorderdetail':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
ApplySalesLineTax(SourceRecordRef, DestinationRecordRef);
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterModifyRecord', '', false, false)]
procedure OnAfterModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'Customer Price Group-CRM Pricelevel':
ResetCRMProductpricelevelFromCustomerPriceGroup(SourceRecordRef);
'Price List Header-CRM Pricelevel':
ResetCRMProductpricelevelFromPriceListHeader(SourceRecordRef);
'Sales Header-CRM Salesorder':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
ResetCRMSalesorderdetailFromSalesOrderLine(SourceRecordRef, DestinationRecordRef);
SetCRMSalesOrderStateCode(SourceRecordRef, DestinationRecordRef);
end else
SubmitOrInvoiceSalesOrder(SourceRecordRef, DestinationRecordRef);
'CRM Salesorder-Sales Header':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
ResetSalesOrderLineFromCRMSalesorderdetail(SourceRecordRef, DestinationRecordRef);
ApplySalesOrderDiscounts(SourceRecordRef, DestinationRecordRef);
CreateFreightLines(SourceRecordRef, DestinationRecordRef);
SetSalesOrderStatus(DestinationRecordRef);
CreateSalesOrderNotes(SourceRecordRef, DestinationRecordRef);
end;
end;
if DestinationRecordRef.Number() = DATABASE::Customer then
CRMSynchHelper.UpdateContactOnModifyCustomer(DestinationRecordRef);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterUnchangedRecordHandled', '', false, false)]
procedure OnAfterUnchangedRecordHandled(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
SalesHeader: Record "Sales Header";
begin
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'Customer Price Group-CRM Pricelevel':
ResetCRMProductpricelevelFromCustomerPriceGroup(SourceRecordRef);
'Price List Header-CRM Pricelevel':
ResetCRMProductpricelevelFromPriceListHeader(SourceRecordRef);
'Sales Header-CRM Salesorder':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
ResetCRMSalesorderdetailFromSalesOrderLine(SourceRecordRef, DestinationRecordRef);
'CRM Salesorder-Sales Header':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
ChangeSalesOrderStatus(DestinationRecordRef, SalesHeader.Status::Open);
ResetSalesOrderLineFromCRMSalesorderdetail(SourceRecordRef, DestinationRecordRef);
CreateFreightLines(SourceRecordRef, DestinationRecordRef);
SetSalesOrderStatus(DestinationRecordRef);
CreateSalesOrderNotes(SourceRecordRef, DestinationRecordRef);
end;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeIgnoreUnchangedRecordHandled', '', false, false)]
local procedure OnBeforeIgnoreUnchangedRecordHandled(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
SalesHeader: Record "Sales Header";
IsHandled: Boolean;
begin
OnHandleOnBeforeIgnoreUnchangedRecordHandled(SourceRecordRef, DestinationRecordRef, IsHandled);
if IsHandled then
exit;
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'Sales Header-CRM Salesorder':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
ResetCRMSalesorderdetailFromSalesOrderLine(SourceRecordRef, DestinationRecordRef);
'CRM Salesorder-Sales Header':
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
ChangeSalesOrderStatus(DestinationRecordRef, SalesHeader.Status::Open);
ResetSalesOrderLineFromCRMSalesorderdetail(SourceRecordRef, DestinationRecordRef);
SetSalesOrderStatus(DestinationRecordRef);
CreateSalesOrderNotes(SourceRecordRef, DestinationRecordRef);
end;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Table Synch.", 'OnQueryPostFilterIgnoreRecord', '', false, false)]
procedure OnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
begin
if IgnoreRecord then
exit;
case SourceRecordRef.Number() of
DATABASE::Contact:
HandleContactQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
DATABASE::Opportunity:
HandleOpportunityQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
DATABASE::"Sales Invoice Line":
Error(CannotSynchOnlyLinesErr);
DATABASE::Item:
HandleItemQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
DATABASE::Resource:
HandleResourceQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
DATABASE::"Sales Invoice Header":
IgnoreReadOnlyInvoiceOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
Database::"Sales Header":
IgnoreArchievedSalesOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
Database::"CRM Salesorder":
IgnoreArchievedCRMSalesordersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
end;
OnAfterHandlePostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
end;
local procedure IgnoreArchievedSalesOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
SalesHeader: Record "Sales Header";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if IgnoreRecord then
exit;
SourceRecordRef.SetTable(SalesHeader);
if not CRMIntegrationRecord.FindByRecordID(SalesHeader.RecordId) then
exit;
if CRMIntegrationRecord."Archived Sales Order" then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
IgnoreRecord := true;
end;
local procedure IgnoreArchievedCRMSalesordersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
CRMSalesorder: Record "CRM Salesorder";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if IgnoreRecord then
exit;
SourceRecordRef.SetTable(CRMSalesorder);
if not CRMIntegrationRecord.FindByCRMID(CRMSalesorder.SalesOrderId) then
exit;
if CRMIntegrationRecord."Archived Sales Order" then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
IgnoreRecord := true;
end;
local procedure IgnoreReadOnlyInvoiceOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
CRMInvoice: Record "CRM Invoice";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMInvoiceID: Guid;
begin
if IgnoreRecord then
exit;
if not CRMIntegrationRecord.FindIDFromRecordRef(SourceRecordRef, CRMInvoiceID) then
exit;
if not CRMInvoice.Get(CRMInvoiceID) then
exit;
if CRMInvoice.StateCode <> CRMInvoice.StateCode::Active then
IgnoreRecord := true;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnFindUncoupledDestinationRecord', '', false, false)]
procedure OnFindUncoupledDestinationRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var DestinationIsDeleted: Boolean; var DestinationFound: Boolean)
begin
if DestinationFound then
exit;
if not CRMIntegrationManagement.IsCRMIntegrationEnabled() then
if not CRMIntegrationManagement.IsCDSIntegrationEnabled() then
exit;
case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
'Unit of Measure-CRM Uomschedule':
if CRMUoMScheduleFindUncoupledDestinationRecord(SourceRecordRef, DestinationRecordRef) then
DestinationFound := true;
'Currency-CRM Transactioncurrency':
if CRMTransactionCurrencyFindUncoupledDestinationRecord(SourceRecordRef, DestinationRecordRef) then
DestinationFound := true;
end;
case SourceRecordRef.Number() of
DATABASE::"Sales Price":
if CRMPriceListLineFindUncoupledDestinationRecord(SourceRecordRef, DestinationRecordRef) then
DestinationFound := true;
DATABASE::"Price List Line":
if CRMExtPriceListLineFindUncoupledDestinationRecord(SourceRecordRef, DestinationRecordRef) then
DestinationFound := true;
end;
end;
[EventSubscriber(ObjectType::Table, Database::"Integration Table Mapping", 'OnAfterDeleteEvent', '', false, false)]
procedure OnAfterDeleteIntegrationTableMapping(var Rec: Record "Integration Table Mapping"; RunTrigger: Boolean)
var
JobQueueEntry: record "Job Queue Entry";
begin
if Rec.IsTemporary() then
exit;
if Rec.Type <> Rec.Type::Dataverse then
exit;
JobQueueEntry.LockTable(true);
JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit);
JobQueueEntry.SetFilter("Object ID to Run", '%1|%2|%3', Codeunit::"Integration Synch. Job Runner", Codeunit::"Int. Uncouple Job Runner", Codeunit::"Int. Coupling Job Runner");
JobQueueEntry.SetRange("Record ID to Process", Rec.RecordId());
JobQueueEntry.DeleteTasks();
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Table Synch.", 'OnAfterInitSynchJob', '', true, true)]
local procedure LogTelemetryOnAfterInitSynchJob(ConnectionType: TableConnectionType; IntegrationTableID: Integer)
var
FeatureTelemetry: Codeunit "Feature Telemetry";
IntegrationRecordRef: RecordRef;
TelemetryCategories: Dictionary of [Text, Text];
IntegrationTableName: Text;
begin
if ConnectionType <> TableConnectionType::CRM then
exit;
if IntegrationTableID in [
Database::"CRM Salesorder",
Database::"CRM Invoice",
Database::"CRM Quote",
Database::"CRM Opportunity",
Database::"CRM Pricelevel",
Database::"CRM Product",
Database::"CRM Productpricelevel",
Database::"CRM Uom",
Database::"CRM Uomschedule",
Database::"CRM Account Statistics"] then begin
TelemetryCategories.Add('Category', CategoryTok);
TelemetryCategories.Add('IntegrationTableID', Format(IntegrationTableID));
if TryCalculateTableName(IntegrationRecordRef, IntegrationTableID, IntegrationTableName) then
TelemetryCategories.Add('IntegrationTableName', IntegrationTableName);
Session.LogMessage('0000FME', StrSubstNo(SynchingSalesSpecificEntityTxt, CRMProductName.SHORT()), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryCategories);
FeatureTelemetry.LogUptake('0000H7F', 'Dynamics 365 Sales', Enum::"Feature Uptake Status"::Used);
FeatureTelemetry.LogUsage('0000H7G', 'Dynamics 365 Sales', 'Sales entity synch');
end;
end;
[TryFunction]
local procedure TryCalculateTableName(var IntegrationRecordRef: RecordRef; TableId: Integer; var TableName: Text)
begin
IntegrationRecordRef.Open(TableId);
TableName := IntegrationRecordRef.Name();
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Int. Rec. Uncouple Invoke", 'OnAfterUncoupleRecord', '', false, false)]
local procedure HandleOnAfterUncoupleRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef)
begin
RemoveChildCouplings(LocalRecordRef, IntegrationRecordRef);
end;
[EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterDeleteEvent', '', false, false)]
local procedure HandleOnAfterDeleteAfterPosting(var Rec: Record "Sales Header")
begin
if Rec.IsTemporary() then
exit;
MarkArchivedSalesOrder(Rec);
end;
procedure MarkArchivedSalesOrder(SalesHeader: Record "Sales Header")
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMIntegrationRecord: Record "CRM Integration Record";
begin
CRMIntegrationRecord.SetRange("Table ID", Database::"Sales Header");
CRMIntegrationRecord.SetRange("Integration ID", SalesHeader.SystemId);
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
if CRMIntegrationRecord.FindFirst() then begin
CRMIntegrationRecord."Archived Sales Order" := true;
CRMIntegrationRecord.Modify();
end;
end;
local procedure RemoveChildCouplings(var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef)
var
SalesLineList: List of [Guid];
begin
if GetCoupledSalesLines(LocalRecordRef, IntegrationRecordRef, SalesLineList) then
CRMIntegrationManagement.RemoveCoupling(Database::"Sales Line", SalesLineList, false);
end;
local procedure GetCoupledSalesLines(var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef; var SalesLineList: List of [Guid]): Boolean
var
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
CRMSalesorder: Record "CRM Salesorder";
CRMIntegrationRecord: Record "CRM Integration Record";
begin
if IntegrationRecordRef.Number() <> Database::"CRM Salesorder" then
exit(false);
IntegrationRecordRef.SetTable(CRMSalesorder);
if IsNullGuid(CRMSalesorder.SalesOrderId) then
exit(false);
LocalRecordRef.SetTable(SalesHeader);
if IsNullGuid(SalesHeader.SystemId) then
exit(false);
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Document Type", SalesLine."Document Type"::Order);
if SalesLine.FindSet() then
repeat
if CRMIntegrationRecord.IsRecordCoupled(SalesLine.RecordId) then
SalesLineList.Add(SalesLine.SystemId);
until SalesLine.Next() = 0;
exit(SalesLineList.Count() > 0);
end;
local procedure UpdateOwnerIdAndCompanyId(var DestinationRecordRef: RecordRef)
var
CDSConnectionSetup: Record "CDS Connection Setup";
begin
if CDSIntegrationImpl.IsIntegrationEnabled() then begin
if not CDSIntegrationImpl.CheckCompanyIdNoTelemetry(DestinationRecordRef) then
CDSIntegrationImpl.SetCompanyId(DestinationRecordRef);
CDSConnectionSetup.Get();
if CDSConnectionSetup."Ownership Model" = CDSConnectionSetup."Ownership Model"::Team then
CDSIntegrationImpl.SetOwningTeam(DestinationRecordRef);
end;
end;
local procedure SetCompanyId(var DestinationRecordRef: RecordRef)
begin
if not CDSIntegrationImpl.IsIntegrationEnabled() then
exit;
if CDSIntegrationImpl.CheckCompanyIdNoTelemetry(DestinationRecordRef) then
exit;
CDSIntegrationImpl.SetCompanyId(DestinationRecordRef);
end;
local procedure GetSourceDestCode(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef): Text
begin
if (SourceRecordRef.Number() <> 0) and (DestinationRecordRef.Number() <> 0) then
exit(StrSubstNo(SourceDestCodePatternTxt, SourceRecordRef.Name(), DestinationRecordRef.Name()));
exit('');
end;
local procedure UpdateCustomerBlocked(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef): Boolean
var
Customer: Record Customer;
CRMAccount: Record "CRM Account";
DestinationFieldRef: FieldRef;
SourceFieldRef: FieldRef;
OptionValue: Integer;
begin
// Blocked - we're only handling from Active > Inactive meaning Blocked::"" > Blocked::"All"
SourceFieldRef := SourceRecordRef.Field(CRMAccount.FieldNo(StatusCode));
OptionValue := SourceFieldRef.Value();
if OptionValue = CRMAccount.StatusCode::Inactive then begin
DestinationFieldRef := DestinationRecordRef.Field(Customer.FieldNo(Blocked));
OptionValue := DestinationFieldRef.Value();
if OptionValue = Customer.Blocked::" ".AsInteger() then begin
DestinationFieldRef.Value := Customer.Blocked::All;
exit(true);
end;
end;
end;
local procedure UpdateContactParentCompany(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMContact: Record "CRM Contact";
SourceFieldRef: FieldRef;
ParentCustomerId: Guid;
begin
Session.LogMessage('0000ECA', UpdateContactParentCompanyTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
// When updating we also want to set the company contact id
// We only allow updating contacts if the company has already been created
SourceFieldRef := SourceRecordRef.Field(CRMContact.FieldNo(ParentCustomerId));
ParentCustomerId := SourceFieldRef.Value();
if not CRMSynchHelper.SetContactParentCompany(ParentCustomerId, DestinationRecordRef) then begin
Session.LogMessage('0000ECB', StrSubstNo(UpdateContactParentCompanyFailedTxt, ParentCustomerId), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
if not CRMSynchHelper.IsContactBusinessRelationOptional() then
Error(ContactMissingCompanyErr);
exit;
end;
Session.LogMessage('0000ECH', UpdateContactParentCompanySuccessfulTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
local procedure HandleContactQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
ContactBusinessRelation: Record "Contact Business Relation";
begin
if IgnoreRecord then
exit;
if CRMSynchHelper.IsContactBusinessRelationOptional() then
exit;
if CRMSynchHelper.FindContactRelatedCustomer(SourceRecordRef, ContactBusinessRelation) then
exit;
if CRMIntegrationManagement.IsCDSIntegrationEnabled() then
if CRMSynchHelper.FindContactRelatedVendor(SourceRecordRef, ContactBusinessRelation) then
exit;
IgnoreRecord := true;
end;
local procedure HandleItemQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
SalesReceivablesSetup: Record "Sales & Receivables Setup";
Item: Record Item;
begin
if IgnoreRecord then
exit;
if not SalesReceivablesSetup.Get() then
exit;
SourceRecordRef.SetTable(Item);
if SalesReceivablesSetup."Write-in Product No." <> Item."No." then
exit;
IgnoreRecord := true;
end;
local procedure HandleResourceQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
SalesReceivablesSetup: Record "Sales & Receivables Setup";
Resource: Record Resource;
begin
if IgnoreRecord then
exit;
if not SalesReceivablesSetup.Get() then
exit;
SourceRecordRef.SetTable(Resource);
if SalesReceivablesSetup."Write-in Product No." <> Resource."No." then
exit;
IgnoreRecord := true;
end;
local procedure HandleOpportunityQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
Opportunity: Record Opportunity;
Contact: Record Contact;
CDSConnectionSetup: Record "CDS Connection Setup";
ContactRecordRef: RecordRef;
begin
if IgnoreRecord then
exit;
if not CRMIntegrationManagement.IsCDSIntegrationEnabled() then
exit;
CDSConnectionSetup.Get();
if CDSConnectionSetup."Ownership Model" = CDSConnectionSetup."Ownership Model"::Person then
exit;
SourceRecordRef.SetTable(Opportunity);
if not Contact.Get(Opportunity."Contact No.") then
exit;
ContactRecordRef.GetTable(Contact);
if (Contact.Type <> Contact.Type::Person) or (Contact."Company No." = '') then
IgnoreRecord := true;
HandleContactQueryPostFilterIgnoreRecord(ContactRecordRef, IgnoreRecord);
end;
local procedure UpdateSalesPersOnBeforeInsertRecord(var DestinationRecordRef: RecordRef)
var
SalespersonPurchaser: Record "Salesperson/Purchaser";
DestinationFieldRef: FieldRef;
NewCode: Text;
begin
if not (CRMIntegrationManagement.IsCDSIntegrationEnabled() or CRMIntegrationManagement.IsCRMIntegrationEnabled()) then
exit;
if CRMIntegrationManagement.IsCDSIntegrationEnabled() then
exit;
// We need to create a new code for this SP.
// To do so we just do a SP NO. A
SalespersonPurchaser.SetFilter(Code, SalespersonPurchaserCodeFilterLbl);
if SalespersonPurchaser.FindLast() then
NewCode := IncStr(SalespersonPurchaser.Code)
else
NewCode := StrSubstNo(NewCodePatternTxt, '00001');
DestinationFieldRef := DestinationRecordRef.Field(SalespersonPurchaser.FieldNo(Code));
DestinationFieldRef.Value := NewCode;
end;
local procedure UpdateCRMContactParentCustomerId(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMContact: Record "CRM Contact";
ParentCustomerIdFieldRef: FieldRef;
AccountId: Guid;
Silent: Boolean;
begin
if CRMIntegrationManagement.IsCDSIntegrationEnabled() then
exit;
Silent := CRMSynchHelper.IsContactBusinessRelationOptional();
if FindParentCRMAccountForContact(SourceRecordRef, Silent, AccountId) then begin
// Tranfer the parent company id to the ParentCustomerId
ParentCustomerIdFieldRef := DestinationRecordRef.Field(CRMContact.FieldNo(ParentCustomerId));
ParentCustomerIdFieldRef.Value := AccountId;
end;
end;
local procedure CheckSalesInvoiceLineItemsAreCoupled(SourceRecordRef: RecordRef)
var
SalesInvoiceHeader: Record "Sales Invoice Header";
SalesInvoiceLine: Record "Sales Invoice Line";
begin
SourceRecordRef.SetTable(SalesInvoiceHeader);
// lock the posted sales invoice and lines tables in order not to read uncommitted records from SQL Server
// it is OK to lock these tables, because we are going to release the locks in this method either with an error or a commit
SalesInvoiceHeader.LockTable();
if not SalesInvoiceHeader.Get(SalesInvoiceHeader."No.") then begin
Session.LogMessage('0000GF4', StrSubstNo(FailedToGetPostedSalesInvoiceTxt, Format(SalesInvoiceHeader.SystemId)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(SalesInvoiceNotCommittedErr, SalesInvoiceHeader."No.");
end;
SalesInvoiceLine.LockTable();
SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
if SalesInvoiceLine.IsEmpty() then begin
Session.LogMessage('0000GF5', StrSubstNo(FailedToGetPostedSalesInvoiceLinesTxt, Format(SalesInvoiceHeader.SystemId)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(SalesInvoiceLinesNotCommittedErr, SalesInvoiceHeader."No.");
end;
// this commit will release the lock on posted sales invoice tables, right after the IsEmpty.
// it is justifiable to do this commit, because before raising OnBeforeInsert event,
// codeunit "Integration Rec. Synch. Invoke" doesn't make a single attempt to write to the database. it just reads before this event.
// It hasn't yet called Dynamics 365 Sales to insert the entity or attempted to couple it in Business Central.
// on top of that, OnAfterInsert event starts with a commit, so subscribers to OnBeforeInsert should never attempt to write into the Business Central database
Commit();
// the lines have been committed to SQL database, it is OK to read them.
// no if - let it throw an error if FindSet fails. in this case invoice will be synchronized with next scheduled job
SalesInvoiceLine.FindSet();
repeat
// this call will throw an error if the Sales Invoice Line has an uncoupled product, thus avoiding the creation of the Dynamics 365 Sales invoice
// at this point we haven't even called Dynamics 365 Sales to insert the invoice and we haven't even attempted to couple the invoice
if SalesInvoiceLine."No." <> '' then
FindCRMProductId(SalesInvoiceLine);
until SalesInvoiceLine.Next() = 0;
end;
local procedure UpdateCRMInvoiceAfterInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMInvoice: Record "CRM Invoice";
SalesInvoiceHeader: Record "Sales Invoice Header";
SalesInvoiceLine: Record "Sales Invoice Line";
CRMConnectionSetup: Record "CRM Connection Setup";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
DocumentTotals: Codeunit "Document Totals";
SourceLinesRecordRef: RecordRef;
TaxAmount: Decimal;
begin
Commit();
SourceRecordRef.SetTable(SalesInvoiceHeader);
DestinationRecordRef.SetTable(CRMInvoice);
SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
if not SalesInvoiceLine.IsEmpty() then begin
SourceLinesRecordRef.GetTable(SalesInvoiceLine);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(SourceLinesRecordRef, false, false);
SalesInvoiceLine.CalcSums("Line Discount Amount");
CRMInvoice.TotalLineItemDiscountAmount := SalesInvoiceLine."Line Discount Amount";
end;
CRMConnectionSetup.Get();
if CRMConnectionSetup."Is S.Order Integration Enabled" or CRMConnectionSetup."Bidirectional Sales Order Int." then begin
DocumentTotals.CalculatePostedSalesInvoiceTotals(SalesInvoiceHeader, TaxAmount, SalesInvoiceLine);
CRMInvoice.TotalAmount := SalesInvoiceHeader."Amount Including VAT";
CRMInvoice.TotalTax := TaxAmount;
CRMInvoice.TotalAmountLessFreight := CRMInvoice.TotalAmount - CRMInvoice.TotalTax;
CRMInvoice.TotalDiscountAmount := SalesInvoiceHeader."Invoice Discount Amount";
end else begin
CRMInvoice.FreightAmount := 0;
CRMInvoice.DiscountPercentage := 0;
CRMInvoice.TotalTax := CRMInvoice.TotalAmount - CRMInvoice.TotalAmountLessFreight;
CRMInvoice.TotalDiscountAmount := CRMInvoice.DiscountAmount + CRMInvoice.TotalLineItemDiscountAmount;
end;
CRMInvoice.Modify();
CRMSynchHelper.UpdateCRMInvoiceStatus(CRMInvoice, SalesInvoiceHeader);
Commit();
end;
local procedure UpdatePricesIncludeVATRounding(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMInvoice: Record "CRM Invoice";
CRMInvoicedetail: Record "CRM Invoicedetail";
WriteInCRMInvoicedetail: Record "CRM Invoicedetail";
SalesInvoiceHeader: Record "Sales Invoice Header";
SalesInvoiceLine: Record "Sales Invoice Line";
DifferenceAmount: Decimal;
begin
SourceRecordRef.SetTable(SalesInvoiceHeader);
DestinationRecordRef.SetTable(CRMInvoice);
if SalesInvoiceHeader."Prices Including VAT" then begin
SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
if SalesInvoiceLine.FindSet() then begin
CRMInvoicedetail.SetRange(InvoiceId, CRMInvoice.InvoiceId);
CRMInvoicedetail.SetRange(IsProductOverridden, true);
CRMInvoicedetail.SetRange(ProductDescription, 'Rounding');
if not CRMInvoicedetail.IsEmpty() then
exit;
CRMInvoicedetail.Reset();
repeat
CRMInvoicedetail.SetRange(InvoiceId, CRMInvoice.InvoiceId);
CRMInvoicedetail.SetRange(LineItemNumber, SalesInvoiceLine."Line No.");
if CRMInvoicedetail.FindFirst() then
DifferenceAmount += SalesInvoiceLine."Amount Including VAT" - ((CRMInvoicedetail.PricePerUnit * CRMInvoicedetail.Quantity) + CRMInvoicedetail.Tax - CRMInvoicedetail.ManualDiscountAmount);
until SalesInvoiceLine.Next() = 0;
if DifferenceAmount <> 0 then begin
WriteInCRMInvoicedetail.Init();
WriteInCRMInvoicedetail.InvoiceId := CRMInvoice.InvoiceId;
WriteInCRMInvoicedetail.Quantity := 1;
WriteInCRMInvoicedetail.PricePerUnit := DifferenceAmount;
WriteInCRMInvoicedetail.IsProductOverridden := true;
WriteInCRMInvoicedetail.ProductDescription := 'Rounding';
WriteInCRMInvoicedetail.Insert();
end;
end;
end;
end;
local procedure UpdateCRMInvoiceBeforeInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMAccount: Record "CRM Account";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMInvoice: Record "CRM Invoice";
CRMPricelevel: Record "CRM Pricelevel";
CRMSalesorder: Record "CRM Salesorder";
Customer: Record Customer;
SalesInvoiceHeader: Record "Sales Invoice Header";
ShipmentMethod: Record "Shipment Method";
CRMSalesOrderToSalesOrder: Codeunit "CRM Sales Order to Sales Order";
OutStream: OutStream;
AccountId: Guid;
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeUpdateCRMInvoiceBeforeInsertRecord(SourceRecordRef, DestinationRecordRef, IsHandled);
if IsHandled then
exit;
SourceRecordRef.SetTable(SalesInvoiceHeader);
DestinationRecordRef.SetTable(CRMInvoice);
if not CRMInvoice.Description.HasValue() then
// Shipment Method Code -> go to table Shipment Method, and from there extract the description and add it to
if ShipmentMethod.Get(SalesInvoiceHeader."Shipment Method Code") then begin
CRMInvoice.Description.CreateOutStream(OutStream, TEXTENCODING::UTF16);
OutStream.WriteText(ShipmentMethod.Description);
end;
if CRMSalesOrderToSalesOrder.GetCRMSalesOrder(CRMSalesorder, SalesInvoiceHeader."Your Reference") then begin
CRMInvoice.OpportunityId := CRMSalesorder.OpportunityId;
CRMInvoice.SalesOrderId := CRMSalesorder.SalesOrderId;
CRMInvoice.PriceLevelId := CRMSalesorder.PriceLevelId;
CRMInvoice.Name := CRMSalesorder.Name;
if not CRMSalesOrderToSalesOrder.GetCoupledCustomer(CRMSalesorder, Customer) then begin
if not CRMSalesOrderToSalesOrder.GetCRMAccountOfCRMSalesOrder(CRMSalesorder, CRMAccount) then
Error(CustomerHasChangedErr, CRMSalesorder.OrderNumber, CRMProductName.CDSServiceName());
if not CRMSynchHelper.SynchRecordIfMappingExists(Database::Customer, Database::"CRM Account", CRMAccount.AccountId) then
Error(CustomerHasChangedErr, CRMSalesorder.OrderNumber, CRMProductName.CDSServiceName());
end;
if Customer."No." <> SalesInvoiceHeader."Sell-to Customer No." then
Error(CustomerHasChangedErr, CRMSalesorder.OrderNumber, CRMProductName.CDSServiceName());
CRMInvoice.CustomerId := CRMSalesorder.CustomerId;
CRMInvoice.CustomerIdType := CRMSalesorder.CustomerIdType;
OnUpdateCRMInvoiceBeforeInsertRecordOnBeforeDestinationRecordRefGetTable(CRMInvoice, SalesInvoiceHeader);
DestinationRecordRef.GetTable(CRMInvoice);
if CRMSalesorder.OwnerIdType = CRMSalesorder.OwnerIdType::systemuser then
CDSIntegrationImpl.SetOwningUser(DestinationRecordRef, CRMSalesOrder.OwnerId, true)
else
CDSIntegrationImpl.SetOwningTeam(DestinationRecordRef, CRMSalesorder.OwnerId, true);
SetCompanyId(DestinationRecordRef);
end else begin
CRMInvoice.Name := SalesInvoiceHeader."No.";
Customer.Get(SalesInvoiceHeader."Sell-to Customer No.");
if not CRMIntegrationRecord.FindIDFromRecordID(Customer.RecordId(), AccountId) then
if not CRMSynchHelper.SynchRecordIfMappingExists(Database::Customer, Database::"CRM Account", Customer.RecordId()) then
Error(CustomerHasChangedErr, CRMSalesorder.OrderNumber, CRMProductName.CDSServiceName())
else
if not CRMIntegrationRecord.FindIDFromRecordID(Customer.RecordId(), AccountId) then
Error(RecordMustBeCoupledErr, Customer.TableCaption(), Customer."No.", CRMProductName.CDSServiceName());
CRMInvoice.CustomerId := AccountId;
CRMInvoice.CustomerIdType := CRMInvoice.CustomerIdType::account;
if not CRMSynchHelper.FindCRMPriceListByCurrencyCode(CRMPricelevel, SalesInvoiceHeader."Currency Code") then
CRMSynchHelper.CreateCRMPricelevelInCurrency(
CRMPricelevel, SalesInvoiceHeader."Currency Code", SalesInvoiceHeader."Currency Factor");
CRMInvoice.PriceLevelId := CRMPricelevel.PriceLevelId;
DestinationRecordRef.GetTable(CRMInvoice);
UpdateOwnerIdAndCompanyId(DestinationRecordRef);
end;
end;
local procedure UpdateCRMInvoiceDetailsAfterInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMInvoicedetail: Record "CRM Invoicedetail";
SalesInvoiceLine: Record "Sales Invoice Line";
begin
SourceRecordRef.SetTable(SalesInvoiceLine);
DestinationRecordRef.SetTable(CRMInvoicedetail);
CRMInvoicedetail.VolumeDiscountAmount := 0;
CRMInvoicedetail.ManualDiscountAmount := SalesInvoiceLine."Line Discount Amount";
CRMInvoicedetail.Tax := SalesInvoiceLine."Amount Including VAT" - SalesInvoiceLine.Amount;
CRMInvoicedetail.BaseAmount :=
SalesInvoiceLine.Amount + SalesInvoiceLine."Inv. Discount Amount" + SalesInvoiceLine."Line Discount Amount";
CRMInvoicedetail.ExtendedAmount :=
SalesInvoiceLine."Amount Including VAT" + SalesInvoiceLine."Inv. Discount Amount";
CRMInvoicedetail.Modify();
DestinationRecordRef.GetTable(CRMInvoicedetail);
end;
local procedure UpdateCRMInvoiceDetailsBeforeInsertRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMInvoicedetail: Record "CRM Invoicedetail";
SalesInvoiceHeader: Record "Sales Invoice Header";
SalesInvoiceLine: Record "Sales Invoice Line";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMSalesInvoiceHeaderId: Guid;
begin
SourceRecordRef.SetTable(SalesInvoiceLine);
DestinationRecordRef.SetTable(CRMInvoicedetail);
// Get the NAV and CRM invoice headers
if not SalesInvoiceHeader.Get(SalesInvoiceLine."Document No.") then begin
// if the Get fails, wait for 5 seconds, flush the cache and retry once more
// this is necessary because lines synch is done in a separate thread from invoice synch, and invoice may not be in the SQL database yet
Session.LogMessage('0000G8C', StrSubstNo(FailedToGetPostedSalesInvoiceTxt, SalesInvoiceLine."Document No."), Verbosity::Warning, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Sleep(5000);
Database.SelectLatestVersion();
SalesInvoiceHeader.Get(SalesInvoiceLine."Document No.");
end;
if not CRMIntegrationRecord.FindIDFromRecordID(SalesInvoiceHeader.RecordId(), CRMSalesInvoiceHeaderId) then
Error(NoCoupledSalesInvoiceHeaderErr, CRMProductName.CDSServiceName());
// Initialize the CRM invoice lines
InitializeCRMInvoiceLineFromCRMHeader(CRMInvoicedetail, CRMSalesInvoiceHeaderId);
InitializeCRMInvoiceLineFromSalesInvoiceHeader(CRMInvoicedetail, SalesInvoiceHeader);
InitializeCRMInvoiceLineFromSalesInvoiceLine(CRMInvoicedetail, SalesInvoiceLine);
InitializeCRMInvoiceLineFromSalesInvoiceHeaderAndLine(CRMInvoicedetail, SalesInvoiceHeader, SalesInvoiceLine);
InitializeCRMInvoiceLineWithProductDetails(CRMInvoicedetail, SalesInvoiceLine);
CRMSynchHelper.CreateCRMProductpriceIfAbsent(CRMInvoicedetail);
DestinationRecordRef.GetTable(CRMInvoicedetail);
end;
local procedure UpdateCRMPricelevelBeforeInsertRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMPricelevel: Record "CRM Pricelevel";
CRMTransactioncurrency: Record "CRM Transactioncurrency";
CustomerPriceGroup: Record "Customer Price Group";
DestinationFieldRef: FieldRef;
OutStream: OutStream;
begin
SourceRecordRef.SetTable(CustomerPriceGroup);
CheckCustPriceGroupForSync(CRMTransactioncurrency, CustomerPriceGroup);
DestinationFieldRef := DestinationRecordRef.Field(CRMPricelevel.FieldNo(TransactionCurrencyId));
CRMSynchHelper.UpdateCRMCurrencyIdIfChanged(CRMTransactioncurrency.ISOCurrencyCode, DestinationFieldRef);
DestinationRecordRef.SetTable(CRMPricelevel);
CRMPricelevel.Description.CreateOutStream(OutStream, TEXTENCODING::UTF16);
OutStream.WriteText(CustomerPriceGroup.Description);
DestinationRecordRef.GetTable(CRMPricelevel);
SetCompanyId(DestinationRecordRef);
end;
local procedure UpdateCRMPricelevelBeforeInsertPriceListHeader(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMPricelevel: Record "CRM Pricelevel";
CRMTransactioncurrency: Record "CRM Transactioncurrency";
PriceListHeader: Record "Price List Header";
DestinationFieldRef: FieldRef;
OutStream: OutStream;
begin
SourceRecordRef.SetTable(PriceListHeader);
CheckPriceListHeaderForSync(CRMTransactioncurrency, PriceListHeader);
DestinationFieldRef := DestinationRecordRef.Field(CRMPricelevel.FieldNo(TransactionCurrencyId));
CRMSynchHelper.UpdateCRMCurrencyIdIfChanged(CRMTransactioncurrency.ISOCurrencyCode, DestinationFieldRef);
DestinationRecordRef.SetTable(CRMPricelevel);
CRMPricelevel.Description.CreateOutStream(OutStream, TEXTENCODING::UTF16);
OutStream.WriteText(PriceListHeader.Description);
if PriceListHeader.Status = PriceListHeader.Status::Active then
CRMPricelevel.StateCode := CRMPricelevel.StateCode::Active
else
CRMPricelevel.StateCode := CRMPricelevel.StateCode::Inactive;
DestinationRecordRef.GetTable(CRMPricelevel);
SetCompanyId(DestinationRecordRef);
end;
local procedure UpdateCRMOpportunityBeforeInsertRecord(var DestinationRecordRef: RecordRef)
begin
UpdateOwnerIdAndCompanyId(DestinationRecordRef);
end;
local procedure UpdateCRMPricelevelBeforeModifyRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMPricelevel: Record "CRM Pricelevel";
CRMTransactioncurrency: Record "CRM Transactioncurrency";
CustomerPriceGroup: Record "Customer Price Group";
begin
SourceRecordRef.SetTable(CustomerPriceGroup);
CheckCustPriceGroupForSync(CRMTransactioncurrency, CustomerPriceGroup);
DestinationRecordRef.SetTable(CRMPricelevel);
CRMPricelevel.TestField(TransactionCurrencyId, CRMTransactioncurrency.TransactionCurrencyId);
SetCompanyId(DestinationRecordRef);
end;
local procedure UpdateCRMPricelevelBeforeModifyPriceListHeader(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMPricelevel: Record "CRM Pricelevel";
CRMTransactioncurrency: Record "CRM Transactioncurrency";
PriceListHeader: Record "Price List Header";
begin
SourceRecordRef.SetTable(PriceListHeader);
CheckPriceListHeaderForSync(CRMTransactioncurrency, PriceListHeader);
DestinationRecordRef.SetTable(CRMPricelevel);
CRMPricelevel.TestField(TransactionCurrencyId, CRMTransactioncurrency.TransactionCurrencyId);
SetCompanyId(DestinationRecordRef);
end;
local procedure ResetCRMProductpricelevelFromCustomerPriceGroup(SourceRecordRef: RecordRef)
var
CustomerPriceGroup: Record "Customer Price Group";
SalesPrice: Record "Sales Price";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
SalesPriceRecordRef: RecordRef;
begin
SourceRecordRef.SetTable(CustomerPriceGroup);
SalesPrice.SetRange("Sales Type", SalesPrice."Sales Type"::"Customer Price Group");
SalesPrice.SetRange("Sales Code", CustomerPriceGroup.Code);
if not SalesPrice.IsEmpty() then begin
SalesPriceRecordRef.GetTable(SalesPrice);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(SalesPriceRecordRef, false, false);
end;
end;
local procedure ResetCRMProductpricelevelFromPriceListHeader(SourceRecordRef: RecordRef)
var
PriceListHeader: Record "Price List Header";
PriceListLine: Record "Price List Line";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
SalesPriceRecordRef: RecordRef;
begin
SourceRecordRef.SetTable(PriceListHeader);
PriceListLine.SetRange("Price List Code", PriceListHeader.Code);
if not PriceListLine.IsEmpty() then begin
SalesPriceRecordRef.GetTable(PriceListLine);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(SalesPriceRecordRef, false, false);
end;
end;
local procedure ResetCRMSalesorderdetailFromSalesOrderLine(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
CRMSalesorder: Record "CRM Salesorder";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
CRMSalesorderdetail2: Record "CRM Salesorderdetail";
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
SalesLineRecordRef: RecordRef;
begin
SourceRecordRef.SetTable(SalesHeader);
DestinationRecordRef.SetTable(CRMSalesorder);
CRMSalesorderdetail.SetRange(SalesOrderId, CRMSalesorder.SalesOrderId);
if CRMSalesorderdetail.FindSet() then
repeat
CRMIntegrationRecord.SetRange("CRM ID", CRMSalesorderdetail.SalesOrderDetailId);
CRMIntegrationRecord.SetRange("Table ID", Database::"Sales Line");
if CRMIntegrationRecord.FindFirst() then begin
SalesLine.SetRange(SystemId, CRMIntegrationRecord."Integration ID");
if SalesLine.IsEmpty() then begin
CRMIntegrationRecord.Delete();
CRMSalesorderdetail2.Get(CRMSalesorderdetail.SalesOrderDetailId);
CRMSalesorderdetail2.Delete();
end;
end;
until CRMSalesorderdetail.Next() = 0;
IntegrationTableMapping.FindMapping(Database::"Sales Line", Database::"CRM Salesorderdetail");
SalesLine.Reset();
SalesLine.SetView(IntegrationTableMapping.GetTableFilter());
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Document Type", SalesLine."Document Type"::Order);
if not SalesLine.IsEmpty() then begin
SalesLineRecordRef.GetTable(SalesLine);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(SalesLineRecordRef, false, false);
SalesLine.CalcSums("Line Discount Amount");
if CRMSalesorder.TotalLineItemDiscountAmount <> SalesLine."Line Discount Amount" then begin
CRMSalesorder.TotalLineItemDiscountAmount := SalesLine."Line Discount Amount";
CRMSalesorder.Modify();
end;
end;
end;
local procedure ResetSalesOrderLineFromCRMSalesorderdetail(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
Salesline2: Record "Sales Line";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMSalesorder: Record "CRM Salesorder";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
CRMSalesorderdetail2: Record "CRM Salesorderdetail";
CRMProduct: Record "CRM Product";
IntegrationTableMapping: Record "Integration Table Mapping";
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
CRMSalesorderdetailRecordRef: RecordRef;
CRMSalesorderdetailId: Guid;
CRMSalesorderdetailIdList: List of [Guid];
CRMSalesorderdetailIdFilter: Text;
begin
SourceRecordRef.SetTable(CRMSalesorder);
DestinationRecordRef.SetTable(SalesHeader);
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Document Type", SalesLine."Document Type"::Order);
if SalesLine.FindSet() then
repeat
CRMIntegrationRecord.SetRange("Integration ID", SalesLine.SystemId);
CRMIntegrationRecord.SetRange("Table ID", Database::"Sales Line");
if CRMIntegrationRecord.FindFirst() then begin
CRMSalesorderdetail.SetRange(SalesOrderDetailId, CRMIntegrationRecord."CRM ID");
if CRMSalesorderdetail.IsEmpty() then begin
CRMIntegrationRecord.Delete();
SalesLine2.GetBySystemId(SalesLine.SystemId);
SalesLine2.Delete(true);
end;
end;
until SalesLine.Next() = 0;
IntegrationTableMapping.FindMapping(Database::"Sales Line", Database::"CRM Salesorderdetail");
CRMSalesorderdetail.Reset();
CRMSalesorderdetail.SetView(IntegrationTableMapping.GetIntegrationTableFilter());
CRMSalesorderdetail.SetRange(SalesOrderId, CRMSalesorder.SalesOrderId);
if CRMSalesorderdetail.FindSet() then begin
repeat
if IsNullGuid(CRMSalesorderdetail.ProductId) then
CRMSalesorderdetailIdList.Add(CRMSalesorderdetail.SalesOrderDetailId)
else begin
CRMProduct.Get(CRMSalesorderdetail.ProductId);
if CRMProduct.ProductTypeCode in [CRMProduct.ProductTypeCode::SalesInventory, CRMProduct.ProductTypeCode::Services] then
CRMSalesorderdetailIdList.Add(CRMSalesorderdetail.SalesOrderDetailId);
end;
until CRMSalesorderdetail.Next() = 0;
foreach CRMSalesorderdetailId in CRMSalesorderdetailIdList do
CRMSalesorderdetailIdFilter += CRMSalesorderdetailId + '|';
CRMSalesorderdetailIdFilter := CRMSalesorderdetailIdFilter.TrimEnd('|');
CRMSalesorderdetail2.SetFilter(SalesOrderDetailId, CRMSalesorderdetailIdFilter);
CRMSalesorderdetailRecordRef.GetTable(CRMSalesorderdetail2);
CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(CRMSalesorderdetailRecordRef, Database::"Sales Line", false, false);
end;
end;
local procedure ApplySalesLineTax(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
SalesLine: Record "Sales Line";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
IsHandled: Boolean;
begin
SourceRecordRef.SetTable(SalesLine);
DestinationRecordRef.SetTable(CRMSalesorderdetail);
IsHandled := false;
OnApplySalesLineTaxOnBeforeSetTax(CRMSalesorderdetail, SalesLine, IsHandled);
if IsHandled then
exit;
CRMSalesorderdetail.Tax := SalesLine."Amount Including VAT" - SalesLine.Amount;
DestinationRecordRef.GetTable(CRMSalesorderdetail);
end;
local procedure ApplySalesOrderDiscounts(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
ChangedSalesHeader: Record "Sales Header";
CRMSalesorder: Record "CRM Salesorder";
SalesCalcDiscountByType: Codeunit "Sales - Calc Discount By Type";
CRMDiscountAmount: Decimal;
begin
SourceRecordRef.SetTable(CRMSalesorder);
DestinationRecordRef.SetTable(SalesHeader);
if (CRMSalesorder.DiscountAmount = 0) and (CRMSalesorder.DiscountPercentage = 0) then
exit;
CRMDiscountAmount := CRMSalesorder.TotalLineItemAmount - CRMSalesorder.TotalAmountLessFreight;
ChangedSalesHeader.Get(SalesHeader."Document Type", SalesHeader."No.");
SalesCalcDiscountByType.ApplyInvDiscBasedOnAmt(CRMDiscountAmount, ChangedSalesHeader);
DestinationRecordRef.GetTable(ChangedSalesHeader);
end;
local procedure CreateFreightLines(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
SalesSetup: Record "Sales & Receivables Setup";
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
CRMSalesorder: Record "CRM Salesorder";
begin
SourceRecordRef.SetTable(CRMSalesorder);
DestinationRecordRef.SetTable(SalesHeader);
SalesSetup.Get();
if SalesSetup."Freight G/L Acc. No." <> '' then begin
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange(Type, SalesLine.Type::"G/L Account");
SalesLine.SetRange("No.", SalesSetup."Freight G/L Acc. No.");
SalesLine.SetRange("Unit Price", CRMSalesorder.FreightAmount);
if not SalesLine.IsEmpty() then
exit;
end;
SalesLine.Reset();
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
if SalesLine.FindLast() then
SalesLine.InsertFreightLine(CRMSalesorder.FreightAmount);
end;
local procedure UpdateCRMProductPricelevelAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) UoMHasBeenChanged: Boolean
var
CRMProductpricelevel: Record "CRM Productpricelevel";
SalesPrice: Record "Sales Price";
CRMUom: Record "CRM Uom";
begin
DestinationRecordRef.SetTable(CRMProductpricelevel);
SourceRecordRef.SetTable(SalesPrice);
FindCRMUoMIdForSalesPrice(Enum::"Price Asset Type"::Item, SalesPrice."Item No.", SalesPrice."Unit of Measure Code", CRMUom);
if CRMProductpricelevel.UoMId <> CRMUom.UoMId then begin
CRMProductpricelevel.UoMId := CRMUom.UoMId;
CRMProductpricelevel.UoMScheduleId := CRMUom.UoMScheduleId;
UoMHasBeenChanged := true;
end;
DestinationRecordRef.GetTable(CRMProductpricelevel);
end;
local procedure UpdateCRMProductPricelevelAfterTransferRecordFieldsPriceListLine(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) UoMHasBeenChanged: Boolean
var
CRMProductpricelevel: Record "CRM Productpricelevel";
PriceListLine: Record "Price List Line";
CRMUom: Record "CRM Uom";
begin
DestinationRecordRef.SetTable(CRMProductpricelevel);
SourceRecordRef.SetTable(PriceListLine);
FindCRMUoMIdForSalesPrice(PriceListLine."Asset Type", PriceListLine."Asset No.", PriceListLine."Unit of Measure Code", CRMUom);
if CRMProductpricelevel.UoMScheduleId <> CRMUom.UoMScheduleId then begin
CRMProductpricelevel.UoMScheduleId := CRMUom.UoMScheduleId;
UoMHasBeenChanged := true;
end;
if CRMProductpricelevel.UoMId <> CRMUom.UoMId then begin
CRMProductpricelevel.UoMId := CRMUom.UoMId;
UoMHasBeenChanged := true;
end;
DestinationRecordRef.GetTable(CRMProductpricelevel);
end;
local procedure UpdateCRMProductAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; DestinationIsInserted: Boolean) AdditionalFieldsWereModified: Boolean
var
Item: Record Item;
Resource: Record Resource;
CRMProduct: Record "CRM Product";
GeneralLedgerSetup: Record "General Ledger Setup";
DestinationFieldRef: FieldRef;
UnitOfMeasureCodeFieldRef: FieldRef;
UnitOfMeasureCode: Code[10];
ProductTypeCode: Option;
Blocked: Boolean;
begin
// Update CRM UoM ID, UoMSchedule Id. The CRM UoM Name and UoMScheduleName will be cascade-updated from their IDs by CRM
if SourceRecordRef.Number() = DATABASE::Item then begin
Blocked := SourceRecordRef.Field(Item.FieldNo(Blocked)).Value();
OnUpdateCRMProductAfterTransferRecordFieldsOnAfterCalcItemBlocked(SourceRecordRef, Blocked);
UnitOfMeasureCodeFieldRef := SourceRecordRef.Field(Item.FieldNo("Base Unit of Measure"));
ProductTypeCode := CRMProduct.ProductTypeCode::SalesInventory;
end;
if SourceRecordRef.Number() = DATABASE::Resource then begin
Blocked := SourceRecordRef.Field(Resource.FieldNo(Blocked)).Value();
UnitOfMeasureCodeFieldRef := SourceRecordRef.Field(Resource.FieldNo("Base Unit of Measure"));
ProductTypeCode := CRMProduct.ProductTypeCode::Services;
end;
// Update CRM Currency Id (if changed)
GeneralLedgerSetup.Get();
DestinationFieldRef := DestinationRecordRef.Field(CRMProduct.FieldNo(TransactionCurrencyId));
if CRMSynchHelper.UpdateCRMCurrencyIdIfChanged(Format(GeneralLedgerSetup."LCY Code"), DestinationFieldRef) then
AdditionalFieldsWereModified := true;
DestinationRecordRef.SetTable(CRMProduct);
if not CRMIntegrationManagement.IsUnitGroupMappingEnabled() then begin
UnitOfMeasureCodeFieldRef.TestField();
UnitOfMeasureCode := Format(UnitOfMeasureCodeFieldRef.Value());
if CRMSynchHelper.UpdateCRMProductUoMFieldsIfChanged(CRMProduct, UnitOfMeasureCode) then
AdditionalFieldsWereModified := true;
end else
if CRMSynchHelper.UpdateCRMProductUomscheduleId(CRMProduct, SourceRecordRef) then
AdditionalFieldsWereModified := true;
// If the CRMProduct price is negative, update it to zero (CRM doesn't allow negative prices)
if CRMSynchHelper.UpdateCRMProductPriceIfNegative(CRMProduct) then
AdditionalFieldsWereModified := true;
// If the CRM Quantity On Hand is negative, update it to zero
if CRMSynchHelper.UpdateCRMProductQuantityOnHandIfNegative(CRMProduct) then
AdditionalFieldsWereModified := true;
// Create or update the default price list
if not CRMIntegrationManagement.IsUnitGroupMappingEnabled() then begin
if CRMSynchHelper.UpdateCRMPriceListItem(CRMProduct) then
AdditionalFieldsWereModified := true;
end else
if CRMSynchHelper.UpdateCRMPriceListItems(CRMProduct) then
AdditionalFieldsWereModified := true;
// Update the Vendor Name
if CRMSynchHelper.UpdateCRMProductVendorNameIfChanged(CRMProduct) then
AdditionalFieldsWereModified := true;
// Set the ProductTypeCode, to later know if this product came from an item or from a resource
if CRMSynchHelper.UpdateCRMProductTypeCodeIfChanged(CRMProduct, ProductTypeCode) then
AdditionalFieldsWereModified := true;
if DestinationIsInserted then
if CRMSynchHelper.UpdateCRMProductStateCodeIfChanged(CRMProduct, Blocked) then
AdditionalFieldsWereModified := true;
if AdditionalFieldsWereModified then
DestinationRecordRef.GetTable(CRMProduct);
end;
local procedure UpdateCRMProductAfterInsertRecord(var DestinationRecordRef: RecordRef)
var
CRMProduct: Record "CRM Product";
begin
DestinationRecordRef.SetTable(CRMProduct);
if not CRMIntegrationManagement.IsUnitGroupMappingEnabled() then
CRMSynchHelper.UpdateCRMPriceListItem(CRMProduct)
else
CRMSynchHelper.UpdateCRMPriceListItems(CRMProduct);
CRMSynchHelper.SetCRMProductStateToActive(CRMProduct);
CRMProduct.Modify();
DestinationRecordRef.GetTable(CRMProduct);
end;
local procedure UpdateCRMUomFromItemAfterInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
Item: Record Item;
ItemUnitOfMeasure: Record "Item Unit of Measure";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMProduct: Record "CRM Product";
CRMUom: Record "CRM Uom";
ItemRecordRef: RecordRef;
SourceFieldRef: FieldRef;
CRMProductId: Guid;
begin
SourceFieldRef := SourceRecordRef.Field(ItemUnitOfMeasure.FieldNo("Item No."));
Item.Get(Format(SourceFieldRef.Value()));
ItemRecordRef.GetTable(Item);
DestinationRecordRef.SetTable(CRMUom);
if CRMIntegrationRecord.FindIDFromRecordRef(ItemRecordRef, CRMProductId) then
if CRMProduct.Get(CRMProductId) then
CRMSynchHelper.UpdateCRMPriceListItemForUom(CRMProduct, CRMUom);
end;
local procedure UpdateCRMUomFromResourceAfterInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
Resource: Record Resource;
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMProduct: Record "CRM Product";
CRMUom: Record "CRM Uom";
ResourceRecordRef: RecordRef;
SourceFieldRef: FieldRef;
CRMProductId: Guid;
begin
SourceFieldRef := SourceRecordRef.Field(ResourceUnitOfMeasure.FieldNo("Resource No."));
Resource.Get(Format(SourceFieldRef.Value()));
ResourceRecordRef.GetTable(Resource);
DestinationRecordRef.SetTable(CRMUom);
if CRMIntegrationRecord.FindIDFromRecordRef(ResourceRecordRef, CRMProductId) then
if CRMProduct.Get(CRMProductId) then
CRMSynchHelper.UpdateCRMPriceListItemForUom(CRMProduct, CRMUom)
end;
local procedure UpdateCRMProductBeforeInsertRecord(var DestinationRecordRef: RecordRef)
var
CRMProduct: Record "CRM Product";
begin
DestinationRecordRef.SetTable(CRMProduct);
CRMSynchHelper.SetCRMDecimalsSupportedValue(CRMProduct);
CRMSynchHelper.SetCRMDefaultPriceListOnProduct(CRMProduct);
DestinationRecordRef.GetTable(CRMProduct);
SetCompanyId(DestinationRecordRef);
end;
local procedure UpdateItemAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) AdditionalFieldsWereModified: Boolean
var
Item: Record Item;
CRMProduct: Record "CRM Product";
Blocked: Boolean;
begin
SourceRecordRef.SetTable(CRMProduct);
DestinationRecordRef.SetTable(Item);
Blocked := CRMProduct.StateCode <> CRMProduct.StateCode::Active;
if CRMSynchHelper.UpdateItemBlockedIfChanged(Item, Blocked) then begin
DestinationRecordRef.GetTable(Item);
AdditionalFieldsWereModified := true;
end;
end;
local procedure UpdateResourceAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) AdditionalFieldsWereModified: Boolean
var
Resource: Record Resource;
CRMProduct: Record "CRM Product";
Blocked: Boolean;
begin
SourceRecordRef.SetTable(CRMProduct);
DestinationRecordRef.SetTable(Resource);
Blocked := CRMProduct.StateCode <> CRMProduct.StateCode::Active;
if CRMSynchHelper.UpdateResourceBlockedIfChanged(Resource, Blocked) then begin
DestinationRecordRef.GetTable(Resource);
AdditionalFieldsWereModified := true;
end;
end;
local procedure UpdateCRMTransactionCurrencyBeforeInsertRecord(var DestinationRecordRef: RecordRef)
var
CRMTransactioncurrency: Record "CRM Transactioncurrency";
DestinationCurrencyPrecisionFieldRef: FieldRef;
begin
// Fill in the target currency precision, taken from CRM precision defaults
DestinationCurrencyPrecisionFieldRef := DestinationRecordRef.Field(CRMTransactioncurrency.FieldNo(CurrencyPrecision));
DestinationCurrencyPrecisionFieldRef.Value := CRMSynchHelper.GetCRMCurrencyDefaultPrecision();
end;
local procedure UpdateCRMTransactionCurrencyAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) AdditionalFieldsWereModified: Boolean
var
CRMTransactioncurrency: Record "CRM Transactioncurrency";
Currency: Record Currency;
CurrencyCodeFieldRef: FieldRef;
DestinationExchangeRateFieldRef: FieldRef;
LatestExchangeRate: Decimal;
begin
// Fill-in the target currency Exchange Rate
CurrencyCodeFieldRef := SourceRecordRef.Field(Currency.FieldNo(Code));
DestinationExchangeRateFieldRef := DestinationRecordRef.Field(CRMTransactioncurrency.FieldNo(ExchangeRate));
LatestExchangeRate := CRMSynchHelper.GetCRMLCYToFCYExchangeRate(Format(CurrencyCodeFieldRef.Value()));
if LatestExchangeRate = 0 then
Error(CurrencyExchangeRateMissingErr, Format(CurrencyCodeFieldRef.Value()), CRMProductName.CDSServiceName());
if CRMSynchHelper.UpdateFieldRefValueIfChanged(
DestinationExchangeRateFieldRef,
Format(LatestExchangeRate))
then
AdditionalFieldsWereModified := true;
end;
local procedure CRMTransactionCurrencyFindUncoupledDestinationRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) DestinationFound: Boolean
var
Currency: Record Currency;
CRMTransactioncurrency: Record "CRM Transactioncurrency";
CurrencyCodeFieldRef: FieldRef;
begin
// Attempt to match currencies between NAV and CRM, on NAVCurrency.Code = CRMCurrency.ISOCode
CurrencyCodeFieldRef := SourceRecordRef.Field(Currency.FieldNo(Code));
// Find destination record
CRMTransactioncurrency.SetRange(ISOCurrencyCode, Format(CurrencyCodeFieldRef.Value()));
// A match between the selected NAV currency and a CRM currency was found
if CRMTransactioncurrency.FindFirst() then
DestinationFound := DestinationRecordRef.Get(CRMTransactioncurrency.RecordId());
end;
local procedure CRMPriceListLineFindUncoupledDestinationRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) DestinationFound: Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMProductpricelevel: Record "CRM Productpricelevel";
CRMUom: Record "CRM Uom";
CustomerPriceGroup: Record "Customer Price Group";
Item: Record Item;
SalesPrice: Record "Sales Price";
begin
// Look for a line with the same combination of ProductId,UoMId
SourceRecordRef.SetTable(SalesPrice);
CustomerPriceGroup.Get(SalesPrice."Sales Code");
if CRMIntegrationRecord.FindByRecordID(CustomerPriceGroup.RecordId()) then begin
CRMProductpricelevel.SetRange(PriceLevelId, CRMIntegrationRecord."CRM ID");
FindCRMUoMIdForSalesPrice(Enum::"Price Asset Type"::Item, SalesPrice."Item No.", SalesPrice."Unit of Measure Code", CRMUom);
CRMProductpricelevel.SetRange(UoMId, CRMUom.UoMId);
Item.Get(SalesPrice."Item No.");
CRMIntegrationRecord.FindByRecordID(Item.RecordId());
CRMProductpricelevel.SetRange(ProductId, CRMIntegrationRecord."CRM ID");
DestinationFound := CRMProductpricelevel.FindFirst();
DestinationRecordRef.GetTable(CRMProductpricelevel);
end;
end;
local procedure CRMExtPriceListLineFindUncoupledDestinationRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) DestinationFound: Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMProductpricelevel: Record "CRM Productpricelevel";
CRMUom: Record "CRM Uom";
Item: Record Item;
Resource: Record Resource;
PriceListHeader: Record "Price List Header";
PriceListLine: Record "Price List Line";
begin
// Look for a line with the same combination of ProductId,UoMId
SourceRecordRef.SetTable(PriceListLine);
PriceListHeader.Get(PriceListLine."Price List Code");
if CRMIntegrationRecord.FindByRecordID(PriceListHeader.RecordId()) then begin
CRMProductpricelevel.SetRange(PriceLevelId, CRMIntegrationRecord."CRM ID");
FindCRMUoMIdForSalesPrice(PriceListLine."Asset Type", PriceListLine."Asset No.", PriceListLine."Unit of Measure Code", CRMUom);
CRMProductpricelevel.SetRange(UoMId, CRMUom.UoMId);
case PriceListLine."Asset Type" of
PriceListLine."Asset Type"::Item:
begin
Item.Get(PriceListLine."Asset No.");
CRMIntegrationRecord.FindByRecordID(Item.RecordId());
end;
PriceListLine."Asset Type"::Resource:
begin
Resource.Get(PriceListLine."Asset No.");
CRMIntegrationRecord.FindByRecordID(Resource.RecordId());
end;
end;
CRMProductpricelevel.SetRange(ProductId, CRMIntegrationRecord."CRM ID");
DestinationFound := CRMProductpricelevel.FindFirst();
DestinationRecordRef.GetTable(CRMProductpricelevel);
end;
end;
local procedure UpdateCRMUoMScheduleAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) AdditionalFieldsWereModified: Boolean
var
CRMUomschedule: Record "CRM Uomschedule";
DestinationFieldRef: FieldRef;
UnitNameWasUpdated: Boolean;
CRMUomScheduleName: Text[200];
CRMUomScheduleStateCode: Option;
UnitGroupName: Text[200];
UnitOfMeasureName: Text[100];
CRMID: Guid;
begin
// Prefix with NAV
UnitOfMeasureName := CRMSynchHelper.GetUnitOfMeasureName(SourceRecordRef);
UnitGroupName := CRMSynchHelper.GetUnitGroupName(UnitOfMeasureName); // prefix with "NAV "
DestinationFieldRef := DestinationRecordRef.Field(CRMUomschedule.FieldNo(Name));
CRMUomScheduleName := Format(DestinationFieldRef.Value());
if CRMUomScheduleName <> UnitGroupName then begin
DestinationFieldRef.Value := UnitGroupName;
AdditionalFieldsWereModified := true;
end;
// Get the State Code
DestinationFieldRef := DestinationRecordRef.Field(CRMUomschedule.FieldNo(StateCode));
CRMUomScheduleStateCode := DestinationFieldRef.Value();
DestinationFieldRef := DestinationRecordRef.Field(CRMUomschedule.FieldNo(UoMScheduleId));
CRMID := DestinationFieldRef.Value();
if not ValidateCRMUoMSchedule(CRMUomScheduleName, CRMUomScheduleStateCode, CRMID, UnitOfMeasureName, UnitNameWasUpdated) then
exit;
if UnitNameWasUpdated then
AdditionalFieldsWereModified := true;
end;
local procedure CheckCRMUoMScheduleAfterTransferRecordFields(var DestinationRecordRef: RecordRef)
var
CRMUomschedule: Record "CRM Uomschedule";
DestinationFieldRef: FieldRef;
CRMUomScheduleName: Text[200];
CRMUomScheduleStateCode: Option;
begin
DestinationFieldRef := DestinationRecordRef.Field(CRMUomschedule.FieldNo(StateCode));
CRMUomScheduleStateCode := DestinationFieldRef.Value();
DestinationFieldRef := DestinationRecordRef.Field(CRMUomschedule.FieldNo(Name));
CRMUomScheduleName := Format(DestinationFieldRef.Value());
if CRMUomScheduleStateCode = CRMUomschedule.StateCode::Inactive then
Error(CRMUnitGroupExistsAndIsInactiveErr, CRMUomschedule.TableCaption(), CRMUomScheduleName, CRMProductName.CDSServiceName());
end;
local procedure UpdateCRMUomFromItemAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef): Boolean
var
Item: Record Item;
ItemUnitOfMeasure: Record "Item Unit of Measure";
UnitGroup: Record "Unit Group";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMUom: Record "CRM Uom";
CRMUomschedule: Record "CRM Uomschedule";
CRMProduct: Record "CRM Product";
ItemRecordRef: RecordRef;
SourceFieldRef: FieldRef;
DestinationFieldRef: FieldRef;
CRMUomscheduleId: Guid;
CRMProductId: Guid;
AdditionalFieldsWereModified: Boolean;
begin
DestinationFieldRef := DestinationRecordRef.Field(CRMUom.FieldNo(UoMScheduleId));
SourceFieldRef := SourceRecordRef.Field(ItemUnitOfMeasure.FieldNo("Item No."));
Item.Get(Format(SourceFieldRef.Value()));
UnitGroup.SetRange("Source Id", Item.SystemId);
UnitGroup.SetRange("Source Type", UnitGroup."Source Type"::Item);
if not UnitGroup.FindFirst() then
Error(ItemUnitGroupNotFoundErr, Item."No.");
CRMUomscheduleId := DestinationFieldRef.Value();
CRMUomschedule.SetRange(Name, UnitGroup.GetCode());
if not CRMUomschedule.FindFirst() then begin
CoupleAndSyncUnitGroup(UnitGroup);
CRMUomschedule.FindFirst();
end;
if CRMUomscheduleId <> CRMUomschedule.UoMScheduleId then begin
DestinationFieldRef.Value := CRMUomschedule.UoMScheduleId;
AdditionalFieldsWereModified := true;
end;
ItemRecordRef.GetTable(Item);
DestinationRecordRef.SetTable(CRMUom);
if CRMIntegrationRecord.FindIDFromRecordRef(ItemRecordRef, CRMProductId) then
if CRMProduct.Get(CRMProductId) then
if CRMSynchHelper.UpdateCRMPriceListItemForUom(CRMProduct, CRMUom) then
AdditionalFieldsWereModified := true;
exit(AdditionalFieldsWereModified);
end;
local procedure UpdateCRMUomFromResourceAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef): Boolean
var
Resource: Record Resource;
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
UnitGroup: Record "Unit Group";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMUom: Record "CRM Uom";
CRMUomschedule: Record "CRM Uomschedule";
CRMProduct: Record "CRM Product";
ResourceRecordRef: RecordRef;
SourceFieldRef: FieldRef;
DestinationFieldRef: FieldRef;
CRMUomscheduleId: Guid;
CRMProductId: Guid;
AdditionalFieldsWereModified: Boolean;
begin
DestinationFieldRef := DestinationRecordRef.Field(CRMUom.FieldNo(UoMScheduleId));
SourceFieldRef := SourceRecordRef.Field(ResourceUnitOfMeasure.FieldNo("Resource No."));
Resource.Get(Format(SourceFieldRef.Value()));
UnitGroup.SetRange("Source Id", Resource.SystemId);
UnitGroup.SetRange("Source Type", UnitGroup."Source Type"::Resource);
if not UnitGroup.FindFirst() then
Error(ResourceUnitGroupNotFoundErr, Resource."No.");
CRMUomscheduleId := DestinationFieldRef.Value();
CRMUomschedule.SetRange(Name, UnitGroup.GetCode());
if not CRMUomschedule.FindFirst() then begin
CoupleAndSyncUnitGroup(UnitGroup);
CRMUomschedule.FindFirst();
end;
if CRMUomscheduleId <> CRMUomschedule.UoMScheduleId then begin
DestinationFieldRef.Value := CRMUomschedule.UoMScheduleId;
AdditionalFieldsWereModified := true;
end;
ResourceRecordRef.GetTable(Resource);
DestinationRecordRef.SetTable(CRMUom);
if CRMIntegrationRecord.FindIDFromRecordRef(ResourceRecordRef, CRMProductId) then
if CRMProduct.Get(CRMProductId) then
if CRMSynchHelper.UpdateCRMPriceListItemForUom(CRMProduct, CRMUom) then
AdditionalFieldsWereModified := true;
exit(AdditionalFieldsWereModified);
end;
local procedure CoupleAndSyncUnitGroup(var UnitGroup: Record "Unit Group")
var
CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
UnitGroupRecordRef: RecordRef;
begin
UnitGroupRecordRef.GetTable(UnitGroup);
CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(UnitGroupRecordRef, false, true);
end;
local procedure CRMUoMScheduleFindUncoupledDestinationRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) DestinationFound: Boolean
var
CRMUomschedule: Record "CRM Uomschedule";
UnitFieldWasUpdated: Boolean;
begin
// A match between the selected NAV Unit of Measure and a CRM <Unit Group, Unit> tuple was found
if FindValidCRMUoMSchedule(CRMUomschedule, SourceRecordRef, UnitFieldWasUpdated) then
DestinationFound := DestinationRecordRef.Get(CRMUomschedule.RecordId());
end;
local procedure FindValidCRMUoMSchedule(var CRMUomschedule: Record "CRM Uomschedule"; SourceRecordRef: RecordRef; var UnitNameWasUpdated: Boolean): Boolean
var
UnitGroupName: Text[200];
UnitOfMeasureName: Text[100];
begin
UnitOfMeasureName := CRMSynchHelper.GetUnitOfMeasureName(SourceRecordRef);
UnitGroupName := CRMSynchHelper.GetUnitGroupName(UnitOfMeasureName); // prefix with "NAV "
// If the CRM Unit Group does not exist, exit
CRMUomschedule.SetRange(Name, UnitGroupName);
if not CRMUomschedule.FindFirst() then
exit(false);
ValidateCRMUoMSchedule(
CRMUomschedule.Name, CRMUomschedule.StateCode, CRMUomschedule.UoMScheduleId, UnitOfMeasureName, UnitNameWasUpdated);
exit(true);
end;
local procedure ValidateCRMUoMSchedule(CRMUomScheduleName: Text[200]; CRMUomScheduleStateCode: Option; CRMUomScheduleId: Guid; UnitOfMeasureName: Text[100]; var UnitNameWasUpdated: Boolean): Boolean
var
CRMUom: Record "CRM Uom";
CRMUomschedule: Record "CRM Uomschedule";
begin
// If the CRM Unit Group is not active throw and error
if CRMUomScheduleStateCode = CRMUomschedule.StateCode::Inactive then
Error(CRMUnitGroupExistsAndIsInactiveErr, CRMUomschedule.TableCaption(), CRMUomScheduleName, CRMProductName.CDSServiceName());
// If the CRM Unit Group contains > 1 Units, fail
CRMUom.SetRange(UoMScheduleId, CRMUomScheduleId);
if CRMUom.Count() > 1 then
Error(
CRMUnitGroupContainsMoreThanOneUoMErr, CRMUomschedule.TableCaption(), CRMUomScheduleName, CRMUom.TableCaption(),
CRMProductName.CDSServiceName());
// If the CRM Unit Group contains zero Units, then exit (no match found)
if not CRMUom.FindFirst() then
exit(false);
// Verify the CRM Unit name is correct, else update it
if CRMUom.Name <> UnitOfMeasureName then begin
CRMUom.Name := UnitOfMeasureName;
CRMUom.Modify();
UnitNameWasUpdated := true;
end;
exit(true);
end;
local procedure FindParentCRMAccountForContact(SourceRecordRef: RecordRef; Silent: Boolean; var AccountId: Guid): Boolean
var
ContactBusinessRelation: Record "Contact Business Relation";
Contact: Record Contact;
Customer: Record Customer;
CRMIntegrationRecord: Record "CRM Integration Record";
begin
if CRMSynchHelper.FindContactRelatedCustomer(SourceRecordRef, ContactBusinessRelation) then begin
if Customer.Get(ContactBusinessRelation."No.") then begin
CRMIntegrationRecord.FindIDFromRecordID(Customer.RecordId(), AccountId);
exit(true);
end;
if not Silent then
Error(RecordNotFoundErr, Customer.TableCaption(), ContactBusinessRelation."No.");
exit(false);
end;
if not Silent then
Error(ContactsMustBeRelatedToCompanyErr, SourceRecordRef.Field(Contact.FieldNo("No.")).Value());
exit(false);
end;
local procedure InitializeCRMInvoiceLineFromCRMHeader(var CRMInvoicedetail: Record "CRM Invoicedetail"; CRMInvoiceId: Guid)
var
CRMInvoice: Record "CRM Invoice";
begin
CRMInvoice.Get(CRMInvoiceId);
CRMInvoicedetail.ActualDeliveryOn := CRMInvoice.DateDelivered;
CRMInvoicedetail.TransactionCurrencyId := CRMInvoice.TransactionCurrencyId;
CRMInvoicedetail.ExchangeRate := CRMInvoice.ExchangeRate;
CRMInvoicedetail.InvoiceId := CRMInvoice.InvoiceId;
CRMInvoicedetail.ShipTo_City := CRMInvoice.ShipTo_City;
CRMInvoicedetail.ShipTo_Country := CRMInvoice.ShipTo_Country;
CRMInvoicedetail.ShipTo_Line1 := CRMInvoice.ShipTo_Line1;
CRMInvoicedetail.ShipTo_Line2 := CRMInvoice.ShipTo_Line2;
CRMInvoicedetail.ShipTo_Line3 := CRMInvoice.ShipTo_Line3;
CRMInvoicedetail.ShipTo_Name := CRMInvoice.ShipTo_Name;
CRMInvoicedetail.ShipTo_PostalCode := CRMInvoice.ShipTo_PostalCode;
CRMInvoicedetail.ShipTo_StateOrProvince := CRMInvoice.ShipTo_StateOrProvince;
CRMInvoicedetail.ShipTo_Fax := CRMInvoice.ShipTo_Fax;
CRMInvoicedetail.ShipTo_Telephone := CRMInvoice.ShipTo_Telephone;
end;
local procedure InitializeCRMInvoiceLineFromSalesInvoiceHeader(var CRMInvoicedetail: Record "CRM Invoicedetail"; SalesInvoiceHeader: Record "Sales Invoice Header")
begin
CRMInvoicedetail.TransactionCurrencyId := CRMSynchHelper.GetCRMTransactioncurrency(SalesInvoiceHeader."Currency Code");
if SalesInvoiceHeader."Currency Factor" = 0 then
CRMInvoicedetail.ExchangeRate := 1
else
CRMInvoicedetail.ExchangeRate := Round(1 / SalesInvoiceHeader."Currency Factor");
end;
local procedure InitializeCRMInvoiceLineFromSalesInvoiceLine(var CRMInvoicedetail: Record "CRM Invoicedetail"; SalesInvoiceLine: Record "Sales Invoice Line")
begin
CRMInvoicedetail.LineItemNumber := SalesInvoiceLine."Line No.";
CRMInvoicedetail.Tax := SalesInvoiceLine."Amount Including VAT" - SalesInvoiceLine.Amount;
end;
local procedure InitializeCRMInvoiceLineFromSalesInvoiceHeaderAndLine(var CRMInvoicedetail: Record "CRM Invoicedetail"; SalesInvoiceHeader: Record "Sales Invoice Header"; SalesInvoiceLine: Record "Sales Invoice Line")
var
GeneralLedgerSetup: Record "General Ledger Setup";
begin
GeneralLedgerSetup.Get();
if SalesInvoiceHeader."Prices Including VAT" then
if SalesInvoiceLine.Quantity <> 0 then
CRMInvoicedetail.PricePerUnit := SalesInvoiceLine."Unit Price" -
Round((SalesInvoiceLine."Amount Including VAT" - SalesInvoiceLine.Amount) / SalesInvoiceLine.Quantity, GeneralLedgerSetup."Unit-Amount Rounding Precision");
end;
local procedure InitializeCRMInvoiceLineWithProductDetails(var CRMInvoicedetail: Record "CRM Invoicedetail"; SalesInvoiceLine: Record "Sales Invoice Line")
var
ItemUnitOfMeasure: Record "Item Unit of Measure";
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
CRMProduct: Record "CRM Product";
CRMIntegrationRecord: Record "CRM Integration Record";
ItemUnitOfMeasureRecordRef: RecordRef;
ResourceUnitOfMeasureRecordRef: RecordRef;
CRMProductId: Guid;
CRMUoMId: Guid;
begin
CRMProductId := FindCRMProductId(SalesInvoiceLine);
if IsNullGuid(CRMProductId) then begin
// This will be created as a CRM write-in product
CRMInvoicedetail.IsProductOverridden := true;
CRMInvoicedetail.ProductDescription :=
StrSubstNo('%1 %2.', Format(SalesInvoiceLine."No."), Format(SalesInvoiceLine.Description));
end else begin
// There is a coupled product or resource in CRM, transfer data from there
CRMProduct.Get(CRMProductId);
CRMInvoicedetail.ProductId := CRMProduct.ProductId;
if CRMIntegrationManagement.IsUnitGroupMappingEnabled() then
case SalesInvoiceLine.Type of
SalesInvoiceLine.Type::Item:
begin
if not ItemUnitOfMeasure.Get(SalesInvoiceLine."No.", SalesInvoiceLine."Unit of Measure Code") then
Error(ItemUnitOfMeasureDoesNotExistErr, CRMProductName.CDSServiceName(), SalesInvoiceLine."Unit of Measure Code");
ItemUnitOfMeasureRecordRef.GetTable(ItemUnitOfMeasure);
if not CRMIntegrationRecord.FindIDFromRecordRef(ItemUnitOfMeasureRecordRef, CRMUoMId) then
Error(NotCoupledItemUoMErr, CRMProductName.CDSServiceName(), SalesInvoiceLine."Unit of Measure Code");
CRMInvoicedetail.UoMId := CRMUoMId;
end;
SalesInvoiceLine.Type::Resource:
begin
if not ResourceUnitOfMeasure.Get(SalesInvoiceLine."No.", SalesInvoiceLine."Unit of Measure Code") then
Error(ResourceUnitOfMeasureDoesNotExistErr, CRMProductName.CDSServiceName(), SalesInvoiceLine."Unit of Measure Code");
ResourceUnitOfMeasureRecordRef.GetTable(ResourceUnitOfMeasure);
if not CRMIntegrationRecord.FindIDFromRecordRef(ResourceUnitOfMeasureRecordRef, CRMUoMId) then
Error(NotCoupledResourceUoMErr, CRMProductName.CDSServiceName(), SalesInvoiceLine."Unit of Measure Code");
CRMInvoicedetail.UoMId := CRMUoMId;
end;
end
else
CRMInvoicedetail.UoMId := CRMProduct.DefaultUoMId;
end;
end;
local procedure FindCRMProductId(SalesInvoiceLine: Record "Sales Invoice Line") CRMID: Guid
var
CRMIntegrationRecord: Record "CRM Integration Record";
SalesReceivablesSetup: Record "Sales & Receivables Setup";
Resource: Record Resource;
begin
Clear(CRMID);
SalesReceivablesSetup.Get();
// if this is the item that represents the Write-In product from CDS, there is no CRMID
if SalesReceivablesSetup."Write-in Product No." = SalesInvoiceLine."No." then
exit;
case SalesInvoiceLine.Type of
SalesInvoiceLine.Type::Item:
CRMID := FindCRMProductIdForItem(SalesInvoiceLine."No.");
SalesInvoiceLine.Type::Resource:
begin
Resource.Get(SalesInvoiceLine."No.");
if not CRMIntegrationRecord.FindIDFromRecordID(Resource.RecordId(), CRMID) then begin
if not CRMSynchHelper.SynchRecordIfMappingExists(Database::Resource, Database::"CRM Product", Resource.RecordId()) then
Error(CannotSynchProductErr, Resource."No.");
if not CRMIntegrationRecord.FindIDFromRecordID(Resource.RecordId(), CRMID) then
Error(CannotFindSyncedProductErr);
end;
end;
end;
end;
local procedure FindCRMProductIdForItem(ItemNo: Code[20]) CRMID: Guid
var
Item: Record Item;
FilterItem: Record Item;
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
Item.Get(ItemNo);
if not CRMIntegrationRecord.FindIDFromRecordID(Item.RecordId(), CRMID) then
if IntegrationTableMapping.FindMapping(Database::Item, Database::"CRM Product") then begin
FilterItem.SetView(IntegrationTableMapping.GetTableFilter());
FilterItem.SetRange("No.", ItemNo);
if not FilterItem.IsEmpty() then begin
if not CRMSynchHelper.SynchRecordIfMappingExists(Database::Item, Database::"CRM Product", Item.RecordId()) then
Error(CannotSynchProductErr, Item."No.");
if not CRMIntegrationRecord.FindIDFromRecordID(Item.RecordId(), CRMID) then
Error(CannotFindSyncedProductErr);
end;
end;
end;
local procedure FindCRMProductIdForResource(ResourceNo: Code[20]) CRMID: Guid
var
Resource: Record Resource;
FilterResource: Record Resource;
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
Resource.Get(ResourceNo);
if not CRMIntegrationRecord.FindIDFromRecordID(Resource.RecordId(), CRMID) then
if IntegrationTableMapping.FindMapping(Database::Resource, Database::"CRM Product") then begin
FilterResource.SetView(IntegrationTableMapping.GetTableFilter());
FilterResource.SetRange("No.", ResourceNo);
if not FilterResource.IsEmpty() then begin
if not CRMSynchHelper.SynchRecordIfMappingExists(Database::Resource, Database::"CRM Product", Resource.RecordId()) then
Error(CannotSynchResourceErr, Resource."No.");
if not CRMIntegrationRecord.FindIDFromRecordID(Resource.RecordId(), CRMID) then
Error(CannotFindSyncedProductErr);
end;
end;
end;
local procedure FindCRMUoMIdForSalesPrice(AssetType: Enum "Price Asset Type"; AssetNo: Code[20];
UoMCode: Code[10]; var CRMUom: Record "CRM Uom")
var
Item: Record Item;
Resource: Record Resource;
UnitGroup: Record "Unit Group";
CRMUomschedule: Record "CRM Uomschedule";
begin
case AssetType of
AssetType::Item:
begin
Item.Get(AssetNo);
if UoMCode = '' then
UoMCode := Item."Base Unit of Measure";
end;
AssetType::Resource:
begin
Resource.Get(AssetNo);
if UoMCode = '' then
UoMCode := Resource."Base Unit of Measure";
end;
else
if UoMCode = '' then
exit;
end;
if not CRMIntegrationManagement.IsUnitGroupMappingEnabled() then
CRMSynchHelper.GetValidCRMUnitOfMeasureRecords(CRMUom, CRMUomschedule, UoMCode)
else begin
if not IsNullGuid(Item.SystemId) then
UnitGroup.Get(UnitGroup."Source Type"::Item, Item.SystemId)
else
UnitGroup.Get(UnitGroup."Source Type"::Resource, Resource.SystemId);
CRMUomschedule.SetRange(Name, UnitGroup.GetCode());
if not CRMUomschedule.FindFirst() then
Error(CRMUnitGroupNotFoundErr, UnitGroup.GetCode());
CRMUom.SetRange(Name, UoMCode);
CRMUom.SetRange(UoMScheduleId, CRMUomschedule.UoMScheduleId);
if not CRMUom.FindFirst() then
Error(CRMUnitNotFoundErr, UoMCode, CRMUomschedule.UoMScheduleId);
end;
end;
local procedure CheckSalesPricesForSync(CustomerPriceGroupCode: Code[10]; ExpectedCurrencyCode: Code[10])
var
SalesPrice: Record "Sales Price";
CRMUom: Record "CRM Uom";
begin
SalesPrice.SetRange("Sales Type", SalesPrice."Sales Type"::"Customer Price Group");
SalesPrice.SetRange("Sales Code", CustomerPriceGroupCode);
if SalesPrice.FindSet() then
repeat
SalesPrice.TestField("Currency Code", ExpectedCurrencyCode);
FindCRMProductIdForItem(SalesPrice."Item No.");
FindCRMUoMIdForSalesPrice(Enum::"Price Asset Type"::Item, SalesPrice."Item No.", SalesPrice."Unit of Measure Code", CRMUom);
until SalesPrice.Next() = 0;
end;
local procedure CheckPriceListLinesForSync(PriceListCode: Code[20]; ExpectedCurrencyCode: Code[10])
var
PriceListLine: Record "Price List Line";
CRMUom: Record "CRM Uom";
begin
PriceListLine.SetRange("Price List Code", PriceListCode);
if PriceListLine.FindSet() then
repeat
PriceListLine.TestField("Currency Code", ExpectedCurrencyCode);
case PriceListLine."Asset Type" of
PriceListLine."Asset Type"::Item:
FindCRMProductIdForItem(PriceListLine."Asset No.");
PriceListLine."Asset Type"::Resource:
FindCRMProductIdForResource(PriceListLine."Asset No.");
end;
FindCRMUoMIdForSalesPrice(PriceListLine."Asset Type", PriceListLine."Asset No.", PriceListLine."Unit of Measure Code", CRMUom);
until PriceListLine.Next() = 0;
end;
local procedure CheckCustPriceGroupForSync(var CRMTransactioncurrency: Record "CRM Transactioncurrency"; CustomerPriceGroup: Record "Customer Price Group")
var
SalesPrice: Record "Sales Price";
begin
SalesPrice.SetRange("Sales Type", SalesPrice."Sales Type"::"Customer Price Group");
SalesPrice.SetRange("Sales Code", CustomerPriceGroup.Code);
if SalesPrice.FindFirst() then begin
CRMTransactioncurrency.Get(CRMSynchHelper.GetCRMTransactioncurrency(SalesPrice."Currency Code"));
CheckSalesPricesForSync(CustomerPriceGroup.Code, SalesPrice."Currency Code");
end else
CRMSynchHelper.FindNAVLocalCurrencyInCRM(CRMTransactioncurrency);
end;
local procedure CheckPriceListHeaderForSync(var CRMTransactioncurrency: Record "CRM Transactioncurrency"; PriceListHeader: Record "Price List Header")
var
PriceListLine: Record "Price List Line";
begin
PriceListLine.SetRange("Price List Code", PriceListHeader.Code);
if PriceListLine.FindFirst() then begin
CRMTransactioncurrency.Get(CRMSynchHelper.GetCRMTransactioncurrency(PriceListLine."Currency Code"));
CheckPriceListLinesForSync(PriceListHeader.Code, PriceListLine."Currency Code");
end else
CRMSynchHelper.FindNAVLocalCurrencyInCRM(CRMTransactioncurrency);
end;
local procedure CheckItemOrResourceIsNotBlocked(SourceRecordRef: RecordRef)
var
SalesInvHeader: Record "Sales Invoice Header";
SalesInvLine: Record "Sales Invoice Line";
Item: Record Item;
Resource: Record Resource;
CRMIntegrationRecord: Record "CRM Integration Record";
CRMInvoiceId: Guid;
begin
SourceRecordRef.SetTable(SalesInvHeader);
// if invoice is coupled already, then skip this check
if CRMIntegrationRecord.FindIDFromRecordID(SalesInvHeader.RecordId(), CRMInvoiceId) then
exit;
SalesInvLine.SetRange("Document No.", SalesInvHeader."No.");
SalesInvLine.SetFilter(Type, '%1|%2', SalesInvLine.Type::Item, SalesInvLine.Type::Resource);
if SalesInvLine.FindSet() then
repeat
if SalesInvLine.Type = SalesInvLine.Type::Item then begin
Item.Get(SalesInvLine."No.");
Item.TestField(Blocked, false);
end else begin
Resource.Get(SalesInvLine."No.");
Resource.TestField(Blocked, false);
end;
OnCheckItemOrResourceIsNotBlockedOnAfterSalesInvLineLoop(SalesInvLine);
until SalesInvLine.Next() = 0;
end;
local procedure AddSalesOrderIdToCRMSalesorderdetail(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMSalesorderId: Guid;
begin
if not CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
exit;
SourceRecordRef.SetTable(SalesLine);
DestinationRecordRef.SetTable(CRMSalesorderdetail);
SalesHeader.Get(SalesHeader."Document Type"::Order, SalesLine."Document No.");
if not CRMIntegrationRecord.FindIDFromRecordID(SalesHeader.RecordId, CRMSalesorderId) then
Error(SalesHeaderNotCoupledErr);
CRMSalesorderdetail.SalesOrderId := CRMSalesorderId;
DestinationRecordRef.GetTable(CRMSalesorderdetail);
end;
local procedure AddDocumentNoToSalesOrderLine(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
CRMIntegrationRecord: Record "CRM Integration Record";
SalesOrderRecordId: RecordId;
begin
if not CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
exit;
SourceRecordRef.SetTable(CRMSalesorderdetail);
DestinationRecordRef.SetTable(SalesLine);
if not CRMIntegrationRecord.FindRecordIDFromID(CRMSalesorderdetail.SalesOrderId, Database::"Sales Header", SalesOrderRecordId) then
Error(SalesHeaderNotCoupledErr);
SalesHeader.Get(SalesOrderRecordId);
SalesLine."Document No." := SalesHeader."No.";
DestinationRecordRef.GetTable(SalesLine);
end;
local procedure AddWriteInProductNo(SourceFieldRef: FieldRef; var NewValue: Variant): Boolean
var
CRMSalesorderdetail: Record "CRM Salesorderdetail";
SalesReceivablesSetup: Record "Sales & Receivables Setup";
begin
SourceFieldRef.Record().SetTable(CRMSalesorderdetail);
if IsNullGuid(CRMSalesorderdetail.ProductId) then begin
SalesReceivablesSetup.Get();
if SalesReceivablesSetup."Write-in Product No." = '' then
Error(WriteInProductErr);
SalesReceivablesSetup.Validate("Write-in Product No.");
NewValue := SalesReceivablesSetup."Write-in Product No.";
exit(true);
end;
end;
local procedure AddWriteInSalesorderdetail(SourceFieldRef: FieldRef; var NewValue: Variant): Boolean
var
SalesLine: Record "Sales Line";
SalesReceivablesSetup: Record "Sales & Receivables Setup";
EmptyGuid: Guid;
begin
SourceFieldRef.Record().SetTable(SalesLine);
SalesReceivablesSetup.Get();
if SalesReceivablesSetup."Write-in Product No." = SalesLine."No." then begin
NewValue := EmptyGuid;
exit(true);
end;
end;
local procedure CreateSalesOrderNotes(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
SalesHeader: Record "Sales Header";
CRMSalesorder: Record "CRM Salesorder";
CRMAnnotation: Record "CRM Annotation";
RecordLink: Record "Record Link";
RecordLink2: Record "Record Link";
CRMAnnotationCoupling: Record "CRM Annotation Coupling";
begin
SourceRecordRef.SetTable(CRMSalesorder);
DestinationRecordRef.SetTable(SalesHeader);
RecordLink.SetRange("Record ID", SalesHeader.RecordId);
if RecordLink.FindSet() then
repeat
CRMAnnotationCoupling.SetRange("Record Link Record ID", RecordLink.RecordId);
if CRMAnnotationCoupling.FindFirst() then begin
CRMAnnotation.SetRange(AnnotationId, CRMAnnotationCoupling."CRM Annotation ID");
if CRMAnnotation.IsEmpty() then begin
CRMAnnotationCoupling.Delete();
RecordLink2.GetBySystemId(RecordLink.SystemId);
RecordLink2.Delete();
end;
end;
until RecordLink.Next() = 0;
CRMAnnotation.Reset();
CRMAnnotation.SetRange(ObjectId, CRMSalesorder.SalesOrderId);
CRMAnnotation.SetRange(IsDocument, false);
CRMAnnotation.SetRange(FileSize, 0);
if CRMAnnotation.FindSet() then
repeat
if not CRMAnnotationCoupling.FindByCRMId(CRMAnnotation.AnnotationId) then begin
CreateNote(SalesHeader, CRMAnnotation, RecordLink);
CRMAnnotationCoupling.CoupleRecordLinkToCRMAnnotation(RecordLink, CRMAnnotation);
end;
until CRMAnnotation.Next() = 0;
end;
local procedure CreateNote(SalesHeader: Record "Sales Header"; CRMAnnotation: Record "CRM Annotation"; var RecordLink: Record "Record Link")
var
CRMAnnotationCoupling: Record "CRM Annotation Coupling";
RecordLinkManagement: Codeunit "Record Link Management";
NoteInStream: InStream;
AnnotationText: Text;
begin
Clear(RecordLink);
RecordLink."Record ID" := SalesHeader.RecordId;
RecordLink.Type := RecordLink.Type::Note;
RecordLink.Description := CRMAnnotation.Subject;
CRMAnnotation.CalcFields(NoteText);
CRMAnnotation.NoteText.CreateInStream(NoteInStream, TextEncoding::UTF16);
NoteInStream.Read(AnnotationText);
AnnotationText := CRMAnnotationCoupling.ExtractNoteText(AnnotationText);
RecordLinkManagement.WriteNote(RecordLink, AnnotationText);
RecordLink.Created := CRMAnnotation.CreatedOn;
RecordLink.Company := CopyStr(CompanyName, 1, MaxStrLen(RecordLink.Company));
RecordLink.Insert();
end;
local procedure AddTypeToSalesOrderLine(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
CRMProduct: Record "CRM Product";
SalesLine: Record "Sales Line";
SalesReceivablesSetup: Record "Sales & Receivables Setup";
begin
if not CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
exit;
SourceRecordRef.SetTable(CRMSalesorderdetail);
DestinationRecordRef.SetTable(SalesLine);
if IsNullGuid(CRMSalesorderdetail.ProductId) then begin
SalesReceivablesSetup.Get();
if SalesReceivablesSetup."Write-in Product No." = '' then
Error(WriteInProductErr);
SalesReceivablesSetup.Validate("Write-in Product No.");
case SalesReceivablesSetup."Write-in Product Type" of
SalesReceivablesSetup."Write-in Product Type"::Item:
SalesLine.Type := SalesLine.Type::Item;
SalesReceivablesSetup."Write-in Product Type"::Resource:
SalesLine.Type := SalesLine.Type::Resource;
end;
end else begin
CRMProduct.Get(CRMSalesorderdetail.ProductId);
case CRMProduct.ProductTypeCode of
CRMProduct.ProductTypeCode::SalesInventory:
SalesLine.Type := SalesLine.Type::Item;
CRMProduct.ProductTypeCode::Services:
SalesLine.Type := SalesLine.Type::Resource;
end;
end;
DestinationRecordRef.GetTable(SalesLine);
end;
local procedure UpdateSalesLineUnitOfMeasure(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
ItemUnitOfMeasure: Record "Item Unit of Measure";
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
CRMSalesorderdetail: Record "CRM Salesorderdetail";
CRMProduct: Record "CRM Product";
SalesLine: Record "Sales Line";
NAVItemUomRecordId: RecordID;
NAVResourceUomRecordId: RecordID;
begin
if not CRMIntegrationManagement.IsUnitGroupMappingEnabled() then
exit;
SourceRecordRef.SetTable(CRMSalesorderdetail);
DestinationRecordRef.SetTable(SalesLine);
if IsNullGuid(CRMSalesorderdetail.ProductId) then
exit;
CRMProduct.Get(CRMSalesorderdetail.ProductId);
case CRMProduct.ProductTypeCode of
CRMProduct.ProductTypeCode::SalesInventory:
begin
if not CRMIntegrationRecord.FindRecordIDFromID(CRMSalesorderdetail.UoMId, Database::"Item Unit of Measure", NAVItemUomRecordId) then
Error(NotCoupledCRMUomErr, CRMSalesorderdetail.UoMIdName, CRMProductName.CDSServiceName());
if not ItemUnitOfMeasure.Get(NAVItemUomRecordId) then
Error(ItemUomDoesNotExistErr, CRMSalesorderdetail.UoMIdName);
SalesLine.Validate("Unit of Measure Code", ItemUnitOfMeasure.Code);
end;
CRMProduct.ProductTypeCode::Services:
begin
if not CRMIntegrationRecord.FindRecordIDFromID(CRMSalesorderdetail.UoMId, Database::"Resource Unit of Measure", NAVResourceUomRecordId) then
Error(NotCoupledCRMUomErr, CRMSalesorderdetail.UoMIdName, CRMProductName.CDSServiceName());
if not ResourceUnitOfMeasure.Get(NAVResourceUomRecordId) then
Error(ResourceUomDoesNotExistErr, CRMSalesorderdetail.UoMIdName);
SalesLine.Validate("Unit of Measure Code", ResourceUnitOfMeasure.Code);
end;
end;
DestinationRecordRef.GetTable(SalesLine);
exit(true);
end;
local procedure UpdateSalesLinePriceOverride(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef): Boolean
var
CRMSalesorderdetail: Record "CRM Salesorderdetail";
SalesLine: Record "Sales Line";
begin
SourceRecordRef.SetTable(CRMSalesorderdetail);
DestinationRecordRef.SetTable(SalesLine);
if SalesLine."Unit Price" <> CRMSalesorderdetail.PricePerUnit then
SalesLine.Validate("Unit Price", CRMSalesorderdetail.PricePerUnit);
DestinationRecordRef.GetTable(SalesLine);
end;
local procedure UpdateCRMSalesorderdetailUom(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef): Boolean
var
CRMSalesorderdetail: Record "CRM Salesorderdetail";
SalesLine: Record "Sales Line";
UnitOfMeasure: Record "Unit of Measure";
ItemUnitOfMeasure: Record "Item Unit of Measure";
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMUom: Record "CRM Uom";
CRMUomscheduleId: Guid;
CRMUomId: Guid;
begin
SourceRecordRef.SetTable(SalesLine);
DestinationRecordRef.SetTable(CRMSalesorderdetail);
if IsNullGuid(CRMSalesorderdetail.ProductId) then
exit;
if not CRMIntegrationManagement.IsUnitGroupMappingEnabled() then begin
UnitOfMeasure.Get(SalesLine."Unit of Measure Code");
if not CRMIntegrationRecord.FindIDFromRecordID(UnitOfMeasure.RecordId, CRMUomscheduleId) then
Error(UnitOfMeasureNotCoupledErr, UnitOfMeasure.Code);
CRMUom.SetRange(UoMScheduleId, CRMUomscheduleId);
CRMUom.SetRange(Name, UnitOfMeasure.Code);
if CRMUom.FindFirst() then
CRMSalesorderdetail.UoMId := CRMUom.UoMId;
end else
case SalesLine.Type of
SalesLine.Type::Item:
begin
ItemUnitOfMeasure.Get(SalesLine."No.", SalesLine."Unit of Measure Code");
if not CRMIntegrationRecord.FindIDFromRecordID(ItemUnitOfMeasure.RecordId, CRMUomId) then
Error(ItemUnitOfMeasureNotCoupledErr, ItemUnitOfMeasure.Code);
CRMSalesorderdetail.UoMId := CRMUomId;
end;
SalesLine.Type::Resource:
begin
ResourceUnitOfMeasure.Get(SalesLine."No.", SalesLine."Unit of Measure Code");
if not CRMIntegrationRecord.FindIDFromRecordID(ResourceUnitOfMeasure.RecordId, CRMUomId) then
Error(ResourceUnitOfMeasureNotCoupledErr, ResourceUnitOfMeasure.Code);
CRMSalesorderdetail.UoMId := CRMUomId;
end;
end;
DestinationRecordRef.GetTable(CRMSalesorderdetail);
end;
local procedure UpdateCRMSalesOrderPriceList(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMSalesorder: Record "CRM Salesorder";
SalesHeader: Record "Sales Header";
CRMPricelevel: Record "CRM Pricelevel";
PriceCalculationMgt: Codeunit "Price Calculation Mgt.";
begin
SourceRecordRef.SetTable(SalesHeader);
DestinationRecordRef.SetTable(CRMSalesorder);
if not PriceCalculationMgt.IsExtendedPriceCalculationEnabled() then begin
if IsNullGuid(CRMSalesorder.PriceLevelId) then begin
if not CRMSynchHelper.FindCRMPriceListByCurrencyCode(CRMPricelevel, SalesHeader."Currency Code") then
CRMSynchHelper.CreateCRMPricelevelInCurrency(CRMPricelevel, SalesHeader."Currency Code", SalesHeader."Currency Factor");
CRMSalesorder.PriceLevelId := CRMPricelevel.PriceLevelId;
DestinationRecordRef.GetTable(CRMSalesorder);
end;
CRMSynchHelper.UpdateCRMPriceList(SalesHeader, CRMSalesorder.PriceLevelId);
end else
if IsNullGuid(CRMSalesorder.PriceLevelId) then begin
CRMPricelevel.SetRange(Name, StrSubstNo(OrderPriceListLbl, SalesHeader."No."));
if not CRMPricelevel.FindFirst() then
CRMSynchHelper.CreateCRMPriceList(SalesHeader, CRMPricelevel)
else
CRMSynchHelper.UpdateCRMPriceList(SalesHeader, CRMPricelevel.PriceLevelId);
CRMSalesorder.PriceLevelId := CRMPricelevel.PriceLevelId;
DestinationRecordRef.GetTable(CRMSalesorder);
end else
CRMSynchHelper.UpdateCRMPriceList(SalesHeader, CRMSalesorder.PriceLevelId);
end;
local procedure SetCRMOrderName(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMSalesorder: Record "CRM Salesorder";
SalesHeader: Record "Sales Header";
begin
SourceRecordRef.SetTable(SalesHeader);
DestinationRecordRef.SetTable(CRMSalesorder);
CRMSalesorder.Name := SalesHeader."Sell-to Customer Name";
DestinationRecordRef.GetTable(CRMSalesorder);
end;
local procedure SetDocOccurenceNumber(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMSalesorder: Record "CRM Salesorder";
SalesHeader: Record "Sales Header";
begin
SourceRecordRef.SetTable(SalesHeader);
DestinationRecordRef.SetTable(CRMSalesorder);
SetDocOccurenceNumber(CRMSalesorder, SalesHeader);
DestinationRecordRef.GetTable(CRMSalesorder);
end;
procedure SetDocOccurenceNumber(var CRMSalesorder: Record "CRM Salesorder"; var SalesHeader: Record "Sales Header")
var
SalesHeaderArchive: Record "Sales Header Archive";
begin
SalesHeaderArchive.SetRange("Document Type", SalesHeaderArchive."Document Type"::Order);
SalesHeaderArchive.SetRange("No.", SalesHeader."No.");
SalesHeaderArchive.SetCurrentKey("Doc. No. Occurrence");
SalesHeaderArchive.SetAscending("Doc. No. Occurrence", true);
if SalesHeaderArchive.FindLast() then
if SalesHeaderArchive."Document Date" = SalesHeader."Document Date" then
CRMSalesorder.BusinessCentralDocumentOccurrenceNumber := SalesHeaderArchive."Doc. No. Occurrence"
else
CRMSalesorder.BusinessCentralDocumentOccurrenceNumber := SalesHeaderArchive."Doc. No. Occurrence" + 1
else
CRMSalesorder.BusinessCentralDocumentOccurrenceNumber := 1;
end;
local procedure SetOrderNumberAndDocOccurenceNumber(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMSalesorder: Record "CRM Salesorder";
ChangedCRMSalesorder: Record "CRM Salesorder";
SalesHeader: Record "Sales Header";
begin
SourceRecordRef.SetTable(CRMSalesorder);
DestinationRecordRef.SetTable(SalesHeader);
ChangedCRMSalesorder.Get(CRMSalesorder.SalesOrderId);
SetDocOccurenceNumber(ChangedCRMSalesorder, SalesHeader);
ChangedCRMSalesorder.BusinessCentralOrderNumber := SalesHeader."No.";
ChangedCRMSalesorder.Modify();
end;
local procedure SetWriteInProduct(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
CRMSalesorderdetail: Record "CRM Salesorderdetail";
SalesLine: Record "Sales Line";
SalesReceivablesSetup: Record "Sales & Receivables Setup";
begin
SourceRecordRef.SetTable(SalesLine);
DestinationRecordRef.SetTable(CRMSalesorderdetail);
if IsNullGuid(CRMSalesorderdetail.ProductId) then begin
SalesReceivablesSetup.Get();
CRMSalesorderdetail.IsProductOverridden := true;
CRMSalesorderdetail.ProductDescription := SalesLine.Description;
end;
DestinationRecordRef.GetTable(CRMSalesorderdetail);
end;
local procedure UpdateSalesOrderQuoteNo(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
var
CRMSalesorder: Record "CRM Salesorder";
CRMQuote: Record "CRM Quote";
SalesHeader: Record "Sales Header";
QuoteSalesHeader: Record "Sales Header";
ArchiveManagement: Codeunit ArchiveManagement;
begin
SourceRecordRef.SetTable(CRMSalesorder);
DestinationRecordRef.SetTable(SalesHeader);
if not IsNullGuid(CRMSalesorder.QuoteId) then
if CRMQuote.Get(CRMSalesOrder.QuoteId) then begin
QuoteSalesHeader.SetRange("Your Reference", CRMQuote.QuoteNumber);
OnBeforeFindQuoteSalesHeader(QuoteSalesHeader);
if QuoteSalesHeader.FindLast() then begin
SalesHeader."Quote No." := QuoteSalesHeader."No.";
ArchiveManagement.ArchSalesDocumentNoConfirm(QuoteSalesHeader);
OnAfterArchSalesDocumentNoConfirm(QuoteSalesHeader);
end;
end;
DestinationRecordRef.GetTable(SalesHeader);
end;
local procedure FindNewValueForCoupledRecordPK(IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant) IsValueFound: Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
RecID: RecordID;
CRMID: Guid;
begin
OnFindNewValueForCoupledRecordPK(IntegrationTableMapping, SourceFieldRef, DestinationFieldRef, NewValue, IsValueFound);
if IsValueFound then
exit(true);
if CRMSynchHelper.FindNewValueForSpecialMapping(SourceFieldRef, NewValue) then
exit(true);
case IntegrationTableMapping.Direction of
IntegrationTableMapping.Direction::ToIntegrationTable:
if Format(SourceFieldRef.Value()) = '' then begin
NewValue := CRMID; // Blank GUID
IsValueFound := true;
end else begin
if CRMSynchHelper.FindRecordIDByPK(IntegrationTableMapping."Table ID", SourceFieldRef.Value(), RecID) then
if CRMIntegrationRecord.FindIDFromRecordID(RecID, NewValue) then
exit(true);
if CRMSynchHelper.IsClearValueOnFailedSync(SourceFieldRef, DestinationFieldRef) then begin
NewValue := CRMID;
exit(true);
end;
Error(RecordMustBeCoupledErr, SourceFieldRef.Caption(), SourceFieldRef.Value(), CRMProductName.CDSServiceName());
end;
IntegrationTableMapping.Direction::FromIntegrationTable:
begin
CRMID := SourceFieldRef.Value();
if IsNullGuid(CRMID) then begin
NewValue := '';
IsValueFound := true;
end else begin
if CRMIntegrationRecord.FindRecordIDFromID(CRMID, IntegrationTableMapping."Table ID", RecID) then
if CRMSynchHelper.FindPKByRecordID(RecID, NewValue) then
exit(true);
if CRMSynchHelper.IsClearValueOnFailedSync(DestinationFieldRef, SourceFieldRef) then begin
NewValue := '';
exit(true);
end;
Error(RecordMustBeCoupledErr, SourceFieldRef.Caption(), CRMID, PRODUCTNAME.Short());
end;
end;
end;
end;
local procedure AutoReserveSalesLine(var DestinationRecordRef: RecordRef)
var
SalesLine: Record "Sales Line";
begin
DestinationRecordRef.SetTable(SalesLine);
if (SalesLine.Type = SalesLine.Type::Item) and (SalesLine.Reserve = SalesLine.Reserve::Always) then
SalesLine.AutoReserve();
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnIsCRMIntegrationRecord', '', false, false)]
local procedure HandleOnIsCRMIntegrationRecord(TableID: Integer; var isIntegrationRecord: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if TableID = Database::"Sales Header Archive" then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
isIntegrationRecord := true;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnIsIntegrationRecordChild', '', false, false)]
local procedure HandleOnIsIntegrationRecordChild(TableId: Integer; var Handled: Boolean; var ReturnValue: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if not CRMConnectionSetup.IsEnabled() then
exit;
if TableId = Database::"Sales Line" then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
Handled := true;
ReturnValue := false;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Int. Table Couple", 'OnBeforeSetMatchingFilter', '', false, false)]
local procedure HandleOnBeforeSetMatchingFilter(var IntegrationRecordRef: RecordRef; var MatchingIntegrationRecordFieldRef: FieldRef; var LocalRecordRef: RecordRef; var MatchingLocalFieldRef: FieldRef; var SetMatchingFilterHandled: Boolean)
var
UnitGroup: Record "Unit Group";
Item: Record Item;
Resource: Record Resource;
ItemUnitOfMeasure: Record "Item Unit of Measure";
ResourceUnitOfMeasure: Record "Resource Unit of Measure";
CRMUomschedule: Record "CRM Uomschedule";
CRMUom: Record "CRM Uom";
AdditionalFieldRef: FieldRef;
begin
if (IntegrationRecordRef.Number = Database::"CRM Uomschedule") and (LocalRecordRef.Number = Database::"Unit Group") then
if (MatchingIntegrationRecordFieldRef.Number = CRMUomschedule.FieldNo(Name)) and (MatchingLocalFieldRef.Number = UnitGroup.FieldNo("Source No.")) then begin
LocalRecordRef.SetTable(UnitGroup);
MatchingIntegrationRecordFieldRef.SetRange(UnitGroup.GetCode());
SetMatchingFilterHandled := true;
end;
if (IntegrationRecordRef.Number = Database::"CRM Uom") and ((LocalRecordRef.Number = Database::"Item Unit of Measure") or (LocalRecordRef.Number = Database::"Resource Unit of Measure")) then
if (MatchingIntegrationRecordFieldRef.Number = CRMUom.FieldNo(Name)) and ((MatchingLocalFieldRef.Number = ItemUnitOfMeasure.FieldNo("Code")) or (MatchingLocalFieldRef.Number = ResourceUnitOfMeasure.FieldNo("Code"))) then begin
if LocalRecordRef.Number = Database::"Item Unit of Measure" then begin
LocalRecordRef.SetTable(ItemUnitOfMeasure);
if Item.Get(ItemUnitOfMeasure."Item No.") then
UnitGroup.Get(UnitGroup."Source Type"::Item, Item.SystemId);
end;
if LocalRecordRef.Number = Database::"Resource Unit of Measure" then begin
LocalRecordRef.SetTable(ResourceUnitOfMeasure);
if Resource.Get(ResourceUnitOfMeasure."Resource No.") then
UnitGroup.Get(UnitGroup."Source Type"::Resource, Resource.SystemId);
end;
CRMUomschedule.SetRange(Name, UnitGroup.GetCode());
if CRMUomschedule.FindFirst() then begin
AdditionalFieldRef := IntegrationRecordRef.Field(CRMUom.FieldNo(UoMScheduleId));
AdditionalFieldRef.SetRange(CRMUomschedule.UoMScheduleId);
end;
end;
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeUpdateCRMInvoiceBeforeInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnCheckItemOrResourceIsNotBlockedOnAfterSalesInvLineLoop(SalesInvLine: Record "Sales Invoice Line")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnFindNewValueForCoupledRecordPK(IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnUpdateCRMProductAfterTransferRecordFieldsOnAfterCalcItemBlocked(SourceRecordRef: RecordRef; var Blocked: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeGetSourceDestCodeOnAfterTransferRecordFields(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var AdditionalFieldsWereModified: Boolean; DestinationIsInserted: Boolean; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnHandleOnBeforeIgnoreUnchangedRecordHandled(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnChangeSalesOrderStatusOnBeforeCompareStatus(var SalesHeader: Record "Sales Header"; var NewSalesDocumentStatus: Enum "Sales Document Status")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnUpdateCRMInvoiceBeforeInsertRecordOnBeforeDestinationRecordRefGetTable(var CRMInvoice: Record "CRM Invoice"; SalesInvoiceHeader: Record "Sales Invoice Header")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnApplySalesLineTaxOnBeforeSetTax(var CRMSalesorderdetail: Record "CRM Salesorderdetail"; var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterArchSalesDocumentNoConfirm(var QuoteSalesHeader: Record "Sales Header")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeFindQuoteSalesHeader(var QuoteSalesHeader: Record "Sales Header")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterHandlePostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
begin
end;
}