Finance & G/L
Read Conventions first.
gl_accounts
Chart of Accounts, with Title (non-postable grouping) rows.
id,business_id,code- UNIQUE (business_id, code);nameaccount_typeNOT NULL - CHECK IN (‘asset’,‘liability’,‘equity’,‘income’,‘expense’)parent_account_id→ gl_accounts NULL - arbitrary nesting; index on itis_titleboolean DEFAULT false - a Title account rolls up descendants and is not postablecontrol_account_typeDEFAULT ‘none’ - CHECK IN (‘none’,‘accounts_receivable’,‘accounts_payable’,‘inventory’,‘gift_voucher_liability’,‘loyalty_points_liability’,‘change_money_liability’). A control account is maintained automatically by subsidiary transactions and can never be posted to by a manual journal entry.is_active,created_at
Journal entries
- journal_entries - the G/L header, written by document posting, not entered directly.
id,business_id,branch_idNULL;entry_dateDEFAULT current_date;reference_typeNOT NULL;reference_idNOT NULL - polymorphic link to the source document;description,created_byNULL,created_at; indexes(business_id, reference_type, reference_id),(business_id, entry_date) - journal_lines -
id,journal_entry_idCASCADE;gl_account_idNOT NULL;debit,creditDEFAULT 0;description;cost_center_idNULL - what cost-centre reports ultimately filter on; indexes(journal_entry_id),(gl_account_id),(cost_center_id)
Manual journal entries
- journal_entry_documents - the user-facing Manual Journal Entry document.
id,business_id,document_noUNIQUE (business_id, document_no);branch_idNOT NULL,entry_date,description;statusDEFAULT ‘draft’ CHECK IN (‘draft’,‘posted’);posted_at/posted_by,created_byNOT NULL,created_at - journal_entry_document_lines -
id,journal_entry_document_idCASCADE,gl_account_idNOT NULL;debit,creditnumeric(14,2) DEFAULT 0;description;cost_center_idNULL;distribution_rule_idNULL - expanded into multiplejournal_linesat posting time;created_at - recurring_journal_entry_templates -
id,business_id,name,is_active,branch_idNOT NULL,description;frequencyCHECK IN (‘daily’,‘weekly’,‘monthly’,‘yearly’);interval_countDEFAULT 1;start_dateNOT NULL,end_dateNULL,next_run_dateNOT NULL,last_generated_at;created_byNOT NULL,created_at,updated_at; index(business_id, is_active, next_run_date) - recurring_journal_entry_template_lines -
id,template_idCASCADE,gl_account_idNOT NULL,debit,credit,description;cost_center_id,distribution_rule_id - recurring_journal_entry_runs -
id,template_idCASCADE,journal_entry_document_idNOT NULL;scheduled_for,generated_at
payment_methods
Behaviour flags drive posting and POS handling. Seeded: ‘Online Transfer’, ‘Cheque’, ‘Cash’, ‘Card Payment’.
id,business_id,name- UNIQUE (business_id, name)gl_account_id- the debit leg for an incoming paymentis_cashbooleanrequires_card_terminalbooleanis_chequeboolean - needs cheque number + expected realization daterequires_bank_accountboolean - GL leg posts to the chosen company bank account instead ofgl_account_id(true for Cheque and Online Transfer)is_gift_voucher,is_loyalty_points,is_change_money,is_credit_noteboolean - tender types redeeming a stored balanceis_active,created_at
company_bank_accounts
id, business_id, bank_name, account_name, account_number - UNIQUE (business_id, account_number);
branch_name, gl_account_id; is_active, created_at
Payments
- incoming_payments (customer receipts) -
id,business_id,document_noUNIQUE (business_id, document_no);branch_idNOT NULL,customer_idNOT NULL,customer_namesnapshot;payment_method_idNOT NULL;payment_dateNOT NULL;amountNOT NULL (tendered);amount_appliedNOT NULL (allocated to invoices);change_returnedDEFAULT 0;change_money_creditedDEFAULT 0;bank_account_idNULL;cheque_number;expected_realization_date/actual_realization_dateNULL;realization_statusDEFAULT ‘not_applicable’ - CHECK IN (‘not_applicable’,‘pending’,‘realized’,‘bounced’); multi-currencycurrency_id/exchange_rate/foreign_amount;cost_center_id;reference,remarks;statusDEFAULT ‘posted’ - CHECK IN (‘posted’,‘cancelled’) (no draft state - posts G/L immediately);posted_by,cancelled_at/cancelled_by,created_by,created_at - incoming_payment_allocations -
id,incoming_payment_idCASCADE,ar_invoice_idRESTRICT;amount_appliedNOT NULL;foreign_amount_appliedNULL - outgoing_payments (supplier payments) - same shape as incoming_payments with supplier fields, plus
supplier_idNOT NULL,supplier_name;withholding_tax_code_idNULL,withholding_tax_amountDEFAULT 0;terminal_idNULL (POS-originated expenses); nochange_returned/change_money_credited; UNIQUE (business_id, document_no);statusCHECK IN (‘posted’,‘cancelled’) - outgoing_payment_allocations -
id,outgoing_payment_idCASCADE,ap_invoice_idRESTRICT;amount_applied;foreign_amount_appliedNULL
Bank deposits & withdrawals
- bank_deposits -
id,business_id,document_noUNIQUE (business_id, document_no);branch_idNOT NULL;company_bank_account_idNOT NULL,company_bank_account_namesnapshot;source_gl_account_idNOT NULL (what is being moved into the bank);deposit_dateNOT NULL,amountNOT NULL;cost_center_id,reference,remarks;statusCHECK IN (‘posted’,‘cancelled’);posted_by,cancelled_at/cancelled_by,created_byNOT NULL,created_at - bank_withdrawals - mirror of bank_deposits with
destination_gl_account_idandwithdrawal_date
Bank reconciliation
- bank_reconciliations -
id,business_id,company_bank_account_idNOT NULL;statement_dateNOT NULL;statement_ending_balanceNOT NULL;statusDEFAULT ‘in_progress’ CHECK IN (‘in_progress’,‘completed’);created_byNOT NULL,created_at,completed_at,completed_by; partial UNIQUE index(company_bank_account_id) WHERE status = 'in_progress'- one open reconciliation per bank account - bank_reconciliation_lines - which internal journal lines are ticked off.
id,bank_reconciliation_idCASCADE,journal_line_idNOT NULL;created_at- UNIQUE (bank_reconciliation_id, journal_line_id) - bank_statement_lines - statement-side rows imported from a CSV, matched (AI-suggested or manual) against outstanding journal lines.
id,bank_reconciliation_idCASCADE;line_dateNOT NULL,description,amountNOT NULL;external_reference;matched_journal_line_idNULL;created_at; index on parent
Cheque lifecycle
- cheque_books - a physical chequebook registered once; every leaf becomes its own row.
id,business_id,company_bank_account_idNOT NULL;book_no,first_cheque_no,last_cheque_noNOT NULL;leaf_countNOT NULL,is_active,created_by,created_at - cheque_leaves -
id,business_id,cheque_book_idNOT NULL;cheque_number- UNIQUE (business_id, cheque_number);leaf_seq;statusDEFAULT ‘unused’ - CHECK IN (‘unused’,‘issued’,‘printed’,‘void’);void_reason,voided_at,voided_by;replaced_by_leaf_idNULL (misprint replacement chain);created_at; index(cheque_book_id, status) - outgoing_payment_cheques - one row per “cheque slot” fulfilling an outgoing payment; a payment may have several.
id,outgoing_payment_idNOT NULL;cheque_leaf_idNOT NULL UNIQUE (a leaf is consumed by at most one slot; on reprint this column is updated to the new leaf rather than inserting a new slot);amountCHECK > 0;payee_nameNOT NULL;crossing_typeDEFAULT ‘account_payee_only’ - CHECK IN (‘none’,‘account_payee_only’,‘not_negotiable’,‘account_payee_only_not_negotiable’);expected_realization_dateNOT NULL;actual_realization_dateNULL;realization_statusDEFAULT ‘pending’ CHECK IN (‘pending’,‘realized’,‘bounced’);printed_at,printed_by,handover_printed_at,created_at; index(outgoing_payment_id) - cheque_print_profiles - millimetre-based print layout per bank, calibrated via Test Print.
id,business_id,nameUNIQUE (business_id, name);company_bank_account_idNULL;leaf_width_mm(203),leaf_height_mm(92), plus x/y coordinates for date/payee/amount-words/amount-figures/crossing/signature, all numeric NOT NULL with defaults;date_box_countDEFAULT 6 - CHECK IN (6, 8) (DD MM YY vs DD MM YYYY);amount_words_currency_labelDEFAULT ‘’ (e.g. “Rupees”; empty = no prefix);is_default,is_active,created_at
Budgets
- budgets -
id,business_id,nameUNIQUE (business_id, name);fiscal_yearNOT NULL;branch_idNULL,cost_center_idNULL (optional scoping dimensions);is_active,created_byNOT NULL,created_at,updated_at - budget_lines -
id,budget_idCASCADE,gl_account_idNOT NULL;month- CHECK BETWEEN 1 AND 12;amountDEFAULT 0; UNIQUE (budget_id, gl_account_id, month)
Fixed assets
- fixed_assets -
id,business_id,asset_noUNIQUE (business_id, asset_no);name;asset_class,branch_idNOT NULL,cost_center_idNULL;asset_account_id,accumulated_depreciation_account_id,depreciation_expense_account_idall NOT NULL;acquisition_dateNOT NULL;acquisition_costNOT NULL;salvage_valueDEFAULT 0;useful_life_monthsNOT NULL;accumulated_depreciationDEFAULT 0 (running total);statusDEFAULT ‘active’ - CHECK IN (‘active’,‘fully_depreciated’,‘disposed’);disposed_at,disposal_proceeds,disposal_gain_lossNULL;remarks,created_byNOT NULL,created_at,updated_at - fixed_asset_depreciation_runs -
id,fixed_asset_idNOT NULL,as_of_dateNOT NULL;amountNOT NULL;journal_entry_idNOT NULL;created_at
withholding_tax_codes
id, business_id, code - UNIQUE (business_id, code); name; rate NOT NULL; liability_account_id
NOT NULL; is_active, created_at
Cost centers & distribution rules
- cost_centers - structurally a clone of the gl_accounts hierarchy.
id,business_id,codeUNIQUE (business_id, code);name;parent_cost_center_idNULL (index on it);is_groupDEFAULT false (the non-postable grouping row);is_active,created_at - distribution_rules - a named allocation splitting one posting across multiple cost centres by percentage; resolved and expanded into real
journal_linesat posting time - the rule itself never appears in the ledger.id,business_id,codeUNIQUE (business_id, code);name,is_active,created_at - distribution_rule_lines -
id,distribution_rule_idCASCADE,cost_center_idNOT NULL;percentage- CHECK > 0 AND <= 100