Add CMFRT naming conventions, object ID ranges, and patterns documentation

- Introduced guidelines for using the "CMFRT" prefix in object names, fields, and procedures to avoid naming collisions and ensure clarity.
- Established rules for object ID ranges to prevent conflicts with other extensions and maintain historical integrity.
- Documented best practices and anti-patterns for various coding patterns, including case statements, interface injection, label usage, and validation methods.
- Implemented a standards review skill to evaluate AL source changes against CMFRT company standards, ensuring compliance with naming, permissions, and architectural patterns.
This commit is contained in:
BeytullahCengiz88 2026-07-14 14:09:04 +02:00
parent d6ac005173
commit afb1fa2883
53 changed files with 1288 additions and 0 deletions

View file

@ -0,0 +1,34 @@
codeunit 55043 "CMFRT AQ FS ProForma Meth"
{
procedure CMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
var
Item: Record Item;
GeneralPostingSetup: Record "General Posting Setup";
IsHandled: Boolean;
begin
OnBeforeCMFRTAQFSResolveItemToGLAcc(SalesLine, IsHandled);
if IsHandled then
exit;
// Business logic inline in the shell: this early exit
// silently skips the OnAfter event below.
if SalesLine."CMFRT AQ FS Item No." = '' then
exit;
if not Item.Get(SalesLine."CMFRT AQ FS Item No.") then
exit;
// ... more inline logic ...
OnAfterCMFRTAQFSResolveItemToGLAcc(SalesLine);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
begin
end;
}

View file

@ -0,0 +1,38 @@
codeunit 55043 "CMFRT AQ FS ProForma Meth"
{
procedure CMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
var
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeCMFRTAQFSResolveItemToGLAcc(SalesLine, IsHandled);
if IsHandled then
exit;
DoCMFRTAQFSResolveItemToGLAcc(SalesLine);
OnAfterCMFRTAQFSResolveItemToGLAcc(SalesLine);
end;
local procedure DoCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
var
Item: Record Item;
GeneralPostingSetup: Record "General Posting Setup";
ItemNotFoundErr: Label 'Item %1 was not found.', Comment = '%1 = Item No.';
begin
Item.SetLoadFields("No.", "Gen. Prod. Posting Group", Description);
if not Item.Get(SalesLine."CMFRT AQ FS Item No.") then
Error(ItemNotFoundErr, SalesLine."CMFRT AQ FS Item No.");
// ... business logic only; early exits here cannot skip OnAfter ...
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCMFRTAQFSResolveItemToGLAcc(var SalesLine: Record "Sales Line")
begin
end;
}

View file

@ -0,0 +1,26 @@
---
bc-version: [all]
domain: events
keywords: [on-before, on-after, do-procedure, thin-shell, meth, is-handled, extraction, entry-point]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CMFRT OnBefore → Do → OnAfter procedure shape
## Description
Global procedures in CMFRT Meth codeunits are thin shells: the body consists of firing `OnBefore<Name>` with `var IsHandled: Boolean`, exiting if handled, calling a local `Do<Name>` procedure that holds all business logic, and firing `OnAfter<Name>`. Local variables that only serve the business logic (record buffers, labels, working values) live in the `Do` procedure, not in the shell. This keeps the event bracket structurally impossible to bypass — the `OnAfter` event cannot be skipped by an early `exit` inside business logic, because business logic lives one level down.
## Best Practice
Write every global Meth procedure as: reset `IsHandled`, fire `OnBefore`, `if IsHandled then exit;`, call `Do<Name>(...)`, fire `OnAfter`. When review finds a global procedure whose body mixes event calls with business logic, extract the logic into `Do<Name>` and move its private variables and labels along with it.
See sample: `cmfrt-onbefore-do-onafter.good.al`.
## Anti Pattern
A global procedure whose business logic sits inline between the `OnBefore` and `OnAfter` calls. Inline bodies grow early `exit` paths that silently skip the `OnAfter` event, and their local variables and labels accumulate at the shell level where every branch can touch them. Equally wrong: declaring the event pair but never calling the events from the procedure (dead events), which advertises an extension point that never fires.
See sample: `cmfrt-onbefore-do-onafter.bad.al`.

View file

@ -0,0 +1,15 @@
// No integration events dependent extensions cannot intercept or react
// to the operation without using an AL override, which is a breaking pattern.
codeunit 2045700 "CMFRT BA Item Price Impl"
{
procedure CMFRTBAUpdateItemPrice(ItemNo: Code[20]; UnitPrice: Decimal)
var
Item: Record Item;
begin
Item.SetLoadFields("Unit Price");
if Item.Get(ItemNo) then begin
Item.Validate("Unit Price", UnitPrice);
Item.Modify();
end;
end;
}

View file

@ -0,0 +1,30 @@
codeunit 2045700 "CMFRT BA Item Price Impl"
{
procedure CMFRTBAUpdateItemPrice(ItemNo: Code[20]; UnitPrice: Decimal)
var
Item: Record Item;
Handled: Boolean;
begin
OnBeforeCMFRTBAUpdateItemPrice(ItemNo, UnitPrice, Handled);
if Handled then
exit;
Item.SetLoadFields("Unit Price");
if Item.Get(ItemNo) then begin
Item.Validate("Unit Price", UnitPrice);
Item.Modify();
end;
OnAfterCMFRTBAUpdateItemPrice(ItemNo, UnitPrice);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeCMFRTBAUpdateItemPrice(ItemNo: Code[20]; UnitPrice: Decimal; var Handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCMFRTBAUpdateItemPrice(ItemNo: Code[20]; UnitPrice: Decimal)
begin
end;
}

View file

@ -0,0 +1,28 @@
---
bc-version: [all]
domain: events
keywords: [integration-event, on-before, on-after, extensibility, event-publisher, global-procedure, handled]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CMFRT OnBefore and OnAfter for every global procedure
## Description
Every global procedure in a CMFRT extension must be bracketed by a paired `OnBefore<ProcedureName>` and `OnAfter<ProcedureName>` integration event declared in the same object. Both events are `[IntegrationEvent(false, false)]` local procedures. The `OnBefore` event passes the procedure's key input variables and a `var Handled: Boolean` parameter. The entry procedure checks `Handled` on return from `OnBefore` and exits without executing its body if a subscriber has already handled the operation. The `OnAfter` event passes the key output variables so subscribers can react to the completed result.
## Best Practice
Declare both events at the time the global procedure is written, not as a later addition. Place the `OnBefore` call at the top of the procedure body before any logic, and the `OnAfter` call at the bottom after the last statement. Follow the naming convention `OnBefore<ProcedureName>` and `OnAfter<ProcedureName>` exactly so event consumers can locate publishers by convention.
See sample: `cmfrt-onbefore-onafter-all-globals.good.al`.
## Anti Pattern
Publishing a global procedure without both `OnBefore` and `OnAfter` integration events, or adding events only after a downstream extension explicitly requests an extension point. A global procedure with no event bracket is a black box: dependent extensions cannot inject logic around it without an AL override, which is a breaking pattern. Adding events later is itself a non-breaking change but causes unnecessary churn and review cycles.
The inverse is equally a violation: event declarations that nothing raises. An `[IntegrationEvent]` declared but never called from any procedure, or a wrapper procedure (for example a `[TryFunction]` insert wrapper) that no caller invokes, is dead code that advertises an extension point which never fires. Wire the event into the owning procedure or delete the declaration and its plumbing.
See sample: `cmfrt-onbefore-onafter-all-globals.bad.al`.