erpClient.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * ERP Bridge HTTP client
  3. * Calls the FastAPI erp-bridge service running at 127.0.0.1:8080 (internal only).
  4. * All requests are authenticated with X-API-Key header.
  5. */
  6. import { ENV } from "./_core/env";
  7. const TIMEOUT_MS = 8_000;
  8. /** Generic fetch wrapper with timeout and error handling */
  9. async function erpFetch<T>(
  10. path: string,
  11. options: RequestInit = {}
  12. ): Promise<T> {
  13. const controller = new AbortController();
  14. const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
  15. const url = `${ENV.erpApiUrl}${path}`;
  16. try {
  17. const res = await fetch(url, {
  18. ...options,
  19. signal: controller.signal,
  20. headers: {
  21. "Content-Type": "application/json",
  22. "X-API-Key": ENV.erpApiKey,
  23. ...(options.headers ?? {}),
  24. },
  25. });
  26. if (!res.ok) {
  27. const text = await res.text().catch(() => "");
  28. throw new Error(`ERP bridge ${res.status} for ${path}: ${text}`);
  29. }
  30. return (await res.json()) as T;
  31. } catch (err: unknown) {
  32. if ((err as Error).name === "AbortError") {
  33. throw new Error(`ERP bridge timeout after ${TIMEOUT_MS}ms for ${path}`);
  34. }
  35. throw err;
  36. } finally {
  37. clearTimeout(timer);
  38. }
  39. }
  40. // ─── Request / response types ─────────────────────────────────────────────────
  41. export interface CatalogItem {
  42. Model: string | null;
  43. Manufacturer: string | null;
  44. Description: string | null;
  45. Type: string | null;
  46. Department: string | null;
  47. Color: string | null;
  48. UOM: string | null;
  49. Status: string | null;
  50. StockQTY: number | null;
  51. EstimatedValue: number | null;
  52. }
  53. export interface ContactRecord {
  54. ContactID: string | null;
  55. Company: string | null;
  56. ContactName1: string | null;
  57. Phone1: string | null;
  58. Email1: string | null;
  59. Address1: string | null;
  60. City: string | null;
  61. State: string | null;
  62. Country: string | null;
  63. Sales1: string | null;
  64. AR_CreditHold: boolean | null;
  65. Active: boolean | null;
  66. Terms: string | null;
  67. PriceType: string | null;
  68. }
  69. export interface OrderListItem {
  70. SOID: string | null;
  71. OrderType: string | null;
  72. Status: string | null;
  73. SubStatus: string | null;
  74. CustomerName: string | null;
  75. CustomerCID: string | null;
  76. POID: string | null;
  77. Model: string | null;
  78. Quantity: number | null;
  79. UnitPrice: number | null;
  80. TotalPrice: number | null;
  81. InvoiceNo: string | null;
  82. InvoiceDate: string | null;
  83. TrackingNumber: string | null;
  84. ETD: string | null;
  85. ShipVia: string | null;
  86. Carrier: string | null;
  87. SODate: string | null;
  88. ScheduleDate: string | null;
  89. }
  90. export interface OrderItem {
  91. LineNo: number | null;
  92. ItemID: string | null;
  93. Model: string | null;
  94. Manufacturer: string | null;
  95. Description: string | null;
  96. Quantity: number | null;
  97. UnitPrice: number | null;
  98. TotalPrice: number | null;
  99. Status: string | null;
  100. StatusDesc: string | null;
  101. ETD: string | null;
  102. ItemNote: string | null;
  103. WarehouseID: number | null;
  104. InvoiceQuantity: number | null;
  105. TransactedQuantity: number | null;
  106. }
  107. export interface OrderNote {
  108. ID: number;
  109. GeneralNote: string | null;
  110. DeliveryCarrier: string | null;
  111. CODCharges: number | null;
  112. PickOrderNote: string | null;
  113. InternalNote: string | null;
  114. AccNotes: string | null;
  115. Pickslip: string | null;
  116. CreateUser: string | null;
  117. CreateTime: string | null;
  118. }
  119. export interface OrderDetail {
  120. SOID: string | null;
  121. POID: string | null;
  122. OrderType: string | null;
  123. Status: string | null;
  124. StatusDesc: string | null;
  125. CustomerName: string | null;
  126. CustomerCID: string | null;
  127. ReceiverName: string | null;
  128. ReceiverAddress1: string | null;
  129. ReceiverCity: string | null;
  130. ReceiverState: string | null;
  131. ReceiverCountry: string | null;
  132. ReceiverZipcode: string | null;
  133. ShipVia: string | null;
  134. Shipper: string | null;
  135. ScheduleDate: string | null;
  136. ArrivalDate: string | null;
  137. Notes: string | null;
  138. InternalNote: string | null;
  139. ModifyTime: string | null;
  140. Items: OrderItem[];
  141. Notes_: OrderNote[]; // mapped from "Notes" array in the response
  142. }
  143. export interface StockRecord {
  144. Model: string | null;
  145. Manufacturer: string | null;
  146. Description: string | null;
  147. Quantity: number | null;
  148. PalletID: string | null;
  149. WarehouseCID: string | null;
  150. SubInv: string | null;
  151. HoldByPalletID: string | null;
  152. HoldByRef: string | null;
  153. B_ETA: string | null;
  154. A_ETA: string | null;
  155. SalesOrderID: string | null;
  156. }
  157. // ─── API calls ────────────────────────────────────────────────────────────────
  158. export interface CatalogParams {
  159. model?: string;
  160. manufacturer?: string;
  161. description?: string;
  162. category?: string;
  163. status?: string;
  164. limit?: number;
  165. }
  166. export async function fetchCatalog(params: CatalogParams): Promise<CatalogItem[]> {
  167. return erpFetch<CatalogItem[]>("/catalog", {
  168. method: "POST",
  169. body: JSON.stringify({ limit: 20, ...params }),
  170. });
  171. }
  172. export interface ContactParams {
  173. contact_id?: string;
  174. company?: string;
  175. name?: string;
  176. limit?: number;
  177. }
  178. export async function fetchContacts(params: ContactParams): Promise<ContactRecord[]> {
  179. return erpFetch<ContactRecord[]>("/contacts", {
  180. method: "POST",
  181. body: JSON.stringify({ limit: 10, ...params }),
  182. });
  183. }
  184. export interface OrderListParams {
  185. so_id?: string;
  186. customer_name?: string;
  187. customer_cid?: string;
  188. po_id?: string;
  189. status?: string;
  190. limit?: number;
  191. }
  192. export async function fetchOrdersList(params: OrderListParams): Promise<OrderListItem[]> {
  193. return erpFetch<OrderListItem[]>("/orders", {
  194. method: "POST",
  195. body: JSON.stringify({ limit: 20, ...params }),
  196. });
  197. }
  198. export async function fetchOrderDetail(soId: string): Promise<OrderDetail | null> {
  199. try {
  200. return await erpFetch<OrderDetail>(`/orders/${encodeURIComponent(soId)}`);
  201. } catch (err: unknown) {
  202. if ((err as Error).message?.includes("404")) return null;
  203. throw err;
  204. }
  205. }
  206. export interface StockParams {
  207. model?: string;
  208. warehouse_cid?: string;
  209. limit?: number;
  210. }
  211. export async function fetchStock(params: StockParams): Promise<StockRecord[]> {
  212. return erpFetch<StockRecord[]>("/stock", {
  213. method: "POST",
  214. body: JSON.stringify({ limit: 50, ...params }),
  215. });
  216. }