Feature Specification: 商家自配送
Feature Branch: test-202609v2(按用户要求不单独开分支,spec 目录为 specs/029-merchant-self-delivery/)
Created: 2026-09-08
Status: Draft
Input: User description: "商家自配送:商家在门店信息勾选自配送并设置时段(全天/自定义多时段),命中时段的外送订单不进骑手抢单池、由商家自行配送;夜市由夜市主统一配置并对旗下摊位生效、夜市主操作送达;运费归配送方结算;前端显示'商家自配送'。合并订单/同一骑手多单机制不在本次范围(另立需求)。"
变更记录
2026-09-10 范围调整(用户确认)
- 普通商家门店不可设置自配送,仅夜市主可设置夜市是否自配送:
saveMdSelfDeliveryHours 对非夜市主门店行一律拒绝(提示 no.store.selfdelivery.nightmarket.only,六语言)。原 US1/FR-002 中"普通商家配置自己门店"的口径按此收紧;普通商家门店的自配送开关恒为关,订单判定自然走骑手池,判定/快照/结算链路不变。
- PC 端(foodie-store)功能先隐藏:T034-T038 挂起不做(PC 从未开发自配送界面,无需代码改动);商家 App(外部)同步只对夜市主展示设置入口,普通商家不展示。
- 已知缺口备查:
messages_th_TH.properties 缺 029 的 no.store.*/no.order.* 系列 key(本变更的新 key 已补,其余历史 key 待统一补译)。
2026-09-10 追加:夜市主 PC 设置入口(T034 夜市子集恢复)
- 后端新增「我的」读写接口:
GET/POST /chanting/store/{get|save}MyNightMarketSelfDelivery——夜市主登录态只有 userId,接口内部按 user_id+is_night_market=1 反查门店行,前端不传也不信任 mdId(save 覆盖客户端 mdId 防越权);反查不到门店行(普通商家/未建夜市门店)统一拒绝 no.store.selfdelivery.market.missing(六语言)。原 mdId 版接口保留。
- PC(foodie-store):「摊位管理」菜单下新增「自配送设置」(仅 userType==3 可见),新页面
SelfDeliverySettings.vue(开关+全天/自定义+星期多时段,复用整体覆盖保存);i18n 四语言新增 Aside.selfDeliverySettings 与 selfDelivery 命名空间。
- 顺带发现既有缺口:foodie-store
en.js/vi.js 缺整个 Aside 命名空间(本次为新增菜单 key 补了只含该 key 的对象,其余历史 key 未回填);后端 messages_th_TH.properties 缺 029 历史 no.* keys。
Clarifications
Session 2026-09-08
- Q: 自配送运费平台是否抽成? → A: 不抽成,运费全额归配送方(商家/夜市主)
- Q: "开始配送"是否为强制步骤? → A: 可选步骤,允许出餐后直接"确认送达"(状态机允许跳过"配送中")
- Q: 自定义时段的粒度? → A: 按星期几设置多时段(对齐现有营业时段的配置模式)
- Q: 时段边界取值? → A: 含开始时刻、不含结束时刻([开始, 结束)):开始时刻整点下的单算自配送,结束时刻整点下的单不算
User Scenarios & Testing (mandatory)
User Story 1 - 普通商家开启自配送并完成配送 (Priority: P1)
普通商家(自有配送人力的餐饮门店)在门店信息中勾选"自配送",选择全天自配送或自定义时间段(支持多个时间段)。开启后,命中时段的外送订单不再进入骑手抢单池,由商家自己配送:商家照常接单、出餐,出餐后标记"开始配送"、送达后"确认送达",订单完成。
Why this priority: 这是功能的核心价值——让有自配能力的商家脱离平台骑手池,是最主要的业务场景(行业标准做法)。
Independent Test: 商家开启全天自配送后下一笔外送订单,验证订单不进骑手池、骑手端不可见,商家端完成 开始配送→确认送达 后订单完结。
Acceptance Scenarios:
- Given 商家门店已开启全天自配送,When 用户下外送订单并支付成功,Then 订单标记为"商家自配送",不出现在骑手抢单池,也不出现在骑手端任何列表
- Given 一笔已支付的自配送外送订单,When 商家接单并出餐,Then 商家可对订单标记"开始配送",用户端看到配送状态推进
- Given 商家已标记"开始配送",When 商家点击"确认送达",Then 订单进入已完成状态,用户收到送达通知
- Given 一笔已出餐的自配送订单且未标记"开始配送",When 商家直接点击"确认送达",Then 订单进入已完成状态,用户端状态由备餐中直接更新为已送达
- Given 自定义时段为 18:00-21:00,When 用户在 20:59 下即时外送单,Then 该单为自配送订单(即使 21:30 才实际送达)
- Given 自定义时段为 18:00-21:00,When 用户在 21:00:00 下即时外送单,Then 该单照常进入骑手抢单池,走现有骑手流程
User Story 2 - 夜市主统一自配送旗下摊位订单 (Priority: P1)
夜市主对整个夜市开启自配送并设置时段,配置对旗下全部摊位统一生效(摊位商家不单独配置)。夜市内摊位的外送订单由夜市主自己配送:夜市主在商家端可查看旗下所有摊位的自配送订单,出餐后由夜市主逐单执行配送操作直至送达。
Why this priority: 这是需求提出的原始场景(夜市主自己配送多个摊位),配置粒度必须落在夜市级才能保证同一夜市订单配送方式统一、不出现分裂配送。
Independent Test: 夜市主开启夜市自配送后,向夜市内某摊位下一笔外送订单,验证摊位商家端不出现自配送配置入口、订单不进骑手池、夜市主端可见并可完成配送操作。
Acceptance Scenarios:
- Given 夜市主已开启夜市自配送(时段 17:00-23:00),When 用户向该夜市任一摊位下外送单(下单时间在时段内),Then 订单为自配送订单,不进骑手池,夜市主在商家端可看到该订单
- Given 夜市内多个摊位的自配送订单同时存在,When 夜市主执行配送操作,Then 可逐单完成"开始配送/确认送达",每笔订单独立流转至完成
- Given 摊位商家登录商家端,When 查看门店设置,Then 不提供自配送配置入口(配置权仅在夜市主)
- Given 用户一次结账同时购买同一夜市的多个摊位商品(生成多笔摊位子订单),When 下单时间在自配送时段内,Then 全部子订单统一为自配送,由夜市主配送
User Story 3 - 用户端显示"商家自配送" (Priority: P1)
用户在订单列表、订单详情和配送跟踪中看到"商家自配送"标识与对应的配送状态(商家备餐中/商家配送中/已送达),不再显示骑手接单、骑手位置等骑手侧信息。
Why this priority: 用户预期管理是自配送上线的前提——没有标识,用户会因"无人接单"而恐慌或取消订单。
Independent Test: 下一笔自配送订单,在订单列表、详情、跟踪页检查标识与状态文案(四种语言均正确)。
Acceptance Scenarios:
- Given 一笔自配送订单已支付,When 用户查看订单详情/跟踪,Then 显示"商家自配送"标识及商家配送状态,无骑手接单相关提示
- Given 系统语言设置为越南语/繁中/英语,When 查看自配送订单,Then "商家自配送"及状态文案以对应语言显示
User Story 4 - 预约单按预约送达时间判定 (Priority: P2)
用户下单时可选择预约时间送达。预约订单以"预约送达时间"是否落在自配送时段内来判定是否自配送,而不是以下单时刻判定(例如 15:00 下的预约 19:00 送达的单,按 19:00 判定,为自配送)。
Why this priority: 预约单是现有能力,自配送必须与之正确衔接,否则时段判定会错位;相对 P1 场景频次低,故 P2。
Independent Test: 自配送时段 18:00-21:00 时,分别在时段外/时段内下单预约不同送达时间,验证判定跟随预约时间。
Acceptance Scenarios:
- Given 自配送时段 18:00-21:00,When 用户 15:00 下单并预约 19:00 送达,Then 该单为自配送订单
- Given 自配送时段 18:00-21:00,When 用户 19:30 下单并预约次日 12:00 送达(时段外),Then 该单进入骑手抢单池,非自配送
- Given 预约送达时间落在自配送时段内,When 该预约单到临近时间才对商家/配送方展示(现有预约单机制),Then 配送方为商家,流转规则与即时自配送单一致
User Story 5 - 自配送运费结算归配送方 (Priority: P2)
自配送订单的运费结算给配送方:普通商家的自配送订单运费结算给商家;夜市摊位订单的运费结算给夜市主。用户侧运费计价不变(照常支付运费)。到付(货到付款)订单的代收金额归因同步调整为配送方。夜市主与摊位主之间如何分摊运费由双方线下自行处理,系统不做分账。
Why this priority: 涉及资金归属,必须在上线前正确;但相对 P1 的主流程可独立验证,故 P2。
Independent Test: 分别完成一笔普通商家自配送单和一笔夜市自配送单(含到付单),核对账单中运费入账记录的归属方与全额(无平台抽成),并确认不产生任何骑手分成记录。
Acceptance Scenarios:
- Given 普通商家的一笔已送达自配送订单(运费 30 元),When 查看结算账单,Then 运费 30 元全额入账归属该商家,无平台抽成、无骑手分成记录
- Given 夜市摊位的一笔已送达自配送订单,When 查看结算账单,Then 运费全额入账归属夜市主,无平台抽成
- Given 一笔到付的自配送订单,When 配送方确认送达,Then 到付代收记录归因到配送方(商家或夜市主),不指向骑手
User Story 6 - 商家端配置与订单标识 (Priority: P2)
商家在商家端 PC 的门店信息中配置自配送(开关 + 全天/自定义时段),订单列表与详情对自配送订单显示"自配送"标识,配送操作按钮替换为商家的配送操作。
Why this priority: 配置入口是 P1 场景的前提操作,作为独立界面能力单列验收。
Independent Test: 在商家端门店信息页开启自配送并保存自定义时段,验证保存生效且订单列表出现标识。
Acceptance Scenarios:
- Given 商家打开门店信息,When 勾选自配送并选择"自定义时段"、按星期几配置多个时间段后保存,Then 配置保存成功,重新打开显示一致
- Given 商家关闭自配送开关,When 之后下的外送订单,Then 照常进入骑手抢单池(关闭前已判定为自配送的订单不受影响)
Edge Cases
- 时段边界:判定区间含开始时刻、不含结束时刻([开始, 结束)):时段 18:00-21:00 时,18:00:00 整下的单为自配送,21:00:00 整下的单进骑手池
- 下单快照不回滚:订单判定为自配送后,商家中途关闭自配送或修改时段,该订单仍按自配送完成;反之已进骑手池的订单不会因配置开启而变为自配送
- 预约时间跨天:预约次日送达的单按预约时间所在日的时段判定
- 多摊位同夜市:同一夜市的摊位统一跟随夜市配置,不存在"部分摊位自配送、部分进骑手池"的分裂状态
- 售后/取消:自配送订单的取消、售后沿用现有规则,仅无骑手环节(无骑手损失申诉等)
- 骑手侧隔离:任何接口路径(列表、详情、直调操作接口)都不能让骑手看到或操作自配送订单
- 时段配置非法输入:时间段为空、结束时间早于开始时间、时间段重叠时的校验与提示
- 到付确认:到付自配送订单的收款确认由配送方(商家/夜市主)操作
Requirements (mandatory)
Functional Requirements
- FR-001: 门店信息 MUST 提供自配送开关与时段设置:全天模式或自定义模式;自定义模式 MUST 按星期几配置,每天可设置一个或多个时间段,每个时间段含开始与结束时刻(与营业时段的配置模式一致)
- FR-002: 配置权限 MUST 按商家类型区分:
普通商家配置自己门店的自配送【2026-09-10 变更:普通商家门店不可设置,接口一律拒绝,仅夜市主本人可配置】;夜市摊位门店 MUST NOT 提供独立配置入口,其配送方式由所属夜市的夜市主统一配置并对该夜市全部摊位生效
- FR-003: 系统 MUST 在外送订单创建并支付成功时判定配送方式并固化到订单:即时单按下单时刻判定,预约单按预约送达时间判定;时段判定含开始时刻、不含结束时刻([开始, 结束));命中自配送时段则订单永久标记为"商家自配送",判定结果 MUST NOT 受后续配置变更影响
- FR-004: 自配送订单 MUST NOT 进入骑手抢单池,MUST NOT 出现在骑手端任何订单列表,骑手 MUST NOT 能通过任何操作接口接单或操作该订单
- FR-005: 商家端 MUST 为自配送订单提供配送操作:出餐后可标记"开始配送"与"确认送达";"开始配送"为可选步骤,MUST 允许出餐后直接"确认送达"(状态机允许跳过"配送中");"确认送达"后订单进入完成状态并记录送达时间;操作后配送状态向用户可见
- FR-006: 夜市主 MUST 能在商家端查看旗下全部摊位的自配送订单,并对其执行与商家自配送一致的配送操作(逐单操作)
- FR-007: 用户端 MUST 在自配送订单的列表、详情与配送跟踪中显示"商家自配送"标识及商家配送状态,MUST NOT 显示骑手接单/骑手信息相关内容
- FR-008: 自配送订单的运费 MUST 结算给配送方:普通商家门店订单结算给商家,夜市摊位订单结算给夜市主;平台 MUST NOT 对自配送运费抽成,运费全额归配送方;到付订单的代收确认与归因 MUST 指向配送方;系统 MUST NOT 为自配送订单生成骑手分成记录
- FR-009: 自配送订单的关键节点(商家接单、出餐、开始配送、已送达)MUST 向用户推送通知;MUST NOT 触发任何面向骑手的接单/派单推送
- FR-010: 商家端订单列表与详情 MUST 对自配送订单显示"自配送"标识
- FR-011: 本功能所有新增用户可见文案 MUST 提供越南语、简体中文、繁体中文、英语四种语言
- FR-012: 自配送时段配置 MUST 校验:时段非空、结束时刻晚于开始时刻;时段外的外送订单照常进入骑手抢单池,走现有骑手配送流程
Key Entities (include if feature involves data)
- 门店自配送配置: 开关、时段模式(全天/自定义)、按星期几的时间段列表;归属规则:普通门店归属该门店商家,摊位门店归属其所属夜市(由夜市主持有)
- 订单配送方式: 订单上的自配送判定标记(下单时固化),以及订单配送状态在商家自配送语境下的流转(备餐→开始配送→已送达)
- 配送方: 自配送订单的履约人与运费结算归属方;普通商家门店为商家本人,夜市摊位订单为夜市主
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: 命中自配送时段的已支付外送订单 100% 不出现在骑手抢单池与骑手端任何列表(含直调接口探测)
- SC-002: 自配送订单从出餐到完结,配送方操作不超过两步(开始配送、确认送达),且每一步用户端状态即时更新
- SC-003: 四种语言下,"商家自配送"标识与配送状态文案均正确显示,无硬编码单一语言文案
- SC-004: 已完结自配送订单的运费 100% 全额入账归属正确配送方(商家/夜市主),无平台抽成、无骑手分成记录
- SC-005: 时段判定(含时段起点/终点边界、预约单按预约时间、跨天预约)在验收用例集中判定正确率 100%
Assumptions
- 自配送仅影响外送订单;自取、堂食、餐桌码扫码点单的既有流程不受影响
- 用户侧运费计价规则不变,用户照常支付运费,仅结算归属变化
- 平台对自配送运费不抽成,运费全额归配送方(2026-09-08 已确认)
- 夜市主与摊位主之间的运费分摊、线下结算为线下行为,系统不做自动分账
- "合并订单/同一配送人一趟多单"的批量合并操作机制不在本功能范围,由独立需求实现;本功能中夜市主对旗下订单逐单操作
- 客户 App(uni-app)与骑手端的界面改造由对应客户端配合交付;本仓库范围包含后端与商家端 PC(含门店设置、订单标识、配送操作、多语言)
- 推送渠道沿用现有推送体系(iOS 云函数 + 消息入库),不新增渠道