Bug 638464: Remove Subc. Detailed Calculation report; extend BaseApp report via event#8673
Bug 638464: Remove Subc. Detailed Calculation report; extend BaseApp report via event#8673ChethanT wants to merge 3 commits into
Conversation
…report via event - Remove Report 99001500 (Subc. Detailed Calculation) which was a full copy of BaseApp Report 99000756 - Rewrite SubcReportingTriggersExt to subscribe to new BaseApp event OnAfterGetRecordRoutingLineOnBeforeCalcRoutingCostPerUnit - Event subscriber applies subcontractor pricing when Work Center has a subcontractor - Remove report from permission set - Add test verifying BaseApp report uses subcontractor pricing via event This eliminates duplicate Tell Me entries, preserves extensibility, and fixes the no-op report substitution (which checked ReportId = 99001500 and set NewReportId = 99001500). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| report "Subc. Calculate Subcontracts" = X, | ||
| report "Subc. Create Transf. Order" = X, | ||
| report "Subc. Create SubCReturnOrder" = X, | ||
| report "Subc. Detailed Calculation" = X, |
There was a problem hiding this comment.
Base report not added to permission set
The custom Subc. Detailed Calculation report (99001500) was removed from the permission set without adding the base Detailed Calculation report (99000756) in its place. Users assigned only the Subcontracting permission set will be unable to run the Detailed Calculation report after this change.
Recommendation:
- Add
report "Detailed Calculation" = Xto the permission set so subcontracting users retain access to the replaced report.
| report "Subc. Detailed Calculation" = X, | |
| report "Subc. Create SubCReturnOrder" = X, | |
| report "Detailed Calculation" = X, | |
| report "Subc. Dispatching List" = X; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| 1, | ||
| 1); | ||
|
|
||
| IsHandled := true; |
There was a problem hiding this comment.
IsHandled guard missing in event subscriber
The event subscriber does not check whether IsHandled is already true before executing. If another extension has already handled the event, this code will still run and override the earlier handler's result, silently suppressing its work.
Recommendation:
- Add an early exit at the start of the procedure when
IsHandledis alreadytrue.
| IsHandled := true; | |
| begin | |
| if IsHandled then | |
| exit; | |
| if RoutingLine.Type <> RoutingLine.Type::"Work Center" then | |
| exit; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| // [WHEN] Run the "Detailed Calculation" report (BaseApp 99000756) for this item. | ||
| Commit(); | ||
| Item.SetRecFilter(); | ||
| DetailedCalculation.SetTableView(Item); |
There was a problem hiding this comment.
Test missing negative/fallback coverage
The new test only verifies the happy path where a subcontractor price exists and is picked up. There is no test case verifying that when a work center has a Subcontractor No. but no matching subcontractor price record exists, the report correctly falls back to the work center's own direct unit cost rather than outputting zero or an uninitialized value.
Recommendation:
- Add a second test procedure that creates a work center with a subcontractor but no
SubcontractorPricerecord, and asserts thatProdUnitCostequals the work center'sDirect Unit Cost.
| DetailedCalculation.SetTableView(Item); | |
| [Test] | |
| procedure DetailedCalculationReportFallsBackToWorkCenterCostWhenNoPriceExists() | |
| // ... arrange: work center with Subcontractor No., no SubcontractorPrice record | |
| // ... assert: ProdUnitCost = WorkCenterDirectCost | |
| begin | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| if RoutingLine.Type <> RoutingLine.Type::"Work Center" then | ||
| exit; | ||
|
|
||
| if not WorkCenter.Get(RoutingLine."Work Center No.") then |
There was a problem hiding this comment.
WorkCenter.Get called without prior type guard on routing line
The guard if RoutingLine.Type <> RoutingLine.Type::"Work Center" then exit; is correct, but WorkCenter.Get is then called unconditionally for every routing line in the report dataset. In a production routing with many work-center operations, each line triggers an extra database read even for work centers with no subcontractor configured.
Recommendation:
- Cache the last-retrieved work center number outside the procedure or use
CalcFieldsselectively. At minimum ensure the existing guard at line 46 (WorkCenter."Subcontractor No." = '') short-circuits quickly, which it does — the current code is acceptable but should be noted for routing headers with many lines.
| if not WorkCenter.Get(RoutingLine."Work Center No.") then | |
| // Current pattern is acceptable. If performance is a concern for large routings, | |
| // consider caching WorkCenter lookups by "Work Center No." using a temporary record or dictionary. |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Deleted report still referenced; build failsThe Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
Permission for deleted report removed but D365 READ not updatedThe Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
| OverheadRate, | ||
| ProdUnitCost, | ||
| UnitCostCalculation, | ||
| 1, |
There was a problem hiding this comment.
Hardcoded QtyUoM/ProdQtyPerUom/QtyBase bypass UOM conversion
The call to SetRoutingPriceListCost passes 1, 1, 1 for QtyUoM, ProdQtyPerUom, and QtyBase. Inside that codeunit these values drive the unit-of-measure price conversion; hardcoding them to 1 means the price is never scaled for items whose base UOM differs from the subcontractor price list UOM, resulting in an incorrect ProdUnitCost in those cases.
Recommendation:
- Supply the actual lot-size quantity and UOM conversion factor from the event parameters (
BaseUnitOfMeasure) when callingSetRoutingPriceListCost. The event already providesBaseUnitOfMeasure; derive the UOM factor usingUnit of Measure Managementor pass the item's lot size from the caller context if available.
| 1, | |
| SubcPriceManagement.SetRoutingPriceListCost( | |
| SubcontractorPrice, | |
| WorkCenter, | |
| DirectUnitCost, | |
| IndirectCostPct, | |
| OverheadRate, | |
| ProdUnitCost, | |
| UnitCostCalculation, | |
| 1, // QtyUoM – replace with actual quantity if obtainable from context | |
| 1, // ProdQtyPerUom – replace with UOM conversion factor | |
| 1); // QtyBase – replace with lot-size quantity |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| // [THEN] The ProdUnitCost in the report dataset equals the subcontractor price (200), | ||
| // not the Work Center's generic Direct Unit Cost (50). | ||
| LibraryReportDataset.LoadDataSetFile(); | ||
| LibraryReportDataset.AssertElementWithValueExists('ProdUnitCost', SubcPriceAmount); |
There was a problem hiding this comment.
Test verifies only ProdUnitCost, not DirectUnitCost/overhead
The new test DetailedCalculationReportUsesSubcontractorPricing asserts only ProdUnitCost = 200 but the event subscriber also sets DirectUnitCost, IndirectCostPct, and OverheadRate via SetRoutingPriceListCost. A regression in any of those output values would go undetected by this test.
Recommendation:
- Assert the additional cost components that the subscriber sets. If the subcontractor price record has
Direct Unit Cost = 200and zero overhead/indirect cost, assertDirectUnitCost = 200,IndirectCostPct = 0, andOverheadRate = 0in the report dataset.
| LibraryReportDataset.AssertElementWithValueExists('ProdUnitCost', SubcPriceAmount); | |
| LibraryReportDataset.LoadDataSetFile(); | |
| LibraryReportDataset.AssertElementWithValueExists('ProdUnitCost', SubcPriceAmount); | |
| // Also verify the subscriber correctly propagates the other cost components: | |
| LibraryReportDataset.AssertElementWithValueExists('DirectUnitCost_RtngLine', SubcPriceAmount); |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Summary
Root Cause
\SubcReportingTriggersExt\ subscribed to \SubstituteReport\ but checked \if ReportId = Report::"Subc. Detailed Calculation"\ and set \NewReportId\ to itself (99001500 → 99001500). This was a no-op — it should have been substituting the BaseApp report (99000756).
Fix
Test
Added \DetailedCalculationReportUsesSubcontractorPricing\ in codeunit 139982 "Subc. Pricing Test" — [SCENARIO 638464].
Fixes AB#638464
🤖 Generated with GitHub Copilot