|
|
@@ -35,6 +35,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取残疾类型分布统计
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 残疾类型分布统计结果
|
|
|
*/
|
|
|
@@ -43,23 +44,16 @@ export class StatisticsService {
|
|
|
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')
|
|
|
+ // 直接从 orderPerson 表统计,关联 person 获取 disabilityType
|
|
|
+ const query = this.orderPersonRepository
|
|
|
+ .createQueryBuilder('op')
|
|
|
+ .innerJoin('op.order', 'order')
|
|
|
+ .innerJoin('op.person', 'dp')
|
|
|
.select('dp.disabilityType', 'key')
|
|
|
- .addSelect('COUNT(dp.id)', 'value')
|
|
|
- .where('dp.id IN (:...personIds)', { personIds })
|
|
|
+ .addSelect('COUNT(DISTINCT dp.id)', 'value') // 使用 DISTINCT 避免重复计数
|
|
|
+ .where('order.companyId = :companyId', { companyId })
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' })
|
|
|
.andWhere('dp.disabilityType IS NOT NULL')
|
|
|
- .andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 })
|
|
|
.groupBy('dp.disabilityType');
|
|
|
|
|
|
const rawStats = await query.getRawMany();
|
|
|
@@ -80,6 +74,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取性别分布统计
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 性别分布统计结果
|
|
|
*/
|
|
|
@@ -88,23 +83,16 @@ export class StatisticsService {
|
|
|
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')
|
|
|
+ // 直接从 orderPerson 表统计,关联 person 获取 gender
|
|
|
+ const query = this.orderPersonRepository
|
|
|
+ .createQueryBuilder('op')
|
|
|
+ .innerJoin('op.order', 'order')
|
|
|
+ .innerJoin('op.person', 'dp')
|
|
|
.select('dp.gender', 'key')
|
|
|
- .addSelect('COUNT(dp.id)', 'value')
|
|
|
- .where('dp.id IN (:...personIds)', { personIds })
|
|
|
+ .addSelect('COUNT(DISTINCT dp.id)', 'value') // 使用 DISTINCT 避免重复计数
|
|
|
+ .where('order.companyId = :companyId', { companyId })
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' })
|
|
|
.andWhere('dp.gender IS NOT NULL')
|
|
|
- .andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 })
|
|
|
.groupBy('dp.gender');
|
|
|
|
|
|
const rawStats = await query.getRawMany();
|
|
|
@@ -125,6 +113,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取年龄分布统计
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 年龄分布统计结果
|
|
|
*/
|
|
|
@@ -133,19 +122,11 @@ export class StatisticsService {
|
|
|
stats: StatItem[];
|
|
|
total: number;
|
|
|
}> {
|
|
|
- const personIds = await this.getCompanyDisabledPersonIds(companyId);
|
|
|
-
|
|
|
- if (personIds.length === 0) {
|
|
|
- return {
|
|
|
- companyId,
|
|
|
- stats: [],
|
|
|
- total: 0
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- // 使用CTE计算年龄分组
|
|
|
- const ageQuery = this.disabledPersonRepository
|
|
|
- .createQueryBuilder('dp')
|
|
|
+ // 使用CTE计算年龄分组,从 orderPerson 表统计
|
|
|
+ const ageQuery = this.orderPersonRepository
|
|
|
+ .createQueryBuilder('op')
|
|
|
+ .innerJoin('op.order', 'order')
|
|
|
+ .innerJoin('op.person', 'dp')
|
|
|
.select('dp.id', 'id')
|
|
|
.addSelect(`
|
|
|
CASE
|
|
|
@@ -155,13 +136,15 @@ export class StatisticsService {
|
|
|
ELSE '46+'
|
|
|
END`, 'age_group'
|
|
|
)
|
|
|
- .where('dp.id IN (:...personIds)', { personIds })
|
|
|
+ .where('order.companyId = :companyId', { companyId })
|
|
|
.andWhere('dp.birth_date IS NOT NULL')
|
|
|
- .andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 });
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' });
|
|
|
|
|
|
const rawAgeData = await ageQuery.getRawMany();
|
|
|
|
|
|
- // 统计年龄分组
|
|
|
+ // 统计年龄分组(去重,因为一个人可能对应多个订单)
|
|
|
+ const uniquePersonIds = new Set(rawAgeData.map(item => item.id));
|
|
|
+
|
|
|
const ageGroups = ['18-25', '26-35', '36-45', '46+'] as const;
|
|
|
const ageStats: Record<typeof ageGroups[number], number> = {
|
|
|
'18-25': 0,
|
|
|
@@ -170,13 +153,16 @@ export class StatisticsService {
|
|
|
'46+': 0
|
|
|
};
|
|
|
|
|
|
+ // 使用 Set 追踪已统计的人员ID,避免重复计数
|
|
|
+ const countedIds = new Set<number>();
|
|
|
rawAgeData.forEach(item => {
|
|
|
- if (item.age_group && Object.prototype.hasOwnProperty.call(ageStats, item.age_group)) {
|
|
|
+ if (item.age_group && Object.prototype.hasOwnProperty.call(ageStats, item.age_group) && !countedIds.has(item.id)) {
|
|
|
ageStats[item.age_group as typeof ageGroups[number]]++;
|
|
|
+ countedIds.add(item.id);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- const total = rawAgeData.length;
|
|
|
+ const total = uniquePersonIds.size;
|
|
|
const stats = ageGroups.map(group => ({
|
|
|
key: group,
|
|
|
value: ageStats[group],
|
|
|
@@ -192,6 +178,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取户籍分布统计
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 户籍分布统计结果
|
|
|
*/
|
|
|
@@ -200,20 +187,12 @@ export class StatisticsService {
|
|
|
stats: HouseholdStatItem[];
|
|
|
total: number;
|
|
|
}> {
|
|
|
- const personIds = await this.getCompanyDisabledPersonIds(companyId);
|
|
|
-
|
|
|
- if (personIds.length === 0) {
|
|
|
- return {
|
|
|
- companyId,
|
|
|
- stats: [],
|
|
|
- total: 0
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
// 使用 LEFT JOIN 关联 areas 表,同时支持数字 ID 和中文名称两种格式
|
|
|
// 使用 CASE WHEN 安全地转换数字 ID,避免对中文名称进行类型转换
|
|
|
- const query = this.disabledPersonRepository
|
|
|
- .createQueryBuilder('dp')
|
|
|
+ const query = this.orderPersonRepository
|
|
|
+ .createQueryBuilder('op')
|
|
|
+ .innerJoin('op.order', 'order')
|
|
|
+ .innerJoin('op.person', 'dp')
|
|
|
.leftJoin(
|
|
|
AreaEntity,
|
|
|
'provinceArea',
|
|
|
@@ -226,10 +205,10 @@ export class StatisticsService {
|
|
|
)
|
|
|
.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 })
|
|
|
+ .addSelect('COUNT(DISTINCT dp.id)', 'value') // 使用 DISTINCT 避免重复计数
|
|
|
+ .where('order.companyId = :companyId', { companyId })
|
|
|
.andWhere('dp.province IS NOT NULL')
|
|
|
- .andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 })
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' })
|
|
|
.groupBy('dp.province, dp.city, provinceArea.name, cityArea.name');
|
|
|
|
|
|
const rawStats = await query.getRawMany();
|
|
|
@@ -302,6 +281,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取薪资分布统计
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 薪资分布统计结果
|
|
|
*/
|
|
|
@@ -314,12 +294,11 @@ export class StatisticsService {
|
|
|
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 });
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' });
|
|
|
|
|
|
const rawSalaries = await query.getRawMany();
|
|
|
|
|
|
@@ -382,6 +361,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取在职人数统计(简化版:只返回当前数据)
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 在职人数统计结果
|
|
|
*/
|
|
|
@@ -389,21 +369,13 @@ export class StatisticsService {
|
|
|
companyId: number;
|
|
|
count: number;
|
|
|
}> {
|
|
|
- // 获取在职人员(jobStatus = 1)
|
|
|
- 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 })
|
|
|
+ // 直接从 orderPerson 表统计在职人数(work_status = 'working')
|
|
|
+ // 与首页 dashboard /company/overview API 使用相同的统计口径
|
|
|
+ const currentCount = await this.orderPersonRepository
|
|
|
+ .createQueryBuilder('op')
|
|
|
+ .innerJoin('op.order', 'order')
|
|
|
+ .where('order.companyId = :companyId', { companyId })
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' })
|
|
|
.getCount();
|
|
|
|
|
|
return {
|
|
|
@@ -414,6 +386,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取平均薪资统计(简化版:只返回当前数据)
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 平均薪资统计结果
|
|
|
*/
|
|
|
@@ -421,14 +394,15 @@ export class StatisticsService {
|
|
|
companyId: number;
|
|
|
average: number;
|
|
|
}> {
|
|
|
- // 获取企业关联的订单人员薪资数据
|
|
|
+ // 获取企业关联的订单人员薪资数据(仅在职人员)
|
|
|
const salaryQuery = this.orderPersonRepository
|
|
|
.createQueryBuilder('op')
|
|
|
.innerJoin('op.order', 'order')
|
|
|
.select('op.salaryDetail', 'salary')
|
|
|
.where('order.companyId = :companyId', { companyId })
|
|
|
.andWhere('op.salaryDetail IS NOT NULL')
|
|
|
- .andWhere('op.salaryDetail > 0');
|
|
|
+ .andWhere('op.salaryDetail > 0')
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' });
|
|
|
|
|
|
const rawSalaries = await salaryQuery.getRawMany();
|
|
|
|
|
|
@@ -451,6 +425,7 @@ export class StatisticsService {
|
|
|
|
|
|
/**
|
|
|
* 获取在职率统计(简化版:只返回当前数据)
|
|
|
+ * 使用 orderPerson.workStatus = 'working' 作为统计口径,与首页 overview 保持一致
|
|
|
* @param companyId 企业ID
|
|
|
* @returns 在职率统计结果
|
|
|
*/
|
|
|
@@ -458,7 +433,7 @@ export class StatisticsService {
|
|
|
companyId: number;
|
|
|
rate: number;
|
|
|
}> {
|
|
|
- // 获取企业关联的残疾人员ID列表
|
|
|
+ // 获取企业关联的残疾人员ID列表(作为分母:总人数)
|
|
|
const personIds = await this.getCompanyDisabledPersonIds(companyId);
|
|
|
|
|
|
if (personIds.length === 0) {
|
|
|
@@ -471,12 +446,17 @@ export class StatisticsService {
|
|
|
// 总人数
|
|
|
const totalPersons = personIds.length;
|
|
|
|
|
|
- // 在职人数
|
|
|
- const employedCount = await this.disabledPersonRepository
|
|
|
- .createQueryBuilder('dp')
|
|
|
- .where('dp.id IN (:...personIds)', { personIds })
|
|
|
- .andWhere('dp.jobStatus = :jobStatus', { jobStatus: 1 })
|
|
|
- .getCount();
|
|
|
+ // 在职人数:使用 orderPerson.workStatus = 'working' 统计
|
|
|
+ // 使用 COUNT(DISTINCT op.personId) 避免同一人员多订单重复计数
|
|
|
+ const result = await this.orderPersonRepository
|
|
|
+ .createQueryBuilder('op')
|
|
|
+ .innerJoin('op.order', 'order')
|
|
|
+ .select('COUNT(DISTINCT op.personId)', 'count')
|
|
|
+ .where('order.companyId = :companyId', { companyId })
|
|
|
+ .andWhere('op.workStatus = :workStatus', { workStatus: 'working' })
|
|
|
+ .getRawOne();
|
|
|
+
|
|
|
+ const employedCount = result ? parseInt(result.count) : 0;
|
|
|
|
|
|
// 计算在职率
|
|
|
const rate = totalPersons > 0 ? Math.round((employedCount / totalPersons) * 100) : 0;
|