> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hitaji360.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Accounting Data Model

> Developer reference for the persistence model of the Hitaji 360 Accounting service.

Developer reference for the persistence model of the Hitaji 360 Accounting service.
Every entity below is a real TypeORM class under
`hitaji-erp-api/src/accounting/entities/`. Column names are the actual database
column names (TypeORM `name:` where it differs from the camelCase property).

> **Read this first — the three scoping columns**
>
> | Column         | Meaning                                                                                                                                                                                           | Source                                                      |
> | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
> | `tenant_id`    | Hard isolation boundary. Every query filters by it.                                                                                                                                               | JWT `tenant_id` claim                                       |
> | `workspace_id` | The **accounting book id** this row belongs to. *Not* a task-management workspace. `bookId` and `workspaceId` are 1:1 during the migration window (`AccountingBook.workspaceId`).                 | request body/query, validated by `AccountingWorkspaceGuard` |
> | `business_id`  | Present only on a handful of per-business master tables and the posting-internals tables; for transactional rows the owning business is resolved *through* the book (`AccountingRoutingService`). | `X-Business-Id` header                                      |
>
> See `multi-tenancy.md` for how these are enforced.

## Base classes

Most entities extend one of two base classes (`src/common/entities/`):

| Base                                       | Adds                                                                                                                                                      | Used by                                                                     |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `BaseEntity`                               | `id` (uuid PK), `tenant_id`, `date_created`, `date_updated`, `created_by_id`, `last_updated_by_id`, `is_deleted`, `deleted_at`                            | Ledger, accounts, periods, payments, masters, etc.                          |
| `SubmittableEntity` (extends `BaseEntity`) | `docstatus` (smallint: 0=Draft, 1=Submitted, 2=Cancelled), `amended_from`, `submitted_at`, `submitted_by_user_id`, `cancelled_at`, `cancelled_by_user_id` | `Invoice`, `Bill`, `Quotation` — documents with the submit/cancel lifecycle |

A number of **child-line entities define their own `id`** (and sometimes a
nullable `tenant_id`) instead of extending `BaseEntity` — they are reached only
through their parent and cascade-delete with it. These are called out in the
tables below.

***

## Domain 1 — Chart of Accounts & General Ledger

| Entity (`@Entity`)                                    | Purpose                                                                                                           | Key columns                                                                                                                                                                                                                                                                                                                                                  | Key relations                                                                      |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `Account` (`accounts`)                                | A chart-of-accounts node (group or postable leaf).                                                                | `workspace_id`, `code`, `name`, `type` (`AccountType`), `root_type` (`AccountRootType`), `normal_side` (`AccountNormalSide`), `subtype`, `parent_account_id`, `is_group`, `path`, `allow_direct_posting`, `system_code`, `is_system`, `is_active`, `is_frozen`/`frozen_*`, `merged_into_account_id`, `balance_must_be`                                       | self-ref `parentAccount` / `childAccounts` (`parent_account_id`)                   |
| `AccountingLedgerEntry` (`accounting_ledger_entries`) | The **posted GL row** — one per debit-or-credit leg. The immutable ledger.                                        | `workspace_id`, `account_id`, `journal_entry_id`, `entry_date`, `source_type` (`JournalSourceType`), `source_id`, `debit`/`credit` (numeric 18,4), `contact_id`, `category_id`, `posting_batch_id`, `book_code`, `dimensions` (jsonb), `is_reversal`, `is_opening`, `is_advance`, `against_voucher_type`/`against_voucher_id`, `reversal_of_ledger_entry_id` | `account` (`account_id`), `journalEntry` (`journal_entry_id`, `onDelete RESTRICT`) |
| `JournalEntry` (`journal_entries`)                    | The journal **header** — the draftable/postable unit before it materializes into ledger rows.                     | `workspace_id`, `entry_number`, `entry_date`, `source_type`, `source_id`, `memo`, `book_code`, `dimensions` (simple-json), `status` (`JournalEntryStatus`: DRAFT/POSTED/REVERSED), `posted_at`, `reversed_by_entry_id`, `voucher_type` (`JournalVoucherType`), `is_opening`, `workflow_state`, `template_id`                                                 | `lines` → `JournalLine[]` (cascade); self-ref `reversedByEntry`                    |
| `JournalLine` (`journal_lines`)                       | A single debit/credit line of a journal entry (own `id`, nullable `tenant_id`).                                   | `workspace_id`, `journal_entry_id`, `account_id`, `debit`/`credit` (numeric 18,4), `currency`, `contact_id`, `category_id`, `cost_center_id`, `against_invoice_id`, `against_bill_id` (AR/AP sub-ledger reconciliation), `cleared_at`/`bank_statement_line_id` (bank-rec clearance)                                                                          | `journalEntry` (`onDelete CASCADE`), `account`                                     |
| `AccountingBook` (`accounting_books`)                 | A per-business set of books (the ledger "company"). The bridge between `businessId` and the legacy `workspaceId`. | `business_id`, `code` (unique per tenant), `name`, `type` (`AccountingBookType`: STATUTORY/MANAGEMENT/BRANCH/FUND), `currency`, `fiscal_year_start_month`, `chart_template_key`, `workspace_id`, `retained_earnings_account_id`, `opening_balance_equity_account_id`, `status` (`AccountingBookStatus`), `default_expense_claim_payable_account_id`          | — (referenced by `Business.defaultBookId`)                                         |
| `AccountingPeriod` (`accounting_periods`)             | A fiscal period that can be OPEN / CLOSED / LOCKED to gate posting.                                               | `workspace_id`, `name`, `start_date`, `end_date`, `fiscal_year`, `status` (`AccountingPeriodStatus`), `closed_at`, `closed_by_id`                                                                                                                                                                                                                            | —                                                                                  |

### Core ledger cluster (ER diagram)

```mermaid theme={null}
erDiagram
    ACCOUNTING_BOOK ||--o{ ACCOUNT : "scopes (workspace_id)"
    ACCOUNTING_BOOK ||--o{ ACCOUNTING_PERIOD : "scopes (workspace_id)"
    ACCOUNT ||--o{ ACCOUNT : "parent_account_id"
    JOURNAL_ENTRY ||--o{ JOURNAL_LINE : "lines (cascade)"
    JOURNAL_ENTRY ||--o{ ACCOUNTING_LEDGER_ENTRY : "posts (journal_entry_id, RESTRICT)"
    ACCOUNT ||--o{ JOURNAL_LINE : "account_id"
    ACCOUNT ||--o{ ACCOUNTING_LEDGER_ENTRY : "account_id"
    JOURNAL_ENTRY ||--o| JOURNAL_ENTRY : "reversed_by_entry_id"

    ACCOUNTING_BOOK {
        uuid id PK
        uuid business_id
        uuid workspace_id "1:1 with book during migration"
        enum type "STATUTORY|MANAGEMENT|BRANCH|FUND"
        enum status
    }
    ACCOUNT {
        uuid id PK
        uuid workspace_id
        varchar code
        enum root_type "ASSET|LIABILITY|EQUITY|INCOME|EXPENSE"
        enum normal_side "DEBIT|CREDIT"
        bool is_group
        bool is_system
        bool is_frozen
        uuid merged_into_account_id
    }
    ACCOUNTING_PERIOD {
        uuid id PK
        uuid workspace_id
        date start_date
        date end_date
        enum status "OPEN|CLOSED|LOCKED"
    }
    JOURNAL_ENTRY {
        uuid id PK
        uuid workspace_id
        enum status "DRAFT|POSTED|REVERSED"
        enum voucher_type
        enum source_type
    }
    JOURNAL_LINE {
        uuid id PK
        uuid journal_entry_id FK
        uuid account_id FK
        numeric debit
        numeric credit
        uuid against_invoice_id
        uuid against_bill_id
    }
    ACCOUNTING_LEDGER_ENTRY {
        uuid id PK
        uuid journal_entry_id FK
        uuid account_id FK
        numeric debit
        numeric credit
        bool is_reversal
        bool is_opening
    }
```

> **`JournalEntry`/`JournalLine` vs `AccountingLedgerEntry`** — A journal entry is
> the editable/approvable document. On `post()` the posting pipeline writes the
> immutable `accounting_ledger_entries` rows (the actual GL). Reports and balances
> read from `accounting_ledger_entries`; the `journal_lines` carry the
> sub-ledger (`against_invoice_id`/`against_bill_id`) and bank-rec clearance tags.

***

## Domain 2 — Sales (Invoices, Quotations, Credit Notes)

| Entity (`@Entity`)                  | Purpose                                                                                           | Key columns                                                                                                                                                                                                                                                                                                               | Key relations                                                                                                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `Invoice` (`invoices`)              | Sales invoice **and** credit note (discriminated by `documentType`). Extends `SubmittableEntity`. | `workspace_id`, `invoice_number` (unique per workspace), `contact_id`, `issueDate`/`dueDate`, `subtotal`/`taxAmount`/`discountAmount`/`total`/`amountPaid`/`balanceDue` (numeric 18,2), `currency`, `workflowState`, `documentType` (INVOICE/CREDIT\_NOTE), `isReturn`, `returnAgainstId`, `efris*` (EFRIS fiscalisation) | `contact`, `journalEntry`, self-ref `returnAgainstId`, `lines` → `InvoiceLine[]`, `allocations` → `PaymentAllocation[]` |
| `InvoiceLine` (`invoice_lines`)     | A revenue line on an invoice (own `id`, no `tenant_id`).                                          | `invoice_id`, `lineOrder`, `quantity` (18,4), `unitPrice`, `discountPercent`, `taxRate`, `lineTotal`, `vatAmount`, `income_account_id`, `tax_code_id`, `cost_center_id`, stock refs (`item_id`/`warehouse_id`/`uom_id`)                                                                                                   | `invoice` (cascade DELETE), `Account` (`income_account_id`)                                                             |
| `Quotation` (`quotations`)          | Sales quotation, convertible to an invoice. Extends `SubmittableEntity`.                          | `quotation_number`, `contact_id`, `issueDate`/`validUntil`, totals (18,2), `workflowState`, `convertedToInvoiceId`, `sentAt`/`acceptedAt`/`convertedAt`, `opportunityId`                                                                                                                                                  | `contact`, `Invoice` (`convertedToInvoiceId`), `TermsTemplate`, `lines` → `QuotationLine[]`                             |
| `QuotationLine` (`quotation_lines`) | A quotation line; supports alternative-line groups (own `id`).                                    | `quotation_id`, `sortOrder`, `item_id`/`uom_id`, qty/price/discount/tax/lineTotal, `convertedQty`, `isAlternative`, `alternativeGroupIndex`                                                                                                                                                                               | `quotation` (cascade DELETE)                                                                                            |

> Credit notes are `Invoice` rows with `documentType = CREDIT_NOTE`; debit notes
> are `Bill` rows with `documentType = DEBIT_NOTE`. Their allocations live in
> `payment_allocations` (see Money).

***

## Domain 3 — Purchases (Bills, Expenses, Petty Cash)

| Entity (`@Entity`)                                     | Purpose                                                                                                           | Key columns                                                                                                                                                                                                                                                           | Key relations                                                                                                                                                           |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Bill` (`bills`)                                       | Vendor bill **and** debit note (`documentType`). Extends `SubmittableEntity`.                                     | `bill_number`, `documentType` (BILL/DEBIT\_NOTE), `isReturn`, `returnAgainstId`, `vendor_id`/`vendorName`, `billDate`/`dueDate`, `subtotal`/`taxAmount`/`total`/`amountPaid`/`balanceDue`, `vatTotal`/`whtTotal`, `purchaseOrderId`, `paymentTermId`, `workflowState` | `contact` (`vendor_id`), self-ref `returnAgainstId`, `journalEntry`, `lineItems` → `BillLine[]`, `allocations` → `PaymentAllocation[]`                                  |
| `BillLine` (`bill_lines`)                              | An expense/asset line on a bill (own `id`).                                                                       | `bill_id`, `quantity`, `unitPrice`/`total`, `taxRate`, `expense_account_id`, `category_id`, `cost_center_id`, `tax_code_id`/`wht_code_id`, `vatAmount`/`whtAmount`, `isFixedAsset`, `asset_category_id`/`assetUsefulLifeMonths`                                       | `bill` (cascade DELETE)                                                                                                                                                 |
| `Expense` (`expenses`)                                 | Staff/vendor expense claim with tax + WHT breakdown. Extends `BaseEntity` (carries its own `docstatus` smallint). | `expense_number`, `expenseDate`, `contact_id`/`employee_id`, `category_id`/`account_id`/`payment_account_id`/`payable_account_id`, `amount`/`currency`, `exchangeRate`, `grandTotal`/`baseGrandTotal` (18,4), `paymentStatus`, `workflowState`, `cost_center_id`      | `contact`, `Account`s, `category`, `journalEntry`/`accrualJournalEntry`, `items` → `ExpenseItem[]`, `claimAdvances` → `ExpenseClaimAdvance[]`, `taxes` → `ExpenseTax[]` |
| `ExpenseItem` (`expense_items`)                        | A line within an expense claim (own `id`).                                                                        | `expense_id`, `expenseDate`, `category_id`/`account_id`/`claim_type_id`, `claimedAmount`/`sanctionedAmount` (+ `base*` 18,4), `cost_center_id`, `sortOrder`                                                                                                           | `expense` (cascade DELETE), `category`, `account`, `ExpenseClaimType`                                                                                                   |
| `ExpenseTax` (`expense_taxes`)                         | A tax row on an expense (own `id`).                                                                               | `expense_id`, `account_id`, `rate`, `taxAmount`/`baseTaxAmount`, `sortOrder`                                                                                                                                                                                          | `expense` (cascade DELETE), `account`                                                                                                                                   |
| `ExpenseClaimType` (`expense_claim_types`)             | Reusable claim-type template (travel, meals…).                                                                    | `workspace_id`, `name`, `expense_account_id`, `default_cost_center_id`, `isActive`                                                                                                                                                                                    | `Account`, `CostCenter`                                                                                                                                                 |
| `ExpenseClaimAdvance` (`expense_claim_advances`)       | Allocation bridge expense ↔ salary advance (own `id`).                                                            | `expense_id`, `advance_id`, `allocatedAmount` (18,4), `sortOrder`                                                                                                                                                                                                     | `expense` (cascade DELETE)                                                                                                                                              |
| `PettyCashFund` (`petty_cash_funds`)                   | A custodian petty-cash float with limit/balance tracking.                                                         | `workspace_id`, `name`, `custodian_id`/`custodianName`, `fundLimit`/`currentBalance`/`totalDisbursed`/`totalReplenished`, `status`, `account_id`                                                                                                                      | `disbursements` → `PettyCashDisbursement[]`, `replenishments` → `PettyCashReplenishment[]`                                                                              |
| `PettyCashDisbursement` (`petty_cash_disbursements`)   | A payout from a fund.                                                                                             | `fund_id`, `amount`, `contact_id`, `recipientName`, `purpose`, `category_id`, `receiptAttachment`, `date`                                                                                                                                                             | `fund` (cascade DELETE)                                                                                                                                                 |
| `PettyCashReplenishment` (`petty_cash_replenishments`) | A top-up of a fund from a bank account.                                                                           | `fund_id`, `amount`, `payment_account_id`, `reference`, `date`, `journal_entry_id`                                                                                                                                                                                    | `fund` (cascade DELETE)                                                                                                                                                 |

***

## Domain 4 — Money (Payments, Allocations, Loans, Bank Reconciliation, Debts)

| Entity (`@Entity`)                                       | Purpose                                                                                                                                          | Key columns                                                                                                                                                                                                                                                                                                         | Key relations                                                                                                    |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `Payment` (`payments`)                                   | Unified Payment-In / Payment-Out (cash receipt or disbursement).                                                                                 | `workspace_id`, `payment_number`, `type` (PAYMENT\_IN/PAYMENT\_OUT), `contact_id`, `paymentDate`, `amount`/`currency`, `account_id`, `paymentMethod`, `status` (DRAFT/SUBMITTED/POSTED), `unallocatedAmount`/`whtTotal`, `advance_id`, `workflowState`                                                              | `contact`, `account`, `journalEntry`, `allocations` → `PaymentAllocation[]`, `deductions` → `PaymentDeduction[]` |
| `PaymentAllocation` (`payment_allocations`)              | N-way matrix linking a money source (payment / credit-note / debit-note) to a target (invoice / bill / expense). Own `id`, nullable `tenant_id`. | source: `payment_id` / `credit_note_invoice_id` / `debit_note_bill_id` (mutually exclusive); target: `invoice_id` / `expense_id` / `bill_id`; refund targets: `payment_target_credit_note_id` / `payment_target_debit_note_id`; `amount`                                                                            | `payment` (cascade DELETE), `Invoice`/`Bill`/`Expense` targets                                                   |
| `PaymentDeduction` (`payment_deductions`)                | A reduction row on a payment (WHT, bank charge, other).                                                                                          | `workspace_id`, `payment_id`, `deductionType` (WHT/BANK\_CHARGE/OTHER), `withholding_code_id`, `amount`, `account_id`, `memo`, `rowOrder`                                                                                                                                                                           | `payment` (cascade DELETE), `WithholdingCode` (RESTRICT), `Account` (RESTRICT)                                   |
| `PaymentScheduleRow` (`payment_schedule_rows`)           | A per-invoice/bill payment milestone expanded from a `PaymentTerm`.                                                                              | `workspace_id`, `invoice_id` / `bill_id` (mutually exclusive), `rowOrder`, `dueDate`, `portionPercent`/`discountPercent`, `amount`/`paidAmount`/`outstandingAmount`, `discountDeadline`                                                                                                                             | `Invoice` / `Bill`                                                                                               |
| `BankLoan` (`bank_loans`)                                | A loan facility with amortization schedule.                                                                                                      | `workspace_id`, `loan_number`, `name`, `loanType`, `status` (DRAFT/ACTIVE/CLOSED), `lenderName`/`lender_contact_id`, `principalAmount`/`interestRate`/`termMonths`/`repaymentFrequency`, `*_account_id` (liability/interest/disbursement), `outstandingBalance`/`nextPaymentAmount`, `amortizationSchedule` (jsonb) | `repayments` → `BankLoanRepayment[]`                                                                             |
| `BankLoanRepayment` (`bank_loan_repayments`)             | One repayment against a loan.                                                                                                                    | `loan_id`, `repaymentDate`, `principalAmount`/`interestAmount`/`totalAmount`, `payment_account_id`, `referenceNumber`, `journal_entry_id`                                                                                                                                                                           | `BankLoan` (`loan_id`)                                                                                           |
| `BankReconciliation` (`bank_reconciliations`)            | A reconciliation session for one bank account over a period.                                                                                     | `workspace_id`, `account_id`/`accountName`, `periodStart`/`periodEnd`, `status` (IN\_PROGRESS/COMPLETED/FAILED), `bankStatementBalance`/`systemBalance`/`difference`, `matchedCount`/`unmatchedCount`                                                                                                               | `statementLines` → `BankStatementLine[]`                                                                         |
| `BankStatementLine` (`bank_statement_lines`)             | An imported bank-statement transaction line.                                                                                                     | `workspace_id`, `account_id`, `reconciliation_id` (nullable), `date`, `description`, `reference`, `amount`, `matchStatus` (UNMATCHED/PARTIALLY\_MATCHED/MATCHED)                                                                                                                                                    | `reconciliation` (SET NULL), `matches` → `BankStatementLineMatch[]`                                              |
| `BankStatementLineMatch` (`bank_statement_line_matches`) | Allocation of a statement line to a GL `journal_line` (unique per `journal_line_id`).                                                            | `workspace_id`, `statement_line_id`, `journal_line_id`, `allocatedAmount`                                                                                                                                                                                                                                           | `BankStatementLine` (cascade DELETE), `JournalLine` (RESTRICT)                                                   |
| `DebtSettings` (`debt_settings`)                         | Per-book defaults for non-sale debt receivables (own `id`/`tenant_id`/`workspace_id`, unique per workspace).                                     | `tenant_id`, `workspace_id`, `reasonDefaults` (jsonb), `deposit_to_account_id`                                                                                                                                                                                                                                      | —                                                                                                                |

***

## Domain 5 — Masters (Contacts, Categories, Tax, Withholding, Payment Terms)

| Entity (`@Entity`)                                | Purpose                                                                                                                            | Key columns                                                                                                                                                                                                                                                       | Key relations                                           |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `Contact` (`contacts`)                            | Unified party master — customer / vendor / employee.                                                                               | `workspace_id`, `type` (`ContactType`), `firstName`/`lastName`/`companyName`/`displayName`, `emailAddress`/`phoneNumber`, address fields, `taxId`/`nin`/`brn`/`legalName`, `buyerType` (EFRIS), `paymentTermsDays`, `creditLimit`, `crm_contact_id`/`employee_id` | — (self-contained; `employee_id` links HR)              |
| `ContactBalanceCache` (`contact_balance_cache`)   | Cached AR/AP totals per contact (denormalized for perf, unique per workspace+contact).                                             | `workspace_id`, `contact_id`, `outstandingReceivable`/`outstandingPayable` (18,4), `lastRecomputedAt`                                                                                                                                                             | —                                                       |
| `Category` (`categories`)                         | Hierarchical income/expense category taxonomy.                                                                                     | `workspace_id`, `name`, `default_expense_account_id`/`default_income_account_id`, `parent_category_id`, `sortOrder`, `isActive`                                                                                                                                   | `Account`s, self-ref `parentCategory`/`childCategories` |
| `TaxCode` (`tax_codes`)                           | Per-business VAT/sales-tax code with rate + accounts. **Carries `business_id`.**                                                   | `workspace_id`, `business_id`, `code`/`label`, `rate`, `jurisdiction`, `kind` (`TaxCodeKind`: BOTH/INPUT\_ONLY/OUTPUT\_ONLY), `output_account_id`/`input_account_id`, `isCompound`/`isIncludedInPrice`/`isDefault`, `efrisTaxCategoryCode`                        | `Account` (output/input)                                |
| `WithholdingCode` (`withholding_codes`)           | Per-business WHT code with bracket thresholds by vendor category. **Carries `business_id`.**                                       | `workspace_id`, `business_id`, `code`/`label`, `basis`, `payable_account_id`/`recoverable_account_id`, `vendorCategories` (text\[]), `isActive`                                                                                                                   | `Account`s, `thresholds` → `WithholdingThreshold[]`     |
| `WithholdingThreshold` (`withholding_thresholds`) | A bracket row (threshold amount → rate%) within a WHT code.                                                                        | `withholding_code_id`, `thresholdAmount`, `ratePercent`, `rowOrder`                                                                                                                                                                                               | `WithholdingCode` (cascade DELETE)                      |
| `WithholdingBalance` (`withholding_balances`)     | Per-party YTD running total for bracket selection (unique per tenant/workspace/business/code/party/FY). **Carries `business_id`.** | `workspace_id`, `business_id`, `withholding_code_id`, `partyType`/`party_id`, `fiscalYear`, `cumulativeAmount`/`cumulativeTaxWithheld`                                                                                                                            | `WithholdingCode`                                       |
| `PaymentTerm` (`payment_terms`)                   | Per-business payment-term template, expanded to `PaymentScheduleRow` at submit. **Carries `business_id`.**                         | `workspace_id`, `business_id`, `name`, `rows` (jsonb), `isDefault`                                                                                                                                                                                                | —                                                       |
| `TermsTemplate` (`terms_templates`)               | Reusable legal/commercial terms text for invoices/bills.                                                                           | `workspace_id`, `name`, `body`, `isDefault`                                                                                                                                                                                                                       | —                                                       |

***

## Domain 6 — Budgeting & Cost Centers

| Entity (`@Entity`)            | Purpose                                                   | Key columns                                                                                                                                                                   | Key relations                           |
| ----------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `Budget` (`budgets`)          | Annual/period budget header with workflow state.          | `workspace_id`, `name`, `fiscalYear`, `periodStart`/`periodEnd`, `totalAmount`, `workflowState`, `docstatus`, `submittedAt`/`approvedAt`                                      | `lines` → `BudgetLine[]` (cascade)      |
| `BudgetLine` (`budget_lines`) | A monthly budget row per cost-center line (own `id`).     | `workspace_id`, `budget_id`, `cost_center_id`, `periodMonth` (YYYY-MM), `budgetedAmount`/`annualAmount`, `lineGroupKey`, `distributionMode` (even/manual)                     | `Budget` (cascade DELETE), `CostCenter` |
| `CostCenter` (`cost_centers`) | Hierarchical cost-allocation dimension (nested-set tree). | `workspace_id`, `code` (unique per workspace), `name`, `parent_id`, `lft`/`rgt` (nested-set pointers), `isGroup`/`isActive`, `hr_department_id`/`org_unit_id`, `displayOrder` | self-ref `parent`/`children`            |

***

## Domain 7 — Posting Internals (Outbox, Source-Link, Idempotency, Profiles, Audit)

These are the plumbing that lets other products post into accounting idempotently
and durably. They scope by `tenant_id` (+ `business_id`/`book_id`), **not**
`workspace_id`.

| Entity (`@Entity`)                                         | Purpose                                                                               | Key columns                                                                                                                                                                                                    | Key relations |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `AccountingIntentOutbox` (`accounting_intent_outbox`)      | Durable async outbox of posting intents (idempotent replay).                          | `business_id`, `book_id`, `productKey`, `intentType`/`sourceId`/`dedupeKey`, `payloadJson` (jsonb), `status` (PENDING/PROCESSED/FAILED), `attemptCount`, `availableAt`/`processedAt`, `lastError`              | —             |
| `AccountingSourceLink` (`accounting_source_links`)         | Canonical 1:1 link from a product source (Invoice, Bill…) to its accounting artifact. | `book_id`, `productKey`, `sourceType`/`sourceId`, `accountingDocumentType` (JOURNAL/DOCUMENT), `accountingDocumentId`/`journalEntryId`, `postingStatus` (PENDING/POSTED/FAILED), `idempotencyKey`, `lastError` | —             |
| `PostingIdempotencyKey` (`posting_idempotency_keys`)       | Per-journal-entry dedup key preventing double-post (own `id`/`tenant_id`, unique).    | `tenant_id`, `journal_entry_id`, `idempotencyKey`, `createdAt`                                                                                                                                                 | —             |
| `AccountingPostingProfile` (`accounting_posting_profiles`) | Product → GL mapping rules (e.g. `retail.sale` → DR AR / CR revenue).                 | `book_id`, `productKey`, `transactionType`, `version`, `rulesJson` (jsonb), `isDefault`/`isActive`                                                                                                             | —             |
| `AccountingAuditEvent` (`accounting_audit_events`)         | Immutable audit trail of accounting entity state changes.                             | `workspace_id`, `entityType`/`action`, `entity_id`, `occurredAt`, `summary`, `metadata` (simple-json)                                                                                                          | —             |

***

## Domain 8 — Sequences & lifecycle records

| Entity (`@Entity`)                                          | Purpose                                                                                  | Key columns                                                                                                                                                                                                               | Key relations                                    |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `DocumentSequence` (`accounting_document_sequences`)        | Per-workspace counter for document numbering (Invoice#, Bill#…).                         | `workspace_id`, `sequenceKey` (unique per workspace), `currentValue` (bigint)                                                                                                                                             | —                                                |
| `OpeningBalanceBatch` (`opening_balance_batches`)           | Wrapper for a bulk opening-balance entry set (GL + AR/AP).                               | `workspace_id`, `name`, `asOfDate`, `status` (DRAFT/SUBMITTED/POSTED), `journal_entry_id`, `notes`                                                                                                                        | `JournalEntry`, `lines` → `OpeningBalanceLine[]` |
| `OpeningBalanceLine` (`opening_balance_lines`)              | An opening-balance row — GL account or AR/AP link (own `id`/`tenant_id`/`workspace_id`). | `batch_id`, `lineType` (GL\_ACCOUNT/RECEIVABLE/PAYABLE), `account_id`/`contact_id`, `documentNumber`/`linked_*`, `debit`/`credit`                                                                                         | `OpeningBalanceBatch` (cascade DELETE)           |
| `AccountMerge` (`account_merges`)                           | Audit log of a GL account merge (source → target).                                       | `workspace_id`, `source_account_id`/`target_account_id`, `mergedAt`, `merged_by_user_id`, `glRowsRewritten`, `reason`                                                                                                     | —                                                |
| `AccountPeriodSummary` (`account_period_summaries`)         | Cached per-account period debit/credit totals for trial-balance perf.                    | `workspace_id`, `account_id`, `periodKey` (YYYY-MM), `totalDebit`/`totalCredit` (18,4), `computedAt`                                                                                                                      | —                                                |
| `AccountingYearEndClose` (`accounting_year_end_closes`)     | Year-end closing record (P\&L → retained earnings).                                      | `workspace_id`, `fiscalYear` (unique per workspace), `periodStart`/`periodEnd`/`closeDate`, `journal_entry_id`, `retained_earnings_account_id`, `netIncome`, `notes`                                                      | `JournalEntry`                                   |
| `PeriodReopenRequest` (`period_reopen_requests`)            | Approval workflow for reversing a period lock.                                           | `workspace_id`, `period_id`, `requested_by_user_id`, `reason`, `status` (PENDING/APPROVED/REJECTED/CANCELLED), `resolved_*`                                                                                               | —                                                |
| `JournalEntryTemplate` (`journal_entry_templates`)          | Reusable JE template for recurring posting patterns. **Carries `business_id`.**          | `workspace_id`, `business_id`, `name`, `defaultVoucherType`, `defaultMemo`, `created_by_user_id`                                                                                                                          | `lines` → `JournalEntryTemplateLine[]`           |
| `JournalEntryTemplateLine` (`journal_entry_template_lines`) | A line of a JE template (account + side).                                                | `template_id`, `lineOrder`, `account_id`, `description`, `side` (debit/credit)                                                                                                                                            | `JournalEntryTemplate` (cascade DELETE)          |
| `RecurringTransaction` (`recurring_transactions`)           | Scheduler config to auto-generate invoices/bills/expenses on a cadence.                  | `workspace_id`, `name`, `entityType`, `frequency` (DAILY…ANNUALLY), `startDate`/`endDate`/`nextOccurrence`/`lastGenerated`, `status` (ACTIVE/PAUSED/INACTIVE), `occurrenceCount`/`maxOccurrences`, `templateData` (jsonb) | `history` → `RecurringTransactionHistory[]`      |

***

## `JournalSourceType` — the product provenance enum

`AccountingLedgerEntry.source_type` / `JournalEntry.source_type` tags every GL row
with the upstream module that produced it. The full enum (`enums/journal-source-type.enum.ts`)
includes the native accounting sources (`INVOICE`, `BILL`, `PAYMENT`, `RECEIPT`,
`MANUAL`, `OPENING_BALANCE`, `ADJUSTMENT`, `CREDIT_NOTE`, `PETTY_CASH`, `BANK_LOAN`)
plus cross-product sources that bridge into accounting: payroll/HR
(`PAYROLL`, `SALARY_ADVANCE`, `EMPLOYEE_LOAN`, `SEPARATION_SETTLEMENT`,
`GRATUITY_PROVISION`, `STATUTORY_REMITTANCE`, `REIMBURSEMENT`, `IMPREST`),
fixed-assets (`ASSET_*`, `DEPRECIATION`), retail (`RETAIL_*`), agriculture
(`AGRI360_*`), SACCO (`SACCO_*`), and faith (`FAITH360_GIVING`).

***

## Gaps / notes for reviewers

* The child-line entity field lists (Domains 2–8) and the `business_id` annotations
  were extracted by reading each entity file; spot-verified `Invoice`,
  `JournalLine`, `Account`, `AccountingBook`, `AccountingPeriod`, `JournalEntry`
  directly. If you change a column, regenerate the relevant table.
* `Invoice`, `Bill`, `Quotation` extend `SubmittableEntity` (docstatus lifecycle);
  `Expense` and `Budget` carry a `docstatus` column but extend `BaseEntity`.
* Several entities referenced here have additional reporting/scheduling siblings
  not tabulated (e.g. `AccountingReportPreset`, `AccountingReportSchedule`,
  `AccountingReportSnapshot`, `ChartImportBatch`, `AccountingChartSetup`,
  `PrintSettings`, `ReminderSettings`, `RecurringTransactionHistory`). See
  `docs/accounting-reports.md` for the reporting cluster.
