import type { StockItem } from "./types"; /** * Stock status levels — used by the /stock list to render the * reorder-alert badge and by the API to flag low-stock rows. * * The "inactive" state is orthogonal: an inactive item is always * surfaced as `inactive` regardless of its current/min qty, so * the operator can spot soft-deleted items that the rest of the * app would otherwise treat as "low stock" or "out". */ export type StockStatus = "ok" | "low" | "out" | "inactive"; /** * Reorder-alert thresholds. * * current_qty === 0 → "out" (red) * 0 < current_qty <= min_qty → "low" (amber) * current_qty > min_qty → "ok" (green) * current_qty < 0 → "out" (red; treat as a bug * rather than clamp) * is_active === false → "inactive" (grey overlay) * * Pure, no side effects. Called by both the list page (to pick the * badge colour) and the API filter (to gate `?low_stock=true`). */ export function getStockStatus( currentQty: number | null | undefined, minQty: number | null | undefined, isActive: boolean | null | undefined, ): StockStatus { if (isActive === false) return "inactive"; const current = Number(currentQty ?? 0); const min = Number(minQty ?? 0); if (current <= 0) return "out"; if (current <= min) return "low"; return "ok"; } /** * Tailwind-friendly colour tokens for each status. Centralised so * the list table, the detail header, and the badge helper all * render the same palette — the operator's eye learns one set of * cues. */ export const STOCK_STATUS_STYLES: Record< StockStatus, { label: string; hex: string; ring: string; text: string } > = { ok: { label: "In stock", hex: "#047857", // emerald-700 — matches CompleteBadge ring: "rgba(4, 120, 87, 0.30)", text: "text-emerald-700 dark:text-emerald-400", }, low: { label: "Low stock", hex: "#b45309", // amber-700 — high contrast on light surfaces ring: "rgba(180, 83, 9, 0.30)", text: "text-amber-700 dark:text-amber-400", }, out: { label: "Out of stock", hex: "#b91c1c", // red-700 ring: "rgba(185, 28, 28, 0.30)", text: "text-red-700 dark:text-red-400", }, inactive: { label: "Inactive", hex: "#475569", // slate-600 ring: "rgba(71, 85, 105, 0.30)", text: "text-slate-600 dark:text-slate-400", }, }; /** * Convenience helper for the list-page filter: true when the row * should be surfaced by `?low_stock=true`. Mirrors the list badge * (red + amber), but EXCLUDES inactive rows — the operator asked * "what needs reordering" not "what's soft-deleted". */ export function isLowStock( currentQty: number | null | undefined, minQty: number | null | undefined, isActive: boolean | null | undefined, ): boolean { if (isActive === false) return false; return getStockStatus(currentQty, minQty, isActive) !== "ok"; } /** * Apply `?low_stock=true` semantics to a StockItem record. Used by * the list API as the gate after the PB fetch. */ export function isLowStockItem(item: Pick): boolean { return isLowStock(item.current_qty, item.min_qty, item.is_active); }