从补货建议到采购单:与 ERP 采购模块的闭环集成教程
为什么"最后一公里"决定补货系统的成败
很多补货项目死在最后一公里:建议算得不错,但业务还要手工把建议抄进 ERP 建采购单——抄错行、漏单、重复单随之而来,建议与单据脱节后,追溯链断裂,系统很快退回 Excel。闭环的要求是:确认后的补货方案必须能一键变成 ERP 里合规的采购订单,并且到货数据能回流修正下一轮计算。
本文以金蝶云星空(K3Cloud WebAPI)为例走通这条链路,思路同样适用于用友、SAP 等其他 ERP。
链路总览
- 方案确认:补货方案行级确认完成(无未结算行),进入待下单状态。
- 拆单:按供应商 × 仓库(orderKey)把方案行拆分为采购订单——一张 PO 只能有一个供应商、一个收货仓。
- 建单:调用 ERP WebAPI 保存采购订单(Save)。
- 提交审核:Submit → Audit,让单据进入 ERP 的正式业务流程。
- 到货回填:定时拉取采购单/入库单状态,回写"已到货量",关闭建议的生命周期。
金蝶云星空采购订单 API 要点
金蝶云星空 WebAPI 的通用单据操作以 FormId 定位单据类型,采购订单的 FormId 为 PUR_PurchaseOrder。核心操作:
| 操作 | 用途 | 说明 |
|---|---|---|
Save | 保存(新增/修改)单据 | 返回单据内码与编号 |
Submit | 提交单据 | 触发工作流/校验 |
Audit | 审核单据 | 单据生效,可被下游引用 |
ExecuteBillQuery | 查询单据 | 状态回填与对账用 |
保存采购订单的请求体核心结构(节选):
{
"FormId": "PUR_PurchaseOrder",
"Data": {
"NeedUpDateFields": [],
"Model": {
"FBillTypeID": { "FNUMBER": "CGDD01_SYS" },
"FDate": "2026-07-30",
"FSupplierId": { "FNumber": "S000123" },
"FPurchaseOrgId": { "FNumber": "100" },
"FPOOrderEntry": [
{
"FMaterialId": { "FNumber": "SKU-10086" },
"FQty": 200,
"FTaxPrice": 12.5,
"FDeliveryDate": "2026-08-06",
"FEntryNote": "补货方案 RP-20260730-003 行号 42"
}
]
}
}
}
要点:FSupplierId、FMaterialId、FPurchaseOrgId 等基础资料字段用 FNumber 编码关联,因此补货侧的主数据编码必须与 ERP 编码严格对齐——这是映射层的第一个硬约束。
字段映射与幂等设计
映射层的三个硬约束
- 编码对齐:SKU、供应商、仓库、计量单位全部以 ERP 编码为准,补货系统内做一层映射表,接口失败时优先怀疑映射缺失。
- 单据类型:标准采购单
CGDD01_SYS与委外、直运等类型分开,拆单时按业务场景选择。 - 价格与税率:含税单价、税率从供应商协议价目表带出,不要让运算层"猜价"。
幂等:重试不产生重复单
补货拆单必须设计幂等键,推荐做法:每个方案行生成稳定的 orderKey = hash(planBatchId + rowNo),下单前按 orderKey 查本地单据登记表:
// 伪代码:幂等建单
async function createPurchaseOrder(line: ConfirmedLine) {
const existing = await poRegistry.findByOrderKey(line.orderKey);
if (existing?.erpBillNo) return existing.erpBillNo; // 已建单,直接复用
const payload = buildPoPayload(line); // 字段映射
const saved = await kingdee.save("PUR_PurchaseOrder", payload);
await poRegistry.upsert({
orderKey: line.orderKey,
erpBillId: saved.Id,
erpBillNo: saved.BillNo,
status: "SAVED",
});
await kingdee.submit("PUR_PurchaseOrder", saved.Id);
await kingdee.audit("PUR_PurchaseOrder", saved.Id);
await poRegistry.markStatus(line.orderKey, "AUDITED");
return saved.BillNo;
}
注意 Save / Submit / Audit 是三个独立调用,任何一步失败都要能从登记表的状态恢复:Save 成功而 Submit 失败时,重试只补 Submit/Audit,绝不重新 Save——这是重复单的主要来源。
到货回填:闭环的另一半
建单只是前半环,后半环是到货数据回流:
- 定时任务(如每小时)用
ExecuteBillQuery拉取在途采购单的累计入库数量,或直接从采购入库单(STK_InStock)按源单关联聚合。 - 回写补货系统的"已到货量",剩余到货量 = 合计到货量 − 已到货量,进入下一轮在途计算。
- 超期未到货、到货量异常的采购单行触发预警,交人工跟进。
只有到货数据稳定回流,补货公式里的"在途"才是可信的——否则下一轮计算会把已在路上的货再补一遍。
上线检查清单
- 主数据编码映射表覆盖全部 SKU / 供应商 / 仓库
- orderKey 幂等键与单据登记表就绪
- Save/Submit/Audit 分步状态可断点恢复
- 失败告警到人,重复单有对账脚本兜底
- 到货回填任务运行正常,在途口径经业务确认
闭环打通之后,补货系统才真正从"建议工具"升级为"执行系统":建议、单据、到货三账对齐,每一行采购都能回到当初的方案行——这既是对账的基础,也是模型持续优化的数据燃料。
相关 API 文档
- 第三方系统登录(LoginByAppSecret)
POST https://{数据中心地址}/k3cloud/Kingdee.BOS.WebApi.ServicesStub.AuthService.LoginByAppSecret.common.kdsvc
- 单据通用查询(ExecuteBillQuery·销售订单)
POST https://{数据中心地址}/k3cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc
- 即时库存查询(STK_Inventory)
POST https://{数据中心地址}/k3cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc