# Story 007.005: 移植订单管理模块（order → @d8d/allin-order-module）

## Status
Completed

## Story
**As a** 开发者，
**I want** 将order模块从allin_system-master移植为独立包@d8d/allin-order-module，完成实体重构、文件模块集成和循环依赖处理，
**so that** 我们可以将Allin系统的订单管理功能集成到当前项目中，遵循现有的模块化架构和编码标准，并实现与@d8d/file-module的文件集成和与@d8d/allin-disability-module的依赖解耦。

## Acceptance Criteria
1. 创建`allin-packages/order-module`目录结构
2. 完成实体转换：3个实体（EmploymentOrder、OrderPerson、OrderPersonAsset）转换
3. **枚举常量集成**：使用`@d8d/allin-enums`包中的`OrderStatus`和`WorkStatus`枚举
4. **文件实体集成**：修改`OrderPersonAsset`实体，添加`fileId`字段引用`File`实体
5. **解决循环依赖**：与disability-module的相互引用处理
6. 完成服务层转换：订单业务逻辑，包含资产文件关联处理
7. 完成路由层转换：Hono路由实现，API接收`fileId`参数
8. 完成验证系统转换：订单相关验证，包含文件类型验证和枚举值验证
9. 配置package.json：依赖管理，包含对`@d8d/file-module`和`@d8d/allin-enums`的依赖
10. 编写API集成测试：覆盖订单全生命周期，包含资产文件关联测试和枚举值测试
11. 通过类型检查和基本测试验证
12. 验证循环依赖解决方案的有效性

## Tasks / Subtasks

### 1. 创建包结构和配置文件 (AC: 1)
- [x] 创建目录：`allin-packages/order-module/`
- [x] 初始化package.json：参考`allin-packages/salary-module/package.json`
  - **参考文件**: `allin-packages/salary-module/package.json`
  - **修改点**: 包名改为`@d8d/allin-order-module`，添加对`@d8d/file-module`和`@d8d/allin-enums`的依赖
  - **关键依赖**: `@d8d/file-module`, `@d8d/allin-enums`, `@d8d/core-module`, `@d8d/shared-crud`, `@d8d/shared-utils`
  - **注意吸取经验**: 根据故事007.006的经验，需要在`pnpm-workspace.yaml`中添加`allin-packages/*`配置
- [x] 配置tsconfig.json：参考`allin-packages/salary-module/tsconfig.json`
  - **参考文件**: `allin-packages/salary-module/tsconfig.json`
- [x] 配置vitest.config.ts：参考`allin-packages/salary-module/vitest.config.ts`
  - **参考文件**: `allin-packages/salary-module/vitest.config.ts`
- [x] 配置workspace：在根目录`pnpm-workspace.yaml`中添加`allin-packages/order-module`
  - **修改文件**: `/mnt/code/188-179-template-6/pnpm-workspace.yaml`
  - **添加内容**: `'allin-packages/order-module'`

### 2. 迁移实体文件 (AC: 2, 3, 4)
✅ **已完成**

#### 2.1 迁移EmploymentOrder实体
**源文件路径**: `allin_system-master/server/src/order/employment_order.entity.ts`
**目标文件路径**: `allin-packages/order-module/src/entities/employment-order.entity.ts`
**参考对照文件**:
- `allin-packages/salary-module/src/entities/salary-level.entity.ts`（实体结构）
- `allin-packages/enums/src/enums/order-status.enum.ts`（枚举引用）
- `allin-packages/enums/src/enums/work-status.enum.ts`（枚举引用）

**修改要点**:
1. 将主键字段名从`orderId`改为`id`以遵循GenericCrudService约定
2. 将`orderStatus`字段类型从`string`改为`OrderStatus`枚举
3. 添加`workStatus`字段，使用`WorkStatus`枚举
4. 将下划线字段名转换为驼峰命名：`order_name` → `orderName`, `platform_id` → `platformId`等
5. 添加详细的TypeORM装饰器配置
6. 保持数据库表名不变：`@Entity('employment_order')`

#### 2.2 迁移OrderPerson实体
**源文件路径**: `allin_system-master/server/src/order/order_person.entity.ts`
**目标文件路径**: `allin-packages/order-module/src/entities/order-person.entity.ts`
**参考对照文件**:
- `allin-packages/disability-module/src/entities/disabled-person.entity.ts`（实体结构）
- **注意循环依赖**: 原实体引用`DisabledPerson`实体，需要解耦处理

**修改要点**:
1. 将主键字段名从`opId`改为`id`
2. 将`personId`字段类型保持为`number`（不直接引用DisabledPerson实体）
3. 将下划线字段名转换为驼峰命名
4. 添加详细的TypeORM装饰器配置
5. 保持数据库表名不变：`@Entity('order_person')`
6. **循环依赖处理**: 移除对`DisabledPerson`的直接引用，改为使用`personId: number`

#### 2.3 迁移OrderPersonAsset实体
**源文件路径**: `allin_system-master/server/src/order/order_person_asset.entity.ts`
**目标文件路径**: `allin-packages/order-module/src/entities/order-person-asset.entity.ts`
**参考对照文件**:
- `allin-packages/disability-module/src/entities/disabled-photo.entity.ts`（文件实体集成）
- `packages/file-module/src/entities/file.entity.ts`（文件实体引用）

**修改要点**:
1. 将主键字段名从`opId`改为`id`
2. **文件模块集成**: 移除`assetUrl: string`字段，添加`fileId: number`字段
3. 添加与File实体的关联关系：`@ManyToOne(() => File)`
4. **循环依赖处理**: 移除对`DisabledPerson`的直接引用，改为使用`personId: number`
5. 将`AssetType`和`AssetFileType`枚举移到实体文件中（模块内部枚举）
6. 将下划线字段名转换为驼峰命名
7. 添加详细的TypeORM装饰器配置
8. 保持数据库表名不变：`@Entity('order_person_asset')`

### 3. 创建DTO和Schema文件 (AC: 8)
✅ **已完成**

**目标文件路径**:
- `allin-packages/order-module/src/dto/create-order.dto.ts`
- `allin-packages/order-module/src/dto/update-order.dto.ts`
- `allin-packages/order-module/src/dto/create-order-person.dto.ts`
- `allin-packages/order-module/src/dto/create-order-person-asset.dto.ts`
- `allin-packages/order-module/src/schemas/order.schema.ts`

**参考对照文件**:
- `allin-packages/salary-module/src/dto/create-salary.dto.ts`（DTO结构）
- `allin-packages/salary-module/src/dto/update-salary.dto.ts`
- `allin-packages/salary-module/src/schemas/salary.schema.ts`（Zod schema）
- `allin-packages/disability-module/src/schemas/disabled-person.schema.ts`（文件ID验证）

**修改要点**:
1. 将class-validator DTO转换为Zod Schema
2. 在OrderPersonAsset的Schema中添加`fileId`验证
3. 添加枚举值验证：使用`OrderStatus`和`WorkStatus`枚举
4. 保持与原始API相同的字段名和验证规则

### 4. 迁移控制器和服务 (AC: 6, 7)
✅ **已完成**

#### 4.1 迁移OrderService
**源文件路径**: `allin_system-master/server/src/order/order.service.ts`
**目标文件路径**: `allin-packages/order-module/src/services/order.service.ts`
**参考对照文件**:
- `allin-packages/salary-module/src/services/salary.service.ts`（服务层结构）
- `allin-packages/disability-module/src/services/disabled-person.service.ts`（文件验证逻辑）

**修改要点**:
1. 将NestJS服务转换为继承`GenericCrudService<EmploymentOrder>`
2. **循环依赖处理**: 移除对`DisabledPersonRepository`的直接依赖
3. **文件验证**: 在创建/更新OrderPersonAsset时验证`fileId`的有效性
4. 将业务逻辑从NestJS风格转换为Hono风格
5. 保持原始的业务逻辑和错误处理

#### 4.2 迁移OrderController为Hono路由
**源文件路径**: `allin_system-master/server/src/order/order.controller.ts`
**目标文件路径**: `allin-packages/order-module/src/routes/order.routes.ts`
**参考对照文件**:
- `allin-packages/salary-module/src/routes/salary.routes.ts`（Hono路由结构）
- `allin-packages/disability-module/src/routes/disabled-person.routes.ts`（文件ID参数处理）

**修改要点**:
1. 将NestJS控制器转换为Hono路由
2. 将class-validator管道验证转换为Zod验证
3. 更新API端点路径，保持与原始API兼容
4. 在OrderPersonAsset路由中添加`fileId`参数处理
5. 配置OpenAPI文档

### 5. 创建模块主文件 (AC: 1)
✅ **已完成**

**目标文件路径**:
- `allin-packages/order-module/src/index.ts`
- `allin-packages/order-module/src/order.module.ts`

**参考对照文件**:
- `allin-packages/salary-module/src/index.ts`（导出结构）
- `allin-packages/salary-module/src/salary.module.ts`（模块定义）

### 6. 创建测试文件 (AC: 10)
✅ **已完成**

**目标文件路径**:
- `allin-packages/order-module/tests/unit/order.controller.test.ts`
- `allin-packages/order-module/tests/unit/order.service.test.ts`
- `allin-packages/order-module/tests/integration/order.integration.test.ts`

**参考对照文件**:
- `allin-packages/salary-module/tests/unit/salary.controller.test.ts`（单元测试结构）
- `allin-packages/salary-module/tests/integration/salary.integration.test.ts`（集成测试结构）
- `allin-packages/disability-module/tests/integration/disability.integration.test.ts`（文件集成测试）

**测试重点**:
1. **枚举测试**: 验证`OrderStatus`和`WorkStatus`枚举的正确使用
2. **文件集成测试**: 测试OrderPersonAsset与File实体的关联
3. **循环依赖测试**: 验证与disability-module的解耦效果
4. **API兼容性测试**: 验证与原始NestJS API的兼容性

### 7. 处理循环依赖 (AC: 5)
✅ **已完成**

**问题**: 订单模块与disability-module存在相互引用
**源文件分析**:
- 订单模块引用: `DisabledPerson`实体
- 残疾人模块引用: `EmploymentOrder`和`OrderPerson`实体

**解决方案**:
1. **接口抽象**: 在订单模块中创建`PersonReference`接口，避免直接引用`DisabledPerson`实体
2. **ID引用**: 使用`personId: number`代替直接的实体引用
3. **事件解耦**: 通过事件或消息队列处理跨模块业务逻辑
4. **参考处理**: 参考`allin-packages/salary-module`与`@d8d/geo-areas`的集成方式

**实施步骤**:
1. 在订单模块中定义`PersonReference`类型
2. 修改OrderPerson实体，使用`personId: number`代替`person: DisabledPerson`
3. 修改OrderPersonAsset实体，使用`personId: number`代替`person: DisabledPerson`
4. 在服务层通过ID验证人员存在性（可选：通过API调用验证）
5. 更新残疾人管理模块，移除对订单实体的直接引用

### 8. 集成测试和验证 (AC: 11, 12)
✅ **已完成**
- [x] 运行单元测试：`pnpm test`
- [x] 运行集成测试：`pnpm test:integration`
- [x] 类型检查：`pnpm typecheck`
- [x] 构建验证：`pnpm build`
- [x] 循环依赖验证：检查模块间导入关系

## Dev Notes

### 从之前故事吸取的经验教训：

#### 从故事007.004（残疾人管理模块）吸取的经验教训：
1. **PostgreSQL类型兼容问题**：需要处理tinyint → smallint，datetime → timestamp的类型转换
2. **Schema验证错误**：实体字段与schema定义不匹配（createTime/updateTime → uploadTime/remarkTime/visitTime）
3. **测试数据问题**：缺少subBankName、operatorId等必填字段
4. **错误处理**：DELETE端点返回404而不是200，GET聚合端点返回404而不是500
5. **文件模块集成**：银行卡实体使用fileId字段而不是cardPhotoUrl
6. **实体主键命名**：确保所有实体使用`id`作为主键字段名，而不是`orderId`、`opId`等特定名称
7. **文件实体集成**：正确配置与`@d8d/file-module`的关联关系，参考disabled-photo.entity.ts

#### 从故事007.007（薪资管理模块）吸取的经验教训：
1. **唯一性约束冲突**：测试数据创建了相同(provinceId, cityId)组合的记录，需要修改测试数据
2. **Schema验证问题**：allowance、insurance、housingFund字段"expected number, received string"，需要改为z.coerce.number()
3. **路由冲突**：CRUD路由可能与自定义路由冲突，需要暂时注释掉CRUD路由聚合
4. **调试信息**：在路由中添加console.debug便于问题排查
5. **decimal字段处理**：需要正确处理字符串格式的decimal字段
6. **测试期望值**：按区域过滤测试期望值需要调整
7. **workspace配置**：在根目录`pnpm-workspace.yaml`中添加新包路径`'allin-packages/order-module'`
8. **测试依赖**：集成测试需要配置测试数据库，参考salary-module的`tests/integration/setup.ts`
9. **类型检查问题**：使用`pnpm typecheck`检查类型错误，特别注意枚举导入和循环依赖
10. **API兼容性**：保持与原始NestJS API相同的请求/响应格式
11. **枚举使用**：通过`@d8d/allin-enums`包导入OrderStatus和WorkStatus枚举
12. **区域包集成经验**：参考salary-module与geo-areas的集成方式处理外部依赖

#### 从故事007.001（渠道管理模块）吸取的经验教训：
1. **实体主键命名**：主键属性名应直接定义为`id`（而不是`channelId`）以遵循GenericCrudService约定
2. **布尔返回值处理**：需要正确处理布尔返回值转换，保持与原始API兼容
3. **软删除考虑**：考虑使用`status`字段实现软删除，而不是硬删除
4. **路由聚合模式**：使用`.route('/', customRoutes).route('/', crudRoutes)`聚合自定义路由和CRUD路由
5. **默认值设置**：创建时设置合理的默认值（contact_person, contact_phone等）

#### 从故事007.002（公司管理模块）吸取的经验教训：
1. **模块间依赖处理**：正确配置对`@d8d/allin-platform-module`的依赖
2. **复合唯一性检查**：公司名称在同一平台下唯一性检查（`company_name`和`platform_id`组合）
3. **关联关系配置**：正确配置与Platform实体的关联关系`@ManyToOne(() => Platform, { eager: true })`
4. **跨模块业务逻辑**：处理跨模块的业务逻辑，如平台ID验证
5. **排序配置**：默认按`company_id`降序排列

#### 从故事007.003（枚举常量包）吸取的经验教训：
1. **枚举值一致性**：保持与原始数据库值一致（小写字符串，下划线分隔）
2. **数字枚举处理**：残疾等级使用数字枚举（1, 2, 3, 4）
3. **注释完整性**：为每个枚举值添加中文注释说明和业务含义
4. **包配置优化**：枚举常量包直接引用源码，避免不必要的构建步骤
5. **类型安全**：提供类型安全的枚举常量，提高代码可读性和维护性

#### 从故事007.006（平台管理模块）吸取的经验教训：
1. **基础依赖包设计**：作为基础包，不需要依赖其他allin模块
2. **唯一性索引配置**：平台名称唯一性索引配置
3. **返回值格式**：成功返回`{ success: true }`，失败返回`{ success: false, message: "..." }`
4. **错误信息明确**：提供明确的错误信息，如"平台不存在或名称重复"
5. **路由模式参考**：参考channel-module的路由模式实现

### 关键经验教训总结（基于史诗007所有已完成故事）：

#### 数据库和类型相关：
1. **PostgreSQL类型兼容**：注意tinyint → smallint，datetime → timestamp的类型转换
2. **Decimal字段处理**：数据库返回的decimal字段可能是字符串格式，需要使用z.coerce.number()处理
3. **Schema一致性**：确保实体字段名与Schema定义完全匹配，特别注意时间字段命名
4. **枚举值一致性**：保持与原始数据库值一致（小写字符串，下划线分隔）
5. **数字枚举处理**：残疾等级等使用数字枚举（1, 2, 3, 4）

#### 实体设计相关：
6. **实体主键命名**：主键属性名应直接定义为`id`以遵循GenericCrudService约定
7. **唯一性约束**：配置正确的唯一性索引（单字段或复合字段）
8. **关联关系配置**：正确配置实体间关联关系，如`@ManyToOne(() => Platform, { eager: true })`
9. **软删除考虑**：考虑使用`status`字段实现软删除，而不是硬删除
10. **默认值设置**：创建时设置合理的默认值

#### 测试相关：
11. **测试数据完整性**：确保测试数据包含所有必填字段
12. **唯一性约束**：测试数据避免创建违反唯一性约束的记录
13. **测试期望值**：根据实际数据调整测试断言期望值
14. **调试信息**：在路由中添加console.debug便于问题排查

#### 路由和API相关：
15. **路由冲突**：CRUD路由可能与自定义路由冲突，需要合理设计路由结构
16. **路由聚合模式**：使用`.route('/', customRoutes).route('/', crudRoutes)`聚合路由
17. **错误响应**：确保DELETE返回200（成功）而不是404，GET聚合端点正确处理404场景
18. **API兼容性**：保持与原始NestJS API相同的请求/响应格式
19. **布尔返回值处理**：需要正确处理布尔返回值转换
20. **错误信息明确**：提供明确的错误信息，如"平台不存在或名称重复"

#### 模块集成相关：
21. **文件模块集成**：正确使用fileId字段而不是URL字段，建立与File实体的关联
22. **循环依赖处理**：使用ID引用代替直接实体引用，避免模块间循环依赖
23. **枚举集成**：通过@d8d/allin-enums包导入共享枚举
24. **模块间依赖处理**：正确配置模块间依赖关系
25. **跨模块业务逻辑**：处理跨模块的业务逻辑，如平台ID验证
26. **基础依赖包设计**：基础包不需要依赖其他allin模块
27. **区域包集成**：参考salary-module与geo-areas的集成方式

#### 开发流程相关：
28. **workspace配置**：及时在pnpm-workspace.yaml中添加新包路径
29. **类型检查**：使用pnpm typecheck检查类型错误，特别注意枚举导入
30. **测试依赖**：集成测试需要正确配置测试数据库和实体
31. **包配置优化**：枚举常量包直接引用源码，避免不必要的构建步骤
32. **排序配置**：默认按主键降序排列
33. **注释完整性**：为枚举值添加中文注释说明和业务含义

### 技术细节：
1. **OrderStatus和WorkStatus枚举集成**：
   ```typescript
   import { OrderStatus, WorkStatus } from '@d8d/allin-enums';

   @Column({
     name: 'order_status',
     type: 'enum',
     enum: OrderStatus,
     default: OrderStatus.DRAFT,
     comment: '订单状态'
   })
   orderStatus!: OrderStatus;

   @Column({
     name: 'work_status',
     type: 'enum',
     enum: WorkStatus,
     default: WorkStatus.NOT_WORKING,
     comment: '工作状态'
   })
   workStatus!: WorkStatus;
   ```

2. **OrderPersonAsset实体文件集成**：
   ```typescript
   import { File } from '@d8d/file-module/entities';

   @Column({
     name: 'file_id',
     type: 'int',
     nullable: false,
     comment: '文件ID，引用files表'
   })
   fileId!: number;

   @ManyToOne(() => File)
   @JoinColumn({ name: 'file_id' })
   file!: File;
   ```

3. **循环依赖处理方案**：
   ```typescript
   // 原代码（有循环依赖）:
   // @ManyToOne(() => DisabledPerson)
   // person!: DisabledPerson;

   // 新代码（解耦）:
   @Column({
     name: 'person_id',
     type: 'int',
     nullable: false,
     comment: '人员ID'
   })
   personId!: number;

   // 可选：定义引用接口
   export interface PersonReference {
     id: number;
     name: string;
     idCard: string;
   }
   ```

4. **AssetType和AssetFileType枚举**：
   - 这些是模块内部枚举，不需要移到enums包
   - 在`order-person-asset.entity.ts`中定义即可
   - 保持与原始值一致：`TAX = 'tax'`, `SALARY = 'salary'`等

### 文件模块集成步骤：
1. 安装依赖：`@d8d/file-module`
2. 在OrderPersonAsset实体中建立与File实体的关联
3. 在DTO中验证fileId的有效性
4. 在服务层处理文件关联逻辑
5. 在集成测试中模拟文件上传和关联

### 测试策略：
1. **单元测试**：覆盖所有控制器和服务方法
2. **集成测试**：测试数据库操作和API端点
3. **枚举验证测试**：测试OrderStatus和WorkStatus枚举的正确使用
4. **文件关联测试**：测试OrderPersonAsset与File实体的关联
5. **循环依赖测试**：验证解耦方案的有效性
6. **API兼容性测试**：验证与原始NestJS API的兼容性

## Dependencies
- @d8d/allin-enums
- @d8d/file-module
- @d8d/shared-crud
- @d8d/shared-utils
- @d8d/shared-types
- @d8d/auth-module
- @d8d/user-module
- typeorm
- zod
- hono
- vitest (dev dependency)
- @d8d/shared-test-util (dev dependency)

## Test Plan
1. **单元测试**：覆盖所有控制器和服务方法
2. **集成测试**：测试数据库操作和API端点
3. **枚举验证**：测试OrderStatus和WorkStatus枚举的正确使用
4. **文件关联测试**：测试OrderPersonAsset与File实体的关联
5. **循环依赖测试**：验证与disability-module的解耦效果
6. **API兼容性测试**：验证与原始NestJS API的兼容性

## Definition of Done
- [x] 所有实体成功迁移并转换为TypeORM + Zod
- [x] 完整的CRUD API端点可用
- [x] 集成@d8d/allin-enums包中的枚举
- [x] 集成@d8d/file-module包，正确处理文件关联
- [x] 解决与disability-module的循环依赖
- [x] 所有测试通过（单元测试、集成测试）
- [x] 类型检查通过
- [x] 构建成功
- [x] 代码符合项目代码规范
- [x] 文档完整（README、API文档）

## 实施建议（基于007.004和007.007经验）

### 1. 数据库类型处理
```typescript
// 处理PostgreSQL类型兼容
@Column({
  name: 'order_status',
  type: 'enum',
  enum: OrderStatus,
  default: OrderStatus.DRAFT
})
orderStatus!: OrderStatus;

// 处理decimal字段
@Column({
  name: 'total_amount',
  type: 'decimal',
  precision: 10,
  scale: 2
})
totalAmount!: number; // Schema中使用z.coerce.number()
```

### 2. Schema验证设计
```typescript
// 使用z.coerce.number()处理数据库返回的字符串
const CreateOrderSchema = z.object({
  totalAmount: z.coerce.number().min(0),
  // 确保字段名与实体完全一致
  createTime: z.string().datetime().optional(),
  updateTime: z.string().datetime().optional(),
});
```

### 3. 测试数据准备
```typescript
// 确保测试数据包含所有必填字段
const testOrderData = {
  orderName: '测试订单',
  platformId: 1,
  orderStatus: OrderStatus.DRAFT,
  workStatus: WorkStatus.NOT_WORKING,
  totalAmount: 1000.00,
  // 不要遗漏任何必填字段
};
```

### 4. 路由设计避免冲突
```typescript
// 合理设计路由结构，避免CRUD路由与自定义路由冲突
const orderRoutes = new Hono()
  .basePath('/orders')
  .route('/', customRoutes)  // 自定义路由
  .route('/', crudRoutes);   // CRUD路由（设置为readOnly: true）
```

### 5. 错误处理规范
```typescript
// DELETE成功返回200，GET聚合端点正确处理404
app.delete('/:id', async (c) => {
  const result = await service.delete(id);
  return c.json({ success: true }, 200); // 不是404
});

app.get('/aggregated/:id', async (c) => {
  const data = await service.findAggregated(id);
  if (!data) {
    return c.json({ error: 'Not found' }, 404); // 正确处理404
  }
  return c.json(data);
});
```

### 6. 调试信息添加
```typescript
// 在关键路由中添加console.debug便于问题排查
app.post('/create', async (c) => {
  const body = await c.req.json();
  console.debug('创建订单请求:', body);
  // ...处理逻辑
});
```

### 7. 布尔返回值处理（基于007.001和007.006）
```typescript
// 正确处理布尔返回值，保持与原始API兼容
app.post('/create', async (c) => {
  try {
    const result = await service.create(data);
    return c.json({ success: true }, 200);
  } catch (error) {
    return c.json({
      success: false,
      message: error.message || '创建失败'
    }, 400);
  }
});
```

### 8. 软删除实现（基于007.001和007.002）
```typescript
// 考虑使用status字段实现软删除
@Column({
  name: 'status',
  type: 'tinyint',
  default: 1,
  comment: '状态：0-删除，1-正常'
})
status!: number;

// 在服务层实现软删除
async delete(id: number): Promise<boolean> {
  const entity = await this.repository.findOne({ where: { id } });
  if (!entity) return false;

  entity.status = 0; // 软删除
  entity.updateTime = new Date();
  await this.repository.save(entity);
  return true;
}
```

### 9. 复合唯一性检查（基于007.002）
```typescript
// 公司名称在同一平台下唯一性检查
async create(data: CreateCompanyDto): Promise<Company> {
  // 检查(companyName, platformId)组合是否已存在
  const existing = await this.repository.findOne({
    where: {
      companyName: data.companyName,
      platformId: data.platformId,
      status: 1 // 只检查正常状态的记录
    }
  });

  if (existing) {
    throw new Error('同一平台下公司名称已存在');
  }

  return super.create(data);
}
```

### 10. 枚举使用规范（基于007.003）
```typescript
// 保持与数据库值一致的小写字符串枚举
import { OrderStatus, WorkStatus } from '@d8d/allin-enums';

// 在实体中使用
@Column({
  name: 'order_status',
  type: 'enum',
  enum: OrderStatus,
  default: OrderStatus.DRAFT,
  comment: '订单状态：draft-草稿, confirmed-已确认, in_progress-进行中, completed-已完成, cancelled-已取消'
})
orderStatus!: OrderStatus;

// 在Schema中验证
const CreateOrderSchema = z.object({
  orderStatus: z.nativeEnum(OrderStatus).default(OrderStatus.DRAFT),
  workStatus: z.nativeEnum(WorkStatus).default(WorkStatus.NOT_WORKING),
});
```

### 11. 路由聚合模式（基于007.001和007.006）
```typescript
// 合理聚合自定义路由和CRUD路由
import customRoutes from './order-custom.routes';
import crudRoutes from './order-crud.routes';

const orderRoutes = new Hono()
  .basePath('/orders')
  .route('/', customRoutes)   // 自定义业务逻辑路由
  .route('/', crudRoutes);    // 标准CRUD路由（readOnly: true）

export default orderRoutes;
```

## Dev Agent Record
**开发时间**: 2025-12-03
**开发者**: James (全栈开发工程师)
**开发状态**: 所有测试通过，类型检查通过，准备提交代码

### 已完成的工作：
1. **✅ 创建包结构和配置文件** (AC: 1)
   - 创建目录：`allin-packages/order-module/`
   - 初始化package.json：添加对`@d8d/file-module`和`@d8d/allin-enums`的依赖
   - 配置tsconfig.json、vitest.config.ts
   - 在pnpm-workspace.yaml中添加`'allin-packages/order-module'`

2. **✅ 迁移实体文件** (AC: 2, 3, 4)
   - 迁移EmploymentOrder实体：集成OrderStatus和WorkStatus枚举，字段名改为驼峰命名
   - 迁移OrderPerson实体：处理循环依赖，使用`personId: number`代替DisabledPerson引用
   - 迁移OrderPersonAsset实体：集成文件模块，添加`fileId`字段和File实体关联

3. **✅ 创建DTO和Schema文件** (AC: 8)
   - 创建order.schema.ts：包含所有实体和DTO的Zod Schema
   - 包含枚举验证、文件ID验证等

4. **✅ 迁移控制器和服务** (AC: 6, 7)
   - 迁移OrderService：继承GenericCrudService，添加文件验证、枚举验证等业务逻辑
   - 创建Hono路由：order-custom.routes.ts（自定义业务路由）、order-crud.routes.ts（标准CRUD路由）、order.routes.ts（主路由聚合）

5. **✅ 创建模块主文件** (AC: 1)
   - 创建index.ts：导出所有公共API
   - 创建order.module.ts：模块定义

6. **✅ 创建测试文件** (AC: 10)
   - 创建order.integration.test.ts：包含31个集成测试用例
   - 覆盖订单全生命周期、枚举验证、文件关联测试等
   - 创建测试数据工厂OrderTestDataFactory，减少重复代码

7. **✅ 处理循环依赖** (AC: 5)
   - 在OrderPerson和OrderPersonAsset实体中使用`personId: number`代替DisabledPerson的直接引用
   - 避免与disability-module的循环依赖

8. **✅ 集成测试和验证** (AC: 11, 12)
   - 运行完整测试套件：31个集成测试全部通过
   - 类型检查通过：pnpm typecheck无错误
   - 循环依赖验证：模块间导入关系清晰，无循环依赖

### 当前问题（已解决）：
1. **✅ 集成测试失败**：创建订单API返回500错误，错误信息：`"Cannot read properties of undefined (reading 'orderStatus')"`
   - 问题定位：Schema导出不一致，路由中导入`CreateOrderSchema`但Schema文件中导出的是`CreateEmploymentOrderSchema`
   - 解决方案：在order.schema.ts中添加兼容性别名导出：`export const CreateOrderSchema = CreateEmploymentOrderSchema;`

2. **✅ 订单人员资产创建测试失败**：返回400错误
   - 问题定位：测试中使用字符串'身份证'，但Schema期望AssetType枚举值
   - 解决方案：更新测试代码使用`AssetType.DISABILITY_CERT`枚举值

3. **✅ 类型检查失败**：运行pnpm typecheck发现大量类型错误
   - 主要问题：
     - index.ts重复导出
     - QueryOrderSchema不匹配
     - OrderPersonAsset实体缺少createTime字段
     - OrderService中createOrderPersonAsset方法参数类型不匹配
     - 测试中的assetType和assetFileType类型不匹配
     - 测试中实体创建使用orderRepository.create()但TypeORM期望DeepPartial类型
     - BatchAddPersonsSchema期望orderId但实际API从URL参数获取
   - 解决方案：
     - 修复index.ts的重复导出问题，改为显式导出
     - 创建正确的QueryOrderSchema和QueryOrderPersonAssetSchema
     - 在OrderPersonAsset实体中添加createTime字段
     - 修复OrderService中createOrderPersonAsset方法的参数类型为AssetType和AssetFileType枚举
     - 更新测试代码，使用正确的枚举值
     - 将测试中的orderRepository.create()改为new EmploymentOrder()和new OrderPersonAsset()
     - 创建BatchAddPersonItemSchema（不需要orderId），更新BatchAddPersonsSchema使用新Schema

4. **✅ 测试运行失败**：运行完整测试发现3个失败
   - 问题1：GET /order/detail/:id返回500错误，错误信息：`"Invalid input: expected number, received undefined"`，路径是`["id"]`
     - 问题定位：OrderService的findOne方法返回`orderId`字段，但EmploymentOrderSchema期望`id`字段
     - 解决方案：修复findOne方法，返回`id`字段而不是`orderId`字段

   - 问题2：处理已结束或已取消的订单返回404而不是400
     - 问题定位：路由错误处理顺序问题，错误消息同时包含"订单ID"和"已结束或已取消"，先匹配了"订单ID"返回404
     - 解决方案：调整错误处理顺序，先检查"已结束或已取消"，再检查"订单ID"

   - 问题3：资产类型枚举值不匹配问题
     - 问题定位：测试断言使用字符串'身份证'，但实际应该使用枚举值`AssetType.DISABILITY_CERT`
     - 解决方案：更新测试断言使用枚举值而不是硬编码字符串

### 技术实现细节：
1. **枚举集成**：正确使用`@d8d/allin-enums`包中的OrderStatus和WorkStatus枚举
2. **文件模块集成**：在OrderPersonAsset实体中建立与File实体的关联
3. **循环依赖处理**：使用ID引用代替直接实体引用
4. **PostgreSQL类型兼容**：正确处理enum字段类型
5. **路由聚合**：使用Hono的路由聚合模式，聚合自定义路由和CRUD路由

### 下一步：
1. ✅ 运行完整测试套件验证修复 - 已完成，31个测试全部通过
2. ✅ 创建测试数据工厂 - 已完成，创建了OrderTestDataFactory
3. ✅ 提交当前版本的模块代码 - 准备提交
4. 验证模块在其他项目中的集成效果（如有需要）

---

*故事创建时间：2025-12-02*
*创建者：Bob (Scrum Master)*
*更新说明：重新创建故事，吸取之前移植故事的经验教训，在任务中标注迁移文件的路径和参考对照文件的路径*
*经验教训更新：2025-12-02，基于故事007.004和007.007的经验教训补充*
*开发记录更新：2025-12-03，记录开发进度和当前问题*