From eb7b2691df21b8d63ec8ac8951b8b6f477db9e08 Mon Sep 17 00:00:00 2001 From: Haritha Randunu Date: Wed, 5 Aug 2026 12:45:34 +0530 Subject: [PATCH] fix: map GL response integers to string enums for Cheque Management fields --- Frontend/PROGRESS.md | 2 + Frontend/erp-system/lib/api/general-ledger.ts | 90 +++++++++++++++---- Frontend/erp-system/types/general-ledger.ts | 53 +++++++++++ docs/21-GENERAL-LEDGER-FRONTEND.md | 1 + 4 files changed, 130 insertions(+), 16 deletions(-) diff --git a/Frontend/PROGRESS.md b/Frontend/PROGRESS.md index 0d154fb..627db79 100644 --- a/Frontend/PROGRESS.md +++ b/Frontend/PROGRESS.md @@ -129,6 +129,8 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit** > **Fields not explicitly spelled out verbatim in GL's reference** (its own numeric-id column names for `ChequeBook`/`ChequePage`, and `ReceivedCheque`'s JSON id field) are built from the request-body field names GL *does* document plus this project's consistent `Id` convention, flagged in `types/general-ledger.ts`'s comments — `chequeNo`/`chequeBookNo` (both explicitly documented as the identifying route values) are used for keys/URLs throughout instead, sidestepping the guess entirely wherever possible. **Not done:** live smoke test against a running GL instance — this entire module is unverified against real Cheque Management data. Verified: `tsc --noEmit`/`eslint` clean on every touched file; `npm run build` compiles successfully (Turbopack), its full-project TypeScript check still blocked only by the pre-existing, unrelated `hrm/employees/[id]` error. > > **2026-07-20 (3) — General Ledger report corrected again: `accountId` dropped entirely, not just made direct-entry.** The GL service's own contract changed (confirmed against its updated docs): `GeneralLedger`'s `accountId` is now optional, and the *omitted* case is the real General Ledger (every postable account together, each with its own running balance, sorted by `accountCode` then `entryDate`) — supplying `accountId` is a separate "Account Ledger" (single account + descendants) mode this page doesn't use. Superseding the same-day entry above: the numeric Account ID input is gone, `reportsApi.generalLedger()` dropped the `accountId` parameter, and the page now fetches on `periodStart`/`periodEnd` alone with sensible defaults — auto-fetching on page load like every other report screen (this also resolves the earlier-reported "no network call when landing on the page," which was the now-removed account-required gate). Result rows are grouped into per-account sections in the table (a header row wherever `accountCode` changes), matching the API's per-account running-balance reset. No frontend change was needed for the same-day `BalanceSheet` response addition (a synthetic `"Current Year Earnings"` equity row) — the existing generic row renderer already displays whatever rows come back. Verified: `tsc --noEmit` clean, `npx eslint app/dashboard/ledgers lib/api/general-ledger.ts` produces zero output, `npm run build` succeeds. +> +> **2026-08-05 — Cheque Management status/type fields were rendering as raw integers, not names (user-reported + confirmed with GL's own `06_Enums_Reference.md`).** That doc's key fact: GL has no global `JsonStringEnumConverter`. A JSON-**body** enum field (e.g. the Issue-cheque form's `payeeType`) is independently declared `string` server-side and parsed via `Enum.TryParse`, and a query-string enum filter binds natively by name — both already correct here, unaffected. But `ChequeBook.status`, `ChequePage.issueStatus`, `ChequePage.payeeType`, `ReceivedCheque.receivedFromType`, and `ReceivedCheque.status` are genuine enum-typed properties on GL's own **response** DTOs, backed by real `integer` DB columns — with no converter, GL's JSON serializes each one as its raw number (`1`/`2`/`3`/...), not its name. This wasn't just a cosmetic label bug: every list badge, the dialogs' status-based available-actions logic, and any `===` comparison against this frontend's own string enums (`ChequeBookStatus.Active`, etc.) would have silently mismatched against these numbers. Fixed at the API boundary, not scattered across every consumer: added five `*_BY_CODE` lookup maps to `types/general-ledger.ts` (one per affected field, keyed by the exact integers `06_Enums_Reference.md` documents), and applied them in `lib/api/general-ledger.ts` via new `Raw*` types (describing GL's actual `number`/`number | null` response shape for these fields) plus `mapChequeBook`/`mapChequePage`/`mapReceivedCheque` helpers wired into every `chequeBooksApi`/`chequePagesApi`/`receivedChequesApi` method that returns one — so every page/dialog/badge map keeps working against the same string values as before, unchanged. Cross-checked every other enum in that doc's "Persisted enums" table against this frontend (`JournalEntryStatus`/`PeriodStatus`/`TaxCalculationBasis`/`TaxAppliesTo`/`DepreciationMethod`/`FixedAssetStatus`/`AuditCategory`/`AuditAction`) — none are consumed anywhere in this app, confirming Cheque Management was the complete fix, not a partial one. Verified: `tsc --noEmit`/`eslint` clean on both touched files. ## 7. UX states - [~] Loading / empty / error states on every list — done for the GRN list/create/detail screens; other screens still unbuilt diff --git a/Frontend/erp-system/lib/api/general-ledger.ts b/Frontend/erp-system/lib/api/general-ledger.ts index c7948a9..03a944d 100644 --- a/Frontend/erp-system/lib/api/general-ledger.ts +++ b/Frontend/erp-system/lib/api/general-ledger.ts @@ -37,6 +37,11 @@ import { ReceivedFromType, CreateReceivedChequeRequest, UpdateReceivedChequeStatusRequest, + 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, } from "@/types/general-ledger" const GL_BASE = "/api/v1/gl" @@ -257,50 +262,100 @@ export const cashAccountTypesApi = { * routes, not a numeric id. No `list()`/`get()` for pages standalone — a book's pages are always * read via `get(chequeBookNo, true)`'s `pages[]`, which is the only place this frontend needs them. */ +// GL sends `ChequeBook.status`/`ChequePage.issueStatus`/`ChequePage.payeeType`/ +// `ReceivedCheque.receivedFromType`/`ReceivedCheque.status` as raw integers, not their string +// name (06_Enums_Reference.md — no global JsonStringEnumConverter on GL's side; see the long +// comment above the `*_BY_CODE` maps in types/general-ledger.ts for why). These `Raw*` shapes +// describe exactly what GL's JSON actually contains for those fields; the `map*` functions below +// translate them into this frontend's normal string-enum `ChequeBook`/`ChequePage`/`ReceivedCheque` +// types immediately after each call returns, so every other file in this app can keep comparing +// against `ChequeBookStatus.Active` etc. exactly as before. +type RawChequePage = Omit & { + issueStatus: number + payeeType: number | null +} +type RawChequeBook = Omit & { + status: number + pages: RawChequePage[] +} +type RawReceivedCheque = Omit & { + receivedFromType: number + status: number +} + +function mapChequePage(raw: RawChequePage): ChequePage { + return { + ...raw, + issueStatus: CHEQUE_PAGE_ISSUE_STATUS_BY_CODE[raw.issueStatus], + payeeType: raw.payeeType == null ? null : PAYEE_TYPE_BY_CODE[raw.payeeType], + } +} + +function mapChequeBook(raw: RawChequeBook): ChequeBook { + return { + ...raw, + status: CHEQUE_BOOK_STATUS_BY_CODE[raw.status], + pages: (raw.pages ?? []).map(mapChequePage), + } +} + +function mapReceivedCheque(raw: RawReceivedCheque): ReceivedCheque { + return { + ...raw, + receivedFromType: RECEIVED_FROM_TYPE_BY_CODE[raw.receivedFromType], + status: RECEIVED_CHEQUE_STATUS_BY_CODE[raw.status], + } +} + export const chequeBooksApi = { - list(params?: { + async list(params?: { bankAccountId?: number branchId?: number status?: ChequeBookStatus page?: number pageSize?: number }): Promise> { - return glRequest>("/cheque-books", { query: { ...params } }) + const res = await glRequest>("/cheque-books", { query: { ...params } }) + return { ...res, items: res.items.map(mapChequeBook) } }, /** `expandPages` maps to GL's `?expand=pages` — omit it for just the book's own fields. */ - get(chequeBookNo: string, expandPages = false): Promise { - return glRequest(`/cheque-books/${encodeURIComponent(chequeBookNo)}`, { + async get(chequeBookNo: string, expandPages = false): Promise { + const res = await glRequest(`/cheque-books/${encodeURIComponent(chequeBookNo)}`, { query: expandPages ? { expand: "pages" } : undefined, }) + return mapChequeBook(res) }, /** Auto-generates every leaf (`totalLeaves` `ChequePage` rows, all `Unused`) in the same call — the response's `pages[]` already has them. */ - create(request: CreateChequeBookRequest): Promise { - return glRequest("/cheque-books", { method: "POST", body: request }) + async create(request: CreateChequeBookRequest): Promise { + const res = await glRequest("/cheque-books", { method: "POST", body: request }) + return mapChequeBook(res) }, } export const chequePagesApi = { - issue(chequeNo: string, request: IssueChequePageRequest): Promise { - return glRequest(`/cheque-pages/${encodeURIComponent(chequeNo)}/issue`, { + async issue(chequeNo: string, request: IssueChequePageRequest): Promise { + const res = await glRequest(`/cheque-pages/${encodeURIComponent(chequeNo)}/issue`, { method: "PUT", body: request, }) + return mapChequePage(res) }, /** `Clear`/`Bounce`/`Cancel`/`Void` — only valid from certain `issueStatus` values, see `types/general-ledger.ts`'s `ChequePageStatusAction`. */ - updateStatus(chequeNo: string, request: UpdateChequePageStatusRequest): Promise { - return glRequest(`/cheque-pages/${encodeURIComponent(chequeNo)}/status`, { + async updateStatus(chequeNo: string, request: UpdateChequePageStatusRequest): Promise { + const res = await glRequest(`/cheque-pages/${encodeURIComponent(chequeNo)}/status`, { method: "PUT", body: request, }) + return mapChequePage(res) }, } /** Received Cheques — cheques received from customers/suppliers/others, deliberately unlinked to any `ChequeBook`. */ export const receivedChequesApi = { - list(params?: { + async list(params?: { companyId?: number branchId?: number status?: ReceivedChequeStatus @@ -308,15 +363,18 @@ export const receivedChequesApi = { page?: number pageSize?: number }): Promise> { - return glRequest>("/received-cheques", { query: { ...params } }) + const res = await glRequest>("/received-cheques", { query: { ...params } }) + return { ...res, items: res.items.map(mapReceivedCheque) } }, - create(request: CreateReceivedChequeRequest): Promise { - return glRequest("/received-cheques", { method: "POST", body: request }) + async create(request: CreateReceivedChequeRequest): Promise { + const res = await glRequest("/received-cheques", { method: "POST", body: request }) + return mapReceivedCheque(res) }, /** `Deposit`/`Clear`/`Return`/`Cancel` — only valid from certain statuses, see `types/general-ledger.ts`'s `ReceivedChequeStatusAction`. */ - updateStatus(id: number, request: UpdateReceivedChequeStatusRequest): Promise { - return glRequest(`/received-cheques/${id}/status`, { method: "PUT", body: request }) + async updateStatus(id: number, request: UpdateReceivedChequeStatusRequest): Promise { + const res = await glRequest(`/received-cheques/${id}/status`, { method: "PUT", body: request }) + return mapReceivedCheque(res) }, } diff --git a/Frontend/erp-system/types/general-ledger.ts b/Frontend/erp-system/types/general-ledger.ts index 66817fa..9e7e0af 100644 --- a/Frontend/erp-system/types/general-ledger.ts +++ b/Frontend/erp-system/types/general-ledger.ts @@ -410,6 +410,59 @@ export enum ReceivedChequeStatusAction { Cancel = "Cancel", } +/** + * Confirmed (`06_Enums_Reference.md`, user-supplied) — GL has **no global `JsonStringEnumConverter`** + * registered. That's documented there as a request-body binding gotcha (a JSON-body enum field must + * be sent as its string name, parsed server-side via `Enum.TryParse`), but the same missing converter + * also governs the other direction: every one of these five fields is a real enum-typed property on + * GL's own response DTO (backed by an `integer` DB column, per that doc's "Persisted enums" table), + * so with no converter registered, GL's JSON response serializes each one as its **raw integer** + * (`1`/`2`/`3`/...), not the name — confirmed live by the user ("most status has integer numbers"). + * Request bodies/query-string filters are unaffected and still take the string name as before (a + * JSON-body enum field is independently declared `string` server-side, and query-string enum + * binding parses names natively) — only inbound response values need translating. These maps do + * that translation, keyed by the exact integer values `06_Enums_Reference.md` documents; applied in + * `lib/api/general-ledger.ts` immediately after each GL call returns, so every consumer of + * `ChequeBook`/`ChequePage`/`ReceivedCheque` in this frontend keeps working with the same string + * enum values as before and never has to know GL sent a number. + */ +export const CHEQUE_BOOK_STATUS_BY_CODE: Record = { + 1: ChequeBookStatus.Active, + 2: ChequeBookStatus.Completed, + 3: ChequeBookStatus.Cancelled, +} + +export const CHEQUE_PAGE_ISSUE_STATUS_BY_CODE: Record = { + 1: ChequePageIssueStatus.Unused, + 2: ChequePageIssueStatus.Issued, + 3: ChequePageIssueStatus.Cleared, + 4: ChequePageIssueStatus.Bounced, + 5: ChequePageIssueStatus.Cancelled, + 6: ChequePageIssueStatus.Void, +} + +/** `ChequePage.payeeType` is nullable — only set once a page is issued (`06_Enums_Reference.md`). */ +export const PAYEE_TYPE_BY_CODE: Record = { + 1: PayeeType.Supplier, + 2: PayeeType.Customer, + 3: PayeeType.Employee, + 4: PayeeType.Other, +} + +export const RECEIVED_FROM_TYPE_BY_CODE: Record = { + 1: ReceivedFromType.Customer, + 2: ReceivedFromType.Supplier, + 3: ReceivedFromType.Other, +} + +export const RECEIVED_CHEQUE_STATUS_BY_CODE: Record = { + 1: ReceivedChequeStatus.Received, + 2: ReceivedChequeStatus.Deposited, + 3: ReceivedChequeStatus.Cleared, + 4: ReceivedChequeStatus.Returned, + 5: ReceivedChequeStatus.Cancelled, +} + /** * A single leaf of a Cheque Book. GL's own reference confirms every field named in the `issue` * request body plus `issueStatus`/`printedAt`/`clearedDate`/`clearedByBank`/`cancelReason` in diff --git a/docs/21-GENERAL-LEDGER-FRONTEND.md b/docs/21-GENERAL-LEDGER-FRONTEND.md index c89cdbb..cabc39e 100644 --- a/docs/21-GENERAL-LEDGER-FRONTEND.md +++ b/docs/21-GENERAL-LEDGER-FRONTEND.md @@ -520,6 +520,7 @@ that role sees the sidebar entries — normal onboarding, not a bug. 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 `