Most AL developers have written this at least once:
alcase Strategy of
Standard: Score := CalculateStandard(...);
Weighted: Score := CalculateWeighted(...);
end;
It works. Until a third strategy. Then a fourth. Then a partner wants their own.
Every addition means opening production code, touching the calculator, risking a regression, redeploying.
The Cash Flow Risk Engine, a BC extension that scores customers 0β100 on payment behavior and generates risk-adjusted 30/60/90-day forecasts, was built to never have this problem. Here's the core pattern:
Interface + Enum = permanently closed calculator
alinterface ICFR_RiskStrategy
{
procedure CalculateScore(...): Decimal;
}
Each algorithm implements the interface independently. The enum binds each value to its codeunit via AL's Implementation keyword. The calculator becomes:
alStrategy := CFRSetup."Default Risk Strategy";
Score := Strategy.CalculateScore(...);
It never mentions a specific codeunit by name. A partner ships a new ML-based strategy as their own extension, zero changes to core code.
Other patterns worth knowing about in this project:
SetLoadFields on every Cust. Ledger Entry read, critical on 1M+ row tables
Query objects instead of table loops for aggregation (compiles to SQL GROUP BY)
Use temporary tables and single bulk writes; avoid commits inside processing loops.
Implement per-record error isolation so that one corrupted entry doesnβt abort an entire batch (e.g., 8,000 customers).
Maintain a telemetry facade with one codeunit, ensuring consistent custom dimensions and respect for the enable/disable flag.
Treat AppSource compliance as a design constraint from day one, not merely a submission checklist.
The probability model is interesting too, every expected amount is discounted by a collection probability derived from the risk score, so the CFO sees the realistic number alongside the raw one.
Full architecture breakdown with every design decision on my blog π blog link
Source code π https://github.com/yahyatouil-dev/bc-cashflow-forecasting-risk-engine
Top comments (0)