Docs menu

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); name
  • account_type NOT NULL - CHECK IN (‘asset’,‘liability’,‘equity’,‘income’,‘expense’)
  • parent_account_id → gl_accounts NULL - arbitrary nesting; index on it
  • is_title boolean DEFAULT false - a Title account rolls up descendants and is not postable
  • control_account_type DEFAULT ‘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_id NULL; entry_date DEFAULT current_date; reference_type NOT NULL; reference_id NOT NULL - polymorphic link to the source document; description, created_by NULL, created_at; indexes (business_id, reference_type, reference_id), (business_id, entry_date)
  • journal_lines - id, journal_entry_id CASCADE; gl_account_id NOT NULL; debit, credit DEFAULT 0; description; cost_center_id NULL - 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_no UNIQUE (business_id, document_no); branch_id NOT NULL, entry_date, description; status DEFAULT ‘draft’ CHECK IN (‘draft’,‘posted’); posted_at/posted_by, created_by NOT NULL, created_at
  • journal_entry_document_lines - id, journal_entry_document_id CASCADE, gl_account_id NOT NULL; debit, credit numeric(14,2) DEFAULT 0; description; cost_center_id NULL; distribution_rule_id NULL - expanded into multiple journal_lines at posting time; created_at
  • recurring_journal_entry_templates - id, business_id, name, is_active, branch_id NOT NULL, description; frequency CHECK IN (‘daily’,‘weekly’,‘monthly’,‘yearly’); interval_count DEFAULT 1; start_date NOT NULL, end_date NULL, next_run_date NOT NULL, last_generated_at; created_by NOT NULL, created_at, updated_at; index (business_id, is_active, next_run_date)
  • recurring_journal_entry_template_lines - id, template_id CASCADE, gl_account_id NOT NULL, debit, credit, description; cost_center_id, distribution_rule_id
  • recurring_journal_entry_runs - id, template_id CASCADE, journal_entry_document_id NOT 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 payment
  • is_cash boolean
  • requires_card_terminal boolean
  • is_cheque boolean - needs cheque number + expected realization date
  • requires_bank_account boolean - GL leg posts to the chosen company bank account instead of gl_account_id (true for Cheque and Online Transfer)
  • is_gift_voucher, is_loyalty_points, is_change_money, is_credit_note boolean - tender types redeeming a stored balance
  • is_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_no UNIQUE (business_id, document_no); branch_id NOT NULL, customer_id NOT NULL, customer_name snapshot; payment_method_id NOT NULL; payment_date NOT NULL; amount NOT NULL (tendered); amount_applied NOT NULL (allocated to invoices); change_returned DEFAULT 0; change_money_credited DEFAULT 0; bank_account_id NULL; cheque_number; expected_realization_date/actual_realization_date NULL; realization_status DEFAULT ‘not_applicable’ - CHECK IN (‘not_applicable’,‘pending’,‘realized’,‘bounced’); multi-currency currency_id/exchange_rate/foreign_amount; cost_center_id; reference, remarks; status DEFAULT ‘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_id CASCADE, ar_invoice_id RESTRICT; amount_applied NOT NULL; foreign_amount_applied NULL
  • outgoing_payments (supplier payments) - same shape as incoming_payments with supplier fields, plus supplier_id NOT NULL, supplier_name; withholding_tax_code_id NULL, withholding_tax_amount DEFAULT 0; terminal_id NULL (POS-originated expenses); no change_returned/change_money_credited; UNIQUE (business_id, document_no); status CHECK IN (‘posted’,‘cancelled’)
  • outgoing_payment_allocations - id, outgoing_payment_id CASCADE, ap_invoice_id RESTRICT; amount_applied; foreign_amount_applied NULL

Bank deposits & withdrawals

  • bank_deposits - id, business_id, document_no UNIQUE (business_id, document_no); branch_id NOT NULL; company_bank_account_id NOT NULL, company_bank_account_name snapshot; source_gl_account_id NOT NULL (what is being moved into the bank); deposit_date NOT NULL, amount NOT NULL; cost_center_id, reference, remarks; status CHECK IN (‘posted’,‘cancelled’); posted_by, cancelled_at/cancelled_by, created_by NOT NULL, created_at
  • bank_withdrawals - mirror of bank_deposits with destination_gl_account_id and withdrawal_date

Bank reconciliation

  • bank_reconciliations - id, business_id, company_bank_account_id NOT NULL; statement_date NOT NULL; statement_ending_balance NOT NULL; status DEFAULT ‘in_progress’ CHECK IN (‘in_progress’,‘completed’); created_by NOT 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_id CASCADE, journal_line_id NOT 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_id CASCADE; line_date NOT NULL, description, amount NOT NULL; external_reference; matched_journal_line_id NULL; 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_id NOT NULL; book_no, first_cheque_no, last_cheque_no NOT NULL; leaf_count NOT NULL, is_active, created_by, created_at
  • cheque_leaves - id, business_id, cheque_book_id NOT NULL; cheque_number - UNIQUE (business_id, cheque_number); leaf_seq; status DEFAULT ‘unused’ - CHECK IN (‘unused’,‘issued’,‘printed’,‘void’); void_reason, voided_at, voided_by; replaced_by_leaf_id NULL (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_id NOT NULL; cheque_leaf_id NOT 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); amount CHECK > 0; payee_name NOT NULL; crossing_type DEFAULT ‘account_payee_only’ - CHECK IN (‘none’,‘account_payee_only’,‘not_negotiable’,‘account_payee_only_not_negotiable’); expected_realization_date NOT NULL; actual_realization_date NULL; realization_status DEFAULT ‘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, name UNIQUE (business_id, name); company_bank_account_id NULL; 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_count DEFAULT 6 - CHECK IN (6, 8) (DD MM YY vs DD MM YYYY); amount_words_currency_label DEFAULT ‘’ (e.g. “Rupees”; empty = no prefix); is_default, is_active, created_at

Budgets

  • budgets - id, business_id, name UNIQUE (business_id, name); fiscal_year NOT NULL; branch_id NULL, cost_center_id NULL (optional scoping dimensions); is_active, created_by NOT NULL, created_at, updated_at
  • budget_lines - id, budget_id CASCADE, gl_account_id NOT NULL; month - CHECK BETWEEN 1 AND 12; amount DEFAULT 0; UNIQUE (budget_id, gl_account_id, month)

Fixed assets

  • fixed_assets - id, business_id, asset_no UNIQUE (business_id, asset_no); name; asset_class, branch_id NOT NULL, cost_center_id NULL; asset_account_id, accumulated_depreciation_account_id, depreciation_expense_account_id all NOT NULL; acquisition_date NOT NULL; acquisition_cost NOT NULL; salvage_value DEFAULT 0; useful_life_months NOT NULL; accumulated_depreciation DEFAULT 0 (running total); status DEFAULT ‘active’ - CHECK IN (‘active’,‘fully_depreciated’,‘disposed’); disposed_at, disposal_proceeds, disposal_gain_loss NULL; remarks, created_by NOT NULL, created_at, updated_at
  • fixed_asset_depreciation_runs - id, fixed_asset_id NOT NULL, as_of_date NOT NULL; amount NOT NULL; journal_entry_id NOT 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, code UNIQUE (business_id, code); name; parent_cost_center_id NULL (index on it); is_group DEFAULT 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_lines at posting time - the rule itself never appears in the ledger. id, business_id, code UNIQUE (business_id, code); name, is_active, created_at
  • distribution_rule_lines - id, distribution_rule_id CASCADE, cost_center_id NOT NULL; percentage - CHECK > 0 AND <= 100