前端对接仓库配货 / 打包
打包负责人物资申领
负责人提交(含批量)/撤销申领,仓管分批出库、手动完成、拒绝;状态机、按钮显隐字段、出库记录,以及进销存出入库权限与库房库存口径
Markdown 原文(给 AI 用):https://doc.xfun.fun/wms-material-apply-api.md
梦花仓管小程序 · 进销存
后端模块 xm-bulky-mall-gallery(与 /supplier/wms/inventory/* 同一服务)。
通用约定
跟 /supplier/wms/inventory/* 一样:base https://api.xunmengvip.cn/xm-bulky-mall-gallery,全部 POST + JSON body,鉴权、{ code, msg, data, success } 包装(success 是布尔,等价于 code === 0;失败时 HTTP 状态码仍是 200,别用状态码判断成败)、index / size 分页都照旧。失败响应的 msg 是可以直接 toast 的中文,速查见「报错文案速查」。
入参怪癖
/apply/sku/stock、/apply/detail、/apply/cancel、/apply/finish 复用通用的 IdQo,入参字段名都叫 id。其中 /apply/sku/stock 的 id 传的是 skuId,不是申领单 ID,其余才是申领单 ID。
角色与状态机
后端从两处判定当前账号的身份,不需要前端传角色来鉴权:
| 角色 | 判定依据 | 能做什么 |
|---|---|---|
| 仓库管理员 | 账号命中配置的仓管角色 | 看全部申领单、出库、手动完成、拒绝。不能提交申领(除非同时也是负责人) |
| 打包负责人 | xm_bulky_team.leader_id = 当前账号,且团队为启用状态 | 提交申领、撤销自己提交且未出库的单、只看自己带的大区的单 |
| 两者都不是 | — | 调任意申领接口都会收到 无物资申领权限 |
一个账号可以同时是仓管和负责人,这种情况下列表默认看全部;前端有「角色切换」时,把当前视角作为 roleName 传给 /apply/page(见「POST /supplier/wms/apply/page」)。
申领单状态
| status | 名称 | 说明 |
|---|---|---|
| 10 | 待审核 | 负责人提交后的初始态。此时 库存不动,不占用、不冻结 |
| 20 | 审核通过 | 历史数据状态,新单据不会再进入该状态 |
| 30 | 已拒绝 | 仓管拒绝,rejectReason 可能为空字符串。仅未出库的待审核单可拒 |
| 40 | 已撤销 | 申领人本人撤回,仅未出库的待审核单可撤 |
| 50 | 部分申领 | 已出库但累计出库数量 < 申领数量,可继续出库或手动完成 |
| 60 | 已完成 | 累计出库 = 申领数量时自动完成,或仓管对部分申领单手动完成 |
10 待审核 ──出库(未领满)──▶ 50 部分申领 ──出库(领满)──▶ 60 已完成(completeType=1 自动)
│ └──出库(一次领满)─────────────────────────────▶ 60 已完成(completeType=1 自动)
│ 50 部分申领 ──手动完成────▶ 60 已完成(completeType=2 手动)
├──拒绝──▶ 30 已拒绝
└──撤销──▶ 40 已撤销completeType:0 未完成、1 自动完成、2 手动完成、3 历史完成。
待审核不占库存
提交时只校验一次「申领数量 ≤ 库房剩余可用」,之后不做任何预占。所以两张待审核的单可能都显示能发,实际出库时第二张可能因库房库存不够失败。出库失败的 toast 要能正常展示,不要假设提交通过就一定能足量出库。
手动完成不补数也不退库
手动完成只是关闭单据:issuedQuantity、remainingQuantity、progressPercent 保持完成时的值,不再允许出库,也不调整任何库存。
负责人侧
POST/supplier/wms/apply/sku/stock查询物资库房剩余可用库存
调用方:负责人 / 仓管
选好物资后调用,用于在申领数量输入框旁边展示「当前可申领上限」,也可用于出库弹窗展示库房可发数量。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 必填 | 物资 SKU ID(不是申领单 ID),取自 /supplier/wms/inventory/sku/list 的 skuId |
响应
| 字段 | 类型 | 说明 |
|---|---|---|
skuId | Long | 物资 SKU ID |
skuName | String | 物资名称(即「物资类别」) |
specification | String | 物资规格 |
unitName | String | 计量单位 |
availableStock | Integer | 库房剩余可用 = 全局实物 − 各大区已分配剩余 |
响应示例
{
"skuId": 1012,
"skuName": "打包纸箱",
"specification": "60×40×40",
"unitName": "个",
"availableStock": 320
}POST/supplier/wms/apply/create提交物资申领
调用方:仅负责人
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
skuId | Long | 必填 | 物资 SKU ID |
teamId | Integer | 可选 | 申领大区。负责人只带 1 个大区时可以不传;带多个大区必传,不传报「请选择申领大区」。传了非自己带的大区报「无权为该大区申领」 |
quantity | Integer | 必填 | 申领数量,≥ 1 且不能超过 availableStock |
voucherUrls | String | 可选 | 凭证图片,JSON 数组的字符串,不是数组对象。最多 5 个元素,整串 ≤ 512 字符;不传或空串表示无凭证 |
remark | String | 可选 | 备注,≤ 255 字符 |
voucherUrls 是字符串,不是数组
后端字段类型是 String,前端要自己 JSON.stringify 后再放进 body。512 字符是整串长度,包含引号和逗号,OSS 地址长的话 5 张可能放不下,提交前先量一次 voucherUrls.length。
请求示例
{ "skuId": 1012, "teamId": 3, "quantity": 50, "voucherUrls": "[\"https://oss/a.jpg\",\"https://oss/b.jpg\"]", "remark": "本周加班" }传 "voucherUrls": ["https://oss/a.jpg"](数组对象)会报「凭证图片格式错误」。
响应
| 字段 | 类型 | 说明 |
|---|---|---|
id | Long | 新建申领单 ID,可直接跳详情或生成二维码 |
applyNo | String | 申领单号,SL + yyyyMMdd + 3 位序列 |
skuId | Long | 申领物资 SKU ID |
响应示例
{ "id": 88, "applyNo": "SL20261104001", "skuId": 1012 }POST/supplier/wms/apply/batchCreate批量提交物资申领
调用方:仅负责人
同一物资类别(skuName 相同)下一次申领多个规格,每个规格各生成一张申领单。整批在一个事务里,任一规格校验失败整批不提交。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
items | Array | 必填 | 规格明细,1 ~ 100 项,skuId 不能重复,且必须属于同一物资类别 |
items[].skuId | Long | 必填 | 物资 SKU ID |
items[].quantity | Integer | 必填 | 申领数量,≥ 1 且不能超过该规格的 availableStock |
teamId | Integer | 可选 | 申领大区,规则同「POST /supplier/wms/apply/create」 |
voucherUrls | String | 可选 | 本批共用的凭证图片,格式同单个提交 |
remark | String | 可选 | 本批共用的备注,≤ 255 字符 |
请求示例
{
"teamId": 3,
"items": [
{ "skuId": 1012, "quantity": 50 },
{ "skuId": 1013, "quantity": 20 }
],
"remark": "本周加班"
}响应
data 为数组,每项字段同「POST /supplier/wms/apply/create」的响应(id / applyNo / skuId),顺序按 skuId 升序。
POST/supplier/wms/apply/cancel撤销物资申领
调用方:仅申领人本人
入参 { "id": 申领单ID },响应 data 为 null。
只有 申领人本人、单据处于 待审核且 尚未出库时才能撤销,否则返回 仅申请人可以撤销尚未出库的待审核申领单。并发下状态已被别人改掉时返回 申领单状态已变更,请刷新后重试,收到后刷新列表。
列表与详情
POST/supplier/wms/apply/page申领列表
调用方:负责人 / 仓管
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
index | Integer | 可选 | 页码,默认 1 |
size | Integer | 可选 | 页大小,默认 10 |
status | Integer | 可选 | 10/20/30/40/50/60,不传查全部。传其他值报「申领状态不合法」 |
skuId | Long | 可选 | 物资 SKU ID |
teamId | Integer | 可选 | 申领大区 ID |
startTime | String | 可选 | 申请开始时间,yyyy-MM-dd HH:mm:ss,按申领时间 ≥ 过滤 |
endTime | String | 可选 | 申请结束时间,yyyy-MM-dd HH:mm:ss,按申领时间 ≤ 过滤;早于 startTime 报「开始时间不能晚于结束时间」 |
roleName | String | 可选 | 当前视角角色名。传 "打包负责人" 时只看自己带的大区 |
roleName 的作用范围
- 非仓管账号:无论传不传
roleName,永远只能看到自己带的大区的单。 - 仓管账号:默认看全部;只有当它 同时也是负责人、且传了
roleName="打包负责人"时,才收窄到自己带的大区。 - 字符串必须 精确等于
打包负责人,拼错就当没传。与/supplier/wms/inventory/team/list的roleName是同一套约定。
列表按申领单 ID 倒序。
响应
records[] 单项:
| 字段 | 类型 | 说明 |
|---|---|---|
id | Long | 申领单 ID |
applyNo | String | 申领单号 |
teamId | Integer | 申领大区 ID |
teamName | String | 申领大区名称 |
skuId | Long | 申领物资 SKU ID |
skuName | String | 物资名称(即「物资类别」) |
specification | String | 物资规格 |
unitName | String | 计量单位 |
applicantName | String | 申领负责人姓名 |
applyQuantity | Integer | 申领数量 |
issuedQuantity | Integer | 累计出库数量,未出库为 0 |
remainingQuantity | Integer | 未出库数量 = 申领数量 − 累计出库数量 |
progressPercent | Number | 出库进度百分比,保留两位小数,如 40.00 |
status | Integer | 申领状态,见「申领单状态」 |
statusDesc | String | 状态中文,直接展示 |
rejectReason | String | 拒绝原因,未拒绝或仓管没填时为空字符串 |
createTime | DateTime | 申领时间 |
auditTime | DateTime | 拒绝时间或历史审批时间,可能为 null |
completeType | Integer | 完成方式:0 未完成、1 自动完成、2 手动完成、3 历史完成 |
completeTime | DateTime | 完成时间,未完成为 null |
completeOperatorId | Long | 完成人 ID |
completeOperatorName | String | 完成人名称(自动完成时为最后一次出库人) |
canCancel | Boolean | 是否显示「撤销」:待审核、未出库、本人提交 |
canAudit | Boolean | 是否显示「拒绝」:待审核、未出库、当前账号是仓管 |
canOutbound | Boolean | 是否显示「出库」:待审核或部分申领、仍有剩余、当前账号是仓管 |
canFinish | Boolean | 是否显示「手动完成」:部分申领、当前账号是仓管 |
响应示例
{
"id": 88,
"applyNo": "SL20261104001",
"teamId": 3,
"teamName": "昆明一区",
"skuId": 1012,
"skuName": "打包纸箱",
"specification": "60×40×40",
"unitName": "个",
"applicantName": "李四",
"applyQuantity": 50,
"issuedQuantity": 20,
"remainingQuantity": 30,
"progressPercent": 40.00,
"status": 50,
"statusDesc": "部分申领",
"rejectReason": "",
"createTime": "2026-11-04 09:12:33",
"auditTime": null,
"completeType": 0,
"completeTime": null,
"completeOperatorId": 0,
"completeOperatorName": "",
"canCancel": false,
"canAudit": false,
"canOutbound": true,
"canFinish": true
}按钮显隐直接用 can* 字段
这四个布尔是后端按「状态 + 出库进度 + 当前账号」算好的,不要在前端自己推。同一条单据对不同人返回的值不同,这是预期的。
POST/supplier/wms/apply/detail申领详情
调用方:仓管 / 本大区负责人
入参 { "id": 申领单ID }。返回列表项的全部字段,另外多出:
| 字段 | 类型 | 说明 |
|---|---|---|
remark | String | 申领备注,无备注时为空字符串 |
voucherInfo | String[] | 这里是真数组,可直接喂给图片预览组件(提交时是字符串,返回时是数组) |
auditorName | String | 拒绝人或历史审批人姓名,没有时为空字符串 |
outboundRecords | Array | 出库记录,按出库时间、明细 ID 升序,字段见「POST /supplier/wms/apply/item/page」 |
cancelTime | DateTime | 撤销时间,未撤销为 null |
cancelOperatorId | Long | 撤销操作人 ID |
cancelOperatorName | String | 撤销操作人名称 |
非仓管账号查非自己大区的单,返回 申领记录不存在(不是无权限),前端按「记录不存在」处理即可。
POST/supplier/wms/apply/item/page出库记录分页
调用方:仓管 / 本大区负责人
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 必填 | 申领单 ID |
index | Integer | 可选 | 页码,默认 1 |
size | Integer | 可选 | 页大小,默认 10 |
响应
records[] 单项,按出库时间、明细 ID 升序:
| 字段 | 类型 | 说明 |
|---|---|---|
id | Long | 出库明细 ID |
applyId | Long | 所属申领单 ID |
quantity | Integer | 本次出库数量 |
issuedQuantity | Integer | 本次出库后的累计出库数量 |
entryId | Long | 本次生成的大区入库批次 ID |
traceId | Long | 本次生成的库房出库流水 ID |
operatorId | Long | 出库操作人 ID |
operatorName | String | 出库操作人名称 |
createTime | DateTime | 出库时间 |
可见范围同详情:非仓管查非自己大区的单返回 申领记录不存在。
仓管操作
POST/supplier/wms/apply/outbound申领出库
调用方:仅仓管
对待审核或部分申领的单出库一次,可多次分批出库。每次出库从库房划拨到申领大区(生成一条大区入库批次和一条库房出库流水),并按累计数量自动流转状态:未领满 → 50 部分申领,领满 → 60 已完成(completeType=1)。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 必填 | 申领单 ID |
quantity | Integer | 必填 | 本次出库数量,≥ 1,且 ≤ 剩余可出库数量(remainingQuantity)、≤ 当前库房剩余可用 |
请求示例
{ "id": 88, "quantity": 20 }响应
| 字段 | 类型 | 说明 |
|---|---|---|
itemId | Long | 本次新增的出库明细 ID |
quantity | Integer | 本次出库数量 |
issuedQuantity | Integer | 本次出库后的累计出库数量 |
remainingQuantity | Integer | 未出库数量 |
progressPercent | Number | 出库进度百分比,保留两位小数 |
status | Integer | 出库后的申领状态(50 或 60) |
响应示例
{ "itemId": 501, "quantity": 20, "issuedQuantity": 20, "remainingQuantity": 30, "progressPercent": 40.00, "status": 50 }15 秒防重
同一张申领单 15 秒内只能出库一次(/outbound 与 /approve 共用),重复点击返回 该申领单过去15秒内进行过出库,请刷新页面,确认未进行重复操作后再次点击。出库按钮提交后要置灰,成功后刷新详情。
出库弹窗建议把数量默认填成 remainingQuantity,上限取 remainingQuantity 与 /apply/sku/stock 的 availableStock 的较小值。
POST/supplier/wms/apply/approve申领出库(兼容入口)
调用方:仅仓管
与「POST /supplier/wms/apply/outbound」逻辑完全相同,只是数量字段名不同,响应也相同。新页面直接用 /outbound。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 必填 | 申领单 ID |
issuedQuantity | Integer | 必填 | 本次出库数量,规则同 /outbound 的 quantity |
POST/supplier/wms/apply/finish手动完成
调用方:仅仓管
入参 { "id": 申领单ID },响应 data 为 null。
只有 部分申领(已出库且未领满)的单可以手动完成,否则返回 仅部分申领单可以手动完成。完成后状态 60、completeType=2,记录完成时间和完成人;数量和库存都不变。对已完成的单重复调用直接返回成功。
POST/supplier/wms/apply/reject拒绝申领
调用方:仅仓管
入参 { "id": 申领单ID, "rejectReason": "库存留给昆明二区" }。rejectReason 后端 不强制(≤ 255 字符),产品要求必填请前端自己拦。响应 data 为 null。
只有 待审核且未出库的单能拒绝;已出库(部分申领)的单不能拒绝,只能手动完成。
并发下的统一反馈
出库、手动完成、拒绝、撤销都带状态条件更新。两个仓管同时操作,后操作的那个会拿到 申领单状态已变更,请刷新后重试 或 申领单状态已变更或不允许出库,请刷新后重试。收到这类文案时自动刷新列表并关闭弹窗。
进销存出入库权限与库存口径
申领链路依赖下面三个进销存接口的规则,前端出入库页、库存页要按此渲染。
POST/supplier/wms/inventory/inbound/create新增入库
仅仓库管理员可调用,非仓管返回 非仓库管理员,无进销存操作权限。仓管可以选库房或任意大区入库,入口按角色隐藏即可。
POST/supplier/wms/inventory/outbound/create新增出库
| 出库来源 | 谁能操作 | 不满足时的报错 |
|---|---|---|
teamId = 0(库房) | 仅仓库管理员 | 非仓库管理员,无进销存操作权限 |
teamId > 0(大区) | 仓库管理员,或 该大区的打包负责人 | 无该大区出库权限 |
打包负责人可以登记本大区的物资消耗,但不能从库房直接出货(库房物资通过申领出库发到大区)。出库页的团队选择器按角色渲染:仓管给全量,负责人只给 /supplier/wms/inventory/team/list 传 roleName="打包负责人" 后返回的大区。
POST/supplier/wms/inventory/stock/page库存页
teamId = 0 那一行的 teamStock 是「库房剩余可用」:
库房剩余可用 = 全局实物库存 − Σ(各大区批次剩余)大区行的 teamStock 是该大区剩余。每行都带 availableStock,值同库房剩余可用;欠库时两者都允许为负数。全局实物库存字段不在手机端返回。
发到大区的物资不在库房行重复展示,所以库房行比全局实物小。文案上建议把库房行标成「库房剩余可用」,避免仓管以为库存丢了。
搭页面时会用到的进销存接口
申领相关页面不需要新的下拉数据接口,直接复用:
| 用途 | 接口 | 说明 |
|---|---|---|
| 选申领大区 | /supplier/wms/inventory/team/list | 传 {"roleName":"打包负责人"} 拿到「库房 + 自己带的大区」。申领页要把 teamId=0 的库房项过滤掉,只留大区。只有 1 个大区时可以隐藏选择器、提交时不传 teamId |
| 选物资 | /supplier/wms/inventory/sku/list | 传 {"skuName":"纸箱"} 或 {"specification":"60"} 模糊搜。返回 skuId / skuName / specification / unitName。「物资类别」对应 skuName,「物资规格」对应 specification |
推荐的提交页交互顺序
team/list(带 roleName)→ 过滤掉库房 → 大区下拉;只有一个就选中并隐藏sku/list→ 物资类别 / 规格下拉,选中拿到skuId;同一类别多选规格时走batchCreateapply/sku/stock(id传 skuId)→ 展示「库房剩余可用 N 个」,作为数量输入框的 max- 上传凭证(可选)→
JSON.stringify(urls),本地先校验张数 ≤ 5、长度 ≤ 512 apply/create或apply/batchCreate→ 用返回的id跳详情或回列表刷新
报错文案速查
| msg | 出现在 | 建议处理 |
|---|---|---|
无物资申领权限 | 全部申领接口 | 既不是仓管也不是负责人,隐藏整个申领入口;提交接口非负责人也报此文案 |
非仓库管理员,无进销存操作权限 | outbound / approve / finish / reject / 库房出入库 | 隐藏仓管操作按钮 |
无该大区出库权限 | inventory/outbound/create | 出库页团队下拉按角色收窄 |
请选择申领大区 | create / batchCreate | 负责人带多个大区却没传 teamId,前端设为必填 |
无权为该大区申领 | create / batchCreate | teamId 不在自己带的大区里,一般是前端串了数据 |
物资不存在或已停用 | sku/stock / create / outbound | 刷新物资下拉 |
规格[60×40×40]库存不足或数量不合法,最大可申请量: 320 | create / batchCreate | 重新拉 sku/stock 刷新上限 |
每批须包含1至100个规格 | batchCreate | 限制多选数量 |
重复规格: 1012 | batchCreate | 同一规格只能出现一次 |
批量申领包含不存在或停用的规格 | batchCreate | 刷新物资下拉 |
批量申领只能选择同一物资类别 | batchCreate | 只允许在同一 skuName 下多选规格 |
凭证图片最多上传5张 | create / batchCreate | 上传组件限 5 张 |
凭证图片URL文本不能超过512个字符 | create / batchCreate | 提交前本地量长度 |
凭证图片格式错误 | create / batchCreate | 大概率是传了数组而不是字符串 |
申领状态不合法 | page | status 只能传 10/20/30/40/50/60 |
开始时间不能晚于结束时间 | page | 校验时间区间 |
申领记录不存在 | detail / item/page / 各操作接口 | 越权查看或单据已删,回列表 |
仅申请人可以撤销尚未出库的待审核申领单 | cancel | 刷新列表,按 canCancel 显隐 |
仅部分申领单可以手动完成 | finish | 刷新详情,按 canFinish 显隐 |
本次出库数量不能超过剩余可出库数量 | outbound / approve | 出库弹窗 max = remainingQuantity |
库房库存不足,当前可发数量: 12 | outbound / approve | 改小数量,或先入库再出 |
该申领单过去15秒内进行过出库,请刷新页面,确认未进行重复操作后再次点击 | outbound / approve | 刷新详情确认出库记录 |
申领单状态已变更或不允许出库,请刷新后重试 | outbound / approve | 自动刷新列表并关弹窗 |
申领单状态已变更,请刷新后重试 | cancel / finish / reject / outbound | 自动刷新列表并关弹窗 |
联调注意
- 测试账号要准备两个:一个是某大区的
leader_id(负责人),一个命中仓管角色。同一个账号两种身份都有时,很多分支测不出来。 - 时间字段格式与进销存接口一致,
auditTime/completeTime/cancelTime都可能为 null,渲染前判空。 - voucherInfo 进出不对称:提交传字符串,详情返回数组。复用同一个表单模型时注意转换。
- 分批出库要看出库记录:每次出库一条明细,详情的
outboundRecords或/apply/item/page都能取到;累计数量以issuedQuantity为准。 - 先看库存页再对申领:联调时先确认
stock/page库房行的数和你预期的「剩余可用」一致,再测申领链路,否则数字对不上会误判成申领的 bug。