plan.md 72 KB

实施计划:闪送配送服务

分支:test | 日期:2026-08-31 | 规格:spec.md

摘要

在既有独立闪送模块上补充包裹类别/四档重量范围、立即或预约配送、可选 PIN 交付、寄件图片、费用明细、页面状态分组、骑手公开摘要和角色安全响应。服务端继续负责路线报价、骑手原子抢单、取件/送达图片、用户签收、24 小时自动完成和平台介入;2026-09-30 补充完成后骑手收入账单(仅记账)。支付网关、退款、结算、钱包入账与自动派单仍不在本期范围。

技术上下文

  • 语言/版本:Java 21
  • 主要依赖:Spring Boot、Spring MVC、Spring Security、MyBatis/MyBatis-Plus、Apache HttpClient 4、fastjson2
  • 存储:MySQL;迁移仅记录在 updatesql/sql.md
  • 测试:JUnit 5、Mockito、Spring MockMvc、MyBatis XML 解析测试
  • 目标平台:现有 RuoYi 多模块 REST API 服务端
  • 性能目标:列表分页;抢单和状态流转单条条件更新;地图超时后立即降级
  • 约束:保持 ruoyi-admin -> ruoyi-system;Controller 不接收 Map;App 身份只从 token 解析;业务错误全部 i18n;不执行数据库迁移
  • 范围:24 个闪送/地址端点、4 张新表、1 张地址表增量、五套语言资源、定向自动化测试

规范检查

  • 独立 flash_delivery_* 模型,不复用 pos_order/taxi_order
  • HTTP 集成只放在 ruoyi-admin
  • Controller 使用显式 DTO 和参数注解
  • 用户与骑手接口通过 token 取身份,平台接口使用明确权限
  • 金额、距离和状态由服务端计算与校验
  • 地址、订单和凭证执行所有权/角色校验
  • SQL 只写 updatesql/sql.md
  • 支付能力明确延期

源码结构

ruoyi-system/src/main/java/com/ruoyi/system/
├── domain/flash/                 # 订单、计价、图片、日志实体
└── mapper/flash/                 # 持久化、原子抢单、条件状态流转
ruoyi-system/src/main/resources/mapper/flash/*.xml

ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/
├── controller/                   # 用户、骑手、平台 Controller
├── dto/                          # 明确请求 DTO 与响应视图
├── service/                      # 报价、状态机、权限和脱敏
├── route/                        # Google Routes 与直线降级
└── task/                         # 送达 24 小时自动完成

ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/
ruoyi-system/src/test/java/com/ruoyi/system/mapper/flash/

地址簿沿用 InfoAddress、IInfoAddressService 和 InfoAddressController,增加请求 DTO、置顶字段、按用户条件查询/更新及所有权测试。

核心实现决策

  1. FlashDeliveryApplicationService 是事务边界;报价只读,创建和每个状态操作同时写订单与日志。
  2. 抢单执行带 status='WAITING_ACCEPTANCE' AND rider_id IS NULL 的单条 UPDATE;受影响行数不是 1 即已被抢走。
  3. 其余状态变更携带订单归属、操作者归属和预期状态条件,阻止并发覆盖;version 字段随每次更新递增,仅作审计参考,不作为更新条件。
  4. (user_id, client_request_id) 唯一键保证创建幂等;冲突时返回原订单。
  5. Google Routes API 字段掩码仅取距离与时长;异常、非 2xx、空路线或无效距离降级为 Haversine。
  6. 待抢单使用专用脱敏视图;抢单后只有订单骑手可见完整电话和详细地址。
  7. 寄件图片在创建事务中写入;取件/送达图片在状态更新事务中写入。三类凭证均校验 1 至 9 个 HTTP(S) URL,取件和送达不得为空。
  8. 预约订单沿用 WAITING_ACCEPTANCE,可抢查询和原子抢单 SQL 同时限制 scheduled_pickup_start_at <= now,不增加仅为调度使用的新状态。
  9. PIN 默认启用并由服务端生成四位数字;用户详情可见,骑手响应始终剔除,送达事务先校验 PIN 再写凭证和状态。
  10. 用户/骑手使用业务视图,平台详情保留完整审计实体;待抢视图只增加类别、重量范围、配送时段、预计时长和市/区级路线信息。
  11. 自动任务扫描送达满 24 小时订单并逐条条件完成,和用户/平台并发时只允许一次成功。

补充原型增量实施批次

  1. 更新规格、API、数据模型和 updatesql/sql.md,明确旧表增量字段及索引。
  2. 先扩展计价器、Mapper XML、应用服务和 Controller 契约测试并确认红灯。
  3. 增加请求/响应 DTO、实体字段、计价明细与角色安全映射。
  4. 实现预约可抢条件、PIN 校验、寄件凭证和状态分组分页。
  5. 使用 JDK 21 运行闪送定向测试、MyBatis XML 测试、模块构建与差异检查。

验证策略

测试先行:先写测试并确认因实现缺失而失败,再添加最小生产代码。最终统一执行:

$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
mvn -pl ruoyi-system -am -Dtest=FlashDeliveryMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl ruoyi-admin -am -Dtest='FlashDelivery*Test,InfoAddressControllerSecurityTest' -Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl ruoyi-admin -am -DskipTests package
git diff --check

复杂度跟踪

无规范违例。四张新表分别承担订单快照、可变计价、图片凭证和审计日志;应用层留在 admin 是外部 HTTP 依赖和模块边界共同决定的。

平台管理前端增量(2026-09-01)

目标与架构

目标:在 foodie-admin-vue 增加可直接运营的闪送价格配置和订单管理入口,完整消费现有六个平台 API,不修改后端接口语义。

架构:继续使用若依动态菜单加载两个 Vue 2 + Element UI 页面。API 请求集中在一个模块,状态/服务类型映射、分页归一化、操作可见性和价格校验集中在可直接由 Node 测试的纯函数模块;订单详情拆为独立展示组件,避免列表页面同时承担全部渲染职责。

技术栈:Vue 2.6、Element UI 2.15、Vue i18n 8、若依 request、动态 sys_menu、Node 内置 node:test。

全局约束

  • 不新建分支或 worktree,后端与平台前端均在当前 test 分支工作。
  • 不增加 npm 依赖,不引入新的 UI 或状态管理框架。
  • 所有新增用户可见文本使用 flashDelivery.*,并同步简中、繁中、英文、越南文。
  • 前端文件保留 CRLF;只修改本功能文件,不格式化既有大文件。
  • 菜单和权限 SQL 只追加到 foodie_server/updatesql/sql.md,不直接执行数据库变更。
  • 后端分页响应读取 data.records 和 data.total,不得套用若依传统 rows/total 响应。
  • 操作成功后重新读取服务端数据;操作失败不提前修改本地订单状态或价格。

文件结构

foodie-admin-vue/
├── package.json                                      # 增加闪送定向契约测试命令
├── tests/flash-delivery.test.cjs                    # 请求配置、页面规则和四语言真实行为
└── src/
    ├── api/flashDelivery/contracts.js               # 可执行的六个平台请求配置构造器
    ├── api/flashDelivery/index.js                   # 调用若依 request 的六个平台接口
    ├── views/flashDelivery/shared.js                # 枚举、分页、价格校验、操作可见性
    ├── views/flashDelivery/pricing/index.vue        # 三种服务价格列表与编辑弹窗
    ├── views/flashDelivery/orders/index.vue         # 筛选、分页、取消和完成
    ├── views/flashDelivery/orders/OrderDetail.vue   # 完整详情、图片和日志
    └── api/language/language.{zh_CN,zh_TW,en_US,vi}.js

foodie_server/
├── specs/024-flash-delivery/{spec,design,plan,tasks}.md
└── updatesql/sql.md                                 # 父菜单、两个页面及六项权限归位

接口和页面数据流

  1. GET /system/flashDelivery/admin/pricing 返回数组;价格页只显示服务端实际存在的配置,不创建前端默认价格。
  2. PUT /system/flashDelivery/admin/pricing/{serviceType} 提交七个完整字段。弹窗先执行纯函数校验,成功响应覆盖列表对应行。
  3. GET /system/flashDelivery/admin/orders 原样提交 pageNum,pageSize,status,serviceType,orderNo,userId,riderId;normalizePage 从 response.data 提取 records,total,current,size。
  4. GET /system/flashDelivery/admin/orders/{id} 返回 {order,images,logs};打开详情前先清空旧数据,失败时保持弹窗关闭。
  5. POST .../{id}/cancel 提交 {reason},只对非 COMPLETED/CANCELLED 状态显示并要求二次确认。
  6. POST .../{id}/complete 无请求体,只对 DELIVERED 状态显示并要求二次确认。

交互设计

  • 价格页使用紧凑表格展示三种服务和版本信息;“编辑”打开宽度 640px 的表单弹窗,金额两位小数、距离单位为米、倍率最小为 1。
  • 订单页顶部使用可折叠行内筛选,表格展示订单号、服务、状态、用户、骑手、距离、金额和创建时间。
  • 详情使用 960px 弹窗,按“订单概况、取送地址、费用与路线、履约与 PIN、图片凭证、状态日志”分区;图片支持预览,空集合显示空状态。
  • 状态使用 Element UI 标签,同时显示本地化文字,避免只靠颜色传达。
  • 表格设置固定操作列和横向滚动容器;页面自身不产生横向溢出。

错误处理

  • 列表和价格请求使用 finally 关闭 loading;后端统一错误拦截负责消息提示。
  • 价格校验失败由表单显示本地化字段错误,不发送请求。
  • 详情请求开始时清空旧详情,请求失败不打开弹窗。
  • 取消原因去除首尾空白后必须非空;取消/完成成功关闭操作层并刷新列表,若详情仍打开则重新读取详情。

测试驱动批次

  1. 先创建 tests/flash-delivery.test.cjs 和 npm 命令,直接执行请求配置构造器、分页、价格校验、状态操作与四语言对象;运行后必须因目标模块不存在而失败。
  2. 实现 API 请求配置、API 封装和 shared.js,使请求、分页、价格校验和状态规则测试转绿。
  3. 实现价格页面与四语言资源,使价格字段和语言一致性测试转绿。
  4. 实现订单页面、详情组件和介入操作;页面调用已经通过测试的状态操作纯函数,权限指令通过 ESLint、构建和页面检查验证。
  5. 追加幂等菜单 SQL,再统一执行定向测试、ESLint、生产构建和两个仓库差异检查。

验证命令

Set-Location E:\QtwCode\foodie\foodie-admin-vue
npm run test:flash-delivery
npx eslint src/api/flashDelivery/index.js src/views/flashDelivery tests/flash-delivery.test.cjs
npm run build:prod
git diff --check

Set-Location E:\QtwCode\foodie\foodie_server
git diff --check

外卖同规则时段运价增量(2026-09-01)

本节替代上方“平台管理前端增量”中的单行价格配置接口、七字段表单和两位小数计价描述;订单管理与平台介入设计继续有效。

目标与架构

目标:将闪送计价改为与现有外卖订单一致的“当前时段 + 起送距离/价格 + 计价距离/金额”规则,移除最低价、时长费、尖峰倍率和启停状态,并让平台在独立闪送入口维护三种服务的多个运价时段。

架构:flash_delivery_pricing 继续作为闪送独立配置表,但从每服务单行改为每服务多时段;flash_delivery_pricing_lock 为三种服务各保存一行事务锁。FlashDeliveryPricingMapper 在数据库当前时间下返回全部匹配时段、提供重叠计数并用 SELECT ... FOR UPDATE 串行化同服务写入;应用服务要求当前时段唯一,异常多行时显式失败。FlashDeliveryPricingCalculator 只负责距离边界和整数 TWD 计算;应用服务负责 CRUD、版本、订单快照和业务错误。平台前端复用现有价格页与权限,改为时段新增、修改和删除。

技术栈:JDK 21、Spring Boot、MyBatis-Plus/MyBatis、MySQL、JUnit 5/Mockito;Vue 2.6、Element UI 2.15、Vue i18n 8、Node node:test。

全局约束

  • 不新建分支或 worktree,后端与平台前端都在当前 test 分支工作。
  • 不直接执行数据库迁移;删除测试阶段旧计价数据和字段的 SQL 只追加到 updatesql/sql.md。
  • 路线预计时长继续保存和展示,但不得参与计价。
  • 起送价格、计价金额、里程费用和最终金额均为整数 TWD;计算结果使用 RoundingMode.HALF_UP 取整到 1 元,不做千位特殊取整。
  • 同一服务类型的时段使用开始包含、结束不包含语义且不得重叠;写操作先取得服务类型数据库行锁再查重,保存即生效,删除即失效,不提供 enabled。
  • Controller 使用明确 DTO 和显式注解,平台配置接口继续使用 flash:pricing:list/edit。
  • 前端新增可见文本同步简中、繁中、英文和越南文,文件保留 CRLF,不增加 npm 依赖。
  • 只修改闪送功能所需文件,不触碰工作区原有脏文件。

文件职责

foodie_server/
├── ruoyi-system/src/main/java/com/ruoyi/system/domain/flash/
│   ├── FlashDeliveryPricing.java          # 时段运价实体
│   └── FlashDeliveryOrder.java            # 整数金额与运价快照
├── ruoyi-system/src/main/java/com/ruoyi/system/mapper/flash/
│   └── FlashDeliveryPricingMapper.java    # 当前时段与重叠查询
├── ruoyi-system/src/main/resources/mapper/flash/
│   └── FlashDeliveryOrderMapper.xml       # 新订单快照字段映射
├── ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/
│   ├── controller/FlashDeliveryAdminController.java
│   ├── dto/FlashDeliveryPricingRequest.java
│   ├── dto/FlashDeliveryPriceBreakdown.java
│   ├── dto/FlashDeliveryQuoteView.java
│   ├── dto/FlashDeliveryServiceView.java
│   └── service/{FlashDeliveryApplicationService,FlashDeliveryPricingCalculator}.java
├── ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/
│   ├── controller/FlashDeliveryControllerContractTest.java
│   └── service/{FlashDeliveryApplicationServiceTest,FlashDeliveryPricingCalculatorTest}.java
├── ruoyi-admin/src/main/resources/i18n/messages_{zh_CN,zh_TW,en_US,vi}.properties
└── updatesql/sql.md

foodie-admin-vue/
├── src/api/flashDelivery/{contracts,index}.js
├── src/views/flashDelivery/shared.js
├── src/views/flashDelivery/pricing/index.vue
├── src/views/flashDelivery/orders/OrderDetail.vue
├── src/api/language/language.{zh_CN,zh_TW,en_US,vi}.js
└── tests/flash-delivery.test.cjs

后端接口与数据流

  1. FlashDeliveryPricing 使用 startTime,endTime,startingDistance,startingFare,distance,freight;距离为正十进制公里,金额为正 Long。
  2. selectCurrent(serviceType) 使用数据库 CURTIME() 和 start_time <= CURTIME() AND end_time > CURTIME() 返回全部匹配时段;找不到时返回无可用运价错误,超过一条时返回配置重叠错误,不用 LIMIT 1 掩盖异常。
  3. countOverlaps(serviceType,startTime,endTime,excludedId) 使用 start_time < endTime AND end_time > startTime 检测重叠。新增、修改和删除在事务中先锁定 flash_delivery_pricing_lock 对应服务行;跨服务修改按字典序锁定旧、新服务,再校验并写入。
  4. 计价器签名改为 calculateBreakdown(FlashDeliveryPricing pricing, int distanceMeters)。超出公里小于 0.5 时计费公里为 0,小于 1 时为 1,否则使用实际超出公里;distanceFee 四舍五入为 Long,amount=startingFare+distanceFee。
  5. GET /admin/pricing 返回全部时段;POST /admin/pricing 新增;PUT /admin/pricing/{id} 按版本更新;DELETE /admin/pricing/{id} 删除。新增、修改和删除都使用 flash:pricing:edit。
  6. home 对三种服务分别读取当前时段,只返回有配置的服务;quote/create 每次重新读取当前时段并计算。订单固化 pricingId,pricingVersion,pricingStartTime,pricingEndTime,startingDistance,startingFare,distance,freight,distanceFee。
  7. 报价与公开服务 DTO 移除旧计价字段,保留 estimatedDurationSeconds;金额 DTO 和订单实体改为整数 Long。

2026-09-17 基础配送费口径调整

baseDeliveryFee 统一表示起步价,distanceFee 保持独立距离附加费;不增加配送小计字段。服务端计算加急费时仍使用起步价与距离附加费之和,订单总额为基础配送费、距离附加费、加急费与小费之和。既有订单快照不回写。

2026-09-17 路线降级预计时长

Google Routes 无路线、异常或不可用时,FlashDeliveryRouteService 继续以 Haversine 直线距离并写入 distanceSource=STRAIGHT_LINE。同时按订单车型生成 estimatedDurationSeconds:机车按 25 km/h,轿车按 30 km/h,结果向上取整且最少 60 秒。该值仅用于履约展示,不参与报价或订单金额计算;不新增来源字段,既有 distanceSource 即可标识降级估算。

平台前端交互

  • 价格页表格按服务类型、开始时间排序,列出起止时间、起送距离/价格、计价距离/金额、版本和更新时间。
  • “新增时段”打开空表单;“编辑”回填完整配置;“删除”二次确认后请求服务端并刷新列表。
  • 表单校验开始时间早于结束时间、距离为正数、金额为正整数,并用当前列表预检同服务时段重叠;服务端仍执行最终重叠校验。
  • 页面不显示启停、最低价、每分钟价格或尖峰倍率。配置保存后立即生效,无匹配时段时由后端阻止报价。
  • 订单详情展示匹配时段、起送距离/价格、计价距离/金额、里程费用和整数总额;预计时长仍显示在路线信息中。

TDD 批次

  1. 先改 FlashDeliveryPricingCalculatorTest,覆盖起送内、超出 0.49/0.50/0.99/1.20 公里、非 1 公里计价距离、.5 金额四舍五入及非法配置;确认旧实现编译或断言失败。
  2. 再改应用服务和 Controller 契约测试,覆盖当前时段读取、无时段、重叠新增/修改、版本冲突、删除、整数快照和新路由;确认旧接口失败。
  3. 实现实体、并发锁表、Mapper、DTO、计算器、应用服务、Controller 和四语言后端错误,使定向后端测试转绿。
  4. 先改 Node 测试,要求新增/修改/删除请求、六字段载荷、时段/整数校验、页面权限和四语言新 key,并确认旧前端失败。
  5. 实现前端请求、纯函数、价格页、订单详情与四语言,使 Node 测试转绿。
  6. 追加开发阶段重建 SQL,删除旧闪送测试表并创建运价锁、时段配置及新订单快照最终结构;不执行 SQL。

验证命令

Set-Location E:\QtwCode\foodie\foodie_server
$env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryPricingCalculatorTest,FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest,FlashDeliveryI18nContractTest' -Dsurefire.failIfNoSpecifiedTests=false test
mvn -pl ruoyi-admin -am -DskipTests package
git diff --check

Set-Location E:\QtwCode\foodie\foodie-admin-vue
npm run test:flash-delivery
npx eslint --no-ignore src/api/flashDelivery/index.js src/api/flashDelivery/contracts.js src/views/flashDelivery/shared.js src/views/flashDelivery/pricing/index.vue src/views/flashDelivery/orders/index.vue src/views/flashDelivery/orders/OrderDetail.vue tests/flash-delivery.test.cjs
npm run build:prod
git diff --check

闪送 App 订单列表与详情精简 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 将用户端和骑手端闪送订单接口收敛为与页面一致的轻量列表与直接详情响应,并让骑手列表复用外卖订单的分页、页签和位置参数。

Architecture: Controller 只暴露新的显式查询参数;FlashDeliveryApplicationService 在数据库分页前完成用户归属、骑手页签、预约可抢和附近距离条件。列表分别使用用户/骑手卡片 DTO,App 详情使用继承订单业务字段的直接 DTO;平台审计详情继续独立保留实体、图片和日志。

Tech Stack: JDK 21、Spring Boot、MyBatis-Plus、MySQL、JUnit 5、Mockito、Lombok。

Global Constraints

  • 不新建分支或 worktree,在当前 test 分支连续实施。
  • 不保留开发测试阶段的 /orders/available、/orders/mine、scene、status、serviceType 兼容入口。
  • 用户和骑手身份只从 @RequestHeader String token 解析;查询参数全部显式使用 @RequestParam。
  • App 列表和详情禁止返回实体、幂等号、内部用户 ID、计价审计字段或原始状态日志。
  • 骑手接单前显示完整取送文字地址,但不返回联系人、电话、实际 PIN、精确坐标或履约图片。
  • 后端业务错误同步 messages.properties、messages_zh_CN.properties、messages_zh_TW.properties、messages_en_US.properties 和 messages_vi.properties。
  • 不执行数据库迁移;本次接口精简不需要表结构变更。

Task 1: 固定 Controller 与 DTO 契约

Files:

  • Modify: ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryControllerContractTest.java
  • Delete: ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderViewTest.java
  • Create: ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListViewTest.java
  • Modify: ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderViewTest.java
  • Create: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryUserOrderListView.java
  • Create: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListView.java
  • Create: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderPageView.java
  • Modify: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderDetailView.java
  • Delete: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderView.java
  • Delete: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryImageView.java
  • Delete: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryLogView.java

Interfaces:

  • Produces: FlashDeliveryUserOrderListView for user cards.
  • Produces: FlashDeliveryRiderOrderListView for all rider tabs.
  • Produces: FlashDeliveryRiderOrderPageView(records,total,current,size,nearbyTaskCount,highestOrderAmount).
  • Produces: direct FlashDeliveryOrderDetailView extends FlashDeliveryOrderView with senderImageUrls,pickupImageUrls,deliveryImageUrls,rider,deliveryPinCode.

  • [ ] Step 1: Write failing reflection and whitelist tests

    Method userOrders = FlashDeliveryUserController.class.getMethod(
        "orders", String.class, int.class, int.class);
    Method riderOrders = FlashDeliveryRiderController.class.getMethod(
        "orders", String.class, int.class, int.class, String.class,
        BigDecimal.class, BigDecimal.class);
    assertThrows(NoSuchFieldException.class,
        () -> FlashDeliveryOrderDetailView.class.getDeclaredField("order"));
    assertTrue(riderFields.containsAll(Set.of("pickupAddress", "deliveryAddress",
        "pickupDistanceMeters", "pinRequired")));
    
  • [ ] Step 2: Run tests and verify the old contract fails

    $env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
    $env:PATH="$env:JAVA_HOME\bin;$env:PATH"
    mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test
    

Expected: compilation or reflection failure because the old controllers and wrapper DTO still exist.

  • [ ] Step 3: Add the minimal DTO types

    @Data
    public class FlashDeliveryOrderDetailView extends FlashDeliveryOrderView {
    private List<String> senderImageUrls = List.of();
    private List<String> pickupImageUrls = List.of();
    private List<String> deliveryImageUrls = List.of();
    private FlashDeliveryRiderSummaryView rider;
    private String deliveryPinCode;
    }
    

FlashDeliveryUserOrderListView 固定卡片字段为订单标识、服务/包裹、状态、配送方式/预约时段、取送文字地址、预计时长、金额、送达时间和创建时间。FlashDeliveryRiderOrderListView 固定增加重量范围、pinRequired、pickupDistanceMeters 和路线距离。分页 DTO 只使用上述六个分页/摘要属性。

  • [ ] Step 4: Run DTO tests

    mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test
    

Expected: DTO whitelist tests pass; controller reflection test remains red until Task 2.

Task 2: 实现轻量列表、统一骑手页签和直接详情

Files:

  • Modify: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryUserController.java
  • Modify: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryRiderController.java
  • Modify: ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java
  • Modify: ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java
  • Modify: ruoyi-admin/src/main/resources/i18n/messages.properties
  • Modify: ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  • Modify: ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties
  • Modify: ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  • Modify: ruoyi-admin/src/main/resources/i18n/messages_vi.properties

Interfaces:

  • Consumes: Task 1 DTOs.
  • Produces: userOrders(Long userId,int page,int size).
  • Produces: riderOrders(Long riderId,int page,int size,String tab,BigDecimal longitude,BigDecimal latitude).
  • Produces: riderDetail(Long riderId,Long orderId) returning FlashDeliveryOrderDetailView, never Object.

  • [ ] Step 1: Write failing service tests

    var userPage = fixture.service.userOrders(7L, 1, 10);
    var newTasks = fixture.service.riderOrders(8L, 1, 10, "newTask",
        new BigDecimal("121.5"), new BigDecimal("25.0"));
    assertEquals("台北市中山區南京東路二段 100 號",
        newTasks.getRecords().get(0).getPickupAddress());
    assertNull(fixture.service.riderDetail(8L, 10L).getPickup().getPhone());
    verify(fixture.logMapper, never()).selectList(any());
    

Tests must also capture SQL segments for all five tab mappings, reject refund/unknown tabs, reject half/malformed coordinates, keep scheduled orders hidden before their start, restrict non-new tabs to rider_id, and verify direct photo arrays grouped by SENDER/PICKUP/DELIVERY.

  • [ ] Step 2: Run service and controller tests and verify failure

    mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test
    

Expected: failure on old method signatures, routes, scene filtering and wrapped detail response.

  • [ ] Step 3: Implement controllers and query mapping

    @GetMapping("/orders")
    public AjaxResult orders(@RequestHeader String token,
        @RequestParam(defaultValue = "1") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "newTask") String tab,
        @RequestParam(required = false) BigDecimal longitude,
        @RequestParam(required = false) BigDecimal latitude) {
    return success(service.riderOrders(userId(token), page, size, tab, longitude, latitude));
    }
    

User Controller uses only page,size. Service maps newTask/toPickup/delivering/completed/cancelled before selectPage; newTask applies WAITING_ACCEPTANCE、rider_id IS NULL、预约开始时间条件 and, when coordinates exist, the same sys_qs_newtask_distance radius and distance sorting used by PosOrderQsOprateController.orderList.

  • [ ] Step 4: Implement list mapping and direct detail

    private FlashDeliveryOrderDetailView participantDetail(FlashDeliveryOrder order,
        boolean userView, boolean protectedRiderView) {
    FlashDeliveryOrderDetailView view = detailOrderView(order, !protectedRiderView);
    if (!protectedRiderView) groupImageUrls(view, selectImages(order.getId()));
    if (userView) {
        view.setDeliveryPinCode(Boolean.TRUE.equals(order.getPinRequired())
                ? order.getDeliveryPinCode() : null);
        view.setRider(riderSummary(order));
    }
    return view;
    }
    

Do not query FlashDeliveryOrderLogMapper while building App details. Pre-accept address views copy city,area,handoffMethod,address,addressDetail only; assigned rider/user detail additionally copies contact and exact coordinates. newTask page summary uses filtered page total as nearbyTaskCount and a same-condition maximum amount query as highestOrderAmount.

  • Step 5: Add localized parameter errors and run targeted tests

Add flash.delivery.tab.invalid and flash.delivery.coordinates.invalid to all five message bundles, then run:

mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test

Expected: PASS.

Task 3: 更新契约文档并统一验证

Files:

  • Modify: docs/flash-delivery-app-api.md
  • Modify: specs/024-flash-delivery/quickstart.md
  • Modify: specs/024-flash-delivery/tasks.md

Interfaces:

  • Consumes: the implemented Controller routes and DTO field names from Tasks 1–2.
  • Produces: App integration examples that exactly match production code.

  • [ ] Step 1: Replace obsolete request and response examples

Document only:

GET /system/flashDelivery/orders?page=1&size=10
GET /system/flashDelivery/rider/orders?page=1&size=10&tab=newTask&longitude=121.5&latitude=25.0

Remove /available、/mine、scene、serviceType list filters, approximate coordinates, {order,images,logs} App wrappers and App raw log examples. Keep platform audit response unchanged.

  • [ ] Step 2: Run all affected tests and package

    $env:JAVA_HOME='C:\Users\qmj\.jdks\graalvm-jdk-21.0.7'
    $env:PATH="$env:JAVA_HOME\bin;$env:PATH"
    mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest,FlashDeliveryI18nContractTest' -Dsurefire.failIfNoSpecifiedTests=false test
    mvn -pl ruoyi-admin -am -DskipTests package
    git diff --check
    

Expected: all targeted tests pass, package succeeds and diff check is clean.

  • [ ] Step 3: Audit scope and commit

    git diff --name-only
    $flashFiles = @(
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryUserController.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryRiderController.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryUserOrderListView.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListView.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderPageView.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderDetailView.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderView.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryImageView.java',
    'ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryLogView.java',
    'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryControllerContractTest.java',
    'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryAvailableOrderViewTest.java',
    'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListViewTest.java',
    'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderViewTest.java',
    'ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java',
    'ruoyi-admin/src/main/resources/i18n/messages.properties',
    'ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties',
    'ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties',
    'ruoyi-admin/src/main/resources/i18n/messages_en_US.properties',
    'ruoyi-admin/src/main/resources/i18n/messages_vi.properties',
    'docs/flash-delivery-app-api.md',
    'specs/024-flash-delivery/quickstart.md',
    'specs/024-flash-delivery/tasks.md'
    )
    git add -- $flashFiles
    git diff --cached --check
    git diff --cached --name-only
    git commit -m "精简闪送 App 订单接口"
    git push origin test
    

The staged list must not contain .claude/homunculus/observations.jsonl、.tmp/ or docs/merchant-subaccount-api.md.

新版蓝湖稿增量计划(2026-09-04)

详细文件级执行步骤见 implementation-plan-2026-09-04.md。

目标

保持闪送非支付和整数 TWD 现状,新增创建时收件账号绑定与“我收的”查询,并将 URGENT 落实为跨外卖、闪送任务的骑手独占。附近骑手人数和接单分钟数不进入接口,用户端统一展示“发布后等待附近骑手接单”。

数据与身份

  1. flash_delivery_order 增加可空 receiver_user_id BIGINT 和 (receiver_user_id, create_time) 索引。
  2. 创建订单时将 delivery.phone 去除空白、+、- 和圆括号但保留国家码数字,并以相同规则只查询已注册普通用户;唯一匹配时固化用户 ID,无匹配或多匹配时留空。
  3. 不扫描或回填历史订单,不因用户注册、注销或手机号换绑修改 receiver_user_id。
  4. 用户列表新增可选 role,默认 sender;receiver 使用 receiver_user_id 在数据库分页前过滤。
  5. 详情和确认签收允许创建人或绑定收件人;取消只允许创建人。

急送独占

  1. 新增共享骑手接单锁服务,使用 Redisson 锁键 lock:delivery:rider:{riderId};闪送接单和当前有效外卖接单入口在事务内首先获取同一把锁。
  2. 抢 URGENT 前检查该骑手没有未送达外卖,也没有 ACCEPTED/PICKED_UP 闪送。
  3. 抢其他任务前检查该骑手没有 ACCEPTED/PICKED_UP 的 URGENT 闪送。
  4. 使用不指定固定租期的 tryLock(waitTime, unit) 交由 Redisson 看门狗续期,并通过事务同步回调在事务完成后释放;获取失败或 Redis 异常时拒绝接单。
  5. 独占冲突使用统一国际化业务错误;数据库条件更新仍作为订单被其他骑手抢走的最终并发保护。

测试与交付

先写并运行失败测试,再实现生产代码。测试覆盖手机号唯一/零个/多个匹配、幂等创建、角色分页、参与者权限、取消边界、急送双向互斥、终态释放和同骑手并发接单。数据库变更只追加到 updatesql/sql.md,不直接执行。最后使用 JDK 21 运行定向测试和模块构建,并执行 git diff --check。

设计稿费用与物品信息对齐计划(2026-09-07)

执行要求:在当前会话按测试驱动方式依次完成下列任务;每个任务先运行指定测试确认旧实现失败,再实现最小改动并重新运行。数据库 SQL 只追加到 updatesql/sql.md,不得执行。

目标:拆分帮送/帮取业务场景与普通/加急配送等级,让两种业务场景共享统一分时段运价,按普通配送费比例和最低金额计算加急费,支持骑手小费、预约时段计价、客户端报价回传校验及设计稿中的完整物品信息。

架构:flash_delivery_pricing 改为全局时间段配置,计价器负责纯函数费用计算,应用服务负责选择立即或预约计价时刻、路线编排和报价一致性校验。订单持久化完整物品及费用快照;delivery_type 成为急送独占的唯一判断字段。平台管理端只维护一组时间段,不再展示三类服务价格。

技术栈:Java 21、Spring Boot、MyBatis-Plus/MyBatis XML、MySQL、JUnit 5、Mockito、Vue 2、Element UI、Node 测试。

全局约束

  • Controller 继续使用明确 DTO、@RequestBody、@RequestParam、@PathVariable 和 @RequestHeader String token,不接收 Map。
  • 所有 TWD 费用为整数 Long;比例和重量使用 BigDecimal,计算使用 RoundingMode.HALF_UP。
  • serviceType 只接受 HELP_SEND/HELP_PICKUP,deliveryType 只接受 NORMAL/URGENT,deliveryMode 只接受 NOW/SCHEDULED。
  • 立即订单按报价时刻、预约订单按 scheduledPickupStartAt 的业务当地时分匹配统一运价。
  • 不接入支付、退款或结算;小费仅作为应付费用快照。
  • 平台新增可见文本同步简中、繁中、英文和越南文;前端文件保持 CRLF。
  • 不执行数据库迁移,只把可人工执行 SQL 追加到 updatesql/sql.md。

Task 1:计价模型与数据层

文件:

  • 修改:ruoyi-system/src/main/java/com/ruoyi/system/domain/flash/FlashDeliveryPricing.java
  • 修改:ruoyi-system/src/main/java/com/ruoyi/system/domain/flash/FlashDeliveryOrder.java
  • 修改:ruoyi-system/src/main/java/com/ruoyi/system/mapper/flash/FlashDeliveryPricingMapper.java
  • 修改:ruoyi-system/src/main/resources/mapper/flash/FlashDeliveryOrderMapper.xml
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryPriceBreakdown.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryPricingCalculator.java
  • 测试:ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryPricingCalculatorTest.java
  • 测试:ruoyi-system/src/test/java/com/ruoyi/system/mapper/flash/FlashDeliveryPricingMapperContractTest.java
  • 测试:ruoyi-system/src/test/java/com/ruoyi/system/mapper/flash/FlashDeliveryMapperXmlTest.java

接口:

  • FlashDeliveryPricingCalculator.calculateBreakdown(FlashDeliveryPricing pricing, int distanceMeters, String deliveryType, Long tipAmount) 返回 distanceFee/baseDeliveryFee/urgentFee/tipAmount/amount。
  • FlashDeliveryPricingMapper.selectAtTime(String targetTime) 返回目标 HH:mm 命中的全部配置;countOverlapping(String startTime,String endTime,Long excludedId) 全局查重;lockPricing() 获取单行全局锁。
  • FlashDeliveryOrder 使用 deliveryType,quantity,weightRange,specification,baseDeliveryFee,urgentRate,minimumUrgentFee,urgentFee,tipAmount。

  • [ ] 先扩展计价器和 Mapper 契约测试,覆盖普通加急费为 0、比例费高于最低值、最低值兜底、HALF_UP、小费相加、全局时段查询/查重和订单字段映射,并运行测试确认旧实现失败。

  • [ ] 修改实体、Mapper 和纯计价器;删除运价配置的 serviceType 依赖,使上述测试通过。

  • [ ] 运行:mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryPricingCalculatorTest,FlashDeliveryPricingMapperContractTest,FlashDeliveryMapperXmlTest' -Dsurefire.failIfNoSpecifiedTests=false test。

Task 2:报价、创建校验与物品信息

文件:

  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryQuoteRequest.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryCreateRequest.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryQuoteView.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryHomeView.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderView.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryUserOrderListView.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListView.java
  • 新增:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/exception/FlashDeliveryQuoteChangedException.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryUserController.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/order/RiderDeliveryExclusivityService.java
  • 测试:ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java
  • 测试:ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryControllerContractTest.java
  • 测试:ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderViewTest.java
  • 测试:ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListViewTest.java

接口:

  • 报价请求使用 deliveryType,deliveryMode,scheduledPickupStartAt,scheduledPickupEndAt,quantity,weightRange,specification,tipAmount,不接收精确重量或另一套包裹尺寸字段。
  • 创建请求新增 pricingId,pricingVersion,quotedBaseDeliveryFee,quotedDistanceFee,quotedUrgentFee,quotedAmount。
  • 报价和订单视图提供业务、物品与费用明细;订单视图原样返回四档 weightRange 快照。
  • 报价变化异常携带 FlashDeliveryQuoteView latestQuote;Controller 返回国际化错误和 data=latestQuote。

  • [ ] 先扩展应用服务、Controller 和 DTO 测试,覆盖业务/配送维度校验、完整物品校验、立即与预约时段匹配、报价费用字段、创建逐项一致、配置或金额变化拒绝并返回最新报价、订单费用快照和 deliveryType=URGENT 独占,并确认旧实现失败。

  • [ ] 实现请求/响应 DTO、目标计价时刻选择、统一运价查询、报价复算比较、重量范围校验、快照保存和报价变化错误响应。

  • [ ] 将所有急送独占查询从 service_type='URGENT' 改为 delivery_type='URGENT',保持既有骑手锁和状态边界不变。

  • [ ] 运行:mvn -pl ruoyi-admin -am -Dtest='FlashDeliveryApplicationServiceTest,FlashDeliveryControllerContractTest,FlashDeliveryOrderViewTest,FlashDeliveryRiderOrderListViewTest' -Dsurefire.failIfNoSpecifiedTests=false test。

Task 3:平台配置接口和管理页面

文件:

  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryPricingRequest.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/controller/FlashDeliveryAdminController.java
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/api/flashDelivery/contracts.js
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/views/flashDelivery/shared.js
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/views/flashDelivery/pricing/index.vue
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/views/flashDelivery/orders/OrderDetail.vue
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.zh.js
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.tw.js
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.en.js
  • 修改:E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.vi.js
  • 测试:E:/QtwCode/foodie/foodie-admin-vue/tests/flash-delivery.test.cjs

接口:

  • 平台运价请求为 startTime,endTime,startingDistance,startingFare,distance,freight,urgentRate,minimumUrgentFee,configVersion?,不再含 serviceType。
  • 列表按 startTime 排序,所有时段全局重叠校验;平台订单详情展示业务场景、配送等级、物品和五项费用。

  • [ ] 先修改后端平台契约测试和前端定向测试,确认旧三服务配置结构失败。

  • [ ] 实现统一时段 CRUD、全局锁和字段校验;更新平台价格表单、订单详情及四语言文案。

  • [ ] 运行后端平台定向测试和 npm run test:flash-delivery,再对修改的前端文件运行 ESLint。

Task 4:SQL、App 文档和规格任务状态

文件:

  • 修改:updatesql/sql.md
  • 修改:docs/flash-delivery-app-api.md
  • 修改:specs/024-flash-delivery/quickstart.md
  • 修改:specs/024-flash-delivery/tasks.md
  • 修改:五个 ruoyi-admin/src/main/resources/i18n/messages*.properties
  • 测试:ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryI18nContractTest.java

  • [ ] 在 updatesql/sql.md 追加 2026-09-07 人工迁移脚本:统一运价时段、全局锁、订单业务/物品/费用字段和相关索引;不得连接或修改数据库。

  • [ ] 重写 App 文档的枚举、首页、报价、创建、列表、详情和费用示例,明确报价变化响应及非支付边界。

  • [ ] 添加物品、配送等级、报价变化和运价字段错误的五语言 key,并运行 i18n 契约测试。

  • [ ] 更新 tasks.md 实际完成状态,不提前勾选未运行验证的任务。

Task 5:统一验证与提交

  • 临时设置 JAVA_HOME=C:\Users\qmj\.jdks\graalvm-jdk-21.0.7,运行闪送、外卖独占和 i18n 定向测试。
  • 运行 mvn -pl ruoyi-admin -am -DskipTests package。
  • 在平台前端运行 npm run test:flash-delivery、修改文件 ESLint 和 npm run build:prod。
  • 运行两个仓库的 git diff --check,核对编码、换行和暂存文件清单。
  • 后端与平台前端分别形成中文独立提交;最终报告两个 SHA、实际检查结果和任何未执行项。

移除运价专用锁表增量(2026-09-07)

本节替代本文此前关于 flash_delivery_pricing_lock、全局锁和 SELECT ... FOR UPDATE 的当前架构要求;旧段落仅保留历史设计背景。

  • 平台运价增删改继续在 Spring 事务内执行,不再读取专用锁行。
  • 新增和修改继续通过 countOverlapping(startTime,endTime,excludedId) 检查所有统一时段;修改继续使用 config_version 更新条件。
  • 报价和创建订单仍要求目标时刻只匹配一个时段,异常多条匹配时明确失败,不使用 LIMIT 1 隐藏数据问题。
  • 业务接受低频后台并发新增极低概率同时通过重叠检查;不为该风险保留专用数据库表。
  • 在 updatesql/sql.md 追加删表语句,仅供开发者手动执行,不连接数据库。

2026-09-09:待接单订单编辑实施计划

目标:为已创建、未被骑手接走的订单提供地址重报价、确认保存及追加小费。 实现:在既有 FlashDeliveryApplicationService 和 FlashDeliveryUserController 增量实现;复用订单计价快照、路线服务、价格计算器、收件人匹配与日志。新增明确 DTO;不改创建、接单与取消业务规则。 约束:JDK 21;token 请求头;显式 RequestBody;服务端业务校验与多语言错误;保留文件编码换行;不执行数据库操作。

  1. 补 Controller 端点契约及 Mapper 原子条件测试,运行 JDK 21 定向测试确认缺失能力。
  2. 新增 AddressChangeRequest、AddressConfirmRequest、TipAddRequest ,地址报价复用 FlashDeliveryQuoteView。订单用户详情返回 orderVersion。
  3. 新增 POST /orders/{id}/address/quote、POST /orders/address、POST /orders/tip。地址报价/保存接收完整 pickup/delivery;保存额外校验 quotedDistanceMeters、quotedBaseDeliveryFee、quotedDistanceFee、quotedUrgentFee、quotedAmount。报价响应含 orderVersion。
  4. 地址重算直接使用原订单 pricingId/pricingVersion、起送、里程、加急规则快照与 tipAmount;不重验已创建订单的预约时间,不读取当前后台运价。收件电话变化后重绑 receiverUserId。
  5. Mapper 新增 updateWaitingAddress(order, expectedVersion) 与 updateWaitingTip(order, expectedVersion),WHERE 校验 id/userId/status/rider_id/version。SQL 显式赋值可空地址字段及 receiver_user_id。小费用 BigDecimal 接收 JSON 原数值,在 Service 经 longValueExact 校验正整数后按原值加增量,Math.addExact 防溢出;版本条件阻止重复加款。
  6. 补服务测试:报价不落库、单端/双端地址、收件人重绑/清空、费用确认、距离上限、跨用户、已接走及状态竞争、版本冲突、重复请求、小费非法与溢出。补 API 成功与报价变化响应、SQL 绑定及六套现有语言资源契约。
  7. 更新 contracts/api.md 与 tasks.md,运行定向测试、受影响模块编译和后端回归;检查 diff。真实地图/数据库/App 联调单列未验证。

2026-09-09:编辑操作订单 ID 改为请求体

按用户要求,仅调整确认修改地址和追加小费:在 FlashDeliveryAddressConfirmRequest、FlashDeliveryTipAddRequest 添加 orderId;Controller 两个方法只接 token 与 RequestBody DTO,通过 DTO 将订单 ID 传给既有 Service。requireEditableOrder 在查询前拒绝空/非正订单 ID。同步端点契约、JSON 绑定/身份传递测试、App 文档及定向验证,原有费用和并发业务规则保持不变。

2026-09-14:多取货点实施计划(2026-09-15 修订)

目标:每站独立联系人及物品,默认按用户顺序引导、取得下单人同意并记录骑手声明后允许调整,单点可省略 stopId/多点必传,重复取件幂等,任意站取货后禁止用户取消。本节替换原多点增量方案;后端与平台 PC 已按此实现,实际验证与待联调项见 validation.md。 实现:新订单统一写站点表,订单表维护首点地址/物品摘要与 pickup_stop_count;不做历史迁移、无站点回退或测试数据清理。用户顺序用于整线报价、展示和默认引导;骑手沟通同意后调整实际取件顺序,记录原因及声明,保留原顺序且不变价,前往与取件分离;追加小费不重算路线。 约束:JDK 21;ruoyi-admin -> ruoyi-system;明确 DTO、token 请求头、Service 国际化校验;SQL 只记录 updatesql/sql.md;保留原编码换行。本轮按用户授权在 test-202609v1 实现后端、平台 PC 并同步关联文档,运行定向测试及构建;SQL 仅记录不执行。商家 PC 与现有 App 目录无闪送页面源码,客户端接入任务保留待办。

  1. 数据层(T089、T090):在 ruoyi-system 的 domain/flash 新增 FlashDeliveryOrderStop,mapper/flash 新增 Mapper 与 XML;存每站联系人、地址、packageType/quantity/weightRange/specification、状态和确认时间,关联站点凭证。订单实体与 FlashDeliveryOrderMapper.xml 的 resultMap、select/insert/update 同步 pickup_stop_count、首点地址及物品摘要;站点按 order_id 查询并建立同单 stop_order 唯一约束。updatesql/sql.md 记录建表、订单加列、凭证关联所需 SQL 及三个字典项,上限初始化为 1;不执行 SQL。
  2. 配置及路线(T090、T091):RouteDistanceProvider 新增多经点方法,GoogleRoutesDistanceProvider 以首取点为 origin、其余 intermediates、收货点 destination,不开启 optimizeWaypointOrder;FlashDeliveryRouteService 降级逐段直线求和。数量有效范围 1..10,字典缺失/非法回退 1,超限在地图调用前拒绝;路线与降级均校验整线 40 公里。FlashDeliveryHomeView 下发 pickupStopLimit;兼容 showMultiPickup 由上限派生,showUrgentOption 固定为 true。
  3. 请求、报价及创建(T091):在 ruoyi-admin 的 flashdelivery/dto 新增含完整地址和物品字段的取货点 DTO,QuoteRequest/CreateRequest 扩展 pickups 数组;保留旧 pickup + 订单级物品单点适配。两种地址输入同时出现、数组与旧订单级物品同时提交均拒绝;逐站校验物品、数量、重量范围、规格、地址/坐标及取收同址,并统一校验数量上限。FlashDeliveryApplicationService 沿用创建幂等、路线及费用复算,创建站点、摘要与图片处于同一事务。
  4. 待接单编辑(T091):AddressChangeRequest/AddressConfirmRequest 同步完整 pickups + delivery;多点不得以 pickup 覆盖站点集合,旧单点编辑保留物品;待接单调整顺序也使用完整站点序列重报价并确认,接单后骑手调整访问先后不走地址编辑和重报价。沿用原运价快照及 orderVersion,确认时复算整线;先执行校验创建人/待接单/无骑手/版本的订单条件 UPDATE,成功后同事务保存站点、首点摘要、数量和收件人绑定,失败整体回滚。addTip 保留只增加小费及 amount,不查路线、不重算其他费用,不重验数量上限。
  5. 取件及取消(T092、T093):FlashDeliveryProofRequest 增加可选 stopId;原 pickup 接口单点可省略、多点必传,另提供路径 stopId 接口;两处 ID 同传必须一致,共用取件业务实现,不自动选择下一未取站。FlashDeliveryOrderMapper 新增按订单 ID SELECT ... FOR UPDATE;取件、用户/平台取消均在事务内先锁订单行,再读最新站点状态,不能使用锁前读到的状态校验。校验订单、骑手、PICKUP 类型;新取件目标须符合 FR-096,非默认站须先保存调整原因及已取得同意声明,不能直接以 stopId 绕过;已确认站点重试无新写入,不能因目标已变化拒绝幂等重试。新取件必须处于 ACCEPTED,站点和凭证保存后检查是否全部取完,递增订单版本并记录站点日志;仅全部取完更新 PICKED_UP/pickedUpAt。用户取消锁内检查任意已取站点并拒绝;平台沿用介入权限,按 FR-098 记录异常站、处理结果、已取物品去向及交接凭证后再关闭异常订单,不能漏取、虚假取件或直接完成。图片、日志或其他更新失败必须全部回滚。
  6. 视图及契约(T093、T094):列表增加 pickupStopCount 并保留首点摘要,各角色详情增加带 ID、独立物品及凭证的 stops,pickupImageUrls 仅作聚合展示;接单前逐站隐藏联系人、电话、坐标、PIN 及图片。用户、骑手及平台展示已取数量/总数量,部分取件仍为 ACCEPTED,但用户取消入口按任意站是否已取控制并在刷新时更新;平台详情同步展示各站和调整记录供介入核对。实现阶段同步 contracts/api.md、data-model.md、quickstart.md、docs/flash-delivery-app-api.md 和现有全部后端语言资源;平台改动同步四语言。错误 key 覆盖缺少 stopId、越权、输入冲突、点数超限;允许按规则调整,补充缺少原因/同意声明、目标或调整记录不匹配等国际化错误。导航使用 travelmode=two-wheeler,默认指向用户顺序中最早的未取站,保存调整后为选定单站,全部取完后为收货点;客户端生成 URL,导航不等同取件。
  7. 测试(T088、T095):扩展 ApplicationService、ControllerContract、RouteService、OrderView、RiderOrderListView、MapperXml、I18nContract 测试。覆盖独立物品、旧单点适配/冲突、完整站点编辑/物品保持/版本竞争;单点省略 ID、多点缺 ID 拒绝、记录同意声明及原因后 B 先于 A 成功、缺少调整记录/原因/声明拒绝、跨单/收货站/他人骑手拒绝;响应丢失后重试及并发重复请求不追加凭证或推进别站,最后两站并发完成只推进一次,凭证失败整体回滚。取消与取件并发验证两种先后结果,不能仅用顺序调用宣称并发通过。数量覆盖 0、1、26、27 及字典缺失/非法,26 点对应 25 intermediates;覆盖整线 40 公里、降级、脱敏和追加小费不调用路线。
  8. 验证交付(T095):实现后临时设置本机 JDK 21,运行受影响闪送定向测试及 mvn -pl ruoyi-admin -am -DskipTests package,再执行 git diff --check、变更范围和编码换行检查。需要 MySQL 的事务/并发检查独立执行记录,单元和 Mapper 文本检查不能替代数据库验证。真实地图、数据库、App 联调及未执行回归明确列出;核对暂存仅含本功能文件后中文提交。

  9. 顺序调整记录(T098):按 FR-095、FR-096 设计独立的履约目标选择/调整操作,使用明确 RequestBody DTO 和 token 请求头;服务端保存当前目标及订单、调整前后目标、原因、声明、骑手和时间,不修改 stop_order、站点物品或报价。采用订单行锁与取件/取消串行校验,防止重复请求追加同一次记录或过期请求覆盖目标;已取站及已取消订单拒绝新调整。目标完成后回到用户顺序中最早未取站。同步实体、Mapper/XML、视图及契约;必要 SQL 在实现时写入 updatesql/sql.md,不执行迁移。此操作保存履约选择,不生成导航 URL,也不推进取件状态。

  10. 客户端与异常核对(T099):用户下单前可排序,待接单调整需重报价确认,显示 FR-094 提示。骑手默认突出下一站,可查看其他站;选择非默认站时先提示联系下单人,要求原因及已取得同意声明,保存成功后再导航。区分“前往”与“已取货”,声明不显示为用户审批;订单聊天可作为依据,首期不新增用户 App 审批流程。各端保留原顺序,展示实际进度及取消条件,平台展示调整记录、异常处理结果和物品交接信息;可见文本按各端既有国际化要求实现。关联文档在 T094 同步,后端契约与平台页面已落实;用户/骑手 App 的排序、目标选择及导航页面仍需客户端接入。

2026-09-16:骑手接单资格与容量实施计划

范围:仅调整当前有效外卖、闪送接单入口。待抢列表共用过滤、服务端位置过期和距离复核明确留待后续,不借本次改动扩大范围。

实现:以 RiderDeliveryAcceptanceService 取代只判断急送的 RiderDeliveryExclusivityService。外卖与闪送在 RiderDeliveryLockService 锁内调用统一策略,一次读取两类进行中任务并依次判断资格、急送独占、类型容量和合计容量;订单自身仍使用既有条件更新保证只能被抢一次。三个容量字典只接受 1..20,任何缺失或非法配置都保守回退 1。

持久化和文案:不新增业务表字段;仅在 updatesql/sql.md 记录三个字典类型及初始值 1,不执行数据库操作。新增离线、外卖容量、闪送容量和合计容量错误,并同步全部后端语言资源。

验证:单元测试覆盖资格、三类容量、非法配置回退和急送双向独占;应用服务测试覆盖校验发生在骑手锁内且早于订单条件更新。使用 JDK 21 运行定向测试、模块构建和差异检查,并单列仓库既有的非本功能失败。

  1. 补充验收(T100):覆盖默认 A、沟通并保存调整后前往 B、仅导航状态不变、缺原因/声明或直接绕过调整操作拒绝、B 取件后默认回 A、原顺序/物品/金额不变、部分取件进度及取消入口、平台明确标注骑手声明、无法取货的介入与交接记录;覆盖调整请求重复、与取件/取消竞争、目标变化后的取件幂等重试。联调验证前不得将 App 行为标记为已验证,骑手声明不能作为已验证用户同意真实性的依据。

外部约束:Google Routes 单次最多 25 个中途点,Maps URL 使用 two-wheeler 且途经点数量受平台限制;本期采用字典有效上限最多 26 个取货点与单站导航,不做无限站点分段请求。

2026-09-17:按配送车型计价实施计划

  1. 为运价、报价请求/响应和订单快照增加 vehicleType(1=机车、2=轿车),数据库迁移仅写入 updatesql/sql.md,旧数据以默认值 1 保持原行为。
  2. 运价查询和时段重叠检查按车型隔离;报价、创建、待接单地址改价均使用订单车型,并将机车映射到 Google Routes TWO_WHEELER、轿车映射到 DRIVE,重量范围和规格仅在传入时校验。
  3. 骑手沿用既有 info_user.vehicle_type;待抢查询、待抢详情和接单重复核验订单车型,历史空值或非法值安全按机车处理。
  4. 平台端运价页面将车型作为必填配置维度,新增车型展示和四语言文案;用户 App 不在本次改动范围内。
  5. 补充服务、DTO 与平台前端定向测试,执行 JDK 21 受影响模块测试、前端测试及两个仓库的差异/换行检查;MySQL 迁移和真实 App 联调单列为未执行项。

2026-09-17 实施进度

  • 车型运价、报价/订单快照、骑手车型接单限制、重量/规格选填和 SQL 迁移登记已实现;SQL 未直接执行。
  • 路线服务已按车型透传:机车为 Google Routes TWO_WHEELER,轿车为 DRIVE;报价、创建及待接单改址均覆盖,历史空值按机车回落。
  • 路线车型映射、路线服务透传、车型报价、Mapper/DTO/i18n/接口契约定向测试通过;JDK 21 执行 mvn -q -pl ruoyi-admin -am -DskipTests package 通过;平台端 npm run test:flash-delivery 8 项通过;两个仓库 git diff --check 通过。
  • 未执行 MySQL 迁移、真实 Google Routes 请求、真实用户 App 联调或完整回归。平台端 npm run build:prod 因本机缺少 vue-cli-service 未完成。两轮路线是 Google Beta;用户 App 若展示两轮路线,需要另行补充 Google 要求的提示文案。

2026-09-17 共享地图 Key 与路线降级诊断

  • 闪送改为与外卖共用 sys_googlemap_key,不再依赖测试库中缺失的 sys_google_routes_key;Key 不进入闪送响应或业务日志。
  • Google Routes 失败时记录车型与脱敏失败原因后降级直线距离;HTTP 非成功响应记录状态码,不记录地址、坐标、Key 或完整响应。
  • GoogleRoutesDistanceProviderTest 与 FlashDeliveryRouteServiceTest 共 5 项通过。
  • 从当前开发机访问 routes.googleapis.com:443 连接超时,未能验证共享 Key 的 Routes API 权限;需在测试服务器部署后查看新告警日志或从该服务器网络发起验证。

2026-09-30:闪送骑手收入账单实施计划

  1. 在 UserBilling 和 XML 映射增加可空 orderSource,迁移 SQL 仅登记;非订单账单保持为空。新增闪送账单明确写 FLASH,历史骑手订单账单在响应中按 FOOD 兼容,不迁移历史账单。
  2. 两个事务完成入口在订单行锁和 DELIVERED -> COMPLETED 条件更新成功后共用 recordRiderIncome,直接调用账单 Mapper 写入全额收入及完成时间。写入失败抛国际化业务异常并回滚;不依赖确认收款,不调用任何钱包服务。
  3. UserBillingController 的日/周/月骑手账单按来源分别批量查询外卖和闪送订单;闪送查询同时约束当前骑手,保留分页顺序和原统计 SQL。QsBillingDto 补充闪送主键、小费与收款状态,通用账单响应也返回来源;旧 WalletController.getUserBills 仅过滤外卖关联查询的来源,余额/提现不变。
  4. TDD 覆盖四种完成操作人、重复/竞争失败、取消/未取完、账单写入失败、确认收款不生成账单,以及单独/混合来源的日周月详情;验证 XML 绑定新字段和完成时间。运行 JDK 21 定向测试、模块测试及构建,检查差异和换行;真实数据库事务/并发、SQL 迁移及 App 联调另列未验证。

    2026-10-10 骑手 LINE Pay 与取消边界增量

按 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,初始值 1;取货侧继续使用 flash_pickup_stop_limit,不设多点或加急展示开关。物品主体仍在 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 联调另行验收;进度按 未开始/实现中/已提交/已验证 口径报告。

2026-10-14 新增多点取送接口统一 body 参数(用户确认旧接口不动)

  1. 仅本次新增的 v2 报价、创建与逐站送达遵循 body 统一业务参数;GET 简单参数保持现状。已有 LINE Pay、旧报价/创建/送达、取件、目标调整、改址及平台接口均不修改。
  2. v2 quote/orders 已是固定路径和明确请求体;新增逐站送达改为 POST /system/flashDelivery/rider/orders/stops/deliver,独立 FlashDeliveryStopDeliverRequest 承载 orderId、stopId、pinCode、imageUrls,token 仍从请求头取得。
  3. Controller 校验必需 orderId/stopId 为正整数,使用现有国际化错误;调用既有 deliverAtStop,服务层状态机及其他审查问题不在此次参数调整范围。同步 024 spec、tasks、契约与 App 文档;不改 SQL 和旧 DTO。
  4. 接口测试验证新增路径、body ID 实际传递、缺失/非正数 ID 拒绝、既有 GET/旧接口契约保持;JDK 21 定向回归与差异检查。

2026-10-14 审查问题修复与通知范围调整(用户已授权)

  1. 流程修复:旧整单送达拒绝 v2;完成、自动完成与确认收货都检查所有 DELIVERY 已送达。逐站送达先核实所属骑手/站点,再幂等返回已确认站;新确认严格跟随目标,送达目标过滤已完成站,目标调整支持 PICKED_UP 阶段的 DELIVERY,继续保留版本与同意声明审计。
  2. v2 完整改址:新增固定路径 /v2/orders/address/quote、/v2/orders/address 和独立 DTO,仅 body 接收 orderId/version/完整 stops(确认保存另含回传报价),共享运价快照与条件更新;旧改址拒绝 v2,保留原参数及历史单行为。站点全量保存计数、备注、收件人与 PIN,物品仍从订单级保留,不直接套用旧单收货点保存方法。
  3. 权限与展示:v2 不生成订单级替代码;用户详情按站返回 PIN(发起人全部、收件人仅本人、骑手不返回)。按站收件人覆盖 receiver 查询、详情及确认收货,DTO 补收货站总数/进度,未绑定用户不追溯认领。
  4. 通知独立实现:每站首次成功提交后仅通知发起人,payload 站点与进度明确、状态真实,末站不叠发相同阶段整单提醒;国际化复用当前五语言资源。逐站收件人通知明确暂缓,后续用户要求再补,当前不排期。
  5. 验证:先补问题场景回归并确认 RED,再批量修复,统一 JDK 21 定向与后端全量回归、模块构建及差异/换行核对。真实数据库多连接并发、推送设备、Routes 和 App 联调另列未验证;SQL 不执行、已有未跟踪 .test-hold 不动。

2026-10-14 简化多点与加急配置(用户确认)

  1. 移除 flash_show_multi_pickup、flash_show_multi_dropoff、flash_show_urgent_option 三个字典依赖;取/收货点只按各自 1..10 的数量上限校验,加急始终可用。
  2. 首页保留现有 showMultiPickup、showMultiDropoff、showUrgentOption 字段兼容客户端;前两个按上限是否大于 1 派生,后者固定为 true。
  3. 在 updatesql/sql.md 登记两项上限初始化和旧开关字典清理语句;用户后续明确要求更新测试库,已按旧版增量路径执行并复核。其他环境不自动执行;同步现有规格、接口契约、前端交接文档及定向测试。

2026-10-15:交错取送与双进度实施计划

目标:按 FR-129 至 FR-131 在当前 test-202609v2 实现,保持 Tab 和接口参数,复用现有表与字段,无迁移 SQL。

  1. T140:在 FlashDeliveryV2Test 先补客户交错顺序的报价/创建/改址、取1→取2→送1→取3→送2、骑手无同意声明先取后送、未取禁止送达、漏取禁止整单送达、错 PIN/目标和重试不重复通知测试;在 FlashDeliveryMapperXmlTest 检查送达条件更新允许 ACCEPTED/PICKED_UP 且仍限制骑手/版本。先运行 JDK 21 定向测试证明旧限制拒绝新流程。
  2. T141:修改 FlashDeliveryApplicationService.normalizeV2Stops/targetStopId/selectTarget/deliverAtStop;v2 默认按所有待完成站点原序选目标,骑手可自主选择待完成站,送达允许部分取件并保持 ACCEPTED,末次送达仍核实全部取件。旧单继续原阶段与同意门禁。修改 FlashDeliveryOrderMapper.updateDeliveryFulfilment 的 SQL 状态条件及注释;取件、送达、目标仍在订单行锁与版本保护内,不改变费用、PIN、支付和记账路径。
  3. T142:同步 spec、plan、tasks、data-model、契约和两份 App 文档,给出四个已有进度字段及 Tab 不变的明确接入示例,修订旧“先取完再送”描述并标注历史范围;补充代码注释。运行定向测试、后端回归、模块构建及差异/换行检查,记录真实 MySQL、App 和设备推送未联调;不提交/推送,等待用户明确指令。

验证重点:真实站点进度驱动目标与完成,不能因主状态 ACCEPTED 丢弃已送达数量;条件更新覆盖未取完阶段送达;目标调整不绕过按站 PIN、图片、支付和权限;已送站重试不恢复旧目标;未完取件不能提前生成收入。使用本机 JDK 21 C:\Users\99029\.jdks\graalvm-jdk-21.0.12+7.1,仅命令内设置环境。既有未跟踪 .test-hold/ 与 specs/037-order-no-rider-auto-cancel/ 不改动。