diff --git a/src/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/src/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al
index 60d8d99d3dd..9144d360dcb 100644
--- a/src/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al
+++ b/src/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al
@@ -284,12 +284,14 @@ codeunit 6610 "FS Int. Table Subscriber"
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterTransferRecordFields', '', true, false)]
- local procedure OnAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var AdditionalFieldsWereModified: Boolean)
+ local procedure OnAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var AdditionalFieldsWereModified: Boolean; DestinationIsInserted: Boolean)
var
FSConnectionSetup: Record "FS Connection Setup";
FSWorkOrderProduct: Record "FS Work Order Product";
FSWorkOrderService: Record "FS Work Order Service";
FSBookableResourceBooking: Record "FS Bookable Resource Booking";
+ CRMProduct: Record "CRM Product";
+ ExistingCRMProduct: Record "CRM Product";
ServiceLine: Record "Service Line";
SourceDestCode: Text;
begin
@@ -299,6 +301,17 @@ codeunit 6610 "FS Int. Table Subscriber"
SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef);
case SourceDestCode of
+ 'Item-CRM Product':
+ begin
+ if not DestinationIsInserted then
+ exit;
+
+ DestinationRecordRef.SetTable(CRMProduct);
+ ExistingCRMProduct.SetLoadFields(ConvertToCustomerAsset);
+ ExistingCRMProduct.Get(CRMProduct.ProductId);
+ if ExistingCRMProduct.ConvertToCustomerAsset then
+ AdditionalFieldsWereModified := true;
+ end;
'FS Work Order Product-Service Line':
begin
SourceRecordRef.SetTable(FSWorkOrderProduct);
@@ -1383,6 +1396,8 @@ codeunit 6610 "FS Int. Table Subscriber"
SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef);
case SourceDestCode of
+ 'Item-CRM Product':
+ DisableCustomerAssetConversion(DestinationRecordRef);
'FS Work Order Service-Job Journal Line':
UpdateCorrelatedJobJournalLine(SourceRecordRef, DestinationRecordRef);
'Service Header-FS Work Order':
@@ -1444,7 +1459,7 @@ codeunit 6610 "FS Int. Table Subscriber"
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnResetItemProductMappingOnAfterInsertFieldsMapping', '', false, false)]
- local procedure AddFieldServiceProductTypeFieldMapping(var Sender: Codeunit "CRM Setup Defaults"; IntegrationTableMappingName: Code[20])
+ local procedure AddFieldServiceProductMappings(var Sender: Codeunit "CRM Setup Defaults"; IntegrationTableMappingName: Code[20])
var
FSConnectionSetup: Record "FS Connection Setup";
Item: Record Item;
@@ -1461,6 +1476,14 @@ codeunit 6610 "FS Int. Table Subscriber"
CRMProduct.FieldNo(FieldServiceProductType),
IntegrationFieldMapping.Direction::ToIntegrationTable,
'', false, false);
+
+ // Business Central service items are the source for Field Service customer assets.
+ Sender.InsertIntegrationFieldMapping(
+ IntegrationTableMappingName,
+ 0,
+ CRMProduct.FieldNo(ConvertToCustomerAsset),
+ IntegrationFieldMapping.Direction::ToIntegrationTable,
+ 'false', false, false);
end;
local procedure UpdateCorrelatedJobJournalLine(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
@@ -1627,7 +1650,7 @@ codeunit 6610 "FS Int. Table Subscriber"
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeInsertRecord', '', true, false)]
- local procedure HandleOnBeforeInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef)
+ local procedure HandleOnBeforeInsertRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
var
FSProjectTask: Record "FS Project Task";
Customer: Record Customer;
@@ -1662,6 +1685,8 @@ codeunit 6610 "FS Int. Table Subscriber"
SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef);
case SourceDestCode of
+ 'Item-CRM Product':
+ DisableCustomerAssetConversion(DestinationRecordRef);
'Location-FS Warehouse':
SetCompanyId(DestinationRecordRef);
'Service Item-FS Customer Asset':
@@ -2518,8 +2543,6 @@ codeunit 6610 "FS Int. Table Subscriber"
IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
Database::"FS Work Order":
IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
- Database::"Service Item":
- IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef, IgnoreRecord);
end;
if FSConnectionSetup.IsEnabled() then
@@ -2685,6 +2708,8 @@ codeunit 6610 "FS Int. Table Subscriber"
IgnoreRecord := true;
end;
+#pragma warning disable AS0105
+ [Obsolete('Remove calls to this procedure. Service items are always synchronized to Field Service customer assets; item-product synchronization disables customer asset conversion.', '30.0')]
internal procedure IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
FSConnectionSetup: Record "FS Connection Setup";
@@ -2718,6 +2743,7 @@ codeunit 6610 "FS Int. Table Subscriber"
if not CRMProduct.ConvertToCustomerAsset then
IgnoreRecord := true;
end;
+#pragma warning restore AS0105
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Table Synch.", 'OnAfterInitSynchJob', '', true, true)]
local procedure LogTelemetryOnAfterInitSynchJob(ConnectionType: TableConnectionType; IntegrationTableID: Integer)
@@ -2780,6 +2806,15 @@ codeunit 6610 "FS Int. Table Subscriber"
exit('');
end;
+ local procedure DisableCustomerAssetConversion(var DestinationRecordRef: RecordRef)
+ var
+ CRMProduct: Record "CRM Product";
+ begin
+ DestinationRecordRef.SetTable(CRMProduct);
+ CRMProduct.ConvertToCustomerAsset := false;
+ DestinationRecordRef.GetTable(CRMProduct);
+ end;
+
[EventSubscriber(ObjectType::Table, Database::"Service Mgt. Setup", 'OnBeforeValidateEvent', 'One Service Item Line/Order', true, false)]
local procedure ServiceMgtSetupOnBeforeValidateOneServiceItemLinePerOrder(var Rec: Record "Service Mgt. Setup")
var
diff --git a/src/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al b/src/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al
index fbb03b04162..4e1517aeb99 100644
--- a/src/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al
+++ b/src/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al
@@ -79,12 +79,17 @@ codeunit 139205 "FS Integration Test Library"
FSIntTableSubscriber.IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord);
end;
+ ///
+ /// Retained for compatibility. Service items are now always synchronized to Field Service customer assets, so this procedure leaves the synchronization decision unchanged.
+ ///
+ /// A reference to the service item to evaluate.
+ /// The existing synchronization decision, which is left unchanged.
+#pragma warning disable AS0105
+ [Obsolete('Remove calls to this procedure. Service items are always synchronized to Field Service customer assets; item-product synchronization disables customer asset conversion.', '30.0')]
procedure IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
- var
- FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
begin
- FSIntTableSubscriber.IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef, IgnoreRecord);
end;
+#pragma warning restore AS0105
procedure MarkArchivedServiceOrder(ServiceHeader: Record "Service Header")
var
diff --git a/src/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al b/src/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al
index 94c69e79ff9..da32168176d 100644
--- a/src/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al
+++ b/src/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al
@@ -23,7 +23,6 @@ using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Customer;
using Microsoft.Service.Archive;
using Microsoft.Service.Document;
-using Microsoft.Service.Item;
using Microsoft.Service.Setup;
using Microsoft.Service.Test;
using Microsoft.TestLibraries.DynamicsFieldService;
@@ -1578,109 +1577,103 @@ codeunit 139204 "FS Integration Test"
end;
[Test]
- procedure IgnoreServiceItemWhenConvertToCustomerAssetIsFalse()
+ [TransactionModel(TransactionModel::AutoCommit)]
+ procedure InitialItemSynchronizationDisablesCustomerAssetConversion()
var
- Item: Record Item;
- TempServiceItem: Record "Service Item" temporary;
- CRMProduct: Record "CRM Product";
CRMIntegrationRecord: Record "CRM Integration Record";
- RecordRef: RecordRef;
- IgnoreRecord: Boolean;
- ProductId: Guid;
+ CRMProduct: Record "CRM Product";
+ CRMTransactioncurrency: Record "CRM Transactioncurrency";
+ CRMUom: Record "CRM Uom";
+ Currency: Record Currency;
+ IntegrationTableMapping: Record "Integration Table Mapping";
+ Item: Record Item;
+ CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
+ CRMSetupDefaults: Codeunit "CRM Setup Defaults";
+ JobID: Guid;
begin
- // [FEATURE] [Service Item Mapping]
- // [SCENARIO] Service Item is skipped when linked CRM Product has Convert to Customer Asset = No.
+ // [FEATURE] [Item-Product Mapping]
+ // [SCENARIO] The first synchronization of an item disables native Field Service customer asset creation.
Initialize();
- InitSetup(true, '');
-
- Item.Get(CreateItem());
- TempServiceItem."Item No." := Item."No.";
- RecordRef.GetTable(TempServiceItem);
+ InitializeItemProductSynchronization();
- ProductId := CreateGuid();
- CRMProduct.ProductId := ProductId;
- CRMProduct.ConvertToCustomerAsset := false;
- CRMProduct.Insert(false);
+ // [GIVEN] An uncoupled item whose unit of measure and currency are coupled to Dataverse.
+ CRMSetupDefaults.ResetItemProductMapping('ITEM-PRODUCT', false);
+ CreateItemWithCoupledUnitOfMeasure(Item, CRMUom);
+ LibraryCRMIntegration.CreateCoupledCurrencyAndTransactionCurrency(Currency, CRMTransactioncurrency);
+ IntegrationTableMapping.Get('ITEM-PRODUCT');
- CRMIntegrationRecord.CoupleCRMIDToRecordID(ProductId, Item.RecordId());
+ // [WHEN] The item is synchronized to a new Field Service product.
+ JobID := CRMIntegrationTableSynch.SynchRecord(IntegrationTableMapping, Item.RecordId(), true, true);
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
-
- Assert.IsTrue(IgnoreRecord, 'Service Item should be ignored when Convert to Customer Asset is false.');
+ // [THEN] Native Field Service customer asset creation is disabled on the new product.
+ VerifySuccessfulIntegrationSynchJob(JobID, 1, 0);
+ Assert.IsTrue(CRMIntegrationRecord.FindByRecordID(Item.RecordId()), 'The item should be coupled to a product.');
+ CRMProduct.Get(CRMIntegrationRecord."CRM ID");
+ Assert.IsFalse(CRMProduct.ConvertToCustomerAsset, 'Convert to Customer Asset should be disabled.');
end;
[Test]
- procedure DoNotIgnoreServiceItemWhenConvertToCustomerAssetIsTrue()
+ [TransactionModel(TransactionModel::AutoCommit)]
+ procedure ItemSynchronizationDisablesCustomerAssetConversion()
var
Item: Record Item;
- TempServiceItem: Record "Service Item" temporary;
CRMProduct: Record "CRM Product";
- CRMIntegrationRecord: Record "CRM Integration Record";
- RecordRef: RecordRef;
- IgnoreRecord: Boolean;
- ProductId: Guid;
+ CRMUom: Record "CRM Uom";
+ IntegrationFieldMapping: Record "Integration Field Mapping";
+ IntegrationTableMapping: Record "Integration Table Mapping";
+ CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
+ CRMSetupDefaults: Codeunit "CRM Setup Defaults";
+ JobID: Guid;
begin
- // [FEATURE] [Service Item Mapping]
- // [SCENARIO] Service Item is not skipped when linked CRM Product has Convert to Customer Asset = Yes.
+ // [FEATURE] [Item-Product Mapping]
+ // [SCENARIO] Synchronizing an item disables native Field Service customer asset creation.
Initialize();
- InitSetup(true, '');
-
- Item.Get(CreateItem());
- TempServiceItem."Item No." := Item."No.";
- RecordRef.GetTable(TempServiceItem);
+ InitializeItemProductSynchronization();
- ProductId := CreateGuid();
- CRMProduct.ProductId := ProductId;
+ // [GIVEN] A coupled item and product where Convert to Customer Asset is Yes and the existing mapping has no conversion rule.
+ CRMSetupDefaults.ResetItemProductMapping('ITEM-PRODUCT', false);
+ CreateItemWithCoupledUnitOfMeasure(Item, CRMUom);
+ LibraryCRMIntegration.CoupleItem(Item, CRMUom, CRMProduct);
CRMProduct.ConvertToCustomerAsset := true;
- CRMProduct.Insert(false);
-
- CRMIntegrationRecord.CoupleCRMIDToRecordID(ProductId, Item.RecordId());
-
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
-
- Assert.IsFalse(IgnoreRecord, 'Service Item should not be ignored when Convert to Customer Asset is true.');
- end;
-
- [Test]
- procedure DoNotIgnoreServiceItemWhenItemNoIsBlank()
- var
- TempServiceItem: Record "Service Item" temporary;
- RecordRef: RecordRef;
- IgnoreRecord: Boolean;
- begin
- // [FEATURE] [Service Item Mapping]
- // [SCENARIO] Service Item with blank Item No. is not skipped by this filter.
- Initialize();
- InitSetup(true, '');
+ CRMProduct.Modify();
- TempServiceItem."Item No." := '';
- RecordRef.GetTable(TempServiceItem);
+ IntegrationFieldMapping.SetRange("Integration Table Mapping Name", 'ITEM-PRODUCT');
+ IntegrationFieldMapping.SetRange("Integration Table Field No.", CRMProduct.FieldNo(ConvertToCustomerAsset));
+ IntegrationFieldMapping.DeleteAll();
+ IntegrationTableMapping.Get('ITEM-PRODUCT');
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
+ // [WHEN] The item is synchronized to the Field Service product.
+ JobID := CRMIntegrationTableSynch.SynchRecord(IntegrationTableMapping, Item.RecordId(), true, false);
- Assert.IsFalse(IgnoreRecord, 'Service Item with blank Item No. should not be ignored by this filter.');
+ // [THEN] Native Field Service customer asset creation is disabled.
+ VerifySuccessfulIntegrationSynchJob(JobID, 0, 1);
+ CRMProduct.Get(CRMProduct.ProductId);
+ Assert.IsFalse(CRMProduct.ConvertToCustomerAsset, 'Convert to Customer Asset should be disabled.');
end;
[Test]
- procedure DoNotIgnoreServiceItemWhenItemIsNotCoupled()
+ [TransactionModel(TransactionModel::AutoCommit)]
+ procedure ItemProductMappingDisablesCustomerAssetConversion()
var
- Item: Record Item;
- TempServiceItem: Record "Service Item" temporary;
- RecordRef: RecordRef;
- IgnoreRecord: Boolean;
+ CRMProduct: Record "CRM Product";
+ IntegrationFieldMapping: Record "Integration Field Mapping";
+ CRMSetupDefaults: Codeunit "CRM Setup Defaults";
begin
- // [FEATURE] [Service Item Mapping]
- // [SCENARIO] Service Item with uncoupled Item is not skipped by this filter.
+ // [FEATURE] [Item-Product Mapping]
+ // [SCENARIO] The item-product mapping always disables native Field Service customer asset creation.
Initialize();
InitSetup(true, '');
- Item.Get(CreateItem());
- TempServiceItem."Item No." := Item."No.";
- RecordRef.GetTable(TempServiceItem);
+ // [WHEN] The default item-product mapping is reset.
+ CRMSetupDefaults.ResetItemProductMapping('ITEM-PRODUCT', false);
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
-
- Assert.IsFalse(IgnoreRecord, 'Service Item with uncoupled Item should not be ignored by this filter.');
+ // [THEN] Convert to Customer Asset is mapped to constant false in the outbound direction.
+ IntegrationFieldMapping.SetRange("Integration Table Mapping Name", 'ITEM-PRODUCT');
+ IntegrationFieldMapping.SetRange("Integration Table Field No.", CRMProduct.FieldNo(ConvertToCustomerAsset));
+ Assert.IsTrue(IntegrationFieldMapping.FindFirst(), 'The Convert to Customer Asset mapping should exist.');
+ Assert.AreEqual(0, IntegrationFieldMapping."Field No.", 'The mapping should use a constant value.');
+ Assert.AreEqual(IntegrationFieldMapping.Direction::ToIntegrationTable, IntegrationFieldMapping.Direction, 'The mapping should be outbound.');
+ Assert.AreEqual('false', IntegrationFieldMapping."Constant Value", 'The mapping should disable Convert to Customer Asset.');
end;
local procedure Initialize()
@@ -1712,6 +1705,51 @@ codeunit 139204 "FS Integration Test"
SetTenantLicenseStateToTrial();
end;
+ local procedure VerifySuccessfulIntegrationSynchJob(JobID: Guid; ExpectedInserted: Integer; ExpectedModified: Integer)
+ var
+ IntegrationSynchJob: Record "Integration Synch. Job";
+ IntegrationSynchJobErrors: Record "Integration Synch. Job Errors";
+ begin
+ IntegrationSynchJob.Get(JobID);
+ IntegrationSynchJobErrors.SetRange("Integration Synch. Job ID", JobID);
+ if IntegrationSynchJobErrors.FindFirst() then
+ Assert.Fail('Unexpected synchronization error: ' + IntegrationSynchJobErrors.Message);
+ IntegrationSynchJob.TestField(Failed, 0);
+ IntegrationSynchJob.TestField(Inserted, ExpectedInserted);
+ IntegrationSynchJob.TestField(Modified, ExpectedModified);
+ end;
+
+ local procedure CreateItemWithCoupledUnitOfMeasure(var Item: Record Item; var CRMUom: Record "CRM Uom")
+ var
+ ItemUnitOfMeasure: Record "Item Unit of Measure";
+ UnitGroup: Record "Unit Group";
+ CRMUomschedule: Record "CRM Uomschedule";
+ begin
+ LibraryCRMIntegration.CreateCoupledItemUnitGroupAndUomSchedule(UnitGroup, CRMUomschedule);
+ Item.GetBySystemId(UnitGroup."Source Id");
+ ItemUnitOfMeasure.Get(Item."No.", Item."Base Unit of Measure");
+ LibraryCRMIntegration.CoupleItemUnitOfMeasure(ItemUnitOfMeasure, CRMUomschedule, CRMUom);
+ end;
+
+ local procedure InitializeItemProductSynchronization()
+ var
+ CDSCompany: Record "CDS Company";
+ CDSConnectionSetup: Record "CDS Connection Setup";
+ CRMConnectionSetup: Record "CRM Connection Setup";
+ begin
+ InitSetup(true, '');
+ LibraryCRMIntegration.ConfigureCRM();
+ LibraryCRMIntegration.CreateCRMOrganization();
+ CRMConnectionSetup.Get();
+ CRMConnectionSetup.Validate("Is Enabled", false);
+ CRMConnectionSetup.Modify(true);
+ CRMConnectionSetup.Validate("Is Enabled", true);
+ CRMConnectionSetup.Modify(true);
+ CDSConnectionSetup.Get();
+ CDSConnectionSetup.SetBaseCurrencyData();
+ LibraryCRMIntegration.EnsureCDSCompany(CDSCompany);
+ end;
+
procedure ResetFSEnvironment()
var
FSConnectionSetup: Record "FS Connection Setup";
@@ -2018,4 +2056,3 @@ codeunit 139204 "FS Integration Test"
InventorySetup.Modify(true);
end;
}
-