# 功能规格：连锁门店复制商品（菜单复制）

**功能标识**：`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，无需复制文件，也不处理图片清理。
- 预览与执行之间存在秒级时间窗，两窗口径一致即可，不保证绝对实时一致（单商家操作场景可接受）。
