|
|
@@ -0,0 +1,475 @@
|
|
|
+# Story 015.005: 就业信息API
|
|
|
+
|
|
|
+## Status
|
|
|
+Approved
|
|
|
+
|
|
|
+## Story
|
|
|
+**作为** 人才用户,
|
|
|
+**我想要** 能够查看我的就业信息,包括当前就业状态、薪资记录和就业历史,
|
|
|
+**以便于** 我能够了解和管理我的工作情况
|
|
|
+
|
|
|
+## Acceptance Criteria
|
|
|
+1. 当前就业状态查询接口返回正确的工作信息(企业名称、岗位名称、入职日期、工作状态、订单编号、薪资水平)
|
|
|
+2. 薪资记录查询接口返回历史薪资记录,支持按月份查询
|
|
|
+3. 就业历史查询接口返回个人的就业历史记录,按时间倒序排列
|
|
|
+4. 薪资视频查询接口返回薪资相关视频记录(工资视频、个税视频),支持按月份查询
|
|
|
+5. 所有接口验证用户权限,确保用户只能查询自己的数据
|
|
|
+6. 查询性能优化,添加必要的数据索引
|
|
|
+7. 所有接口通过单元测试和集成测试
|
|
|
+
|
|
|
+## Tasks / Subtasks
|
|
|
+
|
|
|
+- [ ] 任务1: 创建人才就业信息Schema (AC: 1, 2, 3)
|
|
|
+ - [ ] 在order-module创建人才就业Schema文件 `talent-employment.schema.ts`
|
|
|
+ - [ ] 创建当前就业状态响应Schema (EmploymentStatusResponseSchema)
|
|
|
+ - [ ] 创建薪资记录响应Schema (SalaryRecordResponseSchema)
|
|
|
+ - [ ] 创建就业历史响应Schema (EmploymentHistoryResponseSchema)
|
|
|
+ - [ ] 创建薪资视频响应Schema (SalaryVideoResponseSchema)
|
|
|
+ - [ ] 创建查询参数Schema (月份过滤、分页等)
|
|
|
+ - [ ] 添加OpenAPI文档元数据
|
|
|
+
|
|
|
+- [ ] 任务2: 扩展OrderService添加人才专用查询方法 (AC: 1, 2, 3, 4)
|
|
|
+ - [ ] 添加 `getCurrentEmploymentStatus(personId: number)` 方法 - 获取当前就业状态
|
|
|
+ - [ ] 添加 `getSalaryRecords(personId: number, month?: string)` 方法 - 获取薪资记录
|
|
|
+ - [ ] 添加 `getEmploymentHistory(personId: number)` 方法 - 获取就业历史
|
|
|
+ - [ ] 添加 `getSalaryVideos(personId: number, month?: string)` 方法 - 获取薪资视频
|
|
|
+ - [ ] 关联查询employment_order、company表获取企业信息
|
|
|
+ - [ ] 关联查询order_person_asset表获取视频信息
|
|
|
+ - [ ] 关联file表获取视频URL
|
|
|
+
|
|
|
+- [ ] 任务3: 创建人才就业信息API路由 (AC: 1, 2, 3, 4)
|
|
|
+ - [ ] 在order-module创建人才就业路由文件 `talent-employment.routes.ts`
|
|
|
+ - [ ] 实现GET `/employment/status` 接口 - 查询当前就业状态
|
|
|
+ - [ ] 实现GET `/employment/salary-records` 接口 - 查询薪资记录
|
|
|
+ - [ ] 实现GET `/employment/history` 接口 - 查询就业历史
|
|
|
+ - [ ] 实现GET `/employment/salary-videos` 接口 - 查询薪资视频
|
|
|
+ - [ ] 基于person_id和JWT token验证人才用户身份
|
|
|
+ - [ ] 确保用户只能查询自己的就业信息
|
|
|
+ - [ ] 添加OpenAPI文档元数据
|
|
|
+
|
|
|
+- [ ] 任务4: 使用talentAuthMiddleware认证中间件
|
|
|
+ - [ ] 在人才就业路由中应用talentAuthMiddleware
|
|
|
+ - [ ] 从JWT token中提取person_id
|
|
|
+ - [ ] 验证用户类型为talent
|
|
|
+ - [ ] 将person_id注入到上下文中用于查询
|
|
|
+
|
|
|
+- [ ] 任务5: 添加数据库查询性能优化 (AC: 6)
|
|
|
+ - [ ] 确认order_person表已有索引: `['personId', 'workStatus']`, `['personId', 'joinDate', 'workStatus']`
|
|
|
+ - [ ] 确认order_person_asset表已有索引: `['personId', 'assetType']`, `['relatedTime']`
|
|
|
+ - [ ] 验证查询性能,确保响应时间 < 100ms
|
|
|
+ - [ ] 使用TypeORM relations预加载关联数据
|
|
|
+ - [ ] 避免N+1查询问题
|
|
|
+
|
|
|
+- [ ] 任务6: 在server包注册人才就业路由
|
|
|
+ - [ ] 在server包中导入 `talent-employment.routes.ts`
|
|
|
+ - [ ] 添加 `/api/v1/rencai` 前缀
|
|
|
+ - [ ] 确保路由正确注册到主应用
|
|
|
+
|
|
|
+- [ ] 任务7: 编写单元测试和集成测试 (AC: 5, 7)
|
|
|
+ - [ ] 测试当前就业状态查询成功场景
|
|
|
+ - [ ] 测试薪资记录查询成功场景和月份过滤
|
|
|
+ - [ ] 测试就业历史查询成功场景和时间排序
|
|
|
+ - [ ] 测试薪资视频查询成功场景和类型过滤
|
|
|
+ - [ ] 测试权限验证 - 用户只能查询自己的数据
|
|
|
+ - [ ] 测试认证失败场景 (未登录、非人才用户)
|
|
|
+ - [ ] 测试用户不存在场景
|
|
|
+ - [ ] 测试无就业记录场景
|
|
|
+ - [ ] 集成测试验证完整查询流程
|
|
|
+ - [ ] 性能测试验证查询响应时间
|
|
|
+
|
|
|
+- [ ] 任务8: 更新模块导出和文档
|
|
|
+ - [ ] 在order-module的index.ts中导出新路由和Schema
|
|
|
+ - [ ] 更新README文档 (如果需要)
|
|
|
+ - [ ] 确保类型正确导出供前端使用
|
|
|
+
|
|
|
+## Dev Notes
|
|
|
+
|
|
|
+### 先前故事见解
|
|
|
+- **故事015.001**: 数据库schema扩展完成,users2表支持talent类型和person_id字段 [Source: docs/stories/015.001.story.md#L130-L141]
|
|
|
+- **故事015.002**: 人才用户认证API完成,建立了talentAuthMiddleware中间件模式 [Source: docs/stories/015.002.story.md#L325]
|
|
|
+- **故事015.003**: 个人信息管理API完成,建立了人才专用API路由模式 [Source: docs/stories/015.003.story.md]
|
|
|
+- **认证模式**: 使用talentAuthMiddleware中间件验证人才用户身份,从JWT token中提取person_id [Source: docs/stories/015.002.story.md#L325]
|
|
|
+- **路由前缀约定**: 人才小程序API使用 `/api/v1/rencai` 前缀,模块内路由不包含前缀 [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L53-L61]
|
|
|
+- **只读设计原则**: 人才小程序API以查询功能为主,数据修改由管理员在后台处理 [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L50-L52]
|
|
|
+
|
|
|
+### 技术栈要求
|
|
|
+- **后端框架**: Hono 4.8.5 [Source: docs/architecture/tech-stack.md#L12]
|
|
|
+- **数据库**: PostgreSQL 17 [Source: docs/architecture/tech-stack.md#L15]
|
|
|
+- **ORM**: TypeORM 0.3.20 [Source: docs/architecture/tech-stack.md#L16]
|
|
|
+- **测试框架**: Vitest 2.x [Source: docs/architecture/testing-strategy.md#L45-L46]
|
|
|
+- **认证**: JWT 9.0.2 [Source: docs/architecture/tech-stack.md#L19]
|
|
|
+
|
|
|
+### 数据模型规范
|
|
|
+
|
|
|
+**OrderPerson实体关键字段** [Source: allin-packages/order-module/src/entities/order-person.entity.ts]:
|
|
|
+- `id`: op_id (主键)
|
|
|
+- `orderId`: 订单ID (外键引用employment_order表)
|
|
|
+- `personId`: 残疾人ID (外键)
|
|
|
+- `joinDate`: 入职日期
|
|
|
+- `actualStartDate`: 实际入职日期
|
|
|
+- `leaveDate`: 离职日期
|
|
|
+- `workStatus`: 工作状态 (枚举: not_working, pre_working, working, resigned)
|
|
|
+- `salaryDetail`: 个人薪资 (decimal类型)
|
|
|
+- 关系: `order` (多对一), `person` (多对一)
|
|
|
+
|
|
|
+**EmploymentOrder实体关键字段** [Source: allin-packages/order-module/src/entities/employment-order.entity.ts]:
|
|
|
+- `id`: order_id (主键)
|
|
|
+- `orderName`: 订单名称
|
|
|
+- `platformId`: 用人平台ID
|
|
|
+- `companyId`: 用人单位ID (外键)
|
|
|
+- `channelId`: 渠道ID
|
|
|
+- `expectedStartDate`: 预计开始日期
|
|
|
+- `actualStartDate`: 实际开始日期
|
|
|
+- `actualEndDate`: 实际结束日期
|
|
|
+- `orderStatus`: 订单状态 (枚举: draft, confirmed, in_progress, completed, cancelled)
|
|
|
+- `workStatus`: 工作状态 (枚举: not_working, pre_working, working, resigned)
|
|
|
+- `createTime`: 创建时间
|
|
|
+- `updateTime`: 更新时间
|
|
|
+- 关系: `orderPersons` (一对多)
|
|
|
+
|
|
|
+**OrderPersonAsset实体关键字段** [Source: allin-packages/order-module/src/entities/order-person-asset.entity.ts]:
|
|
|
+- `id`: op_id (主键)
|
|
|
+- `orderId`: 订单ID (外键)
|
|
|
+- `personId`: 残疾人ID (外键)
|
|
|
+- `assetType`: 资产类型 (枚举: tax, salary, job_result, contract_sign, disability_cert, other, salary_video, tax_video, checkin_video, work_video)
|
|
|
+- `assetFileType`: 资产文件类型 (枚举: image, video)
|
|
|
+- `fileId`: 文件ID (外键引用files表)
|
|
|
+- `status`: 视频审核状态 (pending, verified, rejected)
|
|
|
+- `relatedTime`: 关联时间
|
|
|
+- 关系: `order` (多对一), `file` (多对一)
|
|
|
+
|
|
|
+**Company实体关键字段** (参考company-module):
|
|
|
+- `id`: company_id (主键)
|
|
|
+- `companyName`: 企业名称
|
|
|
+- `companyId`: 企业ID (外键引用platform表)
|
|
|
+
|
|
|
+**DisabledPerson实体关键字段** (参考disability-module):
|
|
|
+- `id`: person_id (主键)
|
|
|
+- `name`: 姓名
|
|
|
+
|
|
|
+**UserEntity关键字段** (参考故事015.002):
|
|
|
+- `id`: 用户ID (主键)
|
|
|
+- `username`: 用户名
|
|
|
+- `userType`: 用户类型 (admin/employer/talent)
|
|
|
+- `personId`: 残疾人ID (外键,可为空) [Source: docs/stories/015.001.story.md#L132-L133]
|
|
|
+
|
|
|
+### API路径约定
|
|
|
+- **人才小程序API前缀**: `/api/v1/rencai` [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L53-L61]
|
|
|
+- **模块包内路由定义**: 不应包含API前缀,前缀在server包注册时统一添加 [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L335-L337]
|
|
|
+
|
|
|
+**具体接口路径**:
|
|
|
+- 当前就业状态查询: `GET /api/v1/rencai/employment/status`
|
|
|
+- 薪资记录查询: `GET /api/v1/rencai/employment/salary-records`
|
|
|
+- 就业历史查询: `GET /api/v1/rencai/employment/history`
|
|
|
+- 薪资视频查询: `GET /api/v1/rencai/employment/salary-videos`
|
|
|
+
|
|
|
+### 项目结构指南
|
|
|
+**模块位置**:
|
|
|
+- order-module: `allin-packages/order-module/` [Source: docs/architecture/source-tree.md]
|
|
|
+- disability-module: `allin-packages/disability-module/` (参考人才个人信息API)
|
|
|
+- auth-module: `packages/core-module/auth-module/` (认证中间件) [Source: docs/architecture/source-tree.md#L109-L129]
|
|
|
+- server包: `packages/server/` (路由注册) [Source: docs/architecture/source-tree.md#L56-L61]
|
|
|
+
|
|
|
+**order-module路由文件结构** (参考现有模式):
|
|
|
+```
|
|
|
+allin-packages/order-module/src/routes/
|
|
|
+├── talent-employment.routes.ts # 新增: 人才就业路由
|
|
|
+├── order-custom.routes.ts # 现有: 企业用户扩展路由
|
|
|
+├── order-crud.routes.ts # 现有: CRUD路由
|
|
|
+└── index.ts # 路由导出
|
|
|
+```
|
|
|
+
|
|
|
+**order-module服务文件结构**:
|
|
|
+```
|
|
|
+allin-packages/order-module/src/services/
|
|
|
+├── order.service.ts # 现有服务,需要扩展
|
|
|
+└── index.ts # 服务导出
|
|
|
+```
|
|
|
+
|
|
|
+**order-module Schema文件结构**:
|
|
|
+```
|
|
|
+allin-packages/order-module/src/schemas/
|
|
|
+├── talent-employment.schema.ts # 新增: 人才就业Schema
|
|
|
+├── order.schema.ts # 现有: 订单Schema
|
|
|
+└── index.ts # Schema导出
|
|
|
+```
|
|
|
+
|
|
|
+### Schema验证规范
|
|
|
+
|
|
|
+**当前就业状态响应Schema**:
|
|
|
+```typescript
|
|
|
+const EmploymentStatusResponseSchema = z.object({
|
|
|
+ companyName: z.string(), // 关联company表
|
|
|
+ orderId: z.number(),
|
|
|
+ orderName: z.string().nullable(),
|
|
|
+ positionName: z.string().nullable(), // 订单名称作为岗位名称
|
|
|
+ joinDate: z.string(), // ISO日期字符串 YYYY-MM-DD
|
|
|
+ workStatus: z.string(), // 枚举: not_working, pre_working, working, resigned
|
|
|
+ salaryLevel: z.number(), // 来自salaryDetail
|
|
|
+ actualStartDate: z.string().nullable(),
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**薪资记录响应Schema**:
|
|
|
+```typescript
|
|
|
+const SalaryRecordSchema = z.object({
|
|
|
+ orderId: z.number(),
|
|
|
+ orderName: z.string().nullable(),
|
|
|
+ companyName: z.string().nullable(), // 关联company表
|
|
|
+ salaryAmount: z.number(), // 来自salaryDetail
|
|
|
+ joinDate: z.string(), // ISO日期字符串 YYYY-MM-DD
|
|
|
+ month: z.string(), // 格式: YYYY-MM
|
|
|
+});
|
|
|
+
|
|
|
+const SalaryRecordsResponseSchema = z.object({
|
|
|
+ data: z.array(SalaryRecordSchema),
|
|
|
+ total: z.number(),
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**就业历史响应Schema**:
|
|
|
+```typescript
|
|
|
+const EmploymentHistoryItemSchema = z.object({
|
|
|
+ orderId: z.number(),
|
|
|
+ orderName: z.string().nullable(),
|
|
|
+ companyName: z.string().nullable(), // 关联company表
|
|
|
+ positionName: z.string().nullable(),
|
|
|
+ joinDate: z.string(), // ISO日期字符串 YYYY-MM-DD
|
|
|
+ leaveDate: z.string().nullable(), // ISO日期字符串 YYYY-MM-DD
|
|
|
+ workStatus: z.string(), // 枚举
|
|
|
+ salaryLevel: z.number(),
|
|
|
+});
|
|
|
+
|
|
|
+const EmploymentHistoryResponseSchema = z.object({
|
|
|
+ data: z.array(EmploymentHistoryItemSchema),
|
|
|
+ total: z.number(),
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**薪资视频响应Schema**:
|
|
|
+```typescript
|
|
|
+const SalaryVideoSchema = z.object({
|
|
|
+ id: z.number(),
|
|
|
+ assetType: z.string(), // salary_video 或 tax_video
|
|
|
+ assetFileType: z.string(), // video
|
|
|
+ fileUrl: z.string().nullable(), // 关联file表
|
|
|
+ fileName: z.string().nullable(),
|
|
|
+ status: z.string(), // pending, verified, rejected
|
|
|
+ relatedTime: z.string(), // ISO时间字符串
|
|
|
+ month: z.string(), // 格式: YYYY-MM (从relatedTime提取)
|
|
|
+});
|
|
|
+
|
|
|
+const SalaryVideosResponseSchema = z.object({
|
|
|
+ data: z.array(SalaryVideoSchema),
|
|
|
+ total: z.number(),
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**查询参数Schema**:
|
|
|
+```typescript
|
|
|
+const SalaryQuerySchema = z.object({
|
|
|
+ month: z.string().regex(/^\d{4}-\d{2}$/).optional(), // 格式: YYYY-MM
|
|
|
+ skip: z.coerce.number().int().min(0).default(0),
|
|
|
+ take: z.coerce.number().int().min(1).max(100).default(10),
|
|
|
+});
|
|
|
+
|
|
|
+const EmploymentHistoryQuerySchema = z.object({
|
|
|
+ skip: z.coerce.number().int().min(0).default(0),
|
|
|
+ take: z.coerce.number().int().min(1).max(100).default(20), // 就业历史可能较多,默认20条
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 业务逻辑规范
|
|
|
+
|
|
|
+**当前就业状态查询逻辑**:
|
|
|
+1. 从JWT token获取person_id
|
|
|
+2. 查询order_person表,条件: person_id = ? AND work_status IN ('pre_working', 'working')
|
|
|
+3. 如果有记录,取最新的一条(按join_date降序)
|
|
|
+4. 如果没有记录,返回空状态(未就业)
|
|
|
+5. 关联查询employment_order表获取订单信息
|
|
|
+6. 关联查询company表获取企业名称
|
|
|
+7. 返回当前就业状态
|
|
|
+
|
|
|
+**薪资记录查询逻辑**:
|
|
|
+1. 从JWT token获取person_id
|
|
|
+2. 查询order_person表,条件: person_id = ?,按join_date降序
|
|
|
+3. 如果有month参数,过滤join_date在指定月份的记录
|
|
|
+4. 关联查询employment_order表获取订单信息
|
|
|
+5. 关联查询company表获取企业名称
|
|
|
+6. 返回薪资记录列表,支持分页
|
|
|
+
|
|
|
+**就业历史查询逻辑**:
|
|
|
+1. 从JWT token获取person_id
|
|
|
+2. 查询order_person表,条件: person_id = ?,按join_date降序排列
|
|
|
+3. 关联查询employment_order表获取订单信息
|
|
|
+4. 关联查询company表获取企业名称
|
|
|
+5. 返回就业历史列表,包含所有状态的记录(包括离职记录)
|
|
|
+6. 支持分页
|
|
|
+
|
|
|
+**薪资视频查询逻辑**:
|
|
|
+1. 从JWT token获取person_id
|
|
|
+2. 查询order_person_asset表,条件: person_id = ? AND asset_type IN ('salary_video', 'tax_video')
|
|
|
+3. 如果有month参数,过滤related_time在指定月份的记录
|
|
|
+4. 关联查询file表获取文件URL
|
|
|
+5. 返回薪资视频列表,按related_time降序排列
|
|
|
+6. 支持分页
|
|
|
+
|
|
|
+### 认证和权限验证
|
|
|
+**使用talentAuthMiddleware**:
|
|
|
+- 从故事015.002中复用 `talentAuthMiddleware` [Source: docs/stories/015.002.story.md#L325]
|
|
|
+- 中间件验证JWT token和用户类型为talent
|
|
|
+- 从token中提取person_id并注入到上下文
|
|
|
+
|
|
|
+**权限验证逻辑**:
|
|
|
+1. talentAuthMiddleware验证用户身份
|
|
|
+2. 从上下文获取person_id
|
|
|
+3. 使用person_id查询数据,确保用户只能访问自己的信息
|
|
|
+4. 如果person_id不匹配,返回403错误
|
|
|
+
|
|
|
+### 只读设计原则
|
|
|
+**查询专用接口**:
|
|
|
+- 所有接口均为GET请求,不提供POST/PUT/DELETE
|
|
|
+- 就业信息修改由管理员在管理后台处理
|
|
|
+- 人才用户只能查看数据,不能修改
|
|
|
+
|
|
|
+### 文件模块集成
|
|
|
+**File模块集成** (参考现有模式):
|
|
|
+- 薪资视频关联到file_id
|
|
|
+- 需要关联查询File实体获取文件URL
|
|
|
+- File实体位置: `packages/file-module/src/entities/file.entity.ts` [Source: docs/architecture/source-tree.md#L130-L149]
|
|
|
+- 使用FileService获取文件下载URL (如需要) [Source: allin-packages/order-module/src/services/order.service.ts#L14-L15]
|
|
|
+
|
|
|
+### 数据库索引优化
|
|
|
+**现有索引** (已在实体定义中):
|
|
|
+- OrderPerson: `['personId', 'workStatus']`, `['personId', 'joinDate', 'workStatus']` [Source: allin-packages/order-module/src/entities/order-person.entity.ts#L7-L12]
|
|
|
+- OrderPersonAsset: `['personId', 'assetType']`, `['relatedTime']` [Source: allin-packages/order-module/src/entities/order-person-asset.entity.ts#L7-L11]
|
|
|
+
|
|
|
+**查询性能优化**:
|
|
|
+- 使用现有索引优化查询性能
|
|
|
+- 使用TypeORM的relations预加载关联数据
|
|
|
+- 避免N+1查询问题
|
|
|
+- 验证查询响应时间 < 100ms
|
|
|
+
|
|
|
+### 关联查询优化
|
|
|
+**TypeORM关系配置** (参考现有模式):
|
|
|
+- OrderPerson已配置 `@ManyToOne` 关系: `order`, `person` [Source: allin-packages/order-module/src/entities/order-person.entity.ts#L82-L89]
|
|
|
+- EmploymentOrder已配置 `@OneToMany` 关系: `orderPersons` [Source: allin-packages/order-module/src/entities/employment-order.entity.ts#L109-L110]
|
|
|
+- OrderPersonAsset已配置 `@ManyToOne` 关系: `order`, `file` [Source: allin-packages/order-module/src/entities/order-person-asset.entity.ts#L99-L112]
|
|
|
+- 使用 `relations` 选项预加载关联数据
|
|
|
+
|
|
|
+### 参考实现
|
|
|
+- **人才个人信息路由**: 参考 `talent-personal-info.routes.ts` 的路由定义模式 [Source: allin-packages/disability-module/src/routes/talent-personal-info.routes.ts]
|
|
|
+- **OrderService**: 参考现有的服务方法模式 [Source: allin-packages/order-module/src/services/order.service.ts]
|
|
|
+- **认证中间件**: 参考 `talentAuthMiddleware` [Source: docs/stories/015.002.story.md#L325]
|
|
|
+
|
|
|
+### 文件位置
|
|
|
+**需要创建/修改的文件**:
|
|
|
+- `allin-packages/order-module/src/routes/talent-employment.routes.ts` - 新增: 人才就业路由
|
|
|
+- `allin-packages/order-module/src/routes/index.ts` - 修改: 导出新路由
|
|
|
+- `allin-packages/order-module/src/schemas/talent-employment.schema.ts` - 新增: 人才就业Schema
|
|
|
+- `allin-packages/order-module/src/schemas/index.ts` - 修改: 导出新Schema
|
|
|
+- `allin-packages/order-module/src/services/order.service.ts` - 修改: 添加人才专用查询方法
|
|
|
+- `packages/server/src/index.ts` - 修改: 注册人才就业路由 (添加`/api/v1/rencai`前缀)
|
|
|
+- `allin-packages/order-module/tests/integration/talent-employment.integration.test.ts` - 新增: 集成测试
|
|
|
+
|
|
|
+### 技术约束
|
|
|
+- **包命名**: 使用现有的 `@d8d/allin-order-module` 包 [Source: docs/architecture/backend-module-package-standards.md#L39-L42]
|
|
|
+- **循环依赖处理**: 使用ID引用或TypeORM的relations避免循环依赖 [Source: docs/architecture/backend-module-package-standards.md#L314-L328]
|
|
|
+- **模块导出**: 确保新路由和Schema正确导出供server包使用
|
|
|
+
|
|
|
+### Testing
|
|
|
+
|
|
|
+#### 测试文件位置
|
|
|
+- 集成测试: `allin-packages/order-module/tests/integration/talent-employment.integration.test.ts` [Source: docs/architecture/testing-strategy.md#L53]
|
|
|
+
|
|
|
+#### 测试框架
|
|
|
+- Vitest 2.x [Source: docs/architecture/testing-strategy.md#L45-L46]
|
|
|
+- hono/testing用于API测试 [Source: docs/architecture/tech-stack.md#L26]
|
|
|
+
|
|
|
+#### 测试覆盖率要求
|
|
|
+- 核心业务逻辑 > 80% [Source: docs/architecture/coding-standards.md#L18]
|
|
|
+- API端点覆盖 ≥ 60% [Source: docs/architecture/testing-strategy.md#L169-L173]
|
|
|
+
|
|
|
+#### 测试策略
|
|
|
+**单元测试** (可选):
|
|
|
+- 测试Service层的查询方法
|
|
|
+- 测试数据过滤和排序逻辑
|
|
|
+
|
|
|
+**集成测试** (必须):
|
|
|
+- 测试完整查询流程 (从路由到数据库)
|
|
|
+- 测试认证和权限验证
|
|
|
+- 测试关联查询正确性
|
|
|
+- 测试错误场景
|
|
|
+
|
|
|
+**测试场景**:
|
|
|
+1. **当前就业状态查询成功场景**:
|
|
|
+ - 人才用户登录后成功查询当前就业状态
|
|
|
+ - 返回完整的企业信息、岗位信息、入职日期、工作状态、薪资水平
|
|
|
+ - 数据格式正确
|
|
|
+ - 未就业状态返回空数据
|
|
|
+
|
|
|
+2. **薪资记录查询成功场景**:
|
|
|
+ - 成功查询薪资记录列表
|
|
|
+ - 按join_date降序排列
|
|
|
+ - 企业名称正确关联
|
|
|
+ - 支持按月份过滤
|
|
|
+ - 分页功能正常
|
|
|
+
|
|
|
+3. **就业历史查询成功场景**:
|
|
|
+ - 成功查询就业历史列表
|
|
|
+ - 按join_date降序排列
|
|
|
+ - 包含所有状态的记录(在职、离职)
|
|
|
+ - 企业名称正确关联
|
|
|
+ - 分页功能正常
|
|
|
+
|
|
|
+4. **薪资视频查询成功场景**:
|
|
|
+ - 成功查询薪资视频列表
|
|
|
+ - 按related_time降序排列
|
|
|
+ - 文件URL正确返回
|
|
|
+ - 支持按类型过滤(salary_video, tax_video)
|
|
|
+ - 支持按月份过滤
|
|
|
+ - 分页功能正常
|
|
|
+
|
|
|
+5. **权限验证场景**:
|
|
|
+ - 未登录用户返回401
|
|
|
+ - 非talent用户返回401
|
|
|
+ - 用户尝试查询他人数据返回403
|
|
|
+ - person_id不匹配返回403
|
|
|
+
|
|
|
+6. **错误场景**:
|
|
|
+ - 用户不存在返回404
|
|
|
+ - 无效的月份参数返回400
|
|
|
+ - 数据库错误返回500
|
|
|
+ - 无就业记录时返回空列表
|
|
|
+
|
|
|
+7. **性能测试场景**:
|
|
|
+ - 验证查询响应时间 < 100ms
|
|
|
+ - 验证大数据量场景下的分页性能
|
|
|
+ - 验证索引使用情况
|
|
|
+
|
|
|
+**测试数据工厂** (参考现有模式):
|
|
|
+- 使用测试数据工厂创建EmploymentOrder测试数据
|
|
|
+- 创建关联的OrderPerson测试数据
|
|
|
+- 创建关联的Company测试数据 (如需要)
|
|
|
+- 创建关联的OrderPersonAsset测试数据 (用于薪资视频测试)
|
|
|
+- 创建关联的File测试数据 (如需要)
|
|
|
+- 确保测试数据完整,包含所有必填字段
|
|
|
+
|
|
|
+## Change Log
|
|
|
+| Date | Version | Description | Author |
|
|
|
+|------|---------|-------------|--------|
|
|
|
+| 2025-12-25 | 1.0 | 初始故事创建 | Scrum Master |
|
|
|
+
|
|
|
+## Dev Agent Record
|
|
|
+*此部分由开发代理在实施过程中填写*
|
|
|
+
|
|
|
+### Agent Model Used
|
|
|
+
|
|
|
+### Debug Log References
|
|
|
+
|
|
|
+### Completion Notes List
|
|
|
+
|
|
|
+### File List
|
|
|
+
|
|
|
+## QA Results
|
|
|
+*此部分由QA代理在审查完成后填写*
|