# 连锁门店「复制商品」功能说明

> 版本：v1.1 ｜ 日期：2026-09-03 ｜ 状态：设计已评审定稿
> 适用读者：需求、测试

---

## 一、功能背景

连锁商家（如麦当劳）一个账号下开了多家门店，各家门店的商品菜单基本一致。目前分类和商品都挂在单个门店下，商家新开一家门店只能在商家端逐条重新录入，30 个商品的菜单录入要 1 小时以上，还容易漏录、录错价格。

本功能提供「复制商品」：把一家门店的菜单一键复制到另一家门店，开新店的菜单初始化 1 分钟内完成。

## 二、功能范围

- **复制的内容**：全部分类 + 全部商品，包括价格、图片、商品介绍、属性（加料等）、规格（大小杯/温度等）、排序、是否推荐
- **不复制**：门店资料（店名、地址、统编、税号、客服电话、营业时间）——每家门店这些必然不同
- **实现范围**：商家 PC 端（本期）；商家 App 后续复用同一功能

## 三、怎么用（交互）

### 入口

商家 PC 端「商品管理」页：

- 顶部是门店切换按钮（现状已有），**当前选中的门店就是复制目标**
- 「添加商品」按钮旁边新增「**复制商品**」按钮
- 商家名下门店不足 2 家时，按钮自动隐藏（单店商家完全无感知）

### 操作流程

```
点「复制商品」
   ↓
① 选源门店（下拉框，自动排除当前门店）
   ↓ 选定后自动显示差异概览
② 查看差异概览；如有"内容不同"的商品，选择处理方式
   ↓
③ 点「确定复制」→ 完成提示 → 商品列表自动刷新
```

### 弹窗示意

```
┌─ 复制商品 ──────────────────────┐
│ 从「台北忠孝店」复制到「台北信义店」   │
│                                │
│ 新增分类 1（同名合并 4）           │
│ 新增商品 12                      │
│ 同名且内容一致，跳过 23            │
│                                │
│ ▸ 同名但内容不同：3 个（点击展开）   │
│    麦辣鸡腿堡   价格 65 → 75       │
│    薯条(大)     图片已更换          │
│    可乐(大)     介绍已修改          │
│                                │
│ 这 3 个商品怎么处理？              │
│ ○ 保留本店版本（默认）              │
│ ● 用源店版本覆盖                  │
│                                │
│          [取消]    [确定复制]      │
└────────────────────────────────┘
```

### 特殊状态

| 场景 | 弹窗表现 |
|---|---|
| 两店菜单完全一致 | 显示「已是最新，无新增」，确定按钮不可点 |
| 源门店没有任何商品 | 提示「该门店没有可复制的商品」，确定按钮不可点 |
| 复制完成 | 提示「新增分类 X、新增商品 Y、跳过 Z、覆盖 W」 |

## 四、业务规则（测试依据）

先记住两条判定规则：

- **同一个分类** = 名字 + 语言相同
- **同一个商品** = 名字 + 语言相同（同名但语言不同算**不同**商品，会作为新增补进来——比如本店只有中文「可乐」，源店还有英文「Cola」，复制后英文版也补上）

### 复制时对每个分类/商品的处理

| 情况 | 系统行为 |
|---|---|
| 目标店没有这个分类 | 新建分类 |
| 目标店已有同名分类 | 复用已有分类，新商品挂进去（**不会出现两个"汉堡"分类**） |
| 目标店没有这个商品 | 新增，内容照搬源店，**上下架、审核状态跟源店一致**（复制完即可营业） |
| 同名且内容一致 | 跳过，不动 |
| 同名但内容不同 | **商家选择**：保留本店版本（默认）或 用源店版本覆盖 |

「内容」指：价格、图片、商品介绍、属性（加料等）、规格、排序、是否推荐。

### 覆盖的边界（重要）

选「用源店版本覆盖」时：

- **更新**：价格、图片、介绍、属性、排序、推荐、规格关联
- **不动**：本店的**上下架状态**——例：目标店「薯条」今日临时下架，覆盖后价格图片对齐源店，但**仍是下架**
- **不动**：本店的审核状态
- **不动**：本店已有规格模板里的加价——例：本店「大小杯」模板"大杯+5"是本店自改价，覆盖后**仍是+5**（规格模板全店共享，改它会连带影响本店其他商品，所以不覆盖）

### 其他规则

- 源门店和目标门店不能选同一家
- 权限：主账号可复制自己名下任意两家门店；分管账号只能复制授权范围内的门店
- **可以反复执行**：菜单有变化后再复制一次，只补缺的；显示「新增 0、覆盖 0」就代表两边已一致
- 复制要么全部完成、要么全部不做，不会出现复制一半的情况

## 五、测试用例建议

### 1. 主流程

| 用例 | 前置 | 步骤 | 预期 |
|---|---|---|---|
| TC01 空店全量复制 | 商家有店 X（5 分类 38 商品，含带规格、带属性商品）、空店 Y | X→Y 复制（默认保留） | Y 生成 5 分类 38 商品，内容一致；规格可选且加价正确；图片正常显示 |
| TC02 上下架状态继承 | 源店商品一半上架一半下架 | 复制到空店 | 新店商品上下架状态与源店一致；上架商品顾客端可下单 |
| TC03 重复执行 | TC01 完成后 | 再执行一次相同复制 | 提示新增 0、覆盖 0；数据无变化 |
| TC04 预览与执行一致 | X 有 38 商品，Y 已有 26 个（23 个一致 3 个差异） | 打开预览后确认复制 | 预览计数=新增 12、跳过 23、差异 3；实际执行结果与预览一致 |

### 2. 差异与覆盖

| 用例 | 前置 | 步骤 | 预期 |
|---|---|---|---|
| TC05 差异-保留（默认） | Y 的「麦辣鸡腿堡」价格 65，X 为 75 | 选保留复制 | Y 该商品价格仍 65，其余新增正常 |
| TC06 差异-覆盖内容 | 同上 | 选覆盖复制 | Y 该商品价格变 75，图片/介绍/属性/排序/推荐对齐 X |
| TC07 覆盖不动上下架 | Y 的「薯条」已下架 | 选覆盖复制 | 薯条内容对齐 X，但**仍为下架** |
| TC08 覆盖不动审核 | Y 的商品审核状态与 X 不同 | 选覆盖复制 | Y 审核状态不变 |
| TC09 覆盖不动规格加价 | Y 的「大小杯」模板"大杯+5"，X 为"大杯+3" | 选覆盖复制 | Y 模板仍"大杯+5"；若 X 多关联了「温度」规格，Y 商品补挂「温度」 |

### 3. 匹配规则

| 用例 | 前置 | 预期 |
|---|---|---|
| TC10 分类合并 | Y 已有「汉堡」分类 | 不产生第二个「汉堡」，新增商品挂入已有分类 |
| TC11 多语言独立匹配 | Y 只有中文「可乐」，X 有中英两行 | 英文行新增、中文行按一致/差异规则处理 |
| TC12 源店同名分类脏数据 | X 有两个同名分类 | 与第一个合并，不报错 |
| TC13 名字不同内容相同 | X「麦辣鸡腿堡」、Y「辣鸡腿堡」价格相同 | 判定为不同商品，X 的作为新增 |

### 4. 权限与边界

| 用例 | 前置 | 预期 |
|---|---|---|
| TC14 单店隐藏入口 | 商家只有 1 家门店 | 商品管理页无「复制商品」按钮 |
| TC15 源=目标 | 两个下拉选到同一家（或构造请求） | 系统拒绝，提示错误 |
| TC16 分管账号越权 | 分管账号甲授权 A、B，未授权 C | A→B 成功；伪造参数 A→C 被拒绝，C 数据不变 |
| TC17 源店为空 | 源店无分类无商品 | 提示无可复制，无任何写入 |
| TC18 中途失败 | 模拟复制过程中出错 | 目标店无任何新增（不留半个菜单） |
| TC19 大菜单性能 | 源店 100 商品 | 复制 10 秒内完成，无超时 |
| TC20 多语言界面 | 分别切简中/繁中/英文/越南文打开弹窗 | 文案全部正确翻译，无乱码或显示 key |
