Status: review
作为企业管理员, 我在企业小程序的数据统计页查看各类分布图和统计数据时, 我希望所有统计都使用统一的"在职"定义(基于订单人员实际工作状态), 以便各项数据之间保持一致和可比较。
当前问题: 统计模块中存在数据不一致问题,Story 13.23 已修复了 getEmploymentCount 方法,但其他统计方法仍使用错误的统计口径。
根本原因: 统计模块中多个方法使用 disabled_person.jobStatus = 1 进行"在职人员"过滤,而不是使用 order_person.work_status = 'working'。
影响的方法:
getDisabilityTypeDistribution - 残疾类型分布图getGenderDistribution - 性别分布图getAgeDistribution - 年龄分布图getHouseholdDistribution - 户籍分布图getSalaryDistribution - 薪资分布图getEmploymentRate - 在职率统计技术细节:
disabled_person.jobStatus = 1 是残疾人表的旧字段,可能与实际工作状态不同步order_person.work_status = 'working' 是订单人员表的实际工作状态,反映真实工作情况Given 企业小程序数据统计页
When 查看残疾类型分布图
Then 统计应基于 order_person.work_status = 'working' 进行过滤
And 总人数应与首页仪表板在职人数一致
Given 企业小程序数据统计页
When 查看性别分布图
Then 统计应基于 order_person.work_status = 'working' 进行过滤
And 总人数应与首页仪表板在职人数一致
Given 企业小程序数据统计页
When 查看年龄分布图
Then 统计应基于 order_person.work_status = 'working' 进行过滤
And 总人数应与首页仪表板在职人数一致
Given 企业小程序数据统计页
When 查看户籍分布图
Then 统计应基于 order_person.work_status = 'working' 进行过滤
And 总人数应与首页仪表板在职人数一致
Given 企业小程序数据统计页
When 查看薪资分布图
Then 统计应基于 order_person.work_status = 'working' 进行过滤
And 只统计在职人员的薪资
Given 企业小程序数据统计页
When 查看在职率统计
Then 在职人数应基于 order_person.work_status = 'working' 统计
And 与首页仪表板在职人数保持一致
Given 企业小程序数据统计页 When 查看各分布图 Then 各分布图的总人数应相同 And 与首页仪表板在职人数一致
Given 修改完成后 When 运行数据统计页 E2E 测试 Then 所有测试应该通过
statistics.service.ts 中 6 个需要修复的方法jobStatus = 1 的位置disabledPersonRepository 改为使用 orderPersonRepositoryjobStatus = 1 改为 workStatus = 'working'disabledPersonRepository 改为使用 orderPersonRepositoryjobStatus = 1 改为 workStatus = 'working'disabledPersonRepository 改为使用 orderPersonRepositoryjobStatus = 1 改为 workStatus = 'working'disabledPersonRepository 改为使用 orderPersonRepositoryjobStatus = 1 改为 workStatus = 'working'orderPersonRepository,但关联查询仍使用 dp.jobStatus = 1dp.jobStatus = 1 改为 op.workStatus = 'working'disabledPersonRepository 改为 orderPersonRepositoryjobStatus = 1 改为 workStatus = 'working'getCompanyDisabledPersonIds 获取allin-packages/statistics-module/src/services/statistics.service.tsallin-packages/statistics-module/test/statistics.service.spec.ts// 当前实现(错误)
const query = this.disabledPersonRepository
.createQueryBuilder('dp')
.select('dp.disabilityType', 'key')
.addSelect('COUNT(dp.id)', 'value')
.where('dp.id IN (:...personIds)', { personIds })
.andWhere('dp.disabilityType IS NOT NULL')
.andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 }) // 错误
.groupBy('dp.disabilityType');
// 当前实现(错误)
const query = this.disabledPersonRepository
.createQueryBuilder('dp')
.select('dp.gender', 'key')
.addSelect('COUNT(dp.id)', 'value')
.where('dp.id IN (:...personIds)', { personIds })
.andWhere('dp.gender IS NOT NULL')
.andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 }) // 错误
.groupBy('dp.gender');
// 当前实现(错误)
const ageQuery = this.disabledPersonRepository
.createQueryBuilder('dp')
.select('dp.id', 'id')
.addSelect(/* 年龄分组 SQL */)
.where('dp.id IN (:...personIds)', { personIds })
.andWhere('dp.birth_date IS NOT NULL')
.andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 }); // 错误
// 当前实现(错误)
const query = this.disabledPersonRepository
.createQueryBuilder('dp')
.leftJoin(/* AreaEntity 关联 */)
.select('COALESCE(provinceArea.name, dp.province)', 'province')
.addSelect('COALESCE(cityArea.name, dp.city)', 'city')
.addSelect('COUNT(dp.id)', 'value')
.where('dp.id IN (:...personIds)', { personIds })
.andWhere('dp.province IS NOT NULL')
.andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 }) // 错误
.groupBy('dp.province, dp.city, provinceArea.name, cityArea.name');
// 当前实现(错误 - 已使用 orderPerson 但关联条件错误)
const query = this.orderPersonRepository
.createQueryBuilder('op')
.innerJoin('op.order', 'order')
.innerJoin('op.person', 'dp')
.select('op.salaryDetail', 'salary')
.where('order.companyId = :companyId', { companyId })
.andWhere('op.salaryDetail IS NOT NULL')
.andWhere('op.salaryDetail > 0')
.andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 }); // 应改为 op.workStatus
// 当前实现(错误)
const employedCount = await this.disabledPersonRepository
.createQueryBuilder('dp')
.where('dp.id IN (:...personIds)', { personIds })
.andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 }) // 错误
.getCount();
以 getDisabilityTypeDistribution 为例:
// 修复后的实现(正确)
async getDisabilityTypeDistribution(companyId: number): Promise<{
companyId: number;
stats: StatItem[];
total: number;
}> {
// 直接从 orderPerson 表统计,关联 person 获取 disabilityType
const query = this.orderPersonRepository
.createQueryBuilder('op')
.innerJoin('op.order', 'order')
.innerJoin('op.person', 'dp')
.select('dp.disabilityType', 'key')
.addSelect('COUNT(DISTINCT dp.id)', 'value') // 使用 DISTINCT 避免重复计数
.where('order.companyId = :companyId', { companyId })
.andWhere('op.workStatus = :workStatus', { workStatus: 'working' })
.andWhere('dp.disabilityType IS NOT NULL')
.groupBy('dp.disabilityType');
const rawStats = await query.getRawMany();
const total = rawStats.reduce((sum, item) => sum + parseInt(item.value), 0);
const stats = rawStats.map(item => ({
key: item.key,
value: parseInt(item.value),
percentage: total > 0 ? (parseInt(item.value) / total) * 100 : 0
}));
return {
companyId,
stats,
total
};
}
查询重构原则:
disabledPersonRepository 改为 orderPersonRepositoryinnerJoin('op.person', 'dp') 关联残疾人表innerJoin('op.order', 'order') 获取 companyId 过滤op.workStatus = 'working' 替代 dp.jobStatus = 1DISTINCT 使用:
COUNT(DISTINCT dp.id) 避免重复计数性能考虑:
兼容性考虑:
单元测试:
集成测试:
E2E 测试:
allin-packages/statistics-module/src/services/statistics.service.ts - 统计服务实现allin-packages/statistics-module/src/routes/statistics.routes.ts - 统计 API 路由Claude (d8d-model)
N/A
主要修改文件:
allin-packages/statistics-module/src/services/statistics.service.ts (修改 6 个方法)