[FS] Prevent duplicate Field Service customer assets during item synchronization - #11094
[FS] Prevent duplicate Field Service customer assets during item synchronization#11094tomasevicst wants to merge 19 commits into
Conversation
|
The removed IgnoreServiceItem* tests, and the new ItemSynchronizationDisablesCustomerAssetConversion test, both call the helper procedure directly rather than exercising the actual dispatch path. Critically, the new 'Item-CRM Product' case added to OnBeforeTransferFieldData (which does DestinationRecordRef.SetTable(CRMProduct) / DisableCustomerAssetConversion(...) / DestinationRecordRef.GetTable(CRMProduct)) is never exercised by any test — no test drives an Item-to-CRMProduct transfer through the RecordRef dispatch to confirm the case is reached and the record is written back correctly. Add an integration-style test that triggers the Item-CRM Product transfer path (e.g. via the subscriber's OnBeforeTransferFieldData or a full sync call) and asserts the resulting CRM Product has ConvertToCustomerAsset = false. Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.38.6 |
…ronization logic for service items
…procedure to streamline service item synchronization
…re and update test cases for service item synchronization
…ate_Field_Service_Customer_Asset_Created
… for clarity and compatibility
… to clarify synchronization behavior and conditionally compile based on CLEAN30
| /// </summary> | ||
| /// <param name="SourceRecordRef">A reference to the service item to evaluate.</param> | ||
| /// <param name="IgnoreRecord">The existing synchronization decision, which is left unchanged.</param> | ||
| [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')] |
There was a problem hiding this comment.
The public test-library procedure IgnoreServiceItemsByConvertToCustomerAssetFlag is newly marked [Obsolete(...,'30.0')], but its body was simultaneously replaced with a no-op instead of continuing to delegate to the prior logic. Any dependent app or test still calling this procedure during the deprecation window (before 30.0 removal) will silently stop having IgnoreRecord evaluated/set, changing observable runtime behavior while the symbol still compiles. Staged-obsoletion guidance expects the procedure to keep working during the deprecation window and only become an empty/removed stub at or after the tagged removal version.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
procedure IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
begin
FSIntTableSubscriber.IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef, IgnoreRecord);
end;Knowledge:
- microsoft/knowledge/upgrade/obsolete-pending-to-removed-staging.md
- microsoft/knowledge/breaking-changes/deprecate-public-members-with-the-obsolete-lifecycle.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.39.6
…onization procedures
| /// <param name="SourceRecordRef">A reference to the service item to evaluate.</param> | ||
| /// <param name="IgnoreRecord">The existing synchronization decision, which is left unchanged.</param> | ||
| #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')] |
There was a problem hiding this comment.
The public procedure IgnoreServiceItemsByConvertToCustomerAssetFlag on the FS Integration Test Library codeunit was kept and marked [Obsolete(...,'30.0')], but its implementation was emptied instead of preserved. Emptying the body changes the procedure's observable behavior (IgnoreRecord is now always left unchanged) immediately for any remaining caller, rather than preserving prior behavior through the deprecation window until the tag's removal version. Prefer keeping the forwarding call to FSIntTableSubscriber.IgnoreServiceItemsByConvertToCustomerAssetFlag until the obsolete procedure is actually removed.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
procedure IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
begin
FSIntTableSubscriber.IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef, IgnoreRecord);
end;Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.40.6
…ting CRM products and modify additional fields correctly
…tionIsInserted parameter and adjust logic for field loading
…nationIsInserted condition
| /// <param name="SourceRecordRef">A reference to the service item to evaluate.</param> | ||
| /// <param name="IgnoreRecord">The existing synchronization decision, which is left unchanged.</param> | ||
| #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')] |
There was a problem hiding this comment.
FS Integration Test Library.IgnoreServiceItemsByConvertToCustomerAssetFlag is marked [Obsolete] (pending, target version '30.0') but its body was stripped to a no-op instead of continuing to forward to FS Int. Table Subscriber.IgnoreServiceItemsByConvertToCustomerAssetFlag (which still has its full working implementation, also marked Obsolete-pending but not removed). During the obsolete-pending window a symbol must keep its previous observable behavior so existing callers are not silently broken before their deprecation window elapses. Any remaining caller of the test-library helper now always leaves IgnoreRecord unchanged instead of getting the real computed result, which is a silent behavioral regression, not just a documentation change.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
procedure IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean)
var
FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber";
begin
FSIntTableSubscriber.IgnoreServiceItemsByConvertToCustomerAssetFlag(SourceRecordRef, IgnoreRecord);
end;Knowledge:
- microsoft/knowledge/upgrade/obsolete-pending-to-removed-staging.md
- microsoft/knowledge/breaking-changes/deprecate-public-members-with-the-obsolete-lifecycle.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
…isting CRM products and modify additional fields
…c by removing redundant checks and directly loading fields
| case SourceDestCode of | ||
| 'Item-CRM Product': | ||
| begin | ||
| DestinationRecordRef.SetTable(CRMProduct); |
There was a problem hiding this comment.
In the new 'Item-CRM Product' case of OnAfterTransferRecordFields, DisableCustomerAssetConversion unconditionally reads CRMProduct.ConvertToCustomerAsset, but the field is only explicitly loaded via CRMProduct.LoadFields(ConvertToCustomerAsset) when DestinationIsInserted is true. On the update path (DestinationIsInserted = false), if the field was not already part of the record's loaded field set from an earlier partial-field synchronization step, reading it can raise a 'field has not been loaded' runtime error, causing the synchronization job to fail. Call LoadFields(ConvertToCustomerAsset) unconditionally before DisableCustomerAssetConversion, or have the helper ensure the field is loaded before reading it.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
DestinationRecordRef.SetTable(CRMProduct);
CRMProduct.LoadFields(ConvertToCustomerAsset);
DisableCustomerAssetConversion(CRMProduct, AdditionalFieldsWereModified);
DestinationRecordRef.GetTable(CRMProduct);Agent judgement — not directly backed by a BCQuality knowledge article.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| CRMProduct.ConvertToCustomerAsset := false; | ||
| CRMProduct.Insert(false); | ||
| // [GIVEN] A coupled item and product where Convert to Customer Asset is Yes. | ||
| CRMSetupDefaults.ResetItemProductMapping('ITEM-PRODUCT', false); |
There was a problem hiding this comment.
The new Item-to-CRM Product sync test (ItemSynchronizationDisablesCustomerAssetConversion) only exercises an already-coupled, pre-existing product via CreateCoupledItemAndProduct, so it never covers the new DestinationIsInserted = true branch added in OnAfterTransferRecordFields (the conditional LoadFields(ConvertToCustomerAsset) path taken when a CRM Product is newly created from an item). Add a test case that synchronizes an uncoupled item so a new CRM Product is inserted, and assert the resulting ConvertToCustomerAsset is correctly initialized/disabled.
Agent judgement — not directly backed by a BCQuality knowledge article.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
…multi-company synchronization
| IntegrationFieldMapping.Direction::ToIntegrationTable, | ||
| '', false, false); | ||
|
|
||
| // Business Central service items are the source for Field Service customer assets. |
There was a problem hiding this comment.
The new 'Item-CRM Product' synchronization branch and its Item-Product field mapping constant (ConvertToCustomerAsset = 'false') change the intended behavior for existing tenants: previously-coupled items and their CRM Products keep their pre-30.0 field mapping and Convert-to-Customer-Asset state until a user manually runs 'Reset Configuration' or a full resync occurs. No upgrade code migrates already-installed Integration Field Mapping rows or already-coupled CRM Product records to the new 'service items always synchronize' design, so the fix does not apply safely to tenants that upgrade without manual intervention.
Knowledge:
- microsoft/knowledge/upgrade/upgrade-codeunit-subtype.md
- microsoft/knowledge/upgrade/install-code-does-not-run-on-version-upgrade.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
…ate_Field_Service_Customer_Asset_Created
…ation tests for item variant support
| end; | ||
|
|
||
| /// <summary> | ||
| /// Retained for compatibility. Service items are now always synchronized to Field Service customer assets, so this procedure leaves the synchronization decision unchanged. |
There was a problem hiding this comment.
The obsolete public test-library procedure IgnoreServiceItemsByConvertToCustomerAssetFlag (Microsoft.TestLibraries.DynamicsFieldService, FSIntegrationTestLibrary.Codeunit.al) is correctly marked [Obsolete('...', '30.0')], but its body was replaced with an empty no-op instead of continuing to forward to the previous implementation. During the deprecation window, existing external callers who still invoke this public procedure expecting IgnoreRecord to be set now silently receive unchanged behavior. Guidance on deprecating public members with the Obsolete lifecycle recommends preserving the prior behavior (e.g., keep forwarding to the codeunit's still-present implementation, or otherwise retain the previous decision) until the procedure is actually removed in a later release, rather than emptying it immediately upon obsoletion.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| 'Item-CRM Product': | ||
| begin | ||
| DestinationRecordRef.SetTable(CRMProduct); | ||
| if DestinationIsInserted then |
There was a problem hiding this comment.
The new 'Item-CRM Product' case branch in OnAfterTransferRecordFields has a DestinationIsInserted path that calls CRMProduct.LoadFields(ConvertToCustomerAsset) before disabling customer-asset conversion, but the new tests (ItemSynchronizationDisablesCustomerAssetConversion, ItemProductMappingDisablesCustomerAssetConversion) only exercise the already-coupled/update path via CreateCoupledItemAndProduct. No test drives CRMIntegrationTableSynch.SynchRecord through a path where the CRM Product record is newly inserted, so the DestinationIsInserted = true branch (and its LoadFields call) is not covered by this change.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
What & why
Business Central and Field Service could both create a Customer Asset for the same item, resulting in duplicates when a Work Order was completed.
This change keeps Business Central as the source for Customer Asset creation by:
Linked work
Fixes AB#649195
How I validated this
What I tested and the outcome (required — be specific: scenarios, commands, screenshots for UI changes)
Risk & compatibility
Risk is low and limited to coupled Field Service Products. Their Convert to Customer Asset value is overwritten to No during Item synchronization, preventing native Field Service asset creation.
Uncoupled Field Service Products are unaffected. No schema or public API changes are introduced. Existing configurations are covered by runtime enforcement, while reset or newly created mappings receive the constant outbound mapping.