# 包花厂集采开发文档

> 运营端 / 供应商端两服务分工、代码入口、接口清单、状态写入条件、Redis 锁与事务、定时任务、跨服务结算凭证、数据表与只读核查 SQL、已知坑

- 类型：业务口径
- 分类：包花厂集采
- 后端服务：xm-bulky-mall-admin + xm-bulky-mall-supplier
- 测试分支：sh/code
- 更新：2026-11-03
- 网页版：https://doc.xfun.fun/bulk-purchase-dev-guide

## 结论

集采拆在两个服务里：

- **运营端 `xm-bulky-mall-admin/app`**：采购计划（发布、追加、撤回、终止）、自动定采、运营看单 / 改量 / 处理超量 / 取消、包花厂商品管理、需求推进定时任务。
- **供应商端 `xm-bulky-mall-supplier`**：供应商报价、仓库 PDA 作业（签到、打印、扫码、盘点）、超量处理、**唯一的结算记账实现**、自动结算与自动取消任务。

运营后台的「手动结算」不在 admin 里记账，而是签发 60 秒一次性凭证，经 Feign 调 supplier 内部接口，由 `BulkPurchaseSettleServiceImpl` 统一记账。两端操作同一批表，并发靠「Redis 锁 + 带状态条件的 UPDATE」双保险。

业务口径（给产品 / 测试 / 现场看的版本）见 [包花厂集采业务逻辑](/bulk-purchase-logic)。本篇讲代码在哪、怎么串、改的时候要注意什么。

> [!NOTE]
> 本文基于 `sh/code` 分支 `d40aa32fe7`（2026-10-30）核对。`master` 上集采代码可能落后，排查线上问题前先确认两服务实际部署版本。

## 代码入口

路径前缀：

- admin：`xm-bulky-mall-admin/app/src/main/java/com/xunmeng/xmbulkymalladmin/`
- supplier-core：`xm-bulky-mall-supplier/supplier-core/src/main/java/com/xunmeng/xmbulkymallsupplier/suppliercore/`
- supplier-api：`xm-bulky-mall-supplier/supplier-api/src/main/java/com/xunmeng/xmbulkymallsupplier/supplierapi/`

| 业务 | 类 | 位置 |
|----|----|----|
| 采购计划发布 / 追加 / 撤回 / 终止 / 完成采集 | `BpDemandLogic` | admin `logic/` |
| 供应商范围与资格快照 | `BpSupplierScopeLogic` | admin `logic/` |
| 自动定采流程（生成供货单、完成 / 关闭计划） | `BpConfirmLogic` | admin `logic/` |
| 定采选择与分配（纯函数，有单测） | `BpAutoConfirmAlgorithm` | admin `logic/` |
| 运营看单、超量列表口径、取消 | `BpOrderLogic` | admin `logic/` |
| 运营改量 / 处理超量 / 手动结算（签凭证） | `BpOrderSettleLogic` | admin `logic/` |
| 包花厂商品上下架 / 改价 / 改库存 | `BpFactoryProductLogic` | admin `logic/` |
| 常量、状态、产出截止算法 | `BulkPurchaseEnum` | admin `enums/`、supplier-core `enums/`（**两份，改常量要同步**） |
| 需求推进定时任务 | `BulkPurchaseDemandExpireJob` | admin `task/` |
| 供应商报价、列表、截止校验 | `BulkPurchaseLogic` | supplier-core `logic/` |
| 供应商列表四类筛选 SQL | `BulkPurchaseDemandMapper.xml` | supplier-core `resources/mapper/` |
| 签到、打印、扫码、撤销、盘点、商品库存 | `BulkPurchaseWmsLogic` | supplier-core `logic/` |
| 超量、补偿、改量、取消 | `BulkPurchaseSettleLogic` | supplier-core `logic/` |
| 结算记账（唯一实现） | `BulkPurchaseSettleServiceImpl` | supplier-core `service/impl/` |
| 后台结算凭证消费 | `BulkPurchaseAdminSettleLogic` | supplier-core `logic/` |
| 盘点占用锁 | `BulkPurchaseInventoryLock` | supplier-core `service/` |
| 资金锁 | `BulkPurchaseMoneyLock` | admin `service/` 和 supplier-core `service/` 各一份，同一个 key |
| 厂长 / 采购工作台 | `BulkPurchaseFactoryLogic` | supplier-core `logic/` |
| 扫码接口（内层 `success`） | `BulkPurchaseWmsController` | supplier-api `controller/wms/` |
| 自动结算任务 | `BulkPurchaseSettleJob` | supplier-api `task/` |
| 产出截止自动取消任务 | `BulkPurchaseOrderCancelJob` | supplier-api `task/` |

仓库内已有的历史资料在 `xm-bulky-mall-admin/docs/`（`bulk-purchase-tech-design.md`、`bulk-purchase-api-admin.html`、`bulk-purchase-api-supplier.html`、`bulk-purchase-admin-settle-over-frontend.md`、DDL 在 `docs/sql/2026-09-*-bulk-purchase-*.sql`）。

## 接口清单

全部是 POST。两个服务都有 `/bulkPurchase` 前缀，但网关、鉴权不同，**不要混用地址**。

### 运营后台（admin）

| 前缀 | 接口 | 用途 |
|----|----|----|
| `/bulkPurchase/demand` | `list`、`detail`、`scopePreview`、`skuOptions`、`save`、`appendLog`、`appendSupplier`、`appendNum`、`withdraw`、`terminate`、`delete`、`finishCollect` | 采购计划 |
| `/bulkPurchase/confirm` | `quoteList`、`quoteSuppliers` | 定采报价、已报价供应商 |
| `/bulkPurchase/order` | `list`、`stat`、`detail`、`updateDelivery`、`cancel`、`settle/updateNum`、`handleOver`、`settle/manual` | 供货单、改量、超量、手动结算 |
| `/bulkPurchase/factoryProduct` | `list`、`onSale`、`updateStock`、`updatePrice`、`log/list` | 包花厂商品 |
| `/bulkPurchase/auditLog` | `page` | 操作轨迹 |
| `/bulkPurchase/second` | `list`、`favorite` | 二级分类 |
| `/bulkPurchase/whiteList` | `page`、`add`、`remove` | 全局白名单 |

### 供应商小程序（supplier-api）

| 前缀 | 接口 | 用途 |
|----|----|----|
| `/bulkPurchase` | `demand/list`、`demand/detail`、`demand/summary` | 可见计划 |
| `/bulkPurchase` | `quote/submit`、`quote/update`、`quote/adjustPrice`、`quote/history` | 报价 |
| `/bulkPurchase` | `order/list`、`order/detail` | 我的供货单 |

### 仓库 PDA（supplier-api）

| 前缀 | 接口 | 用途 |
|----|----|----|
| `/wms/bulkPurchase` | `order/list`、`order/detail`、`order/checkIn`、`order/deliveryRemark` | 找单、签到 |
| `/wms/bulkPurchase` | `print/page`、`print/normal`、`print/reprint` | 打印 |
| `/wms/bulkPurchase` | `label/resolve`、`inventory/start`、`inventory/release`、`scan`、`scan/revoke`、`scan/detail`、`inventory/submit` | 盘点 |
| `/wms/bulkPurchase` | `order/occupiedCandidates`、`order/handleOver`、`order/cancel`、`settle/updateNum`、`settle/manual` | 异常与结算 |
| `/wms/bulkPurchase/factory` | `workbench/stat`、`workbench/priority`、`overview`、`demand/list`、`demand/detail` | 厂长 / 采购工作台 |

### 内部接口

| 接口 | 调用方 | 说明 |
|----|----|----|
| `POST /internal/bulkPurchase/order/settle/manual` | admin `BulkPurchaseSettleFeign` | 请求头 `X-Bp-Admin-Settle-Ticket` 携带一次性凭证，见「跨服务手动结算」 |

## 关键常量

| 常量 | 值 | 含义 |
|----|----|----|
| `QUOTE_CLOSE_ADVANCE_SECONDS` | 5 | 供应商报价截止提前秒数 |
| `PRICE_SCALE` | 2 | 价格、补偿金额最多小数位 |
| `PRICE_MAX` | 1000 | 单价上限（元／扎） |
| `AUTO_CONFIRM_CANDIDATE_LIMIT` | 5 | 每等级定采候选数 |
| `PRINT_MAX_TIMES` | 2 | 标签上限 = 整单定采扎数 × 2 |
| `SETTLE_DELAY_DAYS` | 3 | 计划结算时间 = 提交盘点 + 3 天 |
| `COLLECT_END_TIME_OF_DAY` / `COLLECT_END_MIN_BUFFER_HOURS` | 12:00 / 2 | 产出截止 = max（场次次日 12:00，最晚签到截止 + 2h） |
| `ACCOUNT_CHANGE_INCOME` / `ACCOUNT_CHANGE_EXPENSE` | 95 / 96 | 集采收入 / 集采支出账变类型 |
| `MSG_TYPE_FACTORY_PURCHASE` | `14` | 消息中心集采消息类型 |
| `SHELF_CODE_PREFIX` | `JC` | 货架码前缀 |

枚举值（计划 0~5、供货单 10/20/30/40/90、标签 10/30/90、范围 1~6、超量类型 1/2、补偿方式 1/2/3）见 [包花厂集采业务逻辑](/bulk-purchase-logic) 的「主流程」和各规则章节。补充只在代码里出现的：

| 枚举 | 取值 |
|----|----|
| 报价选中 `selectStatus` | `0` 待定 · `1` 中选 · `2` 未中选 |
| 报价版本 `current` | `1` 当前 · `0` 历史 |
| 计划关闭原因 `closeReason` | `1` 无人报价 · `2` 手动 · `3` 全部未中选 |
| 计划完成方式 | `1` 手动完成采集 · `2` 自动 |
| 终止方式 | `1` 自然截止 · `2` 提前终止 |
| 取消方式 `cancelType` | `1` 手动 · `2` 产出截止自动 |
| 结算方式 `settleType` | `1` 自动 · `2` 手动 |
| 扫码拒绝 `rejectReason` | `2` 重复 · `3` 作废 · `4` 跨单 · `5` 不存在 · `6` 单据状态不允许 |
| 资格来源 | `1` 发布 · `2` 追加 |
| 操作端 `operator_end` | `admin` · `pda` · `system` |

## 状态流转与写入条件

所有状态推进都是**带旧状态条件的 UPDATE**，`affected=0` 视为被并发抢先，回滚或返回失败。改代码时不要去掉这些条件。

| 动作 | 关键写入条件 | 失败表现 |
|----|----|----|
| 撤回 | `status=1` 且无报价 | 「已有供应商报价，不能撤回，请使用提前终止」 |
| 报价截止流转 | `status=1` 且两个截止都已过 | 跳过 |
| 提交盘点（抢占） | `beginInventorySubmit`：`status=20` 且占用人是本人，先把状态推到 30 | 返回 false |
| 扫码加产出 | `increaseActualNum`：`status=20` | 抛「供货单已提交盘点，本次扫码未生效」，整次扫码回滚 |
| 扫码占位 | `occupyForScan`：标签 `status=10` | `affected=0` 即重复扫码（唯一判定依据） |
| 取消 | `status=原状态` 且 `actual_label_num=0` | 返回 false（期间刚好扫码成功） |
| 处理超量 | `status=30` 且 `over_flag=1`；先占标识再写补偿 | 重复提交只生效一次 |
| 登记补款加目标结算数 | 目标 `status=30` 且旧 `settle_label_num` 未变 | 整个处理事务回滚 |
| 结算置已结算 | `status=30` 且 `total_amount=读到的值` | **抛异常**（钱已加，不能只返回 false，否则下一轮重复打款） |

### 扫码事务

`BulkPurchaseWmsLogic#scan` 在一个事务里：查标签 → 跨单校验 → 拿盘点锁 → 重读标签 → 作废 / 状态校验 → 占用人校验 → 解析商品映射 → `occupyForScan` → 等级扫码数 +1 → 整单产出 +1 → 商品 `remain_stock`、`max_stock` +1 → 商品日志 / 分类聚合；**开放平台库存推送在事务提交后**执行。

- 失败流水 `bulk_purchase_scan_reject_log` 一律记在**当前作业单**（`expectOrderId`）下，便于按单查扫码明细。
- 非占用人扫码是前置条件错误，直接抛异常，**不写失败流水**。
- 撤销扫码扣库存时把 `remain_stock >= 1` 写进条件，不够扣说明已售出，整次撤销回滚。

### 提交盘点

不信任扫码时的累加值，提交时按 `bulk_purchase_order_rank.scanned_label_num` 重算 `actual_label_num`、`settle_label_num`、`amount`、`total_amount`，作废未扫标签，写 `plan_settle_time = now + 3 天`，`actual > confirm` 时置 `over_flag=1`。最后走带 `SET inventory_by = NULL` 的 SQL 释放占用（MyBatis-Plus 默认不更新 null 字段）。

### 自动定采

`BpConfirmLogic#autoConfirm`：读当前版本报价 → `BpAutoConfirmAlgorithm.allocate(需求含追加, 候选, 5)` → 报价定性（中选 / 未中选）→ 每个中选供应商一张供货单（冻结各等级单价，生成 `JC` 货架码）→ 计划直接 `finishConfirmedDemand` 为已完成；没有中选则关闭（`closeReason=3`）。货架码序号在 Redis `bp:shelf:seq:`，定采按需求加锁 `bp:confirm:lock:`（30 秒）。

算法排序：价格升序 → 提交时间升序 → 供应商 ID 升序（第三级保证同一数据跑两次结果一致）。**永不超采、可能少采**，缺口写进 `traces`。

## 并发控制

| 锁 | Key | 租约 | 用途 |
|----|----|----|----|
| 盘点锁 | `bp:inventory:order:{orderId}` | 120 秒，30 秒续期 | 扫码、撤销、开始 / 释放 / 提交盘点互斥，持有至事务完成 |
| 资金锁 | `bp:money:demand:{demandId}` | 120 秒，30 秒续期 | 改量、处理超量、补偿、结算；按**需求**加锁，因为补偿会跨同需求下多张供货单；admin 与 supplier 共用 |
| 需求任务锁 | `bp:task:demandExpire` | 240 秒 | 多实例下只有一个实例跑需求推进任务 |
| 定采锁 | `bp:confirm:lock:{demandId}` | 30 秒 | 同一需求同时只跑一次定采 |
| 商品映射锁 | `bp:mapping:lock:` | 15 秒 | 防止并发自动建品建出重复商品 |

资金类操作的固定顺序：先 `getById` 拿 `demandId` → 加资金锁 → `getByIdForUpdate` 重读并校验 `demandId` 未变。事务隔离级别 `READ_COMMITTED`，避免拿锁后读到旧快照。

> [!WARNING]
> 锁超过 2 分钟仍在不一定是异常（有续期）。先查是否有正常请求或事务在跑，**不要现场删锁**。

## 定时任务

| 任务 | 所在服务 | 调度 | 每批 | 做什么 |
|----|----|----|----|----|
| `BulkPurchaseDemandExpireJob` | admin | 应用内 `@Scheduled`，每 5 分钟；需 `scheduling.enabled=true` | 200 | ① 报价截止流转 + 自动定采 ② 卡在待定采且**未过采集截止**的重试定采 ③ 历史已确认且到采集截止的计划推成已完成 |
| `BulkPurchaseSettleJob` | supplier-api | SchedulerX（`BaseTaskProcessor`），频率由控制台配置 | 200 | 结算到期且 `over_flag=0` 的单；每轮 warn 打印「已到期但超量未处理」积压数 |
| `BulkPurchaseOrderCancelJob` | supplier-api | SchedulerX | 500 | 取消到产出截止、无产出的待签到 / 待盘点单 |

- 三个步骤各自 try/catch，一步失败不影响其他步。
- 待定采**过了采集截止就不再重试**：此时生成的供货单会立刻被自动取消任务取消。日志 `集采需求卡在待定采且已过采集截止，需人工处理`。
- 自动结算单张独立事务，乐观锁冲突的单留在待结算等下一轮。

## 跨服务手动结算

```text
运营后台 POST /bulkPurchase/order/settle/manual
  └─ BpOrderSettleLogic：校验后台登录态，生成 UUID 凭证
       Redis SET bp:settle:admin-ticket:{uuid} = {orderId, remark, operatorId, operatorName}  EX 60
  └─ Feign POST /internal/bulkPurchase/order/settle/manual  Header X-Bp-Admin-Settle-Ticket: {uuid}
       └─ BulkPurchaseAdminSettleLogic：Lua 原子 GET+DEL 消费凭证（防重放）
            └─ BulkPurchaseSettleServiceImpl#settleManual(..., END_ADMIN)
```

- 操作人来自凭证，supplier **不接受客户端自报的操作人**。
- admin 与 supplier 必须连**同一个 Redis 库**，否则凭证永远「无效或已过期」。
- Feign 异常不打印内容（可能带凭证）。调用超时不代表没结算，先查订单和账变。

### 记账流程

`BulkPurchaseSettleServiceImpl#settle`：

1. 自动路径再挡一次 `over_flag=1`（兜底）。
2. 金额 ≤ 0：直接置已结算、写审计、提交后发通知，不产生账变。
3. 供货方 `increaseAccountMoney`（乐观锁，冲突抛「操作冲突，请刷新后重试」）→ 写 95 账变。
4. 发布主体 `decreaseAccountMoney`，**不校验余额，允许负数** → 写 96 账变（金额为负）。
5. `finishSettle` 带 `status=30 + total_amount` 条件置 40，失败抛异常回滚。
6. 写审计 `settle` / `settleManual`；`afterCommit` 发消息（类型 14，`FACTORY_ORDER_COMPLETED`，只发给供货方）。

## 数据表

| 表 | 用途与关联键 |
|----|----|
| `bulk_purchase_demand`、`bulk_purchase_demand_rank` | 计划与等级配置，`id` / `demand_id` |
| `bulk_purchase_demand_supplier`、`bulk_purchase_white_list` | 发布资格快照（全部供应商用 `supplier_id=0` 哨兵行）与全局白名单 |
| `bulk_purchase_quote`、`bulk_purchase_quote_rank` | 报价版本、当前标志、选中状态、逐等级量价，`quote_id` |
| `bulk_purchase_order`、`bulk_purchase_order_rank` | 供货单、冻结价、打印 / 扫码 / 结算数量；`demand_id`、`quote_id`、`supplier_id` |
| `bulk_purchase_label`、`bulk_purchase_scan_reject_log` | 标签状态、扫码失败原因 |
| `bulk_purchase_over_compensate` | 补偿台账，`order_id`（源单）/ `occupied_order_id`（被占用方） |
| `bulk_purchase_audit_log` | 全链路审计，`biz_type` + `biz_no` |
| `bulk_purchase_product_mapping`、`bulk_purchase_product_log` | 商品映射与商品变更日志 |
| `xm_bulky_product` | 商品库存 `remain_stock`（单位扎）与上下架 |
| `xm_bulky_supplier_account_change` | 账变，`order_sn` = 供货单号，`type IN (95, 96)` |

## 只读核查 SQL

先确认连的是哪个环境，再按单号 / ID 精确查询。以下只提供 SELECT，**不提供生产修改语句**。

### 供货单与等级

```sql
SELECT id, order_no, demand_id, demand_no, quote_id,
       supplier_id, publish_supplier_id, round_no, status,
       confirm_label_num, actual_label_num, settle_label_num, total_amount,
       over_flag, over_handle_time, over_handle_remark,
       inventory_by, inventory_submit_time, output_end_time,
       plan_settle_time, settle_time, settle_flag, settle_type
FROM bulk_purchase_order
WHERE order_no = '替换为完整供货单号';

SELECT order_id, rank_code, price, confirm_label_num,
       printed_label_num, scanned_label_num, settle_label_num, amount
FROM bulk_purchase_order_rank
WHERE order_id = 123456
ORDER BY rank_code;
```

核对顺序：

1. `actual_label_num` = Σ 等级 `scanned_label_num`；
2. `settle_label_num` = Σ 等级 `settle_label_num`；
3. `total_amount` = Σ（等级 `settle_label_num` × `price`）。

被登记补款的目标单，应结算数可能大于扫码数，属正常。

### 标签与扫码失败

```sql
SELECT id, label_code, order_id, rank_code, seq_no, status,
       product_id, scan_by, scan_time, revoke_count, void_time, void_reason
FROM bulk_purchase_label
WHERE order_id = 123456
ORDER BY rank_code, seq_no;

SELECT label_code, order_id, rank_code, reject_reason, fail_msg,
       first_scan_time, operator_name, create_time
FROM bulk_purchase_scan_reject_log
WHERE order_id = 123456
ORDER BY id DESC
LIMIT 100;
```

### 补偿、账变、审计

```sql
SELECT order_id, order_no, occupied_order_id, occupied_order_no,
       occupied_supplier_id, rank_code, occupied_label_num,
       price, compensate_type, compensate_amount, handler_name, create_time
FROM bulk_purchase_over_compensate
WHERE order_id = 123456 OR occupied_order_id = 123456
ORDER BY id;

SELECT supplier_id, type, order_sn, old_money, money, new_money, create_time
FROM xm_bulky_supplier_account_change
WHERE order_sn = '替换为完整供货单号'
  AND type IN (95, 96)
ORDER BY id;

SELECT biz_type, biz_id, biz_no, action, before_value, after_value,
       reason, operator_end, operator_name, create_time
FROM bulk_purchase_audit_log
WHERE biz_no = '替换为完整供货单号'
ORDER BY id;
```

正金额结算应有**一条 95 + 一条 96**。只有单边、同单号多次入账、金额与订单不一致时，保留查询结果和时间线升级，不要自行冲正。

## 日志与审计关键词

按 `traceId`、`demandNo`、`orderNo`、`orderId` 搜对应服务日志。

| 服务 | 日志关键词 |
|----|----|
| admin | `集采需求报价截止流转`、`集采报价截止后自动定采失败`、`集采待定采重试`、`集采需求卡在待定采且已过采集截止`、`集采自动定采完成`、`集采采集兜底完结` |
| supplier | `集采盘点提交`、`集采超量处理`、`集采结算失败`、`集采自动结算完成`、`集采结算余额乐观锁冲突`、`超量未处理，不会自动结算`、`集采供货单取消`、`集采供货单产出截止自动取消完成` |

审计 `action`：`publish`、`appendNum`、`appendSupplier`、`quoteExpire`、`autoConfirm`、`finishCollect`、`submitQuote`、`updateQuote`、`adjustPrice`、`checkIn`、`normal_print`、`reprint`、`revokeScan`、`submitInventory`、`updateSettleNum`、`handleOver`、`overCompensateIn`、`settle`、`settleManual`、`cancel`。

> [!NOTE]
> 扫码成功**不写审计**，以标签表和商品库存为准；失败看 `bulk_purchase_scan_reject_log`。不能靠找成功审计判断是否入库。

## 配置与依赖

| 配置 / 依赖 | 说明 |
|----|----|
| `scheduling.enabled` | admin `bootstrap.properties` 为 `true`，关掉则需求推进任务不跑；supplier 为 `${SUPPLIER_SCHEDULING_ENABLED:true}` |
| `bulk-purchase.factory.supplier-ids` | 运营端包花厂商品管理可见的发布主体范围；各环境值不同，不要照抄 |
| SchedulerX | supplier 自动结算、自动取消任务；核对控制台启用状态、频率、最近执行结果 |
| 消息中心 | 定采采用、结算完成通知，类型 14；30 秒内按 bizType + msgType + bizId + extraJson + 接收人幂等 |
| 开放平台 | 扫码库存变更在事务提交后推送；推送失败不影响本地库存 |

## 联调注意与已知坑

| 项 | 说明 |
|----|----|
| 扫码响应要看内层 | `/wms/bulkPurchase/scan` 被拒时外层 `code=0`，`data.success=false` + `rejectReason`，前端不能只看外层 |
| 打印入参是目标累计张数 | `planNumMap` 每个等级传目标总张数；同一等级重复会被 Map 覆盖，控制器组装前就要校验 |
| 重打状态限制宽 | 只拦已取消；待结算、已结算单也能重打（不新增数量） |
| 定采前调价基本不可达 | `adjustPrice` 要求计划 `status=2`，又要求未过「截止 − 5 秒」；自然截止流转到 2 时截止必然已过，只有「提前终止后定采失败卡在待定采」才可能走通。是否保留该入口待产品确认 |
| 两份 `BulkPurchaseEnum` | admin 与 supplier-core 各一份，`PRINT_MAX_TIMES`、`OVER_*`、`COMPENSATE_*` 等两边都用，改一边要同步另一边 |
| 两份 `BulkPurchaseMoneyLock` | key 都是 `bp:money:demand:`，改实现要两边一致，否则跨端互斥失效 |
| 测试环境定时任务 | 测试服 SchedulerX 不通时，supplier 的自动结算 / 自动取消不会跑，需要手动结算或在测试服另行触发；admin 需求推进是 `@Scheduled`，不受影响 |
| 0 元结算也发通知 | 金额为 0 置已结算、无账变，但仍给供货方发结算完成消息 |
| 处理超量可一步完成 | `handleOver` 可同时传 `rankSettles` + `settleReason`，在一个事务里先改量再处理；只传 `settleReason` 不传等级数量会被拒 |
| 列表与详情超量口径不同 | 运营端列表对「`over_flag=0` 且有处理时间」的单返回 `overNum=0`、`overAmount=0.00`，详情按等级扫码数与定采数保留历史超量差额 |
