- Removed the ApiError class and related functions from the api-client module. - Updated vendor detail page to handle errors without the ApiError class. - Refactored various API modules to use in-memory mock data instead of commented-out fetch implementations. - Removed the auth-token module as it was no longer needed. - Updated error handling to use a more generic error structure. - Adjusted common types to include ApiResult interface for better type management.
31 KiB
Frontend — PROGRESS (Phase 1: Inventory & Supply Chain)
Legend: [ ] not started · [~] in progress · [x] done
Spec: docs/20-FRONTEND.md (flows + rules) · docs/11-BACKEND-PHASE1.md (API contract)
Convention: docs/01-DOC-GUIDE.md §6. Update this file in the same commit as the code. When ticking [x], append a short note + any deviation.
0. Foundation
NEXT_PUBLIC_API_BASE_URLwired (.env.local/.env.local.example, alongside the existingNEXT_PUBLIC_AUTH_API_BASE_URL) — currently unused now that the fetch client is gone (see 2026-07-15 note below)- Typed API client / fetch wrapper — removed 2026-07-15 (
lib/api-client.ts+lib/auth-token.tsdeleted per explicit user request: "delete all api connections"). No live code path in the frontend makes an HTTP call to any backend anymore — every screen is 100% mock data (lib/api/mock-data.ts). - Shared TS types mirroring API DTOs (
types/common.ts,types/master-data.ts,types/procurement.ts,types/grn.ts) — built only for the subset GRN needs (Item/Warehouse/Bin/Vendor/Uom/PurchaseOrder/Grn);types/common.tsnow also carriesApiResult<T>(moved here 2026-07-15 whenlib/api-client.tswas deleted, since it's a plain data envelope, not fetch-specific) - [~] Client validation helpers (
lib/validations/grn.ts) — deviation: useszod(already a project dependency, used bylib/validations.ts/login), not hand-rolled, to stay consistent with the codebase's existing pattern rather than introduce a second validation approach. UX-only; server remains authoritative (docs/20-FRONTEND.md §3) code → messagemap (lib/error-map.ts) —errorMessage/fieldErrorsduck-type any{ code, detail, errors }-shaped rejection (theApiErrorclass they used to check viainstanceofno longer exists); this also fixed a latent bug where the mock layer's plainError-plus-.coderejects never matched the oldinstanceof ApiErrorcheck, soCODE_MESSAGESsilently never applied to any mock error
Scope note: this foundation was built alongside the GRN feature and only covers the endpoints GRN consumes (items, warehouses/bins, vendors, uoms, purchase-orders, grns). Other master-data/procurement API methods still need their own
lib/api/*.tsfiles when those screens are built.
2026-07-15 — fetch infrastructure deleted outright (not just reverted to mock). Following an earlier same-week pass that wired every
lib/api/*.tsmodule to realfetchcalls (then reverted viagit revert --no-commitat the user's request — seeBackend-adjacent history if relevant later), the user asked to go further and delete the underlying connection mechanism entirely, not just leave it unused. Deletedlib/api-client.ts(apiRequest/apiRequestWithETag/buildQuery/ApiError) andlib/auth-token.ts(bearer-token storage) as files. Follow-on fixes this required: (1)ApiResult<T>— used byitems.ts/purchase-orders.ts/vendors.tsfor their mock ETag pattern — moved intotypes/common.ts; (2)lib/error-map.tsrewritten to duck-type instead ofinstanceof ApiError; (3) three detail pages (vendors/[id],products/[id],procurement/purchase-orders/[id]) had theirerr instanceof ApiError ? err.code : (err as {code?:string})?.codeconflict-detection simplified to the duck-typed form only. Also stripped the now-dangling commented-out "real implementation" blocks (// import { apiRequest... } from "@/lib/api-client"etc.) from all 15lib/api/*.tsfiles, since they referenced a now-deleted module.tsc --noEmit/eslintclean (same pre-existinglogin/page.tsxerror and establishedset-state-in-effectpattern only — confirmed unchanged by this pass).If real backend integration is attempted again, note two things found during the reverted pass: the RFQ backend contract had drifted from this file's assumed shapes (
Backend/ERPCore/Dtos/Procurement/RfqDtos.cs/RfqService.cs— no persisted invited-vendor list,requisitionIdrequired on create, different comparison DTO field names) — re-verify against the actual backend rather than trusting old assumptions; and a typed fetch client + ETag/error-normalization layer will need to be rebuilt from scratch sincelib/api-client.ts/lib/auth-token.tsno longer exist.
1. Auth
- [~] Login screen — UI built (
app/login); not yet wired toPOST /auth/login/ token storage - [~] Forgot password — add email screen — UI built (
app/login/forgot); not yet wired to API - [~] Forgot password — verify OTP screen — UI built (
app/login/forgot/otp); not yet wired to API - [~] Forgot password — change password screen — UI built (
app/login/forgot/reset); not yet wired to API
2. Master Data screens
- [~] Items (
app/dashboard/productslist + filters,/newcreate,/[id]full edit + ETag/If-Match with 412-conflict handling + activate/deactivate) — FR-MD-01/08. Reused the pre-existing "Products" sidebar entry/stub route rather than adding a new nav item. - [~] UOM + conversions (
app/dashboard/products/uomsflat list + create dialog; conversions edited inline on the Item detail page viaPUT /items/{itemId}/uom-conversions) — FR-MD-02/03 - [~] Categories (
app/dashboard/products/categoriesindented tree view + create dialog with parent picker) — FR-MD-04 - [~] Vendors (
app/dashboard/vendorslist + search/status-filter + create dialog,[id]full edit + ETag/If-Match with 412-conflict handling + activate/deactivate) — FR-MD-06. First screen this session to exercise the ETag/If-Match/412 pattern end-to-end (lib/api-client.ts'sApiResult<T>was built earlier but unused until now). - [~] Warehouses + Bins (
app/dashboard/warehouselist + create-warehouse dialog,[id]bin list + create-bin dialog) — FR-WH-01/FR-MD-07. Frontend-only (see note below); no ETag handling since there's no edit/delete yet, only create. - [~] Item reorder settings — edited inline on the Item detail page (per-warehouse point/qty rows) via
PUT /items/{itemId}/reorder— FR-MD-05
3. Procurement screens
- [~] Requisition (
app/dashboard/procurement/requisitionslist,/newcreate,/[id]detail + Submit) — FR-PROC-01 - [~] RFQ + quotations + comparison view (
.../rfqslist,/newcreate with vendor multi-invite,/[id]detail: lines, comparison matrix, record-quotation form, "Create PO from vendor") — FR-PROC-02 - [~] Purchase Order (
.../purchase-orderslist,/newcreate — auto-approved, prefillable from a Requisition or an RFQ+vendor quotation via query params —/[id]detail: edit-while-open with ETag/If-Match, Cancel with reason) — FR-PROC-03..07 - [~] Purchase Return (
.../purchase-returnslist,/newcreate against a Confirmed/Closed GRN's lines) — FR-PROC-08; also reachable from aRejectedGRN line via a "Create Return" button on the GRN detail page
4. Receiving screens
- [~] GRN list (
app/dashboard/receiving/grn/page.tsx) — loading/empty/error states, links to detail - [~] GRN create (
app/dashboard/receiving/grn/new/page.tsx) — "Against PO" (picks an Approved/PartiallyReceived PO, prefills open lines) and "Direct receipt" (vendor + manual lines) modes; per-line bin, qty, unit cost, hold status, and batch (batchNo+expiryDate) or serial-number-list capture driven by the item'strackingMode - [~] GRN confirm (
app/dashboard/receiving/grn/[id]/page.tsx) — renders returnedcreatedLayers/ledgerRefs/poStatusas a confirmation panel (20-FRONTEND §4); sends a stableIdempotency-Keyper detail-page session - [~] Inspection hold release / reject — Release/Reject buttons per on-hold line, shown once the GRN is
Confirmed - Sidebar: added "Receiving" nav entry (
components/Layouts/AppSidebar.tsx) →/dashboard/receiving/grn
[~]not[x], by design: these screens are built against the documented+planned contract indocs/11-BACKEND-PHASE1.md§4, but no GRN backend exists yet (this was frontend-only work; see the deviation below).UI-only / mock-data mode (temporary):
lib/api/{grns,purchase-orders,warehouses,items,vendors,uoms}.tscurrently return in-memory sample data (lib/api/mock-data.ts) instead of calling the real API, so the three screens are fully browsable/demoable (list → create against a mocked PO or as a direct receipt → detail → confirm → release/reject) without a running backend. Eachlib/api/*.tsfile keeps the realfetch-based implementation commented out directly above the mock block — switch back by deleting the mock block, uncommenting the real block, and deletinglib/api/mock-data.tsonce the GRN backend exists.npm run dev+tsc --noEmit+eslintare clean (aside from the pre-existing, unrelatedapp/login/page.tsxresolver-typing error and the tworeact-hooks/set-state-in-effectwarnings shared withhooks/use-mobile.ts).Deviation —
GET /grnsandGET /grns/{id}: the API doc only specifiesPOST /grns,POST /grns/{id}/confirm,POST /grns/{id}/lines/{id}/release(no list/detail read). A list screen and a confirm/release screen both need to reload a GRN, solib/api/grns.ts(grnsApi.list/grnsApi.get) andtypes/grn.tsassume these two GET endpoints will exist once the backend is built — flag this to whoever implementsBackend/PROGRESS.md§3 sodocs/11-BACKEND-PHASE1.mdgets the corresponding doc update.
5. Stock screens
- [~] Stock hub (
app/dashboard/stock/page.tsx) — card grid linking to all 7 areas below - [~] Stock enquiry (
.../stock/enquiry) — onHand/available/onHold/inTransit/reserved, search by SKU/name + warehouse filter, links to Valuation per row - [~] Ledger view (
.../stock/ledger) — filterable by item/warehouse/date range, paginated - [~] Valuation view (
.../stock/valuation) — item+warehouse picker (also reachable via?itemId=&warehouseId=from Enquiry), FIFO layer breakdown + totals - [~] Transfer (
.../stock/transferslist,/newcreate,/[id]dispatch → receive) — cost-preserving per line (FR-STK-06) - [~] Adjustment (
.../stock/adjustmentslist,/newcreate) — reason code mandatory, auto-posts on submit (no separate confirm step, matching FR-STK-07) - [~] Count (
.../stock/countslist,/newcreate,/[id]enter counts → post) — posting creates a linked variance adjustment - [~] Reorder alerts (
.../stock/reorder-alerts) — items ≤ reorder point, one-click "Create requisition" - [~] Wastage (
.../stock/wastagereport,/newrecord) — not a documented endpoint or SRS document type: damage/theft-loss/expiry write-offs are modeled as Stock Adjustments with a loss-type reason code (FR-STK-07);lib/api/wastage.tsis a frontend-only lens overstockAdjustmentsApi+ the shared mock ledger (filters to reason codesDMG/LOSS/EXPWO, flattens adjustment lines to per-item wastage records, sums matching outbound ledger entries for cost). No new backend concept — confirmed with the user before building (asked whether "Wastage" meant this vs. a distinct document type). - Sidebar: added "Stock" nav entry (
components/Layouts/AppSidebar.tsx) →/dashboard/stock; top-bar titles mapped per route (components/Layouts/Header.tsx)
[~]not[x], by design — same posture as §4 Receiving: built frontend-only (user request), against the documented+assumed Stock Core contract (docs/11-BACKEND-PHASE1.md§5), with no real backend. Unlike Receiving, though, this pass wired a genuine in-memory Stock Core (lib/api/mock-data.ts:mockStockLayers,mockStockLedger,receiveLayer/consumeFifo/postLedgerEntry/computeOnHand) that GRN confirm now posts through too (previously GRN confirm fabricated a throwaway response; it now creates real layers/ledger entries and accrues POqtyReceived/status) — so Enquiry/Ledger/Valuation reflect what actually happened in the session, and FIFO consumption (transfers, adjustment decreases, count variance decreases) genuinely throws aSTOCK_NEGATIVE_BLOCKED-style error when stock is insufficient.tsc --noEmitandeslintare clean across all new/changed files (only the pre-existinglogin/page.tsxerror and the establishedset-state-in-effectwarnings remain, same as §4).Deviations (same pattern as GRN, see §4):
GET/detail list endpoints for transfers/adjustments/counts (lib/api/stock-transfers.ts,stock-adjustments.ts,stock-counts.ts) are assumed extensions beyonddocs/11-BACKEND-PHASE1.md§5.4-5.6, which document only the transactional POSTs/PUT.stockApi.onHandList()(used by the Enquiry screen) is also not a documented endpoint — it's a frontend-only convenience that iterates known item/warehouse pairs and calls the (documented) on-hand computation per pair; a real backend would want a proper list endpoint instead. Flag all of these to whoever implementsBackend/PROGRESS.md§4/§5 (Stock Core + stock transactions).Simplifications (mock-data limitations, not spec decisions):
StockLayerhas no per-bin field (matches the real ER model, docs/10 Part C.5 — onlyStockLedgercarriesbin_id), so Count lines don't attempt bin-level snapshotting. Transfers don't expose batch selection in the create UI (FIFO picks layers regardless of batch). Adjustment increases always cost at "last known cost" for that item/warehouse (FR-STK-07); there's no landed-cost/manual-cost override. In-transit quantity is shown for visibility at the destination warehouse only and is not subtracted a second time from the source'savailable(dispatch already reduced the source layer'sqtyRemaining) — the docs'available = onHand − onHold − reserved − inTransit(out)formula is ambiguous on this point given dispatch semantics; this was a judgment call, noted here for whoever builds the real backend to confirm or correct.
6. Validation posture (20-FRONTEND §3)
- [~] Client format/required/range checks on all forms — done for GRN create (
lib/validations/grn.ts); not yet done for other forms - Surface server
ProblemDetailsincl. domain codes; map to fields/messages —lib/error-map.ts(errorMessage/fieldErrors), used by GRN create/detail 412conflict → prompt refetch before retry —apiRequestWithETagsurfaces the ETag but no screen edits a GRN yet (GRN has no PUT), so untested in practice- No client-side gating on stock/availability/status (server-authoritative) — GRN create always submits to the server and surfaces
OVER_RECEIPT_TOLERANCE/etc. viaerror-map.tsrather than pre-blocking
7. UX states
- [~] Loading / empty / error states on every list — done for the GRN list/create/detail screens; other screens still unbuilt
- Transactional actions show server-returned side effects as confirmation — GRN confirm renders
createdLayers/ledgerRefs/poStatusfrom the response
Done
2026-07-13 — GRN screens + frontend foundation (frontend-only; no backend changes)
- Foundation:
lib/api-client.ts,lib/error-map.ts,lib/auth-token.ts,types/{common,master-data,procurement,grn}.ts,lib/api/{grns,purchase-orders,warehouses,items,vendors,uoms}.ts— scoped to what the GRN flow needs, not the full API surface. - Added the shadcn
selectprimitive (npx shadcn add select) — wasn't incomponents/ui/yet; needed for PO/vendor/warehouse/bin/item/hold-status pickers. - Screens: GRN list, GRN create (PO-based + direct receipt, batch/serial capture by
trackingMode), GRN detail (confirm + release/reject). Sidebar nav entry added. - This was explicitly frontend-only (user interrupted an initial backend+frontend plan and asked for frontend only). No GRN backend exists —
Backend/PROGRESS.md§3/§4 are unchanged. The screens are built against the contract indocs/11-BACKEND-PHASE1.md§4 plus two assumed-but-undocumented endpoints (GET /grns,GET /grns/{id}, see §4 note above); none of it is runnable end-to-end yet. - Verified:
tsc --noEmitclean for all new/edited files (one pre-existing, unrelated error remains inapp/login/page.tsx);eslintclean aside from tworeact-hooks/set-state-in-effectwarnings matching an already-existing pattern inhooks/use-mobile.ts; all three routes confirmed rendering (200, correct content, no error boundary) via SSR against the dev server.
2026-07-13 — Stock Management screens (frontend-only; no backend changes)
types/stock.ts: full DTO set for on-hand, ledger, valuation, transfers, adjustments, counts, reorder alerts (docs/11 §5).lib/api/mock-data.tsgained a real in-memory Stock Core:mockStockLayers/mockStockLedger+receiveLayer/consumeFifo/postLedgerEntry/computeOnHand/lastKnownCosthelpers, plusmockItemReordersandmockReasonCodesseed data.lib/api/grns.ts'sconfirm()was refactored to post through these helpers instead of fabricating a response, and now also accrues POqtyReceived/recomputes PO status — so GRN and Stock screens are genuinely connected this session.- New API modules:
lib/api/stock.ts(on-hand/ledger/valuation/reorder-alerts),stock-transfers.ts,stock-adjustments.ts,stock-counts.ts,reason-codes.ts— same commented-real-block + active-mock-block pattern as the GRN modules. - Screens: hub, Enquiry, Ledger, Valuation, Transfers (list/new/detail with dispatch+receive), Adjustments (list/new, auto-post), Counts (list/new/detail with enter-counts+post), Reorder Alerts. New shared badge set
components/stock/status-badges.tsx(same fixed-size red/green/yellow convention ascomponents/receiving/status-badges.tsx). Sidebar + header-title mappings added. - Same posture as the GRN pass:
[~]not[x], frontend built ahead of a nonexistent Stock Core backend, deviations/simplifications recorded in the §5 note above.tsc --noEmitandeslintclean (only the same pre-existing/established issues as the GRN pass).
2026-07-13 — Wastage screens (frontend-only; no backend changes)
lib/api/wastage.ts: no new backend concept — confirmed with the user that "Wastage" should be a focused UI lens over the just-built Stock Adjustments (damage/theft-loss/expiry write-off reason codes), not a distinct document type. FiltersmockStockAdjustmentsto loss-type reason codes, flattens to per-itemWastageRecords, and computes cost per record from matching outboundmockStockLedgerentries.- Screens:
.../stock/wastage(report — totals cards, warehouse/reason filters, per-item table) and.../stock/wastage/new(single-line record form, reason dropdown restricted to wastage-type codes, posts via the existingstockAdjustmentsApi.create). Added a "Wastage" card to the Stock hub and header-title mappings. - Verified:
tsc --noEmitclean (same pre-existinglogin/page.tsxerror only);eslintclean aside from one more instance of the already-establishedset-state-in-effectpattern.
2026-07-13 — Warehouse Management screens (frontend-only; no backend changes)
- Scope, per user selection out of the four FR-WH sub-areas offered (Warehouses & Bins / Stock Locator / Batch & Serial / Putaway): Warehouses & Bins master data only (FR-WH-01, FR-MD-07). The other three (bin-level Stock Locator, Batch/Serial tracking, Putaway) were not built — flagged here so a future pass knows they're still open, not forgotten.
lib/api/warehouses.tsgainedcreate/get/createBin(previously list/listBins only, read-only) — duplicate-code validation mirrors the realSKU_DUPLICATE-style 400 pattern used elsewhere.mock-data.tsgainedallocateWarehouseId/allocateBinId.- Screens:
app/dashboard/warehouse(list + "New Warehouse"Dialogform) andapp/dashboard/warehouse/[id](bin list + "New Bin"Dialogform) — usedcomponents/ui/dialog.tsxinstead of a full page for these two-field creates, since a whole page felt heavy for that. Sidebar "Warehouses" entry + header-title mapping added. - Housekeeping: removed two stray duplicate route folders (
app/dashboard/receiving/grn/create new GRN/,.../view GRN/) that were byte-for-byte copies of the realnew/and[id]/GRN pages under garbled folder names — almost certainly an IDE artifact from an earlier malformed file-open path, not intentional work (confirmed untracked in git before removing). Also noted, but deliberately left alone:app/warehouse/*,components/warehouse/,lib/warehouse/are pre-existing empty scaffold folders (no files at all) from initial project setup — Warehouse Management was built underapp/dashboard/warehouse/*instead so it gets the dashboard chrome (sidebar/header) for free, consistent with every other screen this session. - Verified:
tsc --noEmitclean (same pre-existinglogin/page.tsxerror only);eslintclean aside from oneexhaustive-depswarning (not an error) on[id]/page.tsx'sloadBinshelper.
2026-07-13 — Vendor (Supplier) management screens (frontend-only; no backend changes)
- Confirmed with the user first: "supplier/shop" has no distinct "Shop" entity in the SRS/docs — scoped this to the documented Vendor master (FR-MD-06, docs/11 §2.4), "supplier" being the standard ERP synonym.
lib/api/vendors.tsextended from list-only toget/create/update/updateStatus. This is the first screen to exercise the ETag/If-Match/412 pattern:mock-data.tsgained a per-vendor concurrency-token map (getVendorVersion/bumpVendorVersion/initVendorVersion, standing in for the real backend'sxmin— the publicVendortype has no version field of its own since it travels as an HTTPETagheader, not a body field) soupdate()genuinely rejects a staleIf-MatchwithCONCURRENCY_CONFLICT, matchingdocs/11 §1.6and20-FRONTEND.md §3.2.- Screens:
app/dashboard/vendors(list, search + status filter, "New Vendor" dialog) andapp/dashboard/vendors/[id](full edit form using the realapiRequestWithETag-shapedApiResult<T>, a dedicated conflict banner with "Reload before retrying" per the 412 UX rule rather than a generic toast, and an Activate/Deactivate toggle viaPATCH status, FR-MD-08 — deactivate, not hard-delete). Sidebar "Vendors" entry + header-title mapping added. - Verified:
tsc --noEmitclean (same pre-existinglogin/page.tsxerror only);eslintclean aside from the same establishedset-state-in-effectpattern used throughout this session. - Follow-up (same day):
vendorsApi.list()didn't actually paginate (always returned page 1 / all matches, same latent gap the GRN list had before its own pagination pass) — fixed to slice bypage/pageSizeproperly, added the same Previous/Next pagination controls used on the GRN and Stock list screens, and seeded 8 more sample vendors so there's something real to page through.
2026-07-13 — Procurement screens: Requisition → RFQ → PO → Purchase Return (frontend-only; no backend changes)
types/procurement.tsgrew from a GRN-support subset (PO read types only) to the full §3 DTO set: Requisition/ReqLine, Rfq/RfqLine/Quotation/RfqComparison, PO create/update/cancel request types, PurchaseReturn/PurchaseReturnLine — mirrorsdocs/11-BACKEND-PHASE1.md§3 request/response JSON exactly (nodeliveryDatefield on PO lines, since the documentedPOST /purchase-ordersexample doesn't carry one despite FR-PROC-03's prose — contract-over-prose perdocs/20-FRONTEND.md§1).lib/api/mock-data.ts: addedmockRequisitions/mockRfqs/mockQuotations/mockPurchaseReturns+ allocators, a PO concurrency-token map (getPoVersion/bumpPoVersion/initPoVersion, same out-of-band ETag pattern as vendors), andconsumeLayerByGrnLine— a new consumption path deliberately separate fromconsumeFifo: a Purchase Return disposes of the exact layer its GRN line created (oftenOnHold/Rejected, whichconsumeFifo's hold filter would otherwise skip), not "the oldest open layer for this item/warehouse". Seeded Requisition #210 to match the existingmockPurchaseOrders[0].requisitionIdso the two screens cross-reference.- New API modules:
lib/api/requisitions.ts,lib/api/rfqs.ts(create/addQuotation/comparison — comparison is computed client-side from recorded quotations),lib/api/purchase-returns.ts.lib/api/purchase-orders.tsextended from list/get-only (its original GRN-support scope) to full create/update/cancel; addedgetWithETag/isPoEditablewithout touching the existing plainget()GRN's create-flow already depends on, so no existing call site broke. - Wiring, not just new screens:
stockApi.createReorderRequisition(Stock → Reorder Alerts, built in an earlier session) previously fabricated a response with no backing record; it now pushes a real row intomockRequisitions, so a reorder-triggered requisition genuinely shows up in the new Requisitions list — same "connect the mock modules together" posture as GRN confirm → Stock Core. - Screens: Requisition (list/new/detail+Submit), RFQ (list/new with checkbox vendor multi-invite reusing the Stock Count's
Checkboxpattern/new with quotation-recording form + vendor-by-line comparison table/"Create PO from vendor"), Purchase Order (list/new — accepts?requisitionId=or?rfqId=&vendorId=to prefill lines and pricing/detail with inline edit-while-open using the vendor[id]page's ETag+412-conflict-banner pattern, plus a Cancel-with-reason flow client-disabled when any line has receipts), Purchase Return (list/new — pick a Confirmed/Closed GRN, checkbox+qty its lines; also reachable via a new "Create Return" button next toRejectedlines on the GRN detail page, matching the SRS flow diagram's Reject→Purchase Return step). Newcomponents/procurement/status-badges.tsx(same fixed-width badge convention as Receiving/Stock) andlib/validations/procurement.ts(zod-free hand-rolled, matching the GRN validation file's style, not itszoddeviation). - Sidebar: added "Procurement" nav entry between Vendors and Receiving; header title mappings added for all new routes.
- Same
[~]posture as every other module this session: built against the documented+assumed Procurement contract (docs/11-BACKEND-PHASE1.md§3), no Procurement backend exists (Backend/PROGRESS.md§2 unchanged). - Verified:
tsc --noEmitclean (same pre-existinglogin/page.tsxerror only);eslintclean aside from the same establishedset-state-in-effectpattern used throughout this session (confirmed it also fires on the pre-existinggrn/page.tsx/vendors/page.tsx/hooks/use-mobile.ts— not a regression);npm run buildcompiles successfully via Turbopack (same pre-existing login type-check failure blocks the full build, unrelated). All 12 new routes plus the GRN-detail "Create Return" link (including three query-param-prefilled variants) confirmed rendering 200 with no error boundary via SSR against the dev server.
2026-07-13 — Master Data screens: Items, UOM, Categories, Reorder settings (frontend-only; no backend changes)
types/master-data.ts:ItemListItem(the GRN/PO/Requisition/RFQ item-picker subset built in earlier sessions) is now a derived view of a new fullItemtype — SKU/name/description/category/baseUom/defaultVendor/type/trackingMode/taxClass/status plusreorder: ItemReorderSetting[](matches the documentedGET /items/{itemId}example inline) andconversions: UomConversion[](deviation: the doc's example response only showsreorder, and conversions are otherwise reachable only viaPUT /items/{itemId}/uom-conversionswith no matching GET — embedding them on the full resource, like the assumedGET /grns/GET /grns/{id}reads elsewhere in this app, lets the Item detail screen show current conversions before editing). Also addedCategory/CategoryTreeNode,CreateUomRequest,CreateCategoryRequest, and Item create/update/reorder/conversion request types (docs/11 §2.1-2.3).lib/api/mock-data.ts:mockItemschanged storage shape fromItemListItem[]to fullItem[](onlymock-data.tsandlib/api/items.tstouched it directly, confirmed by grep, so no other call site broke) —lib/api/items.ts'slist()now maps down toItemListItem, same "full record → mapped summary" pattern asmockPurchaseOrders→PurchaseOrderSummary. Added a per-item concurrency-token map (getItemVersion/bumpItemVersion/initItemVersion, same out-of-band ETag pattern as vendors/POs),mockCategoriesseeded with a 2-root/1-child tree matching the category IDs the existing sample items already reference (12 "Fasteners" under 3 "Hardware"; 20 "Power Tools"), and a UOM id allocator.- New API modules:
lib/api/categories.ts(list/tree/create—tree()builds the nested structure client-side from the flat list, since the mock has no separate tree-storage concept).lib/api/items.tsgrew from list-only (its original GRN-picker scope) to fullget/create/update/updateStatus/updateReorder/updateUomConversions;lib/api/uoms.tsgainedcreate. - Screens: Items (
app/dashboard/products— reused the pre-existing "Products" sidebar entry and stub route rather than adding a new nav item, since it was already wired to an empty placeholder page; list has search + category/tracking-mode/status filters + pagination,/newcreate,/[id]detail combining three independently-saved sections in one page — basic info with ETag/If-Match + 412-conflict banner mirroring the Vendor[id]page, a Reorder Settings row-editor postingPUT /items/{itemId}/reorder, and a UOM Conversions row-editor postingPUT /items/{itemId}/uom-conversions— matching how the API groups these as sub-resources of Item rather than separate top-level screens). UOM (app/dashboard/products/uoms— flat list + create dialog, same shape as the Warehouses list). Categories (app/dashboard/products/categories— indented recursive tree view + create dialog with a parent picker).lib/validations/master-data.tsadded (hand-rolled, matching the GRN validation file's style, not itszoddeviation). Header title mappings added for all/dashboard/products/*routes. - Housekeeping: removed
app/dashboard/vendors/view vendors/— confirmed byte-for-byte identical tovendors/[id]/page.tsxand untracked in git, same IDE-artifact pattern (malformed file-open path) as the garbled GRN duplicate folders removed in the Warehouse Management pass; noted here rather than silently dropped. Leftapp/dashboard/receiving/grn/[id]/edit/alone — it's untracked too but is a distinct, non-duplicate GRN-edit screen, not an artifact. - Same
[~]posture as every other module this session: built against the documented+assumed Master Data contract (docs/11-BACKEND-PHASE1.md§2), no Master Data backend exists (Backend/PROGRESS.md§1 unchanged). - Verified:
tsc --noEmitclean after clearing a stale.nexttype cache that still referenced the just-deletedview vendorsroute (same pre-existinglogin/page.tsxerror only remains);eslintclean aside from the same establishedset-state-in-effectpattern;npm run buildcompiles successfully via Turbopack (same pre-existing login type-check failure, unrelated). All 5 new/changed routes confirmed rendering 200 with no error boundary against the dev server (one false-alarm 500 during testing traced to an unrelated stale process already bound to port 3000, not this code — retested clean on the actual dev server port).