534 lines
44 KiB
Markdown
534 lines
44 KiB
Markdown
# 21 · GENERAL LEDGER — Frontend (Ledgers section)
|
||
|
||
> **Navigation:** you arrived from `00-CORE.md`. The transport this section calls through is
|
||
> `docs/12-GENERAL-LEDGER-INTEGRATION.md` (ERPCore's `/api/v1/gl/*` proxy into the external General
|
||
> Ledger service — that doc's new §7 has a curated endpoint reference for everything this page uses).
|
||
> General frontend architecture/validation rules are `20-FRONTEND.md` — this doc only adds what's
|
||
> specific to the Ledgers screens. Record work in `Frontend/PROGRESS.md`.
|
||
> **GL's own endpoint contract** (exact request/response shapes, error cases) lives in the General
|
||
> Ledger service's own repo (`04_API_Reference_And_Scenarios.md`) — not duplicated here in full, per
|
||
> `01-DOC-GUIDE.md §5`'s single-source-of-truth rule; `docs/12` §7 is a summary for quick reference,
|
||
> that doc is the canonical detail.
|
||
|
||
---
|
||
|
||
## 0. Major update, 2026-07-22 — the GL backend changed substantially since this section was last built
|
||
|
||
**Nothing in this pass has been implemented against the frontend codebase yet.** The 2026-07-20 build (§6's history) reflects an earlier, now-superseded GL contract. This revision describes the *target* design against the confirmed, live-verified-on-GL's-side contract as of 2026-07-22 — every section below should be read as "what to build," not "what exists," until §6 says otherwise.
|
||
|
||
Three things changed on GL's side, in order of how much they affect this doc:
|
||
|
||
1. **Five reports got real structural rework** (not just field renames) — Trial Balance flattened, Profit & Loss restructured into named sections, Cash Flow's display simplified, Tax Report **completely redesigned** into an Income Tax Computation (previously a VAT/WHT/NBT summary), plus a new `outputFormat=Csv` on all seven reports.
|
||
2. **Cash and Bank accounts are two separate GL tables/endpoints now**, not one table with a type field — `POST /bank-accounts` (unchanged) and a new `POST /cash-accounts`, unified for reading via `GET /bank-accounts?accountType=Cash|Bank|Both`.
|
||
3. **`CLAUDE.md` Rule 8.2 renamed numeric ID fields to business codes across most of GL's API** — the one that reaches this page: `GeneralLedger`'s `accountId` param is now `accountCode` (still optional, same two-mode behavior as before), and the Bank/Cash create forms' `glAccountId` is now `glAccountCode`.
|
||
|
||
---
|
||
|
||
## 1. What this is
|
||
|
||
A new **Ledgers** sidebar section (`app/dashboard/ledgers/*`) giving the frontend statutory-format
|
||
financial reports and cash/bank-account management, sourced entirely from the external General Ledger
|
||
service via ERPCore's generic proxy (`docs/12-GENERAL-LEDGER-INTEGRATION.md`). Every screen calls
|
||
`GET /api/v1/gl/reports` or `/gl/bank-accounts`/`/gl/cash-accounts` — there is no ERPCore business
|
||
logic behind any of it yet (that internal wiring, e.g. posting real journal entries from
|
||
GRN/adjustments, is tracked separately and deliberately deferred, see `docs/12` §6).
|
||
|
||
**Sidebar structure** (`components/Layouts/AppSidebar.tsx`, mirrored server-side in
|
||
`Backend/ERPCore/Infra/Persistence/Configurations/{NavItem,SubNavItem,Permission}Configuration.cs`
|
||
per the existing RBAC-nav convention, docs/10 C.8/C.9) — **gains one entry, Tax Report:**
|
||
|
||
```
|
||
Ledgers
|
||
├── Trial Balance /dashboard/ledgers/trial-balance
|
||
├── Balance Sheet /dashboard/ledgers/balance-sheet
|
||
├── General Ledger /dashboard/ledgers/general-ledger
|
||
├── Profit & Loss /dashboard/ledgers/profit-and-loss
|
||
├── Cash Flow /dashboard/ledgers/cash-flow
|
||
├── Budget vs Actual /dashboard/ledgers/budget-vs-actual
|
||
├── Tax Report /dashboard/ledgers/tax-report (new)
|
||
└── Cash / Bank Accounts /dashboard/ledgers/bank-accounts (+ /new)
|
||
```
|
||
|
||
`/dashboard/ledgers` itself is a card-grid hub, same pattern as `/dashboard/stock` — gains an 8th card.
|
||
|
||
---
|
||
|
||
## 2. Transport: a dedicated GL client, not `lib/api-client.ts`
|
||
|
||
`lib/api/general-ledger.ts` is a **separate** fetch client from the one every other screen in this
|
||
app uses (`lib/api-client.ts`'s `apiRequest`/`apiRequestWithETag`). Reason: those assume ERPCore's
|
||
own RFC 7807 `ProblemDetails` error shape and a bare-DTO success body. The GL service wraps **every**
|
||
response — success and error alike — in its own `{ statusCode, success, message, data }` envelope,
|
||
and (a documented GL quirk, carried through the proxy unchanged per `docs/12` §4) success bodies are
|
||
camelCase while error bodies are PascalCase. `glRequest()` unwraps both casings itself and throws a
|
||
`GlApiError` shaped `{ status, detail }` — duck-type compatible with `lib/error-map.ts`'s
|
||
`ApiErrorLike`, so `errorMessage()`/toasts work unchanged across both API surfaces.
|
||
|
||
All calls are relative to `/api/v1/gl/*` — same-origin through the existing Next `rewrites()` proxy
|
||
(`next.config.ts`) → ERPCore → the GL service. No new proxy config was needed, and **still isn't** for
|
||
any of this revision's changes — ERPCore's `GeneralLedgerController` (docs/12) is a byte-for-byte
|
||
passthrough that never inspects GL's own field names or report contents, so every rename/restructure
|
||
described in this doc requires zero ERPCore-side change, only frontend-side (`docs/12` §4's new note
|
||
on this).
|
||
|
||
**No ETag/If-Match anywhere in this module** — the GL service's own reference documents no
|
||
concurrency tokens on any of the endpoints this UI calls.
|
||
|
||
---
|
||
|
||
## 3. Reports: "Sri Lankan Standard" report UI
|
||
|
||
Every report screen (`app/dashboard/ledgers/{trial-balance,balance-sheet,general-ledger,profit-and-loss,cash-flow,budget-vs-actual,tax-report}/page.tsx`)
|
||
shares three building blocks:
|
||
|
||
- **`components/reports/ReportHeader.tsx`** — a centered statutory header block: small-caps
|
||
"General Ledger" eyebrow, the report's **LKAS-aligned statement name** (not always GL's own
|
||
`reportType` value — see the mapping below), the as-at/for-period line, and a currency note. This
|
||
is the "Sri Lankan Standard report UI" requested: the on-screen equivalent of the formal
|
||
company-financial-statement layout (title block, period line, right-aligned money columns,
|
||
bold/indented subtotal rows), not a bespoke ad-hoc table per screen. **The Tax Report screen does
|
||
not use this component** — see its own entry below, it needs a fuller identity block GL's own PDF
|
||
gives that report alone.
|
||
- **`components/reports/DownloadPdfButton.tsx`** — re-issues the *exact same* report call with
|
||
`outputFormat=Pdf` instead of `Json`, base64-decodes `data.contentBase64` client-side into a
|
||
`Blob`, and triggers a browser download.
|
||
- **`components/reports/DownloadCsvButton.tsx`** *(new)* — identical mechanism, `outputFormat=Csv`
|
||
instead, `Blob` typed `text/csv`, triggers a `.csv` download. Sits next to the PDF button on every
|
||
screen — two download buttons now, not one. No proxy/transport concern either (`docs/12` §7) — same
|
||
passthrough as `Pdf` always was.
|
||
|
||
| Screen | `reportType` | On-screen title | Required params | Response shape |
|
||
|---|---|---|---|---|
|
||
| Trial Balance | `TrialBalance` | Trial Balance | `asOfDate` | **Flat** array `{accountCode, accountName, debit, credit}` — no `depth` anymore |
|
||
| Balance Sheet | `BalanceSheet` | Statement of Financial Position | `asOfDate` | Unchanged — hierarchical array `{depth, lineItem, accountType, balance}`, always ends with a synthetic `"Current Year Earnings"` row under Equity |
|
||
| General Ledger | `GeneralLedger` | General Ledger | `periodStart`, `periodEnd` (`accountCode` optional — renamed from `accountId`, see below) | Unchanged shape |
|
||
| Profit & Loss | `ProfitAndLoss` | Statement of Profit or Loss | `periodStart`, `periodEnd` | **Nested object**, not a flat array — named sections, see below |
|
||
| Cash Flow | `CashFlow` | Statement of Cash Flows | `periodStart`, `periodEnd` | Nested object; `workingCapitalChanges[]` gained `direction`; `openingCashBalance`/`closingCashBalance` still returned, not displayed |
|
||
| Budget vs Actual | `BudgetVsActual` | Budget vs Actual | `budgetId` | Unchanged |
|
||
| Tax Report *(new)* | `TaxSummary` | Income Tax Computation | `periodStart`, `periodEnd`; optional `allowableDeductions`, `otherTaxableIncome`, `qualifyingPaymentsReliefs`, `surchargeAmount`, `taxRateOverride` | Single flat object, see below |
|
||
|
||
**Defaults, so a screen is never blank on first load:** `asOfDate`/`periodEnd` default to today,
|
||
`periodStart` to the 1st of the current month (`lib/format.ts`'s `todayIso`/`startOfMonthIso`) —
|
||
these params are *required* by GL (400 if missing), so the UI always sends something sensible rather
|
||
than erroring on mount. The Tax Report's five optional parameters get **no forced client-side
|
||
default** — an untouched field sends nothing, letting GL's own server-side defaulting (`0` for four
|
||
of them, `system_config`-driven for `qualifyingPaymentsReliefs`/the tax rate) be the single source of
|
||
truth for what "not supplied" means.
|
||
|
||
**`reportType`/`outputFormat` are TS enums** (`types/general-ledger.ts`'s `ReportType`,
|
||
`ReportOutputFormat`) — `ReportOutputFormat` gains a `Csv` member. `GlAccount.accountTypeId` stays a
|
||
`GlAccountTypeId` enum (`Asset=1`…`Expense=5`).
|
||
|
||
**General Ledger: `accountId` renamed to `accountCode` (GL's `CLAUDE.md` Rule 8.2), behavior
|
||
unchanged from the 2026-07-20 correction.** GL's own reference still documents two modes:
|
||
`accountCode` supplied → "Account Ledger" (one account + its descendants, one running balance);
|
||
`accountCode` omitted → the **true General Ledger**, every `is_postable` account's own transactions
|
||
together, each with its own running balance that resets whenever the account changes, sorted by
|
||
`accountCode` then `entryDate`. This screen still always calls the second mode —
|
||
`reportsApi.generalLedger(periodStart, periodEnd)` never sends `accountCode` — and has no account
|
||
picker/input, same as before; only the underlying param name the client would use *if* a
|
||
single-account mode were ever added changed, not this screen's own behavior.
|
||
|
||
**Amount formatting** (`lib/format.ts`): comma-grouped thousands + fixed 2 decimals + parentheses for
|
||
negatives (`formatAmount`) — standard financial-statement convention. **CSV export does not reuse
|
||
this formatter** — GL's own CSV cells are plain decimals with a leading minus sign, no thousands
|
||
separator, no parentheses (spreadsheet-numeric-parsing convention, not human-display convention).
|
||
`DownloadCsvButton` downloads GL's bytes unmodified, same "don't reshape what GL sent" posture as the
|
||
PDF button.
|
||
|
||
**Hierarchical rows:** Balance Sheet still carries `depth`, rendered with `depth`-proportional left
|
||
padding. **Trial Balance dropped this entirely** — now a plain flat list, same row-component style as
|
||
Budget vs Actual.
|
||
|
||
**Profit & Loss is a nested object, not a flat array.** GL's response has named sections — `sales`,
|
||
`costOfSales`, `otherIncome`, `distributionExpenses`, `administrationExpenses`, `otherExpenses`,
|
||
`financialExpenses`, `unclassified` (only present with lines if the COA has untagged Income/Expense
|
||
accounts — GL flags this as worth watching for during setup) — each `{ lines[], total }`, plus
|
||
top-level `grossProfit`/`netProfitForPeriod`. The screen renders one bordered section per non-empty
|
||
group, in this fixed order: Sales, Cost of Sales, **Gross Profit** (its own bold subtotal row, not a
|
||
section), Other Income, the four expense groups, `unclassified` last if present, then **Net Profit
|
||
for the Period**.
|
||
|
||
**Cash Flow is a structured statement layout, not four `StatCard`s.** `Net Earnings` as its own line,
|
||
an "Additions to Cash" bordered section and a "Subtractions From Cash" bordered section — client-side
|
||
bucketed by sign from the combined `nonCashAdjustments[]` + `workingCapitalChanges[]` (a working-capital
|
||
line's label uses its `direction` field, e.g. `direction: "Decrease"` + `accountName: "Trade
|
||
Receivables"` → `"Decrease in Trade Receivables"`; a non-cash-adjustment line just prints its plain
|
||
`description`, e.g. `"Depreciation"`, no Increase/Decrease prefix) — then `Net Cash From Operations`
|
||
as a subtotal, Investing/Financing sections (their `lines[]`, no subtotal row when a section has only
|
||
one line), ending in the final combined total. `openingCashBalance`/`closingCashBalance` are fetched
|
||
(still in the response) but **not rendered anywhere on screen** — same "computed but not displayed"
|
||
posture GL's own PDF/CSV takes.
|
||
|
||
**Tax Report (`app/dashboard/ledgers/tax-report/page.tsx`) is an entirely new screen.** Layout:
|
||
- A `periodStart`/`periodEnd` date-range picker, same component as every other period-based report.
|
||
- **A collapsible "Adjustments" panel**, collapsed by default, with five optional numeric inputs —
|
||
`Allowable Deductions`, `Other Taxable Income`, `Qualifying Payments / Reliefs`,
|
||
`Surcharge / Education Levy`, and `Tax Rate Override` (%). Each maps directly to the report call's
|
||
optional query params.
|
||
- Result: a two-column `Description`/`Amount` table in GL's own fixed order (`profitBeforeTax` through
|
||
`balanceTaxPayable`) — `Add:`/`Less:` prefixes are literal row labels the component renders per a
|
||
fixed lookup, not derived from the amount's sign. Bold rows for the reconciliation checkpoints
|
||
(`Adjusted Business Profit`, `Assessable Income`, `Taxable Income`, `Gross Tax Liability`, the final
|
||
total).
|
||
- **The final row's label depends on the sign of `balanceTaxPayable`** — GL's own PDF renders a
|
||
negative value (credits/payments exceeded the gross liability) as **"BALANCE TAX REFUNDABLE"**, not
|
||
a negative "payable" figure (confirmed live on GL's own seeded loss-making test data,
|
||
`03_Progress_Tracker.md`). The on-screen table should match this exactly: `balanceTaxPayable >= 0` →
|
||
label `"BALANCE TAX PAYABLE"`, amount as-is; `balanceTaxPayable < 0` → label
|
||
`"BALANCE TAX REFUNDABLE"`, amount shown as its absolute value (not in parentheses — GL's own PDF
|
||
changes the *label*, not the sign display, for this one row specifically).
|
||
- Uses its own header block, not the shared `ReportHeader` — GL's own PDF gives this one report a
|
||
fuller identity block (company name, address, TIN, BRN) via `Company:Address`/`Tin`/`Brn` config
|
||
values (`05_LKAS_Report_PDF_Templates.md` §0/§8, confirmed implemented server-side). **Open question,
|
||
genuinely unresolved:** GL exposes no endpoint returning these four values as data — they're
|
||
`appsettings.json` entries, read only by the PDF renderer. The on-screen React version needs the
|
||
*same* values from *somewhere*, and there's no way to fetch them from GL today. Two options, neither
|
||
picked here: (a) GL adds a small `GET /company-info`-style endpoint; (b) the frontend duplicates
|
||
these four values in its own config/env, accepting the risk of drifting out of sync with GL's
|
||
`appsettings.json` whenever one side changes without the other. Flag this for a decision before
|
||
building this screen's header — don't silently pick one.
|
||
|
||
---
|
||
|
||
## 4. Cash / Bank Accounts
|
||
|
||
`app/dashboard/ledgers/bank-accounts/page.tsx` (list) + `/new/page.tsx` (create). No delete — GL's
|
||
own reference has no delete endpoint for either concept, consistent with every other master in this
|
||
system (FR-MD-08's deactivate-not-delete posture, though GL doesn't even expose deactivate here).
|
||
|
||
**Rework — Cash and Bank are separate GL tables/endpoints, not one table with a type field.** An
|
||
earlier design (drafted, never shipped on GL's side) proposed a single `bank_account.accountType`
|
||
discriminator; GL's team judged a cash account a *peer* concept to a bank account, not a *kind of*
|
||
bank account, and built two separate tables instead (`Accounting_System_Design_LKAS.md` §5.8). This
|
||
still satisfies the original requirement — **the create page only ever offers exactly two choices,
|
||
Cash or Bank, nothing else** — the two-choice constraint now lives in "which endpoint does the form
|
||
submit to," not "which value of a discriminator field."
|
||
|
||
### The three endpoints this page now uses
|
||
|
||
- **`POST /bank-accounts`** — unchanged in shape from the original 2026-07-18 design, except
|
||
`glAccountId` → `glAccountCode` (Rule 8.2). Fields: `accountName` (required), `bankName`/
|
||
`accountNumber` (both optional/nullable — a Bank account can genuinely be created with these blank
|
||
and filled in later, unlike Cash's stricter rule below), `glAccountCode` (required), `currencyCode`
|
||
(optional, defaults `"LKR"`).
|
||
- **`POST /cash-accounts`** *(new)* — `accountName` (required), `cashAccountTypeName` (required —
|
||
matched case-insensitively against GL's `cash_account_type` reference table; a name with no
|
||
existing match **creates a new type on the fly**, so a frontend "Other, please specify" free-text
|
||
option becomes a permanent selectable choice for every future cash account created after it),
|
||
`accountNumber` (**optional** — if omitted, GL auto-generates a sequential `CASH-000001`-style
|
||
reference; if supplied, used as entered), `glAccountCode` (required), `currencyCode` (optional,
|
||
defaults `"LKR"`).
|
||
- **`GET /cash-account-types`** *(new)* — flat, unfiltered list, seeded with Petty Cash / Till Cash /
|
||
Safe Cash / Cash in Transit, growing over time. Feeds the create form's Cash Account Type dropdown.
|
||
|
||
### Create form
|
||
A **two-choice toggle** at the top — **Cash** or **Bank**, backed by a `CashBankAccountType` TS enum
|
||
(`Cash`/`Bank`) — decides which of the two fieldsets below shows and which endpoint the form submits
|
||
to:
|
||
|
||
- **Bank selected:** Bank Name + Account Number fields (both optional on this side — GL doesn't
|
||
enforce them as required for a Bank row), submits to `POST /bank-accounts`.
|
||
- **Cash selected:** a Cash Account Type `<Select>` fed by `GET /cash-account-types`, with an "Other,
|
||
please specify" option that reveals a free-text input — its value is sent as `cashAccountTypeName`
|
||
regardless of whether it matched an existing type or not, letting GL's own on-the-fly-creation
|
||
handle the rest (no separate "create type first, then create account" round trip needed on the
|
||
frontend's part). An optional Account Number field, with placeholder text noting it'll be
|
||
auto-generated if left blank. Submits to `POST /cash-accounts`.
|
||
- `glAccountCode` (required regardless of type, references GL's own Chart of Accounts) stays a
|
||
`code — name` picker fed by `glAccountsApi.list()` (`GET /accounts`) either way — both kinds still
|
||
need a linked GL account, nothing about that requirement differs by type.
|
||
|
||
### List page — now a unified, filterable view
|
||
`GET /bank-accounts?accountType=Cash|Bank|Both` (default `Both`) replaces the old
|
||
"flat, unfiltered, unpaginated bank-accounts-only" list — it's GL's own server-side union of both
|
||
tables now, not a client-side-only filter over one table. Response per row:
|
||
`{ accountType, accountId, accountName, bankName, cashAccountTypeName, accountNumber, glAccountId,
|
||
currencyCode, createdAt }` — `bankName` is `null` on `Cash` rows, `cashAccountTypeName` is `null` on
|
||
`Bank` rows. The list table should show an `accountType` badge/column per row (a small "Cash"/"Bank"
|
||
pill next to the account name) so the two kinds are visually distinguishable at a glance. Client-side
|
||
filters (name/bank-or-type/account-number/currency/linked-GL-code) still layer on top of whichever
|
||
server-side `accountType` filter is active — GL's own filter narrows which table(s) contribute rows;
|
||
everything else about the existing client-side filtering approach is unchanged.
|
||
|
||
### Known gap — edit, still unresolved
|
||
**The user-facing requirement was list → create → edit (no delete). Edit still cannot be built.** GL's
|
||
own reference documents no `GET`/`PUT` by id for either `bank_account` or `cash_account` — nothing to
|
||
edit against, for either kind. The list page shows a **disabled** Edit button with a tooltip
|
||
explaining the gap, same posture as before this rework. **This needs a GL service change** (add the
|
||
missing endpoints, for both tables now, not just one) before a real edit screen can exist.
|
||
|
||
### Known gap — response shape, now resolved for Bank, still open for the unified list's exact field names
|
||
GL's reference previously never showed a full bank-account response body (only the create request
|
||
fields), so `types/general-ledger.ts`'s old `BankAccount` interface was an **inferred** shape. This is
|
||
now resolved for the unified list — `GET /bank-accounts`'s response fields are explicitly documented
|
||
(above) — but double-check the exact TS interface against a live call before trusting it blindly;
|
||
inferred-shape mistakes have been a real, repeated source of drift in this module (`§6`'s history).
|
||
|
||
### Fixed — selected GL account showed as a raw number, not its name (2026-07-20, unaffected by this rework)
|
||
The `<Select>` picker (bank account form, and Budget vs Actual's budget picker) displayed the
|
||
selected item's **numeric value** after picking it, even though the correct id/code was genuinely
|
||
being sent to the server. Cause: this app's `<Select>` wraps `@base-ui/react/select`, whose
|
||
`Select.Value` resolves its displayed text from each `Select.Item`'s **`label` prop**, a separate
|
||
field from `children`. Fixed by passing `label={...}` explicitly on each `<SelectItem>`. **Not fixed
|
||
at the shared `components/ui/select.tsx` level** — flagged for whoever next touches a similar picker
|
||
elsewhere in the app. The new Cash Account Type picker (this rework) should apply the same
|
||
`label={...}` pattern from the start, not reintroduce the bug.
|
||
|
||
---
|
||
|
||
## 4a. Accounts section (2026-07-31) — Cheque Management (new module) + Cash/Bank Accounts moved here
|
||
|
||
**A new top-level sidebar item, "Accounts"** (`app/dashboard/accounts/*`), sitting alongside
|
||
"Ledgers" rather than under it — Cash/Bank Accounts and cheques are operational account
|
||
bookkeeping, not a statutory report, so they don't belong under the Ledgers report hub. **Cash /
|
||
Bank Accounts moved here verbatim** from `ledgers/bank-accounts` (§4 above) — same code, same
|
||
behavior, just relocated; the Ledgers hub's card grid dropped its Cash/Bank Accounts card
|
||
accordingly.
|
||
|
||
**Cheque Management** is a new GL module (`04_API_Reference_And_Scenarios.md`, Module: Cheque
|
||
Management — added to GL 2026-07-30, beyond its original plan), purely operational tracking: no
|
||
endpoint in it ever creates or touches a journal entry itself. Two independent sub-areas, per GL's
|
||
own spec:
|
||
|
||
- **Cheque Books/Pages** (`app/dashboard/accounts/cheque-books/*`) — cheques issued from this
|
||
company's own cheque book supply. `POST /cheque-books` auto-generates every leaf (`ChequePage`,
|
||
all `Unused`) in one call. List page filters by status; create form restricts the bank-account
|
||
picker to `Bank`-type accounts only (GL's own module note: cheque books are bank-account-only,
|
||
never cash-account). Clicking a book navigates to `[chequeBookNo]/page.tsx`, which fetches
|
||
`GET /cheque-books/{chequeBookNo}?expand=pages` and lists every leaf — clicking a leaf opens
|
||
`components/accounts/ChequePageDialog.tsx`.
|
||
- **Received Cheques** (`app/dashboard/accounts/received-cheques/*`) — cheques received from
|
||
customers/suppliers/others, deliberately **not** linked to any `ChequeBook`. Same list-then-modal
|
||
shape via `components/accounts/ReceivedChequeDialog.tsx`.
|
||
|
||
**Modal chosen over a second-level page for leaf/row details** (left open by the request) — working
|
||
through several cheque pages in one book, or several received cheques in the list, without losing
|
||
the list's scroll position/filter state each time. Each dialog shows read-only details plus only the
|
||
actions valid from the item's current status (per GL's own status-transition tables), and each
|
||
action reveals only the extra fields *that specific transition* needs — e.g. `Clear` asks for
|
||
`clearedDate`, `Cancel` asks for `cancelReason`, `Bounce`/`Void`/`Deposit`(+bank picker) need
|
||
nothing else beyond an optional `performedBy`/note. This mirrors GL's own validation exactly rather
|
||
than showing one giant always-visible form.
|
||
|
||
**`branchId`/`companyId`/`payeeId`/`voucherId`/`referenceId` are plain numeric inputs, not
|
||
dropdowns** — GL's reference explicitly documents these as "loose references" (no Branch/Company/
|
||
Customer/Supplier table exists in that service for them to point at), so building a picker against
|
||
non-existent master data would be worse than a labeled numeric field.
|
||
|
||
**Types built from request-body fields GL does document, not guessed wholesale** — `ChequeBook`/
|
||
`ChequePage`/`ReceivedCheque` in `types/general-ledger.ts` are assembled from every field named in
|
||
the `POST`/`PUT` request bodies plus the status fields GL's prose confirms (`issueStatus`/
|
||
`printedAt`/`clearedDate`/`clearedByBank`/`cancelReason`, etc.) — not a shape invented from nothing.
|
||
Two things genuinely are guesses, flagged in the type comments: `ChequeBook`/`ChequePage`'s internal
|
||
numeric PK column names (GL never states them — `chequeBookNo`/`chequeNo`, both explicitly
|
||
documented as the identifying route values, are used for every key/URL instead, sidestepping the
|
||
guess), and `ReceivedCheque`'s JSON id field name (`receivedChequeId`, inferred from this project's
|
||
own `<entity>Id` convention — GL's reference only shows a numeric `{id}` in the URL, not the field
|
||
name).
|
||
|
||
**Backend:** migration `AddAccountsNavSeed` — new `NavItem` `accounts` (id 12) and two new
|
||
`SubNavItem`/`Permission` pairs (`accounts.cheque-books`, `accounts.received-cheques`); the existing
|
||
Cash/Bank Accounts `SubNavItem`/`Permission` (ids 15/26) were **updated in place**, not deleted and
|
||
recreated, so a role already granted that permission under its old `ledgers.bank-accounts` code
|
||
keeps it. Applied to the live database this session.
|
||
|
||
**Not done — live smoke test.** This entire module (both sub-areas) is unverified against real
|
||
Cheque Management data — no running GL instance with cheque data was available this session. Same
|
||
"verify before trusting the inferred pieces" posture as the rest of `§6`'s history.
|
||
|
||
---
|
||
|
||
## 5. RBAC nav seed (backend, small, additive)
|
||
|
||
The sidebar's role-based visibility (`docs/10 C.8`, `GET /auth/me`'s `navCodes`) requires every
|
||
`AppSidebar.tsx` entry's `code` to have a matching backend `NavItem`/`SubNavItem` row, or the item is
|
||
invisible to every role regardless of the frontend change. Migration `AddLedgersNavSeed`,
|
||
`Backend/ERPCore/Infra/Persistence/Migrations`:
|
||
|
||
- `NavItem` `ledgers` (id 11) + **8** `SubNavItem` rows (`ledgers.trial-balance` … `ledgers.tax-report`
|
||
(new, id 16) … `ledgers.bank-accounts`, ids 9–16) — `NavItemConfiguration.cs`/`SubNavItemConfiguration.cs`.
|
||
- **9** matching `Permission` rows (`NAV:ledgers`, `NAV:ledgers.*`, ids 19–27, `NAV:ledgers.tax-report`
|
||
new at id 27) — `PermissionConfiguration.cs`, following the existing one-`Permission`-per-nav-entry
|
||
convention exactly.
|
||
|
||
**Operational step still needed, not code:** a brand-new `NavItem`/`SubNavItem` carries no
|
||
`RolePermission` grants by default. An administrator must open **Settings → Roles**, edit the relevant
|
||
role(s), and check the new Ledgers permissions (including the new Tax Report one) before anyone with
|
||
that role sees the sidebar entries — normal onboarding, not a bug.
|
||
|
||
---
|
||
|
||
## 6. Progress
|
||
|
||
- [x] **Reports, original 6 screens (2026-07-20):** all 6 report screens + hub, all calling the live
|
||
GL proxy, all with a working PDF download. Verified `tsc --noEmit`/`eslint` clean.
|
||
- [x] **Cash/Bank Accounts — list + create, original single-table design (2026-07-20):**
|
||
client-side-filtered list, create form with a GL-account picker.
|
||
- [x] **RBAC nav seed, original 7-entry version (2026-07-20):** migration `AddLedgersNavSeed`, not yet
|
||
applied to a live database.
|
||
- [x] **Corrections (2026-07-20):** General Ledger dropped `accountId` entirely (later renamed to
|
||
`accountCode`, see below), `ReportType`/`ReportOutputFormat`/`GlAccountTypeId` became TS enums,
|
||
fixed the Select-shows-raw-number bug on two pickers.
|
||
- [x] **This revision, built (2026-07-30).** Every item in §3/§4's rework is now implemented against
|
||
the frontend codebase:
|
||
- `components/reports/DownloadCsvButton.tsx` — new, added to all seven report screens (alongside
|
||
the existing `DownloadPdfButton`).
|
||
- `components/reports/ReportSection.tsx`/`ReportSubtotal.tsx` — new shared components, extracted
|
||
because Profit & Loss and Cash Flow both needed the identical "bordered section of lines + a
|
||
bold subtotal row" shape; not built as one-off per-screen markup.
|
||
- Trial Balance screen — flat-list renderer, `depth`/`indentedCode` dropped.
|
||
- Profit & Loss screen — full rework to the nested-sections object, fixed section order (Sales →
|
||
Cost of Sales → **Gross Profit** → Other Income → the four expense groups → Unclassified if
|
||
present → **Net Profit for the Period**), empty sections hidden.
|
||
- Cash Flow screen — replaced the four `StatCard`s with the structured statement layout: Net
|
||
Earnings, Additions/Subtractions to Cash (sign-bucketed from the combined
|
||
`nonCashAdjustments[]`/`workingCapitalChanges[]`, working-capital lines labeled via their
|
||
`direction`), Net Cash From Operations, Investing/Financing sections (subtotal row suppressed
|
||
when a section has exactly one line, per spec), final combined total.
|
||
`openingCashBalance`/`closingCashBalance` are fetched but deliberately not rendered.
|
||
- **New Tax Report screen** (`app/dashboard/ledgers/tax-report/page.tsx`) — period picker,
|
||
collapsible Adjustments panel (5 optional numeric inputs, no client-side default), fixed-order
|
||
two-column result table with Add:/Less: row labels from a static lookup, bold reconciliation
|
||
checkpoints, and the payable/refundable label+sign logic on the final row. Uses its own header,
|
||
not the shared `ReportHeader` — the open question about `Company:Address`/`Tin`/`Brn` (§3, end)
|
||
is **still unresolved**; the header explicitly states the company-identity fields aren't shown
|
||
rather than fabricating placeholder values, and a hub card was added.
|
||
- `types/general-ledger.ts` — `ReportOutputFormat` gained `Csv`; `ReportType` gained `TaxSummary`;
|
||
`GeneralLedger`'s (unused-by-this-screen) param renamed `accountCode`; new `CashBankAccountType`
|
||
enum; `TrialBalanceRow` flattened; `ProfitAndLossResponse`/`CashFlowResponse`/`TaxSummaryResponse`
|
||
added (all three **inferred** in places GL's own reference doesn't spell out every field verbatim
|
||
— flagged in the type file's own doc comments, same posture as the original inferred
|
||
`BankAccount` shape); `BankAccount` replaced by the unified `CashAndBankAccountDto` plus separate
|
||
`CreateBankAccountRequest`/`CreateCashAccountRequest` types.
|
||
- Cash/Bank create form — Bank/Cash toggle (two buttons, not a dropdown), conditional fieldsets,
|
||
Cash Account Type `<Select>` (with `label={...}` set correctly from the start, avoiding the
|
||
raw-number-display bug from §4's earlier fix) fed by `GET /cash-account-types`, an "Other, please
|
||
specify" free-text option, `glAccountCode` picker now keyed by the account's code string instead
|
||
of its numeric id.
|
||
- Cash/Bank list page — server-side `accountType` filter (`Both`/`Bank`/`Cash`, a `<Select>` next
|
||
to the existing search box) driving `GET /bank-accounts?accountType=...`, client-side text search
|
||
still layered on top, a Cash/Bank badge column, Edit still disabled with its explanatory tooltip
|
||
(now type-aware: "...for cash accounts"/"...for bank accounts").
|
||
- RBAC nav seed — migration `AddTaxReportNavSeed` adds `SubNavItem` id 16
|
||
(`ledgers.tax-report`) and `Permission` id 27, and re-sequences `ledgers.bank-accounts`'s
|
||
`SortOrder` from 7→8 so Tax Report sits before it, matching the sidebar's array order.
|
||
- Verified: `tsc --noEmit` clean, `npx eslint` clean across every touched file, `npm run build`
|
||
succeeds with all 9 `/dashboard/ledgers/*` routes present (including `/tax-report`), `dotnet
|
||
build` clean (0 warnings/0 errors) after the migration.
|
||
- [x] **Live smoke test, partial (2026-07-31) — Cash Flow crashed, now fixed.** A GL instance became
|
||
reachable and a user hit the exact risk flagged above: `Cash Flow` threw
|
||
`TypeError: Cannot read properties of undefined (reading 'map')` on `report.nonCashAdjustments.map`.
|
||
Confirmed root cause — **GL's serializer omits an empty/zero list or section field from the JSON
|
||
entirely, rather than sending `[]` or `{ lines: [], total: 0 }`.** Fixed in `cash-flow/page.tsx`
|
||
(`?? []` defaults, an `activitySectionLines()` helper, optional chaining on
|
||
`investingActivities`/`financingActivities`/their `.total`) and proactively in
|
||
`profit-and-loss/page.tsx` (same defensive treatment for every section, since
|
||
`ProfitAndLossResponse` shares the identical nested-section shape and hadn't crashed yet only
|
||
because no period tested so far happened to have an empty section). `types/general-ledger.ts`'s
|
||
`CashFlowResponse` and `ProfitAndLossResponse` fields changed from required to optional to match.
|
||
**`TaxSummaryResponse` is still unverified against this same risk** — it wasn't touched this pass;
|
||
treat it as equally likely to have optional/omittable fields until proven otherwise. The unified
|
||
`CashAndBankAccountDto` also remains unverified live.
|
||
- [x] **Corrected against GL's own API reference (2026-07-31 (2)) — Cash Flow's shape was wrong, not
|
||
just fragile; Tax Report was missing five fields.** The user supplied GL's own
|
||
`04_API_Reference_And_Scenarios.md`, letting every report be checked against a confirmed contract
|
||
instead of inference for the first time. Trial Balance, Balance Sheet, General Ledger,
|
||
Profit & Loss, and Budget vs Actual all match it exactly. Two didn't:
|
||
- **`CashFlowResponse` — everything nests under `operatingActivities`.** There is no top-level
|
||
`netEarnings`/`nonCashAdjustments`/`workingCapitalChanges`/`netCashFromOperations` at all; the
|
||
real shape is `operatingActivities: { profitForPeriod, nonCashAdjustments[],
|
||
workingCapitalChanges[], netCashFromOperatingActivities }`, and `investingActivities`/
|
||
`financingActivities` each have their own differently-named total
|
||
(`netCashFromInvestingActivities`/`netCashFromFinancingActivities`), not a shared `total`. This
|
||
was the actual cause of the crash in the entry above — the previous fix's `?? []` guards were
|
||
aimed at fields that never existed at that nesting level, so the page would have kept silently
|
||
rendering an empty Operating Activities section forever, crash or not. Also: `workingCapitalChanges[]`
|
||
entries use `changeAmount`, not `amount`. Rewrote `cash-flow/page.tsx` and
|
||
`types/general-ledger.ts` (`CashFlowResponse` plus new `CashFlowOperatingActivities`/
|
||
`CashFlowInvestingActivities`/`CashFlowFinancingActivities`) to match exactly.
|
||
- **`TaxSummaryResponse` was missing `nonDeductibleExpenses`, `corporateIncomeTax`, `apitCredit`,
|
||
`whtCredit`, `quarterlyTaxPayments` entirely** — real GL-computed figures silently never shown,
|
||
not a wrong guess at a name. Added all five to the type and to `tax-report/page.tsx`'s `ROWS`
|
||
table in their correct position in the confirmed order.
|
||
- `CashFlowActivityLine`'s own field names (`{description, amount}` inside `investingActivities`/
|
||
`financingActivities` `lines[]`) and `CashAndBankAccountDto` remain unverified — GL's reference
|
||
confirms the containing shapes but not these inner fields verbatim.
|
||
- Verified: `tsc --noEmit`/`eslint` clean on every touched file.
|
||
- [x] **Balance Sheet regrouped into a proper LKAS Statement of Financial Position layout
|
||
(2026-07-31 (3), user-reported).** `BalanceSheetRow`'s shape is correct (matches the reference
|
||
above exactly) — this was a presentation bug, not a data bug. The flat one-table rendering made a
|
||
rollup total visually indistinguishable from the leaf amounts it already sums (e.g. "Cash and
|
||
Bank"'s balance already includes "Petty Cash"/"Main Operating Bank Account"/"Savings Bank Account"
|
||
beneath it), only `depth===0` did any bolding, and there was no section grouping at all — a "Type"
|
||
column per row instead of an ASSETS/LIABILITIES/EQUITY structure. Rewrote `balance-sheet/page.tsx`
|
||
to group by `accountType` into sections, each with a bold "Total {Section}" row (summed from that
|
||
section's depth-0 rows only, since a depth-0 row's balance already rolls up its descendants), any
|
||
row followed immediately by a deeper row is bolded as a rollup (not just the top level), and a
|
||
final "Total Liabilities and Equity" row for the standard balance check. Handles one GL quirk
|
||
explicitly: the synthetic "Current Year Earnings" balancing row always carries `depth: 1` even
|
||
though it's a peer Equity entry, not a child of whatever precedes it — a new `effectiveDepth()`
|
||
helper special-cases it to 0. Verified against the actual reported numbers: Total Assets
|
||
(5,880,466.50) = Total Liabilities (2,025,000.00) + Total Equity (3,855,466.50), exact match.
|
||
`tsc --noEmit`/`eslint` clean.
|
||
- [x] **Superseded by a real GL retrofit (2026-07-31 (4)) — `BalanceSheet` is now a pre-classified
|
||
nested shape, not a flat array to regroup client-side.** GL's own API reference documents a
|
||
2026-07-31 backend change: the flat `{depth, lineItem, accountType, balance}` recursive-rollup
|
||
array (what the entry above regrouped client-side) is replaced by
|
||
`{ asOfDate, nonCurrentAssets: {lines[], total}, currentAssets: {lines[], total},
|
||
unclassifiedAssets: {lines[], total}, totalAssets, equity: {lines[], total},
|
||
nonCurrentLiabilities: {lines[], total}, currentLiabilities: {lines[], total},
|
||
unclassifiedLiabilities: {lines[], total}, totalEquityAndLiabilities }`, driven by GL's new
|
||
`accounts.balance_sheet_classification` tag. GL now does the Non-Current/Current classification
|
||
itself — the previous entry's client-side `effectiveDepth`/`sectionTotal`/depth-based rollup
|
||
logic is entirely obsolete. Replaced `BalanceSheetRow` with `BalanceSheetLine`/
|
||
`BalanceSheetSection`/`BalanceSheetResponse` (every section optional, same defensive posture as
|
||
`CashFlowResponse`/`ProfitAndLossResponse` after the Cash Flow crash, since this shape isn't
|
||
live-verified against this frontend yet) and rewrote `balance-sheet/page.tsx` from scratch.
|
||
**Also restyled to match a user-supplied reference Statement of Financial Position image** — a
|
||
real classified SOFP, Non-Current/Current Assets each their own subtotaled block, then Equity and
|
||
Liabilities the same way, ending in a Total Assets vs Total Equity-and-Liabilities check — by
|
||
reusing the same `ReportSection`/`ReportSubtotal` components Profit & Loss and Cash Flow already
|
||
use, rather than one-off markup; line items show plain account names only, no codes, matching the
|
||
reference. Verified: `tsc --noEmit`/`eslint` clean; confirmed no remaining references to the
|
||
removed `BalanceSheetRow`.
|
||
- [x] **New "Accounts" nav section built (2026-07-31 (5)) — Cheque Management (Cheque Books/Pages +
|
||
Received Cheques) plus Cash/Bank Accounts moved here from Ledgers.** Full detail: §4a above. Six
|
||
new screens (`cheque-books` list/create/detail, `received-cheques` list/create) plus two new
|
||
modal components (`ChequePageDialog`/`ReceivedChequeDialog`), new types/API client functions for
|
||
the whole Cheque Management module, migration `AddAccountsNavSeed` (applied live). Verified:
|
||
`tsc --noEmit`/`eslint` clean, `npm run build` compiles successfully. **Not done — live smoke
|
||
test**, this module's inferred id-field-name gaps are flagged in §4a.
|
||
- [x] **`glAccountCode` removed from Cash/Bank Account creation (2026-07-31 (6)), matching a further
|
||
GL retrofit.** GL's own reference now documents that `POST /bank-accounts`/`POST /cash-accounts`
|
||
no longer accept a `glAccountCode` at all — the backing GL account (and, for Cash, its type-header
|
||
node) is always auto-created server-side, never caller-selected. Removed the field from
|
||
`CreateBankAccountRequest`/`CreateCashAccountRequest`, deleted the "GL account" `Select` and its
|
||
`glAccountsApi.list()` fetch from `bank-accounts/new/page.tsx` entirely, and dropped the matching
|
||
check from `validateBankAccountForm`. The create response is typed as the new
|
||
`CreateCashOrBankAccountResponse` (`glAccount` nested, confirmed from the doc; other fields
|
||
inferred) so the success toast can show the auto-generated GL account code back to the user. The
|
||
Cash/Bank **list** page is unaffected — GL's unified list endpoint still returns a flat
|
||
`glAccountId` per row, still resolved against `glAccountsApi.list()` there, same as before.
|
||
- [x] **Create-form layout widened to fill the page (2026-07-31 (6)).** The three GL create forms
|
||
under Accounts (`bank-accounts/new`, `cheque-books/new`, `received-cheques/new`) each wrapped
|
||
their fields in a `max-w-lg` card, leaving roughly half the page blank on any normal desktop
|
||
width. Dropped the `max-w-lg` cap (now full-width, matching the report pages' own `rounded-2xl
|
||
bg-white p-6 ...` card convention, which was never capped) and replaced the vertical
|
||
one-field-per-row `FieldGroup` stacking (plus occasional ad-hoc `grid grid-cols-2` pairs) with one
|
||
consistent `grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3` wrapper per form, so fields actually
|
||
spread across the available width instead of stretching a single narrow column. Modals
|
||
(`ChequePageDialog`/`ReceivedChequeDialog`) were deliberately left at their existing fixed width —
|
||
a dialog is supposed to stay narrow, this complaint was about full-page create forms only.
|
||
- [x] **Cheque Management status/type fields were displaying raw integers, not names (2026-08-05, user-reported + confirmed live) — fixed by mapping GL's response integers to this frontend's string enums at the API boundary.** User supplied GL's own `06_Enums_Reference.md`: GL has no global `JsonStringEnumConverter`, so while a JSON-**body** enum field is independently declared `string` server-side (and a query-string enum filter binds natively by name — both already correct here, unaffected), a real enum-typed **response** DTO property serializes as its raw underlying integer with no converter to turn it back into a name. `ChequeBook.status`, `ChequePage.issueStatus`, `ChequePage.payeeType`, `ReceivedCheque.receivedFromType`, and `ReceivedCheque.status` are exactly that — genuine DB-backed enum properties on GL's response DTOs — so every one of them was arriving as `1`/`2`/`3`/... instead of `"Active"`/`"Issued"`/`"Supplier"`, silently breaking every `===` comparison this frontend does against its own string enums (list badges, the create-form's own `<Select>` values, and both dialogs' status-based available-actions logic), not just the visible label. Added five `*_BY_CODE` lookup maps to `types/general-ledger.ts` (`CHEQUE_BOOK_STATUS_BY_CODE`/`CHEQUE_PAGE_ISSUE_STATUS_BY_CODE`/`PAYEE_TYPE_BY_CODE`/`RECEIVED_FROM_TYPE_BY_CODE`/`RECEIVED_CHEQUE_STATUS_BY_CODE`), keyed by the exact integer values `06_Enums_Reference.md` documents. Applied them in `lib/api/general-ledger.ts` via new `Raw*` types (describing what GL's JSON for these fields actually is: `number`/`number | null`) and `mapChequeBook`/`mapChequePage`/`mapReceivedCheque` helpers, wired into every `chequeBooksApi`/`chequePagesApi`/`receivedChequesApi` method that returns one of these shapes — the translation happens once, at the API boundary, so every existing page/dialog/badge map keeps comparing against the same string enum values as before and needed zero changes itself. Checked every other enum in `06_Enums_Reference.md`'s "Persisted enums" table (`JournalEntryStatus`/`PeriodStatus`/`TaxCalculationBasis`/`TaxAppliesTo`/`DepreciationMethod`/`FixedAssetStatus`/`AuditCategory`/`AuditAction`) against this frontend — none are consumed anywhere (no Journal Entry/Fiscal Period/Tax Code/Fixed Asset/Audit Log UI exists here), so Cheque Management is the complete fix, not a partial one. Verified: `tsc --noEmit`/`eslint` clean on both touched files.
|
||
- [ ] **Not done — internal ERPCore→GL wiring.** Unrelated to this revision, still deferred
|
||
(`docs/12` §6).
|
||
- [ ] **Deferred — bank/cash account edit.** Needs GL service changes first (§4); now needs them for
|
||
*both* tables, not just one.
|
||
- [ ] **Open — Tax Report's company-identity header fields.** Still genuinely unresolved (§3, end);
|
||
not picked in this pass either. The screen ships without them rather than guessing.
|
||
|
||
---
|
||
|
||
*End of 21-GENERAL-LEDGER-FRONTEND.md. Transport: `docs/12-GENERAL-LEDGER-INTEGRATION.md`. General frontend rules: `20-FRONTEND.md`. Record work: `Frontend/PROGRESS.md`.*
|