|
|
@@ -0,0 +1,130 @@
|
|
|
+# 功能规格:连锁门店复制商品(菜单复制)
|
|
|
+
|
|
|
+**功能标识**:`026-store-menu-copy`
|
|
|
+
|
|
|
+**创建日期**:2026-09-03
|
|
|
+
|
|
|
+**状态**:设计定稿,**待排期实现**(2026-09-03 与需求方确认设计后暂缓开发;恢复时从 plan → tasks → implement 继续,禅道对外文档见同目录 zentao-design.md)
|
|
|
+
|
|
|
+**输入**:连锁商家(如麦当劳型)各门店商品菜单基本一致,新开门店或补齐菜单时逐条重录商品费时费力。需要一个「复制商品」功能,把一家门店的分类+商品一次性复制到另一家门店;复制前可预览差异,同名但内容不同的商品由商家选择保留本店版本或用源店版本覆盖;不破坏目标门店已有商品;可重复执行(幂等)。
|
|
|
+
|
|
|
+## 用户场景与测试
|
|
|
+
|
|
|
+### 用户故事 1 - 把源门店菜单复制到新开门店(Priority: P1)
|
|
|
+
|
|
|
+连锁商家主账号新开了一家门店(空店),在商家 PC 端商品管理页选择该门店,点击「复制商品」,选择一家老门店作为源,确认后系统把源门店全部分类和商品(含价格、图片、介绍、属性、规格、排序、推荐)复制到新门店,新门店立即可对外营业。
|
|
|
+
|
|
|
+**为什么这个优先级**:这是功能的核心价值——开新店初始化菜单,替代逐条录入;只要这一个故事成立,功能即产生价值(MVP)。
|
|
|
+
|
|
|
+**独立测试**:商家 A 名下有门店 X(有 5 分类 38 商品)和空门店 Y,执行复制 X→Y 后,Y 拥有同名同内容的 5 分类 38 商品,商品上架与审核状态继承源店。
|
|
|
+
|
|
|
+**验收场景**:
|
|
|
+
|
|
|
+1. **假如** 商家有两家门店且目标店为空,**当** 商家从源店复制到目标店,**那么** 目标店生成与源店一致的全部分类与商品(名字、价格、图片、介绍、属性、规格、排序、推荐)。
|
|
|
+2. **假如** 复制完成,**当** 商家查看目标店商品列表,**那么** 新增商品的图片正常显示(图片为 URL 引用,不重复上传文件)。
|
|
|
+3. **假如** 源店某商品为上架状态,**当** 复制到空店,**那么** 新店该商品为上架状态、可直接被顾客下单。
|
|
|
+4. **假如** 商品带规格(如大/中/杯加价),**当** 复制到空店,**那么** 目标店生成同名规格模板与规格值,且商品正确关联,顾客下单可选规格。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 用户故事 2 - 复制前预览差异,回答「差多少/是否已同步」(Priority: P1)
|
|
|
+
|
|
|
+商家选择源门店后、确认复制前,系统展示差异概览:将新增几个分类(同名合并几个)、新增几个商品、同名且内容一致跳过几个、同名但内容不同几个;差异商品可展开查看明细(商品名+哪个字段变了+前后值)。商家据此决定是否复制以及差异商品如何处理。
|
|
|
+
|
|
|
+**为什么这个优先级**:预览是复制决策的依据,也是轻量版的「同步状态」——差异为 0 即已同步,无需任何状态记录。
|
|
|
+
|
|
|
+**独立测试**:门店 X 有商品「麦辣鸡腿堡」价格 75,门店 Y 同名商品价格 65;打开复制弹窗选 X→Y,预览显示「内容不同 1 个:麦辣鸡腿堡 价格 65 → 75」。
|
|
|
+
|
|
|
+**验收场景**:
|
|
|
+
|
|
|
+1. **假如** 两店菜单完全一致,**当** 商家打开复制弹窗,**那么** 预览显示新增 0、差异 0(等价于「已是最新」)。
|
|
|
+2. **假如** 源店有 38 个商品、目标店已有其中 26 个(23 个内容一致、3 个内容不同),**当** 打开预览,**那么** 显示「新增分类 X(同名合并 Y)、新增商品 12、一致跳过 23、内容不同 3」,且 3 个差异商品可展开查看字段级前后值。
|
|
|
+3. **假如** 预览与执行之间商家未做任何修改,**当** 确认复制,**那么** 实际执行结果与预览计数一致。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 用户故事 3 - 复制到非空门店:差异商品由商家选择保留或覆盖(Priority: P2)
|
|
|
+
|
|
|
+目标门店已有商品时仍允许复制(追加模式):同名分类合并、同名且内容一致的商品跳过、**同名但内容不同的商品由商家整体选择「保留本店版本(默认)」或「用源店版本覆盖」**。覆盖只更新内容字段,不动目标店商品的上架状态与审核状态。
|
|
|
+
|
|
|
+**为什么这个优先级**:解除「仅空店可复制」的死限制(商家先手建了几个商品就废掉功能),同时给连锁商家一个手动「推新品/改价」到其他门店的途径;但它是破坏性决策,排在预览之后。
|
|
|
+
|
|
|
+**独立测试**:门店 Y 有「麦辣鸡腿堡」价格 65(本店自改)、目标店今日已下架「薯条」;选覆盖复制 X→Y 后,「麦辣鸡腿堡」价格变 75,「薯条」内容对齐源店但**仍保持本店下架状态**。
|
|
|
+
|
|
|
+**验收场景**:
|
|
|
+
|
|
|
+1. **假如** 差异商品 3 个且商家选「保留本店版本」,**当** 确认复制,**那么** 3 个差异商品完全不变,仅新增缺失的分类与商品。
|
|
|
+2. **假如** 差异商品 3 个且商家选「用源店版本覆盖」,**当** 确认复制,**那么** 3 个商品的**内容字段**(价格、图片、介绍、属性、规格、排序、推荐)对齐源店,但上架状态、审核状态与商品 id 保持本店原样。
|
|
|
+3. **假如** 目标店某差异商品今日临时下架,**当** 覆盖复制,**那么** 该商品内容被更新但不会因此被拉回上架。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 用户故事 4 - 权限与入口控制(Priority: P2)
|
|
|
+
|
|
|
+只有同时具备源、目标两店操作权限的商家账号可以执行复制:主账号对自己名下所有门店可用;分管账号仅当两店都在其授权范围内可用。源门店与目标门店不能是同一家。商家名下门店数不足 2 家时,PC 端不显示「复制商品」入口。
|
|
|
+
|
|
|
+**为什么这个优先级**:复制是跨店写操作,必须防止越权改动未授权门店的菜单。
|
|
|
+
|
|
|
+**独立测试**:分管账号甲被授权门店 A、B,未授权 C;甲复制 A→B 成功;甲复制 A→C(伪造参数)被后端拒绝;甲的界面上不出现涉及 C 的选项。
|
|
|
+
|
|
|
+**验收场景**:
|
|
|
+
|
|
|
+1. **假如** 分管账号甲负责 A、B,**当** 甲发起 A→B 复制,**那么** 系统允许。
|
|
|
+2. **假如** 甲未负责 C,**当** 甲以伪造门店 id 发起 A→C 复制,**那么** 后端拒绝且 C 数据不变。
|
|
|
+3. **假如** 请求中源门店与目标门店相同,**当** 发起预览或复制,**那么** 系统拒绝并提示。
|
|
|
+4. **假如** 商家名下只有 1 家门店,**当** 其打开商品管理页,**那么** 看不到「复制商品」按钮。
|
|
|
+
|
|
|
+### 边界场景
|
|
|
+
|
|
|
+- 源门店没有任何分类/商品时:预览显示无可复制内容,复制按钮置灰或提示,不产生写入。
|
|
|
+- 同名但语言不同的商品(如本店只有中文「可乐」、源店另有英文「Cola」):按(名字+语言)判定为不同商品,英文行作为新增复制(多语言行各自独立匹配)。
|
|
|
+- 源店存在两个同名分类(历史脏数据):目标店与第一个匹配的分类合并,不产生额外重复。
|
|
|
+- 复制执行中商家在另一端同时改菜单:以复制事务开始时读到的数据为准,整批成功或整批回滚(单商家操作,不做跨端加锁)。
|
|
|
+- 超大菜单(数百商品):批量写入,正常网络下整次复制在 10 秒内完成,不超时。
|
|
|
+- 商品图片:直接引用源店图片 URL,不复制图片文件;后续删除源店商品不影响目标店(图片为共享 URL)。
|
|
|
+
|
|
|
+## 需求
|
|
|
+
|
|
|
+### 功能需求
|
|
|
+
|
|
|
+- **FR-001**: 系统 MUST 提供复制预览能力:给定源门店与目标门店,返回新增分类数(含同名合并数)、新增商品数、同名一致跳过数、内容不同商品数及差异明细(商品名、语言、变更字段、前后值)。
|
|
|
+- **FR-002**: 系统 MUST 提供复制执行能力:按预览口径写入新增分类/商品,并按商家选择处理差异商品(保留或覆盖)。
|
|
|
+- **FR-003**: 分类匹配规则 MUST 为(分类名+语言)相同视为同一分类:目标店已有则复用(新增商品挂入),否则新建分类。
|
|
|
+- **FR-004**: 商品匹配规则 MUST 为(商品名+语言)相同视为同一商品;内容比对字段为:价格、图片、介绍、属性(foodSku)、规格集合(模板+规格值)、排序、推荐。
|
|
|
+- **FR-005**: 新增商品 MUST 继承源店的上架状态与审核状态,图片直接引用源店 URL;所属分类指向目标店合并/新建后的分类。
|
|
|
+- **FR-006**: 覆盖差异商品 MUST 只更新内容字段(同 FR-004 比对字段),MUST NOT 修改目标店商品的 id、上架状态、审核状态。
|
|
|
+- **FR-007**: 差异商品处理方式 MUST 默认为「保留本店版本」,覆盖为显式选择。
|
|
|
+- **FR-008**: 复制 MUST 幂等:同参数重复执行,第二次新增 0、覆盖 0(全部命中跳过)。
|
|
|
+- **FR-009**: 系统 MUST 校验操作者对源、目标两店均有权限,且两店不能相同;无权限或不合法时拒绝且不产生任何写入。
|
|
|
+- **FR-010**: 复制执行 MUST 在单事务内完成:全部成功或全部回滚。
|
|
|
+- **FR-011**: 规格 MUST 随商品复制:新增商品在目标店按(规格名+语言)复用已有规格模板,缺失则新建模板与规格值,并重建商品与规格的关联;覆盖时仅把商品的规格**关联集合**对齐源店(缺失模板同样复用/新建),MUST NOT 修改目标店已有规格模板自身的规格值——规格模板是门店内共享资产,改动会波及其他商品;规格值的同步不在本功能范围。
|
|
|
+- **FR-012**: PC 商家端商品管理页 MUST 提供「复制商品」入口(当前查看门店为目标店),门店数不足 2 家时隐藏;弹窗文案、按钮、结果提示 MUST 覆盖四种界面语言(中/繁/英/越)。
|
|
|
+- **FR-013**: 复制完成后 MUST 向商家反馈结果计数(新增分类、新增商品、跳过、覆盖)并刷新商品列表。
|
|
|
+- **FR-014**: 本功能 MUST NOT 引入数据库结构变更(零 DDL)、MUST NOT 新增同步状态存储——差异一律实时计算。
|
|
|
+
|
|
|
+### 关键实体
|
|
|
+
|
|
|
+- **门店(pos_store)**:连锁商家的经营单元,归属商家主账号(user_id);一个主账号多家门店即「连锁」。
|
|
|
+- **分类(pos_fenlei)**:门店内的商品分类(名称、图片、排序、语言),挂门店(mendid)。
|
|
|
+- **商品(pos_food)**:门店内可售单品(名称、价格、图片、介绍、属性、排序、推荐、语言、上架状态、审核状态),挂门店(mdid)+分类(fl_id)。
|
|
|
+- **规格(food_specs / food_specs_value / food_spec_relation)**:规格模板(店级作用域)→ 规格值(加价选项)→ 商品关联(多对多)。
|
|
|
+- **商家账号(主账号/分管账号)**:主账号拥有全部门店;分管账号按 022 授权范围访问门店。
|
|
|
+
|
|
|
+## 成功标准
|
|
|
+
|
|
|
+### 可度量结果
|
|
|
+
|
|
|
+- **SC-001**: 连锁商家把一家门店的完整菜单(30 个商品以内)复制到另一家门店,全程(含预览确认)操作时间在 1 分钟以内,对比逐条录入(30 个商品约需 1 小时以上)效率提升 90% 以上。
|
|
|
+- **SC-002**: 100 个商品规模的门店单次复制在 10 秒内完成且无超时报错。
|
|
|
+- **SC-003**: 同参数重复执行第二次复制,新增与覆盖计数均为 0(幂等可验证)。
|
|
|
+- **SC-004**: 单店商家在界面上感知不到本功能(无入口、无文案干扰)。
|
|
|
+- **SC-005**: 覆盖操作后,目标门店商品的上架状态 100% 保持不变(抽查验证)。
|
|
|
+
|
|
|
+## 假设
|
|
|
+
|
|
|
+- 分管账号的门店授权模型沿用 022(merchantStoreAccessService 权限口径),本功能不新增角色或权限类型。
|
|
|
+- 商家 App(uni-app)端不在本期实现范围;本功能输出的接口与交互设计可供 App 端后续直接复用。
|
|
|
+- 不做连锁菜单统一管理/定时同步/逐条勾选差异(原方案 3),若后续真实使用提出需求再立项。
|
|
|
+- 商品多语言行为按现状:同一商品的各语言行相互独立,复制按(名字+语言)逐行匹配,不做跨语言语义合并。
|
|
|
+- 图片存储为共享 URL,无需复制文件,也不处理图片清理。
|
|
|
+- 预览与执行之间存在秒级时间窗,两窗口径一致即可,不保证绝对实时一致(单商家操作场景可接受)。
|