Skip to content

RFC 003 β€” Finance Library: Architecture, Doctypes & API

  • Status: Accepted
  • Author(s): Aditya Lohumi
  • Created: 2026-02-26
  • Updated: 2026-02-26
  • TSC Decision: Accepted (2026-02-26)
  • Supersedes: β€”
  • Superseded by: β€”

The finance library (libs/finance) is a shared Python library that provides the complete Finance & Accounts (F&A) domain model for the business-m monorepo. It replaces the legacy F&A module and supports accounting workflows for large enterprises and public-sector organisations, including:

  • Imprest / petty cash management
  • Bank reconciliation
  • Budget control against account heads and cost centres
  • Supplier bills, debit/credit notes, payment vouchers
  • General ledger entries and period closing
  • Fixed asset tracking (Fixed Asset Register) with depreciation
  • Employee expense claims (Travel, Daily Allowance & Other)
  • Earnest Money Deposit (EMD) management
  • Financial reporting via m-ledger

All GL postings flow through the m-ledger Book-Keeper service, keeping the finance library transactional but not directly responsible for ledger I/O.


finance-lib
β”œβ”€β”€ framework-m (DocType, Field, Controller, BaseController)
β”œβ”€β”€ m-core (Party, Supplier, Customer, Employee, Address)
β”œβ”€β”€ m-invoice (PurchaseInvoice, SalesInvoice)
β”œβ”€β”€ m-india (GST / TDS compliance, HSNCode)
β”œβ”€β”€ m-ledger (Book-Keeper β€” all GL postings)
β”œβ”€β”€ m-imprest (Petty cash / imprest workflow)
└── citadel / iam (Authentication & authorisation)

All doctypes extend framework_m.DocType and use framework_m.Field for field declarations. Business logic lives in per-doctype Controller classes that extend framework_m.Controller[T].

Every doctype carries a Meta inner class with the following flags:

Flag Default Meaning
requires_auth False Whether requests require a valid auth token (dev off)
apply_rls False Row-level security enforcement
naming_rule "autoincrement" Primary-key strategy
is_submittable varies Whether the document has a submit/cancel lifecycle
api_resource True Expose CRUD via the REST API
show_in_desk True Show in the Desk UI sidebar
permissions All β†’ All Role-based access map (production will restrict)
class XxxController(Controller[Xxx]):
doctype = Xxx
async def before_save(self, doc: Xxx) -> None: ...
async def after_save(self, doc: Xxx) -> None: ...
async def before_submit(self, doc: Xxx) -> None: ...
async def after_submit(self, doc: Xxx) -> None: ...
async def before_cancel(self, doc: Xxx) -> None: ...
async def after_cancel(self, doc: Xxx) -> None: ...
async def after_naming(self, doc: Xxx) -> None: ... # optimistic naming

Parent–child relationships are modelled with a list[ChildDocType] field on the parent and parent: str / parent_type: str fields on the child. The child doctype has show_in_desk: False and is_submittable: False.


Represents the legal entity for which accounts are maintained.

File: doctypes/company/doctype.py

Field Type Notes
name str Company Name (PK)
abbr str(2–5) Short abbreviation
default_currency str Base currency code (e.g., INR)
chart_of_accounts_template `str None`

Meta: is_submittable=False, api_resource=True, show_in_desk=True

Controller: Stub (before_save, after_save).


Financial year definition (e.g., β€œFY 2026-27”, April–March).

File: doctypes/fiscal_year/doctype.py

Field Type Notes
name str Human-readable name (PK)
start_date datetime Timezone-aware start datetime
end_date datetime Timezone-aware end datetime
is_closed bool False until explicitly closed

Note: Dates must be timezone-aware datetime objects, not naive strings. See Β§10 β€” Decision 5.

Meta: is_submittable=False

Controller: Stub.


Monthly or quarterly sub-periods within a FiscalYear, used to gate GL postings.

File: doctypes/accounting_period/doctype.py

Field Type Notes
name str Period label (e.g., β€œQ1 2026”)
fiscal_year str FK β†’ FiscalYear
start_date datetime Timezone-aware start datetime
end_date datetime Timezone-aware end datetime
is_closed bool When True, postings are blocked

Meta: is_submittable=False

Controller: Stub. Future: validate overlapping periods on before_save.


General ledger account. Supports tree structure (group/leaf) via parent_account. Syncs with m-ledger automatically on after_save with a manual fallback action on failure.

File: doctypes/account/doctype.py

Field Type Notes
name str Account Name (PK)
account_type str `Asset
parent_account `str None`
is_group bool True = folder node, False = leaf (postable)
company str FK β†’ Company
ledger_account_id `str None`

Meta: is_submittable=False

Controller: See Β§10 β€” Decision 1. Sync to Book-Keeper on after_save; if sync fails, surface a manual β€œSync to Ledger” action.


Departmental or project-based cost allocation node. Supports tree structure via parent_cost_center.

File: doctypes/cost_center/doctype.py

Field Type Notes
name str Cost Center Name (PK)
company str FK β†’ Company
parent_cost_center `str None`
is_group bool Tree group indicator

Meta: is_submittable=False

Controller: Stub.


1-to-1 financial extension of m-core’s Supplier. Holds payment terms, default expense account, and Tax ID.

File: doctypes/finance_supplier/doctype.py

Field Type Notes
supplier str FK β†’ m-core.Supplier
company str FK β†’ Company
tax_id `str None`
payment_terms `str None`
default_expense_account `str None`

Meta: is_submittable=False

Controller: Stub.


1-to-1 financial extension of m-core’s Customer. Holds credit limit, default income account, and Tax ID.

File: doctypes/finance_customer/doctype.py

Field Type Notes
customer str FK β†’ m-core.Customer
company str FK β†’ Company
tax_id `str None`
credit_limit float Maximum outstanding (0 = unlimited)
default_income_account `str None`

Meta: is_submittable=False

Controller: Stub. Future: enforce credit_limit on invoice submission.


Master record for a company’s bank account. Links to a GL Account for ledger posting.

File: doctypes/bank_account/doctype.py

Field Type Notes
account_name str Human label (e.g., β€œSBI Current”)
bank_name str e.g., β€œState Bank of India”
account_number str Unique bank account number
ifsc_code `str None`
branch `str None`
company str FK β†’ Company
gl_account `str None`
is_active bool Soft-delete flag

Meta: is_submittable=False


Represents an uploaded bank statement for a period. Contains a child table of transaction entries.

File: doctypes/bank_statement/doctype.py

Field Type Notes
statement_number str(≀50) Unique statement reference
bank_account str FK β†’ BankAccount
from_date date Period start
to_date date Period end
opening_balance Decimal Opening balance
closing_balance Decimal Closing balance
status str `Draft
entries list[BankStatementEntry] Child table

Meta: is_submittable=True


Field Type Notes
parent str FK β†’ BankStatement
parent_type str "BankStatement"
txn_date date Transaction date
description str Narration from bank
debit Decimal Debit amount
credit Decimal Credit amount
balance `Decimal None`
reference_number `str None`
is_reconciled bool Matched with a GL entry
gl_entry `str None`

Meta: is_submittable=False, show_in_desk=False


Field Type Notes
reconciliation_number str(≀50) Unique reconciliation reference
bank_account str FK β†’ BankAccount
reconciliation_date date As-of date
bank_statement str|None FK β†’ BankStatement
statement_balance Decimal Balance per bank statement
gl_balance Decimal Balance per GL
difference Decimal Unreconciled difference
status str `Draft

Meta: is_submittable=True


Field Type Notes
budget_number str(≀50) Unique budget reference
company str FK β†’ Company
fiscal_year str FK β†’ FiscalYear
cost_center `str None`
account str FK β†’ Account (account head)
allocated_amount Decimal Budget ceiling
utilized_amount Decimal Auto-computed from GL postings
status str `Active

Meta: is_submittable=True

See Β§10 β€” Decision 2. Budget validation raises a warning by default; configurable to hard-block via FinanceSettings.


Field Type Notes
revision_number str(≀50) Unique revision reference
budget str FK β†’ Budget
revised_date date Date of revision
previous_amount Decimal Amount before
revised_amount Decimal Amount after
reason str Mandatory justification
status str `Draft

Meta: is_submittable=True


Field Type Notes
voucher_number str(≀50) Unique PV reference
company str FK β†’ Company
payment_date date Date of payment
payment_type str "Outgoing" (default)
party_type str `Supplier
party str FK β†’ party record
paid_from_account str FK β†’ Account (Debit β€” Bank/Cash)
paid_to_account str FK β†’ Account (Credit β€” Payable)
amount Decimal Payment amount
bank_account `str None`
reference_number `str None`
reference_date `date None`
narration `str None`
cost_center `str None`
status str `Draft

Meta: is_submittable=True


Field Type Notes
receipt_number str(≀50) Unique receipt reference
company str FK β†’ Company
receipt_date date Date of receipt
party_type str Customer
party str FK β†’ m-core.Customer
received_in_account str FK β†’ Account (Debit β€” Bank/Cash)
paid_from_account str FK β†’ Account (Credit β€” Receivable)
amount Decimal Receipt amount
bank_account `str None`
reference_number `str None`
reference_date `date None`
narration `str None`
cost_center `str None`
status str `Draft

Meta: is_submittable=True


Field Type Notes
note_number str(≀50) Unique debit note reference
company str FK β†’ Company
posting_date date Posting date
supplier str FK β†’ m-core.Supplier
account str FK β†’ Account (Supplier Payable)
amount Decimal Note amount
reason str Narration / reason
purchase_invoice `str None`
status str `Draft

Meta: is_submittable=True


Field Type Notes
note_number str(≀50) Unique credit note reference
company str FK β†’ Company
posting_date date Posting date
customer str FK β†’ m-core.Customer
account str FK β†’ Account (Customer Receivable)
amount Decimal Note amount
reason str Narration / reason
sales_invoice `str None`
status str `Draft

Meta: is_submittable=True


Field Type Notes
entry_number str(≀50) Unique JE reference
company str FK β†’ Company
posting_date date Posting date
entry_type str `Journal Entry
narration str Document-level narration
total_debit Decimal Sum of debit legs (auto-computed)
total_credit Decimal Sum of credit legs (auto-computed)
status str `Draft
accounts list[JournalEntryAccount] Child table β€” ledger legs

Meta: is_submittable=True β€” See Β§10 β€” Decision 4.


Field Type Notes
parent str FK β†’ JournalEntry
parent_type str "JournalEntry"
account str FK β†’ Account
party_type `str None`
party `str None`
cost_center `str None`
debit Decimal Debit amount
credit Decimal Credit amount
user_remark `str None`

Meta: is_submittable=False, show_in_desk=False


Field Type Notes
voucher_number str(≀50) Unique PCV reference
company str FK β†’ Company
posting_date date Closing date
fiscal_year str FK β†’ FiscalYear being closed
period_year_end_date date Fiscal year end date
closing_account str FK β†’ Account (Retained Earnings)
net_pl_amount Decimal Net P&L transferred
status str `Draft

Meta: is_submittable=True


Field Type Notes
claim_number str(≀50) Unique claim reference
company str FK β†’ Company
employee str FK β†’ m-core.Employee
claim_date date Date claim was raised
claim_type str `Travel
cost_center `str None`
total_claimed_amount Decimal Auto-summed from child rows
total_approved_amount Decimal Approver-filled
narration `str None`
status str `Draft
expenses list[ExpenseClaimDetail] Child table

Meta: is_submittable=True

Multi-level approval (Department Head β†’ Finance) is delegated to the Framework M WorkflowProtocol β€” See Β§10 β€” Decision 7.


Field Type Notes
parent str FK β†’ ExpenseClaim
parent_type str "ExpenseClaim"
expense_date date Date of expense
description str Expense description
expense_type str `Travel
amount Decimal Claimed amount
is_approved bool Approver decision per line
approved_amount Decimal Approved amount

Meta: is_submittable=False, show_in_desk=False


Field Type Notes
category_name str Unique category name
depreciation_method str SLM (Straight Line) or WDV (Written Down Value)
total_useful_life int Useful life in years
frequency_of_depreciation int Months per period (12=annual, 3=quarterly)
depreciation_account str FK β†’ Account (Depreciation Expense)
accumulated_depreciation_account str FK β†’ Account (Accumulated Depreciation)
disposal_account str FK β†’ Account (Gain/Loss on Disposal)

Meta: is_submittable=False


Field Type Notes
asset_number str(≀50) Unique asset register number
company str FK β†’ Company
asset_name str Description
asset_category str FK β†’ AssetCategory
purchase_date date Date of purchase
gross_purchase_amount Decimal Original cost
salvage_value Decimal Expected scrap value
current_value Decimal Book value (updated by depreciation)
location `str None`
custodian `str None`
cost_center `str None`
is_active bool False when disposed
disposal_date `date None`

Meta: is_submittable=True


Field Type Notes
movement_number str(≀50) Unique movement reference
asset str FK β†’ Asset
movement_date date Date of movement
from_location `str None`
to_location str Destination location
from_custodian `str None`
to_custodian `str None`
purpose `str None`
reference `str None`

Meta: is_submittable=True


Field Type Notes
asset str FK β†’ Asset
schedule_date date Date depreciation should be posted
depreciation_amount Decimal Depreciation for this period
accumulated_depreciation Decimal Running total after posting
book_value_after Decimal Net book value after posting
is_depreciated bool True once JE has been posted
journal_entry `str None`

Meta: is_submittable=False, show_in_desk=False

Depreciation batch execution is cron-driven, configurable via FinanceSettings β€” See Β§10 β€” Decision 6.


Field Type Notes
receipt_number str(≀50) Unique EMD receipt reference
company str FK β†’ Company
receipt_date date Date of receipt
party_type str `Supplier
party str FK β†’ party record
amount Decimal EMD amount received
bank_account `str None`
reference_number `str None`
ref_number `str None`
status str `Held

Meta: is_submittable=True


Field Type Notes
refund_number str(≀50) Unique refund reference
emd_receipt str FK β†’ EMDReceipt
refund_date date Date of refund
refund_amount Decimal Refund amount (≀ receipt amount)
bank_account `str None`
reference_number `str None`
reason `str None`
status str `Draft

Meta: is_submittable=True


All doctypes with api_resource=True automatically expose standard CRUD endpoints:

GET /api/resource/{DocType}
POST /api/resource/{DocType}
GET /api/resource/{DocType}/{id}
PUT /api/resource/{DocType}/{id}
DELETE /api/resource/{DocType}/{id}

For submittable doctypes:

POST /api/resource/{DocType}/{id}/submit
POST /api/resource/{DocType}/{id}/cancel

Child doctypes (BankStatementEntry, JournalEntryAccount, ExpenseClaimDetail, DepreciationSchedule) are API-accessible but excluded from the Desk UI sidebar (show_in_desk=False).


Company ──────────────────────────────────┐
β”‚ β”‚
β”œβ”€ FiscalYear ──── AccountingPeriod β”‚
β”œβ”€ Account (tree, syncs to m-ledger) β”‚
β”œβ”€ CostCenter (tree) β”‚
β”œβ”€ BankAccount ──── BankStatement ──── BankStatementEntry
β”‚ └─ BankReconciliation β”‚
β”œβ”€ Budget ──── BudgetRevision β”‚
β”œβ”€ PaymentVoucher (party: Supplier | Employee)
β”œβ”€ PaymentReceipt (party: Customer) β”‚
β”œβ”€ DebitNote (party: Supplier) β”‚
β”œβ”€ CreditNote (party: Customer) β”‚
β”œβ”€ JournalEntry ──── JournalEntryAccountβ”‚
β”œβ”€ PeriodClosingVoucher β”‚
β”œβ”€ ExpenseClaim ──── ExpenseClaimDetail β”‚
β”œβ”€ Asset ──── DepreciationSchedule (category: AssetCategory)
β”‚ └─ AssetMovement β”‚
└─ EMDReceipt ──── EMDRefund β”‚
FinanceSupplier ─── m-core.Supplier
FinanceCustomer ─── m-core.Customer

Dependency How Used
m-ledger All GL postings delegated to Book-Keeper. Finance doctypes create/cancel GL entries on submit/cancel. Account auto-syncs on after_save.
m-core Supplier, Customer, Employee, Party sourced from m-core. Finance adds domain extensions.
m-invoice PurchaseInvoice / SalesInvoice originate in m-invoice. DebitNote/CreditNote carry back-references.
m-india GST/TDS compliance, HSNCode master. Tax IDs surfaced on finance party extensions.
m-imprest Imprest workflows live in m-imprest; finance consumes settlements as GL entries.
citadel/iam Auth and RBAC. requires_auth and apply_rls are False for development; hardened pre-launch.

DocType before_save after_submit
Account β€” Sync to Book-Keeper (auto, fallback: manual)
JournalEntry Validate total_debit == total_credit Push GL entries to Book-Keeper
PaymentVoucher Budget check (warn / block) Post GL: Dr paid_from, Cr paid_to
PaymentReceipt β€” Post GL: Dr received_in, Cr paid_from
DebitNote β€” Post GL reducing Supplier payable
CreditNote β€” Post GL reducing Customer receivable
Budget β€” Lock allocation; enable utilisation tracking
BudgetRevision β€” Update Budget.allocated_amount
BankReconciliation Compute difference Transition bank statement to Reconciled
Asset β€” Auto-generate DepreciationSchedule rows
AssetMovement β€” Update Asset.location / custodian
EMDReceipt β€” Post GL: Dr Bank, Cr EMD Liability
EMDRefund Validate refund ≀ receipt amount Post GL reversal; update receipt status
ExpenseClaim Sum total from child rows Post GL on Paid status
PeriodClosingVoucher Compute net P&L from m-ledger Post closing JE to retained earnings

Reports are generated dynamically from Book-Keeper data via m-ledger. No separate doctypes required.

Report Source
Trial Balance m-ledger GL aggregation
Profit & Loss Statement Income / Expense account totals
Balance Sheet Asset / Liability / Equity totals
General Ledger Statement Per-account GL entry list
Bank Reconciliation Statement BankReconciliation view
Accounts Payable Aging PaymentVoucher + invoice view
Accounts Receivable Aging PaymentReceipt + invoice view

# Question Decision
1 Should Account sync to m-ledger automatically or on manual action? Auto on after_save. If sync fails, surface a manual β€œSync to Ledger” action on the document.
2 Should budget validation block PaymentVoucher submission or warn? Warning by default; configurable to hard-block via a budget_enforcement_mode setting in FinanceSettings (warn | block).
3 When will requires_auth / apply_rls be hardened? Pre-launch. All flags flipped to True and proper role permissions set as a pre-production milestone gate.
4 Should JournalEntry.is_submittable be True? Yes. JournalEntry has full submit/cancel lifecycle enforced by the framework.
5 FiscalYear.start_date / end_date typed as str β€” should this be corrected? Yes. All date fields must use timezone-aware datetime objects. Naive strings are not allowed. This is a required fix before implementation.
6 How is DepreciationSchedule batch processing triggered? Cron-driven, configurable via FinanceSettings.depreciation_cron_schedule.
7 How is multi-level ExpenseClaim approval modelled? Delegated to WorkflowProtocol in Framework M. The controller integrates with the workflow abstraction; no custom approval state machine is built in the finance library.