api.md 6.1 KB

API Contracts: 商家自配送 (029)

Date: 2026-09-08 | 遵守项目 Controller 规范(@RequestHeader token / @RequestBody DTO / @RequestParam 显式标注 / 禁 Map 入参)

1. 自配送配置读写(镜像营业时段接口对)

GET /chanting/store/getMdSelfDeliveryHours

参数 位置 类型 说明
token Header String 登录 token
mdId Query Integer 配置主体门店 id(普通门店=自身;夜市=夜市主门店行)

响应:{ enabled: boolean, mode: Integer(1=全天,2=自定义,null=未设置), hours: [{dayOfWeek, startTime, endTime}] } (摊位主查询自己摊位 → 返回所辖夜市主门店的配置,便于前端展示"由夜市统一配送"提示;无配置返回 enabled=false 空结构)

POST /chanting/store/saveMdSelfDeliveryHours

入参 DTO SelfDeliveryConfigInput:

权限规则:

  • 门店为摊位(is_stall=1)→ 拒绝(key: no.store.stall.selfdelivery.readonly,文案"摊位配送方式由夜市主统一设置")
  • 夜市主门店行(is_night_market=1)→ 仅 store.userId == 登录用户 可保存
  • 普通门店 → merchantStoreAccessService.requireStoreAccess(loginUser, mdId)(复用 022 多门店权限)

效果:pos_store 两列更新;mode=2 时 hours 整体覆盖式重建(先删后插)。已下单订单的 self_delivery 快照不受影响。

2. 商家配送操作(新增于 PosOrderShOprateController /system/orderShOprate)

POST /selfDeliveryStart(开始配送,可选步骤)

入参 DTO SelfDeliveryOperateInput:id: Long(PosOrder id,必填)

字段 类型 校验(Service 内,MessageUtils 报错)
mdId Integer 必填;权限校验见下
enabled Boolean 必填
mode Integer enabled=true 时必填,∈{1,2}
hours List mode=2 时必填非空;每项 dayOfWeek∈[1,7]、startTime/endTime 匹配 HH:mm、end>start
校验 失败响应
订单存在且 requireMerchantOrderAccess(夜市主按 shId、商家按门店权限,复用现有方法) merchant.order.not.found / 权限错误
type==0 && self_delivery==1 && qsId==null no.order.selfdelivery.required(非自配送单)
state==2(已出餐)且 deliveryStatus==0 no.order.status.not.allow
afterSaleStatus==0 no.order.aftersale.block

效果:deliveryStatus=2;订单日志"商家已开始配送";向用户推送配送中通知(新 key no.message.push.merchant.delivery.start,文案区分商家自配送)。Redisson 锁 order:sh:selfdelivery:{id} 防并发(镜像 QsOprate 模式)。

POST /selfDeliveryComplete(确认送达)

入参 DTO 同上(id 必填,qsImg 可选送达凭证)

校验同上,但状态要求 state==2 && deliveryStatus∈{0,2}(允许跳过开始配送); 另须 payStatus==1(在线/到付创建即付;商家POS现金/转账外送单须先确认收款——对齐骑手送达语义与管理端"完成单必已支付"不变量,错误 key no.order.selfdelivery.unpaid,2026-09-08 核验修复)。

效果(事务内):

  1. 原子置 deliveryStatus=3 + state=3 + sdTime=now
  2. completeSideEffects(复用/扩展 OrderLifecycleService L491-496 的分支):商品分成 + 自配送运费入账(UserBilling type="5",user_id=sh_id,amount=freight 全额,divvy=0)
  3. 到付单(collectPayment=1):用户账单 type="3" 的 paymentId=sh_id
  4. 订单日志"商家已确认送达";向用户推送已送达通知(复用现有送达 key 或新 key,文案区分自配送)

3. 下单判定(现有接口行为变化,无新端点)

UserOrderController.createOrder 与 PosOrderShOprateController.createOrder 两个入口在创建每条 PosOrder 时:

  • 解析门店配置:普通门店读自身;摊位(is_stall=1 且 nightMarketId 非空)读夜市主门店行
  • 判定基准:delryTime 非空 → 预约时间;否则当前时间
  • SelfDeliveryUtil.matches(...)([start,end),ISO dayOfWeek)→ 写 self_delivery=1/0

响应变化:订单对象自动携带 selfDelivery 字段(全字段序列化),客户端无需额外接口。

4. 骑手侧隔离(现有接口行为变化)

接口 变化
GET /system/orderQsOprate/orderList?tab=newTask 追加过滤 self_delivery=0(NULL 视为 0,兼容存量)
GET /system/orderQsOprate/acceptOrder 订单 self_delivery=1 → 拒绝 no.order.selfdelivery.rider.denied(防直调,镜像 027 模式)
POST /system/orderQsOprate/pickupOrder / deliverOrder 同上

5. i18n key 清单

后端(messages.properties + en_US/vi/zh_CN/zh_TW 共 5 文件): no.store.stall.selfdelivery.readonly、no.store.selfdelivery.hours.invalid、no.order.selfdelivery.required、no.order.selfdelivery.rider.denied、no.order.selfdelivery.status.invalid、no.order.selfdelivery.unpaid、no.message.push.merchant.accepted.content(自配送单商家接单推用户,核验修复)、no.message.push.merchant.delivery.start、no.billing.selfdelivery.freight(账单说明)

商家端前端(foodie-store src/lang/:zh.js/tw.js/en.js/vi.js,key 用有意义英文驼峰): selfDelivery.*(开关/模式/星期时段编辑/保存成功)、orderList.selfDeliveryTag(订单"自配送"标识)等,四文件 key 完全一致。

6. 前端改动点(foodie-store,商家端 PC)

位置 改动
门店信息页 自配送区块:开关 + 模式选择 + 星期×多时段编辑(复用营业时段编辑交互);摊位主隐藏该区块(读接口返回夜市配置时只读展示)
订单列表/详情 selfDelivery=1 显示"自配送"标签;自配送单不显示"等待骑手接单"类状态
配送操作按钮 独立任务(依赖 clarify Q2 暂缓决策):开始配送/确认送达按钮调用 §2 接口;接口先行交付

注意:foodie-store 文件为 CRLF,编辑用 Python 脚本替换(项目规则)。