13-25-jobstatus-distribution-refactor.md 10 KB

Story 13.25: 重构 getJobStatusDistribution 使用 orderPerson.workStatus

Status: review

元数据

  • Epic: Epic 13 - 跨端数据同步测试
  • 状态: ready-for-dev
  • 优先级: P1 (数据准确性问题)
  • 故事点: 3
  • 依赖: Story 13.24 完成

用户故事

作为企业管理员, 我在企业小程序的数据统计页查看"在职状态分布"图时, 我希望看到基于订单人员实际工作状态的准确分布, 以便了解企业真实的在职/待入职/离职人员构成。

问题背景

当前问题: getJobStatusDistribution 方法使用 disabled_person.jobStatus 字段(数字类型:0=未在职,1=已在职)进行统计,这是一个二元状态的旧字段,无法反映实际的工作状态多样性。

根本原因: 该方法展示的是旧字段 jobStatus 的分布,而不是实际使用的 order_person.work_status 枚举字段('working', 'pending', 'departed' 等)。

影响:

  1. 在职状态分布图只显示"未在职/已在职"两种状态,不够详细
  2. 无法看到"待入职"、"离职"等中间状态的人员分布
  3. 与实际使用的 work_status 字段不一致

技术细节:

  • disabled_person.jobStatus: 数字类型,0 或 1,二元状态
  • order_person.work_status: 字符串枚举,包括 'working', 'pending', 'departed' 等
  • 系统实际使用 work_status 来管理人员的各种工作状态

验收标准

AC 1: 在职状态分布基于 orderPerson.workStatus

Given 企业小程序数据统计页 When 查看在职状态分布图 Then 应显示基于 order_person.work_status 的分布 And 包括 'working', 'pending', 'departed' 等所有状态

AC 2: 状态映射显示友好名称

Given 在职状态分布图 When 显示各状态 Then 应显示友好的中文名称(如"在职"、"待入职"、"已离职"等) And 不显示原始的枚举值

AC 3: 分布总数与首页一致

Given 在职状态分布图 When 查看分布总数 Then 总数应与首页仪表板在职人数一致 And 各状态人数之和应等于总人数

AC 4: API 接口保持兼容

Given 前端调用 /statistics/job-status-distribution API When 获取数据 Then 返回的数据结构应保持不变 And 只改变状态的内容和数量

AC 5: E2E 测试通过

Given 修改完成后 When 运行数据统计页 E2E 测试 Then 在职状态分布相关测试应该通过

任务

任务 0: 分析现有代码和重构方案

  • 分析 getJobStatusDistribution 方法的当前实现
  • 确认 work_status 枚举的所有可能值
  • 设计状态映射到友好名称的方案
  • 评估前端是否需要调整

任务 1: 重构 getJobStatusDistribution 方法

  • 修改查询从 disabledPersonRepository 改为 orderPersonRepository
  • 基于 op.workStatus 进行分组统计
  • 添加 workStatus 枚举值到友好名称的映射
  • 保持 companyId 过滤逻辑不变
  • 验证返回结果正确

任务 2: 状态映射定义

  • 定义 workStatus 枚举值到中文的映射
  • 确认所有可能的 workStatus 值
  • 处理未知状态的显示

任务 3: 前端适配(如需要)

  • 检查前端是否需要调整以支持更多状态
  • 更新图表显示逻辑(如需要)
  • 确保图表能够正确显示多状态分布

任务 4: 单元测试验证

  • 编写或更新单元测试验证 getJobStatusDistribution 的正确性
  • 测试应验证使用正确的 workStatus 分组
  • 测试应包含不同 work_status 的场景
  • 确保单元测试通过

任务 5: E2E 测试验证

  • 使用 Playwright MCP 验证在职状态分布图
  • 验证各状态显示正确的友好名称
  • 运行数据统计页 E2E 测试套件
  • 验证所有测试通过

Dev Notes

相关文件

  • 主要修改文件: allin-packages/statistics-module/src/services/statistics.service.ts
  • 修改方法: getJobStatusDistribution() (行 258-301)
  • 前端文件: allin-packages/mini-enterprise-module/src/pages/statistics.tsx (可能需要调整)

当前实现分析

// 当前实现(使用旧字段)
async getJobStatusDistribution(companyId: number): Promise<{
  companyId: number;
  stats: StatItem[];
  total: number;
}> {
  const personIds = await this.getCompanyDisabledPersonIds(companyId);

  if (personIds.length === 0) {
    return {
      companyId,
      stats: [],
      total: 0
    };
  }

  const query = this.disabledPersonRepository
    .createQueryBuilder('dp')
    .select('dp.jobStatus', 'key')
    .addSelect('COUNT(dp.id)', 'value')
    .where('dp.id IN (:...personIds)', { personIds })
    .andWhere('dp.jobStatus IS NOT NULL')
    .groupBy('dp.jobStatus');

  const rawStats = await query.getRawMany();
  const total = rawStats.reduce((sum, item) => sum + parseInt(item.value), 0);

  // jobStatus映射:0-未在职,1-已在职
  const jobStatusMap: Record<number, string> = {
    0: '未在职',
    1: '已在职'
  };

  const stats = rawStats.map(item => ({
    key: jobStatusMap[item.key] || `未知(${item.key})`,
    value: parseInt(item.value),
    percentage: total > 0 ? (parseInt(item.value) / total) * 100 : 0
  }));

  return {
    companyId,
    stats,
    total
  };
}

修复方案

// 修复后的实现(使用 work_status 枚举)
async getJobStatusDistribution(companyId: number): Promise<{
  companyId: number;
  stats: StatItem[];
  total: number;
}> {
  // 直接从 orderPerson 表统计,基于 workStatus 分组
  const query = this.orderPersonRepository
    .createQueryBuilder('op')
    .innerJoin('op.order', 'order')
    .select('op.workStatus', 'key')
    .addSelect('COUNT(DISTINCT op.personId)', 'value')  // 使用 DISTINCT 避免重复计数
    .where('order.companyId = :companyId', { companyId })
    .andWhere('op.workStatus IS NOT NULL')
    .groupBy('op.workStatus');

  const rawStats = await query.getRawMany();
  const total = rawStats.reduce((sum, item) => sum + parseInt(item.value), 0);

  // workStatus 枚举映射到中文
  const workStatusMap: Record<string, string> = {
    'working': '在职',
    'pending': '待入职',
    'departed': '已离职',
    // 根据实际枚举值补充其他状态
  };

  const stats = rawStats.map(item => ({
    key: workStatusMap[item.key] || item.key,  // 未知状态显示原始值
    value: parseInt(item.value),
    percentage: total > 0 ? (parseInt(item.value) / total) * 100 : 0
  }));

  return {
    companyId,
    stats,
    total
  };
}

WorkStatus 枚举定义

需要确认 WorkStatus 枚举的完整定义,通常包括:

enum WorkStatus {
  WORKING = 'working',      // 在职
  PENDING = 'pending',      // 待入职
  DEPARTED = 'departed',    // 已离职
  // 可能有其他状态
}

技术要点

  1. 查询重构:

    • disabledPersonRepository 改为 orderPersonRepository
    • 直接使用 op.workStatus 分组,无需先获取 personIds
    • 使用 COUNT(DISTINCT op.personId) 避免同一人员多订单重复计数
  2. 状态映射:

    • 需要确认 WorkStatus 枚举的所有可能值
    • 提供完整的中文映射
    • 处理未知状态的情况
  3. 数据一致性:

    • 与其他统计方法使用相同的 workStatus 字段
    • 确保分布图总数与首页一致
  4. 前端兼容:

    • API 返回的数据结构保持不变
    • 前端可能需要调整以支持更多状态显示

测试策略

  1. 单元测试:

    • 测试验证使用正确的 workStatus 分组
    • 测试包含不同 work_status 的场景
    • 测试验证状态映射正确
  2. 集成测试:

    • 验证返回数据的格式正确
    • 验证总数计算正确
  3. E2E 测试:

    • 使用 Playwright MCP 验证图表显示
    • 验证各状态显示正确的友好名称
    • 验证数据与其他统计一致

回归测试检查点

  • 在职状态分布图显示正常
  • 显示所有工作状态(在职、待入职、已离职等)
  • 状态名称显示为中文友好名称
  • 分布总数与首页仪表板一致
  • 各状态人数之和等于总人数

References

相关文档

相关代码

  • allin-packages/statistics-module/src/services/statistics.service.ts - 统计服务实现
  • allin-packages/order-module/entities/order-person.entity.ts - OrderPerson 实体(WorkStatus 枚举定义)
  • allin-packages/mini-enterprise-module/src/pages/statistics.tsx - 前端统计页面

Dev Agent Record

Agent Model Used

Claude (d8d-model)

Debug Log References

N/A

Completion Notes List

  • Story 创建于 2026-01-18
  • 依赖于 Story 13.24(统一其他统计方法)
  • 重构 getJobStatusDistribution 使用 workStatus 枚举
  • 实现完成 (2026-01-18):
    • 导入 WorkStatusLabels 用于状态映射
    • 将查询从 disabledPersonRepository 改为 orderPersonRepository
    • 基于 op.workStatus 进行分组统计
    • 使用 COUNT(DISTINCT op.personId) 避免重复计数
    • 移除旧的 jobStatus 数字映射(0-未在职,1-已在职)
    • 使用 WorkStatusLabels 映射枚举值到中文友好名称(未就业、待就业、已就业、已离职)
    • 更新集成测试(添加 Channel 实体导入)
    • 类型检查通过
    • 前端无需调整(API 返回数据结构保持不变)

File List

主要修改文件:

  • allin-packages/statistics-module/src/services/statistics.service.ts (修改 getJobStatusDistribution 方法,添加 WorkStatusLabels 导入)
  • allin-packages/statistics-module/tests/integration/statistics.integration.test.ts (更新集成测试,添加 Channel 实体和新的测试用例)

Change Log

  • 2026-01-18: 创建 Story,重构 getJobStatusDistribution 使用 orderPerson.workStatus
  • 2026-01-18: 完成代码实现,类型检查通过