[Subcontracting] Add feature mechanism for subcontracting app#8436
Conversation
- Implemented Subc. Feature Flag Handler to check if subcontracting is enabled. - Created Subc. Upgrade Tag Def. Ext. to manage upgrade tags for subcontracting. - Enhanced Subcontracting Install codeunit to set subcontracting feature on installation. - Introduced Subc. Application Area Handler to manage application area settings for subcontracting.
…e-handling-to-subcontracting-app
…e-handling-to-subcontracting-app
GetGuidXxx returns empty Guid when disabled
Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
ClearAllDictionariesForKey skips clearing when disabled
Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
Delete-guard runs on all purchase ordersThe old subscriber checked Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
Stock-at-subcontractor check removed on deleteThe Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
Purchase line field guards silently removedSix Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
Transfer line validation guards removedThree Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
…e-handling-to-subcontracting-app
…e-handling-to-subcontracting-app
Agentic PR Review - Round 1Recommendation: Accept with SuggestionsWhat this PR doesThis adds an on/off switch for the Subcontracting app. The switch is the BaseApp field The whole mechanism sits under I checked three things and they hold up:
The core logic is sound. My suggestions are about diff size, migration, and test depth - not the switch itself. SuggestionsS1 - Split out the line-ending and BOM changes S2 - Confirm the migration plan with the BaseApp change S3 - Add a test for the "app is off" case S4 - Consider caching the switch value S5 - Make the "not visible" test clearer Risk assessment and necessityRisk: The change touches a lot of code - the guard goes into ~42 posting, transfer, warehouse, and planning subscribers, plus many table Necessity: Justified. A clean on/off switch with a defined obsolete lifecycle is the right way to ship a replacement for an old BaseApp feature and let environments fall back during the transition. The scope fits an infrastructure change, the gating is complete, and tests exist for the switch. The open items are confirming the migration plan and adding an "app off" test - not a redesign.
|
| exit(not ManufacturingSetup."Legacy Subcontracting"); | ||
| end; | ||
| } | ||
| #endif No newline at end of file |
There was a problem hiding this comment.
ManufacturingSetup.Get() on every call without caching
IsSubcontractingEnabled() executes a ManufacturingSetup.Get() database read on every invocation. This method is now called at the top of every event subscriber across ~60 codeunits, including high-frequency paths such as OnAfterValidateEvent field triggers, posting routines, and transfer order operations, meaning each of those operations hits the database once more than before.
Recommendation:
- Cache the result of
IsSubcontractingEnabled()in aSingleInstancecodeunit (e.g.,SubcSessionState, which is alreadySingleInstance = true) and invalidate the cache whenManufacturingSetupchanges. Alternatively, since the value changes only when an admin modifies setup, a session-scoped static Boolean field in the codeunit is sufficient.
| #endif | |
| // In SubcSessionState (SingleInstance = true): | |
| var | |
| IsSubcontractingEnabledCached: Boolean; | |
| IsSubcontractingEnabledCacheInitialized: Boolean; | |
| procedure IsSubcontractingEnabled(): Boolean | |
| var | |
| ManufacturingSetup: Record "Manufacturing Setup"; | |
| begin | |
| if not IsSubcontractingEnabledCacheInitialized then begin | |
| ManufacturingSetup.SetLoadFields("Legacy Subcontracting"); | |
| IsSubcontractingEnabledCached := ManufacturingSetup.Get() and not ManufacturingSetup."Legacy Subcontracting"; | |
| IsSubcontractingEnabledCacheInitialized := true; | |
| end; | |
| exit(IsSubcontractingEnabledCached); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| #endif | ||
|
|
||
| internal procedure UpdateApplicationArea() | ||
| var |
There was a problem hiding this comment.
Default state mismatch between flag handler and area handler
When ManufacturingSetup does not exist, SubcApplicationAreaHandler.SetApplicationArea() defaults to TempApplicationAreaSetup."Subcontracting" := true (feature ON), while SubcFeatureFlagHandler.IsSubcontractingEnabled() returns false (feature OFF). This creates a broken state: the UI renders Subcontracting fields as visible, but every code path guarded by the feature flag silently exits, leaving users with an inaccessible, non-functional feature surface.
Recommendation:
- Align the defaults so that both components agree on the fallback when no setup record exists. Since the absence of setup is an edge case (typically a brand-new company), the safest default is to treat the feature as enabled (
true) for both, consistent with the application area handler.
| var | |
| // In SubcFeatureFlagHandler.IsSubcontractingEnabled(): | |
| procedure IsSubcontractingEnabled(): Boolean | |
| var | |
| ManufacturingSetup: Record "Manufacturing Setup"; | |
| begin | |
| ManufacturingSetup.SetLoadFields("Legacy Subcontracting"); | |
| if not ManufacturingSetup.Get() then | |
| exit(true); // align with SetApplicationArea default | |
| exit(not ManufacturingSetup."Legacy Subcontracting"); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
|
|
||
| VendorCard.Close(); | ||
| end; | ||
|
|
There was a problem hiding this comment.
asserterror Assert.IsFalse pattern always passes
In VendorCardSubcFieldsNotVisibleWhenDisabled, the pattern asserterror Assert.IsFalse(VendorCard."...".Visible(), '...') passes in both the correct and incorrect case: if the field is inaccessible, .Visible() throws and asserterror catches it; if the field is accessible, Assert.IsFalse(true,...) throws an assertion error which asserterror also catches. The test never actually fails, providing false confidence in the feature-flag gating.
Recommendation:
- Use a bare
asserterroron the field access to verify inaccessibility. After theasserterror, assert the last error text to confirm the specific application-area error rather than any arbitrary error.
| // Replace asserterror Assert.IsFalse(VendorCard."Subcr. Location Code".Visible(), '...') with: | |
| assertError VendorCard."Subcr. Location Code".Visible(); | |
| Assert.ExpectedError(''); // or check GetLastErrorText() for the ApplicationArea error text | |
| assertError VendorCard."Subc. Linked to Work Center".Visible(); | |
| assertError VendorCard."Subc. Work Center No.".Visible(); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| // [THEN] Returns false | ||
| #pragma warning disable AL0432 | ||
| Assert.IsFalse(SubcFeatureFlagHandler.IsSubcontractingEnabled(), 'Guard should return false when ManufacturingSetup does not exist.'); | ||
| #pragma warning restore AL0432 |
There was a problem hiding this comment.
Test deletes ManufacturingSetup without TearDown
GuardReturnsFalseWhenSetupNotExists deletes the ManufacturingSetup record, asserts, then manually calls Insert() + Commit() to restore it. If the assertion at line 169 fails (or any error is thrown between delete and restore), subsequent tests run without a ManufacturingSetup record, potentially causing cascading, hard-to-diagnose failures in other test codeunits that rely on it.
Recommendation:
- Wrap the delete-and-restore logic in a pattern that guarantees cleanup: either use a
try/finally-equivalent viaAssertError-safe wrapper, or implement a dedicatedTearDown/[TearDown]test isolation mechanism that restores state unconditionally.
| #pragma warning restore AL0432 | |
| // Protect restoration from test failure: | |
| ManufacturingSetup.Delete(); | |
| if GuardReturnsFalseAssert(SubcFeatureFlagHandler) then; // replace inline assertion | |
| ManufacturingSetup.Init(); | |
| ManufacturingSetup.Copy(TempManufacturingSetupBackup); | |
| ManufacturingSetup.Insert(); | |
| Commit(); | |
| // Or move the deletion to a TearDown that always restores state |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Missing newline at end of fileAfter the modifications, Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
SetSubcontractingFeatureOnInstall no-op on reinstall
Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
| { | ||
| ObsoleteState = Pending; | ||
| ObsoleteTag = '29.0'; | ||
| ObsoleteReason = 'Legacy Subcontracting will be discontinued, environments should move to the Subcontracting App so this codeunit will be removed in a future release.'; |
There was a problem hiding this comment.
ObsoleteReason is misleading for the feature flag codeunit
The ObsoleteReason says "Legacy Subcontracting will be discontinued, environments should move to the Subcontracting App so this codeunit will be removed in a future release." This is confusing because this codeunit IS the bridge enabling migration away from legacy subcontracting — the reason implies the codeunit exists to support the old system, when in fact it exists to support the transition to the new one.
Recommendation:
- Rewrite the
ObsoleteReasonto accurately describe the purpose and removal rationale: the flag handler is a transition mechanism that will be removed once all customers have migrated and theLegacy Subcontractingfield is removed.
| ObsoleteReason = 'Legacy Subcontracting will be discontinued, environments should move to the Subcontracting App so this codeunit will be removed in a future release.'; | |
| ObsoleteReason = 'This codeunit is a temporary transition guard for the migration from Legacy Subcontracting to the Subcontracting App. It will be removed in 29.0 once the Legacy Subcontracting field and all related conditional compilation blocks are cleaned up.'; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| begin | ||
| exit(UnbindSubscription(SubcPostingPreviewHandler)); | ||
| end; | ||
| } |
There was a problem hiding this comment.
TryBindPostingPreviewHandler returns implicit false
TryBindPostingPreviewHandler() is declared as Boolean but uses a bare exit; (implicit false) when the feature flag is disabled. Callers that inspect the return value cannot distinguish between "feature disabled" and "BindSubscription failed", both of which silently return false. This can hide binding failures.
Recommendation:
- Use
exit(false)explicitly to document intent and enable callers to differentiate correctly if needed.
| } | |
| #if not CLEAN29 | |
| #pragma warning disable AL0432 | |
| if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then | |
| #pragma warning restore AL0432 | |
| exit(false); | |
| #endif |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| #pragma warning disable AL0432 | ||
| if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then | ||
| #pragma warning restore AL0432 | ||
| exit; |
There was a problem hiding this comment.
TryUnbindPostingPreviewHandler returns implicit false
TryUnbindPostingPreviewHandler() is also declared as Boolean and uses a bare exit; when the feature is disabled, returning an implicit false. The caller in UnbindPostPrecEventHandlerOnAfterUnbindSubscription discards the return value, so there is no immediate bug, but the inconsistency makes the contract of the function unclear and fragile under future refactoring.
Recommendation:
- Use
exit(false)to be explicit about the returned value when the guard fires.
| exit; | |
| #if not CLEAN29 | |
| #pragma warning disable AL0432 | |
| if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then | |
| #pragma warning restore AL0432 | |
| exit(false); | |
| #endif |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
@alexei-dobriansky S1 - Split out the line-ending and BOM changes S2 - Confirm the migration plan with the BaseApp change S3 - Add a test for the "app is off" case S4 - Consider caching the switch value S5 - Make the "not visible" test clearer To summarize the only real possible suggestion to discuss is in my opinion S3 but it think this is solid tested. |
This pull request introduces a new feature flag mechanism for the Subcontracting module, allowing all related logic to be conditionally enabled or disabled based on a central setting. The main changes include the implementation of the feature flag handler, integration of feature checks throughout the session state and process extension codeunits, and the addition of upgrade tag management to support future upgrades and feature rollouts.
Fixes AB#406123