2026-08-04 17:09:55 +08:00
|
|
|
|
import api from "./api";
|
2026-09-23 15:18:06 +08:00
|
|
|
|
import type {
|
|
|
|
|
|
ProductOutboundMaterial,
|
|
|
|
|
|
ProductScanResponse,
|
|
|
|
|
|
ProductScrap,
|
|
|
|
|
|
} from "../types/api";
|
2026-08-04 17:09:55 +08:00
|
|
|
|
|
2026-08-07 11:44:12 +08:00
|
|
|
|
/** 扫码查询 — 根据 16 位产品身份证查产品 + 顶层任务 */
|
2026-08-04 17:09:55 +08:00
|
|
|
|
export async function scanProduct(serialNumber: string): Promise<ProductScanResponse> {
|
|
|
|
|
|
const { data } = await api.get<ProductScanResponse>(
|
|
|
|
|
|
`/products/scan/${encodeURIComponent(serialNumber)}`
|
|
|
|
|
|
);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
2026-09-23 15:18:06 +08:00
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 设备的 MOM 出库明细(统一后只有这一组)
|
|
|
|
|
|
//
|
|
|
|
|
|
// 原先这里是两组接口,对应两张表:
|
|
|
|
|
|
// · outbound-orders —— 产品 ↔ 出库**单**(单据级 product_outbounds)
|
|
|
|
|
|
// · materials —— 任务 ↔ 出库**明细**(明细级 task_outbound_materials)
|
|
|
|
|
|
// 两者是同一个概念,却因为粒度不同被拆开:用户要面对两个入口两张卡,
|
|
|
|
|
|
// 而且走单据级挂的料**没有明细行 id,报不了废**。
|
|
|
|
|
|
// 现已合并成一张表、一组接口、界面上只有一张卡。
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 列出该设备挂载的全部 MOM 出库明细(按出库时间倒序)。
|
|
|
|
|
|
* 对应后端 GET /api/v1/products/{productId}/outbound-materials
|
|
|
|
|
|
*
|
|
|
|
|
|
* 一行 = 一条出库明细。`mom_line_id` 为空表示这条是**单据级存档**
|
|
|
|
|
|
* (MOM 回调时查不到明细),能看、能标撤回,但**不能报废**。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function getProductOutboundMaterials(
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
): Promise<ProductOutboundMaterial[]> {
|
|
|
|
|
|
const { data } = await api.get<ProductOutboundMaterial[]>(
|
|
|
|
|
|
`/products/${productId}/outbound-materials`,
|
|
|
|
|
|
);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 给设备挂载 MOM 出库明细(网页端/移动端的「+ 领料」都走这里)。
|
|
|
|
|
|
* 对应后端 POST /api/v1/products/{productId}/outbound-materials
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 只传 `mom_line_ids`,物料快照由后端现查 MOM —— 前端不传快照。
|
|
|
|
|
|
* ⚠️ `taskId` 可空,仅作溯源(这条料挂在哪条任务上),不参与展示/报废/删除。
|
|
|
|
|
|
* 幂等:已挂过的明细会被后端跳过。返回该设备当前**全部**出库明细。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function mountProductOutboundMaterials(
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
momLineIds: number[],
|
|
|
|
|
|
taskId?: string | null,
|
|
|
|
|
|
): Promise<ProductOutboundMaterial[]> {
|
|
|
|
|
|
const { data } = await api.post<ProductOutboundMaterial[]>(
|
|
|
|
|
|
`/products/${productId}/outbound-materials`,
|
|
|
|
|
|
{ mom_line_ids: momLineIds, task_id: taskId || null },
|
|
|
|
|
|
);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 摘掉一条**人工挂载**的出库明细(挂错了要能撤)。
|
|
|
|
|
|
* 对应后端 DELETE /api/v1/products/{productId}/outbound-materials/{materialId}
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ 只能删 `source='manual'` 的。MOM 回调自动存档的行后端返回 409 ——
|
|
|
|
|
|
* 那是系统事实,要撤得去 MOM 撤回。返回该设备**剩余**的全部出库明细。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function removeProductOutboundMaterial(
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
materialId: number,
|
|
|
|
|
|
): Promise<ProductOutboundMaterial[]> {
|
|
|
|
|
|
const { data } = await api.delete<ProductOutboundMaterial[]>(
|
|
|
|
|
|
`/products/${productId}/outbound-materials/${materialId}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 整张出库单一起摘掉(挂错了要能撤)。
|
|
|
|
|
|
* 对应后端 DELETE /api/v1/products/{productId}/outbound-materials/by-order/{outboundNo}
|
|
|
|
|
|
*
|
|
|
|
|
|
* 界面上更容易碰到的是「这一单整个挂错了」,逐条删要删好几下。
|
|
|
|
|
|
* 规则与逐条删一致:含 webhook 存档记录的单整单删不掉(后端 409)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function removeProductOutboundOrder(
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
outboundNo: string,
|
|
|
|
|
|
): Promise<ProductOutboundMaterial[]> {
|
|
|
|
|
|
const { data } = await api.delete<ProductOutboundMaterial[]>(
|
|
|
|
|
|
`/products/${productId}/outbound-materials/by-order/${encodeURIComponent(outboundNo)}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// 生产报废
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 列出该设备的报废记录(含实时回查 MOM 的状态与金额)。
|
|
|
|
|
|
* 对应后端 GET /api/v1/products/{productId}/scraps
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function listProductScraps(productId: string): Promise<ProductScrap[]> {
|
|
|
|
|
|
const { data } = await api.get<ProductScrap[]>(`/products/${productId}/scraps`);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 提交一条生产报废(领用的料在生产中损坏)。
|
|
|
|
|
|
* 对应后端 POST /api/v1/products/{productId}/scraps
|
|
|
|
|
|
*
|
|
|
|
|
|
* ⚠️ `track_ref` 必须在**打开弹窗时生成一次**并在重试时复用 ——
|
|
|
|
|
|
* 每次提交都换新的话,用户重试会在 MOM 里多报一张报废单。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function submitProductScrap(
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
payload: { mom_line_id: number; quantity: number; track_ref: string; reason?: string | null },
|
|
|
|
|
|
): Promise<ProductScrap> {
|
|
|
|
|
|
const { data } = await api.post<ProductScrap>(
|
|
|
|
|
|
`/products/${productId}/scraps`, payload,
|
|
|
|
|
|
);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|