diff --git a/community/knowledge/finance/do-not-modify-or-delete-posted-ledger-entries.md b/community/knowledge/finance/do-not-modify-or-delete-posted-ledger-entries.md new file mode 100644 index 0000000..0f8eb7f --- /dev/null +++ b/community/knowledge/finance/do-not-modify-or-delete-posted-ledger-entries.md @@ -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. diff --git a/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.bad.al b/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.bad.al new file mode 100644 index 0000000..3d6fe51 --- /dev/null +++ b/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.bad.al @@ -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; +} diff --git a/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.good.al b/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.good.al new file mode 100644 index 0000000..761b5a7 --- /dev/null +++ b/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.good.al @@ -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; +} diff --git a/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.md b/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.md new file mode 100644 index 0000000..a78186a --- /dev/null +++ b/community/knowledge/finance/post-ledger-entries-through-posting-codeunits.md @@ -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`. diff --git a/community/knowledge/finance/write-dimensions-as-dimension-set-entries.md b/community/knowledge/finance/write-dimensions-as-dimension-set-entries.md new file mode 100644 index 0000000..a13d91c --- /dev/null +++ b/community/knowledge/finance/write-dimensions-as-dimension-set-entries.md @@ -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.