Posting rules that survive a real close
Every accounting system posts to the general ledger. The ones that survive a real month-end close are the ones that made posting a single, auditable path — not a helper that each module calls in its own slightly different way.
The rule that pays for itself
One function writes to the GL. Everything else — sales invoices, cash receipts, disbursements, journal vouchers — hands it a balanced set of lines and lets it decide nothing. The moment a second module is allowed to write its own entries, the two implementations start to disagree, and the disagreement only shows up as a trial balance that is out by a few centavos three weeks later.
What that buys you
- Debits and credits are checked in exactly one place.
- The fiscal period check happens once, so a closed period is genuinely closed.
- Reversal is symmetrical, because there is only one shape to reverse.
- The audit trail is a by-product rather than a feature someone remembered.
If you cannot point at the one function that writes to the ledger, you do not have a posting rule. You have a convention, and conventions drift.
Making it enforceable
A convention that lives in a code review is not enforceable. Put the balance check where the database can refuse the write:
-- Refuse an unbalanced batch outright.
SELECT batch_id
FROM gl_details
GROUP BY batch_id
HAVING SUM(debit) <> SUM(credit);Run that as a guard before the batch is committed, not as a report someone reads on the fifth working day. The difference between the two is whether the error is a rejected save or a reconciliation.