feat: Implement new General Ledger frontend section with comprehensive report screens and cash/bank account management
- Added a new Ledgers sidebar section for statutory-format financial reports and cash/bank-account management. - Introduced dedicated GL client for API interactions, handling response envelopes and error management. - Developed report screens for Trial Balance, Balance Sheet, General Ledger, Profit & Loss, Cash Flow, Budget vs Actual, and a new Tax Report. - Implemented CSV download functionality alongside existing PDF downloads for all report screens. - Separated Cash and Bank accounts into distinct tables/endpoints, with updated create forms and unified list view. - Created a new Accounts section for Cheque Management, moving Cash/Bank Accounts from the Ledgers section. - Updated RBAC navigation to include new permissions and sub-navigation items for the added features. - Ensured compliance with GL's updated API contract, including renaming fields and adjusting response shapes. - Addressed various bugs and presentation issues, enhancing user experience across the new module.
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
# 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.
|
||||
- [ ] **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`.*
|
||||
Reference in New Issue
Block a user