From f30532b7d1fb56d81425da863e1da8eab92da264 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Fri, 17 Jul 2026 14:43:09 +0530 Subject: [PATCH] =?UTF-8?q?feat(import):=20APEX=20cutover=20web=20UI=20?= =?UTF-8?q?=E2=80=94=20stage/verify/commit=20over=20the=20existing=20impor?= =?UTF-8?q?ter=20(Phase=207,=20D18=20WS-B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /import/stage (CSV text), GET /import/status (report + first-50 problem rows with honest totals), POST /import/commit — all owner-only, audited; commit still refuses while ANY staged row carries problems (existing guard) - /import page (ADMIN nav, owner-only): file pickers → problem tables → verification stats (incl. invoice-value cross-check) → confirm-gated Commit; re-stage freely, series seeding message surfaced - 2 route tests (403s for staff; bad→refuse→clean→commit round-trip incl. series seeding past legacy numbers); suite 344 green Co-Authored-By: Claude Fable 5 --- apps/hq-web/src/api.ts | 19 ++++ apps/hq-web/src/main.tsx | 2 + apps/hq-web/src/nav.ts | 4 +- apps/hq-web/src/pages/ImportApex.tsx | 157 +++++++++++++++++++++++++++ apps/hq/src/api.ts | 42 +++++++ apps/hq/test/import-routes.test.ts | 78 +++++++++++++ 6 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 apps/hq-web/src/pages/ImportApex.tsx create mode 100644 apps/hq/test/import-routes.test.ts diff --git a/apps/hq-web/src/api.ts b/apps/hq-web/src/api.ts index f74a632..7dd9030 100644 --- a/apps/hq-web/src/api.ts +++ b/apps/hq-web/src/api.ts @@ -559,6 +559,25 @@ export async function downloadModuleClientsCsv(moduleId: string, moduleCode: str URL.revokeObjectURL(url) } +// ---------- APEX import (D18 WS-B; owner-only) ---------- +export interface ImportReport { + clients: { staged: number; problems: number } + invoices: { staged: number; problems: number; totalPaise: number } + samples: { firstClients: string[]; lastInvoices: string[] } +} +export interface ImportStatus { + report: ImportReport + problemClients: { row_no: number; code: string | null; name: string | null; problems: string }[] + problemInvoices: { row_no: number; client_code: string | null; doc_no: string | null; problems: string }[] +} +export const getImportStatus = (): Promise => apiFetch('/import/status') +export const stageApexCsv = (body: { clientsCsv?: string; invoicesCsv?: string }): Promise => + apiFetch<{ report: ImportReport }>('/import/stage', { method: 'POST', body: JSON.stringify(body) }).then((r) => r.report) +export const commitApexImport = (): Promise<{ clients: number; invoices: number; seeded: { fy: string; lastSeq: number } | null }> => + apiFetch<{ result: { clients: number; invoices: number; seeded: { fy: string; lastSeq: number } | null } }>( + '/import/commit', { method: 'POST', body: '{}' }, + ).then((r) => r.result) + /** The PDF route needs the bearer header, which an