# 推荐箱型 / 设箱校验

> 需求速读、白场与晚场完整测试步骤、供应商设箱校验、测试矩阵及可执行 SQL

- 类型：测试手册
- 分类：仓库配货 / 打包
- 更新：2026-08-08
- 网页版：https://doc.xfun.fun/box-suggest-test-guide

给第一次接触该需求的测试人员。目标不是只在后台看到一个箱型字段，而是把“配置、生成、打包校验、晚场重算”四条链路完整走通。

依据提交 `0019b5646c3a98e7c813e0cd14a4ba7561ebf89f`，测试场次日期以 `2026-10-27` 为例，执行时替换为当天。

## 交付状态

- **白场测试数据** 已于 2026-08-08 在测试环境验证
- **供应商设箱** 需使用有货区权限的打包账号
- **晚场加单** 必须使用 type=2 非自营城市仓数据
- **真实令牌** 不写入文档，只在本机临时粘贴

## 这次到底改了什么

一件花材订单先按箱型容量生成推荐方案；仓库真正设箱时再比较实际箱与推荐箱；城市仓晚场有新单时，还要把未出库包裹按新总量重新规划。

**本提交覆盖四条业务链路**

- 后台打包配置新增 `flowerWeight`，单位 kg，0 表示不参与普通推荐。
- 普通物流按箱型容量拆包，中通仍按每 45kg 一件且不推荐普通箱。
- 供应商设箱不符时，小程序先返回 `6001`，确认后带 `forceFlag=1` 重调并留痕。
- 非自营城市仓晚场加单后，type=2 聚合面单按白天加晚场总量重算。

> [!IMPORTANT]
> **不要把测试缩成一个列表查询**：后台列表有推荐值，只能证明生成落库成功。它不能证明 6001 二次确认、强制留痕、PDA 绕过、晚场幂等与部分出库扣重正确。

1. **配置容量**：普通箱维护可装花材重量；专用箱容量为 0。

2. **生成推荐**：最大箱先装整箱，余数匹配第一个能装下的最小箱。

3. **实际设箱**：实际箱与推荐箱比较，生成合规、不符或不可比较状态。

4. **晚场重算**：仅城市仓 type=2；已出库不动，其余重排并按需追加。

> [!WARNING]
> **以当前代码为唯一验收口径。**
>
> 旧文档有两处已过时：PDA 现在遇到不符不会返回 6001；白天已有蝴蝶兰且晚场再次新增蝴蝶兰时，当前实现会再追加一张蝴蝶兰箱面单。

## 先记住这些判定规则

下面是执行测试时最容易混淆的口径。建议先用两分钟看完，再开始造数据。

| 规则 | 输入 | 正确结果 |
|----|----|----|
| 普通箱拆分 | 80kg，最大容量 87kg | 1 张 110\*60\*60 |
| 整箱加余数 | 180kg | 87 + 87 + 6.1，共 3 张 |
| 特殊花材 | 普通45kg + 蝴蝶兰2kg + 蓝色妖姬1kg | 1 张普通箱 + 蝴蝶兰箱 + 蓝妖箱 |
| 中通 | 100kg | ceil(100/45)=3 张，推荐字段为空 |
| 无有效容量 | 所有普通箱 flowerWeight=0 | 回退每 60kg 一件，推荐字段为空 |
| 匹配状态 | 未设箱，或无推荐 | `boxMatchStatus=0`，尚不可判定合规 |
| 匹配状态 | 实际箱 = 推荐箱 | `boxMatchStatus=1` |
| 匹配状态 | 实际箱 ≠ 推荐箱 | `boxMatchStatus=2` |
| 晚场状态 0/10 | 未出库 | 一起按新推荐序列覆盖，实际箱不跟随修改 |
| 晚场状态 ≥20 | 已出库 | 任何字段都不改，优先按实际箱容量扣减已带走重量 |

> [!NOTE]
> **18:05 是当前边界：** 白场只取 `create_time < 18:05`，晚场新增判断只认 `create_time > 18:05`。主流程请用 17:00 和 21:00，避免恰好 18:05 的边界空档。

## 环境准备与完整 SQL

下文 cURL 里的场次日期 `2026-10-27`、API 地址、`usertokenxm`、`wareId` 都是占位，执行前替换成实际值。**令牌不要写进文档或聊天记录。**

### 执行前置

| 检查项 | 要求 | 不满足时 |
|----|----|----|
| 数据库字段 | `flower_weight`、`suggest_pack_id`、`suggest_pack_info` 已存在 | 停止，先补发布 DDL |
| Admin 服务 | 包含本提交，测试接口可调用 | 确认部署分支和实例版本 |
| Supplier 服务 | 包含本提交，账号有打包货区权限 | 更换账号或调整 SQL 货位 |
| 物流配置 | 供应商脚本的 `@express_id` 是有效物流且不等于 146 | 改成测试环境真实 id |
| 配置缓存 | 直接 SQL 改箱型后等待约 100 秒，或后台保存任一配置刷新 | 旧缓存会让结果看似错误 |

#### 只读 DDL 自检

```sql
SELECT table_name, column_name, column_type
FROM information_schema.columns
WHERE table_schema = DATABASE()
  AND ((table_name = 'xm_bulky_pack_config' AND column_name = 'flower_weight')
    OR (table_name = 'xm_bulky_wms_express_package'
        AND column_name IN ('suggest_pack_id', 'suggest_pack_info')))
ORDER BY table_name, ordinal_position;

-- 预期恰好返回 3 行
```

### 测试数据文件

- **SQL 1** [测试数据-推荐箱型设箱校验-admin.sql](/测试数据-推荐箱型设箱校验-admin.sql)：箱型容量 + TESTADM-A/B/C/D 白场后台数据，可重复执行

- **SQL 2** [测试数据-推荐箱型设箱校验.sql](/测试数据-推荐箱型设箱校验.sql)：TESTBOX-A/B/C/D 供应商设箱、筛选、角标和 6001 数据

- **SQL 3** [测试数据-推荐箱型设箱校验-晚场.sql](/测试数据-推荐箱型设箱校验-晚场.sql)：隔离城市仓的白场 + 21:00 加单，支持 NORMAL / PACKED / PARTIAL / SPECIAL / ZTO

> [!WARNING]
> **只在测试库执行。**Admin 脚本会把 DDL 默认残留的 `flower_weight=1.00` 清零，并按测试名称清理旧数据。先确认当前连接库名，再执行。

## 白场后台生成

这组用例证明容量拆分、特殊花材、中通和幂等。TESTADM 数据是 ware_id=0 的 type=1 数据，不能拿去测晚场城市仓重算。

### 步骤 1 · 执行 Admin 测试 SQL

执行 SQL 1。结果区必须看到 `template_ready=OK`，且 TESTADM-A 到 D 的 `generation_ready` 都是 OK。

**验收**：SQL 执行成功，模板和 4 条发货单自检均为 OK

### 步骤 2 · 触发白场包裹生成

调用一次测试接口。若 SQL 刚修改箱型容量，先等待缓存过期或在后台保存任一箱型。

`POST generateWmsExpressPackage`

```bash
curl --url 'https://api.xunmengvip.cn/xm-bulky-admin/test/generateWmsExpressPackage' \
  -H 'accept: */*' \
  -H 'channel: admin' \
  -H 'content-type: application/json' \
  -H 'usertokenxm: <粘贴当前 usertokenxm>' \
  --data-raw '{"date":"2026-10-27"}'
```
**验收**：接口 HTTP 200 且业务响应成功

### 步骤 3 · 查询全部序号

列表请求不要传 `serialNum:1`，否则只能看到每组第 1 张，容易误判“没有追加面单”。

`POST wmsExpressPackage/list · sceneType=1`

```bash
curl --url 'https://api.xunmengvip.cn/xm-bulky-admin/order/wmsExpressPackage/list' \
  -H 'accept: */*' \
  -H 'channel: admin' \
  -H 'content-type: application/json' \
  -H 'usertokenxm: <粘贴当前 usertokenxm>' \
  --data-raw '{"roundNo":"2026-10-27","sceneType":1,"zoneList":[],"index":1,"size":100}'
```
**验收**：已核对 TESTADM-A/B/C/D 的全部 serialNum

### 步骤 4 · 对照精确预期

A：1 张 110\*60\*60。B：110\*60\*60、110\*60\*60、小件90\*20。C：普通 110\*45\*45，加蝴蝶兰箱与蓝妖箱各 1 张。D：中通 3 张，推荐 id 全为 0。

**验收**：数量、推荐名称、容量与 packageNum 全部符合

### 步骤 5 · 重跑验证幂等

先记录 TESTADM 包裹总行数和 id，再调用同一个白场接口一次。行数与 id 必须不变。

**验收**：第二次生成没有重复插入

#### 白场数据库断言

```sql
SELECT p.send_no, p.serial_num, p.package_num,
       p.suggest_pack_id, p.suggest_pack_info,
       c.flower_weight AS suggest_capacity,
       p.total_weight, p.express_status
FROM xm_bulky_wms_express_package p
LEFT JOIN xm_bulky_pack_config c ON c.id = p.suggest_pack_id
WHERE p.send_no LIKE 'TESTADM%'
  AND p.data_status = 0
ORDER BY p.send_no, p.serial_num;

-- 幂等前后记录两次结果，COUNT 与 id 集合都应一致
SELECT COUNT(*) AS row_count,
       GROUP_CONCAT(id ORDER BY id) AS id_list
FROM xm_bulky_wms_express_package
WHERE send_no LIKE 'TESTADM%' AND data_status = 0;
```

## 供应商设箱校验

每次测试会修改包裹状态。建议一个动作验证完就重跑 SQL 2 复位，避免前一个用例影响下一个用例。

> [!WARNING]
> 执行 SQL 前先改 `@shelf_a` 到 `@shelf_d`，确保货位在测试打包员权限范围内；把 `@express_id` 改成有效物流 id，且不要用 146。成功打包后，同一 userId 测下一包需间隔 10 秒。

### 步骤 1 · 执行 Supplier 测试 SQL

记录脚本最后输出的包裹 id，扫码内容就是该 id。T-991 待打包；T-992 合规；T-993 含不符；T-994 无推荐。

**验收**：已保存 T-991 到 T-994 的包裹 id 和箱型 id

### 步骤 2 · 验证货架列表与筛选

`status=0` 时验证 T-991。`status=1` 时应有 T-992/T-993/T-994；T-993 的 `boxMismatchFlag=1`。筛选 2 仅 T-993；筛选 1 返回 T-992 和 T-994。

**验收**：全部、合规、不符三个筛选结果正确

### 步骤 3 · 验证详情状态

T-991 三行均为 0，但前两行仍有推荐字段；T-992 两行均为 1；T-993 序号1为2、序号2为1；T-994为0。

**验收**：suggestPackId、suggestPackInfo、boxMatchStatus 均符合

### 步骤 4 · 推荐箱直接成功

重置 SQL 后，对 T-991 序号1传推荐箱，`forceFlag=0`。预期成功，状态10，匹配状态1，不产生强制不符留痕。

**验收**：推荐箱设箱成功且无不符留痕

### 步骤 5 · 不符时 6001，再确认强制使用

重置 SQL 后，对 T-991 序号2选另一个箱。首次 `forceFlag=0` 必须返回6001且不改状态；立刻用完全相同参数重调，只改 `forceFlag=1`，应成功并记录一次修改留痕。

**验收**：6001、取消不请求、强制重调和留痕全部正确

### 步骤 6 · 无推荐与 PDA 例外

T-991 序号3可用任意合法箱直接成功。PDA `/pack/finish/scan` 用非推荐箱也应直接成功，不返回6001，但要写“扫码枪打包”不符留痕。PDA 账号必须有物流专员角色。

**验收**：无推荐不拦截；PDA 不拦截但正确留痕

#### 供应商接口请求体模板

```text
// 货架列表。status=0 查待打包；status=1 查已打包
POST /package/shelf/page
{
  "date": "2026-10-27",
  "userId": <测试账号ID>,
  "userName": "<测试账号名称>",
  "status": 1,
  "boxMatchFilter": 2,
  "current": 1,
  "size": 20
}

// 首次设箱。codeContent 填 SQL 输出的包裹 id
POST /package/pack/finish
{
  "codeContent": "<包裹ID>",
  "userId": <测试账号ID>,
  "userName": "<测试账号名称>",
  "phone": "13800000000",
  "packId": <实际箱ID>,
  "packName": "<实际箱名称>",
  "url": "",
  "shelfPackage": 0,
  "forceFlag": 0
}

// 收到 code=6001 后，只把 forceFlag 改成 1，其余参数原样重调
```

#### 强制使用留痕断言

```sql
SELECT package_id, origin_pack_id, origin_pack_name,
       target_pack_id, target_pack_name,
       operate_user_id, operate_user_name, remark, create_time
FROM xm_bulky_package_box_modify_record
WHERE send_no LIKE 'TESTBOX%'
  AND data_status = 0
ORDER BY id DESC;

-- 小程序强制使用预期 remark：
-- 实际箱型与推荐箱型不一致，已确认强制使用
-- PDA 不符预期 remark：
-- 扫码枪打包，实际箱型与推荐箱型不一致
```

## 晚场加单应该这样测

晚场重算只发生在 type=2 非自营城市仓聚合组。TESTADM-A 到 D 都是 type=1，继续用它们触发晚场接口只会得到“没有重算”的正确结果。

> [!WARNING]
> **查询用 sceneType=2，不要用 sceneType=4。**
>
> type=2 聚合包裹的 `send_no` 为空；sceneType=4 按晚场发货单号查，通常看不到这组包裹。也不要传 `serialNum=1`，否则看不到追加行。

### 主流程：NORMAL

### 步骤 1 · 准备同仓白场与晚场发货单

打开 SQL 3，确认 `@case_name='NORMAL'` 后执行准备区。脚本会选择当天无真实订单占用的非自营城市仓，插入 17:00 的 45kg 白场单和 21:00 的 50kg 晚场单，并输出 wareId。

**验收**：late_fixture_ready=OK，已记下输出的 wareId

### 步骤 2 · 先调用白场生成

虽然数据库已有晚场单，白场入口只会读取 18:05 前的白场单。此时应只有 1 张 `110*45*45`，总重45kg。

**验收**：白场基线为 1 张 110\*45\*45

### 步骤 3 · 触发晚场重算

调用晚场测试接口。预期总重95kg：原 serial1 改成 `110*60*60`，追加 serial2 `小件100*25`，两张未出库行的 `packageNum=2`。

`POST generateWmsExpressPackageNight`

```bash
curl --url 'https://api.xunmengvip.cn/xm-bulky-admin/test/generateWmsExpressPackageNight' \
  -H 'accept: */*' \
  -H 'channel: admin' \
  -H 'content-type: application/json' \
  -H 'usertokenxm: <粘贴当前 usertokenxm>' \
  --data-raw '{"date":"2026-10-27"}'
```
**验收**：推荐被覆盖、追加 1 张、总重量和件数刷新

### 步骤 4 · 查询全部 type=2 包裹

填写 SQL 输出的 wareId 后复制请求。接口用于页面核对，精确字段仍建议同时跑下方数据库断言。

`POST wmsExpressPackage/list · sceneType=2`

```bash
curl --url 'https://api.xunmengvip.cn/xm-bulky-admin/order/wmsExpressPackage/list' \
  -H 'accept: */*' \
  -H 'channel: admin' \
  -H 'content-type: application/json' \
  -H 'usertokenxm: <粘贴当前 usertokenxm>' \
  --data-raw '{"roundNo":"2026-10-27","sceneType":2,"zoneList":[],"index":1,"size":100}'
```
**验收**：sceneType=2 能看到 serial1 与 serial2

### 步骤 5 · 同一晚场接口再调一次

记录行数和 id，再跑一次。第二次不得继续追加，id、serialNum 与推荐都保持不变。

**验收**：晚场第二次执行完全幂等

#### 晚场数据库断言，wareId 取 SQL 输出

```sql
SELECT id, round_no, ware_id, partner_id, serial_num, package_num,
       total_weight, item_num, express_status,
       package_id, package_info, suggest_pack_id, suggest_pack_info,
       data_status, create_time, update_time
FROM xm_bulky_wms_express_package
WHERE round_no = CURDATE()
  AND type = 2
  AND ware_id = <SQL输出的wareId>
  AND partner_id = 0
ORDER BY serial_num, id;

-- 幂等前后比较
SELECT COUNT(*) AS row_count,
       GROUP_CONCAT(id ORDER BY id) AS id_list,
       GROUP_CONCAT(serial_num ORDER BY serial_num) AS serial_list
FROM xm_bulky_wms_express_package
WHERE round_no = CURDATE()
  AND type = 2
  AND ware_id = <SQL输出的wareId>
  AND partner_id = 0
  AND data_status = 0;
```

### 晚场扩展场景

- **PACKED · 已设箱也重排**：白场45kg先把 serial1 设为实际 110\*45\*45、状态10。晚场后推荐改成110\*60\*60，实际箱不变，因此匹配状态变2。

- **PARTIAL · 部分出库扣容量**：白场80kg的87kg箱设为状态20；晚场再加100kg。剩93kg，追加87kg箱和6.1kg箱；已出库旧行任何字段不变。

- **SPECIAL / ZTO · 专用箱与中通**：同品种跨白晚场要追加专用箱。中通总95kg目标3件，从白场1件追加2件，全部无普通箱推荐。

#### 如何切换晚场扩展场景

重跑 SQL 3 前，把第一段 `@case_name` 改成 `PACKED`、`PARTIAL`、`SPECIAL` 或 `ZTO`。执行准备区，调用白场接口；PACKED/PARTIAL 再执行 SQL 文件里的“白场后置”区；最后调用晚场接口并按文件尾部预期核对。每个场景都要再跑一次晚场接口验证幂等。

## 完整测试矩阵

P0 是本次发布必测；P1 是边界、风险和兼容性。

| 级别 | ID | 场景 | 关键预期 |
|----|----|----|----|
| P0 | CFG-01 | flowerWeight 列表、保存与校验 | 0、87.00 可保存；null、负数、1000.01、3位小数失败 |
| P0 | DAY-01 | 80kg / 180kg 普通箱拆分 | A 为 1 张最大箱；B 为 87+87+6.1 |
| P0 | DAY-02 | 特殊花材独立装箱 | 普通重量先扣特殊重量；专用箱仍有推荐 id |
| P0 | DAY-03 | 中通100kg | 3件，推荐 id=0，推荐容量=null |
| P0 | DAY-04 | 白场重复执行 | 行数和 id 不变 |
| P0 | PACK-01 | 货架筛选与角标 | 不符筛选仅 T-993；合规筛选含无推荐 T-994 |
| P0 | PACK-02 | 推荐箱直接设箱 | 成功，状态1，无强制留痕 |
| P0 | PACK-03 | 非推荐箱 + forceFlag | 首次6001不落库；force=1成功且仅一条留痕 |
| P0 | PACK-04 | 点击“否” | 只关闭弹窗，不发第二次请求 |
| P0 | PACK-05 | PDA 非推荐箱 | 不返回6001，直接成功，写扫码枪不符留痕 |
| P0 | NIGHT-01 | 45kg + 50kg 主流程 | 49.6箱改87箱，追加13kg箱，总2件 |
| P0 | NIGHT-02 | 状态10重排 | 推荐更新，实际箱不变，变为设箱不符 |
| P0 | NIGHT-03 | 部分已出库 | 按实际箱容量优先扣重，状态20整行不改 |
| P0 | NIGHT-04 | 晚场幂等 | 第二次无更新或追加，id 不变 |
| P0 | NIGHT-05 | 特殊花材跨阶段 | 白场蝴蝶兰 + 晚场蝴蝶兰，共2张蝴蝶兰箱 |
| P0 | NIGHT-06 | 中通晚场 | 总95kg目标3件，追加2件，均无推荐 |
| P1 | EDGE-01 | 0.5 / 6 / 174kg | 最小有效箱、6.1箱、两个87箱且无余箱 |
| P1 | EDGE-02 | 中通冷链硬限制 | expressId=146 + packId 12/17，force=1也不能绕过 |
| P1 | EDGE-03 | 重算后需求变少 | 多余面单保留，不删除、不作废、不清旧推荐 |
| P1 | EDGE-04 | 作废行与序号 | 作废行不参与件数，但新序号从历史最大值继续 |
| P1 | RISK-01 | FHH 更小晚场货位 | 观察是否因 key 改变而重复整组生成，属于已知风险 |
| P1 | RISK-02 | 物流电子面单数量 | 仅隔离配置下测试，避免真实第三方面单副作用 |

## 怎样算测试完成

每条失败必须能回答：输入是什么、接口返回什么、数据库最终是什么、第二次执行是否变化。

- **保留请求**：接口、请求体、场次、wareId、包裹id。令牌打码，禁止贴完整 JWT。

- **保留响应**：HTTP 状态、业务 code、msg、records 中的推荐与匹配字段。

- **保留前后快照**：id、serialNum、status、实际箱、推荐箱、总重、件数及留痕记录。

#### 测试结果回报模板

```text
提交：0019b5646c3a98e7c813e0cd14a4ba7561ebf89f
环境：test1 / API 实例：________
场次：________    测试账号：________

白场后台：通过 / 不通过 / 未测
供应商设箱：通过 / 不通过 / 未测
晚场 NORMAL：通过 / 不通过 / 未测
晚场 PACKED：通过 / 不通过 / 未测
晚场 PARTIAL：通过 / 不通过 / 未测
晚场 SPECIAL/ZTO：通过 / 不通过 / 未测

失败用例 ID：________
请求与响应：________
数据库前后快照：________
重跑结果：________
日志关键字：________
结论与阻塞：________
```

## 看不到数据时先查这里

大多数“接口没值”不是算法问题，而是日期、场景、序号、分组或缓存没有对齐。

### Admin 列表没有今天的发货单

- 确认请求 `roundNo` 与 SQL 的 `CURDATE()` 是同一天，数据库与应用时区均为 Asia/Shanghai。
- SQL 末尾的 `template_ready`、`generation_ready` 必须为 OK。
- 执行 SQL 只造发货单，不会自动造包裹；必须再调用白场生成接口。
- 查所有包裹时删除 `serialNum:1`；它会隐藏追加序号。

### 晚场接口成功，但列表还是没有值

- 主流程必须是 `ware_id>0`、`single_delivery_flag=0`、type=2；TESTADM-A 到 D 不满足。
- 使用 `sceneType=2` 并传 wareId，不要用 sceneType=4。
- 白场发货单时间应为17:00，晚场新增应为21:00；恰好18:05不会触发。
- 白场与晚场必须同 wareId、partnerId 和物流分组。

### 数量对，但推荐箱不对

- 确认箱型配置缓存已刷新，数据库改值后最多等待约100秒。
- 确认普通箱的 flowerWeight 大于0，专用箱名称没有误当普通箱。
- 特殊花材重量会先从普通拆箱重量中扣除。
- 同容量箱按 sort，再按 id 取更靠前的配置。

### 供应商打包没有返回 6001

- 确认该包裹 `suggest_pack_id>0`，且请求 packId 与推荐 id 不同。
- 确认走的是 `/package/pack/finish` 或 `/finish/change`，PDA scan 入口本来就不返回6001。
- 确认没有误传 `forceFlag=1`。
- 中通冷链箱型硬限制是普通失败，不属于6001。

### 自动化测试为什么没有全绿

Admin 的推荐拆箱 7 条、晚场规划器 10 条目标单测已通过。Supplier 的 `PackageLogicFinishTest` 当前有 1 失败、1 错误，原因是测试仍断言 PDA 受区域权限限制，并保留了已不再调用的权限 stub；生产代码当前口径是 PDA 必须有物流专员角色，但跳过货区限制。不要把这两个旧断言误判为本需求手工链路失败，手工验收仍需按本页 P0 完整执行。

## 相关文档

- [推荐箱型 / 设箱校验 · 前端对接](/box-suggest-api)
- [推荐箱型 / 设箱校验 · 业务口径](/box-suggest-logic)

