|
@@ -0,0 +1,389 @@
|
|
|
|
|
+# Story 007.007: 移植薪资管理模块(salary → @d8d/allin-salary-module)
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+Draft
|
|
|
|
|
+
|
|
|
|
|
+## Story
|
|
|
|
|
+**As a** 开发者,
|
|
|
|
|
+**I want** 将salary模块从allin_system-master移植为独立包@d8d/allin-salary-module,完成区域包集成和实体重构,
|
|
|
|
|
+**so that** 我们可以将Allin系统的薪资管理功能集成到当前项目中,遵循现有的模块化架构和编码标准,并实现与@d8d/geo-areas包的集成。
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+1. ✅ 创建`allin-packages/salary-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:`SalaryLevel`实体转换,添加区域ID字段
|
|
|
|
|
+3. ✅ **区域包集成**:使用`@d8d/geo-areas`包管理区域数据
|
|
|
|
|
+4. ✅ 完成服务层转换:薪资业务逻辑,包含区域数据验证
|
|
|
|
|
+5. ✅ 完成路由层转换:Hono路由实现,支持区域ID参数
|
|
|
|
|
+6. ✅ 完成验证系统转换:Zod Schema定义,包含区域ID验证
|
|
|
|
|
+7. ✅ 配置package.json:依赖管理,包含对`@d8d/geo-areas`的依赖
|
|
|
|
|
+8. ✅ 编写API集成测试:验证薪资管理功能,包含区域数据测试
|
|
|
|
|
+9. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+10. ✅ 整体验证:所有7个模块的集成测试
|
|
|
|
|
+
|
|
|
|
|
+## Tasks / Subtasks
|
|
|
|
|
+- [ ] 创建`allin-packages/salary-module`目录结构 (AC: 1)
|
|
|
|
|
+ - [ ] 创建`allin-packages/salary-module/`目录
|
|
|
|
|
+ - [ ] 创建`package.json`文件,配置包名`@d8d/allin-salary-module`和workspace依赖
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/package.json`
|
|
|
|
|
+ - **修改点**: 包名改为`@d8d/allin-salary-module`,添加对`@d8d/geo-areas`的依赖
|
|
|
|
|
+ - **关键依赖**: `@d8d/geo-areas`, `@d8d/core-module`, `@d8d/shared-crud`, `@d8d/shared-utils`
|
|
|
|
|
+ - **注意吸取经验**: 根据故事007.006的经验,需要在`pnpm-workspace.yaml`中添加`allin-packages/*`配置
|
|
|
|
|
+ - [ ] 创建`tsconfig.json`文件,配置TypeScript编译选项
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/tsconfig.json`
|
|
|
|
|
+ - [ ] 创建`vitest.config.ts`文件,配置测试环境
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/vitest.config.ts`
|
|
|
|
|
+ - [ ] 创建`src/`目录结构:`entities/`, `services/`, `routes/`, `schemas/`, `types/`
|
|
|
|
|
+ - **参考结构**: `allin-packages/platform-module/src/`目录结构
|
|
|
|
|
+- [ ] 完成实体转换:`SalaryLevel`实体转换,添加区域ID字段 (AC: 2)
|
|
|
|
|
+ - [ ] 分析源实体`allin_system-master/server/src/salary/salary.entity.ts`
|
|
|
|
|
+ - **源文件**: `allin_system-master/server/src/salary/salary.entity.ts`
|
|
|
|
|
+ - **关键字段**: `salary_id`, `province`, `city`, `district`, `basic_salary`, `allowance`, `insurance`, `housing_fund`, `total_salary`, `update_time`
|
|
|
|
|
+ - **注意吸取经验**: 根据故事007.006的经验,主键属性名应直接定义为`id`(而不是`salaryId`)以遵循GenericCrudService约定
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/entities/salary-level.entity.ts`
|
|
|
|
|
+ - [ ] 创建转换后的实体文件`src/entities/salary-level.entity.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/entities/platform.entity.ts`
|
|
|
|
|
+ - **转换要点**: 下划线命名 → 驼峰命名,添加详细TypeORM配置
|
|
|
|
|
+ - **将下划线字段名转换为驼峰命名**: `salary_id` → `id`, `basic_salary` → `basicSalary`, `housing_fund` → `housingFund`等
|
|
|
|
|
+ - **添加详细的TypeORM装饰器配置**: `@Column({ name: 'basic_salary', type: 'decimal', precision: 10, scale: 2 })`
|
|
|
|
|
+ - **保持数据库表名不变**: `@Entity('salary_level')`
|
|
|
|
|
+ - **区域包集成**: 添加`provinceId`, `cityId`, `districtId`字段,引用`AreaEntity`
|
|
|
|
|
+ - **参考文件**: `packages/geo-areas/src/modules/areas/area.entity.ts`
|
|
|
|
|
+ - **外键关系**: `@ManyToOne(() => AreaEntity)`,`@JoinColumn({ name: 'province_id' })`
|
|
|
|
|
+ - **移除原字符串字段**: `province`, `city`, `district`
|
|
|
|
|
+ - **唯一性约束**: 修改为`@Unique(['provinceId', 'cityId'])`
|
|
|
|
|
+- [ ] **区域包集成**:使用`@d8d/geo-areas`包管理区域数据 (AC: 3)
|
|
|
|
|
+ - [ ] 分析`@d8d/geo-areas`包结构和API
|
|
|
|
|
+ - **参考文件**: `packages/geo-areas/src/modules/areas/area.service.ts`
|
|
|
|
|
+ - **关键方法**: `findById`, `findByCode`, `findByParentCode`, `validateAreaHierarchy`
|
|
|
|
|
+ - **区域验证**: 验证省→市→区的层级关系
|
|
|
|
|
+ - [ ] 在服务层集成区域验证逻辑
|
|
|
|
|
+ - **验证逻辑**: 创建薪资记录时验证`provinceId`, `cityId`, `districtId`的有效性和层级关系
|
|
|
|
|
+ - **错误处理**: 区域不存在或层级关系错误时返回相应错误信息
|
|
|
|
|
+ - [ ] 在API响应中返回区域完整信息
|
|
|
|
|
+ - **响应格式**: 包含区域ID和区域名称的完整信息
|
|
|
|
|
+ - **数据转换**: 使用区域服务获取区域名称,构建完整响应
|
|
|
|
|
+- [ ] 完成服务层转换:薪资业务逻辑,包含区域数据验证 (AC: 4)
|
|
|
|
|
+ - [ ] 分析源服务`allin_system-master/server/src/salary/salary.service.ts`
|
|
|
|
|
+ - **源文件**: `allin_system-master/server/src/salary/salary.service.ts`
|
|
|
|
|
+ - **关键方法**: `createSalary`, `findAllSalaries`, `findSalaryById`, `findSalaryByProvinceCity`, `updateSalary`, `deleteSalary`
|
|
|
|
|
+ - **业务逻辑**: 省份城市唯一性检查,总薪资计算,分页查询
|
|
|
|
|
+ - [ ] 创建转换后的服务文件`src/services/salary.service.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/services/platform.service.ts`
|
|
|
|
|
+ - **架构**: 继承`GenericCrudService<SalaryLevel>`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/services/salary.service.ts`
|
|
|
|
|
+ - [ ] 继承`GenericCrudService<SalaryLevel>`,配置搜索字段
|
|
|
|
|
+ - **参考**: `packages/shared-crud/src/services/generic-crud.service.ts`
|
|
|
|
|
+ - **搜索字段**: 通过区域关联查询
|
|
|
|
|
+ - [ ] 覆盖`create`方法:添加区域验证和唯一性检查
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:14-39` - 检查`(province, city)`是否已存在
|
|
|
|
|
+ - **新逻辑**: 检查`(provinceId, cityId)`是否已存在,验证区域层级关系
|
|
|
|
|
+ - **总薪资计算**: `basicSalary + allowance + insurance + housingFund`
|
|
|
|
|
+ - [ ] 覆盖`update`方法:检查薪资存在性和区域验证
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:91-107` - 检查薪资是否存在,重新计算总薪资
|
|
|
|
|
+ - **新逻辑**: 检查薪资是否存在,验证更新后的区域数据
|
|
|
|
|
+ - [ ] 覆盖`findAll`方法:需要返回`{ data: SalaryLevel[], total: number }`格式
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:41-71` - 支持按省份、城市、区县查询
|
|
|
|
|
+ - **新逻辑**: 支持按区域ID查询,关联区域实体
|
|
|
|
|
+ - [ ] 自定义`findByProvinceCity`方法:按省份城市查询薪资
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:82-89` - 根据省份和城市字符串查询
|
|
|
|
|
+ - **新逻辑**: 根据`provinceId`和`cityId`查询,支持区域ID参数
|
|
|
|
|
+ - [ ] 集成区域服务验证逻辑
|
|
|
|
|
+ - **区域验证**: 使用`AreaService`验证区域ID的有效性
|
|
|
|
|
+ - **层级验证**: 验证`provinceId`→`cityId`→`districtId`的层级关系
|
|
|
|
|
+ - **错误处理**: 区域不存在或层级错误时抛出相应异常
|
|
|
|
|
+- [ ] 完成路由层转换:Hono路由实现,支持区域ID参数 (AC: 5)
|
|
|
|
|
+ - [ ] 分析源控制器`allin_system-master/server/src/salary/salary.controller.ts`
|
|
|
|
|
+ - **源文件**: `allin_system-master/server/src/salary/salary.controller.ts`
|
|
|
|
|
+ - **API端点**:
|
|
|
|
|
+ - `POST /salary/create` - 创建薪资水平
|
|
|
|
|
+ - `GET /salary/list` - 获取所有薪资水平(分页+条件查询)
|
|
|
|
|
+ - `GET /salary/detail/:id` - 根据ID获取薪资水平
|
|
|
|
|
+ - `GET /salary/byProvinceCity` - 根据省份和城市获取薪资水平
|
|
|
|
|
+ - `PUT /salary/update/:id` - 更新薪资水平
|
|
|
|
|
+ - `DELETE /salary/delete/:id` - 删除薪资水平
|
|
|
|
|
+ - **认证**: 所有端点需要JWT认证 (`@UseGuards(JwtAuthGuard)`)
|
|
|
|
|
+ - [ ] 创建自定义路由文件`src/routes/salary-custom.routes.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/routes/salary-custom.routes.ts`
|
|
|
|
|
+ - [ ] 自定义`POST /create`路由:处理区域ID参数和总薪资计算
|
|
|
|
|
+ - **参数**: 接收`provinceId`, `cityId`, `districtId`等区域ID参数
|
|
|
|
|
+ - **返回格式**: 成功返回`SalaryLevel`对象,包含区域完整信息
|
|
|
|
|
+ - **源逻辑**: `salary.controller.ts:12-16`,`salary.service.ts:14-39`
|
|
|
|
|
+ - **参考模式**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`中的`createPlatformRoute`
|
|
|
|
|
+ - [ ] 自定义`GET /list`路由:处理分页参数和区域查询
|
|
|
|
|
+ - **参数**: `skip`, `take`查询参数,支持`provinceId`, `cityId`, `districtId`过滤
|
|
|
|
|
+ - **返回格式**: `{ data: SalaryLevel[], total: number }`,包含区域关联数据
|
|
|
|
|
+ - **源逻辑**: `salary.controller.ts:18-26`,`salary.service.ts:41-71`
|
|
|
|
|
+ - **参考模式**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`中的`getAllPlatformsRoute`
|
|
|
|
|
+ - [ ] 自定义`GET /detail/:id`路由:处理单个薪资查询
|
|
|
|
|
+ - **参数**: `id`路径参数
|
|
|
|
|
+ - **返回格式**: `SalaryLevel`对象,包含区域关联信息
|
|
|
|
|
+ - **源逻辑**: `salary.controller.ts:28-32`,`salary.service.ts:73-80`
|
|
|
|
|
+ - **参考模式**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`中的参数验证和错误处理
|
|
|
|
|
+ - [ ] 自定义`GET /byProvinceCity`路由:按省份城市查询
|
|
|
|
|
+ - **参数**: `provinceId`, `cityId`查询参数
|
|
|
|
|
+ - **返回格式**: `SalaryLevel`对象,包含区域关联信息
|
|
|
|
|
+ - **源逻辑**: `salary.controller.ts:34-38`,`salary.service.ts:82-89`
|
|
|
|
|
+ - **参考模式**: 自定义查询路由,参考`allin-packages/platform-module/src/routes/platform-custom.routes.ts`中的搜索路由
|
|
|
|
|
+ - [ ] 自定义`PUT /update/:id`路由:处理更新操作
|
|
|
|
|
+ - **参数**: `id`路径参数,更新数据体
|
|
|
|
|
+ - **返回格式**: 更新后的`SalaryLevel`对象
|
|
|
|
|
+ - **源逻辑**: `salary.controller.ts:40-47`,`salary.service.ts:91-107`
|
|
|
|
|
+ - **参考模式**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`中的`updatePlatformRoute`
|
|
|
|
|
+ - [ ] 自定义`DELETE /delete/:id`路由:处理删除操作
|
|
|
|
|
+ - **参数**: `id`路径参数
|
|
|
|
|
+ - **返回格式**: 成功返回`{ success: true }`
|
|
|
|
|
+ - **源逻辑**: `salary.controller.ts:49-53`,`salary.service.ts:109-116`
|
|
|
|
|
+ - **参考模式**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`中的`deletePlatformRoute`
|
|
|
|
|
+ - [ ] 创建CRUD路由文件`src/routes/salary-crud.routes.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/routes/platform-crud.routes.ts`
|
|
|
|
|
+ - **架构**: 使用`createCrudRoutes`生成标准CRUD路由
|
|
|
|
|
+ - **配置**: 配置`entity`, `createSchema`, `updateSchema`, `getSchema`, `listSchema`, `searchFields`等参数
|
|
|
|
|
+ - **注意**: 设置`readOnly: true`,因为创建、更新、删除操作通过自定义路由处理
|
|
|
|
|
+ - [ ] 创建主路由文件`src/routes/salary.routes.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/routes/platform.routes.ts`
|
|
|
|
|
+ - **功能**: 聚合自定义路由和CRUD路由,导出路由实例
|
|
|
|
|
+- [ ] 完成验证系统转换:Zod Schema定义,包含区域ID验证 (AC: 6)
|
|
|
|
|
+ - [ ] 分析源DTO`allin_system-master/server/src/salary/salary.dto.ts`
|
|
|
|
|
+ - **源文件**: `allin_system-master/server/src/salary/salary.dto.ts`
|
|
|
|
|
+ - **DTO类型**: `CreateSalaryDto`, `UpdateSalaryDto`, `QuerySalaryDto`, `GetSalaryByProvinceCityDto`
|
|
|
|
|
+ - [ ] 创建转换后的Schema文件`src/schemas/salary.schema.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/schemas/platform.schema.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/schemas/salary.schema.ts`
|
|
|
|
|
+ - [ ] 使用`z.object()`定义`CreateSalarySchema`, `UpdateSalarySchema`, `QuerySalarySchema`, `GetSalaryByProvinceCitySchema`
|
|
|
|
|
+ - [ ] 添加详细的验证规则:数值范围、必填字段、可选字段、区域ID验证
|
|
|
|
|
+ - **区域ID验证**: `provinceId`, `cityId`为必填正整数,`districtId`为可选正整数
|
|
|
|
|
+ - **数值验证**: `basicSalary`, `allowance`, `insurance`, `housingFund`必须为≥0的数字,保留2位小数
|
|
|
|
|
+ - [ ] 创建对应的TypeScript类型定义:`CreateSalaryDto`, `UpdateSalaryDto`, `QuerySalaryDto`, `GetSalaryByProvinceCityDto`
|
|
|
|
|
+- [ ] 配置package.json:依赖管理,包含对`@d8d/geo-areas`的依赖 (AC: 7)
|
|
|
|
|
+ - [ ] 配置`package.json`中的`name`字段为`@d8d/allin-salary-module`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/package.json`
|
|
|
|
|
+ - [ ] 设置`type: "module"`和主入口`src/index.ts`
|
|
|
|
|
+ - [ ] 添加workspace依赖:`@d8d/core-module`, `@d8d/shared-crud`, `@d8d/shared-utils`, `@d8d/geo-areas`
|
|
|
|
|
+ - [ ] 添加外部依赖:`@hono/zod-openapi`, `typeorm`, `zod`
|
|
|
|
|
+ - [ ] 配置导出路径:`services`, `schemas`, `routes`, `entities`
|
|
|
|
|
+- [ ] 编写API集成测试:验证薪资管理功能,包含区域数据测试 (AC: 8)
|
|
|
|
|
+ - [ ] 创建测试文件`tests/integration/salary.integration.test.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/tests/integration/platform.integration.test.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/tests/integration/salary.integration.test.ts`
|
|
|
|
|
+ - [ ] 参考`platform-module`的集成测试模式
|
|
|
|
|
+ - **测试模式**: 使用`testClient`, `setupIntegrationDatabaseHooksWithEntities`
|
|
|
|
|
+ - [ ] 使用`testClient`创建测试客户端
|
|
|
|
|
+ - [ ] 使用`setupIntegrationDatabaseHooksWithEntities`设置测试数据库
|
|
|
|
|
+ - **工具**: `@d8d/shared-test-util`中的测试基础设施
|
|
|
|
|
+ - **实体**: 包含`SalaryLevel`和`AreaEntity`
|
|
|
|
|
+ - [ ] 编写测试用例覆盖所有端点:创建、查询、更新、删除、按区域查询
|
|
|
|
|
+ - [ ] **区域测试重点**:
|
|
|
|
|
+ - 验证区域ID的有效性检查
|
|
|
|
|
+ - 测试区域层级关系验证(省→市→区)
|
|
|
|
|
+ - 验证区域不存在时的错误处理
|
|
|
|
|
+ - 测试区域唯一性约束`(provinceId, cityId)`
|
|
|
|
|
+ - 验证区域数据在响应中的完整性
|
|
|
|
|
+ - [ ] 添加认证测试、数据验证测试、错误处理测试
|
|
|
|
|
+ - [ ] 包含边界条件和异常场景测试
|
|
|
|
|
+ - [ ] 特别测试薪资计算逻辑和区域唯一性检查功能
|
|
|
|
|
+- [ ] 通过类型检查和基本测试验证 (AC: 9)
|
|
|
|
|
+ - [ ] 运行`pnpm typecheck`确保无类型错误
|
|
|
|
|
+ - [ ] 运行`pnpm test`确保所有测试通过
|
|
|
|
|
+ - [ ] 运行`pnpm test:integration`验证集成测试
|
|
|
|
|
+ - [ ] 检查测试覆盖率是否满足要求
|
|
|
|
|
+ - **标准**: 集成测试 ≥ 60% [Source: architecture/testing-strategy.md#测试覆盖率标准]
|
|
|
|
|
+ - [ ] 验证模块可以正确导入和使用
|
|
|
|
|
+- [ ] 整体验证:所有7个模块的集成测试 (AC: 10)
|
|
|
|
|
+ - [ ] 验证薪资模块与其他Allin模块的兼容性
|
|
|
|
|
+ - [ ] 测试跨模块数据一致性
|
|
|
|
|
+ - [ ] 验证整体系统功能完整性
|
|
|
|
|
+
|
|
|
|
|
+## Dev Notes
|
|
|
|
|
+
|
|
|
|
|
+### 先前故事洞察
|
|
|
|
|
+- **故事007.006**:已完成平台管理模块移植,提供了完整的参考实现 [Source: docs/stories/007.006.transplant-platform-management-module.story.md]
|
|
|
|
|
+- **关键经验教训**:
|
|
|
|
|
+ 1. **实体主键命名**:根据用户反馈,主键属性名应直接定义为`id`(而不是`platformId`)以遵循GenericCrudService约定
|
|
|
|
|
+ 2. **workspace配置**:需要在`pnpm-workspace.yaml`中添加`allin-packages/*`配置
|
|
|
|
|
+ 3. **测试依赖**:需要添加`@d8d/user-module`和`@d8d/file-module`依赖
|
|
|
|
|
+ 4. **类型检查**:需要注意导出重复和路由返回类型问题
|
|
|
|
|
+ 5. **API兼容性**:需要保持原始API的返回格式
|
|
|
|
|
+
|
|
|
|
|
+### 数据模型
|
|
|
|
|
+- **源实体结构**:`SalaryLevel`实体包含以下字段 [Source: allin_system-master/server/src/salary/salary.entity.ts]:
|
|
|
|
|
+ - `salary_id: number` (主键,自增)
|
|
|
|
|
+ - `province: string` (省份)
|
|
|
|
|
+ - `city: string` (城市)
|
|
|
|
|
+ - `district?: string` (区县,可选)
|
|
|
|
|
+ - `basic_salary: number` (基本工资,decimal(10,2))
|
|
|
|
|
+ - `allowance: number` (津贴补贴,decimal(10,2),默认0.00)
|
|
|
|
|
+ - `insurance: number` (保险费用,decimal(10,2),默认0.00)
|
|
|
|
|
+ - `housing_fund: number` (住房公积金,decimal(10,2),默认0.00)
|
|
|
|
|
+ - `total_salary: number` (总薪资,decimal(10,2))
|
|
|
|
|
+ - `update_time: Date` (更新时间)
|
|
|
|
|
+- **唯一性约束**: `@Unique(['province', 'city'])`
|
|
|
|
|
+- **转换要求**:
|
|
|
|
|
+ - 下划线命名 → 驼峰命名
|
|
|
|
|
+ - 添加详细TypeORM配置
|
|
|
|
|
+ - **区域包集成**:移除`province`, `city`, `district`字符串字段,添加`provinceId`, `cityId`, `districtId`引用`AreaEntity`
|
|
|
|
|
+ - **唯一性约束修改**: `@Unique(['provinceId', 'cityId'])`
|
|
|
|
|
+- **表名保持**:`salary_level`表名不变
|
|
|
|
|
+
|
|
|
|
|
+### 区域包集成方案
|
|
|
|
|
+- **采用方案**:完全依赖区域包,将所有区域数据改为引用`@d8d/geo-areas`包中的`AreaEntity` [Source: docs/prd/epic-007-allin-system-transplant.md#设计决策:区域数据统一使用@d8d/geo-areas包]
|
|
|
|
|
+- **改造方案**:
|
|
|
|
|
+ 1. **移除字符串字段**:`province`, `city`, `district`
|
|
|
|
|
+ 2. **添加外键字段**:`provinceId`, `cityId`, `districtId`(外键引用`AreaEntity`)
|
|
|
|
|
+ 3. **修改唯一性约束**:`(provinceId, cityId)`
|
|
|
|
|
+ 4. **使用区域服务**:`AreaService`验证区域数据的有效性和层级关系
|
|
|
|
|
+- **优势**:
|
|
|
|
|
+ - ✅ **数据一致性**:统一使用区域包数据,避免数据不一致
|
|
|
|
|
+ - ✅ **类型安全**:外键引用提供编译时检查
|
|
|
|
|
+ - ✅ **维护简单**:区域变更只需更新区域包数据
|
|
|
|
|
+ - ✅ **功能完整**:利用区域包的树形结构、层级验证等功能
|
|
|
|
|
+ - ✅ **性能优化**:减少重复数据存储,提高查询效率
|
|
|
|
|
+
|
|
|
|
|
+### 自定义实现分析
|
|
|
|
|
+- **需要覆盖GenericCrudService的方法**:
|
|
|
|
|
+ - **`create`方法**:需要添加区域验证和`(provinceId, cityId)`唯一性检查
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:14-39` - 检查`(province, city)`是否已存在
|
|
|
|
|
+ - **新逻辑**: 检查`(provinceId, cityId)`是否已存在,验证区域层级关系
|
|
|
|
|
+ - **总薪资计算**: `basicSalary + allowance + insurance + housingFund`
|
|
|
|
|
+ - **`update`方法**:需要检查薪资存在性和区域验证
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:91-107` - 检查薪资是否存在,重新计算总薪资
|
|
|
|
|
+ - **新逻辑**: 检查薪资是否存在,验证更新后的区域数据
|
|
|
|
|
+ - **`findAll`方法**:需要返回`{ data: SalaryLevel[], total: number }`格式
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:41-71` - 支持按省份、城市、区县查询
|
|
|
|
|
+ - **新逻辑**: 支持按区域ID查询,关联区域实体
|
|
|
|
|
+ - **自定义`findByProvinceCity`方法**:按省份城市查询薪资
|
|
|
|
|
+ - **源逻辑**: `salary.service.ts:82-89` - 根据省份和城市字符串查询
|
|
|
|
|
+ - **新逻辑**: 根据`provinceId`和`cityId`查询,支持区域ID参数
|
|
|
|
|
+
|
|
|
|
|
+- **与GenericCrudService的差异**:
|
|
|
|
|
+ - **区域集成**:需要集成`AreaService`进行区域验证
|
|
|
|
|
+ - **唯一性约束**:基于`(provinceId, cityId)`而非单个字段
|
|
|
|
|
+ - **计算字段**:`totalSalary`为计算字段,需要自动计算
|
|
|
|
|
+ - **关联查询**:需要关联`AreaEntity`获取区域完整信息
|
|
|
|
|
+
|
|
|
|
|
+### API规范
|
|
|
|
|
+- **现有API端点** [Source: allin_system-master/server/src/salary/salary.controller.ts]:
|
|
|
|
|
+ - `POST /salary/create` - 创建薪资水平
|
|
|
|
|
+ - `GET /salary/list` - 获取所有薪资水平(分页+条件查询)
|
|
|
|
|
+ - `GET /salary/detail/:id` - 根据ID获取薪资水平
|
|
|
|
|
+ - `GET /salary/byProvinceCity` - 根据省份和城市获取薪资水平
|
|
|
|
|
+ - `PUT /salary/update/:id` - 更新薪资水平
|
|
|
|
|
+ - `DELETE /salary/delete/:id` - 删除薪资水平
|
|
|
|
|
+- **认证要求**:所有端点需要JWT认证 (`@UseGuards(JwtAuthGuard)`)
|
|
|
|
|
+- **转换策略**:NestJS控制器 → Hono路由,保持相同的端点路径和功能
|
|
|
|
|
+- **参数变更**:区域参数从字符串改为ID,需要兼容性处理
|
|
|
|
|
+
|
|
|
|
|
+### 服务规范
|
|
|
|
|
+- **源服务逻辑** [Source: allin_system-master/server/src/salary/salary.service.ts]:
|
|
|
|
|
+ - `createSalary`: 检查`(province, city)`重复,计算总薪资,插入数据
|
|
|
|
|
+ - `findAllSalaries`: 分页查询,支持按省份、城市、区县过滤
|
|
|
|
|
+ - `findSalaryById`: 根据ID查询单个
|
|
|
|
|
+ - `findSalaryByProvinceCity`: 根据省份和城市查询
|
|
|
|
|
+ - `updateSalary`: 检查存在性,重新计算总薪资,更新数据
|
|
|
|
|
+ - `deleteSalary`: 根据ID删除
|
|
|
|
|
+- **转换策略**:继承`GenericCrudService`,复用现有CRUD模式,集成区域服务,保持业务逻辑
|
|
|
|
|
+
|
|
|
|
|
+### 验证系统
|
|
|
|
|
+- **源DTO结构** [Source: allin_system-master/server/src/salary/salary.dto.ts]:
|
|
|
|
|
+ - `CreateSalaryDto`: `province`(必填), `city`(必填), `district`(可选), `basicSalary`(必填), `allowance`(可选), `insurance`(可选), `housingFund`(可选)
|
|
|
|
|
+ - `UpdateSalaryDto`: `district`(可选), `basicSalary`(可选), `allowance`(可选), `insurance`(可选), `housingFund`(可选)
|
|
|
|
|
+ - `QuerySalaryDto`: `province`(可选), `city`(可选), `district`(可选)
|
|
|
|
|
+ - `GetSalaryByProvinceCityDto`: `province`(必填), `city`(必填)
|
|
|
|
|
+- **转换策略**:`class-validator` → `Zod Schema`,区域字段从字符串改为ID,添加详细的验证规则
|
|
|
|
|
+
|
|
|
|
|
+### 文件位置
|
|
|
|
|
+- **包目录**:`allin-packages/salary-module/` (根据史诗007的目录结构决策)
|
|
|
|
|
+- **源文件位置**:
|
|
|
|
|
+ - **实体源文件**: `allin_system-master/server/src/salary/salary.entity.ts`
|
|
|
|
|
+ - **服务源文件**: `allin_system-master/server/src/salary/salary.service.ts`
|
|
|
|
|
+ - **控制器源文件**: `allin_system-master/server/src/salary/salary.controller.ts`
|
|
|
|
|
+ - **DTO源文件**: `allin_system-master/server/src/salary/salary.dto.ts`
|
|
|
|
|
+- **目标文件位置**:
|
|
|
|
|
+ - **实体文件**: `src/entities/salary-level.entity.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/entities/platform.entity.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/entities/salary-level.entity.ts`
|
|
|
|
|
+ - **服务文件**: `src/services/salary.service.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/services/platform.service.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/services/salary.service.ts`
|
|
|
|
|
+ - **主路由文件**: `src/routes/salary.routes.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/routes/platform.routes.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/routes/salary.routes.ts`
|
|
|
|
|
+ - **自定义路由文件**: `src/routes/salary-custom.routes.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/routes/salary-custom.routes.ts`
|
|
|
|
|
+ - **CRUD路由文件**: `src/routes/salary-crud.routes.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/routes/platform-crud.routes.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/routes/salary-crud.routes.ts`
|
|
|
|
|
+ - **Schema文件**: `src/schemas/salary.schema.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/src/schemas/platform.schema.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/src/schemas/salary.schema.ts`
|
|
|
|
|
+ - **测试文件**: `tests/integration/salary.integration.test.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/tests/integration/platform.integration.test.ts`
|
|
|
|
|
+ - **迁移文件路径**: `allin-packages/salary-module/tests/integration/salary.integration.test.ts`
|
|
|
|
|
+ - **包配置**: `package.json`, `tsconfig.json`, `vitest.config.ts`
|
|
|
|
|
+ - **参考文件**: `allin-packages/platform-module/`中的对应配置文件
|
|
|
|
|
+
|
|
|
|
|
+### 测试要求
|
|
|
|
|
+- **测试框架**:Vitest [Source: architecture/testing-strategy.md#单元测试]
|
|
|
|
|
+- **测试位置**:`tests/integration/`目录 [Source: architecture/testing-strategy.md#测试金字塔策略]
|
|
|
|
|
+- **测试类型**:集成测试,验证API端点和数据库交互 [Source: architecture/testing-strategy.md#集成测试]
|
|
|
|
|
+- **覆盖率目标**:集成测试 ≥ 60% [Source: architecture/testing-strategy.md#测试覆盖率标准]
|
|
|
|
|
+- **测试模式**:参考`platform-module`的集成测试模式 [Source: allin-packages/platform-module/tests/integration/platform.integration.test.ts]
|
|
|
|
|
+- **测试工具**:使用`@d8d/shared-test-util`中的测试基础设施 [Source: architecture/testing-strategy.md#包测试架构]
|
|
|
|
|
+- **区域测试重点**:
|
|
|
|
|
+ - 验证区域ID的有效性检查
|
|
|
|
|
+ - 测试区域层级关系验证(省→市→区)
|
|
|
|
|
+ - 验证区域不存在时的错误处理
|
|
|
|
|
+ - 测试区域唯一性约束`(provinceId, cityId)`
|
|
|
|
|
+ - 验证区域数据在响应中的完整性
|
|
|
|
|
+
|
|
|
|
|
+### 技术约束
|
|
|
|
|
+- **技术栈**:Node.js 20.19.2, Hono 4.8.5, TypeORM 0.3.25, PostgreSQL 17 [Source: architecture/tech-stack.md]
|
|
|
|
|
+- **编码标准**:TypeScript严格模式,一致的缩进和命名 [Source: architecture/coding-standards.md]
|
|
|
|
|
+- **API设计**:RESTful API设计,使用Hono框架 [Source: architecture/source-tree.md#API设计]
|
|
|
|
|
+- **包管理**:使用pnpm workspace管理依赖 [Source: architecture/source-tree.md#集成指南]
|
|
|
|
|
+- **模块导出**:通过`src/index.ts`统一导出 [Source: architecture/source-tree.md#包结构规范]
|
|
|
|
|
+- **区域包依赖**:必须集成`@d8d/geo-areas`包进行区域数据管理
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构注意事项
|
|
|
|
|
+- **目录结构冲突**:根据史诗007决策,Allin系统专属包放在`allin-packages/`目录,而非通用的`packages/`目录
|
|
|
|
|
+- **命名规范**:使用`@d8d/allin-`前缀,`-module`后缀,非多租户版本
|
|
|
|
|
+- **依赖管理**:通过workspace依赖引用`@d8d/core-module`、`@d8d/shared-crud`、`@d8d/shared-utils`、`@d8d/geo-areas`
|
|
|
|
|
+- **区域包集成**:必须正确配置对`@d8d/geo-areas`的依赖,确保区域验证功能正常工作
|
|
|
|
|
+
|
|
|
|
|
+### 测试标准
|
|
|
|
|
+1. **API端点测试**:必须覆盖所有6个端点(创建、查询列表、查询详情、按区域查询、更新、删除)
|
|
|
|
|
+2. **认证测试**:验证所有端点需要有效的JWT令牌
|
|
|
|
|
+3. **数据验证测试**:测试输入验证规则(区域ID验证、数值范围、必填字段)
|
|
|
|
|
+4. **业务逻辑测试**:测试区域唯一性检查、总薪资计算、区域层级验证
|
|
|
|
|
+5. **错误处理测试**:测试各种错误场景(无效区域ID、重复区域、缺失字段、无效数值)
|
|
|
|
|
+6. **数据库集成测试**:验证数据正确持久化和查询,区域外键关系
|
|
|
|
|
+7. **区域集成测试**:验证与`@d8d/geo-areas`包的集成,区域数据验证和查询
|
|
|
|
|
+8. **测试数据管理**:使用测试数据工厂模式,每个测试后清理数据
|
|
|
|
|
+
|
|
|
|
|
+### 测试执行流程
|
|
|
|
|
+1. 设置测试数据库,包含`SalaryLevel`和`AreaEntity`实体
|
|
|
|
|
+2. 创建测试区域数据(省、市、区)
|
|
|
|
|
+3. 创建测试用户和认证令牌
|
|
|
|
|
+4. 为每个端点编写成功场景测试
|
|
|
|
|
+5. 为每个端点编写失败场景测试
|
|
|
|
|
+6. 验证响应格式和数据正确性,包含区域完整信息
|
|
|
|
|
+7. 检查数据库状态变化和外键关系
|
|
|
|
|
+
|
|
|
|
|
+## Change Log
|
|
|
|
|
+| Date | Version | Description | Author |
|
|
|
|
|
+|------|---------|-------------|--------|
|
|
|
|
|
+| 2025-12-02 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
|
|
|
|
|
+
|
|
|
|
|
+## Dev Agent Record
|
|
|
|
|
+*此部分由开发代理在实现过程中填写*
|
|
|
|
|
+
|
|
|
|
|
+### Agent Model Used
|
|
|
|
|
+
|
|
|
|
|
+### Debug Log References
|
|
|
|
|
+
|
|
|
|
|
+### Completion Notes List
|
|
|
|
|
+
|
|
|
|
|
+### File List
|
|
|
|
|
+
|
|
|
|
|
+## QA Results
|