plan.md 8.9 KB

连锁门店复制商品 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-adminPosFoodController 只负责认证边界和 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-systemruoyi-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:

StoreMenuCopyPreviewResponse preview(Long loginUserId, StoreMenuCopyPreviewRequest request);
StoreMenuCopyResult execute(Long loginUserId, StoreMenuCopyExecuteRequest request);

// conflictStrategy 仅允许 KEEP_TARGET / OVERWRITE_TARGET,空值按 KEEP_TARGET。
  • DTO 仅承载源/目标门店、冲突策略、预览计数、字段差异和执行计数,不使用 Bean Validation 注解。
  • 响应字段固定为 addedCategoryCountmergedCategoryCountaddedProductCountidenticalProductCountdifferentProductCountdifferences,执行结果固定为新增分类/商品、跳过、覆盖四项。
  • 服务接口不依赖 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:

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、无同步状态、无整文件换行变化。

  • [ ] 将规格状态更新为“已实现/待人工联调”,任务清单记录实际测试结果与明确未执行项。