# 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 防越权);非夜市主(user_type!=3)统一拒绝 `no.store.selfdelivery.market.missing`(六语言)。原 mdId 版接口保留。 - 【2026-10-08 变更】实测全库不存在任何 `is_night_market=1` 门店行(无创建路径产出过它),原"反查不到即拒绝"使所有夜市主打不开配置页。改为:GET 对夜市主在锚点行缺失时返回默认未启用配置(enabled=false、mode=null、hours=[],前端 `data.mode||1` 兼容);SAVE 由 `SelfDeliveryService.resolveOrCreateNightMarketStoreByOwner` 首次保存时自动补建锚点行(user_id=夜市主、is_night_market=1、is_stall=0、pos_name=账号昵称的纯配置行);后建摊位经 night_market_id 照常解析。C 端不受影响(夜市列表读 info_user、摊位列表过滤 is_stall=1)。 - 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**: 1. **Given** 商家门店已开启全天自配送,**When** 用户下外送订单并支付成功,**Then** 订单标记为"商家自配送",不出现在骑手抢单池,也不出现在骑手端任何列表 2. **Given** 一笔已支付的自配送外送订单,**When** 商家接单并出餐,**Then** 商家可对订单标记"开始配送",用户端看到配送状态推进 3. **Given** 商家已标记"开始配送",**When** 商家点击"确认送达",**Then** 订单进入已完成状态,用户收到送达通知 4. **Given** 一笔已出餐的自配送订单且未标记"开始配送",**When** 商家直接点击"确认送达",**Then** 订单进入已完成状态,用户端状态由备餐中直接更新为已送达 5. **Given** 自定义时段为 18:00-21:00,**When** 用户在 20:59 下即时外送单,**Then** 该单为自配送订单(即使 21:30 才实际送达) 6. **Given** 自定义时段为 18:00-21:00,**When** 用户在 21:00:00 下即时外送单,**Then** 该单照常进入骑手抢单池,走现有骑手流程 --- ### User Story 2 - 夜市主统一自配送旗下摊位订单 (Priority: P1) 夜市主对整个夜市开启自配送并设置时段,配置对旗下全部摊位统一生效(摊位商家不单独配置)。夜市内摊位的外送订单由夜市主自己配送:夜市主在商家端可查看旗下所有摊位的自配送订单,出餐后由夜市主逐单执行配送操作直至送达。 **Why this priority**: 这是需求提出的原始场景(夜市主自己配送多个摊位),配置粒度必须落在夜市级才能保证同一夜市订单配送方式统一、不出现分裂配送。 **Independent Test**: 夜市主开启夜市自配送后,向夜市内某摊位下一笔外送订单,验证摊位商家端不出现自配送配置入口、订单不进骑手池、夜市主端可见并可完成配送操作。 **Acceptance Scenarios**: 1. **Given** 夜市主已开启夜市自配送(时段 17:00-23:00),**When** 用户向该夜市任一摊位下外送单(下单时间在时段内),**Then** 订单为自配送订单,不进骑手池,夜市主在商家端可看到该订单 2. **Given** 夜市内多个摊位的自配送订单同时存在,**When** 夜市主执行配送操作,**Then** 可逐单完成"开始配送/确认送达",每笔订单独立流转至完成 3. **Given** 摊位商家登录商家端,**When** 查看门店设置,**Then** 不提供自配送配置入口(配置权仅在夜市主) 4. **Given** 用户一次结账同时购买同一夜市的多个摊位商品(生成多笔摊位子订单),**When** 下单时间在自配送时段内,**Then** 全部子订单统一为自配送,由夜市主配送 --- ### User Story 3 - 用户端显示"商家自配送" (Priority: P1) 用户在订单列表、订单详情和配送跟踪中看到"商家自配送"标识与对应的配送状态(商家备餐中/商家配送中/已送达),不再显示骑手接单、骑手位置等骑手侧信息。 **Why this priority**: 用户预期管理是自配送上线的前提——没有标识,用户会因"无人接单"而恐慌或取消订单。 **Independent Test**: 下一笔自配送订单,在订单列表、详情、跟踪页检查标识与状态文案(四种语言均正确)。 **Acceptance Scenarios**: 1. **Given** 一笔自配送订单已支付,**When** 用户查看订单详情/跟踪,**Then** 显示"商家自配送"标识及商家配送状态,无骑手接单相关提示 2. **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**: 1. **Given** 自配送时段 18:00-21:00,**When** 用户 15:00 下单并预约 19:00 送达,**Then** 该单为自配送订单 2. **Given** 自配送时段 18:00-21:00,**When** 用户 19:30 下单并预约次日 12:00 送达(时段外),**Then** 该单进入骑手抢单池,非自配送 3. **Given** 预约送达时间落在自配送时段内,**When** 该预约单到临近时间才对商家/配送方展示(现有预约单机制),**Then** 配送方为商家,流转规则与即时自配送单一致 --- ### User Story 5 - 自配送运费结算归配送方 (Priority: P2) 自配送订单的运费结算给配送方:普通商家的自配送订单运费结算给商家;夜市摊位订单的运费结算给夜市主。用户侧运费计价不变(照常支付运费)。到付(货到付款)订单的代收金额归因同步调整为配送方。夜市主与摊位主之间如何分摊运费由双方线下自行处理,系统不做分账。 **Why this priority**: 涉及资金归属,必须在上线前正确;但相对 P1 的主流程可独立验证,故 P2。 **Independent Test**: 分别完成一笔普通商家自配送单和一笔夜市自配送单(含到付单),核对账单中运费入账记录的归属方与全额(无平台抽成),并确认不产生任何骑手分成记录。 **Acceptance Scenarios**: 1. **Given** 普通商家的一笔已送达自配送订单(运费 30 元),**When** 查看结算账单,**Then** 运费 30 元全额入账归属该商家,无平台抽成、无骑手分成记录 2. **Given** 夜市摊位的一笔已送达自配送订单,**When** 查看结算账单,**Then** 运费全额入账归属夜市主,无平台抽成 3. **Given** 一笔到付的自配送订单,**When** 配送方确认送达,**Then** 到付代收记录归因到配送方(商家或夜市主),不指向骑手 --- ### User Story 6 - 商家端配置与订单标识 (Priority: P2) 商家在商家端 PC 的门店信息中配置自配送(开关 + 全天/自定义时段),订单列表与详情对自配送订单显示"自配送"标识,配送操作按钮替换为商家的配送操作。 **Why this priority**: 配置入口是 P1 场景的前提操作,作为独立界面能力单列验收。 **Independent Test**: 在商家端门店信息页开启自配送并保存自定义时段,验证保存生效且订单列表出现标识。 **Acceptance Scenarios**: 1. **Given** 商家打开门店信息,**When** 勾选自配送并选择"自定义时段"、按星期几配置多个时间段后保存,**Then** 配置保存成功,重新打开显示一致 2. **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 云函数 + 消息入库),不新增渠道