ERP Core packages shared, cross-cutting requirements used across Kodlyft's ERPNext deployments, so they live in one versioned place instead of being duplicated per site.
It currently ships three modules:
| Module | What it does |
|---|---|
| Cheque Management | Cheque books, per-leaf status tracking, void reasons, and post-dated cheques received from customers |
| Gate Pass | Inward/outward material passes, returnable-material tracking, and security check-in/check-out |
| Monthly Closing | Per-company month-end close: checklist, automated checks, exceptions, review and approval, an enforced lock through native Accounting Period, sealed evidence packet, controlled reopening |
Status: active development. Cheque books and gate passes are functional. Inward cheque receipts, cheque printing, visitor passes, print formats and reports are still landing. Expect the surface area to change until a
1.0release. Watch the releases page.
ERPNext gives you a free-text Cheque/Reference No on Payment Entry and a
Reference Number on Journal Entry. There is no cheque book, no per-cheque status, no
void register, and no way to answer "which cheque numbers are still unused?"
A Cheque Book is a bank account plus a contiguous range of pre-printed numbers. Submitting one generates a Cheque Leaf document per number, each with its own status.
Cheque Book CHQ-BK-2026-0001
bank_account HBL Current
prefix "A" start 100101 ×25 leaves
│
└─▶ 25 Cheque Leaf records: A100101 … A100125
Overlapping books are rejected: if a leaf in [start, end] already exists on the same
bank account, submission throws and names the conflicting book. This catches the common
real-world error of entering the same book twice.
Books above the configured threshold generate their leaves in a background job, which is resumable. A retry after a worker timeout tops the book up rather than duplicating it.
Unused ──▶ Reserved ──▶ Issued ──▶ Cleared
│ │ │
│ │ ├──▶ Stopped (stop payment)
│ │ └──▶ Bounced (returned unpaid)
├──▶ Void (misprint, wrong payee, … - reason required)
└──▶ Lost
- Reserved is set when a draft voucher selects a leaf, so two users cannot both draft against the same cheque and discover the clash only at submit.
- Void requires a reason code Reasons are a master
(
Cheque Void Reason), not a hardcoded list, because they are reported on and differ per client. - Cancelling a voucher voids its leaf with reason Cancelled Voucher rather than
returning it to the pool. A cheque physically handed over is gone regardless of what
happens to the entry, and the number must stay traceable. Sites that genuinely re-run
misprinted stationery can flip
Allow Void Leaf Reusein Cheque Settings.
Selecting a Cheque Leaf on a Payment Entry or Journal Entry fills in the existing
reference_no / cheque_no and date fields.
The existing Data fields are deliberately left alone rather than converted to Links.
They are mandatory for every bank transaction, wire transfers, IBFT, online payments,
not just cheques, so a Link would make non-cheque payments impossible to record. (Frappe's
ALLOWED_FIELDTYPE_CHANGE forbids Data → Link in any case.)
Three independent layers stop two vouchers consuming one leaf:
- the link query never offers a leaf that is not available,
- a controller guard throws on save, and
- a unique index on
Cheque Leaf(reference_doctype, reference_name).
Only the third survives a genuine concurrent double-submit, which is why it exists.
ERPNext writes clearance_date from three places, and only one of them runs hooks:
| Source | Mechanism | Fires on_change? |
|---|---|---|
| Bank Clearance | Document.db_set |
Yes |
| Bank Reconciliation Tool | raw frappe.db.set_value |
No |
| Bank Transaction | raw frappe.db.set_value |
No |
So a document hook alone would leave cheques cleared through reconciliation stuck on Issued forever. A daily scheduled reconciler is the authoritative sync; the hook is just the fast path.
Cheque Receipt records customer cheques, including post-dated ones, through
Received → Deposited → Cleared / Bounced / Returned.
These are registers only no GL entries are posted. The Payment Entry you create on clearing does all the accounting, which keeps the cheque register from ever double-counting against the ledger.
Records material physically crossing a site boundary. A gate pass is a security document, not a stock document: Purchase Receipt, Delivery Note and Stock Entry keep owning the ledger.
Direction plus returnability derives the type, and the naming series follows it. So the document number itself tells you what kind of pass it is.
| Direction | Returnable | Type | Series |
|---|---|---|---|
| Inward | No | IGP | GP-IGP-.YYYY.-.##### |
| Outward | No | NRGP | GP-NRGP-.YYYY.-.##### |
| Either | Yes | RGP | GP-RGP-.YYYY.-.##### |
Items can be pulled from a Purchase Order, Delivery Note or Stock Entry, and pulling from a second document appends rather than replacing what you already collected.
Draft ─▶ Pending Approval ─▶ Approved (Frappe Workflow, optional)
│
guard verifies ─▶ At Gate
│
check in / out
│
┌────────────────┴─────────────────┐
non-returnable returnable
│ │
Exited (outward) Exited ─▶ Partly Returned ─▶ Returned
Received (inward) └──▶ Overdue
The approval workflow deliberately stops at Approved. Everything after that is an
operational event a guard records on an already-submitted document, and Frappe Workflow
cannot stamp a timestamp or a user for you, so those are one-tap whitelisted methods
instead. The workflow writes into the same status field, so there is no
workflow_state/status pair to keep in sync.
Returns are a separate Gate Pass Return document. The same idiom as Purchase Order → Purchase Receipt. Each return carries its own date, gate, guard and condition per line, which mutating quantities on the original pass could never record.
Returning more than is still outside is refused, and the outstanding calculation excludes the return's own prior version so amending cannot double-count.
Gate Keeper gets read access only. Check-in, check-out and verification go through
whitelisted methods that write a fixed allow-list of fields, so a guard can stamp a
timestamp on a submitted pass but can never alter quantities, parties or items.
Vehicles and drivers link to ERPNext's existing Vehicle and Driver masters rather
than being free text.
A Monthly Close coordinates one company's month end: a frozen checklist, a versioned set of automated checks, approved exceptions, preparer/reviewer/manager approval, and a hard close that locks the month through an ERPNext Accounting Period owned by the close. Closing seals a packet of report snapshots (Trial Balance, P&L month and year-to-date, Balance Sheet, AR/AP ageing, stock vs accounts) with SHA-256 hashes. Reopening needs a reason and a second Close Manager, and starts a new revision. The earlier revision's evidence is kept and marked Superseded.
It never posts anything. It does not create a Period Closing Voucher, zero income and expense accounts, revalue currencies, run depreciation or touch the company freeze date.
- Roles: Close Preparer, Close Reviewer, Close Manager, Close Auditor.
- Set up a Monthly Close Policy per company (first managed month, checklist template, check severities and tolerances, suspense accounts). Installing or upgrading never creates a policy, so no historical month is managed or locked automatically.
- Workspace: Monthly Closing. Reports: Status, Outstanding Tasks, Exceptions, Lock Integrity, Audit Trail, Posting Coverage.
- Each closed revision seals a manifest (policy, checks, tasks with evidence hashes, exceptions, bank workpapers, actors, lock details, report hashes, app commits) and a readable HTML packet rendered from it. Reopening never overwrites them.
Full guides live in docs/monthly-closing/:
configuration and cutover ·
roles and data scope ·
tasks, evidence and bank workpapers ·
posting coverage ·
close, reopen and revalidation ·
job recovery ·
packets ·
extending checks and vouchers ·
migration and uninstall.
| Dependency | Version |
|---|---|
| Frappe Framework | v16 |
| ERPNext | v16 (required) |
| Python | >= 3.14 |
ERPNext is a hard dependency. It is declared in required_apps, so installing
erpcore on a site without ERPNext will fail.
Install with the bench CLI:
cd $PATH_TO_YOUR_BENCH
bench get-app https://github.com/kodlyft/erpcore --branch version-16
bench --site your-site.com install-app erpcoreversion-16 is the release branch (tagged by release.yml). develop is where work
lands first and is what CI tests on every push; use it only for development.
Installation seeds the default void reason codes and creates the database indexes. Both
steps are idempotent and re-run on every bench migrate, so a site installed before a
seed was added still picks it up.
| Setting | Where | Notes |
|---|---|---|
| Padding, book size limits, void reuse, PDC alerts | Cheque Settings | Allow Void Leaf Reuse is off by default and should usually stay off |
| Return days, guard verification, approval workflow, visitor rules | Gate Pass Settings | Turning off Enable Approval Workflow deactivates the workflow on the next migrate |
| Module switch, default template, sample size | Monthly Close Settings | Disabling stops new closes and transitions; existing locks stay in force |
| Cutover month, template, check severities, self-approval | Monthly Close Policy (one per company) | Self-approval is off by default; each use is recorded |
This app uses pre-commit for formatting and linting. After cloning:
cd apps/erpcore
pip install pre-commit
pre-commit install
pre-commit install --hook-type commit-msgConfigured hooks:
- ruff: Python linting, import sorting, and formatting
- prettier: JS / Vue / SCSS formatting
- eslint: JavaScript linting
- Frappe semgrep rules: vendored static-analysis rules (see
.semgrep) - commitlint: enforces Conventional Commits
The semgrep hook applies fixes automatically. Review what it changes before committing it is not always right about raw SQL.
Run the test suite:
bench --site your-site.com run-tests --app erpcoreThis repository ships a full CI/CD suite via GitHub Actions:
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
push to develop, PRs |
Installs ERPNext + this app on a fresh bench and runs unit tests |
linter.yml |
PRs | Pre-commit, Frappe semgrep rules, and pip-audit dependency scan |
semantic-commits.yml |
PRs | Validates commit titles against Conventional Commits |
release.yml |
push to version-16 |
Automated semantic versioning + GitHub releases |
Dependabot keeps GitHub Actions and Python dependencies up to date weekly.
Contributions are welcome! Please read the Contributing Guide and our Code of Conduct before opening a pull request.
- Report bugs with the Bug Report template.
- Suggest features with the Feature Request template.
- Found a security issue? See the Security Policy.