Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
bc-version: [all]
domain: finance
keywords: [ledger-entry, immutable, reversal, audit-trail, correction, custentry-edit, non-financial-fields]
technologies: [al]
countries: [w1]
application-area: [finance]
---

# Correct posted ledger entries by reversing, not by editing or deleting them

## Description

A posted ledger entry is part of Business Central's permanent audit trail, and its **financial content** is immutable. The amounts, accounts, posting date, and quantities on `G/L Entry`, `Cust. Ledger Entry`, `Vendor Ledger Entry`, and similar tables must not change after posting, because registers, applications, VAT statements, and statutory reports all assume that content is append-only. Correcting financial content is therefore itself a posting: BC provides reversal (the `Reverse` / `Reverse Register` routines) and correcting documents (credit memos, correcting journals) that post a new, offsetting entry and leave the original intact. This immutability rule is about financial content — it is not a blanket ban on touching the entry (see Best Practice).

## Best Practice

To undo or correct a posting's **financial** content, post a reversing or correcting entry through the normal posting path so the offset is itself a balanced, dated, traceable transaction. The original entry stays in place and the two net to zero, preserving the audit trail.

Non-financial **operational** fields are a deliberate exception and are meant to be edited after posting. BC supports updating a defined set of post-posting fields — payment and application data such as due date, payment-discount dates, on-hold status, applies-to ID, and recipient/communication fields — and the Customer and Vendor Ledger Entries pages expose several of them as editable. Make those changes through the dedicated `CustEntry-Edit` / `VendEntry-Edit` routines (which those pages call), not a raw `Modify`, so the edit stays within the supported set and leaves the entry's financial content and audit trail intact.

## Anti Pattern

Calling `Modify` or `Delete` on a posted ledger entry to fix a mistake — changing an amount, repointing an account, or removing the row. Detection signal: `Modify`, `ModifyAll`, `Delete`, or `DeleteAll` on a `*Ledger Entry` or `G/L Entry` record outside a dedicated `*Entry-Edit` routine. This destroys the audit trail, desynchronizes the entry from its register and detailed entries, and corrupts any report or reconciliation that already consumed the original value.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Demonstration only. Self-contained illustration, not derived from the
// Business Central base application source.
//
// BAD: insert straight into the ledger table and hand-compute Entry No.
// The row is unbalanced (no balancing entry), has no register, no resolved
// dimensions, and no VAT. FindLast + "+ 1" races under concurrency and will
// collide on the primary key. Reconciliation treats the result as corrupt.
codeunit 50100 "Post GL Adjustment"
{
procedure PostAdjustment(AccountNo: Code[20]; Amount: Decimal; PostingDate: Date)
var
GLEntry: Record "G/L Entry";
LastGLEntry: Record "G/L Entry";
begin
if LastGLEntry.FindLast() then;

GLEntry.Init();
GLEntry."Entry No." := LastGLEntry."Entry No." + 1;
GLEntry."G/L Account No." := AccountNo;
GLEntry.Amount := Amount;
GLEntry."Posting Date" := PostingDate;
GLEntry.Insert();
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Demonstration only. Self-contained illustration, not derived from the
// Business Central base application source.
//
// GOOD: build a journal line and let the posting engine create the ledger
// entry. The platform assigns Entry No., writes the balancing entry, creates
// the register, and resolves dimensions and VAT.
codeunit 50100 "Post GL Adjustment"
{
procedure PostAdjustment(AccountNo: Code[20]; BalAccountNo: Code[20]; Amount: Decimal; PostingDate: Date)
var
GenJnlLine: Record "Gen. Journal Line";
GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
begin
GenJnlLine.Init();
GenJnlLine."Posting Date" := PostingDate;
GenJnlLine."Document Type" := GenJnlLine."Document Type"::" ";
GenJnlLine."Account Type" := GenJnlLine."Account Type"::"G/L Account";
GenJnlLine.Validate("Account No.", AccountNo);
GenJnlLine."Bal. Account Type" := GenJnlLine."Bal. Account Type"::"G/L Account";
GenJnlLine.Validate("Bal. Account No.", BalAccountNo);
GenJnlLine.Validate(Amount, Amount);
GenJnlLine."Source Code" := 'ADJUST';

GenJnlPostLine.Run(GenJnlLine);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
bc-version: [all]
domain: finance
keywords: [posting, g-l-entry, ledger-entry, gen-jnl-post-line, journal-line, balancing, entry-no]
technologies: [al]
countries: [w1]
application-area: [finance]
---

# Post ledger entries through the posting engine, never by inserting them directly

## Description

Ledger entries — `G/L Entry`, `Cust. Ledger Entry`, `Vendor Ledger Entry`, `Item Ledger Entry`, and their detailed counterparts — are the output of Business Central's posting engine, not ordinary tables an extension writes to. The supported way to create them is to populate a journal line (for example `Gen. Journal Line`) and run the matching posting codeunit (`Gen. Jnl.-Post Line`, codeunit 12, for general-ledger postings). The posting routine enforces double-entry balancing, allocates `Entry No.` safely under concurrency, creates the register, resolves dimensions, applies VAT, and links source and application data. None of that happens when a row is inserted into the ledger table directly.

## Best Practice

Build the transaction as one or more journal lines and hand them to the posting codeunit. Let the engine assign `Entry No.`, create the `G/L Register`, and write the balancing entries. When you need a reusable entry point, wrap the journal-line setup in your own codeunit but still post through `Gen. Jnl.-Post Line` — or through the document-posting routines (sales, purchase, service) that ultimately call it. See sample: `post-ledger-entries-through-posting-codeunits.good.al`.

## Anti Pattern

Calling `Insert` on a ledger table — typically after a `FindLast` to guess the next `Entry No.` Detection signal: any `Insert` on a `*Ledger Entry` or `G/L Entry` record, or an `Entry No.` computed in AL rather than returned by the platform. Such code produces an unbalanced, registerless, dimensionless row that reconciliation and reporting will treat as corrupt, and the hand-computed `Entry No.` races under concurrency. See sample: `post-ledger-entries-through-posting-codeunits.bad.al`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
bc-version: [all]
domain: finance
keywords: [dimensions, dimension-set-id, dimension-set-entry, global-dimension, shortcut-dimension, dimensionmanagement]
technologies: [al]
countries: [w1]
application-area: [finance]
---

# Treat the Dimension Set ID as the source of truth for dimensions

## Description

Modern Business Central stores the dimensions of a record as a single `Dimension Set ID` that points at an immutable combination of `Dimension Set Entry` rows. The `Global Dimension 1 Code` / `Global Dimension 2 Code` fields and the `Shortcut Dimension 3..8` fields are denormalized projections the platform keeps in sync — they are not the source of truth, and they cover only a handful of the up to eight dimensions a set can hold. New or merged dimension combinations are obtained from codeunit `DimensionManagement` (for example `GetDimensionSetID`), which returns the `Dimension Set ID` to store on the record.

## Best Practice

When code sets, copies, or merges dimensions, work in terms of `Dimension Set ID` values and resolve or create them through `DimensionManagement`; assign the resulting set ID to the record and let the platform derive the global and shortcut projections. On records that expose shortcut-dimension fields (journal and document lines), call `Validate` on those fields — the field's logic updates the `Dimension Set ID` for you. To combine dimensions from several sources (document plus customer, header plus line) use the dimension-set combination routines instead of copying individual codes.

## Anti Pattern

Direct assignment (`:=`) to `Global Dimension 1 Code` or `Global Dimension 2 Code`, or reading those fields to determine "the dimensions," as if they were the record's dimension state. Detection signal: an assignment of a global/shortcut dimension field with no corresponding `Dimension Set ID` update, or analysis logic that branches on the global-dimension fields rather than the set entries. The projection silently disagrees with the set once a third dimension is involved, so posting and analysis-by-dimension produce wrong results.
Loading