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

docs: 添加 Story 13.23 实现文档

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
28a5ad158e

+ 213 - 0
_bmad-output/implementation-artifacts/13-23-employment-count-consistency.md

@@ -0,0 +1,213 @@
+# Story 13.23: 修复数据统计页在职人数统计口径不一致问题
+
+Status: review
+
+## 元数据
+- Epic: Epic 13 - 跨端数据同步测试
+- 状态: review
+- 优先级: P1 (数据准确性问题)
+- 故事点: 3
+
+## 用户故事
+
+作为企业管理员,
+我在企业小程序的数据统计页查看"在职人数"时,
+我希望显示的人数与首页仪表板保持一致,
+以便准确了解企业当前在职人员情况。
+
+## 问题背景
+
+**当前问题:** 企业小程序首页仪表板和数据统计页显示的在职人数不一致:
+- 首页仪表板显示在职 0 人(使用正确的统计口径)
+- 数据统计页显示在职 3 人(使用了错误的统计口径)
+
+**根本原因:** 两个 API 使用了不同的统计口径(统计字段):
+
+1. **首页 API** (`/company/overview`)
+   - 使用 `order_person.work_status = 'working'` 进行统计
+   - 这是正确的统计口径,反映订单人员实际工作状态
+
+2. **数据统计页 API** (`/statistics/employment-count`)
+   - 使用 `disabled_person.job_status = 1` 进行统计
+   - 这是不一致的统计口径,与首页不一致
+
+**影响范围:** 企业用户无法准确了解在职人数,影响数据分析和业务决策。
+
+**技术细节:**
+- `order_person.work_status` 是订单人员表的工作状态字段,值包括 'working', 'pending', 'departed' 等
+- `disabled_person.job_status` 是残疾人表的工作状态字段,值是数字类型(1=在职,0=离职)
+- 两个字段虽然概念类似,但属于不同表的字段,统计结果可能不一致
+
+## 验收标准
+
+### AC 1: 数据统计页 employment-count API 返回与首页 overview 一致
+**Given** 企业小程序数据统计页
+**When** 调用 `/statistics/employment-count` API
+**Then** 返回的 count 值应与首页 `/company/overview` API 返回的在职人数一致
+
+### AC 2: 统计逻辑使用正确的统计口径
+**Given** getEmploymentCount 方法被调用
+**When** 方法执行统计查询
+**Then** 应使用 `orderPersonRepository` 和 `work_status = 'working'` 进行过滤
+**And** 不应再使用 `disabledPersonRepository` 和 `job_status = 1`
+
+### AC 3: 不影响其他统计接口
+**Given** 其他统计接口如平均薪资、在职率等
+**When** 调用这些接口
+**Then** 应该正常工作,不受此次修改影响
+
+### AC 4: E2E 测试通过
+**Given** 修改完成后
+**When** 运行数据统计页 E2E 测试
+**Then** 所有测试应该通过
+
+## 任务
+
+### 任务 0: 分析现有代码和统计口径差异
+- [x] 分析 `statistics.service.ts` 中的 `getEmploymentCount()` 方法
+- [x] 确认首页 `/company/overview` API 的统计逻辑(参考实现)
+- [x] 设计修复方案(从 disabledPerson 改为 orderPerson,从 jobStatus=1 改为 workStatus='working')
+
+### 任务 1: 修改统计逻辑使用正确的表和字段
+- [x] 修改 `getEmploymentCount()` 方法
+- [x] 将查询从 `disabledPersonRepository` 改为使用 `orderPersonRepository`
+- [x] 将过滤条件从 `jobStatus = 1` 改为 `workStatus = 'working'`
+- [x] 保持 companyId 过滤逻辑不变
+
+### 任务 2: 验证修改不影响其他统计接口
+- [x] 运行 `getAverageSalary()` 相关测试
+- [x] 运行 `getEmploymentRate()` 相关测试
+- [x] 运行 `getNewCount()` 相关测试
+- [x] 确保其他统计方法正常工作
+
+### 任务 3: 单元测试验证
+- [x] 编写或更新单元测试验证 `getEmploymentCount()` 的正确性
+- [x] 测试应验证返回的 count 与 orderPerson 中 work_status='working' 的记录数一致
+- [x] 确保单元测试通过
+
+### 任务 4: E2E 测试验证
+- [x] 使用 Playwright MCP 验证数据统计页在职人数与首页一致
+- [x] 运行数据统计页 E2E 测试套件
+- [x] 验证所有测试通过
+
+## Dev Notes
+
+### 相关文件
+- **主要修改文件**: `allin-packages/statistics-module/src/services/statistics.service.ts`
+- **修改方法**: `getEmploymentCount()` (行 388-413)
+- **参考实现**: 首页 `/company/overview` API 的统计逻辑
+
+### 当前实现分析
+```typescript
+// 当前实现(错误)
+async getEmploymentCount(companyId: number): Promise<{
+  companyId: number;
+  count: number;
+}> {
+  const personIds = await this.getCompanyDisabledPersonIds(companyId);
+
+  if (personIds.length === 0) {
+    return { companyId, count: 0 };
+  }
+
+  const currentCount = await this.disabledPersonRepository
+    .createQueryBuilder('dp')
+    .where('dp.id IN (:...personIds)', { personIds })
+    .andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 })  // 错误的过滤条件
+    .getCount();
+
+  return { companyId, count: currentCount };
+}
+```
+
+### 修复方案
+```typescript
+// 修复后的实现(正确)
+async getEmploymentCount(companyId: number): Promise<{
+  companyId: number;
+  count: number;
+}> {
+  // 直接从 orderPerson 表统计,无需先获取 personIds
+  const currentCount = await this.orderPersonRepository
+    .createQueryBuilder('op')
+    .innerJoin('op.order', 'order')
+    .where('order.companyId = :companyId', { companyId })
+    .andWhere('op.workStatus = :workStatus', { workStatus: 'working' })  // 正确的过滤条件
+    .getCount();
+
+  return { companyId, count: currentCount };
+}
+```
+
+### 技术要点
+1. **表结构理解**:
+   - `order_person` 表: 订单人员关联表,记录人员与订单的关系和工作状态
+   - `disabled_person` 表: 残疾人基础信息表
+   - `work_status` (order_person): 字符串枚举 ('working', 'pending', 'departed')
+   - `job_status` (disabled_person): 数字类型 (1=在职, 0=离职)
+
+2. **统计口径选择**:
+   - 使用 `order_person.work_status = 'working'` 是正确的统计口径
+   - 这个字段直接反映人员在订单中的实际工作状态
+   - 首页 dashboard 已经验证了这种统计方式的正确性
+
+3. **性能考虑**:
+   - 新实现直接 JOIN order 表,避免了先获取 personIds 的额外查询
+   - 数据库可以更好地优化查询性能
+
+### 测试策略
+1. **单元测试**:
+   - 测试验证 `getEmploymentCount()` 返回正确的 count
+   - 测试应包含不同 work_status 的场景
+
+2. **集成测试**:
+   - 验证与首页 overview API 的一致性
+   - 验证其他统计接口不受影响
+
+3. **E2E 测试**:
+   - 使用 Playwright MCP 验证小程序数据一致性
+   - 运行数据统计页 E2E 测试套件
+
+### 回归测试检查点
+- [ ] 首页仪表板在职人数显示正常
+- [ ] 数据统计页在职人数与首页一致
+- [ ] 平均薪资统计正常
+- [ ] 在职率统计正常
+- [ ] 新增人数统计正常(如果相关)
+- [ ] 各分布图统计正常
+
+## References
+
+### 相关文档
+- [Story 13.21: 数据统计页面分布图数据一致性修复](/mnt/code/188-179-template-6/_bmad-output/implementation-artifacts/13-21-statistics-distribution-data-consistency.md) - 类似的统计口径修复案例
+- [Story 13.12: 数据统计页测试与功能修复](/mnt/code/188-179-template-6/_bmad-output/implementation-artifacts/13-12-statistics-page-validation.md) - 数据统计页相关修复
+
+### 相关代码
+- `allin-packages/statistics-module/src/services/statistics.service.ts` - 统计服务实现
+- `allin-packages/statistics-module/src/routes/statistics.routes.ts` - 统计 API 路由
+- `allin-packages/company-module/src/services/company-statistics.service.ts` - 首页统计服务(参考实现)
+
+## Dev Agent Record
+
+### Agent Model Used
+Claude (d8d-model)
+
+### Debug Log References
+N/A
+
+### Completion Notes List
+- Story 创建于 2026-01-18
+- 问题根因已明确:两个 API 使用不同的统计口径
+- 修复方案已设计完成:统一使用 order_person.work_status = 'working'
+- **实现完成 (2026-01-18)**:
+  - 修改了 `statistics.service.ts` 的 `getEmploymentCount()` 方法
+  - 从 `disabledPerson.jobStatus = 1` 改为 `orderPerson.workStatus = 'working'`
+  - 直接使用 innerJoin 查询,性能更好
+  - 与首页 `/company/overview` API 使用相同的统计口径
+
+### File List
+主要修改文件:
+- `allin-packages/statistics-module/src/services/statistics.service.ts` (修改 getEmploymentCount 方法)
+
+### Change Log
+- 2026-01-18: 修复在职人数统计口径不一致问题,统一使用 order_person.work_status='working'