|
|
@@ -0,0 +1,166 @@
|
|
|
+# Story 006.014: 订单提交快照商品名称优化
|
|
|
+
|
|
|
+## Status
|
|
|
+Draft
|
|
|
+
|
|
|
+## Story
|
|
|
+**As a** 用户,
|
|
|
+**I want** 订单快照中的商品名称包含完整的父商品名称和规格信息,
|
|
|
+**so that** 订单页面能正确显示商品信息,与购物车显示逻辑保持一致
|
|
|
+
|
|
|
+## Acceptance Criteria
|
|
|
+1. 提交订单时,子商品的快照商品名称包含完整的父商品名称和规格信息(例如:"连衣裙 红色 大码")
|
|
|
+2. 单规格商品的快照商品名称保持不变
|
|
|
+3. 订单详情页面正确显示完整的商品信息
|
|
|
+4. 现有功能不受影响,无回归问题
|
|
|
+
|
|
|
+## Tasks / Subtasks
|
|
|
+- [ ] 任务1:分析当前订单商品快照逻辑 (AC: 1, 2, 3, 4)
|
|
|
+ - [ ] 检查 `packages/orders-module-mt/src/services/order.mt.service.ts` 中的 `createOrder` 方法
|
|
|
+ - [ ] 分析当前 `goodsName` 字段的赋值逻辑(第139行:`goodsName: info.goods.name`)
|
|
|
+ - [ ] 理解商品实体结构,确认 `spuId` 字段的使用方式
|
|
|
+ - [ ] 检查购物车商品名称显示逻辑,确保与订单快照逻辑保持一致
|
|
|
+- [ ] 任务2:实现子商品快照名称优化逻辑 (AC: 1, 2, 4)
|
|
|
+ - [ ] 在 `createOrder` 方法的商品循环中,判断商品是否为子商品(通过 `spuId` 字段)
|
|
|
+ - [ ] 如果是子商品(`spuId > 0`),通过 `spuId` 查询父商品实体,获取父商品名称
|
|
|
+ - [ ] 将父商品名称和子商品名称拼接后赋值给 `goodsName` 字段(例如:`goodsName = `${parentGoods.name} ${goods.name}``)
|
|
|
+ - [ ] 确保多租户过滤:父子商品在同一租户下
|
|
|
+ - [ ] 确保单规格商品(`spuId = 0`)保持现有逻辑不变
|
|
|
+- [ ] 任务3:添加单元测试和集成测试 (AC: 1, 2, 3, 4)
|
|
|
+ - [ ] 为 `createOrder` 方法中的商品名称拼接逻辑添加单元测试
|
|
|
+ - [ ] 在现有集成测试文件 `packages/orders-module-mt/tests/integration/user-orders-routes.integration.test.ts` 中新增针对父子商品的订单创建测试用例
|
|
|
+ - [ ] 在集成测试中验证子商品订单快照的商品名称格式
|
|
|
+ - [ ] 验证单规格商品的现有行为保持不变
|
|
|
+- [ ] 任务4:验证功能完整性和性能 (AC: 3, 4)
|
|
|
+ - [ ] 验证订单详情页面正确显示完整的商品信息
|
|
|
+ - [ ] 测试父子商品在不同租户下的隔离性
|
|
|
+ - [ ] 确保没有额外的数据库查询影响性能
|
|
|
+ - [ ] 运行现有测试套件,确保无回归问题
|
|
|
+
|
|
|
+## Dev Notes
|
|
|
+
|
|
|
+### 问题分析
|
|
|
+- **当前问题**:在 `packages/orders-module-mt/src/services/order.mt.service.ts` 的 `createOrder` 方法中,`goodsName` 字段直接使用商品实体的 `name` 字段(第139行)。对于子商品,这会导致订单快照中存储的是子商品的规格名称,而不是父商品名称+规格名称的组合。
|
|
|
+- **影响**:订单页面显示的商品名称与购物车显示逻辑不一致。购物车中商品名称显示父商品名称,规格名称显示子商品规格名称,但订单快照只存储子商品的名称。
|
|
|
+- **技术背景**:
|
|
|
+ - 商品实体(`GoodsMt`)包含 `spuId` 字段,用于标识父子商品关系(`spuId = 0` 表示父商品,`spuId > 0` 表示子商品)
|
|
|
+ - 购物车显示逻辑已优化:显示父商品名称 + 子商品规格名称
|
|
|
+ - 需要保持订单快照与购物车显示逻辑的一致性
|
|
|
+
|
|
|
+### 技术实现细节
|
|
|
+- **文件位置**:
|
|
|
+ - 主要修改文件:`packages/orders-module-mt/src/services/order.mt.service.ts` [Source: architecture/source-tree.md#实际项目结构]
|
|
|
+ - 测试文件:在现有集成测试文件 `packages/orders-module-mt/tests/integration/user-orders-routes.integration.test.ts` 中新增测试用例
|
|
|
+- **商品实体结构**:
|
|
|
+ - `GoodsMt` 实体包含 `spuId: number` 字段(父商品ID)[Source: packages/goods-module-mt/src/entities/goods.entity.mt.ts:77-78]
|
|
|
+ - `spuId = 0`:父商品或无父子关系的商品
|
|
|
+ - `spuId > 0`:子商品,指向父商品的ID
|
|
|
+ - 商品名称字段:`name: string` [Source: packages/goods-module-mt/src/entities/goods.entity.mt.ts:17-18]
|
|
|
+- **当前实现分析**:
|
|
|
+ - `createOrder` 方法在第134-152行创建订单商品明细
|
|
|
+ - 第139行:`goodsName: info.goods.name` 直接使用商品名称
|
|
|
+ - 需要修改:对于子商品,使用父商品名称 + 子商品名称的组合
|
|
|
+- **多租户要求**:
|
|
|
+ - 所有查询必须包含 `tenantId` 过滤条件 [Source: architecture/coding-standards.md#架构原则]
|
|
|
+ - 父子商品必须在同一租户下
|
|
|
+- **性能考虑**:
|
|
|
+ - 避免N+1查询问题:批量查询父商品信息
|
|
|
+ - 考虑在事务中缓存父商品查询结果
|
|
|
+
|
|
|
+### 解决方案设计
|
|
|
+1. **修改 `createOrder` 方法**:
|
|
|
+ ```typescript
|
|
|
+ // 在商品循环中收集需要查询的父商品ID
|
|
|
+ const parentGoodsIds = new Set<number>();
|
|
|
+ for (const item of products) {
|
|
|
+ const goods = await this.goodsRepository.findOne({
|
|
|
+ where: { id: item.id, tenantId }
|
|
|
+ });
|
|
|
+ // ... 现有验证逻辑
|
|
|
+
|
|
|
+ if (goods.spuId > 0) {
|
|
|
+ parentGoodsIds.add(goods.spuId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量查询父商品信息
|
|
|
+ const parentGoodsMap = new Map<number, GoodsMt>();
|
|
|
+ if (parentGoodsIds.size > 0) {
|
|
|
+ const parentGoods = await this.goodsRepository.find({
|
|
|
+ where: { id: In([...parentGoodsIds]), tenantId }
|
|
|
+ });
|
|
|
+ parentGoods.forEach(g => parentGoodsMap.set(g.id, g));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建订单商品明细时使用正确的商品名称
|
|
|
+ const orderGoodsList = goodsInfo.map(info => {
|
|
|
+ let goodsName = info.goods.name;
|
|
|
+ if (info.goods.spuId > 0) {
|
|
|
+ const parentGoods = parentGoodsMap.get(info.goods.spuId);
|
|
|
+ if (parentGoods) {
|
|
|
+ goodsName = `${parentGoods.name} ${info.goods.name}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ // ... 其他字段
|
|
|
+ goodsName,
|
|
|
+ // ... 其他字段
|
|
|
+ };
|
|
|
+ });
|
|
|
+ ```
|
|
|
+
|
|
|
+2. **测试策略**:
|
|
|
+ - 单元测试:验证商品名称拼接逻辑
|
|
|
+ - 集成测试:验证完整订单创建流程
|
|
|
+ - 测试父子商品、单规格商品的不同场景
|
|
|
+
|
|
|
+### 文件位置
|
|
|
+- **主要修改文件**:
|
|
|
+ - `packages/orders-module-mt/src/services/order.mt.service.ts` - 修改 `createOrder` 方法中的商品名称拼接逻辑
|
|
|
+- **测试文件**:
|
|
|
+ - `packages/orders-module-mt/tests/integration/user-orders-routes.integration.test.ts` - 在现有集成测试中新增针对父子商品的订单创建测试用例,验证订单快照商品名称格式
|
|
|
+
|
|
|
+### 技术约束
|
|
|
+- **多租户隔离**:所有数据库查询必须包含 `tenantId` 条件 [Source: architecture/coding-standards.md#架构原则]
|
|
|
+- **TypeORM使用**:使用 `In` 操作符进行批量查询,避免N+1问题
|
|
|
+- **事务安全**:修改在现有事务中,确保数据一致性
|
|
|
+- **向后兼容性**:单规格商品(`spuId = 0`)的行为保持不变
|
|
|
+
|
|
|
+### Testing
|
|
|
+- **测试框架**:Vitest [Source: architecture/tech-stack.md#新技术添加]
|
|
|
+- **集成测试框架**:hono/testing [Source: architecture/tech-stack.md#新技术添加]
|
|
|
+- **测试位置**:
|
|
|
+ - 单元测试:`packages/orders-module-mt/tests/unit/**/*.test.ts` [Source: architecture/testing-strategy.md#单元测试]
|
|
|
+ - 集成测试:`packages/orders-module-mt/tests/integration/**/*.test.ts` [Source: architecture/testing-strategy.md#集成测试]
|
|
|
+- **测试标准**:
|
|
|
+ - 单元测试覆盖率目标:≥ 80% [Source: architecture/testing-strategy.md#单元测试]
|
|
|
+ - 集成测试覆盖率目标:≥ 60% [Source: architecture/testing-strategy.md#集成测试]
|
|
|
+- **具体测试要求**:
|
|
|
+ - 验证子商品订单快照的商品名称格式
|
|
|
+ - 验证单规格商品的现有行为不变
|
|
|
+ - 验证多租户数据隔离
|
|
|
+ - 验证事务回滚场景
|
|
|
+
|
|
|
+## Change Log
|
|
|
+| Date | Version | Description | Author |
|
|
|
+|------|---------|-------------|--------|
|
|
|
+| 2025-12-15 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
|
|
|
+
|
|
|
+## Dev Agent Record
|
|
|
+*此部分由开发代理在实施过程中填写*
|
|
|
+
|
|
|
+### Agent Model Used
|
|
|
+-
|
|
|
+
|
|
|
+### Debug Log References
|
|
|
+-
|
|
|
+
|
|
|
+### Completion Notes List
|
|
|
+-
|
|
|
+
|
|
|
+### File List
|
|
|
+-
|
|
|
+
|
|
|
+## QA Results
|
|
|
+*此部分由QA代理在审查完成后填写*
|