Просмотр исходного кода

📝 docs(stories): 创建父子商品名称关联查询优化故事006.009

创建故事006.009:父子商品名称关联查询优化(为购物车显示做准备)
- 故事目标:为购物车商品名称显示提供准确数据基础
- 任务包括:更新商品Schema、完善商品服务、验证购物车页面数据获取
- 验收标准:确保父子商品名称通过关联查询准确获取
- 技术约束:多租户兼容、向后兼容、性能优化

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 месяц назад
Родитель
Сommit
63012ffd00
1 измененных файлов с 160 добавлено и 0 удалено
  1. 160 0
      docs/stories/006.009.parent-child-goods-name-relation-query.story.md

+ 160 - 0
docs/stories/006.009.parent-child-goods-name-relation-query.story.md

@@ -0,0 +1,160 @@
+# Story 006.009: 父子商品名称关联查询优化(为购物车显示做准备)
+
+## Status
+Draft
+
+## Story
+**As a** 购物车用户,
+**I want** 父子商品名称通过关联查询准确获取,
+**so that** 购物车中能显示正确的父商品名称,避免数据不一致问题
+
+## Acceptance Criteria
+1. 商品详情API返回的 `parent` 对象包含完整的父商品基本信息(id、name等)
+2. 购物车页面能正确通过 `goods.parent?.name` 获取父商品名称(为故事10做准备)
+3. API不再返回 `spuName` 字段,前端代码直接使用 `parent.name` 获取父商品名称
+4. 父子商品名称显示准确,无数据不一致问题
+5. 所有测试通过,无回归问题
+6. 故事10能顺利基于故事9的实现完成购物车商品名称显示优化
+
+## Tasks / Subtasks
+- [ ] 任务1:更新商品Schema,完善父子商品类型定义 (AC: 1, 3)
+  - [ ] 创建父商品精简Schema(`ParentGoodsSchema`),包含基本字段:id、name、price、costPrice、stock、imageFileId、goodsType
+  - [ ] 更新`PublicGoodsSchema`中的`parent`字段类型:从`z.any()`改为`ParentGoodsSchema.nullable().optional()`
+  - [ ] 更新`AdminGoodsSchema`中的`parent`字段类型:从`z.any()`改为`ParentGoodsSchema.nullable().optional()`
+  - [ ] 更新`PublicGoodsSchema`中的`children`字段类型:从`z.array(z.any())`改为`z.array(PublicGoodsSchema).nullable().optional()`
+  - [ ] 从`PublicGoodsSchema`、`AdminGoodsSchema`、`UserGoodsSchema`中移除`spuName`字段(保留实体中的字段,仅从Schema中移除)
+  - [ ] 更新`UpdateGoodsDto`,移除`spuName`字段(API不再接受`spuName`字段更新)
+- [ ] 任务2:完善商品服务`GoodsServiceMt.getById`方法 (AC: 1)
+  - [ ] 确保`parent`对象包含完整的父商品基本信息:id、name、price、costPrice、stock、imageFileId、goodsType、spuId(0)
+  - [ ] 添加租户ID过滤,确保父商品与子商品在同一租户下(已实现,需要验证)
+  - [ ] 优化`parent`对象字段选择,确保包含所有必要字段
+  - [ ] 确保`children`列表返回完整的子商品信息(包含所有Schema字段)
+- [ ] 任务3:验证购物车页面父子商品名称获取 (AC: 2)
+  - [ ] 检查购物车页面(`mini/src/pages/cart/index.tsx`)当前商品名称显示逻辑
+  - [ ] 确认`goodsMap`中的商品数据包含`parent`对象
+  - [ ] 验证购物车页面能通过`latestGoods?.parent?.name`获取父商品名称
+  - [ ] 无需修改购物车页面代码(故事10将修改显示逻辑),仅确保数据基础可用
+- [ ] 任务4:编写和更新测试 (AC: 5)
+  - [ ] 更新商品服务单元测试,验证`getById`方法返回正确的`parent`对象
+  - [ ] 添加集成测试,验证商品详情API返回的`parent`对象包含完整字段
+  - [ ] 验证API不再返回`spuName`字段
+  - [ ] 测试父子商品关联查询的准确性
+  - [ ] 确保所有现有测试通过,无回归问题
+- [ ] 任务5:验证多租户兼容性和向后兼容性 (AC: 4, 5)
+  - [ ] 验证父子商品在同一租户下的约束
+  - [ ] 确保现有功能不受影响(单规格商品、无父子关系的商品)
+  - [ ] 验证数据库实体中的`spuName`字段仍然存在(保持向后兼容性),仅从API响应中移除
+
+## Dev Notes
+
+### 先前故事洞察
+- **故事8(购物车页面规格切换功能)**:已扩展`CartContext`,`CartItem`接口包含`parentGoodsId`字段,购物车页面已集成规格选择器
+- **故事4-6**:商品API已支持父子商品关系,商品详情API返回`children`和`parent`字段(类型为`any`)
+- **关键设计决策**:规格=子商品的名称,规格选择=选择子商品,购物车逻辑简化(使用子商品的`id`、`name`、`price`、`stock`)
+- **当前实现状态**:`GoodsServiceMt.getById`方法已返回`parent`对象,但只包含有限字段:`['id', 'name', 'price', 'costPrice', 'stock', 'imageFileId', 'goodsType']`
+
+### 数据模型
+- **商品实体 (`GoodsMt`)**:
+  - `spuId`字段:0表示父商品或单规格商品,>0表示子商品
+  - `spuName`字段:父商品名称(冗余字段,存在数据一致性问题)
+  - `tenantId`字段:租户ID,父子商品必须在同一租户下
+  - [Source: packages/goods-module-mt/src/entities/goods.entity.mt.ts#L76-L81]
+
+- **商品Schema**:
+  - `PublicGoodsSchema`: 包含`parent: z.any()`和`children: z.array(z.any())`字段,`spuName`字段存在
+  - `AdminGoodsSchema`: 包含`parent: z.any()`和`children: z.array(z.any())`字段,`spuName`字段存在
+  - `UserGoodsSchema`: 不包含`parent`和`children`字段,有`spuName`字段
+  - [Source: packages/goods-module-mt/src/schemas/public-goods.schema.mt.ts#L125-L127]
+  - [Source: packages/goods-module-mt/src/schemas/admin-goods.schema.mt.ts#L127-L129]
+
+- **父商品精简Schema需求**:
+  ```typescript
+  const ParentGoodsSchema = z.object({
+    id: z.number().int().positive(),
+    name: z.string().min(1).max(255),
+    price: z.coerce.number().multipleOf(0.01).min(0).default(0),
+    costPrice: z.coerce.number().multipleOf(0.01).min(0).default(0),
+    stock: z.coerce.number().int().nonnegative().default(0),
+    imageFileId: z.number().int().positive().nullable(),
+    goodsType: z.number().int().min(1).max(2).default(1),
+    spuId: z.number().int().nonnegative().default(0) // 父商品的spuId总是0
+  })
+  ```
+
+### API 规范
+- **商品详情API** (`GET /api/v1/goods/:id`):
+  - 父商品:返回商品详情 + `children`数组(子商品列表)
+  - 子商品:返回子商品详情 + `parent`对象(父商品基本信息)
+  - 当前`parent`对象字段:`['id', 'name', 'price', 'costPrice', 'stock', 'imageFileId', 'goodsType']`
+  - [Source: packages/goods-module-mt/src/services/goods.service.mt.ts#L120-L126]
+
+- **API变更策略**:
+  - 不再返回`spuName`字段,前端使用`parent.name`获取父商品名称
+  - `parent`字段类型从`any`改为具体的`ParentGoodsSchema`
+  - `children`字段类型从`any[]`改为`PublicGoodsSchema[]`
+  - 保持向后兼容性:数据库实体保留`spuName`字段,仅从API响应中移除
+
+### 组件规范
+- **购物车页面 (`cart/index.tsx`)**:
+  - 使用`goodsMap`存储从商品详情API获取的最新商品信息
+  - 当前商品名称显示:`goodsName = latestGoods?.name || item.name`
+  - 购物车项包含`parentGoodsId`字段(来自`CartItem`接口)
+  - 商品详情API返回的数据存储在`goodsMap`中,可通过`latestGoods?.parent?.name`获取父商品名称
+  - [Source: mini/src/pages/cart/index.tsx#L251-L253]
+
+- **购物车上下文 (`CartContext`)**:
+  - `CartItem`接口包含`parentGoodsId`字段
+  - `switchSpec`函数支持规格切换
+  - [Source: mini/src/contexts/CartContext.tsx#L4-L13]
+
+### 文件位置
+- **商品Schema文件**:
+  - `packages/goods-module-mt/src/schemas/public-goods.schema.mt.ts` - 更新`parent`和`children`字段类型,移除`spuName`
+  - `packages/goods-module-mt/src/schemas/admin-goods.schema.mt.ts` - 更新`parent`和`children`字段类型,移除`spuName`
+  - `packages/goods-module-mt/src/schemas/user-goods.schema.mt.ts` - 移除`spuName`字段
+  - `packages/goods-module-mt/src/schemas/goods.schema.mt.ts` - 更新`UpdateGoodsDto`,移除`spuName`字段
+  - 可能新建:`packages/goods-module-mt/src/schemas/parent-goods.schema.mt.ts` - 父商品精简Schema
+
+- **商品服务文件**:
+  - `packages/goods-module-mt/src/services/goods.service.mt.ts` - 完善`getById`方法,确保`parent`对象包含完整字段
+
+- **测试文件**:
+  - `packages/goods-module-mt/tests/unit/services/goods.service.mt.test.ts` - 更新单元测试
+  - `packages/goods-module-mt/tests/integration/public-goods-routes.integration.test.ts` - 更新集成测试
+
+- **购物车页面**:
+  - `mini/src/pages/cart/index.tsx` - 无需修改(故事10将修改显示逻辑),仅验证数据可用性
+
+### 技术约束
+- **多租户要求**:所有操作必须包含`tenantId`过滤,父子商品必须在同一租户下
+- **向后兼容性**:现有功能不受影响,数据库实体保留`spuName`字段,仅从API响应中移除
+- **性能考虑**:关联查询不应显著影响API响应时间
+- **数据一致性**:通过关联查询解决`spuName`字段同步问题,确保父子商品名称显示准确
+
+### 测试标准
+- **测试框架**:商品模块使用Vitest,mini项目使用Jest
+- **测试位置**:与源码并列的`tests/`目录
+- **单元测试位置**:`packages/goods-module-mt/tests/unit/services/goods.service.mt.test.ts`
+- **集成测试位置**:`packages/goods-module-mt/tests/integration/public-goods-routes.integration.test.ts`
+- **测试覆盖率**:核心业务逻辑 > 80%,关键函数 > 90%
+- **测试策略**:验证`parent`对象字段完整性、API响应不包含`spuName`字段、父子商品关联查询准确性
+- [Source: docs/architecture/testing-strategy.md#单元测试-unit-tests]
+
+## Change Log
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2025-12-14 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
+
+## Dev Agent Record
+*此部分由开发代理在实施过程中填写*
+
+### Agent Model Used
+
+### Debug Log References
+
+### Completion Notes List
+
+### File List
+
+## QA Results
+*此部分由QA代理在审查完成后填写*