| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- /**
- * 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;
- /** Caller identity forwarded to the ERP bridge for permission scoping. */
- export interface UserCtx {
- role: string; // "admin" | "agent" | "user"
- erpContactCid?: string | null; // ERP ContactID for dealer-role scoping
- }
- /** Generic fetch wrapper with timeout and error handling */
- async function erpFetch<T>(
- path: string,
- options: RequestInit = {},
- userCtx?: UserCtx
- ): Promise<T> {
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
- const url = `${ENV.erpApiUrl}${path}`;
- const roleHeader = userCtx?.role ?? "user";
- const cidHeader = userCtx?.erpContactCid ?? "";
- try {
- const res = await fetch(url, {
- ...options,
- signal: controller.signal,
- headers: {
- "Content-Type": "application/json",
- "X-API-Key": ENV.erpApiKey,
- "X-User-Role": roleHeader,
- "X-Customer-CID": cidHeader,
- ...(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, userCtx?: UserCtx): Promise<CatalogItem[]> {
- return erpFetch<CatalogItem[]>("/catalog", {
- method: "POST",
- body: JSON.stringify({ limit: 20, ...params }),
- }, userCtx);
- }
- export interface ContactParams {
- contact_id?: string;
- company?: string;
- name?: string;
- limit?: number;
- }
- export async function fetchContacts(params: ContactParams, userCtx?: UserCtx): Promise<ContactRecord[]> {
- return erpFetch<ContactRecord[]>("/contacts", {
- method: "POST",
- body: JSON.stringify({ limit: 10, ...params }),
- }, userCtx);
- }
- 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, userCtx?: UserCtx): Promise<OrderListItem[]> {
- return erpFetch<OrderListItem[]>("/orders", {
- method: "POST",
- body: JSON.stringify({ limit: 20, ...params }),
- }, userCtx);
- }
- export async function fetchOrderDetail(soId: string, userCtx?: UserCtx): Promise<OrderDetail | null> {
- try {
- return await erpFetch<OrderDetail>(`/orders/${encodeURIComponent(soId)}`, {}, userCtx);
- } 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, userCtx?: UserCtx): Promise<StockRecord[]> {
- return erpFetch<StockRecord[]>("/stock", {
- method: "POST",
- body: JSON.stringify({ limit: 50, ...params }),
- }, userCtx);
- }
|