Skip to content
Finance · Accounting module

A custom accounting module for the controls your auditor keeps asking about

ERPNext and Odoo both ship a proper double-entry ledger, and you shouldn't replace it. What finance teams usually need is the layer on top. A custom accounting module adds the journal controls, month-end allocations and branch reports your auditor and your finance director keep asking about, without touching the ledger itself.

ERPNext v16

Works with ERPNext v15 and v16, and Odoo 17, 18 and 19.

Features

What your accounting module can do

Manual journal guardrails

Mark accounts as restricted, require a reason above a threshold, and keep automated entries such as depreciation out of the rules entirely.

Allocations as reviewable drafts

Shared costs are split by a rule you can read, and the result arrives as a draft journal entry. Finance checks it and posts. The code never posts on its own.

Branch and department reporting

Profit and loss by cost center or analytic account, with the columns your finance director actually asks for, not a generic export.

Month-end checklists in the ERP

Bank reconciled, accruals posted, allocations reviewed. Each step has an owner and a date, so the close doesn't live in someone's notebook.

An audit trail in plain view

Who posted what, when, against which rule and with what reason. Exportable when the auditors arrive.

The output

What actually gets generated

Real files in a real repository. Here’s the typical output when someone asks for accounting.

ERPNext / Frappe app

  • Restrict Manual Posting check field on Account (fixture)
  • Journal Entry validate hook in hooks.py
  • Overhead Allocation Rule DocType with branch percentages
  • Monthly job that drafts allocation Journal Entries
  • Branch Profit and Loss Script Report

Odoo addon

  • account.account flag for restricted manual posting
  • account.move _post guard for manual entries
  • Allocation rule model using account.analytic.account
  • ir.cron that drafts monthly allocation entries
  • Pivot and list views for allocation history
finance_controls/journal_entry.py
import frappe
from frappe import _
from frappe.utils import flt, fmt_money

REASON_THRESHOLD = 5000
# Types people post by hand. Depreciation, revaluation and deferred
# revenue entries come from the system and skip these checks.
MANUAL_TYPES = ("Journal Entry", "Bank Entry", "Cash Entry", "Credit Note",
                "Debit Note", "Contra Entry", "Write Off Entry")


def validate_manual_journal(doc, method=None):
    """Journal Entry.validate: guard manual postings."""
    if doc.voucher_type not in MANUAL_TYPES:
        return

    is_manager = "Accounts Manager" in frappe.get_roles()
    for row in doc.accounts:
        restricted = frappe.get_cached_value("Account", row.account, "restrict_manual_posting")
        if restricted and not is_manager:
            frappe.throw(
                _("Row {0}: {1} is restricted. Post through the source document "
                  "or ask an Accounts Manager.").format(row.idx, frappe.bold(row.account)),
                title=_("Restricted account"),
            )

    if flt(doc.total_debit) > REASON_THRESHOLD and not (doc.user_remark or "").strip():
        frappe.throw(
            _("Manual journals over {0} need a reason in User Remark.").format(
                fmt_money(REASON_THRESHOLD, currency=frappe.get_cached_value("Company", doc.company, "default_currency"))
            ),
            title=_("Reason required"),
        )
Trimmed excerpt. The full module includes tests, fixtures and a README.

How it works

From a paragraph to a pull request

The long version
  1. 01

    Describe it

    In your own words. Paste the spreadsheet or a photo of the paper form if that's easier.

  2. 02

    Answer a couple of questions

    It asks only what it can't work out from your setup, like who's allowed to override.

  3. 03

    Try it on a sandbox

    A copy of your site with the module installed. Break it, then ask for changes.

  4. 04

    Merge when it's right

    Code lands as a pull request with tests. Your developer, or ours, reviews it first.

Leave the general ledger alone

We’ll start with the thing we refuse to do. We don’t write general ledgers. ERPNext and Odoo already post balanced entries, handle multi-currency, close periods and produce a trial balance that ties. Rewriting any of that is a way to spend a lot of money on a future audit problem.

What does go wrong is everything around the ledger. Someone posts a manual journal to the receivables control account to make a balance look right. Shared rent gets split by gut feel. The branch P&L lives in a spreadsheet that’s rebuilt every month and argued over at every board meeting. Those are the problems a module can fix.

What belongs in a custom accounting module

Three kinds of thing, mostly.

Controls before posting. Rules about who can post what, when a reason is required and which accounts should only ever be touched by source documents like invoices and payments.

Entries drafted by rules. Allocations, recurring accruals and intercompany recharges, generated as drafts from a rule someone can read, then posted by a person.

Reports built for your structure. Branch, department or project P&L from cost centers in ERPNext or analytic accounts in Odoo, laid out the way your board pack wants.

On ERPNext, that becomes a Frappe app with hooks on Journal Entry, a rule DocType, a scheduled job and a Script Report. On Odoo, it’s an addon that guards account.move posting, reads account.analytic.account for distribution and runs allocations on an ir.cron. In both cases the code stays out of core, which is why the approach in our guide to keeping ERPNext customizations through upgrades applies here too.

Worked example: three branches, one rent bill

A group runs three branches from one leased building. Rent is $18,000 a month on a single supplier invoice. The branches occupy 1,000 m², 600 m² and 400 m².

On the first working day of the month, the module drafts a journal entry that moves rent out of the shared cost center: $9,000 to branch one, $5,400 to branch two and $3,600 to branch three. The draft includes the rule name and the floor areas used, in the remark. The finance manager glances at it and submits. If branch two takes an extra 100 m² in March, someone updates the rule once and April’s draft follows.

Separately, a junior accountant tries to post a $7,500 manual journal with no remark. The system asks for a reason. Later they try to credit the trade receivables account directly, which has been marked restricted. That’s blocked, with a message pointing them to a credit note or payment instead. An accounts manager can still post it when there’s a genuine reason, and the entry carries their name.

Notice what the module doesn’t do. It doesn’t decide whether the $7,500 journal is correct. It makes sure there’s a written reason attached, so when the auditor picks it out of a sample in eleven months’ time, the accountant who posted it doesn’t have to reconstruct the story from memory. Controls like this are less about stopping fraud and more about stopping the slow drift of entries that even their authors can’t explain a year later.

The snippet on this page is the journal guard. About 30 lines, and most of it is the error messages.

Controls we’d skip, or do differently

  • Blocking backdated entries in custom code. Both platforms have period and lock date features. Use those. A second, homemade lock just confuses people about which one applies.
  • Allocations that post automatically. A draft costs a finance manager thirty seconds. An automatic wrong posting costs a reversal, a note and a conversation.
  • Approval on every journal entry. If every entry needs approval, approvers stop reading. Put the gate on restricted accounts and large amounts, and trust the rest.
  • A parallel tax calculation. If tax is wrong, fix the tax configuration.

Where accounting meets the rest

The ledger is where the rest of your ERP ends up. Clean data from invoice management and expense claims makes month end shorter than any report can. If the control you need depends on how ERPNext is configured rather than on new code, start with our page on ERPNext customization. And if your finance processes are unusual enough that neither platform fits, custom ERP development is the alternative we’d look at.

Guides and terms for accounting

Accounting module questions

Something missing? Email [email protected] and a person will answer.

Are you going to change how the general ledger works?

No. Every entry still goes through the platform's own posting logic, so trial balance, tax reports and financial statements behave as they always did. The module adds checks before posting and drafts entries for people to review.

Will this work with our country's localisation and tax reports?

It sits alongside them. We don't generate tax calculations or statutory reports where a localisation already covers them. If yours has a gap, we'll tell you what we'd build and suggest you check the result with your accountant.

What happens to the controls when we upgrade?

They live in a separate Frappe app or Odoo addon. Journal and account structures are among the more stable parts of both platforms, but accounting is exactly where you should run the tests on a copy before upgrading live.

Can we migrate our chart of accounts and history from QuickBooks or Xero?

Yes. The usual approach is the chart of accounts, opening balances at a clean cut-off date and open invoices and bills. Full transaction history can come across too, but it's slow and rarely worth the reconciliation effort.

Do we need ERPNext cost centers or Odoo analytic accounts for branch reporting?

You need one of them, set up before the transactions arrive. Adding branch tags retrospectively is painful on any platform. If you're not using them yet, that's the first job, before any custom report.

Who can change the rules later?

Thresholds and restricted accounts are settings your finance team can edit. The logic itself is code in your own repository, so changes go through whoever maintains it, which is how you'd want it in finance.

Your next module is one paragraph away

Write it the way you’d explain it to a new hire. We’ll turn it into an app you can read, test and install.

ERPNext v16

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.