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..15f68a31e97 100644
--- a/src/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al
+++ b/src/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al
@@ -284,7 +284,7 @@ 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";
@@ -358,6 +358,35 @@ codeunit 6610 "FS Int. Table Subscriber"
end;
end;
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Record Synch.", 'OnBeforeIsFieldModified', '', true, false)]
+ local procedure OnBeforeIsFieldModified(var SourceFieldRef: FieldRef; var DestinationFieldRef: FieldRef; var Result: Boolean; var IsHandled: Boolean)
+ begin
+ HandleOnBeforeIsFieldModified(SourceFieldRef, DestinationFieldRef, Result, IsHandled);
+ end;
+
+ internal procedure HandleOnBeforeIsFieldModified(var SourceFieldRef: FieldRef; var DestinationFieldRef: FieldRef; var Result: Boolean; var IsHandled: Boolean)
+ var
+ FSConnectionSetup: Record "FS Connection Setup";
+ CRMProduct: Record "CRM Product";
+ ExistingCRMProduct: Record "CRM Product";
+ DestinationRecordRef: RecordRef;
+ ItemIsManaged: Boolean;
+ begin
+ if not FSConnectionSetup.IsEnabled() then
+ exit;
+
+ if not IsItemCouplingToCustomerAssetConversion(SourceFieldRef, DestinationFieldRef) then
+ exit;
+
+ ItemIsManaged := SourceFieldRef.Value();
+ DestinationRecordRef := DestinationFieldRef.Record();
+ DestinationRecordRef.SetTable(CRMProduct);
+ ExistingCRMProduct.SetLoadFields(ConvertToCustomerAsset);
+ ExistingCRMProduct.Get(CRMProduct.ProductId);
+ Result := ExistingCRMProduct.ConvertToCustomerAsset <> GetCustomerAssetConversion(ItemIsManaged);
+ IsHandled := true;
+ end;
+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Record Synch.", 'OnTransferFieldData', '', true, false)]
local procedure OnTransferFieldData(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean; var NeedsConversion: Boolean)
var
@@ -380,6 +409,7 @@ codeunit 6610 "FS Int. Table Subscriber"
QuantityToTransferToInvoice: Decimal;
QuantityCurrentlyConsumed: Decimal;
QuantityCurrentlyInvoiced: Decimal;
+ ItemIsManaged: Boolean;
NotCoupledCRMUomErr: Label 'The unit is not coupled to a unit of measure.';
begin
if not FSConnectionSetup.IsEnabled() then
@@ -392,6 +422,14 @@ codeunit 6610 "FS Int. Table Subscriber"
if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then
exit;
+ if IsItemCouplingToCustomerAssetConversion(SourceFieldRef, DestinationFieldRef) then begin
+ ItemIsManaged := SourceFieldRef.Value();
+ NewValue := GetCustomerAssetConversion(ItemIsManaged);
+ IsValueFound := true;
+ NeedsConversion := false;
+ exit;
+ end;
+
if (SourceFieldRef.Record().Number = Database::"Service Header") and
(DestinationFieldRef.Record().Number = Database::"FS Work Order") then
case DestinationFieldRef.Name() of
@@ -672,6 +710,23 @@ codeunit 6610 "FS Int. Table Subscriber"
exit(MaxQuantity);
end;
+ local procedure IsItemCouplingToCustomerAssetConversion(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef): Boolean
+ var
+ Item: Record Item;
+ CRMProduct: Record "CRM Product";
+ begin
+ exit(
+ (SourceFieldRef.Record().Number() = Database::Item) and
+ (SourceFieldRef.Number() = Item.FieldNo("Coupled to Dataverse")) and
+ (DestinationFieldRef.Record().Number() = Database::"CRM Product") and
+ (DestinationFieldRef.Number() = CRMProduct.FieldNo(ConvertToCustomerAsset)));
+ end;
+
+ internal procedure GetCustomerAssetConversion(ItemIsManaged: Boolean): Boolean
+ begin
+ exit(not ItemIsManaged);
+ end;
+
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Int. Table. Subscriber", 'OnFindNewValueForCoupledRecordPK', '', true, false)]
local procedure OnFindNewValueForCoupledRecordPK(IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean)
var
@@ -1444,7 +1499,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 +1516,14 @@ codeunit 6610 "FS Int. Table Subscriber"
CRMProduct.FieldNo(FieldServiceProductType),
IntegrationFieldMapping.Direction::ToIntegrationTable,
'', false, false);
+
+ // Coupled Business Central items are managed by Field Service customer assets.
+ Sender.InsertIntegrationFieldMapping(
+ IntegrationTableMappingName,
+ Item.FieldNo("Coupled to Dataverse"),
+ CRMProduct.FieldNo(ConvertToCustomerAsset),
+ IntegrationFieldMapping.Direction::ToIntegrationTable,
+ '', false, false);
end;
local procedure UpdateCorrelatedJobJournalLine(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
@@ -2518,8 +2581,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 +2746,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 +2781,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)
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..1a7e247c0a1 100644
--- a/src/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al
+++ b/src/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al
@@ -58,6 +58,20 @@ codeunit 139205 "FS Integration Test Library"
FSIntTableSubscriber.UpdateQuantities(FSBookableResourceBooking, ServiceLine);
end;
+ procedure GetCustomerAssetConversion(ItemIsManaged: Boolean): Boolean
+ var
+ FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
+ begin
+ exit(FSIntTableSubscriber.GetCustomerAssetConversion(ItemIsManaged));
+ end;
+
+ procedure HandleOnBeforeIsFieldModified(var SourceFieldRef: FieldRef; var DestinationFieldRef: FieldRef; var Result: Boolean; var IsHandled: Boolean)
+ var
+ FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
+ begin
+ FSIntTableSubscriber.HandleOnBeforeIsFieldModified(SourceFieldRef, DestinationFieldRef, Result, IsHandled);
+ end;
+
procedure IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
@@ -79,12 +93,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..3c70255f726 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,107 @@ codeunit 139204 "FS Integration Test"
end;
[Test]
- procedure IgnoreServiceItemWhenConvertToCustomerAssetIsFalse()
+ [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;
+ IntegrationTableMapping: Record "Integration Table Mapping";
+ CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
+ CRMSetupDefaults: Codeunit "CRM Setup Defaults";
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] Synchronizing an item disables native Field Service customer asset creation.
Initialize();
+ LibraryCRMIntegration.CreateCRMConnectionSetup('', '@@test@@', true);
InitSetup(true, '');
- Item.Get(CreateItem());
- TempServiceItem."Item No." := Item."No.";
- RecordRef.GetTable(TempServiceItem);
-
- ProductId := CreateGuid();
- CRMProduct.ProductId := ProductId;
- CRMProduct.ConvertToCustomerAsset := false;
- CRMProduct.Insert(false);
+ // [GIVEN] A coupled item and product where Convert to Customer Asset is Yes.
+ CRMSetupDefaults.ResetItemProductMapping('ITEM-PRODUCT', false);
+ LibraryCRMIntegration.CreateCoupledItemAndProduct(Item, CRMProduct);
+ CRMProduct.ConvertToCustomerAsset := true;
+ CRMProduct.Modify();
- CRMIntegrationRecord.CoupleCRMIDToRecordID(ProductId, Item.RecordId());
+ IntegrationTableMapping.Get('ITEM-PRODUCT');
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
+ // [WHEN] The item is synchronized to the Field Service product.
+ CRMIntegrationTableSynch.SynchRecord(IntegrationTableMapping, Item.RecordId(), true, false);
- Assert.IsTrue(IgnoreRecord, 'Service Item should be ignored when Convert to Customer Asset is false.');
+ // [THEN] Native Field Service customer asset creation is disabled.
+ CRMProduct.Get(CRMProduct.ProductId);
+ Assert.IsFalse(CRMProduct.ConvertToCustomerAsset, 'Convert to Customer Asset should be disabled.');
end;
[Test]
- procedure DoNotIgnoreServiceItemWhenConvertToCustomerAssetIsTrue()
- var
- Item: Record Item;
- TempServiceItem: Record "Service Item" temporary;
- CRMProduct: Record "CRM Product";
- CRMIntegrationRecord: Record "CRM Integration Record";
- RecordRef: RecordRef;
- IgnoreRecord: Boolean;
- ProductId: Guid;
+ [TransactionModel(TransactionModel::AutoRollback)]
+ procedure CustomerAssetConversionReflectsItemManagement()
begin
- // [FEATURE] [Service Item Mapping]
- // [SCENARIO] Service Item is not skipped when linked CRM Product has Convert to Customer Asset = Yes.
- Initialize();
- InitSetup(true, '');
-
- Item.Get(CreateItem());
- TempServiceItem."Item No." := Item."No.";
- RecordRef.GetTable(TempServiceItem);
+ // [FEATURE] [Item-Product Mapping]
+ // [SCENARIO] Customer asset conversion is disabled only for items managed through synchronization.
- ProductId := CreateGuid();
- CRMProduct.ProductId := ProductId;
- CRMProduct.ConvertToCustomerAsset := true;
- CRMProduct.Insert(false);
-
- CRMIntegrationRecord.CoupleCRMIDToRecordID(ProductId, Item.RecordId());
+ // [WHEN] An item is not managed through synchronization.
+ // [THEN] Native Field Service customer asset conversion remains enabled.
+ Assert.IsTrue(FSIntegrationTestLibrary.GetCustomerAssetConversion(false), 'An unmanaged item should allow customer asset conversion.');
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
-
- Assert.IsFalse(IgnoreRecord, 'Service Item should not be ignored when Convert to Customer Asset is true.');
+ // [WHEN] An item is managed through synchronization.
+ // [THEN] Native Field Service customer asset conversion is disabled.
+ Assert.IsFalse(FSIntegrationTestLibrary.GetCustomerAssetConversion(true), 'A managed item should disable customer asset conversion.');
end;
[Test]
- procedure DoNotIgnoreServiceItemWhenItemNoIsBlank()
+ [TransactionModel(TransactionModel::AutoRollback)]
+ procedure ItemProductComparisonIgnoredWhenFieldServiceIsDisabled()
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, '');
+ Item: Record Item;
+ CRMProduct: Record "CRM Product";
+ SourceRecordRef: RecordRef;
+ DestinationRecordRef: RecordRef;
+ SourceFieldRef: FieldRef;
+ DestinationFieldRef: FieldRef;
+ Result: Boolean;
+ IsHandled: Boolean;
+ begin
+ // [FEATURE] [Item-Product Mapping]
+ // [SCENARIO] Field Service does not handle synchronization comparisons while its integration is disabled.
+ ResetFSEnvironment();
- TempServiceItem."Item No." := '';
- RecordRef.GetTable(TempServiceItem);
+ // [GIVEN] The fields used to derive customer asset conversion and no Field Service connection setup.
+ SourceRecordRef.GetTable(Item);
+ SourceFieldRef := SourceRecordRef.Field(Item.FieldNo("Coupled to Dataverse"));
+ DestinationRecordRef.GetTable(CRMProduct);
+ DestinationFieldRef := DestinationRecordRef.Field(CRMProduct.FieldNo(ConvertToCustomerAsset));
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
+ // [WHEN] The synchronization engine compares those fields.
+ FSIntegrationTestLibrary.HandleOnBeforeIsFieldModified(SourceFieldRef, DestinationFieldRef, Result, IsHandled);
- Assert.IsFalse(IgnoreRecord, 'Service Item with blank Item No. should not be ignored by this filter.');
+ // [THEN] Field Service leaves the comparison to the synchronization engine.
+ Assert.IsFalse(IsHandled, 'Field Service should not handle synchronization comparisons while its integration is 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 derives native Field Service customer asset creation from item management.
Initialize();
InitSetup(true, '');
- Item.Get(CreateItem());
- TempServiceItem."Item No." := Item."No.";
- RecordRef.GetTable(TempServiceItem);
-
- FSIntegrationTestLibrary.IgnoreServiceItemsByConvertToCustomerAssetFlag(RecordRef, IgnoreRecord);
+ // [WHEN] The default item-product mapping is reset.
+ CRMSetupDefaults.ResetItemProductMapping('ITEM-PRODUCT', false);
- Assert.IsFalse(IgnoreRecord, 'Service Item with uncoupled Item should not be ignored by this filter.');
+ // [THEN] Convert to Customer Asset is mapped from the item's Dataverse coupling state 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(Item.FieldNo("Coupled to Dataverse"), IntegrationFieldMapping."Field No.", 'The mapping should use the item coupling state.');
+ Assert.AreEqual(IntegrationFieldMapping.Direction::ToIntegrationTable, IntegrationFieldMapping.Direction, 'The mapping should be outbound.');
+ Assert.AreEqual('', IntegrationFieldMapping."Constant Value", 'The mapping should not use a constant value.');
end;
local procedure Initialize()