Input: Design documents from /specs/029-merchant-self-delivery/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/api.md, quickstart.md
Tests: 已包含——spec SC-001/SC-004/SC-005 要求验收用例集 100%,quickstart 场景 1 即单测。测试任务先写并确认 RED,再实现转 GREEN。
Organization: 按用户故事分阶段,每个故事可独立实现与验证。
E:\QtwCode\foodie\foodie_server(下述相对路径基于此)E:\QtwCode\foodie\foodie-store(CRLF,用 Python 脚本编辑)JAVA_HOME=C:\Users\qmj\.jdks\graalvm-jdk-21.0.7;本项目无 mapper XML 新增(新表走 MyBatis-Plus BaseMapper),但改 XML 时须 python -c "import xml.etree.ElementTree as ET; ET.parse(...)" 校验updatesql/sql.md,标注 -- 2026-09-08 029 商家自配送;不直接执行ruoyi-system/src/main/java/com/ruoyi/system/domain/PosStoreSelfDeliveryHours.java(MyBatis-Plus 注解风格对齐 OperatingHours)与 ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosStoreSelfDeliveryHoursMapper.java(BaseMapper,无 XML)ruoyi-system/src/main/java/com/ruoyi/system/domain/PosStore.java(+selfDeliveryEnabled/selfDeliveryMode)、ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrder.java(+selfDelivery);grep PosStoreMapper.xml/PosOrderMapper.xml 若有涉及全列的 resultMap/select 则同步ruoyi-admin/src/main/resources/i18n/ 下 5 个 properties(messages、zh_CN、zh_TW、en_US、vi)统一加 contracts/api.md §5 全部 key⚠️ US 全部依赖本阶段完成
ruoyi-admin/src/test/java/com/ruoyi/app/utils/SelfDeliveryUtilTest.java:[开始,结束) 边界(18:00:00 命中/21:00:00 不命中)、按星期、多段任一命中、全天、跨天(周二配置周一时刻不命中);确认 REDruoyi-admin/src/main/java/com/ruoyi/app/utils/SelfDeliveryUtil.java:matches(enabled, mode, hours, target LocalDateTime),HH:mm 定长比较、ISO dayOfWeek 过滤、区间含首不含尾;T005 转 GREENruoyi-admin/src/test/java/com/ruoyi/app/order/SelfDeliveryServiceTest.java:配置主体解析(普通门店=自身/摊位→夜市主门店行/夜市主门店行不存在→未配置回落)、保存校验(自定义至少一段、end>start、HH:mm 格式、dayOfWeek∈[1,7]);确认 REDruoyi-admin/src/main/java/com/ruoyi/app/order/SelfDeliveryService.java:resolveConfigStore(PosStore)、isSelfDelivery(store, target)、saveConfig(input)(整体覆盖式:先删后插)、getConfig(mdId);T007 转 GREENGoal: 普通商家配置自配送→即时单命中标记→不进骑手池→商家接单/出餐/(可选开始配送)/确认送达全链路。
Independent Test: quickstart 场景 3+5+6(隔离/流转/可选步骤)。
ruoyi-admin/src/test/java/com/ruoyi/app/order/PosOrderShOprateControllerTest.java 补用例——self_delivery=1 单接单/出餐放行(现状被 requireRiderAssigned 拦)、selfDeliveryStart/Complete 状态校验(state≠2 拒绝、重复开始拒绝);确认 REDruoyi-admin/src/test/java/com/ruoyi/app/order/PosOrderQsOprateControllerTest.java 补用例——acceptOrder 对 self_delivery=1 拒绝(no.order.selfdelivery.rider.denied);确认 REDruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreController.java 加 GET /getMdSelfDeliveryHours 与 POST /saveMdSelfDeliveryHours(@RequestHeader token + DTO);新建 ruoyi-admin/src/main/java/com/ruoyi/app/mendian/dto/SelfDeliveryConfigInput.java(含内嵌 SelfDeliveryHourInput,无 Bean Validation 注解,校验在 Service);普通商家走 merchantStoreAccessService.requireStoreAccessruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java 的 requireRiderAssigned 对 self_delivery=1 放行(research F2)POST /selfDeliveryStart(state=2 且 deliveryStatus=0 → 2)与 POST /selfDeliveryComplete(state=2 且 deliveryStatus∈{0,2} → 原子置 deliveryStatus=3+state=3+sdTime);新建 DTO ruoyi-admin/src/main/java/com/ruoyi/app/order/dto/SelfDeliveryOperateInput.java(id 必填、qsImg 可选);Redisson 锁 order:sh:selfdelivery:{id};订单日志;用户推送(区分自配论文案)chuCan 出餐推送:self_delivery=1 跳过骑手推送、保留用户推送ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java 的 createOrderChild 与 PosOrderShOprateController.java 的商家端 createOrder,创建 PosOrder 时调 SelfDeliveryService.isSelfDelivery(store, 基准时间) 写 self_delivery(本阶段基准=当前时间;预约基准在 T026 接入)ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java——newTask 过滤追加 self_delivery=0(NULL 视为 0 兼容存量)、acceptOrder/pickupOrder/deliverOrder 对 self_delivery=1 抛 no.order.selfdelivery.rider.denied(防直调,镜像 027 模式)Checkpoint: 普通商家即时单 MVP 端到端可用。
Goal: 夜市主在夜市主门店行配置,对旗下全部摊位生效;夜市主可见并可操作摊位自配送订单;摊位无配置入口。
Independent Test: quickstart 场景 2(配置权限+摊位只读)+ 夜市摊位单不进池。
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreController.java 配置接口权限完善:夜市主门店行归属校验、摊位拒绝(no.store.stall.selfdelivery.readonly);getMdSelfDeliveryHours 对摊位 mdId 返回所辖夜市配置(前端只读展示)SelfDeliveryService.resolveConfigStore 摊位(isStall=1 且 nightMarketId 非空)→ 按 nightMarketId 查夜市主门店行(isNightMarket=1 且 userId=nightMarketId);用测试库核验夜市主门店行存在性,缺失则视为未配置回落骑手池(research D1 待验证项)requireMerchantOrderAccess(PosOrderShOprateController)对 shId=夜市主放行 selfDeliveryStart/Complete(补契约测试用例);同一父单下多摊位子单 self_delivery 一致性用例Checkpoint: 夜市场景与普通商家场景并存,同一套操作端点。
Goal: 订单数据透出自配送标记与配送状态;推送文案区分;客户端 App 展示由外部配合(本仓库交付字段与文案)。
Independent Test: quickstart 相关断言——订单接口响应含 selfDelivery;推送文案 key 生效;骑手无推送。
no.message.push.merchant.delivery.start、selfDeliveryComplete→送达文案(复用现有送达 key,文案区分自配送);断言 self_delivery=1 全流程不触发骑手推送Goal: delryTime 非空的订单按预约时间(而非下单时刻)判定自配送时段。
Independent Test: quickstart 场景 4。
delryTime 非空 ? delryTime 解析值 : 当前时间(ruoyi-admin/.../order/UserOrderController.java、PosOrderShOprateController.java);注意 delryTime 字符串格式的解析与失败回落(解析失败按即时单处理并记 warn 日志)Goal: 确认送达后运费全额(不抽成)入账 user_id=sh_id;到付归因同步;无骑手分成。
Independent Test: quickstart 场景 5 账单核对(本阶段补全账单断言)。
setType(" 与 user_billing 现有 type 语义,确认 "5" 未被占用(research D5);若占用换值并同步 data-model.md/contractsruoyi-admin/src/main/java/com/ruoyi/app/order/OrderService.java#setSelfDeliveryFreightBilling(含 walletService.addBalance(sh_id)),并在 ruoyi-admin/src/main/java/com/ruoyi/app/order/OrderLifecycleService.java#completeSideEffects 增分支:type==0 && self_delivery==1 && qsId==null 调用之(管理端 018 与商家送达共用此副作用)Goal: foodie-store(E:\QtwCode\foodie\foodie-store)门店设置自配送区块 + 订单"自配送"标识,四语言。
Independent Test: quickstart 场景 7。
src/lang/ 四语言文件对应对象层级,输出改动点清单(无代码改动)src/lang/zh.js、tw.js、en.js、vi.js 四文件同步加 selfDelivery.* keys(有意义英文驼峰、按对象层级插入、四文件 key 一致)- [ ] T036 [US6] 订单列表/详情:selfDelivery=1 显示"自配送"标签($t),不显示等待骑手类状态- [ ] T037 [US6] 【可裁剪:依赖 clarify Q2 端覆盖决策,未决策前跳过】PC 配送操作按钮:开始配送/确认送达调 /system/orderShOprate/selfDeliveryStart|Complete【转外部配合】T033 探索结论:foodie-store 无门店信息页与订单列表页(在商家 App),本仓库前端无可挂载点,界面归商家 App 接入(见 delivery.md 外部配合清单);i18n 前端 key 随 App 端实施
mvn -pl ruoyi-system,ruoyi-admin -am compile + mvn -pl ruoyi-admin test;若有 XML 改动跑 ET.parse 校验不触碰废弃代码清单(research.md「涉及的废弃代码」)
[x] T041 【2026-09-10 变更】普通门店保存自配送配置改为一律拒绝:仅夜市主门店行且夜市主本人可保存;新增提示 no.store.selfdelivery.nightmarket.only(六语言,th_TH 顺带补本 key);测试 PosStoreControllerSelfDeliveryTest.normalStoreCannotSaveSelfDeliveryConfig(原 normalStoreSaveGoesThroughStoreAccess 断言翻转)
[x] T042 【2026-09-10 追加】夜市主 PC 自配送设置入口:后端 my 读写接口(token 反查门店行+六语言拒绝文案+3 个控制器测试);foodie-store 新页面/路由/菜单/API/四语言 i18n
[x] T043 【2026-09-16 补正】补登记 3 条自配送 DDL;已支付自配送订单统一改为通知门店/夜市主而非骑手;type=5 账单显示统一为自配送运费;用户下单缺失 shId 时按已解析的门店归属回填。
[ ] T044 【外部配合】商家手机 App 的订单页面源码不在当前工作区:接入自配送标签及 selfDeliveryStart/selfDeliveryComplete 操作入口后,才能完成商家侧实际操作闭环。
[ ] T045 【范围外】用户 App(msd-user)按用户要求不修改;如后续需要,接入自配送标识、状态文案以及骑手入口隔离。
[ ] T046 【待用户确认后实施】复用 foodie-store 首页订单管理:夜市主仅查看 type=0 && selfDelivery=1 的旗下摊位订单;提供全部、备餐中、待配送、配送中、已完成、已取消/售后 Tab。摊位主保留接单/出餐;夜市主仅在待配送/配送中使用 selfDeliveryStart、selfDeliveryComplete。详见 delivery.md「夜市主 PC 订单配送调度」。