Files
docs/二期/二期需求优化版-v2-测量计费入库主流程.md
T
2026-08-07 10:45:12 +08:00

447 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 虫虫出海二期需求优化版 V2:测量、计费、托盘入库闭环
> 形成日期:2026-08-01
> 文档状态:待业务审核
> 依据:二期原型、现有二期需求优化稿及 2026-08-01 新增流程说明
> 本文只定义二期范围和业务契约,不代表功能已经开发完成。
> 主体模型:本文业务数据中的“租户”历史术语与 `tenantId` / `tenant_id` 字段,当前均按客户归属兼容语义理解;产品只有“平台—客户”两级。`tenant_id=0` 是平台身份、RBAC 和平台公共仓储资源技术作用域,客户往来单位、订单、文件和费用使用所属客户 `tenant_id>0`。二期不引入真实租户层。
> 技术实现状态更新(2026-08-04):任务 `019fc6f9-8dcf-74e1-bc68-d42f15d7802a` 已完成核心正常主链路开发,并通过本地测试 MySQL 和隔离端口真实 API 回归。产品/业务待确认项仍以第九章和《二期 V2 需求确认清单》为准。
> 订单衔接规则更新(2026-08-07,已确认):草稿不生成入仓码;订单提交进入待审核时立即生成并固定唯一入仓码。平台审核无需先选择承运商;审核通过后才创建正式入库单并沿用该码,承运商可在后续履约或计费前补充。
## 一、结论
二期主流程应从原来的“人工测量—测量审核—接收审核—人工计费—填写一个库位”调整为:
```text
一期头程订单提交待审核
→ 生成并固定唯一入仓码
→ 平台审核通过(承运商不是审核前置条件)
→ 创建正式入库单并沿用该入仓码
→ 第三方测量设备按入仓码回传长宽高
→ 上传称重图片并由 OCR 识别重量
→ 人工校正 OCR 结果并汇总实际重量
→ 测量数据审核
→ 按客户规则、船司和 FBA 目的仓自动试算
→ 人工确认费用
→ 点击“发送费用给客户”
→ 拆分托盘并分配库位
→ 占用库位、入库成功、费用对客户可见
→ 异步通知客户
```
“发送费用给客户”是二期业务完成节点,也是入库成功节点。原方案中的独立“接收审核”不再作为入库前置节点;如页面需要保留,可改名为“入库确认”,其核心动作就是确认费用并完成托盘、库位占用。
二期不新增微服务:
- 入库单、测量数据、审核、计费试算和费用快照放在头程服务。
- 托盘基础资料、库位及库位占用放在仓库服务。
- 测量图、称重图继续使用文件服务。
- 客户通知通过现有事件桥接服务发送,不在入库事务中同步调用外部消息渠道。
- 权限继续复用 IAM,所有后台按钮做到客户归属、企业、仓库数据范围校验。
## 二、核心业务规则
### 2.1 测量审核失败是否允许入库
保留 Nacos 动态开关,默认关闭:
```yaml
firstleg:
inbound:
allow-rejected-measurement: false
```
建议配置归属:
- Data ID:`firstleg-${spring.profiles.active}.yaml`
- Group:`BAIYUE`
- 默认值:代码和 Nacos 均为 `false`,避免 Nacos 配置缺失时意外放行。
开关语义:
| 配置值 | 规则 |
|---|---|
| `false` | 只有测量审核通过,才能确认费用、发送费用和入库;审核失败必须修正后重新提交。 |
| `true` | 测量审核失败的入库单允许由有“异常放行”权限的人员继续;必须填写放行原因。 |
即使开关开启,也不能让所有失败单自动入库。后端还必须同时校验按钮权限 `firstleg:inbound:exception-release`,记录操作人、原因、审核失败原因、开关当时值和时间。作废单、缺少费用快照或没有可用库位的单据始终不得放行。
### 2.2 第三方长宽高测量
第三方设备只通过入仓码绑定入库单,不接受第三方直接传内部数据库主键。一个入仓码可回传多条箱规测量明细,每条回传必须带第三方事件号以保证幂等。
建议开放接口:
```http
POST /openapi/v1/inbound-measurements
```
请求头:
```text
X-App-Key: 分配给测量服务商的应用标识
X-Timestamp: Unix 毫秒时间戳
X-Nonce: 随机串
X-Signature: HMAC-SHA256 签名
Idempotency-Key: 第三方事件号
```
请求示例:
```json
{
"eventNo": "M202608010001",
"warehouseCode": "CN-YW-01",
"inboundCode": "RK202608010001",
"deviceCode": "DEVICE-01",
"measuredAt": "2026-08-01T10:30:00+08:00",
"items": [
{
"itemNo": "001",
"lengthCm": 50.20,
"widthCm": 40.10,
"heightCm": 30.00,
"quantity": 5,
"sourceImageUrl": null
}
]
}
```
统一响应示例:
```json
{
"code": "0",
"message": "成功",
"data": {
"eventNo": "M202608010001",
"inboundCode": "RK202608010001",
"accepted": true
},
"requestId": "...",
"timestamp": 1785541800000
}
```
接口校验:
- 入仓码必须存在,且属于请求中的仓库和客户归属关系。
- 入库单必须处于待测量或测量被驳回状态;已入库、已作废、费用已发送的数据不得覆盖。
- 长宽高必须大于 0,单位统一为厘米;数量必须为正整数。
- 同一个 `appKey + eventNo` 重复请求返回第一次结果,不重复新增明细。
- 第三方原始报文、签名主体、处理结果和请求时间单独留档,业务表保存标准化后的数据。
- 回调接口只接收测量结果,不直接审核、计费、占用库位或改变入库状态。
建议同时提供查询接口,供设备确认处理结果:
```http
GET /openapi/v1/inbound-measurements/events/{eventNo}
```
### 2.3 OCR 重量识别与人工校正
重量数据来自称重图片,而不是长宽高设备。前端先把图片上传文件中心,再把 `fileId` 提交给头程服务识别,避免业务服务接收和长期保存大文件。
建议接口:
```http
POST /api/platform/inbounds/{inboundId}/weight-images/recognize
PUT /api/platform/inbounds/{inboundId}/weight-results/{resultId}
POST /api/platform/inbounds/{inboundId}/weight-results/confirm
```
识别请求只传文件标识:
```json
{
"fileIds": ["file-001", "file-002"]
}
```
每张图片形成一条结果,至少保存:图片、OCR 原始文本、OCR 原始重量、置信度、修正后重量、修正人、修正原因、确认状态。总实际重量由所有“已确认且有效”的修正后重量累加,禁止前端直接传一个不可追溯的总重量。
规则:
- OCR 结果允许人工修改,但不得覆盖原始识别值。
- 无法识别、置信度过低或单位不明时标记为“待人工确认”。
- 支持公斤和磅,服务端统一换算成 KG,原单位和原值必须保留。
- 同一图片不得重复计重;替换、删除图片必须留下操作记录。
- 测量提交审核前,所有有效图片必须已确认,且总重量大于 0。
### 2.4 FBA 计费范围
二期只支持 FBA 目的仓计费,不做完整财务、账单、收付款、核销和利润模块。
计费匹配顺序:
1. 根据客户归属、业务客户和生效时间选择客户计费规则版本;两类客户含义必须按字段契约区分。
2. 根据起运仓、渠道/计费类型、船司、FBA 目的仓和生效时间匹配船司价格。
3. 计算实测体积、实重和折算量。
4. 按客户规则确定 KG 或 CBM 计费口径,并取实际量与折算量中的较大值。
5. 生成费用试算明细,人工确认后形成不可变费用快照。
包税、按 KG 计费:
```text
体积重(kg) = 实测体积(CBM) × 167
计费重量(kg) = max(实际重量(kg), 体积重(kg))
基础费用 = 计费重量 × KG 单价
```
不包税、按 CBM 计费:
```text
重量折算体积(CBM) = 实际重量(kg) ÷ 363
计费体积(CBM) = max(实测体积(CBM), 重量折算体积(CBM))
基础费用 = 计费体积 × CBM 单价
```
其中 `167`、`363` 只是当前业务给出的默认示例,必须配置在客户规则版本中,不得硬编码。金额还可叠加船司价格表中明确配置的附加费;二期头程/FBA 计费不支持阶梯价、组合优惠和复杂表达式。Warehouse 服务中的海外仓客户级天数阶梯仓储费仅为基础资料 CRUD,当前不参与本流程自动计费。
费用单必须保存以下快照,避免基础资料后续修改影响历史订单:
- 客户规则 ID、版本、计费类型、换算系数和舍入规则。
- 船司价格 ID、版本、船司、起运仓、FBA 目的仓、有效期和币种。
- 实测体积、实际重量、折算量、最终计费量、单价、附加费和总金额。
- 试算人、确认人、确认时间,以及是否异常放行。
无法唯一匹配客户规则或船司价格时,不允许发送费用和入库;返回明确错误,交由运营补充配置。
### 2.5 发送费用与入库成功
后台按钮:`发送费用给客户`。建议权限码:`firstleg:inbound:send-fee`。
点击前必须满足:
- 入库单未作废、未入库、费用未发送。
- 长宽高测量数据完整,重量图片均已确认。
- 测量审核通过;或异常放行开关开启、操作人有权限且填写原因。
- 费用已按当前有效规则重新试算并由操作人确认。
- 已选择足够数量的可用托盘和库位。
成功后一次业务操作应产生以下结果:
1. 锁定费用快照,不再允许原地覆盖。
2. 生成托盘分配明细。
3. 在仓库服务预占并确认库位占用。
4. 入库单状态变为 `INBOUNDED`,记录入库人和入库时间。
5. 费用状态变为 `SENT`,客户侧可只读查看。
6. 写入“费用已发送”领域事件,由事件桥接服务异步通知客户。
客户消息发送失败不能回滚已经完成的实物入库和库位占用。事件通过 Outbox 重试,并在后台显示“通知失败/待重试”;禁止因用户重复点击产生重复费用、重复托盘或重复占位。
### 2.6 托盘拆分与库位占用
托盘数量按总体积和选定托盘可用容积向上取整:
```text
托盘数量 = ceil(入库单实际总体积 ÷ 单托盘可用容积)
```
示例:总体积 `20 CBM`,托盘可用容积 `2 CBM`,需要 `10` 个托盘;默认一个托盘绑定一个库位,因此需要占用 `10` 个库位。若总体积为 `20.1 CBM`,则需要 `11` 个托盘,最后一个托盘保存剩余分配体积。
注意:业务含义应是“10 个托盘分别绑定 10 个库位”,不是“一个托盘同时绑定 10 个库位”。如果实际仓库允许同一库位存多个托盘,后续再增加库位容量模型;二期默认一托一位,以避免占用口径不清。
拆分规则:
- 托盘容积取托盘基础资料快照,历史分配不随托盘资料修改。
- 托盘编号由仓库服务生成,禁止前端自行拼接。
- 后端校验库位属于当前仓库、启用且未占用。
- 多人同时选同一库位时,以数据库唯一约束或乐观锁保证只有一个请求成功。
- 已入库后的换位、释放和重新上架不属于本次主流程;如确有需要,应作为 P1 的库位调整功能单独验收。
## 三、状态模型
建议把“流程状态”“审核状态”“费用状态”“通知状态”分开保存,避免一个状态字段组合爆炸。
### 3.1 入库单流程状态
| 状态 | 说明 |
|---|---|
| `WAIT_MEASURE` | 等待第三方长宽高和称重图片。 |
| `MEASURING` | 已接收部分测量或 OCR 数据,尚未确认完整。 |
| `MEASURE_PENDING` | 测量已提交,等待审核。 |
| `MEASURE_REJECTED` | 审核失败,等待修正;默认不能继续入库。 |
| `WAIT_PRICING` | 测量审核通过,等待计费试算。 |
| `FEE_CONFIRMING` | 已试算,等待人工确认并发送。 |
| `INBOUNDING` | 正在预占托盘和库位,防止重复操作。 |
| `INBOUNDED` | 费用已确认并发送,托盘和库位已占用,入库成功。 |
| `VOIDED` | 已作废。 |
### 3.2 子状态
- 测量审核:`PENDING / APPROVED / REJECTED`。
- 费用:`UNPRICED / CALCULATED / CONFIRMED / SENT / VOIDED`。
- 客户通知:`NOT_CREATED / PENDING / SUCCESS / FAILED`。
## 四、服务与数据边界
| 服务 | 二期职责 | 主要新增数据 |
|---|---|---|
| 头程服务 | 入库单、第三方测量接入、OCR 结果业务管理、测量审核、计费匹配、费用快照、入库编排 | 入库单、测量批次/明细、OCR 重量结果、审核记录、客户规则、船司价格、费用快照、托盘分配引用 |
| 仓库服务 | 托盘资料、库位资料、可用库位查询、预占/确认/释放 | 托盘、库位、库位占用、预占记录 |
| 文件服务 | 称重图片、测量图片和附件 | 文件元数据和对象存储引用 |
| 事件桥接服务 | 客户通知的可靠投递 | Outbox 消费记录、通知结果 |
| IAM | 客户归属、企业、仓库范围和按钮权限 | 新增二期权限资源,不新增业务数据 |
头程与仓库分库,不能依赖跨库事务。采用“仓库库位预占 → 头程确认入库 → 仓库确认占用”的幂等 Saga;补偿只释放仍处于预占状态的库位,已经完成实物入库的记录不得被通知失败反向释放。
## 五、建议新增的核心接口
### 5.1 平台后台接口
```text
GET /api/platform/inbounds
GET /api/platform/inbounds/{id}
POST /api/platform/inbounds/{id}/measurements/submit
POST /api/platform/inbounds/{id}/measurements/approve
POST /api/platform/inbounds/{id}/measurements/reject
POST /api/platform/inbounds/{id}/weight-images/recognize
PUT /api/platform/inbounds/{id}/weight-results/{resultId}
POST /api/platform/inbounds/{id}/weight-results/confirm
POST /api/platform/inbounds/{id}/fees/calculate
POST /api/platform/inbounds/{id}/fees/confirm
POST /api/platform/inbounds/{id}/send-fee-and-inbound
GET /api/platform/inbounds/{id}/pallet-allocations
GET /api/platform/inbounds/{id}/operation-logs
```
### 5.2 仓库内部接口
```text
GET /internal/warehouses/{warehouseId}/locations/available
POST /internal/location-reservations
POST /internal/location-reservations/{reservationNo}/confirm
POST /internal/location-reservations/{reservationNo}/release
```
所有写接口必须支持幂等键;所有查询由后端基于登录上下文注入客户归属和企业范围,禁止单独相信前端传入的 `tenantId`;该参数当前表示目标客户 ID。
## 六、页面范围
### P0 页面
- 入库单列表和详情。
- 入库单测量:第三方数据展示、称重图片、OCR 原值/修正值和总重量。
- 测量数据审核:客户下单、第三方长宽高、OCR 重量三组信息和驳回原因。
- 费用试算与确认:客户规则、船司价格、FBA 目的仓、计费过程和费用快照预览。
- 托盘/库位分配:托盘数量、每托盘体积、候选库位、占用结果。
- 客户订单详情:只读显示已发送费用和入库结果。
- 客户计费规则、船司价格表、托盘管理和库位管理。
### P1 页面/功能
- 通知失败人工重试。
- 入库后库位调整。
- 测量视频。
- 单票导出。
## 七、二期开发任务重排
| ID | 模块 | 交付内容 | 优先级 |
|---|---|---|---|
| P2-01 | 契约、状态与权限 | 状态机、错误码、按钮权限、客户/企业/仓库数据范围 | P0 |
| P2-02 | 入库单 | 列表、详情、入仓码和操作历史 | P0 |
| P2-03 | 第三方测量 | 签名、幂等、长宽高回传、原始报文留档和查询 | P0 |
| P2-04 | OCR 称重 | 图片关联、OCR、人工校正、单位换算、确认和重量汇总 | P0 |
| P2-05 | 测量审核 | 原单/设备/OCR 对比、通过、驳回和异常放行开关 | P0 |
| P2-06 | 客户计费规则 | KG/CBM 类型、换算系数、舍入、生效期和版本 | P0 |
| P2-07 | 船司 FBA 价格 | 起运仓、船司、FBA 目的仓、单价、币种、附加费和有效期 | P0 |
| P2-08 | 自动试算与费用快照 | 规则匹配、取大计算、人工确认、费用发送和历史快照 | P0 |
| P2-09 | 托盘与库位 | 托盘资料、向上拆分、库位可用查询、预占和确认占用 | P0 |
| P2-10 | 入库编排 | 发送费用、入库成功、跨服务 Saga、幂等和失败补偿 | P0 |
| P2-11 | 客户通知与展示 | Outbox 通知、失败重试、客户侧费用和入库结果只读展示 | P0 |
| P2-12 | 文件与审计 | 称重/测量图片鉴权、第三方调用日志和全操作审计 | P0 |
| P2-13 | 回归与发布 | 正常、驳回、放行、并发占位、重复提交、越权和异常恢复 | P0 |
## 八、验收主流程
### 8.1 正常流程
1. 一期订单提交待审核时生成唯一入仓码,审核通过后创建正式入库单并沿用该码。
2. 第三方用入仓码回传多条长宽高,同一事件重复回传不产生重复数据。
3. 操作员上传多张称重图片,OCR 识别后可逐条修正,系统正确累加 KG。
4. 审核员通过测量,系统按客户规则、船司、FBA 目的仓匹配唯一有效价格。
5. 系统按 KG 或 CBM 口径取大计算,页面展示完整计算过程。
6. 操作员确认后点击发送费用,系统生成正确托盘数并占用相同数量的可用库位。
7. 入库单变为已入库,客户能看到已发送费用;通知异步成功。
### 8.2 必须覆盖的异常流程
- 默认开关关闭时,测量审核失败不能计费发送和入库。
- 开关开启但无异常放行权限,仍不能入库;有权限且填写原因时才可放行。
- 无客户规则、无船司价格、价格过期或匹配多条时禁止发送费用。
- OCR 图片重复、单位不明、未确认或总重为零时禁止提交审核。
- 库位数量不足、被并发抢占或已停用时,入库失败且不能留下半完成费用/占用。
- 重复点击发送费用只产生一份费用快照、一组托盘和一次入库结果。
- 客户通知失败时入库状态不回滚,系统自动重试并可查失败原因。
- 跨客户、跨企业、跨仓库查询和操作全部拒绝,并记录安全审计;具体使用 HTTP 状态还是业务码以接口统一响应契约为准。
## 九、仍需业务最终确认的事项
1. 包税是否固定采用 `CBM × 167` 转 KG,不包税是否固定采用 `KG ÷ 363` 转 CBM;不同客户能否配置不同系数。
2. “根据客户规则然后根据船司计算”是只生成客户应收费用,还是同时保存船司成本。本文默认二期只向客户发送应收费用,船司价格作为计价基础,不进入应付和利润闭环。
3. 船司价格唯一匹配所需维度是否就是:起运仓、渠道/类型、船司、FBA 目的仓和有效期。
4. 托盘是否始终一托一库位。本文按一托一位设计;若一个库位可放多个托盘,需要补充库位容积、承重和剩余容量规则。
5. 托盘分配是只按体积平均拆分,还是还要拆到具体 SKU/箱数。本文二期只分配体积和箱数汇总,不做完整 SKU 库存。
6. 点击发送费用前是否还需要第二人复核。本文默认测量审核完成后由有发送权限的操作员确认,不再增加独立接收审核。
7. OCR 服务商、图片格式、单图大小、识别时限和失败重试次数需要在技术接入前确定。
## 十、本次相对上一版的变更
- 第三方长宽高测量从“不纳入”改为二期 P0,并定义按入仓码接入的开放接口。
- OCR 称重从“不纳入”改为二期 P0,支持多图累加和人工修正。
- 人工简单计费改为客户规则加船司 FBA 目的仓自动试算。
- 新增 KG/CBM 换算取大和费用规则/价格快照。
- 新增 Nacos 审核失败异常放行开关,默认关闭。
- 原“接收审核通过即入库”改为“发送费用给客户即入库成功”。
- 原“只填一个库位”改为按托盘容积向上拆分,并一托一位占用库位。
- 托盘管理从“不纳入”改为二期必要基础能力,但不扩展为完整 WMS。
## 十一、实现与验收状态(2026-08-04)
### 11.1 已实现并通过真实 API 验证
- 订单提交待审核时生成唯一入仓码;审核通过后创建正式入库单并沿用该码,审核不强制选择承运商。
- 平台测量草稿、箱规明细、尺寸证据图片绑定。
- 称重图片上传、OCR 失败状态、人工修正、重量确认和多重量汇总。
- 测量提交、托盘拆分、库位预占及确认占用。
- 客服测量审核通过后自动计费、发送费用并完成入库。
- FBA 目的仓计费按实重和体积重取大,本轮 `20kg` 与 `40.08kg` 取 `40.08kg`。
- 入库详情、费用快照、托盘和库位分配结果查询。
- 公共收货仓的公共主数据技术归属与商户订单的客户归属分别校验;这只是当前兼容实现,不形成真实租户层。
### 11.2 最终验证数据
| 项目 | 结果 |
|---|---|
| 入库单 | `2084549769916538881` |
| 入仓码 | `RK-2084549604719718402` |
| 测量批次 | `2084556854154715137` |
| 最终状态 | `INBOUNDED / APPROVED / SENT` |
| 计费 | `40.08kg × 11 CNY/kg = 440.68 CNY` |
| 托盘 / 库位 | `2084561214309851137` / `2084561214452457473` |
| 预占单 | `LR2084562525306675202`,状态 `CONFIRMED` |
### 11.3 已落地配置与数据快照
公共仓主数据技术归属配置(配置名保留历史 `tenant` 命名):
```yaml
firstleg:
inbound:
warehouse-resource-tenant-id: ${WAREHOUSE_RESOURCE_TENANT_ID:0}
```
公共仓库、快递公司、托盘和库位当前统一使用平台技术作用域 `tenant_id=0`,不得绑定客户 `10001` 或其他客户 ID。客户往来单位、订单、文件和费用仍使用所属客户 `tenant_id>0`。托盘分配快照继续保存历史字段 `warehouse_resource_tenant_id` 和 `reservation_no`,其中公共资源归属值应为 `0`,避免配置变化后无法追溯历史资源归属。
### 11.4 尚未关闭的环境验收项
- 接入真实 OCR 供应商凭证后,以现场秤图验证识别准确率、超时和重试。
- 前端浏览器完成登录、权限按钮、403、跨客户拒绝、重复提交、空态和移动端体验验收。
- C1-C8 产品结论仍需产品/业务负责人正式填写,技术实现通过不等于代替产品确认。