统一暗色模式页面/顶栏/Handsontable 选中色,调整下单到仓方式文案与提示,CustomLeftMenu 卡片统一 60px;补充揽收单、问题件处理与 H5 相关页面。 Co-authored-by: Cursor <cursoragent@cursor.com>
21 KiB
揽收单与物流单串联方案
文档状态:产品 + 架构 + 表结构 + 接口(先评审,再开发)
编写日期:2026-08-03
仓库:前端E:\oms0414,后端oms0414-back
关联页面:PC「我的订单」/order/myOrders、下单页/order/placeOrder、揽收管理(新建)、H5 订单 / 发货指引
关联能力:小包创建CreateParcelCoreAsync、客户发件地址簿、订单支付、应收应付
关联文档:docs/oms系统设计-050801.md(§4.4.4 旧揽收)、docs/应收应付、docs/前端页面设计规范.md、docs/H5客户中心设计方案.md
目标读者:产品、前端、后端、运营、测试
0 阅读指引
| 角色 | 优先阅读 |
|---|---|
| 产品 / 运营 | §1~§3、§5、§6、§11 |
| 前端 | §5、§6、§8、§10 |
| 后端 / DBA | §4、§7~§9、§10、§12 |
| 测试 | §3、§7、§11、§13 |
分期对照
| 阶段 | 名称 | 交付物 |
|---|---|---|
| P1 | 主路径打通 | 表结构、创建/列表/详情、我的订单批量创建揽收、下单页到仓方式、对客揽收费支付(基础) |
| P2 | 履约与改绑 | 追加/移除关联、改约/取消、承运商单号、管理端全操作、草稿补承运 |
| P3 | 承运与结算 | 承运商 API 下发、结算批次与应付报表、H5 多选合并 |
本文默认按 P1 → P2 落地;P3 列边界。
旧能力:OrderSource=pickup+POST /order/pickup/create直接落t_order(收件=仓库)退出主路径,见 §12。
1 背景与目标
1.1 现状与问题
| 现状 | 问题 |
|---|---|
无独立揽收实体;t_order.OrderSource=pickup 复用小包创建 |
揽收与跨境物流语义混在一张单上(收件被映射为仓库) |
| H5 / PC 提供「揽收订单」独立入口 | 用户以为是另一品类,与「物流订单」割裂 |
发货指引「叫上门揽收」再调 pickup/create |
另开一张送仓单,与已下跨境单无关联 |
揽收列表有 serviceNo 但不能下钻;订单详情几乎不展示预约 |
无法「一单一号」跟完履约 |
| 无承运商侧应付与结算对象 | 无法管理揽收承运对账 |
1.2 建设目标
- 物流单为主业务单:跨境收件、申报、计费、支付、出库、轨迹仍在
t_order。 - 揽收单为独立履约 / 结算单:自有
pickupNo,可对接承运商、可对客收揽收运费、可对承运商结算。 - 1 揽收 : N 物流:多票货可合并一次上门取件。
- 双入口汇入同一模型:下单时声明到仓方式;我的订单勾选批量「创建揽收」。
- 先物流后挂载:主路径是先有物流单,再创建或挂载揽收单(下单勾选上门时同步建揽收亦可)。
1.3 已拍板决策
| # | 决策 | 口径 |
|---|---|---|
| 1 | 合并约束 | 同一仓库 + 同一取件地址 方可并入同一揽收单 |
| 2 | 改绑时机 | 已支付但未入库 允许改绑/合并;已取件 / 已到仓后禁止 |
| 3 | 揽收运费 | 客户支付(揽收单对客应收);承运商成本在揽收单应付侧结算 |
| 4 | 承运商 | 可后补:允许无承运商落 draft,补齐后再预约/下发 |
1.4 设计原则
- 两本账分离:跨境应收应付仍在
t_order/t_order_fee;上门取件费在t_pickup_order,不拆到各物流单费用行(避免 N 票改绑费用乱飞)。 - 列表可冗余:
t_order.PickupOrderId/InboundMethod方便筛选,真源以揽收单 + 关联表为准。 - 不与仓储
C_parcel混表(见仓储模块设计)。 - 支付失败不阻断建运单:下单同步建揽收失败时,物流单保留,提示客户到「我的订单」补建。
- 默认先付再履约:
draft → scheduled前须运费已确认且(默认)客户已支付揽收费;到付若需要另开PayMode(P2+)。
1.5 非目标(P1 / P2 不做)
- 承运商实时轨迹深度对接(P3)
- 一揽收送多仓、多取件地址拼车
- 把「上门取件」做成销售产品附加服务勾选替代揽收单
- 旧
OrderSource=pickup送仓单自动「升级」改海外收件人 - 改变跨境订单主状态机与现有小包计费主链路
2 概念与命名
| 术语 | 含义 |
|---|---|
| 物流订单 / 运单 | t_order;对外单号多为 ServiceNo |
| 揽收单 | t_pickup_order;独立单号 PickupNo;上门取件履约 + 对客收款 + 承运结算 |
| 到仓方式 | InboundMethod:selfShip 自寄仓 / doorPickup 上门揽收 |
| 挂载 | 物流单通过 t_pickup_order_line 关联到揽收单 |
| 改绑 | 未取件前将物流单从 A 揽收移到 B 揽收,或移出后新建 |
| 揽收运费(对客) | 客户支付的上门取件费,记在揽收单 |
| 承运应付 | 付给揽收承运商的成本,记在揽收单,管理端结算 |
命名注意:对外文案优先用「上门取件 / 揽收单」;避免再让客户以为「揽收订单」是另一种跨境运单。
3 总览
flowchart TB
subgraph create [创建物流]
A[创建物流订单] --> B{到仓方式}
B -->|自寄| C[InboundMethod=selfShip<br/>不建揽收单]
B -->|上门揽收| D[发件地址 + 期望时间<br/>承运可空]
D --> E[落 t_order + 建 t_pickup_order<br/>1 行 line]
end
subgraph batch [我的订单]
F[勾选多票物流单] --> G[创建揽收]
G --> H{同仓且同取件地址?}
H -->|否| I[按行列出拒绝原因]
H -->|是| J[一张 PickupNo + N 行 line]
end
E --> K[t_pickup_order]
J --> K
K --> L[确认运费 → 客户支付]
L --> M[补承运商 → scheduled]
M --> N[取件 → 到仓]
N --> O[承运商结算 Settlement]
N --> P[物流单继续入库/支付跨境费/出库…]
客户心智:多票可共享一个揽收单号上门取件;每票跨境仍各有 serviceNo。
4 数据模型
4.1 揽收单头 t_pickup_order
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | INT PK | 是 | 主键 |
| PickupNo | VARCHAR(32) | 是 | 揽收单号,唯一;建议 PKyyyyMMddHHmmss#### |
| RequestId | VARCHAR(64) | 否 | 幂等键(对齐 t_order.RequestId) |
| CustomerId | INT | 是 | 客户 |
| OrganizationId / SecretLevel | CHAR(36) / TINYINT | 否/是 | 数据权限(对齐 t_order) |
| SenderAddressId | INT | 是 | 发件地址簿 Id |
| Sender* 快照 | 对齐 t_order_sender |
是/视情况 | Name/Company/Country/CountryCode/省市区邮编/地址1-3/门牌/Phone/TelePhone/Email |
| WarehouseId | INT | 是 | 送到哪仓;关联物流单仓必须一致 |
| ConcurrencyStamp | CHAR(36) | 是 | 并发戳(现表公共字段) |
| CarrierCode | VARCHAR(64) | 否 | 承运商编码;draft 可空 |
| CarrierService | VARCHAR(64) | 否 | 承运服务;draft 可空 |
| CarrierWaybillNo | VARCHAR(64) | 否 | 承运商回执运单号 |
| ExpectPickupTime | DATETIME | 是 | 期望揽收时间 |
| PickupWindowStart / End | DATETIME | 否 | 时间窗(P2) |
| ActualPickupTime | DATETIME | 否 | 实际取件时间 |
| Status | VARCHAR(32) | 是 | 见 §4.4 |
| ParcelCount | INT | 否 | 票数;可由 line 汇总 |
| TotalActualWeight / ChargeableWeight | DECIMAL | 否 | 汇总或人工 |
| Currency | VARCHAR(8) | 是 | 默认 CNY |
| FreightAmount | DECIMAL(18,2) | 否 | 对客揽收运费 |
| OtherFeeAmount | DECIMAL(18,2) | 否 | 对客其他费 |
| ReceivableAmount | DECIMAL(18,2) | 否 | 对客应收合计 |
| PayStatus | VARCHAR(16) | 是 | unpaid / paid |
| PayTime | DATETIME | 否 | |
| PayMode | VARCHAR(16) | 否 | 默认 prepaid;预留 collect |
| CarrierPayableAmount | DECIMAL(18,2) | 否 | 应付承运商 |
| SettlementStatus | VARCHAR(16) | 是 | none / pending / settled |
| SettlementBatchNo | VARCHAR(64) | 否 | |
| SettledAt | DATETIME | 否 | |
| Remark | VARCHAR(512) | 否 | |
| IsDeleted | TINYINT | 是 | 软删 |
| CreatedBy / UpdatedBy | 否 | ||
| CreateTime / UpdateTime | DATETIME | 是 |
索引建议:UK(PickupNo)、IX(CustomerId, Status, CreateTime)、IX(WarehouseId, ExpectPickupTime)、IX(SettlementStatus)、IX(PayStatus)。
4.2 关联行 t_pickup_order_line
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | INT PK | |
| PickupOrderId / OrderId | INT | FK |
| ServiceNo / ClientNo | VARCHAR | 运单号/客户单号快照 |
| SortNo | INT | 展示序 |
| ConcurrencyStamp + 审计列 | 对齐现表子表 |
约束:
UK(OrderId):一票物流同时只能挂一个有效揽收(取消关联后可再挂)。UK(PickupOrderId, OrderId)。- 应用层保证同揽收下各
Order.WarehouseId、取件地址与头表一致。
4.3 物流单增量 t_order
| 字段 | 说明 |
|---|---|
| InboundMethod | selfShip | doorPickup | NULL(历史单) |
| PickupOrderId | 冗余 FK;解绑时清空 |
可保留现有 PickupTime / CustomerSenderAddressId 作展示缓存,真源以揽收单为准;P1 写入时与揽收单同步回写便于旧列表列配置过渡。
4.4 状态机
履约 Status
draft 已建单;承运商可空;运费可未确认
scheduled 已预约 / 已下发承运商(须承运已填;默认须已支付)
dispatched 司机已派出(可选,P2)
picked 已取件
inbound 已送达仓库(可与入库扫描联动)
cancelled 已取消
failed 取件失败
合法迁移(P1 最小集):
| 从 | 到 | 条件摘要 |
|---|---|---|
| draft | scheduled | 承运已填、运费已确认、PayStatus=paid(PayMode=prepaid) |
| draft / scheduled | cancelled | 未取件 |
| scheduled | picked | 人工或承运回传 |
| picked | inbound | 到仓确认 / 入库联动 |
| scheduled | failed | 取件失败 |
支付 PayStatus(对客)
unpaid → paid(揽收单维度一笔支付;多票共享不拆单)。
结算 SettlementStatus(对承运)
none → pending(通常 picked/inbound 后)→ settled。
三者正交:例如 Status=picked, PayStatus=paid, SettlementStatus=pending。
5 业务规则
5.1 创建 / 追加挂载校验
全部满足方可写入 line:
- 物流单
CustomerId= 揽收单CustomerId - 物流单未删除、非不可发货终态(实现按现有订单状态枚举收紧)
- 未绑定其他有效揽收(line 唯一;或仅允许从已取消揽收解绑后的单)
WarehouseId与揽收头一致- 取件地址与揽收头一致(比
SenderAddressId,或比快照关键字段) - 货尚未在仓:默认允许
draft / predeclared / pendingAllocation / pendingPayment / pendingShip / pendingOnline等;已received及之后默认拒绝 - 已支付未入库允许改绑/合并(决策 #2)
- 揽收头
Status ∈ {draft, scheduled}才允许追加;picked/inbound/cancelled/failed禁止追加
失败时 API 返回明细数组:{ orderId, serviceNo, reason },前端按行展示。
5.2 改绑 / 移除
- 原揽收
draft|scheduled且未取件 → 可remove-orders/ 移入另一揽收。 - 移除后若 line 数为 0 → 自动将揽收置
cancelled(或保持空单由客服取消,P1 建议自动取消并要求 PayStatus 未支付;若已支付走退款策略见 §7.3)。 picked|inbound→ 拒绝改绑。
5.3 下单页同步建揽收
CreateParcelCore成功。InboundMethod=doorPickup时内部创建揽收单(orderIds=[newId])。- 承运商可空 →
Status=draft。 - 揽收创建失败:不回滚物流单;返回物流成功 + 揽收错误码/文案,引导我的订单补建。
5.4 费用归属
| 费用 | 记在哪 | 谁付 / 谁收 |
|---|---|---|
| 跨境运费等 | t_order / t_order_fee receivable |
客户 → 平台(现有) |
| 上门揽收运费 | t_pickup_order.FreightAmount 等 |
客户 → 平台 |
| 揽收承运成本 | t_pickup_order.CarrierPayableAmount |
平台 → 承运商 |
报价:P1 允许人工录入或简单按重量×单价(配置表可后置);P2 再对接承运商询价。
6 交互方案
6.1 创建物流订单(PC / H5)
在基础信息增加 到仓方式:
- 自己寄到仓库:只读展示仓址;不建揽收。
- 上门揽收:展开
- 发件地址(地址簿,必填)
- 期望揽收时间(必填)
- 承运服务(选填,可后补)
- 提示:「多票合并取件请到我的订单勾选后点创建揽收」
提交成功页 / Toast:
- 运单号
ServiceNo - 若已建揽收:揽收单号
PickupNo(可点详情) - 若揽收失败:明确「运单已创建,揽收未建成功」+ 去补建入口
6.2 我的订单:创建揽收(主合并入口)
- 工具栏按钮 创建揽收(需勾选 ≥1 行;权限如
Order.PickupOrder.Create)。 - 弹窗:
- 前端/后端预校验,不合格行红字原因。
- 合格则填写:发件地址、期望时间、承运(可选)、备注。
- 只读汇总:票数、仓库、运单号列表、预估重量。
- 成功:
揽收单 PKxxx 已创建,共 N 票→ 可跳转揽收详情。 - 列表列:到仓方式、揽收单号(链接)。
- 已绑定行:操作「查看揽收」;未绑定且可挂:可走创建或(P2)「加入已有揽收」。
6.3 物流订单详情
概览增加「到仓 / 揽收」块:
- 到仓方式、揽收单号、承运、期望时间、揽收状态、揽收支付状态
- 操作:查看揽收、去付揽收费(未付时)、改约(未取件)
6.4 揽收单管理(PC 菜单)
建议菜单:订单 → 揽收单(替换或降级原「揽收订单」页语义)。
| 区域 | 内容 |
|---|---|
| 列表 | PickupNo、客户、仓、承运、期望时间、状态、票数、应收、支付、结算 |
| 详情 | 抬头 + 关联物流表(ServiceNo、状态、跳转)+ 费用 + 日志 |
| 操作 | 确认运费、登记支付(或调支付)、补承运、下发、改约、取消、已取件、到仓、录入承运商单号、结算(P2/P3) |
客户门户:只读己方揽收 + 关联运单 + 支付;无结算。
6.5 H5
- 下单:同到仓方式。
- 订单详情:展示挂载揽收摘要;「预约上门」对未挂载单。
- 「我的 → 揽收」:改为揽收单列表(非假物流单);点进详情看关联运单。
- 发货指引:禁止再调旧
pickup/create建第二张物流单;改为对本单创建/查看揽收。
6.6 冲突文案示例
以下运单不能合并:
· SNxxx — 已绑定揽收单 PKyyy
· SNzzz — 仓库不一致
· SNaaa — 已入库
7 支付与结算
7.1 对客支付(揽收运费)
- 管理端或系统确认
FreightAmount/ReceivableAmount。 - 客户对
pickupNo发起支付(复用余额支付能力;接口可POST /order/pickup-order/pay)。 - 成功写
PayStatus=paid、PayTime。 - 默认
PayMode=prepaid:未支付不能scheduled。
与物流待支付并列展示,避免客户只付跨境费误以为取件已安排。
7.2 承运商结算
- 对象:揽收单,不是物流单。
- 流程:取件/到仓后
SettlementStatus=pending→ 对账录入/导入 →settled+SettlementBatchNo。 - 二期进入统一应付报表时,来源类型建议
pickup_carrier。
7.3 取消与退款(P1 口径)
| 场景 | 处理 |
|---|---|
| 未支付取消 | 直接 cancelled |
| 已支付未取件取消 | P1:标记取消 + 退款待办(人工退余额);P2:自动退回客户余额 |
| 已取件 | 不可取消;纠纷走客服 |
8 前端要点
遵循 docs/前端页面设计规范.md:
- 容器
xxx-container、table-toolbar、FormPro / TablePro / ModalPro / HandsontablePro。 - 权限建议:
Order.PickupOrder.Query/Create/Update/Pay/Settle(Settle 仅管理端)。 - 路由建议:
/order/pickupOrder改为揽收单列表(或新 path/order/pickupOrders,旧 path 重定向)。 - 我的订单工具栏「创建揽收」与批量打印等并列,勾选态禁用规则与现有批量操作一致。
9 接口草案
基础路径前缀建议:/order/pickup-order(与旧 /order/pickup/create 区分)。
| 接口 | 方法 | 说明 |
|---|---|---|
| 创建揽收单 | POST | /order/pickup-order/create |
| 追加物流单 | POST | /order/pickup-order/add-orders |
| 移除物流单 | POST | /order/pickup-order/remove-orders |
| 分页列表 | POST | /order/pickup-order/list |
| 详情 | GET | /order/pickup-order/detail?id= |
| 取消 | POST | /order/pickup-order/cancel |
| 改约 | POST | /order/pickup-order/reschedule |
| 确认运费 | POST | /order/pickup-order/confirm-freight |
| 支付 | POST | /order/pickup-order/pay |
| 补承运 / 预约下发 | POST | /order/pickup-order/schedule |
| 标记已取件 | POST | /order/pickup-order/mark-picked |
| 确认到仓 | POST | /order/pickup-order/mark-inbound |
| 结算 | POST | /order/pickup-order/settle(管理端,P2/P3) |
9.1 创建请求示例
{
"senderAddressId": 10,
"warehouseId": 1,
"carrierCode": "",
"carrierService": "",
"expectPickupTime": "2026-08-04 10:00:00",
"orderIds": [1001, 1002, 1003],
"remark": "下午电联"
}
9.2 创建响应要点
{
"id": 1,
"pickupNo": "PK202608031200000001",
"status": "draft",
"payStatus": "unpaid",
"orderCount": 3,
"rejected": []
}
部分失败策略:P1 建议 全部校验通过才创建(原子);若需尽最大成功,须产品另批。
9.3 小包创建扩展字段
POST /order/parcel/create(及下单 UI)增加:
{
"inboundMethod": "doorPickup",
"senderAddressId": 10,
"expectPickupTime": "2026-08-04 10:00:00",
"carrierCode": null,
"carrierService": null
}
服务端在物流创建成功后按需调内部 CreatePickupOrder。
9.4 列表筛选(物流)
POST /order/list 增加:inboundMethod、pickupOrderId、hasPickup(bool)。
详情 OrderOutput 回显:inboundMethod、pickupOrderId、pickupNo(可 join)、揽收摘要可选嵌套。
10 权限与隔离
- 客户会话:强制
CustomerId = effectiveCustomerId(与 §4.4.0 一致)。 - 管理端代客:须显式
CustomerId+ 数据权限。 - 关联表不可仅凭
OrderId越权:改绑前校验订单归属与揽收归属一致。
11 分期与验收
11.1 P1 验收清单
- 表
t_pickup_order/t_pickup_order_line及t_order两字段迁移成功 - 我的订单多选创建揽收:同仓同址成功;冲突按行提示
- 下单选上门揽收:物流单 + draft 揽收;承运可空
- 物流详情可见揽收单号与状态
- 客户可支付揽收运费;未支付不能 schedule(若 P1 含 schedule)
- 发货指引不再创建第二张
OrderSource=pickup物流单 - 旧菜单入口有说明或重定向,不误导新建假送仓单为主路径
11.2 P2 / P3
见 §0 分期表;P3 含承运商 API 与结算报表对接。
12 旧揽收(OrderSource=pickup)处置
| 项 | 口径 |
|---|---|
POST /order/pickup/create |
标记废弃;P1 起主路径停用或仅白名单兼容 |
存量 OrderSource=pickup 单 |
保留流转;可选迁移脚本:生成 t_pickup_order + line(收件为仓的历史单需运营确认是否迁移) |
文档 oms系统设计-050801.md §4.4.4 |
本文生效后以本文为准;旧节加「已由揽收单模型替代」脚注(另改) |
禁止:继续用「建一张收件=仓库的物流单」冒充揽收管理与承运结算对象。
13 测试要点
| 场景 | 期望 |
|---|---|
| 3 票同仓同址合并 | 1 个 PickupNo,3 行 line,订单冗余 PickupOrderId |
| 2 票不同仓 | 整单创建失败,原因含仓库不一致 |
| 已绑定再合并 | 拒绝,提示已绑定 PKxxx |
| 已支付未入库改绑 | 允许;原揽收 line=0 则取消 |
| 已取件改绑 | 拒绝 |
| 下单上门 + 揽收内部失败 | 物流单在,错误可补建 |
| 支付揽收费后 schedule | 成功;未支付 schedule 失败 |
| 客户 A 单不可挂进客户 B 揽收 | 鉴权失败 |
14 SQL 草案文件名
建议后端仓库脚本:
sql/20260803_pickup_order.sql— 建t_pickup_order、t_pickup_order_linesql/20260803_order_inbound_pickup_fields.sql—t_order增加InboundMethod、PickupOrderId
(评审通过后由开发落具体 DDL;本文不代替迁移脚本。)
15 修订记录
| 日期 | 说明 |
|---|---|
| 2026-08-03 | 首版:1 揽收 N 物流、双入口、对客付揽收费、draft 可后补承运;决策四条落地 |