소스 검색

docs(闪送): 多点取送统一契约规格(多取多送、订单级物品、v2创建接口与旧接口退役)

qmj 5 일 전
부모
커밋
25e8e7733c

+ 41 - 0
specs/024-flash-delivery/contracts/api.md

@@ -227,3 +227,44 @@
 
 - `GET /system/flashDelivery/rider/orders` 摘要新增 `userNote`:骑手本人任务页签(`toPickup/delivering/completed/cancelled`)返回订单备注,无需进详情;`newTask` 待抢单卡片按接单前脱敏规则固定为 `null`,与详情接口的备注可见性一致。
 
+
+## 2026-10-14:多点取送 v2 创建契约(新增;旧接口过渡期保留,确认待实施)
+
+### v2 报价与创建
+
+| 方法 | 路径 | 说明 |
+| --- | --- | --- |
+| POST | `/system/flashDelivery/v2/quote` | 统一结构报价:订单级物品 + `pickups[]`(1..N) + `deliveries[]`(1..M) |
+| POST | `/system/flashDelivery/v2/orders` | 按 v2 报价创建订单,服务端复算费用与路线 |
+
+请求结构(要点):取货点与收货点放在同一个 `stops[]` 数组,顺序即用户添加顺序、也是骑手路线顺序,元素以 `type`(`PICKUP`/`DELIVERY`)区分:
+
+```json
+{
+  "stops": [
+    {"type":"PICKUP","name":"李先生","phone":"17788889999","address":"台北市大安區信義路三段56號","addressDetail":"10樓","latitude":25.0330,"longitude":121.5654,"remark":"取 4 盒月餅"},
+    {"type":"PICKUP","name":"王小姐","phone":"17788880000","address":"台北市信義區松高路11號","addressDetail":"1樓門市","latitude":25.0410,"longitude":121.5670,"remark":"取 2 箱牛奶"},
+    {"type":"DELIVERY","name":"陳先生","phone":"17788881111","address":"台北市松山區南京東路五段123巷4弄5號","addressDetail":"3樓","latitude":25.0520,"longitude":121.5760,"remark":"整份交給管理室代收"}
+  ],
+  "packageType":"食品雜貨","quantity":8,"weightRange":"4","specification":null,
+  "deliveryMode":"NOW","payType":"CASH","tipAmount":0
+}
+```
+
+地址模型与 Lalamove 一致:站点只存 `address` 完整地址文本(台湾地址为连续文字,不做城市/区结构化拆分)、可选 `addressDetail`(樓層/室)与必填经纬度;路线与计价以经纬度为准,地址文本用于展示和骑手导航。物品主体 `packageType,quantity,weightRange?,specification?` 只出现一次;`remark` 选填、不超过 200 字;预约时间、支付方式、小费等订单级字段沿用现有契约。服务端校验:至少 1 个 `PICKUP` 与 1 个 `DELIVERY`、取货站必须全部位于收货站之前(先取完再送,交错取送属后续增量),并按 `flash_pickup_stop_limit`/`flash_delivery_stop_limit` 与对应多点开关限制数量;v2 运费计算使用独立的新计价方法,报价路线经点为全部站点,整线 40 公里上限不变。旧单点订单的市/区结构化字段仅随旧接口与旧订单继续存在。
+
+响应要点:报价返回整线距离、时长与费用明细(结构同现有报价);创建返回订单详情,站点数组含每站 `stopId`、状态与备注;收货站按联系人电话固化 `receiverUserId`(可空)。启用收货码时每个收货站返回独立 `deliveryPinCode`,仅订单参与方可见。
+
+### 骑手逐站送达
+
+| 方法 | 路径 | 说明 |
+| --- | --- | --- |
+| POST | `/system/flashDelivery/rider/orders/{id}/stops/{stopId}/deliver` | `{"imageUrls":[...]}` 至少一张;幂等重试返回已确认结果 |
+
+全部收货站确认送达后才推进主状态 DELIVERED 并生成骑手收入账单;任一收货站已送达后订单进入禁止取消状态(用户与平台同口径)。
+
+### 废弃与退役计划
+
+- 自 v2 上线起,`POST /system/flashDelivery/quote`、`POST /system/flashDelivery/orders`(含旧单点 `pickup`、逐站物品 `pickups`、混传校验)及旧送达接口标注废弃:代码 `@Deprecated` + 本契约标注"过渡期保留";行为冻结,不接收新功能,既有线上 App 不受影响。
+- 退役删除为独立收尾批次,前置条件:用户/骑手 App 两端全部切换 v2 并通过真实环境联调;删除范围含旧结构 DTO 与互斥校验、对应测试、本契约废弃段落及 `pickupStopLimit`/`showMultiPickup` 等被 v2 字段取代的首页配置。
+- 数据无迁移:v2 订单与旧订单共用站点表;`remark`、按站收件人与按站收货码仅 v2 新订单写入。

+ 7 - 0
specs/024-flash-delivery/data-model.md

@@ -52,3 +52,10 @@
 - 闪送账单:`user_id=rider_id,user_type=2,type=0,state=0,dd_id=order_no,amount=订单总额,cretim=完成时间,payment_method=pay_type`。抽成、抽成比例、税均为 0;门店、摊位及 wallet_balance 不填写,不写钱包记录。
 - 主订单行锁及成功完成条件更新为幂等入口;用户签收、平台完成和自动完成在同一事务内写状态、日志和账单,任一步失败整体回滚。只生成未来成功完成的订单账单,不扫描补历史账单。
 - 收款状态仍保存在 `flash_delivery_order.payment_status`,账单收入不能推断实际收款;账单列表动态关联本骑手闪送订单,返回主键、小费、收款状态和当前评分。不得以闪送订单号去关联 `pos_order`。
+
+## 2026-10-14 多点取送统一契约数据增量
+
+- `flash_delivery_order_stop` 增加 `remark VARCHAR(200)`(选填备注,骑手了解该点取/送什么);收货站增加可空 `receiver_user_id`(按站匹配的注册收件人)与可空 `delivery_pin_code`(启用收货码时按站生成)。取货站两列为空,历史语义不变。
+- `flash_delivery_order` 增加 `delivery_stop_count`、`delivered_stop_count`,镜像取件侧计数;全部收货站完成才推进 DELIVERED。订单级物品字段恢复为唯一物品来源,站点表不再承载结构化物品;订单级 `delivery_pin_code` 仅对 v2 之前的旧订单继续使用。
+- 字典新增 `flash_delivery_stop_limit`(1..10,缺失或非法回退 1)与 `flash_show_multi_dropoff`(关);与取货侧字典独立配置。
+- 全部 DDL 修订 `updatesql/sql.md` 中尚未执行的多站点增量段落,由开发者手动执行;无历史订单站点迁移、回写或兼容清理。

+ 9 - 0
specs/024-flash-delivery/plan.md

@@ -773,3 +773,12 @@ The staged list must not contain `.claude/homunculus/observations.jsonl`、`.tmp
 按 019 rider-flash-online-plan.md 原地实施。骑手独立凭证、支付/退款表和恢复编排在 019;本规格接单复核收款资格,单点/逐站取件校验可信 PAID,禁止手工确认 LINE 收款。用户与平台取消共用订单锁和真实站点判断;平台列表批量生成 canAdminCancel,详情/按钮/弹窗同口径,缺事实安全拒绝。任一站已取件后不再平台取消,历史交接数据仍可读。本文较早的取件后人工取消/收款描述由本增量覆盖。
 
 取消和退款意图同事务,网络退款在事务外;迟到付款只补偿,不恢复履约。App 不修改,见 ../../docs/flash-delivery-line-pay-app-integration.md。SQL 仅登记,真实资金/数据库并发另行验证,当前实现中、未提交。
+
+## 2026-10-14 多点取送统一契约实施计划(确认待实施)
+
+1. 数据与字典:修订尚未执行的多站点增量 SQL——`flash_delivery_order_stop` 加 `remark VARCHAR(200)`、收货站 `receiver_user_id`(可空)与 `delivery_pin_code`(可空,启用收货码时按站);`flash_delivery_order` 加 `delivery_stop_count`、`delivered_stop_count`;字典新增 `flash_delivery_stop_limit`、`flash_show_multi_dropoff`,初始 1/关。物品主体仍在 `flash_delivery_order` 原有订单级字段,无迁移。
+2. v2 契约:新增 v2 报价/创建 DTO 与服务入口(统一 `stops[]` 站点数组,取/送以 `type` 区分、数组顺序即路线顺序即 `stop_order`,订单级物品+每站备注),校验至少 1 取 1 收、取货站全部位于收货站之前、上限/开关,收件人按站匹配;复用既有路线、运价、订单锁、站点写入与图片机制,不为 v2 复制业务表。
+3. 送达状态机:新增站点级送达确认端点与幂等(镜像取件侧实现),部分送达不推进主状态、末站完成条件更新推进 DELIVERED 并沿用账单/推送编排;任一收货站已送达后复用 FR-111/112 的取消门禁。收货码按站生成校验,订单级旧码仅对旧订单保留。
+4. 展示与推送:平台 PC 站点列表含备注与送达状态;用户/骑手列表摘要首取首收+计数;逐站送达推送对应收件人,沿用既有推送场景框架。
+5. 退役管理:旧报价/创建/送达接口标注 `@Deprecated` 与契约废弃说明,行为冻结;删除为收尾独立批次(App 两端切换并联调通过后执行),删除范围含旧结构分支、逐站物品 DTO、互斥校验、对应测试与文档。
+6. 验证:TDD 覆盖 v2 契约、上限/开关、整线报价、逐站送达幂等与并发、部分送达取消门禁、按站收件人与推送、旧接口过渡行为;JDK 21 定向与全量、模块构建、平台构建。SQL 执行、真实 Google Routes、App 联调另行验收;进度按 未开始/实现中/已提交/已验证 口径报告。

+ 30 - 0
specs/024-flash-delivery/spec.md

@@ -404,3 +404,33 @@
 - FR-113:平台取消按钮依据服务端取消资格展示,不仅依据主状态;缺少多站点事实时不得默认放行,取消接口必须重新校验。保留既有历史处置记录用于审计,不新增取件后异常关闭或客服工单功能;异常由客服人工处理,不自动退款。
 - FR-114:取件前取消的 LINE Pay 已付款订单自动全额退款,未知及迟到付款先核实再补偿;资金状态不与履约 CANCELLED 混同。此支付增量由 specs/019-line-pay/rider-flash-online-spec.md 维护,替代旧规格“不接入付款退款”的范围限制,仅针对新增闪送骑手 LINE Pay,不改变现金/转账收款及外卖支付。
 - FR-115:取件门禁、取消竞态、部分取件判断与退款补偿必须补充原因型注释;测试覆盖用户/平台、单站/多站、历史状态、并发及未知资金结果。当前实现中,本地测试与构建结果以 tasks.md 为准,真实数据库并发和资金联调另行验收。
+
+## 2026-10-14:多点取送统一契约(多取多送、订单级物品与旧创建接口退役,确认待实施)
+
+用户确认:闪送支持多取货点和多收货点;每个取货/收货点设置联系人信息;物品主体信息整单只设置一次、跟随订单;每个取/收货点可填写备注让骑手知道该点取什么或送什么。本节修订 2026-09-14 多取货点节确立的"逐站独立物品"模型:新契约下物品回到订单级,站点只承载联系人、地址与备注。旧报价/创建接口过渡期原样保留,全部功能调整完成且 App 切换并联调通过后统一删除;已实现但 App 未接入的"逐站物品版 pickups"契约由新契约直接取代。
+
+### 用户故事 9:用户发布多取多送订单并由骑手逐站取件与送达(优先级:P1)
+
+用户设置订单级物品主体(类型、数量、重量段、规格,一次填写),逐站填写取货点和收货点的联系人、电话、地址及选填备注,获取整条路线报价后提交;骑手接单后按顺序逐站取件(沿用既有逐站取件规则),全部取货站完成后进入已取件,再按顺序逐站送达确认并上传凭证,全部收货站完成订单才进入已送达。
+
+**优先级原因**:多点取送是闪送对复杂配送场景的核心能力;物品一次设置贴合真实整车配送并简化填单。
+
+**独立测试**:验证订单级物品一次设置、每站联系人与备注独立、取/收站点上限与开关、整线报价含全部经点、逐站送达确认与凭证幂等、部分送达禁止自助取消、末站完成才推进主状态、按站收件人匹配与送达通知、旧接口过渡期行为不变及退役前置条件。
+
+**验收场景**:
+1. **假如**用户在 `stops` 数组按添加顺序填写 2 个取货点和 3 个收货点及订单级物品,**当**请求 v2 报价或创建,**那么**按整条路线(全部经点)计价,站点按添加顺序保存 `stop_order`,备注逐站留存。
+2. **假如**取货点或收货点数量超过字典上限或对应多点开关关闭,**当**请求 v2 报价或创建,**那么**返回国际化错误;既有订单履约和仅追加小费不受影响。
+3. **假如**订单有多个收货站,**当**骑手确认某一站送达,**那么**必须传站点 ID、上传至少一张凭证并幂等重试;全部收货站完成才推进 DELIVERED 并触发完成账单,任一站重复确认不重复写凭证或推进状态。
+4. **假如**任一收货站已送达,**当**用户自助取消,**那么**拒绝并要求平台介入;取消与送达并发时按订单行锁内最新事实判定,先成功者生效。
+5. **假如**收货站电话能匹配注册用户,**当**创建订单,**那么**按站固化收件人并向其推送送达进度;匹配不到则该站不固化,仅保留联系人文字。启用收货码时按收货站生成与校验。
+6. **假如**客户端仍调用旧报价/创建接口,**当**过渡期内请求,**那么**行为与现状一致并返回废弃标注;退役批次在 App 两端切换并联调通过前不得执行。
+
+- FR-116:v2 报价与创建契约只接受统一站点数组 `stops[]`:取货点与收货点放在同一数组,元素以 `type`(`PICKUP`/`DELIVERY`)区分,包含联系人、电话、完整地址文本、可选详细地址(樓層/室)、必填经纬度及选填备注(不超过 200 字);地址采用单一文本字段(如「台北市大安區信義路三段56號10樓」),不做城市/区结构化拆分,路由与计价以经纬度为准(与 Lalamove 地址模型一致);数组顺序即用户添加顺序,也是骑手路线顺序与站点 `stop_order`,类型是取/送的唯一区别。物品主体(类型、数量、重量段、规格)只在订单级设置一次,新契约不存在逐站物品字段。数组必须至少含 1 个取货站与 1 个收货站,且取货站全部位于收货站之前(先取完再送,与设计稿一致;交错取送属后续增量)。
+- FR-117:取货点和收货点数量上限分别走数据字典 `flash_pickup_stop_limit`、`flash_delivery_stop_limit`(有效值 1..10,字典缺失或非法回退 1),多点开关 `flash_show_multi_pickup`、`flash_show_multi_dropoff` 关闭时只能提交一个对应站点;初始上限 1、开关关闭,App 就绪联调后再放开。
+- FR-118:v2 运费计算使用独立的新计价方法(不挂接旧报价逻辑),整条路线距离与车型运价规则复用底层实现,车型按订单级重量段判定;旧报价计算方法随 FR-122 退役批次删除;报价路线经点为全部取货站与收货站,整线仍不得超过 40 公里,不新增每站点附加费。
+- FR-119:每个收货站按联系人电话匹配注册普通用户并按站固化收件人,匹配不到不固化且不影响创建;送达推送、取件码/收货码及详情可见性按站判定。取货站仍以收货站电话匹配的收件人为准,不存在按取货站认领。
+- FR-120:送达阶段沿用取件侧状态机规则:主状态 PICKED_UP 后骑手按顺序或经下单人同意调整后逐站确认送达,站点级幂等、凭证入 `flash_delivery_order_image`(`stop_id` 指向收货站);全部收货站完成才推进 DELIVERED,仅推进一次。任一收货站已送达后用户与平台均不能取消(镜像 FR-111/FR-112),异常由客服人工处理。
+- FR-121:逐站送达触发对应收件人推送;启用收货码时每收货站独立生成、校验,不再使用订单级单码。骑手收入账单仍按整单一次记录,不按站点拆分。
+- FR-122:新增 `POST /system/flashDelivery/v2/quote` 与 `POST /system/flashDelivery/v2/orders`;骑手新增 `POST /system/flashDelivery/rider/orders/{id}/stops/{stopId}/deliver`。旧报价/创建接口及旧送达接口自 v2 上线起在契约与代码中标注废弃,过渡期行为不变、不接收新功能;删除为独立收尾批次,前置条件是用户/骑手 App 两端已全部切换 v2 并通过真实环境联调,删除时同步更新契约文档与测试。
+- FR-123:`flash_delivery_order_stop` 增加备注与按站收件人字段,`flash_delivery_order` 增加收货站计数与已送达计数;待执行 SQL 直接修订(多站点增量 SQL 尚未在任何环境执行),无历史订单迁移、不回写旧单。
+- FR-124:平台 PC 与用户/骑手列表摘要以首取站、首收站及站点数展示,详情返回全部站点(含备注与送达状态);响应仍不暴露非参与方的联系人精确信息与内部计价审计。

+ 11 - 0
specs/024-flash-delivery/tasks.md

@@ -247,3 +247,14 @@ Phase 24 本地验证:最终后端 164 类/1140 项测试、0 失败/错误/
 
 - [x] T122 新增 `GET /system/flashDelivery/rider/orderCount`,一次返回闪送订单 newTask/toPickup/delivering 三个页签计数;复用 riderOrdersQuery 同口径查询(车型、支付方式、时间窗与可选距离上限),未开通闪送的骑手 newTask 计 0。
 - [x] T123 新增 FlashDeliveryRiderOrderCountView 与契约/服务定向测试;JDK 21 定向测试通过。App 联调未执行。
+
+## Phase 26:多点取送统一契约与旧创建接口退役(2026-10-14,确认待实施)
+
+用户确认:多取货点+多收货点;每站联系人信息与选填备注;物品主体整单一次设置随订单;使用新的创建/报价接口,旧接口过渡期保留、全部调整完成且 App 切换联调通过后统一删除。本阶段修订 2026-09-14 节的逐站物品模型。
+
+- [ ] T124 修订待执行 SQL 与字典:`flash_delivery_order_stop` 加 `remark`、收货站 `receiver_user_id`、按站 `delivery_pin_code`;`flash_delivery_order` 加 `delivery_stop_count`、`delivered_stop_count`;新增 `flash_delivery_stop_limit`(1..10)、`flash_show_multi_dropoff`,初始 1/关。
+- [ ] T125 v2 报价与创建:统一 `stops[]` 站点数组(`type` 区分取/送、顺序即路线顺序)、订单级物品+每站备注;校验至少 1 取 1 收且取前收后、上限/开关、整线经点报价、按站收件人匹配;补契约与服务定向测试(RED→GREEN)。
+- [ ] T126 逐站送达状态机:站点级送达确认端点、凭证与幂等重试、部分送达禁自助取消(用户与平台)、末站推进 DELIVERED 一次;收货码按站生成校验;补并发与门禁测试。
+- [ ] T127 展示与推送:平台 PC 站点列表(备注/送达状态)、用户/骑手列表摘要首取首收+计数、逐站送达推送对应收件人;四语文案同步。
+- [ ] T128 退役管理:旧报价/创建/送达接口代码与契约标注废弃、行为冻结;在 tasks.md 记录删除批次清单及前置条件(App 两端切 v2 且真实联调通过),本阶段不删除。
+- [ ] T129 统一验证:JDK 21 定向测试、后端全量与模块构建、平台构建、差异与 CRLF 核对;记录 SQL 未执行、App 未联调等未验证项,按四种进度状态报告。