← Back to Benchmark Results

anthropic/claude-opus-4-5-20251101

Pass Rate:66.1%
Tasks Passed:37/56
Total Shortcomings:21

All Known Shortcomings

Sorted by occurrence count (most frequent first)

# Concept AL Concept Count Affected Tasks
1 parse-failure unknown 2 CG-AL-H003, CG-AL-H002

Description: Failed to parse LLM analysis response: {"outcome":"test_logic_bug","category":"test_logic_bug","description":"The test TestHighInventoryDiscount assumes that if an Item with Inventory >= 100 and Unit Price > 0 exists in the database, it wi

Correct Pattern:
Incorrect Pattern:
2 reserved-keyword-variable-name variable-naming 1 CG-AL-H020

Description: The model used 'Key' as a variable name, but 'Key' is a reserved keyword in AL. The compiler errors AL0519 ('Key' is not valid value in this context) and subsequent syntax errors occur because the AL compiler interprets 'Key' as a keyword rather than a variable identifier. The model should have used a different variable name like 'DictKey' or 'KeyValue' to avoid the reserved keyword conflict.

Correct Pattern:
var
    DictKey: Text;
begin
    foreach DictKey in Dict1.Keys do begin
Incorrect Pattern:
var
    Key: Text;
begin
    foreach Key in Dict1.Keys do begin

Error Codes: AL0519

3 message-in-event-subscriber event-subscriber-patterns 1 CG-AL-E010

Description: The model generated an event subscriber that calls Message() inside the OnAfterInsertEvent handler. In automated test scenarios, Message() causes an 'Unhandled UI' error because there's no user interface to display the message. Event subscribers that run during automated tests should not use UI-blocking functions like Message(), Error(), or Confirm() unless properly handled. The task asked to 'display a message when a new item is created' but in a test context, this pattern fails. The model should have used a non-UI approach like logging to a table, or the code should check if it's running in a test context.

Correct Pattern:
[EventSubscriber(ObjectType::Table, Database::Item, 'OnAfterInsertEvent', '', false, false)]
local procedure OnAfterInsertItem(var Rec: Record Item; RunTrigger: Boolean)
begin
    if Rec.IsTemporary() then
        exit;

    LastInsertedItemNo := Rec."No.";
    ItemInsertedFlag := true;
    // Log to a table instead of using Message() to avoid UI blocking in tests
    // Or use: if GuiAllowed then Message(...);
end;
Incorrect Pattern:
[EventSubscriber(ObjectType::Table, Database::Item, 'OnAfterInsertEvent', '', false, false)]
local procedure OnAfterInsertItem(var Rec: Record Item; RunTrigger: Boolean)
begin
    if Rec.IsTemporary() then
        exit;

    LastInsertedItemNo := Rec."No.";
    ItemInsertedFlag := true;

    Message('New item created: %1 - %2', Rec."No.", Rec.Description);
end;

Error Codes: Unhandled UI: Message

4 multiline-string-literal-syntax text-literal-syntax 1 CG-AL-E050

Description: The model attempted to use multiline string literals by simply breaking the string across multiple lines in the source code. However, AL does not support this syntax - text literals must be terminated on the same line they start. AL requires either string concatenation with '+' operator or using the newer AL multiline string literal syntax with triple single quotes ('''...''') introduced in later BC versions. The model incorrectly assumed that breaking a string across lines would work like in some other languages.

Correct Pattern:
For multiline strings in AL, use either:
1. String concatenation: exit('SELECT CustomerNo, Name, Balance' + Environment.NewLine + 'FROM Customer' + ...)
2. Or if using BC 2022 wave 2+, use triple-quoted strings: exit('''SELECT CustomerNo, Name, Balance
FROM Customer
WHERE Active = true
ORDER BY Name''');
Incorrect Pattern:
exit('SELECT CustomerNo, Name, Balance
FROM Customer
WHERE Active = true
ORDER BY Name');

Error Codes: AL0360

5 isolated-storage-secrettext-overload isolated-storage-api 1 CG-AL-H016

Description: The model incorrectly used IsolatedStorage.Set() with a SecretText parameter directly. In AL, IsolatedStorage.Set() has specific overloads for SecretText handling. The model passed SecretText as the second argument, but the compiler expects either a Text value or an array of SecretText for certain overloads. The correct approach is to use the proper IsolatedStorage.SetEncrypted() method or ensure the correct overload signature is used for storing SecretText values.

Correct Pattern:
For storing SecretText in IsolatedStorage, use IsolatedStorage.SetEncrypted() or unwrap the SecretText first if encryption is handled separately. The Set method with SecretText requires: IsolatedStorage.Set(Key: Text, Value: SecretText, DataScope: DataScope) but the model may have hit a version mismatch or incorrect overload. The correct pattern for BC versions supporting SecretText storage is to verify the exact method signature available.
Incorrect Pattern:
IsolatedStorage.Set(ApiKeyStorageKeyTok, ApiKey, DataScope::Module);

Error Codes: AL0133

6 reserved-keyword-variable-name variable-naming-rules 1 CG-AL-H020

Description: The model used 'Key' as a variable name, but 'Key' is a reserved keyword in AL. The compiler errors AL0519 ('Key' is not valid value in this context) and the subsequent syntax errors occur because the AL compiler interprets 'Key' as a keyword rather than a variable identifier. The model should have used a different variable name like 'DictKey', 'KeyValue', or 'K' to avoid the reserved keyword conflict.

Correct Pattern:
var
    DictKey: Text;
begin
    foreach DictKey in Dict1.Keys do begin
Incorrect Pattern:
var
    Key: Text;
begin
    foreach Key in Dict1.Keys do begin

Error Codes: AL0519

7 else-statement-semicolon control-flow-syntax 1 CG-AL-H023

Description: The model generated code with an orphaned ELSE statement, which occurs when a semicolon is incorrectly placed before the ELSE keyword. In AL, the statement before ELSE in an IF-THEN-ELSE construct must NOT end with a semicolon. The model failed to generate any code file (indicated by 'Generated code not found'), and the compilation error AL0110 indicates the model produced syntactically invalid AL code with improper control flow structure.

Correct Pattern:
if Condition then
    DoSomething() // <-- no semicolon before else
else
    DoSomethingElse();
Incorrect Pattern:
// Code not captured but error indicates pattern like:
if Condition then
    DoSomething(); // <-- incorrect semicolon here
else
    DoSomethingElse();

Error Codes: AL0110

8 unused-variable-declaration variable-usage 1 CG-AL-M003

Description: The model declared a variable 'NoSeriesManagement: Codeunit NoSeriesManagement' in the GetNextContractNo procedure but never used it. The compiler error AL0185 indicates the codeunit 'NoSeriesManagement' is missing - this is because the model referenced a codeunit that doesn't exist in the standard BC application (it was renamed/restructured in newer versions). The model attempted to use a pattern for number series management but declared an unnecessary variable for a codeunit that doesn't exist, and then implemented its own logic anyway without using it.

Correct Pattern:
local procedure GetNextContractNo(): Code[20]
var
    SalesContract: Record "Sales Contract";
    LastNo: Code[20];
    NextNo: Integer;
begin
    SalesContract.SetCurrentKey("Contract No.");
    if SalesContract.FindLast() then begin
        LastNo := SalesContract."Contract No.";
        if Evaluate(NextNo, DelChr(LastNo, '=', DelChr(LastNo, '=', '0123456789'))) then
            exit(StrSubstNo('SC-%1', Format(NextNo + 1, 0, '<Integer,5><Filler Character,0>')));
    end;
    exit('SC-00001');
end;
Incorrect Pattern:
local procedure GetNextContractNo(): Code[20]
var
    SalesContract: Record "Sales Contract";
    NoSeriesManagement: Codeunit NoSeriesManagement;
    LastNo: Code[20];
    NextNo: Integer;
begin
    SalesContract.SetCurrentKey("Contract No.");
    if SalesContract.FindLast() then begin
        LastNo := SalesContract."Contract No.";
        if Evaluate(NextNo, DelChr(LastNo, '=', DelChr(LastNo, '=', '0123456789'))) then
            exit(StrSubstNo('SC-%1', Format(NextNo + 1, 0, '<Integer,5><Filler Character,0>')));
    end;
    exit('SC-00001');
end;

Error Codes: AL0185

9 session-logmessage-signature telemetry-logging 1 CG-AL-M005

Description: The model generated code that calls Session.LogMessage with 11 arguments, but the AL built-in LogMessage method only accepts either 9 arguments (with optional last two) or 6 arguments (with Dictionary). The model likely attempted to use a non-existent overload of the LogMessage procedure. Looking at the error at line 272, the model's LogError procedure or some telemetry logging code is incorrectly calling LogMessage with too many parameters.

Correct Pattern:
Session.LogMessage(EventId: Text, Message: Text, Verbosity: Verbosity, DataClassification: DataClassification, TelemetryScope: TelemetryScope, Dimension1Name: Text, Dimension1Value: Text, [Dimension2Name: Text], [Dimension2Value: Text]) - maximum 9 arguments, or use Dictionary overload for more dimensions
Incorrect Pattern:
LogMessage with 11 arguments (exact call not visible but error indicates line 272)

Error Codes: AL0126

10 buffer-table-field-names temporary-table-handling 1 CG-AL-M007

Description: The model attempted to use non-existent fields on Business Central buffer tables. It referenced 'Item No.', 'Description', 'Quantity (Base)' on 'Item Statistics Buffer' and 'Customer No.', 'No. of Invoices' on 'Customer Sales Buffer'. These are standard BC temporary buffer tables but the model assumed incorrect field names. The generated code shown doesn't include these buffer tables, suggesting the model generated additional code beyond what was provided that uses these tables incorrectly. The model lacks knowledge of the actual field structure of BC buffer tables.

Correct Pattern:
The model should either: 1) Use the correct field names from the actual BC buffer table definitions (e.g., 'Item Statistics Buffer' has fields like 'Code', 'Value Entry No.', etc.), or 2) Define custom temporary tables with the needed fields, or 3) Use Dictionary/array-based tracking as shown in the mock calculator codeunit which correctly uses Dictionary of [Code[20], Decimal] patterns.
Incorrect Pattern:
TempItemStatBuffer."Item No." := ItemNo;
TempItemStatBuffer.Description := ItemDesc;
TempItemStatBuffer."Quantity (Base)" := Qty;
TempCustSalesBuffer."Customer No." := CustNo;
TempCustSalesBuffer."No. of Invoices" := InvoiceCount;

Error Codes: AL0132

11 flowfield-calcformula-syntax flowfield-definition 1 CG-AL-M010

Description: The model incorrectly used a multiplication expression within the CalcFormula for a FlowField. In AL, CalcFormula does not support arithmetic operations like multiplication between fields. The CalcFormula can only use aggregate functions (Sum, Count, Average, Min, Max, Exist, Lookup) on a single field with optional filters. To calculate 'Actual Hours * Hourly Rate', you cannot use a FlowField with CalcFormula - you would need either a calculated field updated via triggers, or calculate it in code.

Correct Pattern:
FlowFields with CalcFormula cannot perform arithmetic between fields. Either: (1) Use a single field: CalcFormula = sum("Project Task"."Actual Hours" where("Project Code" = field("Project Code"))); or (2) Remove FieldClass = FlowField and calculate the value in code/triggers, or (3) Add a computed field on Project Task that stores the product and sum that field.
Incorrect Pattern:
CalcFormula = sum("Project Task"."Actual Hours" * "Project Task"."Hourly Rate" where("Project Code" = field("Project Code")));

Error Codes: AL0104

12 multi-table-generation table-definition 1 CG-AL-M112

Description: The task required creating TWO tables: 'CG Project' (ID 70112) and 'CG Time Entry' (ID 70113). The model only generated the first table 'CG Project' and completely omitted the second table 'CG Time Entry'. This caused compilation errors because the 'CG Project' table references 'CG Time Entry' in its FlowField CalcFormula and OnDelete trigger, but that table doesn't exist.

Correct Pattern:
// File: CG-AL-M112.al
table 70112 "CG Project"
{
    Caption = 'CG Project';
    DataClassification = CustomerContent;
    // ... fields and triggers
}

table 70113 "CG Time Entry"
{
    Caption = 'CG Time Entry';
    DataClassification = CustomerContent;

    fields
    {
        field(1; "Entry No."; Integer)
        {
            Caption = 'Entry No.';
            AutoIncrement = true;
        }
        field(2; "Project No."; Code[20])
        {
            Caption = 'Project No.';
            NotBlank = true;
            TableRelation = "CG Project"."No.";
        }
        field(3; "Entry Date"; Date)
        {
            Caption = 'Entry Date';
        }
        field(4; Hours; Decimal)
        {
            Caption = 'Hours';
            trigger OnValidate()
            begin
                if Hours <= 0 then
                    Error('Hours must be greater than zero');
            end;
        }
        field(5; Posted; Boolean)
        {
            Caption = 'Posted';
            InitValue = false;
        }
    }

    keys
    {
        key(PK; "Entry No.")
        {
            Clustered = true;
        }
    }

    trigger OnInsert()
    begin
        if "Entry Date" = 0D then
            "Entry Date" := WorkDate();
    end;
}
Incorrect Pattern:
// File: CG-AL-M112.al
table 70112 "CG Project"
{
    // ... only one table generated
}

Error Codes: AL0185

13 report-layout-requirement report-definition 1 CG-AL-E007

Description: The model generated a valid AL report structure with correct dataset columns, but failed to include a layout definition. In Business Central, reports require a valid layout (Word, RDLC, Excel, or Custom) to be runnable. The error 'Report 70000 does not have a valid layout' indicates the report compiles but cannot execute without a rendering layout. The model should have either included a DefaultRenderingLayout property pointing to a layout file, or included an rdlcLayout/wordLayout property, or at minimum a rendering section.

Correct Pattern:
report 70000 "Customer List Report"
{
    UsageCategory = ReportsAndAnalysis;
    ApplicationArea = All;
    Caption = 'Customer List Report';
    DefaultRenderingLayout = CustomerListLayout;

    dataset
    {
        dataitem(Customer; Customer)
        {
            column(No_Customer; "No.") { }
            column(Name_Customer; Name) { }
            column(City_Customer; City) { }
            column(PhoneNo_Customer; "Phone No.") { }
        }
    }

    rendering
    {
        layout(CustomerListLayout)
        {
            Type = RDLC;
            LayoutFile = 'CustomerListReport.rdl';
        }
    }
}
Incorrect Pattern:
report 70000 "Customer List Report"
{
    UsageCategory = ReportsAndAnalysis;
    ApplicationArea = All;
    Caption = 'Customer List Report';

    dataset
    {
        dataitem(Customer; Customer)
        {
            column(No_Customer; "No.")
            {
            }
            ...
        }
    }
}
14 flowfield-calcfields-requirement flowfield 1 CG-AL-E009

Description: The model correctly created the XMLport structure, but the test fails because the 'Inventory' field on the Item table is a FlowField. When exporting FlowFields via XMLport, the value is not automatically calculated - it exports as 0 or empty unless CalcFields is called. The model should have added a trigger to calculate the Inventory field before export, or the XMLport needs OnBeforePassField/OnAfterGetRecord trigger to call CalcFields on the Inventory field.

Correct Pattern:
tableelement(Item; Item)
{
    XmlName = 'Item';
    
    trigger OnAfterGetRecord()
    begin
        Item.CalcFields(Inventory);
    end;
    
    fieldelement(Inventory; Item.Inventory)
    {
        XmlName = 'Inventory';
    }
}
Incorrect Pattern:
fieldelement(Inventory; Item.Inventory)
{
    XmlName = 'Inventory';
}
15 ondelete-trigger-active-check table-triggers 1 CG-AL-E031

Description: The model generated an OnDelete trigger that checks 'if Rec.Active then Error(...)'. However, the test shows that when a record is inserted with Active defaulting to true (via InitValue), and then the test calls Plan.Delete(), the asserterror expects an error but none is raised. The issue is that the Active field has InitValue = true, but when the record is retrieved after insert and delete is called, the OnDelete trigger's check of Rec.Active should find it true. The test failure 'An error was expected inside an ASSERTERROR statement' indicates the OnDelete trigger did NOT raise an error when it should have. This suggests the model's code may have an issue with how the Active field value is being read in the OnDelete trigger context, or there's a subtle issue with how InitValue works with the trigger check.

Correct Pattern:
The OnDelete trigger should check the Active field value. The issue may be that 'Rec.Active' vs just 'Active' behaves differently, or the InitValue isn't being properly evaluated. The correct pattern should simply be 'if Active then Error(...)' without the Rec prefix, as in triggers the current record fields are directly accessible.
Incorrect Pattern:
trigger OnDelete()
begin
    if Rec.Active then
        Error('Cannot delete active subscription plan');
end;
16 temporary-table-parameter-instance-isolation temporary-table 1 CG-AL-H003

Description: The model's code is technically correct for temporary table handling, but the test failure reveals a subtle issue with how the test environment works. The test 'TestHighInventoryDiscount' searches for an Item with Inventory >= 100 and Unit Price > 0, then calls ProcessItemsWithDiscount expecting that item to appear in results. The test fails because either: (1) no items in the test database meet the criteria (Inventory >= 100 AND Unit Price > 0), or (2) there's a mismatch between how the test finds items and how the processor filters them. The model correctly implemented the logic, but the test assumes specific data exists in the database. However, looking more carefully - the model's code filters Item.SetFilter("Unit Price", '>0') which should match the test's filter. The real issue is that the test database may not have items with both high inventory AND positive unit price. This is actually a test_logic_bug since the test assumes data exists without ensuring it.

Correct Pattern:
The code is correct. The test assumes items exist with Inventory >= 100 AND Unit Price > 0, but such items may not exist in the test database. The test should either create test data or handle the case where no matching items exist more gracefully.
Incorrect Pattern:
Item.SetFilter("Unit Price", '>0');
if Item.FindSet() then
    repeat
        DiscountPercent := CalculateDiscountPercent(Item.Inventory);
        if DiscountPercent >= MinDiscount then begin
            // add to TempResult
        end;
    until Item.Next() = 0;
17 format-decimal-locale decimal-formatting 1 CG-AL-H005

Description: The model correctly implemented the OnModify trigger logic to detect changes and create audit log entries. However, the test fails because the Format() function for Decimal values in Business Central includes locale-specific formatting (e.g., '100.00' instead of '100'). The test filters for exact string matches like '100' and '150', but Format(xRec."Unit Price") produces formatted decimal strings that may include decimal places (e.g., '100.00' or locale-specific formatting). The model's code is functionally correct, but the formatted output doesn't match the test's expected string values.

Correct Pattern:
The Format() function with Decimal values produces locale-aware output with decimal places. The test expects '100' but Format(100.00) likely produces '100.00' or similar. Either the test should use more flexible matching (like STRPOS or contains logic), or the code should use Format with specific formatting parameters to control output, such as Format(xRec."Unit Price", 0, '<Integer>') for integer-only output.
Incorrect Pattern:
CGAuditLog."Old Value" := Format(xRec."Unit Price");
CGAuditLog."New Value" := Format(Rec."Unit Price");
18 interface-collections-bc-version interface-collections 1 CG-AL-H021

Description: The model generated code using 'List of [Interface "INotificationChannel"]' and 'Dictionary of [Text, Interface "INotificationChannel"]' which are features introduced in BC 2025 Wave 1. The error 'System.InvalidOperationException: C# compilation has failed for the application object CodeUnit_70220' indicates that the runtime/compiler doesn't support interfaces as generic type parameters in List and Dictionary collections. This is likely because the BC container being used doesn't have the 2025 Wave 1 runtime that supports this feature. The model correctly implemented the syntax as specified in the task, but the test environment doesn't support this BC version-specific feature.

Correct Pattern:
This syntax is correct for BC 2025 Wave 1, but the test environment appears to be running an older BC version that doesn't support interfaces as generic type parameters. The task itself requires BC 2025 Wave 1 features that may not be available in the test container.
Incorrect Pattern:
var
    ChannelList: List of [Interface "INotificationChannel"];
    ChannelDictionary: Dictionary of [Text, Interface "INotificationChannel"];
19 api-page-field-name-mapping api-page-design 1 CG-AL-M001

Description: The model created an API page with field names that don't match what the test expects. The test expects 'productCode' to map to the record's 'No.' field (Product.SetRange("No.", ProductCode)), but the model created a separate 'Product Code' field and mapped 'productCode' to that instead of 'No.'. The test creates products using productCode.SetValue() and expects it to populate the 'No.' field, but the generated code has 'number' mapped to 'No.' and 'productCode' mapped to a separate 'Product Code' field. Additionally, the error messages for validation don't match - test expects 'Price must be positive' but model generates 'Unit Price must be a positive value. Current value: %1', and test expects 'Stock must be non-negative' but model generates 'Stock Quantity must be non-negative. Current value: %1'.

Correct Pattern:
field(productCode; Rec."No.")
{
    Caption = 'Product Code';
}
...
PriceMustBePositiveErr: Label 'Price must be positive';
StockMustBeNonNegativeErr: Label 'Stock must be non-negative';
Incorrect Pattern:
field(number; Rec."No.")
{
    Caption = 'No.';
}
field(productCode; Rec."Product Code")
{
    Caption = 'Product Code';
    ...
}
...
UnitPriceMustBePositiveErr: Label 'Unit Price must be a positive value. Current value: %1';
StockQuantityMustBeNonNegativeErr: Label 'Stock Quantity must be non-negative. Current value: %1';
20 risk-level-option-values option-field-definition 1 CG-AL-M006

Description: The model defined the Risk Level option field with a blank first option (' ',Low,Medium,High,Critical) instead of starting with the actual values (Low,Medium,High,Critical). This causes the option ordinal values to be off by one. The test expects 'Low' to be ordinal 0, 'Medium' to be 1, 'High' to be 2, and 'Critical' to be 3, but the model's definition makes them 1, 2, 3, and 4 respectively. Additionally, the model's UpdateRiskLevel() uses different credit score thresholds than what the tests expect (model uses 750/650/550 boundaries with payment history adjustment, but tests expect specific ranges like 300-499=Critical, 500-579=High, 580-669=Medium, 670+=Low). The ValidateNewOrder function also throws an error instead of returning false for rejected orders, and the test cleanup after asserterror fails because the record doesn't exist (asserterror rolls back the transaction including the customer creation).

Correct Pattern:
field(70002; "Risk Level"; Option)
{
    OptionMembers = Low,Medium,High,Critical;
}

procedure UpdateRiskLevel()
begin
    // Tests expect: 300-499=Critical, 500-579=High, 580-669=Medium, 670+=Low
    case true of
        "Credit Score" >= 670:
            "Risk Level" := "Risk Level"::Low;
        "Credit Score" >= 580:
            "Risk Level" := "Risk Level"::Medium;
        "Credit Score" >= 500:
            "Risk Level" := "Risk Level"::High;
        else
            "Risk Level" := "Risk Level"::Critical;
    end;
end;

procedure ValidateNewOrder(OrderAmount: Decimal): Boolean
begin
    // Should return false, not throw error
    if OrderAmount > AvailableCredit then
        exit(false);
    exit(true);
end;
Incorrect Pattern:
field(70002; "Risk Level"; Option)
{
    OptionMembers = " ",Low,Medium,High,Critical;
}

procedure UpdateRiskLevel()
begin
    case true of
        AdjustedScore >= 750:
            "Risk Level" := "Risk Level"::Low;
        AdjustedScore >= 650:
            "Risk Level" := "Risk Level"::Medium;
        AdjustedScore >= 550:
            "Risk Level" := "Risk Level"::High;
        else
            "Risk Level" := "Risk Level"::Critical;
    end;
end;

procedure ValidateNewOrder(OrderAmount: Decimal): Boolean
begin
    if OrderAmount > AvailableCredit then
        Error(OrderExceedsCreditLimitErr, ...);
    exit(true);
end;
21 codeunit-instance-state-persistence codeunit-state-management 1 CG-AL-M008

Description: The model implemented workflow state using Dictionary variables at the codeunit level, but these are instance-scoped and not persisted between codeunit calls. In the test, InitiateApprovalProcess is called, then ProcessApprovalRequest with 'APPROVE' action, which internally calls CompleteApprovalProcess and sets status to 2 (Approved). When the test then calls CompleteApprovalProcess directly, the status is already 2 (not 1/Pending), so the function returns false because it checks 'if CurrentStatus = 1' before completing. The model failed to understand that after approval, the status changes and subsequent CompleteApprovalProcess calls would fail the pending check.

Correct Pattern:
The CompleteApprovalProcess should either: 1) Not be called internally by ProcessApprovalRequest when approving, leaving status as 1 (Pending) so the explicit CompleteApprovalProcess call can succeed, OR 2) Check for status = 1 OR status = 2 (already approved) and return true in both cases, OR 3) Use a different status flow where APPROVE sets an intermediate status that CompleteApprovalProcess then finalizes.
Incorrect Pattern:
procedure ProcessApprovalRequest(...):
  case Action of
    'APPROVE':
      begin
        ApprovalHistoryBuffer.Set(DocumentNo, HistoryCount + 1);
        CompleteApprovalProcess(DocumentNo); // Sets status to 2
        exit(true);
      end;

procedure CompleteApprovalProcess(DocumentNo: Code[20]): Boolean
begin
  if not ApprovalStatusBuffer.ContainsKey(DocumentNo) then
    exit(false);
  // After APPROVE, status is already 2, not 1
  ApprovalStatusBuffer.Set(DocumentNo, 2);