# 029 商家自配送 — 交付说明 ## 商家 App 接入补充(2026-10-12) 前端接入文档:[frontend-app-integration.md](frontend-app-integration.md)。包含夜市主 My 配置接口、请求与响应示例、星期多时段校验及配送操作衔接。本次仅更新文档,不修改 App 或后端业务代码。 核对当前源码发现:`PosOrderShOprateController.orderList` 仍要求外送单具备骑手 ID,正常 `selfDelivery=1 && qsId=null` 的订单会被过滤,设置页可独立接入,但订单列表与完整配送闭环必须先修正后端过滤并回归。本次未修复,未进行线上/数据库联调;下文历史交付状态不能替代这一当前限制。 **日期**: 2026-09-08 | **分支**: `test-202609v2`(按用户要求未单独开分支) ## 交付状态 | 部分 | 状态 | |------|------| | 后端(配置/判定/隔离/配送操作/结算/推送/i18n) | ✅ 完成,43 个新增测试全绿;全仓 455/455 全绿零回归 | | 数据库 DDL | ✅ 已登记 `updatesql/sql.md`(2026-09-16 补登记 029 段,共 3 条)——**需开发者手动执行后方可部署** | | 商家端 PC(foodie-store) | ⚠️ 夜市主自配送设置页已完成;无订单列表/详情及配送操作入口 | | 客户端 App(msd-user) | ⛔ 按本次范围不修改,待外部接入自配送展示与骑手入口隔离 | | 商家手机 App | ⛔ 源码未在当前工作区,仍需外部接入配送操作 | ## 上线前置清单(开发者手动执行) 1. 在数据库执行 `updatesql/sql.md` 中「2026-09-16 补登记商家自配送表结构(029-merchant-self-delivery)」的 3 条 DDL(pos_store 2 列、pos_store_self_delivery_hours 建表、pos_order 1 列)。**未执行前部署新包会导致 pos_order 相关查询报列不存在**。 2. 执行后按 `quickstart.md` 场景 1-8 回归(服务启动 + 接口级冒烟)。 3. (可选,T020 遗留)用有权限的账号跑下面的只读核验 SQL,确认夜市主门店行齐全;解析不到的夜市会自动回落骑手池(不报错,但那部分夜市自配送不生效): ```sql SELECT DISTINCT p.night_market_id FROM pos_store p WHERE p.is_stall=1 AND p.del_flag='0' AND p.night_market_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM pos_store m WHERE m.is_night_market=1 AND m.del_flag='0' AND m.user_id=p.night_market_id); ``` ## 范围调整:商家端界面落点(重要) 执行 T033 探索时发现:**foodie-store(PC)没有既有的门店信息页和订单列表页**(仅商品/分类/促销/对账等管理页);因此已额外落地一个仅供夜市主使用的“自配送设置”页,但订单管理界面仍在商家手机 App(uni-app,不在本工作区)。 因此 FR-001 的夜市主配置子集已由 PC “自配送设置”页承接;FR-010 的订单列表标识及配送操作仍归商家 App 外部配合。后端接口(`/chanting/store/get|saveMdSelfDeliveryHours`)与订单字段已就绪,App 端接入即可。是否在 PC 端新建订单页面,与 clarify Q2(配送操作界面端覆盖)同属一个待决策问题,建议一并讨论后追加。 ## 外部配合清单(客户端接入) **商家手机 App(uni-app)**: - 门店信息页:自配送区块——开关 + 模式(全天/自定义按星期)+ 星期×多时段编辑;调 `POST /chanting/store/saveMdSelfDeliveryHours`(DTO: mdId/enabled/mode/hours[{dayOfWeek,startTime,endTime}]),读 `GET /chanting/store/getMdSelfDeliveryHours`(返回 enabled/mode/hours/configMdId)。摊位主(isStall=1)保存会被拒(no.store.stall.selfdelivery.readonly),前端按 configMdId≠自身做只读展示"由夜市主统一配送" - 订单列表/详情:`selfDelivery==1` 显示"自配送"标签,不显示等待骑手接单状态 - 配送操作(开始配送/确认送达,可与 Q2 决策合并):`POST /system/orderShOprate/selfDeliveryStart` / `selfDeliveryComplete`(DTO: {id, qsImg?});开始配送为可选步骤 **客户 App(uni-app,`msd-user`)**: - 按本次范围不修改;后续如需接入,订单对象使用 `selfDelivery` 字段(0/1):列表与详情显示"商家自配送";deliveryStatus 0/1=商家备餐中、2=商家配送中、3=已送达(无骑手接单环节)。自配送订单应隐藏联系骑手入口与骑手地图路径。 - 推送文案 key:`no.message.push.merchant.delivery.start` / `no.message.push.merchant.delivery.complete`(5 个 properties 已含四语言) ## 实现要点(代码导航) - 判定:`SelfDeliveryUtil.matches`([开始,结束)、ISO 星期)+ `SelfDeliveryService.resolveJudgeTime`(即时=下单时刻;预约按 delryTime 时间段**开始时刻**,格式 `yyyy-MM-dd HH:mm-HH:mm`,解析失败回落即时) - 快照:两个下单入口(UserOrderController/PosOrderShOprateController 的 createOrderChild)写 `pos_order.self_delivery`,终身不变 - 隔离:骑手 newTask 池过滤 + accept/pickup/deliver 直调拒绝(no.order.selfdelivery.rider.denied) - 放行:`requireRiderAssigned` 对 self_delivery=1 跳过骑手校验 - 操作:`selfDeliveryStart`(state=2 且 deliveryStatus=0→2)/ `selfDeliveryComplete`(state=2 且 deliveryStatus∈{0,2}→原子置 3+state=3+sdTime),Redisson 锁 + CAS - 结算:`OrderService.setSelfDeliveryFreightBilling`(UserBilling type="5"、user_id=sh_id、全额、divvy=0);挂在商家送达与 `OrderLifecycleService.completeSideEffects`(管理端路径)两处;到付 type="3" 账单 paymentId=sh_id - 快照不回滚:判定只在创建时发生,配置后续变更不影响已创建订单(结构性保证) ## 核验与修复记录(2026-09-08 子代理核验) 核验结论:FR-001~FR-012 逐条落实(FR-007/010 界面部分归客户端仓库);发现并已修复: 1. 【中】确认送达未校验支付状态——商家POS现金/转账外送单(payStatus=0)曾可被直接送达完成并生成分成账单;已加 payStatus==1 前置(no.order.selfdelivery.unpaid),对齐骑手送达语义 2. 【中】FR-009"商家接单"节点推送缺失——自配送单已补:商家接单后推用户(no.message.push.merchant.accepted.content) 3. 【低】时段重叠校验、pickup/deliver 直调拒绝测试、两处过期注释、既有缺口 key no.order.not.found(5 个 properties 补齐) 已知隐患(本期不改,记录备查):平台管理端 `GET /system/order/list` 走 XML 显式列清单不含 self_delivery 列——管理端界面按 D10 不在本期范围,未来管理端需要展示自配送标识时须同步 PosOrderMapper.xml 的 selectPosOrderVo。 ## 补正记录(2026-09-16) 1. 029 的 3 条 DDL 曾缺失于 `updatesql/sql.md`,现已补登记;仍由开发者手动执行,应用不会自动迁移数据库。 2. 已支付自配送外送单的创建、LINE Pay/OMG 支付成功及线下转账确认均复用 `DeliveryOrderNotificationService.notifyOrderAvailable`。该公共入口现统一分流:自配送订单仅通知门店/夜市主,其他外送订单才通知骑手;新增回归测试覆盖,避免骑手收到无效新单推送。 3. `user_billing.type="5"` 固定表示自配送运费收入;已停止的“金额解冻”业务不再占用该类型。商家端筛选与列表显示统一为“自配送运费”(四语言),骑手和商家商品分成仍沿用 type="0"。 4. 用户下单请求未传 `shId` 时,服务端以实际 `mdId` 对应门店的 `userId` 回填;已传 `shId` 保持旧客户端兼容行为。夜市摊位门店的 `userId` 为夜市主,因此自配送运费归属保持正确。 5. 用户端 `msd-user` 按本次范围不修改,仍待接入自配送标识、状态文案与骑手入口隔离;商家手机 App 的订单页面源码不在当前工作区,仍须接入 `/system/orderShOprate/selfDeliveryStart|selfDeliveryComplete`。 历史全量回归记录保留于原始交付;本次补正仅执行定向后端测试、商家端生产构建和用户端语言 JSON 解析,详见本次提交说明。 ## 遗留决策 1. **clarify Q2**(配送操作界面端覆盖)——用户暂缓;后端接口先行,PC/App 界面待决策 2. **PC 端是否新建门店设置/订单页**——随 Q2 一并讨论 3. T040 运行时冒烟待 DDL 执行后进行(quickstart 场景 1-8) ### 已记录待实施方案:夜市主 PC 订单配送调度(2026-09-16) 确认不新建页面,复用 `foodie-store` 首页的“最新订单”管理区。夜市主(`userType=3`)不负责摊位接单和出餐,只管理旗下摊位已出餐的自配送外送单;摊位主继续使用既有“接单 → 出餐”操作。 夜市主仅展示 `type=0 && selfDelivery=1` 的订单,状态 Tab 为: 1. **全部**:只读查看自配送订单详情。 2. **备餐中**(state=0/1):只读,等待摊位主接单、出餐。 3. **待配送**(state=2 且 deliveryStatus=0):显示“开始配送”,并可直接“确认送达”。 4. **配送中**(state=2 且 deliveryStatus=2):显示“确认送达”。 5. **已完成**(state=3 或 deliveryStatus=3):只读。 6. **已取消/售后**:只读。 订单行与详情需标明“自配送”和摊位名称;“开始配送”“确认送达”分别调用既有 `selfDeliveryStart`、`selfDeliveryComplete`。前端仅控制显示,接口仍以服务端订单归属、`selfDelivery`、订单状态和配送状态重新校验。