# 连锁门店复制商品 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在商家 PC 商品管理页提供跨授权门店的菜单差异预览与整批复制,支持保留本店或覆盖差异商品,并保证幂等、事务性及规格关联正确。 **Architecture:** `ruoyi-system` 新增独立的 `StoreMenuCopyService`,批量读取源店和目标店的分类、商品、规格模板、规格值与关联,使用“名称+语言”键实时生成预览;执行时在同一事务内重新计算并写入。`ruoyi-admin` 的 `PosFoodController` 只负责认证边界和 DTO 转发;`foodie-store` 新增独立弹窗组件完成源店选择、预览、冲突策略与结果反馈。平台 PC 不在本功能范围内。 **Tech Stack:** Java 21、Spring Boot、MyBatis-Plus/MyBatis XML、JUnit 5、Mockito、Vue 2、Element UI、vue-i18n。 ## Global Constraints - Controller 必须使用 `@RequestHeader String token`、明确的 `@RequestBody` DTO,禁止 `Map` 入参。 - 源门店与目标门店必须不同,且操作人必须同时拥有两店当前访问权限;权限判断复用 `MerchantStoreAccessService`。 - 分类和商品均按“名称+语言”精确匹配;同名不同语言作为不同记录。 - 新增商品继承源店上下架和审核状态;覆盖不得修改目标商品 ID、上下架状态和审核状态。 - 规格模板按“规格名+语言”复用;缺失模板连同规格值复制;覆盖只调整商品关联,不修改目标店已有模板及规格值。 - 执行必须在一个事务内完成;不增加数据库表、字段或同步状态,不执行 SQL 迁移。 - 商家端所有新文案同步维护 `zh/tw/en/vi`,保持原 CRLF;平台 PC 不修改。 - 测试使用 JDK 21;后端定向测试后运行 `ruoyi-system` 与 `ruoyi-admin` 相关构建,商家端运行生产构建。 --- ### Task 1: 定义菜单复制 DTO 与服务接口 **Files:** - Create: `ruoyi-system/src/main/java/com/ruoyi/system/dto/menu/StoreMenuCopyPreviewRequest.java` - Create: `ruoyi-system/src/main/java/com/ruoyi/system/dto/menu/StoreMenuCopyExecuteRequest.java` - Create: `ruoyi-system/src/main/java/com/ruoyi/system/dto/menu/StoreMenuCopyPreviewResponse.java` - Create: `ruoyi-system/src/main/java/com/ruoyi/system/dto/menu/StoreMenuCopyResult.java` - Create: `ruoyi-system/src/main/java/com/ruoyi/system/service/StoreMenuCopyService.java` **Interfaces:** ```java StoreMenuCopyPreviewResponse preview(Long loginUserId, StoreMenuCopyPreviewRequest request); StoreMenuCopyResult execute(Long loginUserId, StoreMenuCopyExecuteRequest request); // conflictStrategy 仅允许 KEEP_TARGET / OVERWRITE_TARGET,空值按 KEEP_TARGET。 ``` - [ ] DTO 仅承载源/目标门店、冲突策略、预览计数、字段差异和执行计数,不使用 Bean Validation 注解。 - [ ] 响应字段固定为 `addedCategoryCount`、`mergedCategoryCount`、`addedProductCount`、`identicalProductCount`、`differentProductCount`、`differences`,执行结果固定为新增分类/商品、跳过、覆盖四项。 - [ ] 服务接口不依赖 `com.ruoyi.app.*`,保持模块依赖 `ruoyi-admin -> ruoyi-system`。 ### Task 2: 测试先行实现实时差异预览 **Files:** - Create: `ruoyi-system/src/test/java/com/ruoyi/system/service/StoreMenuCopyServiceTest.java` - Create: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/StoreMenuCopyServiceImpl.java` **Test breaks caught:** 错误匹配语言、漏报价格/图片/介绍/属性/排序/推荐/规格关联差异、重复分类计数错误、越权或同店预览被放行。 - [ ] 写失败测试:源店 2 个分类、3 个商品,对目标店构造“新增、完全一致、内容不同”各一项,手工断言全部预览计数和字段差异。 - [ ] 写失败测试:同名不同语言必须新增;源店重复同名分类只映射到第一项,不产生额外重复分类。 - [ ] 写失败测试:源目标相同、任一门店越权、空门店 ID 均抛出国际化业务异常,且不调用写 Mapper。 - [ ] 运行 `mvn -pl ruoyi-system -Dtest=StoreMenuCopyServiceTest test`,确认因服务实现缺失而 RED。 - [ ] 实现批量快照读取、稳定键匹配和差异生成;预览全程只读。 - [ ] 重跑定向测试确认 GREEN。 ### Task 3: 测试先行实现事务复制、覆盖与规格映射 **Files:** - Modify: `ruoyi-system/src/test/java/com/ruoyi/system/service/StoreMenuCopyServiceTest.java` - Modify: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/StoreMenuCopyServiceImpl.java` **Test breaks caught:** 新商品状态丢失、覆盖篡改目标状态、已有规格模板被修改、遗漏规格关联、第二次执行重复写入、异常时缺少事务边界。 - [ ] 写失败测试:空目标店复制后新增分类、商品、缺失规格模板/规格值及商品规格关联,新增商品继承上下架和审核状态。 - [ ] 写失败测试:`KEEP_TARGET` 不更新差异商品;`OVERWRITE_TARGET` 更新内容和规格关联,但保持目标 ID、上下架、审核状态。 - [ ] 写失败测试:目标店已有同名语言规格模板时复用且不更新模板/规格值;重复执行全部转为跳过。 - [ ] 运行定向测试确认新增断言 RED。 - [ ] 在 `@Transactional(rollbackFor = Exception.class)` 的 `execute` 中重新加载快照并完成最小写入;所有查询使用 MyBatis-Plus 参数绑定,不拼接 SQL。 - [ ] 重跑定向测试确认 GREEN,并检查异常路径不会在权限验证前写入。 ### Task 4: 暴露认证接口并补齐后端国际化 **Files:** - Modify: `ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosFoodController.java` - Create: `ruoyi-admin/src/test/java/com/ruoyi/app/mendian/StoreMenuCopyControllerTest.java` - Modify: `ruoyi-admin/src/main/resources/i18n/messages.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_en_US.properties` - Modify: `ruoyi-admin/src/main/resources/i18n/messages_vi.properties` **Interfaces:** ```text POST /chanting/food/menu-copy/preview POST /chanting/food/menu-copy/execute Header: token Body: { sourceStoreId, targetStoreId [, conflictStrategy] } ``` - [ ] 写 Controller 失败测试,断言两个接口声明 `@RequestHeader String token` 和 `@RequestBody` DTO,并把 `AuthContext.requireUserId()` 传给服务。 - [ ] 增加 `@Anonymous`、`@Auth(session = true)` 和两个 POST 方法,不从 token 文本自行解析用户 ID。 - [ ] 五个后端资源文件补齐参数缺失、同店复制、非法冲突策略、无权限等消息 key。 - [ ] 运行 `mvn -pl ruoyi-admin -am -Dtest=StoreMenuCopyControllerTest,StoreMenuCopyServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`。 ### Task 5: 商家 PC 复制弹窗与四语言 **Files:** - Modify: `E:/QtwCode/foodie/foodie-store/src/api/food.js` - Create: `E:/QtwCode/foodie/foodie-store/src/components/StoreMenuCopyDialog.vue` - Modify: `E:/QtwCode/foodie/foodie-store/src/views/AcidrollingActual.vue` - Modify: `E:/QtwCode/foodie/foodie-store/src/lang/zh.js` - Modify: `E:/QtwCode/foodie/foodie-store/src/lang/tw.js` - Modify: `E:/QtwCode/foodie/foodie-store/src/lang/en.js` - Modify: `E:/QtwCode/foodie/foodie-store/src/lang/vi.js` - [ ] API 模块增加 `previewStoreMenuCopy(data)` 与 `executeStoreMenuCopy(data)`,均走现有 token 请求链。 - [ ] 商品管理页仅在 `mdlist.length >= 2` 时显示复制按钮,当前 `dqmd` 为目标门店,源门店下拉自动排除目标店。 - [ ] 弹窗在选择源店后请求预览,显示新增分类、合并分类、新增商品、一致跳过、差异商品和字段级前后值。 - [ ] 差异商品默认 `KEEP_TARGET`,用户显式选择后才能使用 `OVERWRITE_TARGET`;无变化或源店为空时禁用执行按钮。 - [ ] 成功后显示新增分类/商品、跳过、覆盖计数,关闭弹窗并通知父页面刷新分类与商品列表。 - [ ] 四语言新增相同的 `menuCopy` 嵌套 key,不硬编码用户可见文本;保持全部修改文件 CRLF。 ### Task 6: 回归、安全和交付检查 **Files:** - Modify: `specs/026-store-menu-copy/spec.md` - Modify: `specs/026-store-menu-copy/tasks.md` - [ ] 使用 JDK 21 运行菜单复制定向测试、`ruoyi-system` 全量测试和 `ruoyi-admin` 相关测试/构建。 - [ ] 在 `foodie-store` 运行 `npm run build`;确认平台 PC 无需修改。 - [ ] 安全检查:无 Map Controller 入参、无字符串 SQL、两店权限先于读取/写入、无 token/敏感信息日志、覆盖不接受客户端商品字段。 - [ ] 差异检查:三仓库仅包含本功能文件;无 DDL、无同步状态、无整文件换行变化。 - [ ] 将规格状态更新为“已实现/待人工联调”,任务清单记录实际测试结果与明确未执行项。