/** * ERP Bridge HTTP client * Calls the FastAPI erp-bridge service running at 127.0.0.1:8080 (internal only). * All requests are authenticated with X-API-Key header. */ import { ENV } from "./_core/env"; const TIMEOUT_MS = 8_000; /** Generic fetch wrapper with timeout and error handling */ async function erpFetch( path: string, options: RequestInit = {} ): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), TIMEOUT_MS); const url = `${ENV.erpApiUrl}${path}`; try { const res = await fetch(url, { ...options, signal: controller.signal, headers: { "Content-Type": "application/json", "X-API-Key": ENV.erpApiKey, ...(options.headers ?? {}), }, }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`ERP bridge ${res.status} for ${path}: ${text}`); } return (await res.json()) as T; } catch (err: unknown) { if ((err as Error).name === "AbortError") { throw new Error(`ERP bridge timeout after ${TIMEOUT_MS}ms for ${path}`); } throw err; } finally { clearTimeout(timer); } } // ─── Request / response types ───────────────────────────────────────────────── export interface CatalogItem { Model: string | null; Manufacturer: string | null; Description: string | null; Type: string | null; Department: string | null; Color: string | null; UOM: string | null; Status: string | null; StockQTY: number | null; EstimatedValue: number | null; } export interface ContactRecord { ContactID: string | null; Company: string | null; ContactName1: string | null; Phone1: string | null; Email1: string | null; Address1: string | null; City: string | null; State: string | null; Country: string | null; Sales1: string | null; AR_CreditHold: boolean | null; Active: boolean | null; Terms: string | null; PriceType: string | null; } export interface OrderListItem { SOID: string | null; OrderType: string | null; Status: string | null; SubStatus: string | null; CustomerName: string | null; CustomerCID: string | null; POID: string | null; Model: string | null; Quantity: number | null; UnitPrice: number | null; TotalPrice: number | null; InvoiceNo: string | null; InvoiceDate: string | null; TrackingNumber: string | null; ETD: string | null; ShipVia: string | null; Carrier: string | null; SODate: string | null; ScheduleDate: string | null; } export interface OrderItem { LineNo: number | null; ItemID: string | null; Model: string | null; Manufacturer: string | null; Description: string | null; Quantity: number | null; UnitPrice: number | null; TotalPrice: number | null; Status: string | null; StatusDesc: string | null; ETD: string | null; ItemNote: string | null; WarehouseID: number | null; InvoiceQuantity: number | null; TransactedQuantity: number | null; } export interface OrderNote { ID: number; GeneralNote: string | null; DeliveryCarrier: string | null; CODCharges: number | null; PickOrderNote: string | null; InternalNote: string | null; AccNotes: string | null; Pickslip: string | null; CreateUser: string | null; CreateTime: string | null; } export interface OrderDetail { SOID: string | null; POID: string | null; OrderType: string | null; Status: string | null; StatusDesc: string | null; CustomerName: string | null; CustomerCID: string | null; ReceiverName: string | null; ReceiverAddress1: string | null; ReceiverCity: string | null; ReceiverState: string | null; ReceiverCountry: string | null; ReceiverZipcode: string | null; ShipVia: string | null; Shipper: string | null; ScheduleDate: string | null; ArrivalDate: string | null; Notes: string | null; InternalNote: string | null; ModifyTime: string | null; Items: OrderItem[]; Notes_: OrderNote[]; // mapped from "Notes" array in the response } export interface StockRecord { Model: string | null; Manufacturer: string | null; Description: string | null; Quantity: number | null; PalletID: string | null; WarehouseCID: string | null; SubInv: string | null; HoldByPalletID: string | null; HoldByRef: string | null; B_ETA: string | null; A_ETA: string | null; SalesOrderID: string | null; } // ─── API calls ──────────────────────────────────────────────────────────────── export interface CatalogParams { model?: string; manufacturer?: string; description?: string; category?: string; status?: string; limit?: number; } export async function fetchCatalog(params: CatalogParams): Promise { return erpFetch("/catalog", { method: "POST", body: JSON.stringify({ limit: 20, ...params }), }); } export interface ContactParams { contact_id?: string; company?: string; name?: string; limit?: number; } export async function fetchContacts(params: ContactParams): Promise { return erpFetch("/contacts", { method: "POST", body: JSON.stringify({ limit: 10, ...params }), }); } export interface OrderListParams { so_id?: string; customer_name?: string; customer_cid?: string; po_id?: string; status?: string; limit?: number; } export async function fetchOrdersList(params: OrderListParams): Promise { return erpFetch("/orders", { method: "POST", body: JSON.stringify({ limit: 20, ...params }), }); } export async function fetchOrderDetail(soId: string): Promise { try { return await erpFetch(`/orders/${encodeURIComponent(soId)}`); } catch (err: unknown) { if ((err as Error).message?.includes("404")) return null; throw err; } } export interface StockParams { model?: string; warehouse_cid?: string; limit?: number; } export async function fetchStock(params: StockParams): Promise { return erpFetch("/stock", { method: "POST", body: JSON.stringify({ limit: 50, ...params }), }); }