梦花仓管小程序 · 进销存
打包负责人线上提交物资申领,仓库管理员审核发放。新增 7 个接口,另有 3 处既有接口的行为变化必须跟进。
跟现有 /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 的中文,速查见 08 节。
/apply/sku/stock、/apply/detail、/apply/cancel 三个接口复用了通用的 IdQo,入参字段名都叫 id。其中 /apply/sku/stock 的 id 传的是 skuId,不是申领单 ID,另外两个才是申领单 ID。
后端从两处判定当前账号的身份,不需要前端传角色来鉴权:
| 角色 | 判定依据 | 能做什么 |
|---|---|---|
| 仓库管理员 | 账号命中配置的仓管角色 | 看全部申领单、同意、拒绝。不能提交申领(除非同时也是负责人) |
| 打包负责人 | xm_bulky_team.leader_id = 当前账号,且团队为启用状态 |
提交申领、撤销自己提交的单、只看自己带的大区的单 |
| 两者都不是 | — | 调任意申领接口都会收到 无物资申领权限 |
一个账号可以同时是仓管和负责人,这种情况下列表默认看全部;如果前端提供了「角色切换」,把当前视角作为 roleName 传给 /apply/page 即可(见 04 节)。
issuedQuantity 有值rejectReason 可能为空字符串提交时只校验一次「申领数量 ≤ 库房剩余可用」,之后不做任何预占。所以两张待审核的单可能都显示能发,实际审批时第二张会因为库存不够被拒。审批失败的 toast 要能正常展示,不要假设提交通过就一定能审批通过。
选好物资后调用,用于在申领数量输入框旁边展示「当前可申领上限」。
| 请求字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 是 | 物资 SKU ID(不是申领单 ID),取自 /supplier/wms/inventory/sku/list 的 skuId |
// 响应 data { "skuId": 1012, "skuName": "打包纸箱", "specification": "60×40×40", "unitName": "个", "availableStock": 320 // 库房剩余可用 = 全局实物 − 各大区已分配剩余 }
| 请求字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
skuId | Long | 是 | 物资 SKU ID |
teamId | Integer | 条件 | 申领大区。负责人只带 1 个大区时可以不传;带多个大区必传,不传报「请选择申领大区」。传了非自己带的大区报「无权为该大区申领」 |
quantity | Integer | 是 | 申领数量,1 ~ 10000,且不能超过 availableStock |
voucherUrls | String | 是 | JSON 数组的字符串,不是数组对象。1 ~ 5 个元素,整串 ≤ 512 字符 |
remark | String | 否 | 备注,≤ 255 字符 |
后端字段类型是 String,前端要自己 JSON.stringify 后再放进 body:
// ✅ 正确 { "skuId": 1012, "quantity": 50, "voucherUrls": "[\"https://oss/a.jpg\",\"https://oss/b.jpg\"]" } // ❌ 会报「凭证图片格式错误」 { "skuId": 1012, "quantity": 50, "voucherUrls": ["https://oss/a.jpg"] }
512 字符是整串长度,包含引号和逗号。OSS 地址长的话 5 张可能放不下 —— 建议前端上传后存相对路径或短地址,并在提交前自己先量一次 voucherUrls.length,超了就地提示,别等后端报错。
响应 data 为 null,成功即可返回列表页刷新。接口不返回新建的申领单 ID,需要跳详情的话得先刷列表拿。
入参 { "id": 申领单ID },响应 data 为 null。
只有申领人本人且单据处于待审核时才能撤销。不满足条件统一返回 申领单状态已变更,请刷新后重试(不区分「不是你的单」和「已经被审了」,避免泄露他人单据)。收到这个提示应当刷新列表。
| 请求字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
index | Integer | 否 | 页码,默认 1 |
size | Integer | 否 | 页大小,默认 10 |
status | Integer | 否 | 10/20/30/40,不传查全部。传其他值报「申领状态不合法」 |
roleName | String | 否 | 当前视角角色名。传 "打包负责人" 时强制只看自己带的大区 |
roleName,永远只能看到自己带的大区的单。roleName="打包负责人" 时,才收窄到自己带的大区。打包负责人,拼错就当没传。这和现有 /supplier/wms/inventory/team/list 的 roleName 是同一套约定,小程序当前传的 "仓库管理员" 走的就是默认(不收窄)分支,角色切换器的取值直接复用即可。响应 records 单项:
{
"id": 88,
"applyNo": "SL20260825001", // SL + yyyyMMdd + 3位序列
"teamName": "昆明一区",
"skuName": "打包纸箱", // 即「物资类别」
"specification": "60×40×40",
"unitName": "个",
"applyQuantity": 50,
"issuedQuantity": 45, // 仅「已同意」有值,其余状态恒为 null
"status": 20,
"statusDesc": "已同意", // 后端已给中文,直接展示
"createTime": "2026-08-25 09:12:33",
"auditTime": "2026-08-25 10:04:51",
"canCancel": false, // 撤销按钮显隐
"canAudit": false // 同意/拒绝按钮显隐
}
这两个布尔是后端按「状态 + 当前账号」算好的,不要在前端用 status===10 && applicantId===me 自己推,列表里也没返回 applicantId。同一条单据对不同人返回的值不同,这是预期的。
入参 { "id": 申领单ID }。返回值是列表项的超集,在上面所有字段之外多出:
| 字段 | 类型 | 说明 |
|---|---|---|
remark | String | 申领备注,无备注时是空字符串 |
voucherInfo | String[] | 这里是真数组,可直接喂给图片预览组件(提交时是字符串,返回时是数组,注意不对称) |
applicantName | String | 申领人姓名快照 |
auditorName | String | 审核人姓名,未审核时是空字符串 |
rejectReason | String | 拒绝原因,未拒绝或仓管没填时是空字符串 |
cancelTime | DateTime | 撤销时间,未撤销时为 null |
非仓管账号查非自己大区的单,返回 申领记录不存在(不是无权限),前端按「记录不存在」处理即可。
| 请求字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | Long | 是 | 申领单 ID |
issuedQuantity | Integer | 是 | 实际出库数量,可以少发但不能多发:必须 ≥1、≤ applyQuantity、≤ 当前库房剩余可用 |
审批弹窗建议把 issuedQuantity 默认填成 applyQuantity,允许仓管改小。响应 data 为 null。
入参 { "id": 申领单ID, "rejectReason": "库存留给昆明二区" }。rejectReason 后端不强制(≤255 字符),如果产品要求必填请前端自己拦。响应 data 为 null。
同意/拒绝/撤销都是 CAS 更新,只有单据仍是「待审核」才会成功。两个仓管同时点,后点的那个会拿到 申领单状态已变更,请刷新后重试。收到这条文案时请自动刷新列表并关闭弹窗,否则用户会一直点一直报错。
这三处不是新接口,是本次一并收口的老接口。前端不跟会出报错或数字对不上。
该接口此前完全没有角色校验,任何登录账号都能调。现在非仓管调用会返回 非仓库管理员,无进销存操作权限。
团队选择逻辑不变,仓管依旧可以选库房或任意大区入库。打包负责人此前若能进到这个页面,现在会被拦下,入口按角色隐藏即可。
| 出库来源 | 谁能操作 | 不满足时的报错 |
|---|---|---|
teamId = 0(库房) | 仅仓库管理员 | 非仓库管理员,无进销存操作权限 |
teamId > 0(大区) | 仓库管理员,或该大区的打包负责人 | 无该大区出库权限 |
打包负责人登记本大区物资消耗的能力保留,但不能再从库房直接出货。出库页的团队选择器建议按角色渲染:仓管给全量,负责人只给 /supplier/wms/inventory/team/list 传 roleName="打包负责人" 后返回的那几个大区。
teamId = 0 那一行的 teamStock,从「全局实物库存」改成了「库房剩余可用」:
库房剩余可用 = 全局实物库存 − Σ(各大区批次剩余)
大区行的 teamStock 语义不变(该大区剩余)。响应额外带一个 availableStock 字段,值同库房剩余可用,各行都有。
只要有物资发到过大区,库房那一行的数就会比改版前小。这正是需求要的「发放给大区的部分不再在库房重复展示」。文案上建议把库房行标成「库房剩余可用」,避免仓管以为库存丢了。
注意 physicalStock(全局实物)字段在手机端接口被 @JsonIgnore 掉了,响应里没有,前端拿不到也不需要拿。
申领相关页面不需要新的下拉数据接口,直接复用现成的两个:
| 用途 | 接口 | 说明 |
|---|---|---|
| 选申领大区 | /supplier/wms/inventory/team/list |
传 {"roleName":"打包负责人"} 拿到「库房 + 自己带的大区」。申领页要把 teamId=0 的库房项过滤掉,只留大区。返回只有 1 个大区时可以隐藏选择器、create 不传 teamId |
| 选物资 | /supplier/wms/inventory/sku/list |
传 {"skuName":"纸箱"} 或 {"specification":"60"} 模糊搜。返回 skuId / skuName / specification / unitName。「物资类别」对应 skuName,「物资规格」对应 specification |
team/list(带 roleName)→ 过滤掉库房 → 大区下拉;只有一个就选中并隐藏sku/list → 物资类别 / 规格下拉,选中拿到 skuIdapply/sku/stock(id 传 skuId)→ 展示「库房剩余可用 N 个」,作为数量输入框的 maxJSON.stringify(urls),本地先校验张数 ≤5、长度 ≤512apply/create → 成功后回列表页刷新所有失败响应的 msg 都是可以直接 toast 的中文,无需前端翻译。下表用于确认交互分支:
| msg | 出现在 | 建议处理 |
|---|---|---|
无物资申领权限 | 全部申领接口 | 既不是仓管也不是负责人,隐藏整个申领入口 |
非仓库管理员,无进销存操作权限 | approve / reject / 库房出入库 | 隐藏审核按钮 |
无该大区出库权限 | outbound/create | 出库页团队下拉按角色收窄 |
请选择申领大区 | create | 负责人带多个大区却没传 teamId,前端应设为必填 |
无权为该大区申领 | create | teamId 不在自己带的大区里,一般是前端串了数据 |
物资不存在或已停用 | sku/stock、create | 刷新物资下拉 |
申领数量超过库房剩余可用库存 | create | 重新拉 sku/stock 刷新上限 |
申领数量不能大于10000 | create | 前端 max 设 10000 |
请至少上传1张凭证 | create | 凭证必填 |
凭证图片最多上传5张 | create | 上传组件限 5 张 |
凭证图片URL文本不能超过512个字符 | create | 见 03 节说明,提交前本地量长度 |
凭证图片格式错误 | create | 大概率是传了数组而不是字符串 |
申领状态不合法 | page | status 只能传 10/20/30/40 |
申领记录不存在 | detail | 越权查看或单据已删,回列表 |
申领单状态已变更,请刷新后重试 | cancel / approve / reject | 自动刷新列表并关弹窗 |
实际出库数量不能超过申领数量 | approve | 审批弹窗 max = applyQuantity |
实际出库数量超过库房剩余可用库存 | approve | 刷新后重审 |
leader_id(负责人),一个命中仓管角色。同一个账号两种身份都有时,很多分支测不出来。createTime / auditTime / cancelTime 都可能为 null,渲染前判空。create 的 data 是 null,需要「提交成功后跳详情」的话,得先刷列表取第一条。stock/page 库房行的数和你预期的「剩余可用」一致,再测申领链路,否则数字对不上会误判成申领的 bug。