> ## 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 Module — Architecture

> A developer's map of the Hitaji 360 accounting service. This document describes how the module is layered, how its ~40 controllers and services group by…

A developer's map of the Hitaji 360 accounting service. This document describes
how the module is layered, how its \~40 controllers and services group by domain,
how a write request travels from HTTP down to a balanced double-entry ledger
posting, and where the multi-tenancy and period guards sit.

All paths below are relative to `hitaji-erp-api/src/accounting`.

## Purpose & scope

The accounting module is the single general-ledger authority for the whole
platform. It owns the chart of accounts, the journal/ledger, invoices, bills,
expenses, payments, budgets, contacts, financial reports — and, crucially, the
**bridge** through which every *other* product (HR/payroll, SACCO, retail,
fixed-assets, agri360, faith360, personal-finance) posts money into the ledger
without touching the GL primitives directly. See
[`posting-engine.md`](/accounting/developer/posting-engine) and
[`accounting-bridge.md`](/accounting/developer/accounting-bridge) for the two deepest seams.

## Layering

```
HTTP (controllers/*.controller.ts)
   │  @AdminOrJwtAuth + TenantAuthGuard + PermissionsGuard + @BusinessCtx
   ▼
Domain services (services/*.service.ts)
   │  business rules, DTO ⇄ entity, document lifecycle
   ▼
Posting engine (services/accounting-posting.service.ts + posting/*)
   │  validate → guard (period/frozen) → merge → round-off
   ▼
Ledger repository (repositories/accounting-ledger.repository.ts)
   │  one SERIALIZABLE transaction, idempotency, WHT balance deltas
   ▼
Ledger entities (entities/accounting-ledger-entry.entity.ts, journal-*.entity.ts)
   │  TypeORM → PostgreSQL
   ▼
PostgreSQL (single DB, tenant_id + workspace_id scoped)
```

A few cross-cutting layers wrap every write:

* **Auth/tenant**: `TenantAuthGuard` (`src/tenant/guards`), the global
  `PermissionsGuard` (`src/auth/permissions.guard.ts`) keyed off
  `@RequirePermissions(...)`, and the `@BusinessCtx()` decorator
  (`src/common/business-context`) which resolves the active `businessId`.
* **Idempotency**: the `@Idempotent()` decorator (`src/common/idempotency`) on
  mutating bridge endpoints.
* **Events**: `EventEmitter2` — posting emits `JOURNAL_POSTED_EVENT`
  (`events/journal-posted.event.ts`); listeners live in `listeners/`.
* **Scheduling**: `@nestjs/schedule` cron jobs (outbox drain, idempotency-key
  prune, overdue reminders, recurring transactions).

## Module map

`accounting.module.ts` registers 39 controllers and a large provider list. The
controllers/services group into the following domains.

### General ledger core

| Concern                   | Controller                                               | Service(s)                                                                                                                                                                                               |
| ------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Chart of accounts         | `accounts.controller.ts`                                 | `accounts.service.ts`, `account-defaults.service.ts`, `account-default-resolver.ts`, `account-invariants.service.ts`, `account-merge.service.ts`, `chart-template.service.ts`, `chart-import.service.ts` |
| Journals                  | `journal.controller.ts`                                  | `journal.service.ts`, `journal-entry-template.service.ts`                                                                                                                                                |
| Ledger posting engine     | (none — internal)                                        | `accounting-posting.service.ts` + `posting/*`                                                                                                                                                            |
| Opening balances          | `opening-balances.controller.ts`                         | `opening-balances.service.ts`                                                                                                                                                                            |
| Categories / cost centers | `categories.controller.ts`, `cost-centers.controller.ts` | `categories.service.ts`, `cost-centers.service.ts`, `org-unit-cost-center-sync.service.ts`                                                                                                               |

### Receivables / payables (sub-ledger documents)

| Concern                | Controller                                                                                     | Service(s)                                                                                                                                                                             |
| ---------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invoices               | `invoices.controller.ts`                                                                       | `invoices.service.ts`, `invoice-returns.service.ts`, `invoice-overdue.service.ts`                                                                                                      |
| Bills                  | `bills.controller.ts`                                                                          | `bills.service.ts`, `bill-returns.service.ts`                                                                                                                                          |
| Quotations             | `quotations.controller.ts`                                                                     | `quotations.service.ts`, `quotation-conversion.service.ts`, `quotation-expiry.service.ts`                                                                                              |
| Expenses               | `expenses.controller.ts`, `expense-claim-types.controller.ts`                                  | `expenses.service.ts`, `expense-claim-type.service.ts`                                                                                                                                 |
| Payments & allocation  | `payments.controller.ts`, `payment-reconciliation.controller.ts`, `payment-term.controller.ts` | `payments.service.ts`, `payment-reconciliation.service.ts`, `payment-deduction.service.ts`, `payment-receivable-resolver.ts`, `payment-schedule.service.ts`, `payment-term.service.ts` |
| Debts (AR) & reminders | `debts.controller.ts`, `debt-settings.controller.ts`, `reminder-settings.controller.ts`        | `debt-settings.service.ts`, `overdue-reminder.*`, `reminder-settings.service.ts`                                                                                                       |
| Contacts               | `contacts.controller.ts`                                                                       | `contacts.service.ts`, `contact-linking.service.ts`, `contact-balance.service.ts`                                                                                                      |

### Cash, bank, petty cash

| Concern             | Controller                                                                 | Service(s)                        |
| ------------------- | -------------------------------------------------------------------------- | --------------------------------- |
| Bank reconciliation | `bank-reconciliations.controller.ts`, `bank-statement-lines.controller.ts` | `bank-reconciliations.service.ts` |
| Bank loans          | `bank-loans.controller.ts`                                                 | `bank-loans.service.ts`           |
| Petty cash          | `petty-cash.controller.ts`                                                 | `petty-cash.service.ts`           |

### Periods, close, tax

| Concern           | Controller                                                        | Service(s)                                                                                              |
| ----------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Periods & locking | `accounting-periods.controller.ts`, `period-reopen.controller.ts` | `period-close.service.ts`, `period-reopen.service.ts`, `period-summary.service.ts`                      |
| Year-end close    | `year-end-close.controller.ts`                                    | `year-end-close.service.ts`                                                                             |
| Tax / WHT codes   | `tax-code.controller.ts`, `withholding-code.controller.ts`        | `tax-code.service.ts`, `withholding-code.service.ts` + `posting/vat-engine.ts`, `posting/wht-engine.ts` |

### Reporting & budgets

| Concern           | Controller                                                        | Service(s)                                                                                     |
| ----------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Financial reports | `financial-reports.controller.ts`, `report-presets.controller.ts` | `financial-reports.service.ts`, `report-presets.service.ts` (see `docs/accounting-reports.md`) |
| Budgets           | `budgets.controller.ts`                                           | `budgets.service.ts`, `budget-balance.service.ts`, `consolidated-budget.service.ts`            |
| Recurring         | `recurring-transactions.controller.ts`                            | `recurring-transactions.service.ts`, `recurring-transactions-scheduler.service.ts`             |

### Integration surface (the bridge)

| Concern             | Controller                                                     | Service(s)                                                                                                                                                                                                                                                                                                                         |
| ------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Product → GL bridge | `accounting-bridge.controller.ts`, `integration.controller.ts` | `accounting-bridge.service.ts`, `accounting-routing.service.ts`, `posting-profile-resolver.service.ts`, `bulk-posting.service.ts`, `accounting-outbox-writer.service.ts`, `accounting-intent-processor.service.ts`, `staff-posting-book-resolver.service.ts`, `employee-contact-provisioning.service.ts`, `integration.service.ts` |

### Settings, audit, documents

`workspace-settings.controller.ts`, `print-settings.controller.ts`,
`terms-templates.controller.ts`, `accounting-audit.controller.ts` over
`print-settings.service.ts`, `terms-templates.service.ts`,
`accounting-audit.service.ts`, `document-sequence.service.ts`,
`document-template.service.ts`, `document-pdf.service.ts`, `pdf-render.service.ts`.

## How a write becomes a ledger posting

There are two front doors that converge on the **same** posting engine.

### Front door A — a user posts a manual journal

```mermaid theme={null}
sequenceDiagram
    participant C as JournalController
    participant J as JournalService
    participant PC as PeriodCloseService
    participant P as AccountingPostingService
    participant R as AccountingLedgerRepository
    C->>J: post(id, workspaceId, userId, tenantId)
    J->>PC: assertPostingAllowed(entryDate, workspaceId, tenantId)
    J->>J: updateStatus(POSTED) + workflow transition
    J->>P: postJournalEntryWithContext(entry, context)
    P->>P: validate → frozen/period guards → merge → round-off
    P->>R: createEntriesForJournalEntry(entry, {rowDrafts})
    R->>R: SERIALIZABLE txn: existence check + insert rows
    P->>C: emit JOURNAL_POSTED_EVENT
```

`JournalService.post()` (`services/journal.service.ts:324`) re-checks balance,
calls `PeriodCloseService.assertPostingAllowed()`, transitions the workflow
state, flips the entry to `POSTED`, then calls the private
`postWithCurrentPipeline()` (`journal.service.ts:718`) which builds an
`AccountingPostingContext` (resolving `businessId` via
`AccountingRoutingService.resolveBusinessIdForWorkspace`) and hands off to
`AccountingPostingService.postJournalEntryWithContext()`.

### Front door B — another product posts through the bridge

```
product module → POST /accounting/bridge/journal-intents
   → AccountingBridgeService.publishJournalIntent
      → routing.resolve(businessId → book → workspaceId)
      → profileResolver.resolveJournalLines(systemCode → accountId)
      → journalService.create(...) → journalService.post(...)  ← rejoins front door A
```

Either way, control reaches **`AccountingPostingService`**, the single posting
engine. It (`accounting-posting.service.ts:198` `validateAndPrepareRows`):

1. Loads the referenced accounts (tenant + workspace scoped; throws if any
   account is out of scope — `loadAccounts`).
2. Runs `PostingValidatorService.validate()` — balance check (|Dr−Cr| ≤ 0.005),
   no line with both debit and credit, accounts postable/active.
3. Runs `FrozenAccountGuardService.check()` and `PeriodLockGuardService.check()`.
4. Optionally merges duplicate rows (`PostingRowMergerService`) and appends a
   round-off row (`RoundOffCalculatorService`).
5. If any issue is collected, throws `BadRequestException` with
   `code: 'POSTING_FAILED'`; otherwise writes via
   `AccountingLedgerRepository.createEntriesForJournalEntry()`, which performs
   the insert inside a single transaction with a ledger-existence guard and
   (when supplied) applies WHT balance deltas in the same transaction.

The full engine internals — idempotency keys, the intent→outbox→ledger pipeline,
posting profiles, and bulk posting — are documented in
[`posting-engine.md`](/accounting/developer/posting-engine).

## Where multi-tenancy and period guards sit

* **Tenant isolation** is enforced at *every* layer, not just the edge. The JWT
  yields a `tenantId` (extracted in `src/tenant`); domain services thread it
  into every repository call; the ledger repository scopes every query by
  `tenant_id` + `workspace_id`; and `AccountingPostingService.loadAccounts()`
  hard-fails if a journal line references an account outside the active
  tenant/workspace.
* **Business scoping**: an accounting `workspaceId` is an **accounting book id**,
  not a task-management workspace (see the repo `CLAUDE.md`). The
  `@BusinessCtx()` decorator resolves the active `businessId`;
  `AccountingRoutingService` maps `businessId → book → workspaceId` and *refuses
  to guess* when a business has multiple active books with no default
  (`BOOK_ROUTING_AMBIGUOUS`).
* **Period locking** is centralised in two places that agree on stable codes:
  * `PeriodCloseService.assertPostingAllowed()` throws the typed
    `PeriodClosedException` with `code: 'PERIOD_CLOSED' | 'PERIOD_LOCKED'`.
    Called by `JournalService.post/reverse/submitForApproval`. Downstream
    products (the SACCO bridge) pattern-match on `body.code` — do not replace it
    with a generic throw.
  * `PeriodLockGuardService.check()` (`posting/period-lock-guard.service.ts`) is
    the engine-level guard inside `validateAndPrepareRows`. It returns
    `ok | override | blocked` and is overridable via
    `context.overrides.periodLock` (the `post-to-closed-period` role).

## Extension points

* **New product posting source** → do not add a controller. Publish a journal
  intent through `AccountingBridgeService` and register a `JournalSourceType`
  mapping (`accounting-bridge.service.ts` `mapIntentToSourceType`). See
  [`accounting-bridge.md`](/accounting/developer/accounting-bridge).
* **New account-mapping rule** → add an `AccountingPostingProfile`
  (`entities/accounting-posting-profile.entity.ts`) keyed by
  `(book, productKey, transactionType)`, resolved by
  `PostingProfileResolver`.
* **New financial report** → extend `financial-reports.service.ts`; read
  `docs/accounting-reports.md` first (reconciliation contracts, snapshot hooks).
* **New posting-time validation/guard** → add a service under `posting/` and
  wire it into `AccountingPostingService.validateAndPrepareRows` so it runs for
  *both* front doors.
* **New entity column** → ship a TypeORM migration (`migrations/`);
  `synchronize: false` in every environment, including local dev.
