|
|
@@ -0,0 +1,470 @@
|
|
|
+# 故事 018.002 - 完善订单编辑功能
|
|
|
+
|
|
|
+## Status
|
|
|
+Approved
|
|
|
+
|
|
|
+## Story
|
|
|
+**作为** 订单管理员
|
|
|
+**我希望** 能够完整编辑订单信息
|
|
|
+**以便** 修正订单中的错误或更新订单信息
|
|
|
+
|
|
|
+## Acceptance Criteria
|
|
|
+1. 订单编辑功能正常工作,能够修改订单信息
|
|
|
+2. 订单详情页显示完整的公司名称(而非"公司29")
|
|
|
+3. 订单详情页显示完整的平台名称(而非"平台3")
|
|
|
+4. 订单详情页显示完整的渠道名称(而非"渠道4")
|
|
|
+5. 订单关联信息(公司、平台、渠道)以可读的名称显示
|
|
|
+6. 订单编辑后保存成功,数据正确更新
|
|
|
+
|
|
|
+## Tasks / Subtasks
|
|
|
+
|
|
|
+### Task 1: 排查订单编辑和详情显示问题根因 (AC: 1, 2, 3, 4, 5)
|
|
|
+- [ ] 检查 `OrderManagement.tsx` 中订单详情获取逻辑
|
|
|
+- [ ] 验证后端API `order-custom.routes.ts` 中获取订单详情时的关联查询
|
|
|
+- [ ] 检查 `OrderService.findOne()` 方法是否正确加载关联数据(公司、平台、渠道)
|
|
|
+- [ ] 验证 `EmploymentOrder` Entity 是否定义了与 Company、Platform、Channel 的关联关系
|
|
|
+- [ ] 检查订单详情页 `OrderDetailModal` 组件显示逻辑
|
|
|
+- [ ] 验证前端是否正确处理后端返回的关联对象数据
|
|
|
+
|
|
|
+### Task 2: 修复后端订单详情API关联查询 (AC: 2, 3, 4, 5)
|
|
|
+- [ ] 在 `EmploymentOrder` Entity 中添加关联关系定义(如果缺失)
|
|
|
+- [ ] 修改 `OrderService.findOne()` 方法,添加关联加载:
|
|
|
+ - `relations: ['company', 'platform', 'channel']`
|
|
|
+- [ ] 确保返回的订单对象包含完整的公司、平台、渠道信息
|
|
|
+- [ ] 验证Schema定义是否允许返回关联对象
|
|
|
+
|
|
|
+### Task 3: 修复前端订单详情显示 (AC: 2, 3, 4, 5)
|
|
|
+- [ ] 修改 `OrderDetailModal.tsx` 组件,使用关联对象的名称字段
|
|
|
+- [ ] 确保公司名称显示为 `order.company.companyName` 而非 `order.companyId`
|
|
|
+- [ ] 确保平台名称显示为 `order.platform.platformName` 而非 `order.platformId`
|
|
|
+- [ ] 确保渠道名称显示为 `order.channel.channelName` 而非 `order.channelId`
|
|
|
+- [ ] 处理关联对象可能为空的情况(null check)
|
|
|
+
|
|
|
+### Task 4: 修复前端订单编辑功能 (AC: 1, 6)
|
|
|
+- [ ] 检查 `OrderForm.tsx` 组件的编辑模式实现
|
|
|
+- [ ] 验证表单初始化时是否正确填充订单数据
|
|
|
+- [ ] 确保编辑表单能够正常提交
|
|
|
+- [ ] 检查 `OrderManagement.tsx` 中编辑按钮的事件处理
|
|
|
+- [ ] 验证更新订单API调用是否正确
|
|
|
+
|
|
|
+### Task 5: 优化订单列表显示 (AC: 2, 3, 4)
|
|
|
+- [ ] 修改 `OrderManagement.tsx` 列表页面,显示完整的公司/平台/渠道名称
|
|
|
+- [ ] 在 `OrderService.findAll()` 方法中添加关联数据加载
|
|
|
+- [ ] 确保列表页面也显示可读的名称而非ID
|
|
|
+
|
|
|
+### Task 6: 单元测试 (AC: 所有)
|
|
|
+- [ ] 为订单详情关联查询添加单元测试
|
|
|
+- [ ] 测试后端 `OrderService.findOne()` 方法正确加载关联数据
|
|
|
+- [ ] 测试前端组件正确显示关联对象名称
|
|
|
+- [ ] 测试订单编辑功能完整流程
|
|
|
+
|
|
|
+### Task 7: 集成测试 (AC: 1, 6)
|
|
|
+- [ ] 编写端到端集成测试:创建订单并编辑
|
|
|
+- [ ] 编写端到端集成测试:查看订单详情页验证名称显示
|
|
|
+- [ ] 验证数据库中订单信息正确更新
|
|
|
+
|
|
|
+## Dev Notes
|
|
|
+
|
|
|
+### 数据模型信息
|
|
|
+
|
|
|
+**EmploymentOrder Entity**
|
|
|
+[Source: allin-packages/order-module/src/entities/employment-order.entity.ts]
|
|
|
+
|
|
|
+**字段定义**:
|
|
|
+- `id` (order_id): 主键,自增整数
|
|
|
+- `orderName` (order_name): 订单名称,varchar(50),可选
|
|
|
+- `platformId` (platform_id): 用人平台ID,int,非空
|
|
|
+- `companyId` (company_id): 用人单位ID,int,非空
|
|
|
+- `channelId` (channel_id): 渠道ID,int,可选
|
|
|
+- `expectedStartDate` (expected_start_date): 预计开始日期,date,可选
|
|
|
+- `actualStartDate` (actual_start_date): 实际开始日期,date,可选
|
|
|
+- `actualEndDate` (actual_end_date): 实际结束日期,date,可选
|
|
|
+- `orderStatus` (order_status): 订单状态,enum,默认 `OrderStatus.DRAFT`
|
|
|
+- `workStatus` (work_status): 工作状态,enum,默认 `WorkStatus.NOT_WORKING`
|
|
|
+- `createTime` (create_time): 创建时间,timestamp
|
|
|
+- `updateTime` (update_time): 更新时间,timestamp
|
|
|
+
|
|
|
+**关系定义**:
|
|
|
+- `@OneToMany(() => OrderPerson, (orderPerson) => orderPerson.order)`: 订单人员关系
|
|
|
+
|
|
|
+**缺失的关系定义**:
|
|
|
+- 当前Entity未定义与 Company、Platform、Channel 的关联关系
|
|
|
+- 需要添加 `@ManyToOne` 关系以便 TypeORM 加载关联数据
|
|
|
+
|
|
|
+### 关联Entity参考
|
|
|
+
|
|
|
+**Company Entity**
|
|
|
+[Source: allin-packages/company-module/src/entities/company.entity.ts]
|
|
|
+- 主键: `id` (company_id)
|
|
|
+- 名称字段: `companyName`
|
|
|
+
|
|
|
+**Platform Entity**
|
|
|
+[Source: allin-packages/platform-module/src/entities/platform.entity.ts]
|
|
|
+- 主键: `id` (platform_id)
|
|
|
+- 名称字段: `platformName`
|
|
|
+
|
|
|
+**Channel Entity**
|
|
|
+[Source: allin-packages/channel-module/src/entities/channel.entity.ts]
|
|
|
+- 主键: `id` (channel_id)
|
|
|
+- 名称字段: `channelName`
|
|
|
+
|
|
|
+### 后端Service层
|
|
|
+
|
|
|
+**OrderService.findOne()**
|
|
|
+[Source: allin-packages/order-module/src/services/order.service.ts#169-214]
|
|
|
+
|
|
|
+**当前实现**:
|
|
|
+```typescript
|
|
|
+async findOne(id: number): Promise<any | null> {
|
|
|
+ const order = await this.repository.findOne({
|
|
|
+ where: { id },
|
|
|
+ relations: ['orderPersons', 'orderPersons.person']
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!order) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回格式化数据,但缺少公司、平台、渠道信息
|
|
|
+ return {
|
|
|
+ id: order.id,
|
|
|
+ orderName: order.orderName,
|
|
|
+ platformId: order.platformId, // 只返回ID
|
|
|
+ companyId: order.companyId, // 只返回ID
|
|
|
+ channelId: order.channelId, // 只返回ID
|
|
|
+ // ... 其他字段
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**问题分析**:
|
|
|
+1. `relations` 中未包含 `company`, `platform`, `channel`
|
|
|
+2. 返回的数据只包含ID,不包含关联对象的完整信息
|
|
|
+3. 需要添加关联加载并在返回数据中包含关联对象
|
|
|
+
|
|
|
+### 后端路由层
|
|
|
+
|
|
|
+**获取订单详情路由**
|
|
|
+[Source: allin-packages/order-module/src/routes/order-custom.routes.ts#223-257]
|
|
|
+
|
|
|
+**路由定义**:
|
|
|
+```typescript
|
|
|
+const getOrderByIdRoute = createRoute({
|
|
|
+ method: 'get',
|
|
|
+ path: '/detail/{id}',
|
|
|
+ middleware: [authMiddleware],
|
|
|
+ operationId: 'getOrderById',
|
|
|
+ // ...
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**路由处理器**:
|
|
|
+[Source: allin-packages/order-module/src/routes/order-custom.routes.ts#995-1014]
|
|
|
+```typescript
|
|
|
+.openapi(getOrderByIdRoute, async (c) => {
|
|
|
+ try {
|
|
|
+ const { id } = c.req.valid('param');
|
|
|
+ const orderService = new OrderService(AppDataSource);
|
|
|
+
|
|
|
+ const result = await orderService.findOne(id);
|
|
|
+
|
|
|
+ if (!result) {
|
|
|
+ return c.json({ code: 404, message: '订单不存在' }, 404);
|
|
|
+ }
|
|
|
+
|
|
|
+ const validatedResult = await parseWithAwait(EmploymentOrderSchema, result);
|
|
|
+ return c.json(validatedResult, 200);
|
|
|
+ } catch (error) {
|
|
|
+ // 错误处理
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 前端组件结构
|
|
|
+
|
|
|
+**OrderManagement 组件**
|
|
|
+[Source: allin-packages/order-management-ui/src/components/OrderManagement.tsx]
|
|
|
+
|
|
|
+**关键功能**:
|
|
|
+- 订单列表展示
|
|
|
+- 编辑按钮触发编辑对话框
|
|
|
+- 查看详情按钮触发详情弹窗
|
|
|
+
|
|
|
+**状态管理**:
|
|
|
+```typescript
|
|
|
+const [selectedOrder, setSelectedOrder] = useState<OrderDetail | undefined>();
|
|
|
+const [isOrderFormOpen, setIsOrderFormOpen] = useState(false);
|
|
|
+const [isOrderDetailOpen, setIsOrderDetailOpen] = useState(false);
|
|
|
+```
|
|
|
+
|
|
|
+**获取订单详情**:
|
|
|
+```typescript
|
|
|
+const getOrderDetailMutation = useMutation({
|
|
|
+ mutationFn: async (id: number) => {
|
|
|
+ const response = await orderClientManager.get().detail[':id'].$get({
|
|
|
+ param: { id },
|
|
|
+ });
|
|
|
+ // ...
|
|
|
+ },
|
|
|
+ onSuccess: (data) => {
|
|
|
+ setSelectedOrder(data);
|
|
|
+ setIsOrderDetailOpen(true);
|
|
|
+ },
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**OrderDetailModal 组件**
|
|
|
+[Source: allin-packages/order-management-ui/src/components/OrderDetailModal.tsx]
|
|
|
+
|
|
|
+**显示问题**:
|
|
|
+- 当前可能直接显示 `platformId`, `companyId`, `channelId` 数字ID
|
|
|
+- 需要改为显示关联对象的名称字段
|
|
|
+
|
|
|
+**OrderForm 组件**
|
|
|
+[Source: allin-packages/order-management-ui/src/components/OrderForm.tsx]
|
|
|
+
|
|
|
+**编辑功能**:
|
|
|
+- 需要验证编辑模式是否正常工作
|
|
|
+- 表单初始化应该加载现有订单数据
|
|
|
+- 提交时调用更新API
|
|
|
+
|
|
|
+### API客户端
|
|
|
+
|
|
|
+**orderClientManager**
|
|
|
+[Source: allin-packages/order-management-ui/src/api/orderClient.ts]
|
|
|
+
|
|
|
+**获取详情方法**:
|
|
|
+```typescript
|
|
|
+const response = await orderClientManager.get().detail[':id'].$get({
|
|
|
+ param: { id },
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**更新订单方法**:
|
|
|
+```typescript
|
|
|
+const response = await orderClientManager.get().update[':id'].$put({
|
|
|
+ param: { id },
|
|
|
+ json: updateData,
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### Schema定义
|
|
|
+
|
|
|
+**EmploymentOrderSchema**
|
|
|
+[Source: allin-packages/order-module/src/schemas/order.schema.ts]
|
|
|
+
|
|
|
+需要验证Schema是否支持返回关联对象。可能需要:
|
|
|
+1. 添加关联对象的字段定义
|
|
|
+2. 或使用 `.nullable()` 和 `.optional()` 处理可能为空的关联
|
|
|
+
|
|
|
+### 修复建议
|
|
|
+
|
|
|
+#### 后端修复步骤
|
|
|
+
|
|
|
+1. **修改 EmploymentOrder Entity**:
|
|
|
+```typescript
|
|
|
+// allin-packages/order-module/src/entities/employment-order.entity.ts
|
|
|
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
|
|
|
+import { Company } from '@d8d/allin-company-module/entities';
|
|
|
+import { Platform } from '@d8d/allin-platform-module/entities';
|
|
|
+import { Channel } from '@d8d/allin-channel-module/entities';
|
|
|
+
|
|
|
+@Entity('employment_order', { comment: '用工订单表' })
|
|
|
+export class EmploymentOrder {
|
|
|
+ // ... 现有字段
|
|
|
+
|
|
|
+ @ManyToOne(() => Company)
|
|
|
+ @JoinColumn({ name: 'company_id' })
|
|
|
+ company!: Company;
|
|
|
+
|
|
|
+ @ManyToOne(() => Platform)
|
|
|
+ @JoinColumn({ name: 'platform_id' })
|
|
|
+ platform!: Platform;
|
|
|
+
|
|
|
+ @ManyToOne(() => Channel)
|
|
|
+ @JoinColumn({ name: 'channel_id' })
|
|
|
+ channel?: Channel;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+2. **修改 OrderService.findOne()**:
|
|
|
+```typescript
|
|
|
+async findOne(id: number): Promise<any | null> {
|
|
|
+ const order = await this.repository.findOne({
|
|
|
+ where: { id },
|
|
|
+ relations: [
|
|
|
+ 'orderPersons',
|
|
|
+ 'orderPersons.person',
|
|
|
+ 'company', // 添加关联
|
|
|
+ 'platform', // 添加关联
|
|
|
+ 'channel' // 添加关联
|
|
|
+ ]
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!order) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: order.id,
|
|
|
+ orderName: order.orderName,
|
|
|
+ platformId: order.platformId,
|
|
|
+ companyId: order.companyId,
|
|
|
+ channelId: order.channelId,
|
|
|
+ // 添加关联对象信息
|
|
|
+ company: order.company ? {
|
|
|
+ id: order.company.id,
|
|
|
+ companyName: order.company.companyName
|
|
|
+ } : null,
|
|
|
+ platform: order.platform ? {
|
|
|
+ id: order.platform.id,
|
|
|
+ platformName: order.platform.platformName
|
|
|
+ } : null,
|
|
|
+ channel: order.channel ? {
|
|
|
+ id: order.channel.id,
|
|
|
+ channelName: order.channel.channelName
|
|
|
+ } : null,
|
|
|
+ // ... 其他字段
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 前端修复步骤
|
|
|
+
|
|
|
+1. **修改 OrderDetailModal.tsx**:
|
|
|
+```typescript
|
|
|
+// 显示公司名称
|
|
|
+{order.company?.companyName || `公司${order.companyId}`}
|
|
|
+
|
|
|
+// 显示平台名称
|
|
|
+{order.platform?.platformName || `平台${order.platformId}`}
|
|
|
+
|
|
|
+// 显示渠道名称
|
|
|
+{order.channel?.channelName || `渠道${order.channelId}`}
|
|
|
+```
|
|
|
+
|
|
|
+2. **修改 OrderManagement.tsx 列表显示**:
|
|
|
+```typescript
|
|
|
+// 表格列显示名称而非ID
|
|
|
+<TableCell>
|
|
|
+ {order.company?.companyName || `公司${order.companyId}`}
|
|
|
+</TableCell>
|
|
|
+```
|
|
|
+
|
|
|
+### 编码标准
|
|
|
+[Source: docs/architecture/coding-standards.md]
|
|
|
+
|
|
|
+**TypeScript严格模式**: 所有代码必须启用严格类型检查
|
|
|
+**命名约定**:
|
|
|
+- 文件名: kebab-case (如: `order-detail-modal.tsx`)
|
|
|
+- 类名: PascalCase (如: `OrderDetailModal`)
|
|
|
+- 函数/变量: camelCase (如: `getOrderById`)
|
|
|
+- 接口: PascalCase,无I前缀 (如: `OrderDetail`)
|
|
|
+
|
|
|
+**UI包开发规范**:
|
|
|
+[Source: docs/architecture/ui-package-standards.md]
|
|
|
+- API路径映射验证: 确保API路径与实际后端路由一致
|
|
|
+- 类型推断最佳实践: 使用RPC推断类型
|
|
|
+- 测试选择器优化: 为关键交互元素添加 `data-testid` 属性
|
|
|
+
|
|
|
+### 项目结构
|
|
|
+[Source: docs/architecture/source-tree.md]
|
|
|
+
|
|
|
+**订单相关包**:
|
|
|
+```
|
|
|
+allin-packages/
|
|
|
+├── order-module/ # 订单后端模块
|
|
|
+│ ├── src/
|
|
|
+│ │ ├── entities/ # 数据实体
|
|
|
+│ │ │ └── employment-order.entity.ts
|
|
|
+│ │ ├── services/ # 业务逻辑
|
|
|
+│ │ │ └── order.service.ts
|
|
|
+│ │ └── routes/ # API路由
|
|
|
+│ │ └── order-custom.routes.ts
|
|
|
+│ └── tests/
|
|
|
+└── order-management-ui/ # 订单前端UI
|
|
|
+ ├── src/
|
|
|
+ │ ├── components/ # UI组件
|
|
|
+ │ │ ├── OrderManagement.tsx
|
|
|
+ │ │ ├── OrderForm.tsx
|
|
|
+ │ │ └── OrderDetailModal.tsx
|
|
|
+ │ └── api/ # API客户端
|
|
|
+ │ └── orderClient.ts
|
|
|
+ └── tests/ # 测试文件
|
|
|
+ └── integration/
|
|
|
+```
|
|
|
+
|
|
|
+**关联包**:
|
|
|
+- `allin-packages/company-module`: 企业管理模块
|
|
|
+- `allin-packages/platform-module`: 平台管理模块
|
|
|
+- `allin-packages/channel-module`: 渠道管理模块
|
|
|
+
|
|
|
+### 技术栈
|
|
|
+[Source: docs/architecture/tech-stack.md]
|
|
|
+
|
|
|
+- **运行时**: Node.js 20.18.3
|
|
|
+- **前端框架**: React 19.1.0
|
|
|
+- **数据库**: PostgreSQL 17 + TypeORM 0.3.25
|
|
|
+- **状态管理**: React Query 5.83.0
|
|
|
+- **表单管理**: react-hook-form + zod
|
|
|
+- **测试框架**: Vitest 3.2.4
|
|
|
+
|
|
|
+### 前序故事经验
|
|
|
+[Source: docs/stories/018.001.story.md - Dev Agent Record]
|
|
|
+
|
|
|
+**故事018.001经验**:
|
|
|
+- 排查问题时先检查代码逻辑,再检查实际数据
|
|
|
+- 使用 `console.debug` 查看实际API返回数据
|
|
|
+- 前端显示问题可能是后端未返回完整数据导致
|
|
|
+- Entity关联关系需要正确定义才能使用 TypeORM 的 relations 加载
|
|
|
+
|
|
|
+### 可能的问题点
|
|
|
+
|
|
|
+#### 后端问题
|
|
|
+1. **Entity缺少关联定义**: EmploymentOrder 未定义与 Company/Platform/Channel 的关系
|
|
|
+2. **Service未加载关联**: `findOne()` 方法的 relations 中未包含关联表
|
|
|
+3. **返回数据不完整**: 只返回ID,不包含关联对象信息
|
|
|
+4. **Schema不支持关联对象**: Schema可能需要更新以支持嵌套对象
|
|
|
+
|
|
|
+#### 前端问题
|
|
|
+1. **显示ID而非名称**: 组件直接显示ID字段
|
|
|
+2. **未处理null**: 关联对象可能为空,需要null check
|
|
|
+3. **编辑功能未实现**: OrderForm可能只支持创建,不支持编辑
|
|
|
+4. **类型不匹配**: TypeScript类型可能未包含关联对象字段
|
|
|
+
|
|
|
+### 修复检查清单
|
|
|
+
|
|
|
+在修复过程中,请按以下顺序检查:
|
|
|
+
|
|
|
+1. [ ] 后端Entity是否定义了Company/Platform/Channel关联关系
|
|
|
+2. [ ] OrderService.findOne()是否加载了关联数据
|
|
|
+3. [ ] OrderService.findAll()是否加载了关联数据(用于列表显示)
|
|
|
+4. [ ] 后端API返回数据是否包含完整的关联对象信息
|
|
|
+5. [ ] Schema是否支持返回嵌套的关联对象
|
|
|
+6. [ ] 前端OrderDetailModal是否使用关联对象的名称字段
|
|
|
+7. [ ] 前端OrderManagement列表是否显示名称而非ID
|
|
|
+8. [ ] 前端OrderForm编辑功能是否正常工作
|
|
|
+9. [ ] 是否处理了关联对象为空的情况
|
|
|
+10. [ ] 是否添加了必要的单元测试和集成测试
|
|
|
+
|
|
|
+## Change Log
|
|
|
+| Date | Version | Description | Author |
|
|
|
+|------|---------|-------------|--------|
|
|
|
+| 2025-12-31 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
|
|
|
+
|
|
|
+## Dev Agent Record
|
|
|
+
|
|
|
+### Agent Model Used
|
|
|
+待开发时填写
|
|
|
+
|
|
|
+### Debug Log References
|
|
|
+待开发时填写
|
|
|
+
|
|
|
+### Completion Notes List
|
|
|
+待开发时填写
|
|
|
+
|
|
|
+### File List
|
|
|
+待开发时填写
|
|
|
+
|
|
|
+## QA Results
|
|
|
+待QA测试时填写
|