Skip to content

[Subcontracting] Add feature mechanism for subcontracting app#8436

Open
DennisFranzmannGOB wants to merge 8 commits into
microsoft:mainfrom
GOB-Software-Systeme-DevOps:w/franzd/add-feature-handling-to-subcontracting-app
Open

[Subcontracting] Add feature mechanism for subcontracting app#8436
DennisFranzmannGOB wants to merge 8 commits into
microsoft:mainfrom
GOB-Software-Systeme-DevOps:w/franzd/add-feature-handling-to-subcontracting-app

Conversation

@DennisFranzmannGOB
Copy link
Copy Markdown

@DennisFranzmannGOB DennisFranzmannGOB commented Jun 3, 2026

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

- 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.
@DennisFranzmannGOB DennisFranzmannGOB requested a review from a team as a code owner June 3, 2026 10:02
@github-actions github-actions Bot added AL: Apps (W1) Add-on apps for W1 From Fork Pull request is coming from a fork labels Jun 3, 2026
@DennisFranzmannGOB
Copy link
Copy Markdown
Author

@ChethanT

@github-actions github-actions Bot added the Linked Issue is linked to a Azure Boards work item label Jun 3, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Jun 3, 2026
@github-actions github-actions Bot added the needs-approval Workflow runs require maintainer approval to start label Jun 3, 2026
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Manufacturing.Subcontracting;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field "Legacy Subcontracting" is marked as obsolete 29, this code should follow the same and all the references to this as well


codeunit 99001569 "Subc. Feature Flag Handler"
{
procedure IsSubcontractingEnabled(): Boolean
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🔴\ Critical\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Feature flag issues DB read on every call

IsSubcontractingEnabled() calls ManufacturingSetup.Get() every time it is invoked, and SubcFeatureFlagHandler is not SingleInstance. Because this guard is added to dozens of event subscribers (purchase posting, planning, WIP, BOM calculation, etc.) each invocation triggers a separate database round-trip, multiplying the cost on any operation that fires multiple subscribers.

Recommendation:

  • Declare the codeunit as SingleInstance = true so the result is computed once per session. Alternatively, cache the boolean result in a local variable or store it in SubcSessionState on first read, and clear it only when ManufacturingSetup is modified.
Suggested change
procedure IsSubcontractingEnabled(): Boolean
codeunit 99001569 "Subc. Feature Flag Handler"
{
SingleInstance = true;
var
IsEnabled: Boolean;
Initialized: Boolean;
procedure IsSubcontractingEnabled(): Boolean
var
ManufacturingSetup: Record "Manufacturing Setup";
begin
if Initialized then
exit(IsEnabled);
ManufacturingSetup.SetLoadFields("Legacy Subcontracting");
if not ManufacturingSetup.Get() then
exit(false);
IsEnabled := not ManufacturingSetup."Legacy Subcontracting";
Initialized := true;
exit(IsEnabled);
end;
procedure ResetCache()
begin
Initialized := false;
end;
}

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why


procedure ClearAllDictionariesForKey(StoredKey: Text)
begin
if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

SingleInstance cache defeated by per-call DB reads

SubcSessionState is a SingleInstance codeunit whose purpose is to cache values across calls within a session. Adding a non-cached IsSubcontractingEnabled() (which calls ManufacturingSetup.Get()) at the top of every Set, Get, and Clear method means every dictionary access now incurs a database read, negating the performance benefit of the singleton pattern entirely.

Recommendation:

  • Cache the feature-enabled flag once inside SubcSessionState itself, or make SubcFeatureFlagHandler SingleInstance so the result is only read once per session across all callers.
Suggested change
if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
// In SubcSessionState, replace repeated IsSubcontractingEnabled() calls:
var
SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
FeatureFlagChecked: Boolean;
FeatureEnabled: Boolean;
local procedure GetFeatureEnabled(): Boolean
begin
if not FeatureFlagChecked then begin
FeatureEnabled := SubcFeatureFlagHandler.IsSubcontractingEnabled();
FeatureFlagChecked := true;
end;
exit(FeatureEnabled);
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

$\textbf{🟠\ High\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

GetGuidXxx returns empty Guid when disabled

GetGuidProductionOrderCreatedNotification() and GetGuidSubcontractingPOCreatedNotification() use bare exit (no argument) when subcontracting is disabled, returning the default empty Guid {00000000-0000-0000-0000-000000000000}. Any caller that passes this value to MyNotifications.InsertDefault(), IsEnabled(), or notification lookup will silently operate on the zero-GUID, potentially matching unrelated notifications or causing data corruption.

Recommendation:

  • Callers of these GetGuid* procedures should check IsSubcontractingEnabled() before calling them. Alternatively, keep the procedures unconditional — they return a constant and adding a guard here is unnecessary since the callers (InitializeSubcontractingNotifications, DisableNotification) already guard themselves.
procedure GetGuidProductionOrderCreatedNotification(): Guid
begin
    exit('{5d564aca-ce60-4345-ba68-e1e50976a346}');
end;

procedure GetGuidSubcontractingPOCreatedNotification(): Guid
begin
    exit('{f7b10c9e-071a-4455-a048-d17b29ef764c}');
end;

Line mapping was unavailable, so this was posted as an issue comment.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

BaseQtyToPurch: Decimal;
QtyToPurch: Decimal;
begin
if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Feature guard in setter/helper methods is redundant

Methods such as SetOperationNoForCreatedPurchaseOrder, ClearOperationNoForCreatedPurchaseOrder, SetRoutingReferenceNoForCreatedPurchaseOrder, and ClearRoutingReferenceNoForCreatedPurchaseOrder merely assign or clear a variable. Adding an IsSubcontractingEnabled() DB read to each of these trivial setters is disproportionate to the risk and multiplies the database load when the purchase order creator is active.

Recommendation:

  • Guard only the entry-point public methods (e.g., the purchase order creation methods) and remove guards from internal state-management helpers whose sole effect is updating a local variable. The entry-point check is sufficient to prevent execution when the feature is disabled.
Suggested change
if not SubcFeatureFlagHandler.IsSubcontractingEnabled() then
// Remove guards from setter/clear helpers:
internal procedure SetOperationNoForCreatedPurchaseOrder(OperationNoToSet: Code[10])
begin
OperationNo := OperationNoToSet;
end;
internal procedure ClearOperationNoForCreatedPurchaseOrder()
begin
Clear(OperationNo);
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why


codeunit 99001568 "Subc. Application Area Handler"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Application Area Mgmt. Facade", OnGetPremiumExperienceAppAreas, '', false, false)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Application area subscriber reads DB on every call

The OnGetPremiumExperienceAppAreasSubscriber event fires whenever an application area is evaluated (e.g., page open, field visibility check). SetApplicationArea performs ManufacturingSetup.Get() on every invocation. If SubcFeatureFlagHandler were SingleInstance, this would be free; without caching it adds a database round-trip to every page-open event in a premium session.

Recommendation:

  • This concern is resolved by making SubcFeatureFlagHandler SingleInstance (see finding Fix CICD #1). Alternatively, call SubcFeatureFlagHandler.IsSubcontractingEnabled() directly here so the cached value is used.
Suggested change
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Application Area Mgmt. Facade", OnGetPremiumExperienceAppAreas, '', false, false)]
local procedure SetApplicationArea(var TempApplicationAreaSetup: Record "Application Area Setup")
var
SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler"; // SingleInstance after fix
begin
TempApplicationAreaSetup."Subcontracting" := SubcFeatureFlagHandler.IsSubcontractingEnabled();
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Manufacturing.Subcontracting;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Style} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

File uses CRLF line endings inconsistently

The diff for this file shows ^M (carriage-return) at the end of every line, indicating CRLF line endings. The rest of the changed AL files in this PR use LF only. Mixed line endings within the repository can cause diff noise and may violate the .gitattributes normalisation rules.

Recommendation:

  • Convert the file to LF-only line endings to match the repository convention, or ensure the author's editor/IDE is configured to use LF for .al files.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

begin
exit('MS-406123-Subcontracting-20260601');
end;
} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Style} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

File missing newline at end

The new SubcUpgradeTagDefExt.al file is missing a newline at end of file (diff shows \ No newline at end of file). This is inconsistent with the rest of the codebase and can produce noisy diffs in future edits.

Recommendation:

  • Add a trailing newline after the closing } on the last line.
Suggested change
}
internal procedure GetSubcontractingUpgradeTag(): Code[250]
begin
exit('MS-406123-Subcontracting-20260601');
end;
}

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why


if ManufacturingSetup.Get() then begin
ManufacturingSetupBackup.Copy(ManufacturingSetup);
ManufacturingSetup.Delete();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Style} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Test deletes singleton table without proper isolation

GuardReturnsFalseWhenSetupNotExists calls ManufacturingSetup.Delete() on the company-wide singleton record. If the assertion at line 152 or the subsequent restore at lines 155-157 fails (e.g., due to a conflicting test or a runtime error), the ManufacturingSetup record is left permanently deleted in the test company, breaking all tests that follow in the same test suite.

Recommendation:

  • Wrap the delete/restore logic in a try-finally pattern, or use a test isolation library (e.g., mock/stub the codeunit) instead of mutating the singleton table. At minimum, add a teardown step via [TearDown] or move the restore into a protected block.
Suggested change
ManufacturingSetup.Delete();
procedure GuardReturnsFalseWhenSetupNotExists()
var
ManufacturingSetup: Record "Manufacturing Setup";
ManufacturingSetupBackup: Record "Manufacturing Setup" temporary;
SubcFeatureFlagHandler: Codeunit "Subc. Feature Flag Handler";
begin
Initialize();
if ManufacturingSetup.Get() then begin
ManufacturingSetupBackup.Copy(ManufacturingSetup);
ManufacturingSetup.Delete();
end;
// Guard check
Assert.IsFalse(SubcFeatureFlagHandler.IsSubcontractingEnabled(), 'Guard should return false when ManufacturingSetup does not exist.');
finally
// Always restore
ManufacturingSetup.Init();
ManufacturingSetup.Copy(ManufacturingSetupBackup);
if not ManufacturingSetup.Insert() then
ManufacturingSetup.Modify();
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

begin
end;

local procedure SetSubcontractingFeatureOnInstall()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Upgrade} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Upgrade path skips feature-flag initialisation

SetSubcontractingFeatureOnInstall() is called only from HandleFreshInstallPerCompany(). Existing customers who upgrade (which triggers HandleReinstallPerCompany) never receive the feature-flag setup, so the upgrade tag MS-406123-Subcontracting-20260601 is never recorded for them. Any future upgrade logic that checks HasUpgradeTag for this tag will incorrectly assume it has not run.

Recommendation:

  • Call SetSubcontractingFeatureOnInstall() from HandleReinstallPerCompany() as well, or move the logic into a dedicated upgrade codeunit that fires during the upgrade path. The internal upgrade-tag guard (HasUpgradeTag) already prevents double execution.
Suggested change
local procedure SetSubcontractingFeatureOnInstall()
local procedure HandleReinstallPerCompany()
var
SubcontractingCompInit: Codeunit "Subcontracting Comp. Init.";
begin
SubcontractingCompInit.CreateBasicSubcontractingMgtSetup();
SetSubcontractingFeatureOnInstall(); // ensure tag is stamped on upgrades too
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

if UpgradeTag.HasUpgradeTag(SubcUpgradeTagDefExt.GetSubcontractingUpgradeTag()) then
exit;

SubcApplicationAreaHandler.UpdateApplicationArea();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Upgrade} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

Install does not set Legacy Subcontracting default

SetSubcontractingFeatureOnInstall() only calls UpdateApplicationArea() (which refreshes the experience-tier cache) but never writes ManufacturingSetup."Legacy Subcontracting" := false. The feature-enabled state therefore depends entirely on the field's default value supplied by the table extension. If the field defaults to true, a fresh installation will have subcontracting silently disabled.

Recommendation:

  • Explicitly set ManufacturingSetup."Legacy Subcontracting" := false before calling UpdateApplicationArea() to make the intended default explicit and self-documenting.
Suggested change
SubcApplicationAreaHandler.UpdateApplicationArea();
local procedure SetSubcontractingFeatureOnInstall()
var
ManufacturingSetup: Record "Manufacturing Setup";
UpgradeTag: Codeunit "Upgrade Tag";
SubcApplicationAreaHandler: Codeunit "Subc. Application Area Handler";
SubcUpgradeTagDefExt: Codeunit "Subc. Upgrade Tag Def. Ext.";
begin
if UpgradeTag.HasUpgradeTag(SubcUpgradeTagDefExt.GetSubcontractingUpgradeTag()) then
exit;
if ManufacturingSetup.Get() then begin
ManufacturingSetup."Legacy Subcontracting" := false;
ManufacturingSetup.Modify();
end;
SubcApplicationAreaHandler.UpdateApplicationArea();
UpgradeTag.SetUpgradeTag(SubcUpgradeTagDefExt.GetSubcontractingUpgradeTag());
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

@github-actions github-actions Bot removed the needs-approval Workflow runs require maintainer approval to start label Jun 3, 2026
@microsoft-github-policy-service
Copy link
Copy Markdown

@DennisFranzmannGOB please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AL: Apps (W1) Add-on apps for W1 From Fork Pull request is coming from a fork Linked Issue is linked to a Azure Boards work item

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants