# 让 AI 操作海解货票款：普通产品下单到发货

版本：2026-10-15。范围：普通产品（GENERAL）+ 普通库存（2）+ 实体仓 + 我方仓发（DIRECT_TYPE=0）。本轮结束于发货，不含售后、签收、销售开票与采购收票。现有场景截图另见官网“普通产品从下单到发货”。

这份文档让具备 HTTP 调用或终端执行能力的 AI 工具按真实业务流程操作系统。WorkBuddy 可以读取授权工作目录并使用技能/脚本调用第三方 API；本包采用“文档 + HTTP 接口”接入方式，不依赖特定模型，不是内置聊天机器人，也没有新增 MCP Server 或免登录密钥。

## 1. 准备与接入

1. 解压下载包，在 WorkBuddy 中将解压目录作为可读取的工作目录；先让 AI 阅读 README.md、AGENT.md 和 openapi.json。其他能够读取文件、发起 HTTP 请求的 AI 工具也可采用相同方法。无需假定 WorkBuddy 支持一键导入 OpenAPI。
2. 准备当前部署地址、专用业务账号及最小所需角色；先确认客户、联系人、地址、商品、供应商、实体仓及库位已经维护并审核。不要使用真实管理员凭据做公开演示。
3. 登录使用账号密码，无需图形验证码。可在浏览器登录后在同一已登录浏览器内操作；如使用 curl/脚本，必须为该 HTTP 客户端单独建立登录 cookie jar。浏览器已登录不代表另一个 HTTP 客户端已登录。
4. 先完成只读查询和结果核对；再明确授权写入范围与金额、数量、收货及发货事实。本包不自动执行整条流程。

### 为 HTTP 客户端建立会话

认证接口：GET /login/companies.html 返回公司数组（companyId、companyName）；POST /login/doLogin.html 使用表单字段 userName、password、companyId 及 AJAX 请求头，无需 captcha。

下面命令使用同一个 cookie jar 保留登录会话。工作目录应为仅本人可访问的本地目录，Cookie、密码文件不要提交源码或发给咨询客户。远程部署使用实际 HTTPS 地址。

```sh
umask 077
BASE_URL='http://localhost:8889'
curl --fail-with-body -sS -c session.cookies "$BASE_URL/login/login.html" -o login.html
curl --fail-with-body -sS -b session.cookies -c session.cookies "$BASE_URL/login/companies.html"
```

把本次账号、密码、公司 ID 分别保存为本地单行文件 user-name.txt、password.txt、company-id.txt（不含末尾换行；不要写入公共包）。公司 ID 从 companies.html 的结果选择。无需获取验证码或传入 captcha；登录失败时先核对账号、密码与公司。

```sh
curl --fail-with-body -sS -b session.cookies -c session.cookies \
  -H 'X-Requested-With: XMLHttpRequest' \
  --data-urlencode userName@user-name.txt \
  --data-urlencode password@password.txt \
  --data-urlencode companyId@company-id.txt \
  "$BASE_URL/login/doLogin.html"
curl --fail-with-body -sS -b session.cookies -c session.cookies \
  -H 'Accept: application/json' "$BASE_URL/login/currentUser.html"
```

登录成功返回 success=true；currentUser 返回 data.userName。业务接口以当前账号公司为上下文，不传 COMPANY_ID、操作人或审计字段。Cookie 必须来自正常登录，不构造 EZ_SID。登录过期、重定向、HTML 登录页均停止操作，不自动跟随重定向当作成功。不要把用户确认规则误认为系统新增的强制审批门禁。

## 2. 调用约定

- GET 为查询；写入统一使用 POST。Controller 中部分是宽泛 RequestMapping，本包只公开最小调用方式。
- 带 form 的操作是 application/x-www-form-urlencoded；带 json 的操作是 application/json。不要把表单接口一律用 JSON 调用，尤其销售明细和资金流水。
- 业务响应示意：`{"success":true,"code":"0","message":null,"data":{},"count":0}`。必须同时检查内容类型、success、code，并重读相关单据。HTTP 200 不等于业务成功。
- 登录接口是单独的 success/message/redirectUrl 结构，currentUser 只有 success/data，均不按 EzResult 的 code 字段解析。列表数据通常在 data 数组，DSL 数据页 count 可能仅是页大小；待采购总数另查 countpage，分页用 currentPage/perPageInt；出入库详情在 data 对象。数据库映射使用大写字段，DTO/实体可能使用驼峰；区分大小写。
- 所有 ID 都从查询或前一步真实响应取得；示例数字仅说明格式。不要从单号截取 ID，不复用别人的公司或演示单据。
- 金额、数量使用十进制，汇总核对后再提交；不从演示售价推断真实价格，不把采购与销售价差当作最终利润。
- 当前没有统一幂等键。超时后先查是否已落单、已记流水、已生成包裹，再决定处理。没有查清时停下；严禁盲目重试新增、审核、清点、复核或发货。

## 3. 按步骤完成一笔普通产品业务

| 步骤 | 操作与接口 | 必须核对的结果 |
|---|---|---|
| 01 准备 | 查客户、联系人、地址、产品、仓库；必要时在商品详情核验类型 | 客户/供应商审核启用；GENERAL + 库存类型2；实体仓及库位正确 |
| 02 下单 | POST /p/form/submit-saleorder/saleorder-step1，再 POST /saleorder/detail/item/save | 保存 data.ID 为 saleorderId；商品数量、售价、总额、收货信息正确，DIRECT_TYPE=0 |
| 03 销售审核 | start/Process_order → 由有资格人员 complete/Process_order | 重读 header，CHECK_STATUS=3；中间审批节点不得自动循环通过 |
| 04 收款 | 查 detail/arap；查对应应收 flows；登记 flow/save；应收审核 Process_finance_arap | 实际已到账才登记；金额与流水一致；销售 PAYMENT_STATUS=2（全额收款场景） |
| 05 待采购 | 查 purchase-suggestion-list，筛选 RELATED_TYPE=2、RELATED_ID=本销售单 | 建议在销售审核后产生，不是收款后才产生；WAIT、剩余数量及源销售行一致 |
| 06 采购 | generateFromSuggestion（SELF_WAREHOUSE）→ Process_purchase 审核 | data.purchaseId；单价/数量正确；采购 CHECK_STATUS=3；LOCKED 建议不可重复下单 |
| 07 付款 | 查 purchase/detail/payables，登记真实付款流水，Process_finance_arap 审核 | 实际付款金额一致，付款状态结清；不是调用支付机构付款 |
| 08 入库清点 | purchase/detail/inbound 获取 inboundId → inbound/detail → review/oneKeyComplete | 实际已整单到货；已生成标签与待上架数量；清点不代表实体库存已上架 |
| 09 上架 | 逐级查询库位 → putaway/scan/confirm | 标签均属于本单、本商品、本仓；上架完成，待上架减少、可用库存增加 |
| 10 拣货 | saleorder/detail/outbound 获取 OUTBOUND_ID → outbound/detail → pick/start → pick/complete | outboundDetailId 是出库行；operateNum 为累计已拣数；数量正确且待复核 |
| 11 复核 | 从 candidateLabels/真实标签取码，review/scan | 按标签数量累计，不按扫码次数；reviewNum 等于本次 pickedNum，进入 PENDING_PACKING |
| 12 打包发货 | package/create → package/complete → package/weigh → express/generate → express/markPrinted → shipping/confirmPackage | 实物打包、重量、面单、打印及交运事实已确认；包裹与销售单发货结果一致 |
| 13 对账 | 重读销售 header/items/arap/flow/outbound、采购及仓库详情；界面库存变动日志复核 | 同一仓与商品的本笔入库数=本笔出库数；收付款、来源单号及交付数量一致 |

这是一笔两件商品的示例：销售2×100=200，采购2×60=120。若期初无库存、没有并发其他业务，实体仓上架后数量2，发货后0；已有库存时以期初基线+本笔增减核对，不能要求总库存为0。发货不等于签收或订单最终完结。

### 关键 ID 的传递

`saleorderId=data.ID → arap[].ID（应收） → suggestions[].ID（待采建议） → generate.data.purchaseId → payables[].ID（应付） → inbound[].ID → inbound.items[].inboundItemId + labels[].LABEL_CODE → sale.outbound[].OUTBOUND_ID → outbound.items[].outboundItemId → package.data.packageId / packages[].PACKAGE_ID`。

销售产品行 ID、待采购建议 ID、入库行 ID、出库行 ID 互不通用。一个销售行也可能拆成多个出库行/包裹；必须逐行核对数量与关联关系，不能固定取数组第一条。

## 4. 发给 AI 的第一条任务

> 请先阅读本目录 README.md、AGENT.md 与 openapi.json。我的系统地址是【实际地址】，我已为本 HTTP 客户端完成正常登录，cookie jar 在【本地路径】。第一步只查询销售单【指定 ID】的商品、应收与流水、关联出库及包裹，输出当前进度、缺少的资料和下一步建议。不要改数据，不要打印 Cookie 或读取无关文件。后续写入仅在我明确授权的业务范围内执行。

要从新订单开始，可随后提供客户、产品、数量、价格、实体仓、采购供应商、采购价和实际收付款/到货情况。让 AI 先输出计划、金额与库存影响，再授权具体步骤；没有实际收货、打包、打印或交运时不能登记完成。

## 5. HTTP 调用样例

先替换以下本地变量与请求文件中的所有示例 ID。JSON 文件示例见 request-examples.json，该文件是说明数据，不是批量执行脚本。

```sh
SALEORDER_ID='替换为真实销售单ID'
PROD_ID='替换为真实产品ID'
curl --fail-with-body -sS -b session.cookies -c session.cookies \
  -H 'Accept: application/json' "$BASE_URL/saleorder/detail/header?id=$SALEORDER_ID"
# 表单写入示意：仅在已授权、未付款且允许编辑的单据上执行
curl --fail-with-body -sS -b session.cookies -c session.cookies \
  -H 'Accept: application/json' \
  --data-urlencode "SALEORDER_ID=$SALEORDER_ID" \
  --data-urlencode "PROD_ID=$PROD_ID" --data-urlencode 'PROD_NUM=2' \
  --data-urlencode 'BASE_PRICE=100' --data-urlencode 'DIRECT_TYPE=0' \
  "$BASE_URL/saleorder/detail/item/save"
# JSON 写入示意：先在 purchase-request.json 填写已核对的建议ID、数量、价格
curl --fail-with-body -sS -b session.cookies -c session.cookies \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  --data-binary @purchase-request.json "$BASE_URL/purchase/generateFromSuggestion"
```

不使用 curl -L 自动跟随登录跳转。不要把完整响应中的联系人、银行账号等信息写入公开演示文档。写入超时不直接重复上述命令。

## 6. 常见阻断

| 表现 | 处理 |
|---|---|
| 返回登录页 / 302 / HTML | 本客户端会话失效；重新人工登录，禁止当成 JSON 成功 |
| success=false 或 code 非0 | 原样归纳 message，停下核对，不继续下游操作 |
| 审核提交成功但状态未通过 | 查看审核历史、待办与账号角色；交给当前审核人 |
| 普通产品被资质提示阻断 | 核对产品是否确为 GENERAL，是否混入医疗器械；不要删除资质规则来绕过 |
| 没有待采购行 | 检查销售审核、源订单筛选、剩余待采量、WAIT/LOCKED及分页 |
| 清点成功但不能拣货 | 本场景实体仓尚需上架；核对库存、库位及锁定信息 |
| 标签重复、库位不符或数量不足 | 重读候选、已复核标签和商品行，不自造标签或扩大数量 |
| 请求超时 | 先按关联单、流水、包裹与状态查重；无法证明未执行则停止 |

## 7. 能力与验证边界

接口路径、字段、请求类型按当前 Controller、DTO、服务与页面调用源码核对。已有 demo 普通产品完整业务及截图见场景01；此次文档不重复创建交易。不代表已在 WorkBuddy 客户端完成端到端联调，也不承诺一键导入自动执行。部署版本变化、DSL 表单调整或角色变化后，需要重新验收。

源码索引：saleorder/controller/SaleorderDetailController.java；purchase/controller/PurchaseController.java、PurchaseDetailController.java；finance/controller/FinanceArapDetailController.java；warehouse/controller/InboundController.java、OutBoundController.java；jxc/camunda/CamundaController.java；controller/LoginControllerCustom.java；topezadmin/config/layui/dsl/form/saleorder/saleorder-step1*；topezadmin/config/layui/dsl/list/purchase/purchase-suggestion-list*。EzResult 与 DSL 请求解析来自相邻 ezadmin 依赖项目。

WorkBuddy 官方参考（2026-10-15核对）：[产品介绍](https://www.workbuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Product-Guide)、[技能说明](https://www.workbuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/Skills-Market)。本包提供可供 AI 读取的资料，不将其称作已验收的 WorkBuddy 插件。

## 8. 接口逐项说明

下列字段“必填”是本场景建议的最小输入契约，部分后端可能容忍省略；AI 不应依赖宽松校验。机器定义见 openapi.json。

### 01. 检查登录

`GET /login/currentUser.html` · query

返回 data.userName。公司由登录账号决定，调用前人工核对登录账套。

### 02. 查找客户

`GET /saleorder/trader/search` · query

按名称定位客户；多条匹配必须让用户选择。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| keyword | string | 是 | 客户关键词 |

### 03. 查询客户联系人

`GET /trader/detail/contacts` · query

只使用所选客户所属联系人。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 04. 查询客户地址

`GET /trader/detail/addresses` · query

核对真实收货地址及联系人。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 05. 查询销售仓库

`GET /saleorder/warehouse/list` · query

本场景选择实体仓；不选择供应商直发虚拟仓。

### 06. 查询产品

`GET /saleorder/product/search` · query

检查业务类型 GENERAL、普通库存类型 2；不要把 GENERAL 与库存类型混为一谈。搜索结果不足时在商品详情人工核实。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| keyword | string | 是 | 商品编码或名称 |
| pageIndex | integer | 否 | 页码，从 1 开始 |

### 07. 创建销售单头

`POST /p/form/submit-saleorder/saleorder-step1` · form

现有 DSL 表单协议。先 GET /p/form/page-saleorder/saleorder-step1 核对当前版本表单；返回 data.ID、data.SALEORDER_NO。不可传入公司、操作人或自定义单号。本请求省略 ID 表示新增。本例客户、收货方、发票抬头相同，显式填写三组 ID；实际收货方不同需逐一查询确认。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| TRADER_ID | integer | 是 | 客户 ID |
| TRADER_CONTACT_ID | integer | 是 | 客户联系人 ID |
| TRADER_ADDRESS_ID | integer | 是 | 客户地址 ID |
| WAREHOUSE_ID | integer | 是 | 实体仓 ID |
| TAKE_TRADER_ID | integer | 是 | 收货客户 ID，本例与客户相同 |
| TAKE_TRADER_CONTACT_ID | integer | 是 | 收货联系人 ID |
| TAKE_TRADER_ADDRESS_ID | integer | 是 | 收货地址 ID |
| INVOICE_TRADER_ID | integer | 是 | 发票抬头客户 ID，本例与客户相同 |
| INVOICE_TRADER_CONTACT_ID | integer | 是 | 发票联系人 ID |
| INVOICE_TRADER_ADDRESS_ID | integer | 是 | 发票地址 ID |

格式示例（ID 必须替换）：

```json
{
  "TRADER_ID": 10001,
  "TRADER_CONTACT_ID": 10002,
  "TRADER_ADDRESS_ID": 10003,
  "WAREHOUSE_ID": 10004,
  "TAKE_TRADER_ID": 10001,
  "TAKE_TRADER_CONTACT_ID": 10002,
  "TAKE_TRADER_ADDRESS_ID": 10003,
  "INVOICE_TRADER_ID": 10001,
  "INVOICE_TRADER_CONTACT_ID": 10002,
  "INVOICE_TRADER_ADDRESS_ID": 10003
}
```

### 08. 添加普通产品

`POST /saleorder/detail/item/save` · form

新行不传 ID；既有行修改才传 ID。只在允许编辑、未付款状态下操作。保存后重读明细和总额；普通仓发 DIRECT_TYPE=0。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| SALEORDER_ID | integer | 是 | 销售单 ID |
| PROD_ID | integer | 是 | 产品 ID |
| PROD_NUM | number | 是 | 数量，必须大于 0 |
| BASE_PRICE | number | 是 | 经用户确认的销售单价 |
| DIRECT_TYPE | integer | 是 | 本场景固定 0 |
| PROD_COMMENTS | string | 否 | 明细备注 |
| ID | integer | 否 | 可选：已有销售明细 ID |

格式示例（ID 必须替换）：

```json
{
  "SALEORDER_ID": 10005,
  "PROD_ID": 10006,
  "PROD_NUM": 2,
  "BASE_PRICE": 100,
  "DIRECT_TYPE": 0
}
```

### 09. 销售单头

`GET /saleorder/detail/header` · query

核对客户、仓库、总额及审核、收款、发货状态。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 10. 销售明细

`GET /saleorder/detail/items` · query

记录商品行 ID、数量、单价。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 11. 关联应收

`GET /saleorder/detail/arap` · query

返回关联应收单，记录对应 ID、单号、金额，勿取其他订单应收。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 12. 销售资金流水

`GET /saleorder/detail/flow` · query

核对已有收款，避免重复登记。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 13. 关联出库

`GET /saleorder/detail/outbound` · query

每行 OUTBOUND_ID 是出库单 ID；行 ID 是出库明细 ID。按产品和关联销售行匹配。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 14. 提交销售审核

`POST /mycamunda/check/start/Process_order` · form

提交申请；提交不等于审核通过。重读业务状态后再决定下一步。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 15. 完成销售审核

`POST /mycamunda/check/complete/Process_order` · form

仅在当前账号有审批资格且用户已授权时调用。pass=true 通过；false 驳回时填写 comment。多级流程需按真实待办逐级办理，不能循环自动点通过。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单 ID |
| pass | boolean | 是 | 是否通过 |
| comment | string | 否 | 审核意见 |

### 16. 销售审核历史

`GET /mycamunda/check/historyJson/Process_order` · query

结合对应单头 CHECK_STATUS 核对审批结果；不以单次 HTTP 200 判断审批完成。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 17. 提交采购审核

`POST /mycamunda/check/start/Process_purchase` · form

提交申请；提交不等于审核通过。重读业务状态后再决定下一步。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 18. 完成采购审核

`POST /mycamunda/check/complete/Process_purchase` · form

仅在当前账号有审批资格且用户已授权时调用。pass=true 通过；false 驳回时填写 comment。多级流程需按真实待办逐级办理，不能循环自动点通过。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单 ID |
| pass | boolean | 是 | 是否通过 |
| comment | string | 否 | 审核意见 |

### 19. 采购审核历史

`GET /mycamunda/check/historyJson/Process_purchase` · query

结合对应单头 CHECK_STATUS 核对审批结果；不以单次 HTTP 200 判断审批完成。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 20. 提交应收应付审核

`POST /mycamunda/check/start/Process_finance_arap` · form

提交申请；提交不等于审核通过。重读业务状态后再决定下一步。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 21. 完成应收应付审核

`POST /mycamunda/check/complete/Process_finance_arap` · form

仅在当前账号有审批资格且用户已授权时调用。pass=true 通过；false 驳回时填写 comment。多级流程需按真实待办逐级办理，不能循环自动点通过。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单 ID |
| pass | boolean | 是 | 是否通过 |
| comment | string | 否 | 审核意见 |

### 22. 应收应付审核历史

`GET /mycamunda/check/historyJson/Process_finance_arap` · query

结合对应单头 CHECK_STATUS 核对审批结果；不以单次 HTTP 200 判断审批完成。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 23. 应收应付单头

`GET /finance/arap/detail/bill` · query

读取金额及当前状态。收款和付款使用各自关联的应收/应付单 ID。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 24. 已登记资金流水

`GET /finance/arap/detail/flows` · query

读取金额及当前状态。收款和付款使用各自关联的应收/应付单 ID。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 25. 登记收付款流水

`POST /finance/arap/flow/save` · form

只记录已实际发生的收付款，不会向银行或支付平台转账。新增不传 ID。金额、对象、时间需用户确认；保存后还需审核对应应收应付单。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| FINANCE_BILL_ID | integer | 是 | 关联应收/应付单 ID |
| TRADER_TIME | string | 是 | 本地交易时间，如 2026-10-15 10:00:00 |
| AMOUNT | number | 是 | 实际发生金额，大于 0 |
| TRADER_SUBJECT | integer | 是 | 1 对公，2 对私 |
| TRADER_MODE | integer | 是 | 1 支付宝，2 银行，3 微信，4 现金 |
| PAYER | string | 是 | 付款方 |
| PAYEE | string | 是 | 收款方 |
| PAYER_BANK_NAME | string | 否 | 付款银行 |
| PAYER_BANK_ACCOUNT | string | 否 | 付款账号 |
| PAYEE_BANK_NAME | string | 否 | 收款银行 |
| PAYEE_BANK_ACCOUNT | string | 否 | 收款账号 |
| FILE_IDS | string | 否 | 已上传附件 ID，逗号分隔 |
| COMMENTS | string | 否 | 备注 |
| ID | integer | 否 | 可选：编辑的流水 ID |

格式示例（ID 必须替换）：

```json
{
  "FINANCE_BILL_ID": 10007,
  "TRADER_TIME": "2026-10-15 10:00:00",
  "AMOUNT": 200,
  "TRADER_SUBJECT": 1,
  "TRADER_MODE": 2,
  "PAYER": "经确认的客户名称",
  "PAYEE": "经确认的收款方名称",
  "COMMENTS": "按实际到账凭证登记"
}
```

### 26. 查询待采购建议

`GET /p/list/data-purchase/purchase-suggestion-list` · query

分页返回 data 列表。仅处理 RELATED_TYPE=2、RELATED_ID=本销售单且 SUGGEST_STATUS=WAIT 的行；LOCKED 不可再次生成。分页参数为 currentPage/perPageInt，勿用 page/limit；总数见同筛选 countpage 接口，不将当前页 count 当总数。注意建议 ID 与产品 ID、销售明细 ID 不同。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| RELATED_TYPE | integer | 是 | 销售固定 2 |
| RELATED_ID | integer | 是 | 销售单 ID |
| currentPage | integer | 否 | 页码，从 1 开始 |
| perPageInt | integer | 否 | 每页条数 |

### 27. 待采购总数

`GET /p/list/countpage-purchase/purchase-suggestion-list` · query

与 data 接口使用相同筛选；读取 count 判断总条数。分页未穷尽时不能假定没有待采。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| RELATED_TYPE | integer | 是 | 销售固定 2 |
| RELATED_ID | integer | 是 | 销售单 ID |

### 28. 查询采购供应商

`GET /purchase/supplierOptions` · query

候选不等于可下单；生成时仍校验供应商与产品适用规则。

### 29. 查询采购仓库

`GET /purchase/warehouseOptions` · query

本场景使用与销售匹配的实体仓。

### 30. 从待采购生成采购单

`POST /purchase/generateFromSuggestion` · json

返回 data.purchaseId。锁定建议后进入采购审核，避免重复调用生成多单。履约方式必须与源建议一致。本场景为 SELF_WAREHOUSE。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array | 是 | 本次选中的待采购建议 ID |
| supplierId | integer | 是 | 供应商 ID |
| warehouseId | integer | 是 | 实体仓 ID |
| fulfillmentType | string | 是 | 本场景固定 SELF_WAREHOUSE |
| items | array | 是 | 逐行填写数量与采购价，id 对应 ids；元素字段：id（待采购建议 ID）；purchaseNum（本次采购数，不能超过剩余待采数量）；purchasePrice（采购单价，不能小于 0） |

格式示例（ID 必须替换）：

```json
{
  "ids": [
    10008
  ],
  "supplierId": 10009,
  "warehouseId": 10004,
  "fulfillmentType": "SELF_WAREHOUSE",
  "items": [
    {
      "id": 10008,
      "purchaseNum": 2,
      "purchasePrice": 60
    }
  ]
}
```

### 31. 采购单头

`GET /purchase/detail/header` · query

核对供应商、仓库、采购金额及审核状态。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 32. 采购明细

`GET /purchase/detail/items` · query

核对数量、价格及销售来源。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 33. 关联应付

`GET /purchase/detail/payables` · query

记录 ID、EXPECTED_AMOUNT、PAID_AMOUNT、PAYMENT_STATUS。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 34. 关联入库

`GET /purchase/detail/inbound` · query

每行 ID 为入库单 ID；不要用采购单 ID 代替。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | integer | 是 | 业务单据 ID，取上一步响应；不是单号 |

### 35. 入库详情

`GET /warehouse/inbound/detail` · query

data.inbound 为单头、items 为业务行（inboundItemId）、labels 为标签（ID、LABEL_CODE、INBOUND_ITEM_ID）。按关联关系取 ID。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| inboundId | integer | 是 | 入库单 ID |

### 36. 普通库存一键清点

`POST /warehouse/inbound/review/oneKeyComplete` · form

只在真实收货数量已确认、整单剩余数量全部到齐时使用；部分到货用人工清点。普通产品的普通库存为 2。本场景清点后仍需实体仓上架；系统生成标签，不自己编造。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| inboundId | integer | 是 | 入库单 ID |

### 37. 查询库房

`GET /warehouse/inbound/location/rooms` · query

逐级选择真实库位；不可随意取第一条。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| warehouseId | integer | 是 | 上一级位置 ID |

### 38. 查询货区

`GET /warehouse/inbound/location/areas` · query

逐级选择真实库位；不可随意取第一条。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| warehouseRoomId | integer | 是 | 上一级位置 ID |

### 39. 查询货架

`GET /warehouse/inbound/location/racks` · query

逐级选择真实库位；不可随意取第一条。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| warehouseAreaId | integer | 是 | 上一级位置 ID |

### 40. 查询库位

`GET /warehouse/inbound/location/locations` · query

逐级选择真实库位；不可随意取第一条。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| warehouseRackId | integer | 是 | 上一级位置 ID |

### 41. 按标签确认上架

`POST /warehouse/inbound/putaway/scan/confirm` · json

逐商品分组提交未上架标签，全部位置必须属于所选实体仓。上架影响库存，先核对实物。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| inboundItemId | integer | 是 | 入库业务行 ID |
| warehouseId | integer | 是 | 仓库 ID |
| labelCodes | array | 是 | 本次实际上架的标签码，取 labels.LABEL_CODE |
| warehouseRoomId | integer | 是 | 库房 ID |
| warehouseAreaId | integer | 是 | 货区 ID |
| warehouseRackId | integer | 是 | 货架 ID |
| warehouseLocationId | integer | 是 | 库位 ID |

格式示例（ID 必须替换）：

```json
{
  "inboundItemId": 10010,
  "warehouseId": 10004,
  "labelCodes": [
    "从入库详情读取的真实标签码"
  ],
  "warehouseRoomId": 10011,
  "warehouseAreaId": 10012,
  "warehouseRackId": 10013,
  "warehouseLocationId": 10014
}
```

### 42. 出库作业详情

`GET /warehouse/outbound/detail` · query

data.outbound、items、candidateLabels、reviewedLabels、packages。items 的 outboundItemId 提交时字段名为 outboundDetailId；packages 的 PACKAGE_ID 用于后续包裹操作。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outboundId | integer | 是 | 出库单 ID |

### 43. 记录拣货

`POST /warehouse/outbound/pick/start` · json

operateNum 是累计数；根据最新 pickedNum 计算。实际拣货后才登记。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outboundDetailId | integer | 是 | 出库业务行 ID，不是出库单 ID |
| operateNum | number | 是 | 本行累计拣货数，不是本次增量 |

格式示例（ID 必须替换）：

```json
{
  "outboundDetailId": 10015,
  "operateNum": 2
}
```

### 44. 完成拣货

`POST /warehouse/outbound/pick/complete` · json

请求根节点是数组。每行传已拣数量；完成后重读出库详情核对待复核状态。

请求体为数组，以下为每个元素的字段。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outboundDetailId | integer | 是 | 出库业务行 ID，不是出库单 ID |
| operateNum | number | 是 | 本行累计拣货数，不是本次增量 |

格式示例（ID 必须替换）：

```json
[
  {
    "outboundDetailId": 10015,
    "operateNum": 2
  }
]
```

### 45. 按标签复核

`POST /warehouse/outbound/review/scan` · json

每次扫描一个真实候选标签，按标签数量累加，不是每扫一次加 1；重复扫码会失败。普通库存内部标签可从候选数据取得，不要求打印。复核完成自动进入待打包。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outboundId | integer | 是 | 出库单 ID |
| outboundDetailId | integer | 是 | 对应出库业务行 ID |
| labelCode | string | 是 | 实际出库标签码 |

### 46. 创建包裹

`POST /warehouse/outbound/package/create` · json

返回 data 为包裹实体（packageId）；随后重读出库详情，从 packages 的 PACKAGE_ID 核对新包裹。只装入已复核商品。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| outboundId | integer | 是 | 出库单 ID |
| verifyLocation | string | 否 | 打包作业位置说明 |
| remark | string | 否 | 包裹备注 |
| items | array | 是 | 包裹商品行；元素字段：outboundItemId（出库业务行 ID）；packageQty（本包裹数量，不大于 availablePackingQty）；skuName（商品名称） |

格式示例（ID 必须替换）：

```json
{
  "outboundId": 10016,
  "items": [
    {
      "outboundItemId": 10015,
      "packageQty": 2
    }
  ]
}
```

### 47. 完成打包

`POST /warehouse/outbound/package/complete` · json

实物已完成打包后登记。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| packageId | integer | 是 | 包裹 ID |

### 48. 登记重量

`POST /warehouse/outbound/package/weigh` · json

按实际称重结果填写。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| packageId | integer | 是 | 包裹 ID |
| weight | number | 是 | 实际重量，必须大于 0；与界面重量单位一致 |

### 49. 登记面单

`POST /warehouse/outbound/express/generate` · json

本接口登记承运商和运单号，不保证调用快递公司购单。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| packageId | integer | 是 | 包裹 ID |
| expressCompany | string | 是 | 真实承运商名称 |
| expressNo | string | 是 | 已取得的真实运单号 |

### 50. 标记面单已打印

`POST /warehouse/outbound/express/markPrinted` · json

只在实际完成打印后调用，接口成功不代表打印机执行成功。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| packageId | integer | 是 | 包裹 ID |

### 51. 确认包裹发货

`POST /warehouse/outbound/shipping/confirmPackage` · json

人工确认货物已经交运后调用。需打包、称重、面单及打印状态满足要求。登记成功后重读销售及出库状态；不会替用户完成物理交运。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| packageId | integer | 是 | 包裹 ID |
