// Packet endpoints from the phase 2 contract. import { api, request } from "./client"; import { asList, ifMatch, qs } from "./query"; import type { AttachPagesBody, BomLine, BomPatch, DeptPerson, FieldCas, Guarded, HeatTableBody, HeatTagBody, LogRow, PacketCard, PacketCreate, PacketHeader, PacketListQuery, PacketRead, ReceiptBody, ShortBody, StageFields, TransitionBody, WeldBody, WhStageBody, WorkOrder, } from "./types"; const base = (id: number) => `/api/packets/${encodeURIComponent(String(id))}`; export const packetsApi = { list: async (q: PacketListQuery = {}, signal?: AbortSignal): Promise => asList(await api.get(`/api/packets${qs({ limit: 500, ...q })}`, signal), "packets"), get: (id: number, signal?: AbortSignal) => api.get(base(id), signal), log: async (id: number, opts: { limit?: number; before?: number } = {}, signal?: AbortSignal): Promise => asList(await api.get(`${base(id)}/log${qs({ limit: opts.limit ?? 300, before: opts.before })}`, signal), "log"), create: (body: PacketCreate) => api.post("/api/packets", body), patch: (id: number, body: FieldCas) => api.patch(base(id), body), transition: (id: number, body: TransitionBody) => api.post(`${base(id)}/transitions`, body), putWorkOrders: (id: number, version: number, work_orders: WorkOrder[], extra: Guarded = {}) => request(`${base(id)}/work-orders`, { method: "PUT", body: { work_orders, ...extra }, headers: ifMatch(version) }), putBom: (id: number, version: number, bom: BomLine[], extra: Guarded = {}) => request(`${base(id)}/bom`, { method: "PUT", body: { bom, ...extra }, headers: ifMatch(version) }), attachPages: (id: number, body: AttachPagesBody) => api.post(`${base(id)}/pages`, body), removePages: (id: number, n: number, body: { reason?: string } & Guarded = {}) => api.delWithBody(`${base(id)}/pages/${n}`, body), addDoc: (id: number, body: { asset_id: string; kind: string; title: string } & Guarded) => api.post(`${base(id)}/docs`, body), removeDoc: (id: number, docId: number, extra: Guarded = {}) => api.delWithBody(`${base(id)}/docs/${docId}`, extra), move: (id: number, body: { unit_id: number; reason?: string } & Guarded) => api.post(`${base(id)}/move`, body), remove: (id: number, body: { reason: string } & Guarded) => api.delWithBody(base(id), body), setVclosed: (id: number, closed: boolean, extra: Guarded = {}) => api.post(`${base(id)}/vclosed`, { closed, ...extra }), databook: (id: number) => api.post(`${base(id)}/databook`, {}), /* Phase 3: VISUAL packets. */ revApprove: (id: number, body: { reason: string } & Guarded) => api.post(`${base(id)}/rev-approve`, body), handDrawing: (id: number, body: { asset_id: string; reason: string } & Guarded) => api.post(`${base(id)}/hand/drawing`, body), /** Multipart cannot carry JSON, so the signature rides as sign_factor and sign_secret. */ handBom: (id: number, file: File, reason: string, extra: Guarded = {}) => { const fd = new FormData(); fd.append("file", file, file.name); fd.append("reason", reason); if (extra.sign) { fd.append("sign_factor", extra.sign.factor); fd.append("sign_secret", extra.sign.secret); } if (extra.it_reason) fd.append("it_reason", extra.it_reason); return request(`${base(id)}/hand/bom`, { method: "POST", body: fd }); }, removeHandBom: (id: number, extra: Guarded = {}) => api.delWithBody(`${base(id)}/hand/bom`, extra), /* Change orders. */ coApprove: (id: number, no: number, extra: Guarded = {}) => api.post(`${base(id)}/change-orders/${no}/approve`, extra), coReject: (id: number, no: number, body: { reason: string } & Guarded) => api.post(`${base(id)}/change-orders/${no}/reject`, body), coHold: (id: number, no: number, hold: boolean, extra: Guarded = {}) => api.post(`${base(id)}/change-orders/${no}/hold`, { hold, ...extra }), /* Phase 4: the warehouse. */ /** One BOM line. If-Match is the line's row_version. */ patchBomLine: (id: number, item: number, rowVersion: number, body: BomPatch, g: Guarded = {}) => request(`${base(id)}/bom/${item}`, { method: "PATCH", body: { ...body, ...g }, headers: ifMatch(rowVersion) }), /** Opens the short on a line, or updates the open one. expect_open says which the caller saw. */ saveShort: (id: number, item: number, body: ShortBody, g: Guarded = {}) => api.post(`${base(id)}/bom/${item}/short`, { ...body, ...g }), /** "Not short after all": closes the open short. */ clearShort: (id: number, item: number, body: { expect_open: boolean }, g: Guarded = {}) => api.delWithBody(`${base(id)}/bom/${item}/short`, { ...body, ...g }), receiveShort: (id: number, item: number, body: ReceiptBody, g: Guarded = {}) => api.post(`${base(id)}/bom/${item}/short/receipts`, { ...body, ...g }), setWhStage: (id: number, body: WhStageBody, g: Guarded = {}) => api.post(`${base(id)}/wh`, { ...body, ...g }), /* Phase 5: the shop. Every answer is the packet. */ addWeld: (id: number, body: WeldBody, g: Guarded = {}) => api.post(`${base(id)}/welds`, { ...body, ...g }), /** If-Match is the weld's row_version. */ patchWeld: (id: number, no: number, rowVersion: number, body: WeldBody, g: Guarded = {}) => request(`${base(id)}/welds/${no}`, { method: "PATCH", body: { ...body, ...g }, headers: ifMatch(rowVersion) }), removeWeld: (id: number, no: number, rowVersion: number, body: { reason?: string } & Guarded = {}) => request(`${base(id)}/welds/${no}`, { method: "DELETE", body, headers: ifMatch(rowVersion) }), addHeatTag: (id: number, body: HeatTagBody, g: Guarded = {}) => api.post(`${base(id)}/heat-tags`, { ...body, ...g }), patchHeatTag: (id: number, no: number, rowVersion: number, body: HeatTagBody, g: Guarded = {}) => request(`${base(id)}/heat-tags/${no}`, { method: "PATCH", body: { ...body, ...g }, headers: ifMatch(rowVersion) }), removeHeatTag: (id: number, no: number, rowVersion: number, body: { reason?: string } & Guarded = {}) => request(`${base(id)}/heat-tags/${no}`, { method: "DELETE", body, headers: ifMatch(rowVersion) }), putHeatTable: (id: number, page: number, body: HeatTableBody, g: Guarded = {}) => api.put(`${base(id)}/heat-table/${page}`, { ...body, ...g }), removeHeatTable: (id: number, page: number, g: Guarded = {}) => api.delWithBody(`${base(id)}/heat-table/${page}`, g), /** The weld record fields with a field-level base: 409 field_conflict when someone changed one first. */ patchStage: (id: number, body: FieldCas) => api.patch(`${base(id)}/stage`, body), setChecklist: (id: number, key: string, done: boolean, g: Guarded = {}) => api.put(`${base(id)}/stage/checklist/${encodeURIComponent(key)}`, { done, ...g }), clock: (id: number, op: "start" | "pause" | "resume", g: Guarded = {}) => api.post(`${base(id)}/stage/clock`, { op, ...g }), coVerify: (id: number, no: number, body: { dept: string; note?: string } & Guarded) => api.post(`${base(id)}/change-orders/${no}/verify`, body), }; /** GET /api/departments/{code}/people: the welder picker and the weld sheet's stamp. */ export const peopleApi = { list: async (dept: string, signal?: AbortSignal): Promise => asList(await api.get(`/api/departments/${encodeURIComponent(dept)}/people`, signal), "people"), };