Refactor API client and remove unused code
- 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.
This commit is contained in:
@@ -5,14 +5,18 @@ Spec: `docs/20-FRONTEND.md` (flows + rules) · `docs/11-BACKEND-PHASE1.md` (API
|
|||||||
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.
|
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
|
## 0. Foundation
|
||||||
- [x] `NEXT_PUBLIC_API_BASE_URL` wired (`.env.local` / `.env.local.example`, alongside the existing `NEXT_PUBLIC_AUTH_API_BASE_URL`) — points at `Backend/ERPCore`'s https profile (`7112`)
|
- [x] `NEXT_PUBLIC_API_BASE_URL` wired (`.env.local` / `.env.local.example`, alongside the existing `NEXT_PUBLIC_AUTH_API_BASE_URL`) — currently unused now that the fetch client is gone (see 2026-07-15 note below)
|
||||||
- [x] Typed API client / fetch wrapper (`lib/api-client.ts`: `apiRequest`/`apiRequestWithETag`/`buildQuery`) + bearer token handling (`lib/auth-token.ts`, degrades gracefully — no login endpoint wired yet, see §1)
|
- [ ] Typed API client / fetch wrapper — **removed 2026-07-15** (`lib/api-client.ts` + `lib/auth-token.ts` deleted 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`).
|
||||||
- [x] 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)
|
- [x] 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.ts` now also carries `ApiResult<T>` (moved here 2026-07-15 when `lib/api-client.ts` was deleted, since it's a plain data envelope, not fetch-specific)
|
||||||
- [~] Client validation helpers (`lib/validations/grn.ts`) — **deviation**: uses `zod` (already a project dependency, used by `lib/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)
|
- [~] Client validation helpers (`lib/validations/grn.ts`) — **deviation**: uses `zod` (already a project dependency, used by `lib/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)
|
||||||
- [x] `ProblemDetails` normalizer (`ApiError` in `lib/api-client.ts`) + `code → message` map (`lib/error-map.ts`)
|
- [x] `code → message` map (`lib/error-map.ts`) — `errorMessage`/`fieldErrors` duck-type any `{ code, detail, errors }`-shaped rejection (the `ApiError` class they used to check via `instanceof` no longer exists); this also fixed a latent bug where the mock layer's plain `Error`-plus-`.code` rejects never matched the old `instanceof ApiError` check, so `CODE_MESSAGES` silently 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/*.ts` files when those screens are built.
|
> **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/*.ts` files 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/*.ts` module to real `fetch` calls (then reverted via `git revert --no-commit` at the user's request — see `Backend`-adjacent history if relevant later), the user asked to go further and delete the underlying connection mechanism entirely, not just leave it unused. Deleted `lib/api-client.ts` (`apiRequest`/`apiRequestWithETag`/`buildQuery`/`ApiError`) and `lib/auth-token.ts` (bearer-token storage) as files. Follow-on fixes this required: (1) `ApiResult<T>` — used by `items.ts`/`purchase-orders.ts`/`vendors.ts` for their mock ETag pattern — moved into `types/common.ts`; (2) `lib/error-map.ts` rewritten to duck-type instead of `instanceof ApiError`; (3) three detail pages (`vendors/[id]`, `products/[id]`, `procurement/purchase-orders/[id]`) had their `err instanceof ApiError ? err.code : (err as {code?:string})?.code` conflict-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 15 `lib/api/*.ts` files, since they referenced a now-deleted module. `tsc --noEmit`/`eslint` clean (same pre-existing `login/page.tsx` error and established `set-state-in-effect` pattern 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, `requisitionId` required 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 since `lib/api-client.ts`/`lib/auth-token.ts` no longer exist.
|
||||||
|
|
||||||
## 1. Auth
|
## 1. Auth
|
||||||
- [~] Login screen — UI built (`app/login`); not yet wired to `POST /auth/login` / token storage
|
- [~] Login screen — UI built (`app/login`); not yet wired to `POST /auth/login` / token storage
|
||||||
- [~] Forgot password — add email screen — UI built (`app/login/forgot`); not yet wired to API
|
- [~] Forgot password — add email screen — UI built (`app/login/forgot`); not yet wired to API
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { warehousesApi } from "@/lib/api/warehouses"
|
|||||||
import { itemsApi } from "@/lib/api/items"
|
import { itemsApi } from "@/lib/api/items"
|
||||||
import { uomsApi } from "@/lib/api/uoms"
|
import { uomsApi } from "@/lib/api/uoms"
|
||||||
import { vendorsApi } from "@/lib/api/vendors"
|
import { vendorsApi } from "@/lib/api/vendors"
|
||||||
import { ApiError } from "@/lib/api-client"
|
|
||||||
import { errorMessage } from "@/lib/error-map"
|
import { errorMessage } from "@/lib/error-map"
|
||||||
import { validatePoLine } from "@/lib/validations/procurement"
|
import { validatePoLine } from "@/lib/validations/procurement"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -172,7 +171,7 @@ export default function PurchaseOrderDetailPage() {
|
|||||||
setLines(toDraftLines(result.data))
|
setLines(toDraftLines(result.data))
|
||||||
toast.success("Purchase order saved", `${result.data.docNo} updated (FR-PROC-05, edit-while-open).`)
|
toast.success("Purchase order saved", `${result.data.docNo} updated (FR-PROC-05, edit-while-open).`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code = err instanceof ApiError ? err.code : (err as { code?: string })?.code
|
const code = (err as { code?: string })?.code
|
||||||
if (code === "CONCURRENCY_CONFLICT") {
|
if (code === "CONCURRENCY_CONFLICT") {
|
||||||
setConflict(true)
|
setConflict(true)
|
||||||
setSaveError(errorMessage(err))
|
setSaveError(errorMessage(err))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { categoriesApi } from "@/lib/api/categories"
|
|||||||
import { uomsApi } from "@/lib/api/uoms"
|
import { uomsApi } from "@/lib/api/uoms"
|
||||||
import { vendorsApi } from "@/lib/api/vendors"
|
import { vendorsApi } from "@/lib/api/vendors"
|
||||||
import { warehousesApi } from "@/lib/api/warehouses"
|
import { warehousesApi } from "@/lib/api/warehouses"
|
||||||
import { ApiError } from "@/lib/api-client"
|
|
||||||
import { errorMessage, fieldErrors } from "@/lib/error-map"
|
import { errorMessage, fieldErrors } from "@/lib/error-map"
|
||||||
import { validateConversionLine, validateItemForm, validateReorderLine } from "@/lib/validations/master-data"
|
import { validateConversionLine, validateItemForm, validateReorderLine } from "@/lib/validations/master-data"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -147,7 +146,7 @@ export default function ItemDetailPage() {
|
|||||||
setEtag(result.etag)
|
setEtag(result.etag)
|
||||||
toast.success("Item saved", `${result.data.sku} — ${result.data.name}`)
|
toast.success("Item saved", `${result.data.sku} — ${result.data.name}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code = err instanceof ApiError ? err.code : (err as { code?: string })?.code
|
const code = (err as { code?: string })?.code
|
||||||
if (code === "CONCURRENCY_CONFLICT") {
|
if (code === "CONCURRENCY_CONFLICT") {
|
||||||
setConflict(true)
|
setConflict(true)
|
||||||
setSaveError(errorMessage(err))
|
setSaveError(errorMessage(err))
|
||||||
|
|||||||
+1
-2
@@ -6,7 +6,6 @@ import Link from "next/link"
|
|||||||
import { AlertTriangle, ArrowLeft, Save } from "lucide-react"
|
import { AlertTriangle, ArrowLeft, Save } from "lucide-react"
|
||||||
|
|
||||||
import { vendorsApi } from "@/lib/api/vendors"
|
import { vendorsApi } from "@/lib/api/vendors"
|
||||||
import { ApiError } from "@/lib/api-client"
|
|
||||||
import { errorMessage, fieldErrors } from "@/lib/error-map"
|
import { errorMessage, fieldErrors } from "@/lib/error-map"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Vendor } from "@/types/master-data"
|
import { Vendor } from "@/types/master-data"
|
||||||
@@ -78,7 +77,7 @@ export default function VendorDetailPage() {
|
|||||||
setEtag(result.etag)
|
setEtag(result.etag)
|
||||||
toast.success("Vendor saved", `${result.data.code} — ${result.data.name}`)
|
toast.success("Vendor saved", `${result.data.code} — ${result.data.name}`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code = err instanceof ApiError ? err.code : (err as { code?: string })?.code
|
const code = (err as { code?: string })?.code
|
||||||
if (code === "CONCURRENCY_CONFLICT") {
|
if (code === "CONCURRENCY_CONFLICT") {
|
||||||
setConflict(true)
|
setConflict(true)
|
||||||
setSaveError(errorMessage(err))
|
setSaveError(errorMessage(err))
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
// Single typed fetch client for the ERPCore API (docs/20-FRONTEND.md §1: "A single
|
|
||||||
// typed fetch client against NEXT_PUBLIC_API_BASE_URL; all calls go through it. No
|
|
||||||
// scattered fetch() in components."). Per-endpoint methods live in lib/api/*.ts.
|
|
||||||
import { ProblemDetails } from "@/types/common"
|
|
||||||
import { getAccessToken } from "@/lib/auth-token"
|
|
||||||
|
|
||||||
const API_BASE = `${process.env.NEXT_PUBLIC_API_BASE_URL ?? ""}/api/v1`
|
|
||||||
|
|
||||||
/** Normalized RFC 7807 error (docs/11-BACKEND-PHASE1.md §1.8) thrown on any non-2xx response. */
|
|
||||||
export class ApiError extends Error {
|
|
||||||
status: number
|
|
||||||
code?: string
|
|
||||||
detail?: string
|
|
||||||
errors?: Record<string, string[]>
|
|
||||||
traceId?: string
|
|
||||||
|
|
||||||
constructor(problem: ProblemDetails) {
|
|
||||||
super(problem.title || "Request failed")
|
|
||||||
this.status = problem.status
|
|
||||||
this.code = problem.code
|
|
||||||
this.detail = problem.detail
|
|
||||||
this.errors = problem.errors
|
|
||||||
this.traceId = problem.traceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestOptions extends Omit<RequestInit, "body"> {
|
|
||||||
body?: unknown
|
|
||||||
/** Sent as If-Match for concurrency-guarded PUT/PATCH (docs/11 §1.6). */
|
|
||||||
ifMatch?: string
|
|
||||||
/** Sent as Idempotency-Key for transactional POSTs (e.g. GRN confirm, docs/11 §1.6). */
|
|
||||||
idempotencyKey?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiResult<T> {
|
|
||||||
data: T
|
|
||||||
/** Response ETag header, when the resource is mutable (docs/11 §1.6). */
|
|
||||||
etag: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function rawRequest(path: string, options: RequestOptions = {}): Promise<Response> {
|
|
||||||
const { body, ifMatch, idempotencyKey, headers, ...rest } = options
|
|
||||||
const token = getAccessToken()
|
|
||||||
|
|
||||||
const finalHeaders: Record<string, string> = {
|
|
||||||
Accept: "application/json",
|
|
||||||
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
||||||
...(ifMatch ? { "If-Match": ifMatch } : {}),
|
|
||||||
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
|
|
||||||
...((headers as Record<string, string> | undefined) ?? {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}${path}`, {
|
|
||||||
...rest,
|
|
||||||
headers: finalHeaders,
|
|
||||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
let problem: ProblemDetails
|
|
||||||
try {
|
|
||||||
problem = (await response.json()) as ProblemDetails
|
|
||||||
} catch {
|
|
||||||
problem = { title: response.statusText || "Request failed", status: response.status }
|
|
||||||
}
|
|
||||||
if (!problem.status) problem.status = response.status
|
|
||||||
throw new ApiError(problem)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fire a request and decode the JSON body only (no ETag needed). */
|
|
||||||
export async function apiRequest<T>(path: string, options?: RequestOptions): Promise<T> {
|
|
||||||
const response = await rawRequest(path, options)
|
|
||||||
if (response.status === 204) return undefined as T
|
|
||||||
return (await response.json()) as T
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fire a request and also surface the ETag header, for resources that support If-Match. */
|
|
||||||
export async function apiRequestWithETag<T>(path: string, options?: RequestOptions): Promise<ApiResult<T>> {
|
|
||||||
const response = await rawRequest(path, options)
|
|
||||||
const etag = response.headers.get("ETag")
|
|
||||||
const data = response.status === 204 ? (undefined as T) : ((await response.json()) as T)
|
|
||||||
return { data, etag }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Build a `?a=1&b=2` query string, dropping null/undefined/empty values. */
|
|
||||||
export function buildQuery(params: object): string {
|
|
||||||
const search = new URLSearchParams()
|
|
||||||
for (const [key, value] of Object.entries(params) as [string, string | number | boolean | null | undefined][]) {
|
|
||||||
if (value === null || value === undefined || value === "") continue
|
|
||||||
search.set(key, String(value))
|
|
||||||
}
|
|
||||||
const qs = search.toString()
|
|
||||||
return qs ? `?${qs}` : ""
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,9 @@
|
|||||||
// One typed client method per Category endpoint (docs/11-BACKEND-PHASE1.md §2.3, FR-MD-04).
|
// One typed client method per Category endpoint (docs/11-BACKEND-PHASE1.md §2.3, FR-MD-04).
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so these screens
|
|
||||||
// can be reviewed without a running backend. Restore the commented block and
|
|
||||||
// delete the mock block once Backend/PROGRESS.md §1 (Master Data) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { Category, CategoryTreeNode, CreateCategoryRequest } from "@/types/master-data"
|
import { Category, CategoryTreeNode, CreateCategoryRequest } from "@/types/master-data"
|
||||||
import { allocateCategoryId, mockCategories, mockDelay } from "@/lib/api/mock-data"
|
import { allocateCategoryId, mockCategories, mockDelay } from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export const categoriesApi = {
|
|
||||||
// list() {
|
|
||||||
// return apiRequest<PagedResponse<Category>>("/categories")
|
|
||||||
// },
|
|
||||||
// tree() {
|
|
||||||
// return apiRequest<CategoryTreeNode[]>("/categories?tree=true")
|
|
||||||
// },
|
|
||||||
// create(request: CreateCategoryRequest) {
|
|
||||||
// return apiRequest<Category>("/categories", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
function buildTree(categories: Category[]): CategoryTreeNode[] {
|
function buildTree(categories: Category[]): CategoryTreeNode[] {
|
||||||
const nodes = new Map<number, CategoryTreeNode>(categories.map((c) => [c.categoryId, { ...c, children: [] }]))
|
const nodes = new Map<number, CategoryTreeNode>(categories.map((c) => [c.categoryId, { ...c, children: [] }]))
|
||||||
const roots: CategoryTreeNode[] = []
|
const roots: CategoryTreeNode[] = []
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
// One typed client method per GRN endpoint (docs/11-BACKEND-PHASE1.md §4).
|
// One typed client method per GRN endpoint (docs/11-BACKEND-PHASE1.md §4).
|
||||||
//
|
// In-memory mock store (lib/api/mock-data.ts) — no backend API calls. Note
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
// GET /grns and GET /grns/{id} are not in docs/11-BACKEND-PHASE1.md §4 — see
|
||||||
// replaced with an in-memory mock store (lib/api/mock-data.ts) so the GRN
|
// the note in Frontend/PROGRESS.md §4.
|
||||||
// screens (list/create/confirm/release) can be reviewed end-to-end without a
|
|
||||||
// running backend. Restore the commented block and delete the mock block once
|
|
||||||
// Backend/PROGRESS.md §3/§4 (GRN + Stock Core) exist. Note GET /grns and
|
|
||||||
// GET /grns/{id} are not yet in docs/11-BACKEND-PHASE1.md §4 — see the note in
|
|
||||||
// Frontend/PROGRESS.md §4.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import {
|
import {
|
||||||
ConfirmGrnResponse,
|
ConfirmGrnResponse,
|
||||||
@@ -20,60 +15,6 @@ import {
|
|||||||
} from "@/types/grn"
|
} from "@/types/grn"
|
||||||
import { allocateGrnId, allocateGrnLineId, mockDelay, mockGrns, mockPurchaseOrders, receiveLayer } from "@/lib/api/mock-data"
|
import { allocateGrnId, allocateGrnLineId, mockDelay, mockGrns, mockPurchaseOrders, receiveLayer } from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, apiRequestWithETag, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListGrnsParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// q?: string
|
|
||||||
// status?: GrnStatus
|
|
||||||
// poId?: number
|
|
||||||
// warehouseId?: number
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const grnsApi = {
|
|
||||||
// list(params: ListGrnsParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<GrnSummary>>(`/grns${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// get(grnId: number) {
|
|
||||||
// return apiRequest<Grn>(`/grns/${grnId}`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// async create(request: CreateGrnRequest) {
|
|
||||||
// const { data } = await apiRequestWithETag<Grn>("/grns", { method: "POST", body: request })
|
|
||||||
// return data
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// // Draft-only — a GRN with createdLayers/ledger postings (Confirmed/Closed) is
|
|
||||||
// // immutable per docs/11 §4.
|
|
||||||
// async update(grnId: number, request: CreateGrnRequest, ifMatch: string) {
|
|
||||||
// const { data } = await apiRequestWithETag<Grn>(`/grns/${grnId}`, { method: "PUT", body: request, ifMatch })
|
|
||||||
// return data
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// remove(grnId: number) {
|
|
||||||
// return apiRequest<void>(`/grns/${grnId}`, { method: "DELETE" })
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// confirm(grnId: number, idempotencyKey?: string) {
|
|
||||||
// return apiRequest<ConfirmGrnResponse>(`/grns/${grnId}/confirm`, {
|
|
||||||
// method: "POST",
|
|
||||||
// body: {},
|
|
||||||
// idempotencyKey,
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// releaseLine(grnId: number, grnLineId: number, action: ReleaseAction) {
|
|
||||||
// return apiRequest<ReleaseGrnLineResponse>(`/grns/${grnId}/lines/${grnLineId}/release`, {
|
|
||||||
// method: "POST",
|
|
||||||
// body: { action },
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListGrnsParams {
|
export interface ListGrnsParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
// One typed client method per Item endpoint (docs/11-BACKEND-PHASE1.md §2.1, FR-MD-01/05/08).
|
// One typed client method per Item endpoint (docs/11-BACKEND-PHASE1.md §2.1, FR-MD-01/05/08).
|
||||||
// `list` also backs the GRN/PO/Requisition/RFQ item pickers built in earlier sessions.
|
// `list` also backs the GRN/PO/Requisition/RFQ item pickers built in earlier sessions.
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
import { ApiResult, EntityStatus, PagedResponse } from "@/types/common"
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so these screens
|
|
||||||
// can be reviewed without a running backend. Restore the commented block and
|
|
||||||
// delete the mock block once Backend/PROGRESS.md §1 (Master Data) exists.
|
|
||||||
import { ApiResult } from "@/lib/api-client"
|
|
||||||
import { EntityStatus, PagedResponse } from "@/types/common"
|
|
||||||
import {
|
import {
|
||||||
CreateItemRequest,
|
CreateItemRequest,
|
||||||
Item,
|
Item,
|
||||||
@@ -26,43 +21,6 @@ import {
|
|||||||
mockItems,
|
mockItems,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, apiRequestWithETag, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListItemsParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// q?: string
|
|
||||||
// status?: EntityStatus
|
|
||||||
// categoryId?: number
|
|
||||||
// trackingMode?: TrackingMode
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const itemsApi = {
|
|
||||||
// list(params: ListItemsParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<ItemListItem>>(`/items${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(itemId: number) {
|
|
||||||
// return apiRequestWithETag<Item>(`/items/${itemId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateItemRequest) {
|
|
||||||
// return apiRequestWithETag<Item>("/items", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// update(itemId: number, request: UpdateItemRequest, ifMatch: string) {
|
|
||||||
// return apiRequestWithETag<Item>(`/items/${itemId}`, { method: "PUT", body: request, ifMatch })
|
|
||||||
// },
|
|
||||||
// updateStatus(itemId: number, status: EntityStatus) {
|
|
||||||
// return apiRequest<void>(`/items/${itemId}/status`, { method: "PATCH", body: { status } })
|
|
||||||
// },
|
|
||||||
// updateReorder(itemId: number, request: UpdateItemReorderRequest) {
|
|
||||||
// return apiRequest<{ settings: UpdateItemReorderRequest["settings"] }>(`/items/${itemId}/reorder`, { method: "PUT", body: request })
|
|
||||||
// },
|
|
||||||
// updateUomConversions(itemId: number, request: UpdateUomConversionsRequest) {
|
|
||||||
// return apiRequest<UpdateUomConversionsResponse>(`/items/${itemId}/uom-conversions`, { method: "PUT", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListItemsParams {
|
export interface ListItemsParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
// Temporary in-memory sample data so the GRN screens can be reviewed as pure UI
|
// In-memory sample data backing every lib/api/*.ts module — the app has no
|
||||||
// without a running backend. Shapes mirror docs/11-BACKEND-PHASE1.md exactly.
|
// fetch-based backend connection (lib/api-client.ts and lib/auth-token.ts were
|
||||||
//
|
// removed). Shapes mirror docs/11-BACKEND-PHASE1.md.
|
||||||
// This file (and the "MOCK" blocks in the sibling lib/api/*.ts files) is meant
|
|
||||||
// to be deleted once the real GRN backend exists — the original fetch-based
|
|
||||||
// implementations are left commented out in each file for that switch-back.
|
|
||||||
import { Bin, Category, Item, Uom, Vendor, Warehouse } from "@/types/master-data"
|
import { Bin, Category, Item, Uom, Vendor, Warehouse } from "@/types/master-data"
|
||||||
import { PurchaseOrder, Quotation, Requisition, Rfq, PurchaseReturn } from "@/types/procurement"
|
import { PurchaseOrder, Quotation, Requisition, Rfq, PurchaseReturn } from "@/types/procurement"
|
||||||
import { Grn } from "@/types/grn"
|
import { Grn } from "@/types/grn"
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
// One typed client method per Purchase Order endpoint (docs/11-BACKEND-PHASE1.md §3.3,
|
// One typed client method per Purchase Order endpoint (docs/11-BACKEND-PHASE1.md §3.3,
|
||||||
// FR-PROC-03..07). `get`/`list` also back the GRN "against a PO" picker.
|
// FR-PROC-03..07). `get`/`list` also back the GRN "against a PO" picker.
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
import { ApiResult, PagedResponse } from "@/types/common"
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so the GRN and
|
|
||||||
// Procurement screens can be reviewed without a running backend. Restore the
|
|
||||||
// commented block and delete the mock block once Backend/PROGRESS.md §2/§3/§4
|
|
||||||
// (Procurement + GRN + Stock Core) exist.
|
|
||||||
import { ApiResult } from "@/lib/api-client"
|
|
||||||
import { PagedResponse } from "@/types/common"
|
|
||||||
import {
|
import {
|
||||||
CancelPurchaseOrderRequest,
|
CancelPurchaseOrderRequest,
|
||||||
CreatePurchaseOrderRequest,
|
CreatePurchaseOrderRequest,
|
||||||
@@ -25,39 +19,6 @@ import {
|
|||||||
mockPurchaseOrders,
|
mockPurchaseOrders,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, apiRequestWithETag, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListPurchaseOrdersParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// q?: string
|
|
||||||
// status?: PurchaseOrderStatus
|
|
||||||
// vendorId?: number
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const purchaseOrdersApi = {
|
|
||||||
// list(params: ListPurchaseOrdersParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<PurchaseOrderSummary>>(`/purchase-orders${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(poId: number) {
|
|
||||||
// return apiRequest<PurchaseOrder>(`/purchase-orders/${poId}`)
|
|
||||||
// },
|
|
||||||
// getWithETag(poId: number) {
|
|
||||||
// return apiRequestWithETag<PurchaseOrder>(`/purchase-orders/${poId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreatePurchaseOrderRequest) {
|
|
||||||
// return apiRequestWithETag<PurchaseOrder>("/purchase-orders", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// update(poId: number, request: UpdatePurchaseOrderRequest, ifMatch: string) {
|
|
||||||
// return apiRequestWithETag<PurchaseOrder>(`/purchase-orders/${poId}`, { method: "PUT", body: request, ifMatch })
|
|
||||||
// },
|
|
||||||
// cancel(poId: number, request: CancelPurchaseOrderRequest) {
|
|
||||||
// return apiRequest<PurchaseOrder>(`/purchase-orders/${poId}/cancel`, { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListPurchaseOrdersParams {
|
export interface ListPurchaseOrdersParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
// One typed client method per Purchase Return endpoint (docs/11-BACKEND-PHASE1.md §3.4, FR-PROC-08).
|
// One typed client method per Purchase Return endpoint (docs/11-BACKEND-PHASE1.md §3.4, FR-PROC-08).
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so the Procurement
|
|
||||||
// screens can be reviewed without a running backend. Restore the commented block
|
|
||||||
// and delete the mock block once Backend/PROGRESS.md §2 (Procurement) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { CreatePurchaseReturnRequest, PurchaseReturn, PurchaseReturnSummary } from "@/types/procurement"
|
import { CreatePurchaseReturnRequest, PurchaseReturn, PurchaseReturnSummary } from "@/types/procurement"
|
||||||
import {
|
import {
|
||||||
@@ -15,22 +11,6 @@ import {
|
|||||||
postLedgerEntry,
|
postLedgerEntry,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export const purchaseReturnsApi = {
|
|
||||||
// list() {
|
|
||||||
// return apiRequest<PagedResponse<PurchaseReturnSummary>>("/purchase-returns")
|
|
||||||
// },
|
|
||||||
// get(returnId: number) {
|
|
||||||
// return apiRequest<PurchaseReturn>(`/purchase-returns/${returnId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreatePurchaseReturnRequest) {
|
|
||||||
// return apiRequest<PurchaseReturn>("/purchase-returns", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
function toSummary(r: PurchaseReturn): PurchaseReturnSummary {
|
function toSummary(r: PurchaseReturn): PurchaseReturnSummary {
|
||||||
return {
|
return {
|
||||||
returnId: r.returnId,
|
returnId: r.returnId,
|
||||||
|
|||||||
@@ -1,21 +1,9 @@
|
|||||||
// One typed client method for reference data (docs/11-BACKEND-PHASE1.md §6).
|
// One typed client method for reference data (docs/11-BACKEND-PHASE1.md §6).
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts).
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { ReasonCode, ReasonCodeContext } from "@/types/stock"
|
import { ReasonCode, ReasonCodeContext } from "@/types/stock"
|
||||||
import { mockDelay, mockReasonCodes } from "@/lib/api/mock-data"
|
import { mockDelay, mockReasonCodes } from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export const reasonCodesApi = {
|
|
||||||
// list(context?: ReasonCodeContext) {
|
|
||||||
// return apiRequest<PagedResponse<ReasonCode>>(`/reason-codes${buildQuery({ context })}`)
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export const reasonCodesApi = {
|
export const reasonCodesApi = {
|
||||||
list(context?: ReasonCodeContext): Promise<PagedResponse<ReasonCode>> {
|
list(context?: ReasonCodeContext): Promise<PagedResponse<ReasonCode>> {
|
||||||
const items = mockReasonCodes.filter((r) => !context || r.context === context)
|
const items = mockReasonCodes.filter((r) => !context || r.context === context)
|
||||||
|
|||||||
@@ -1,38 +1,9 @@
|
|||||||
// One typed client method per Requisition endpoint (docs/11-BACKEND-PHASE1.md §3.1, FR-PROC-01).
|
// One typed client method per Requisition endpoint (docs/11-BACKEND-PHASE1.md §3.1, FR-PROC-01).
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so the Procurement
|
|
||||||
// screens can be reviewed without a running backend. Restore the commented block
|
|
||||||
// and delete the mock block once Backend/PROGRESS.md §2 (Procurement) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { CreateRequisitionRequest, Requisition, RequisitionStatus, RequisitionSummary } from "@/types/procurement"
|
import { CreateRequisitionRequest, Requisition, RequisitionStatus, RequisitionSummary } from "@/types/procurement"
|
||||||
import { allocateReqLineId, allocateRequisitionId, mockDelay, mockRequisitions } from "@/lib/api/mock-data"
|
import { allocateReqLineId, allocateRequisitionId, mockDelay, mockRequisitions } from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListRequisitionsParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// status?: RequisitionStatus
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const requisitionsApi = {
|
|
||||||
// list(params: ListRequisitionsParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<RequisitionSummary>>(`/requisitions${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(requisitionId: number) {
|
|
||||||
// return apiRequest<Requisition>(`/requisitions/${requisitionId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateRequisitionRequest) {
|
|
||||||
// return apiRequest<Requisition>("/requisitions", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// submit(requisitionId: number) {
|
|
||||||
// return apiRequest<Requisition>(`/requisitions/${requisitionId}/submit`, { method: "POST", body: {} })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListRequisitionsParams {
|
export interface ListRequisitionsParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
// One typed client method per RFQ/Quotation endpoint (docs/11-BACKEND-PHASE1.md §3.2, FR-PROC-02).
|
// One typed client method per RFQ/Quotation endpoint (docs/11-BACKEND-PHASE1.md §3.2, FR-PROC-02).
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so the Procurement
|
|
||||||
// screens can be reviewed without a running backend. Restore the commented block
|
|
||||||
// and delete the mock block once Backend/PROGRESS.md §2 (Procurement) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import {
|
import {
|
||||||
CreateQuotationRequest,
|
CreateQuotationRequest,
|
||||||
@@ -23,28 +19,6 @@ import {
|
|||||||
mockRfqs,
|
mockRfqs,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export const rfqsApi = {
|
|
||||||
// list() {
|
|
||||||
// return apiRequest<PagedResponse<RfqSummary>>("/rfqs")
|
|
||||||
// },
|
|
||||||
// get(rfqId: number) {
|
|
||||||
// return apiRequest<Rfq>(`/rfqs/${rfqId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateRfqRequest) {
|
|
||||||
// return apiRequest<Rfq>("/rfqs", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// addQuotation(rfqId: number, request: CreateQuotationRequest) {
|
|
||||||
// return apiRequest<Quotation>(`/rfqs/${rfqId}/quotations`, { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// comparison(rfqId: number) {
|
|
||||||
// return apiRequest<RfqComparison>(`/rfqs/${rfqId}/comparison`)
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
function toSummary(r: Rfq): RfqSummary {
|
function toSummary(r: Rfq): RfqSummary {
|
||||||
return {
|
return {
|
||||||
rfqId: r.rfqId,
|
rfqId: r.rfqId,
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// One typed client method per adjustment endpoint (docs/11-BACKEND-PHASE1.md §5.5).
|
// One typed client method per adjustment endpoint (docs/11-BACKEND-PHASE1.md §5.5).
|
||||||
//
|
// In-memory Stock Core (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with the in-memory Stock Core (lib/api/mock-data.ts). Restore the
|
|
||||||
// commented block and delete the mock block once Backend/PROGRESS.md §5
|
|
||||||
// (stock transactions) exists. GET /stock-adjustments and GET
|
|
||||||
// /stock-adjustments/{id} are not documented in docs/11 §5.5 — same
|
|
||||||
// assumed-extension deviation as GRN (see Frontend/PROGRESS.md §5).
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { AdjustmentStatus, CreateAdjustmentRequest, StockAdjustment, StockAdjustmentSummary } from "@/types/stock"
|
import { AdjustmentStatus, CreateAdjustmentRequest, StockAdjustment, StockAdjustmentSummary } from "@/types/stock"
|
||||||
import {
|
import {
|
||||||
@@ -19,28 +13,6 @@ import {
|
|||||||
receiveLayer,
|
receiveLayer,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListAdjustmentsParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// warehouseId?: number
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const stockAdjustmentsApi = {
|
|
||||||
// list(params: ListAdjustmentsParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<StockAdjustmentSummary>>(`/stock-adjustments${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(adjustmentId: number) {
|
|
||||||
// return apiRequest<StockAdjustment>(`/stock-adjustments/${adjustmentId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateAdjustmentRequest) {
|
|
||||||
// return apiRequest<StockAdjustment>("/stock-adjustments", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListAdjustmentsParams {
|
export interface ListAdjustmentsParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// One typed client method per count endpoint (docs/11-BACKEND-PHASE1.md §5.6).
|
// One typed client method per count endpoint (docs/11-BACKEND-PHASE1.md §5.6).
|
||||||
//
|
// In-memory Stock Core (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with the in-memory Stock Core (lib/api/mock-data.ts). Restore the
|
|
||||||
// commented block and delete the mock block once Backend/PROGRESS.md §5
|
|
||||||
// (stock transactions) exists. GET /stock-counts and GET /stock-counts/{id}
|
|
||||||
// are not documented in docs/11 §5.6 — same assumed-extension deviation as
|
|
||||||
// GRN (see Frontend/PROGRESS.md §5).
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import {
|
import {
|
||||||
CountStatus,
|
CountStatus,
|
||||||
@@ -31,34 +25,6 @@ import {
|
|||||||
receiveLayer,
|
receiveLayer,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListCountsParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// warehouseId?: number
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const stockCountsApi = {
|
|
||||||
// list(params: ListCountsParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<StockCountSummary>>(`/stock-counts${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(countId: number) {
|
|
||||||
// return apiRequest<StockCount>(`/stock-counts/${countId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateCountRequest) {
|
|
||||||
// return apiRequest<StockCount>("/stock-counts", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// enterCounts(countId: number, request: EnterCountsRequest) {
|
|
||||||
// return apiRequest<EnterCountsResponse>(`/stock-counts/${countId}/counts`, { method: "PUT", body: request })
|
|
||||||
// },
|
|
||||||
// post(countId: number) {
|
|
||||||
// return apiRequest<PostCountResponse>(`/stock-counts/${countId}/post`, { method: "POST", body: {} })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListCountsParams {
|
export interface ListCountsParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// One typed client method per transfer endpoint (docs/11-BACKEND-PHASE1.md §5.4).
|
// One typed client method per transfer endpoint (docs/11-BACKEND-PHASE1.md §5.4).
|
||||||
//
|
// In-memory Stock Core (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with the in-memory Stock Core (lib/api/mock-data.ts). Restore the
|
|
||||||
// commented block and delete the mock block once Backend/PROGRESS.md §5
|
|
||||||
// (stock transactions) exists. GET /stock-transfers and GET /stock-transfers/{id}
|
|
||||||
// are not documented in docs/11 §5.4 — same assumed-extension deviation as GRN
|
|
||||||
// (see Frontend/PROGRESS.md §5).
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import {
|
import {
|
||||||
CreateTransferRequest,
|
CreateTransferRequest,
|
||||||
@@ -27,39 +21,6 @@ import {
|
|||||||
receiveLayer,
|
receiveLayer,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListTransfersParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// status?: TransferStatus
|
|
||||||
// srcWarehouseId?: number
|
|
||||||
// destWarehouseId?: number
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const stockTransfersApi = {
|
|
||||||
// list(params: ListTransfersParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<StockTransferSummary>>(`/stock-transfers${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(transferId: number) {
|
|
||||||
// return apiRequest<StockTransfer>(`/stock-transfers/${transferId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateTransferRequest) {
|
|
||||||
// return apiRequest<StockTransfer>("/stock-transfers", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// dispatch(transferId: number) {
|
|
||||||
// return apiRequest<DispatchTransferResponse>(`/stock-transfers/${transferId}/dispatch`, { method: "POST", body: {} })
|
|
||||||
// },
|
|
||||||
// receive(transferId: number, lines: ReceiveTransferLineInput[]) {
|
|
||||||
// return apiRequest<ReceiveTransferResponse>(`/stock-transfers/${transferId}/receive`, {
|
|
||||||
// method: "POST",
|
|
||||||
// body: { lines },
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListTransfersParams {
|
export interface ListTransfersParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
// One typed client method per stock-enquiry endpoint (docs/11-BACKEND-PHASE1.md §5.1-5.3, §5.7).
|
// One typed client method per stock-enquiry endpoint (docs/11-BACKEND-PHASE1.md §5.1-5.3, §5.7).
|
||||||
//
|
// In-memory Stock Core (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with the in-memory Stock Core (lib/api/mock-data.ts) so the Stock
|
|
||||||
// Management screens can be reviewed without a running backend. Restore the
|
|
||||||
// commented block and delete the mock block once Backend/PROGRESS.md §4
|
|
||||||
// (Stock Core) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { LedgerEntry, OnHand, ReorderAlert, ReorderRequisitionResponse, Valuation } from "@/types/stock"
|
import { LedgerEntry, OnHand, ReorderAlert, ReorderRequisitionResponse, Valuation } from "@/types/stock"
|
||||||
import {
|
import {
|
||||||
@@ -19,44 +14,6 @@ import {
|
|||||||
mockStockLedger,
|
mockStockLedger,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface LedgerQuery {
|
|
||||||
// itemId?: number
|
|
||||||
// warehouseId?: number
|
|
||||||
// from?: string
|
|
||||||
// to?: string
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const stockApi = {
|
|
||||||
// onHand(itemId: number, warehouseId: number) {
|
|
||||||
// return apiRequest<OnHand>(`/stock/on-hand${buildQuery({ itemId, warehouseId })}`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// ledger(params: LedgerQuery) {
|
|
||||||
// return apiRequest<PagedResponse<LedgerEntry>>(`/stock/ledger${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// valuation(itemId: number, warehouseId: number) {
|
|
||||||
// return apiRequest<Valuation>(`/stock/valuation${buildQuery({ itemId, warehouseId })}`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// reorderAlerts(warehouseId?: number) {
|
|
||||||
// return apiRequest<PagedResponse<ReorderAlert>>(`/stock/reorder-alerts${buildQuery({ warehouseId })}`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// createReorderRequisition(itemId: number, warehouseId: number) {
|
|
||||||
// return apiRequest<ReorderRequisitionResponse>(
|
|
||||||
// `/stock/reorder-alerts/${itemId}/requisition${buildQuery({ warehouseId })}`,
|
|
||||||
// { method: "POST", body: {} }
|
|
||||||
// )
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface LedgerQuery {
|
export interface LedgerQuery {
|
||||||
itemId?: number
|
itemId?: number
|
||||||
warehouseId?: number
|
warehouseId?: number
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
// One typed client method per UOM endpoint (docs/11-BACKEND-PHASE1.md §2.2, FR-MD-02).
|
// One typed client method per UOM endpoint (docs/11-BACKEND-PHASE1.md §2.2, FR-MD-02).
|
||||||
// `list` also backs the GRN/PO line UOM picker built in earlier sessions.
|
// `list` also backs the GRN/PO line UOM picker built in earlier sessions.
|
||||||
//
|
// In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so these screens
|
|
||||||
// can be reviewed without a running backend. Restore the commented block and
|
|
||||||
// delete the mock block once Backend/PROGRESS.md §1 (Master Data) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { CreateUomRequest, Uom } from "@/types/master-data"
|
import { CreateUomRequest, Uom } from "@/types/master-data"
|
||||||
import { allocateUomId, mockDelay, mockUoms } from "@/lib/api/mock-data"
|
import { allocateUomId, mockDelay, mockUoms } from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export const uomsApi = {
|
|
||||||
// list() {
|
|
||||||
// return apiRequest<PagedResponse<Uom>>("/uoms")
|
|
||||||
// },
|
|
||||||
// create(request: CreateUomRequest) {
|
|
||||||
// return apiRequest<Uom>("/uoms", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export const uomsApi = {
|
export const uomsApi = {
|
||||||
list(): Promise<PagedResponse<Uom>> {
|
list(): Promise<PagedResponse<Uom>> {
|
||||||
return mockDelay({
|
return mockDelay({
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
// One typed client method per vendor (supplier) endpoint (docs/11-BACKEND-PHASE1.md
|
// One typed client method per vendor (supplier) endpoint (docs/11-BACKEND-PHASE1.md
|
||||||
// §2.4, FR-MD-06). "GET/PUT/PATCH follow the Item pattern" per the doc — ETag/If-Match
|
// §2.4, FR-MD-06). ETag/If-Match on update, PATCH status for deactivate (masters
|
||||||
// on update, PATCH status for deactivate (masters are deactivated, not hard-deleted,
|
// are deactivated, not hard-deleted, FR-MD-08). In-memory sample data
|
||||||
// FR-MD-08).
|
// (lib/api/mock-data.ts) — no backend API calls.
|
||||||
//
|
import { ApiResult, EntityStatus, PagedResponse } from "@/types/common"
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so these screens can
|
|
||||||
// be reviewed without a running backend. Restore the commented block and delete
|
|
||||||
// the mock block once Backend/PROGRESS.md §1 (Master Data) exists.
|
|
||||||
import { ApiResult } from "@/lib/api-client"
|
|
||||||
import { EntityStatus, PagedResponse } from "@/types/common"
|
|
||||||
import { Vendor } from "@/types/master-data"
|
import { Vendor } from "@/types/master-data"
|
||||||
import {
|
import {
|
||||||
allocateVendorId,
|
allocateVendorId,
|
||||||
@@ -19,45 +13,6 @@ import {
|
|||||||
mockVendors,
|
mockVendors,
|
||||||
} from "@/lib/api/mock-data"
|
} from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest, apiRequestWithETag, buildQuery } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface ListVendorsParams {
|
|
||||||
// page?: number
|
|
||||||
// pageSize?: number
|
|
||||||
// q?: string
|
|
||||||
// status?: EntityStatus
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export interface CreateVendorRequest {
|
|
||||||
// code: string
|
|
||||||
// name: string
|
|
||||||
// terms?: string | null
|
|
||||||
// taxReg?: string | null
|
|
||||||
// currency: string
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export type UpdateVendorRequest = CreateVendorRequest
|
|
||||||
//
|
|
||||||
// export const vendorsApi = {
|
|
||||||
// list(params: ListVendorsParams = {}) {
|
|
||||||
// return apiRequest<PagedResponse<Vendor>>(`/vendors${buildQuery(params)}`)
|
|
||||||
// },
|
|
||||||
// get(vendorId: number) {
|
|
||||||
// return apiRequestWithETag<Vendor>(`/vendors/${vendorId}`)
|
|
||||||
// },
|
|
||||||
// create(request: CreateVendorRequest) {
|
|
||||||
// return apiRequestWithETag<Vendor>("/vendors", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// update(vendorId: number, request: UpdateVendorRequest, ifMatch: string) {
|
|
||||||
// return apiRequestWithETag<Vendor>(`/vendors/${vendorId}`, { method: "PUT", body: request, ifMatch })
|
|
||||||
// },
|
|
||||||
// updateStatus(vendorId: number, status: EntityStatus) {
|
|
||||||
// return apiRequest<void>(`/vendors/${vendorId}/status`, { method: "PATCH", body: { status } })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface ListVendorsParams {
|
export interface ListVendorsParams {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
|
|||||||
@@ -1,47 +1,9 @@
|
|||||||
// One typed client method per warehouse/bin endpoint (docs/11-BACKEND-PHASE1.md §2.5,
|
// One typed client method per warehouse/bin endpoint (docs/11-BACKEND-PHASE1.md §2.5,
|
||||||
// FR-MD-07/FR-WH-01).
|
// FR-MD-07/FR-WH-01). In-memory sample data (lib/api/mock-data.ts) — no backend API calls.
|
||||||
//
|
|
||||||
// UI-ONLY MODE: the real fetch-based implementation is commented out below and
|
|
||||||
// replaced with in-memory sample data (lib/api/mock-data.ts) so the GRN/Stock/
|
|
||||||
// Warehouse screens can be reviewed without a running backend. Restore the
|
|
||||||
// commented block and delete the mock block once Backend/PROGRESS.md §1
|
|
||||||
// (Master Data) exists.
|
|
||||||
import { PagedResponse } from "@/types/common"
|
import { PagedResponse } from "@/types/common"
|
||||||
import { Bin, Warehouse } from "@/types/master-data"
|
import { Bin, Warehouse } from "@/types/master-data"
|
||||||
import { allocateBinId, allocateWarehouseId, mockBins, mockDelay, mockWarehouses } from "@/lib/api/mock-data"
|
import { allocateBinId, allocateWarehouseId, mockBins, mockDelay, mockWarehouses } from "@/lib/api/mock-data"
|
||||||
|
|
||||||
// --- Real implementation (restore when the backend exists) --------------------
|
|
||||||
// import { apiRequest } from "@/lib/api-client"
|
|
||||||
//
|
|
||||||
// export interface CreateWarehouseRequest {
|
|
||||||
// code: string
|
|
||||||
// name: string
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export interface CreateBinRequest {
|
|
||||||
// code: string
|
|
||||||
// binType?: string | null
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// export const warehousesApi = {
|
|
||||||
// list() {
|
|
||||||
// return apiRequest<PagedResponse<Warehouse>>("/warehouses")
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// create(request: CreateWarehouseRequest) {
|
|
||||||
// return apiRequest<Warehouse>("/warehouses", { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// listBins(warehouseId: number) {
|
|
||||||
// return apiRequest<PagedResponse<Bin>>(`/warehouses/${warehouseId}/bins`)
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// createBin(warehouseId: number, request: CreateBinRequest) {
|
|
||||||
// return apiRequest<Bin>(`/warehouses/${warehouseId}/bins`, { method: "POST", body: request })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- Mock implementation (UI-only review) --------------------------------------
|
|
||||||
export interface CreateWarehouseRequest {
|
export interface CreateWarehouseRequest {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Bearer-token storage for the ERPCore API client (docs/20-FRONTEND.md §1).
|
|
||||||
// Login (POST /auth/login) is not wired yet — reads simply return null until it is,
|
|
||||||
// and the API client omits the Authorization header in that case.
|
|
||||||
|
|
||||||
const STORAGE_KEY = "erpcore.accessToken"
|
|
||||||
|
|
||||||
export function getAccessToken(): string | null {
|
|
||||||
if (typeof window === "undefined") return null
|
|
||||||
return window.localStorage.getItem(STORAGE_KEY)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setAccessToken(token: string | null) {
|
|
||||||
if (typeof window === "undefined") return
|
|
||||||
if (token) window.localStorage.setItem(STORAGE_KEY, token)
|
|
||||||
else window.localStorage.removeItem(STORAGE_KEY)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
// code -> user-facing message (docs/20-FRONTEND.md §3.2: "Keep a code -> message
|
// code -> user-facing message (docs/20-FRONTEND.md §3.2: "Keep a code -> message
|
||||||
// map in lib/ so messages are consistent"). Falls back to the ProblemDetails
|
// map in lib/ so messages are consistent"). Falls back to the ProblemDetails
|
||||||
// title/detail from the server when a code isn't in the map.
|
// title/detail from the server when a code isn't in the map.
|
||||||
import { ApiError } from "@/lib/api-client"
|
|
||||||
|
interface ApiErrorLike {
|
||||||
|
code?: string
|
||||||
|
detail?: string
|
||||||
|
errors?: Record<string, string[]>
|
||||||
|
}
|
||||||
|
|
||||||
const CODE_MESSAGES: Record<string, string> = {
|
const CODE_MESSAGES: Record<string, string> = {
|
||||||
OVER_RECEIPT_TOLERANCE: "This quantity exceeds the purchase order's open quantity beyond the allowed tolerance.",
|
OVER_RECEIPT_TOLERANCE: "This quantity exceeds the purchase order's open quantity beyond the allowed tolerance.",
|
||||||
@@ -21,21 +26,21 @@ const CODE_MESSAGES: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function errorMessage(error: unknown): string {
|
export function errorMessage(error: unknown): string {
|
||||||
if (error instanceof ApiError) {
|
if (error && typeof error === "object") {
|
||||||
if (error.code && CODE_MESSAGES[error.code]) return CODE_MESSAGES[error.code]
|
const e = error as ApiErrorLike
|
||||||
return error.detail || error.message || "Something went wrong."
|
if (e.code && CODE_MESSAGES[e.code]) return CODE_MESSAGES[e.code]
|
||||||
|
if (e.detail) return e.detail
|
||||||
}
|
}
|
||||||
if (error instanceof Error) return error.message
|
if (error instanceof Error) return error.message
|
||||||
return "Something went wrong."
|
return "Something went wrong."
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fieldErrors(error: unknown): Record<string, string> | null {
|
export function fieldErrors(error: unknown): Record<string, string> | null {
|
||||||
if (error instanceof ApiError && error.errors) {
|
const errors = error && typeof error === "object" ? (error as ApiErrorLike).errors : undefined
|
||||||
|
if (!errors) return null
|
||||||
const out: Record<string, string> = {}
|
const out: Record<string, string> = {}
|
||||||
for (const [field, messages] of Object.entries(error.errors)) {
|
for (const [field, messages] of Object.entries(errors)) {
|
||||||
out[field] = messages[0]
|
out[field] = messages[0]
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ export interface PagedResponse<T> {
|
|||||||
pagination: PaginationMeta
|
pagination: PaginationMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
/** RFC 7807 ProblemDetails, normalized by lib/api-client.ts. */
|
/** A resource plus its concurrency token (docs/11 §1.6 ETag/If-Match), e.g. `vendorsApi.get()`. */
|
||||||
|
export interface ApiResult<T> {
|
||||||
|
data: T
|
||||||
|
etag: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** RFC 7807 ProblemDetails shape (docs/11-BACKEND-PHASE1.md §1.8). */
|
||||||
export interface ProblemDetails {
|
export interface ProblemDetails {
|
||||||
type?: string
|
type?: string
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
Reference in New Issue
Block a user