| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- import { DataSource, Repository } from 'typeorm';
- import { DisabledPerson } from '@d8d/allin-disability-module/entities';
- import { OrderPerson } from '@d8d/allin-order-module/entities';
- import { EmploymentOrder } from '@d8d/allin-order-module/entities';
- import { AgeGroup, SalaryRange, StatItem, HouseholdStatItem } from '../schemas/statistics.schema';
- export class StatisticsService {
- private readonly disabledPersonRepository: Repository<DisabledPerson>;
- private readonly orderPersonRepository: Repository<OrderPerson>;
- private readonly employmentOrderRepository: Repository<EmploymentOrder>;
- constructor(dataSource: DataSource) {
- this.disabledPersonRepository = dataSource.getRepository(DisabledPerson);
- this.orderPersonRepository = dataSource.getRepository(OrderPerson);
- this.employmentOrderRepository = dataSource.getRepository(EmploymentOrder);
- }
- /**
- * 获取企业关联的残疾人员ID列表(用于数据隔离)
- * @param companyId 企业ID
- * @returns 残疾人员ID数组
- */
- private async getCompanyDisabledPersonIds(companyId: number): Promise<number[]> {
- const query = this.disabledPersonRepository
- .createQueryBuilder('dp')
- .innerJoin('dp.orderPersons', 'op')
- .innerJoin('op.order', 'order')
- .where('order.companyId = :companyId', { companyId })
- .select('dp.id', 'id');
- const result = await query.getRawMany();
- return result.map(item => item.id);
- }
- /**
- * 获取残疾类型分布统计
- * @param companyId 企业ID
- * @returns 残疾类型分布统计结果
- */
- async getDisabilityTypeDistribution(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.disabilityType', 'key')
- .addSelect('COUNT(dp.id)', 'value')
- .where('dp.id IN (:...personIds)', { personIds })
- .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
- };
- }
- /**
- * 获取性别分布统计
- * @param companyId 企业ID
- * @returns 性别分布统计结果
- */
- async getGenderDistribution(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.gender', 'key')
- .addSelect('COUNT(dp.id)', 'value')
- .where('dp.id IN (:...personIds)', { personIds })
- .andWhere('dp.gender IS NOT NULL')
- .groupBy('dp.gender');
- 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
- };
- }
- /**
- * 获取年龄分布统计
- * @param companyId 企业ID
- * @returns 年龄分布统计结果
- */
- async getAgeDistribution(companyId: number): Promise<{
- companyId: number;
- 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')
- .select('dp.id', 'id')
- .addSelect(`
- CASE
- WHEN EXTRACT(YEAR FROM AGE(dp.birth_date)) BETWEEN 18 AND 25 THEN '18-25'
- WHEN EXTRACT(YEAR FROM AGE(dp.birth_date)) BETWEEN 26 AND 35 THEN '26-35'
- WHEN EXTRACT(YEAR FROM AGE(dp.birth_date)) BETWEEN 36 AND 45 THEN '36-45'
- ELSE '46+'
- END`, 'age_group'
- )
- .where('dp.id IN (:...personIds)', { personIds })
- .andWhere('dp.birth_date IS NOT NULL');
- const rawAgeData = await ageQuery.getRawMany();
- // 统计年龄分组
- const ageGroups = ['18-25', '26-35', '36-45', '46+'] as const;
- const ageStats: Record<typeof ageGroups[number], number> = {
- '18-25': 0,
- '26-35': 0,
- '36-45': 0,
- '46+': 0
- };
- rawAgeData.forEach(item => {
- if (item.age_group && ageStats.hasOwnProperty(item.age_group)) {
- ageStats[item.age_group as typeof ageGroups[number]]++;
- }
- });
- const total = rawAgeData.length;
- const stats = ageGroups.map(group => ({
- key: group,
- value: ageStats[group],
- percentage: total > 0 ? (ageStats[group] / total) * 100 : 0
- })).filter(item => item.value > 0);
- return {
- companyId,
- stats,
- total
- };
- }
- /**
- * 获取户籍分布统计
- * @param companyId 企业ID
- * @returns 户籍分布统计结果
- */
- async getHouseholdDistribution(companyId: number): Promise<{
- companyId: number;
- stats: HouseholdStatItem[];
- 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.householdProvince', 'province')
- .addSelect('dp.householdCity', 'city')
- .addSelect('COUNT(dp.id)', 'value')
- .where('dp.id IN (:...personIds)', { personIds })
- .andWhere('dp.householdProvince IS NOT NULL')
- .groupBy('dp.householdProvince, dp.householdCity');
- const rawStats = await query.getRawMany();
- const total = rawStats.reduce((sum, item) => sum + parseInt(item.value), 0);
- const stats = rawStats.map(item => ({
- key: `${item.province}${item.city ? `-${item.city}` : ''}`,
- value: parseInt(item.value),
- percentage: total > 0 ? (parseInt(item.value) / total) * 100 : 0,
- province: item.province,
- city: item.city || undefined
- }));
- return {
- companyId,
- stats,
- total
- };
- }
- /**
- * 获取在职状态分布统计
- * @param companyId 企业ID
- * @returns 在职状态分布统计结果
- */
- 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);
- 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
- };
- }
- /**
- * 获取薪资分布统计
- * @param companyId 企业ID
- * @returns 薪资分布统计结果
- */
- async getSalaryDistribution(companyId: number): Promise<{
- companyId: number;
- stats: StatItem[];
- total: number;
- }> {
- // 获取企业关联的订单人员薪资数据
- const query = 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');
- const rawSalaries = await query.getRawMany();
- if (rawSalaries.length === 0) {
- return {
- companyId,
- stats: [],
- total: 0
- };
- }
- // 定义薪资范围
- const salaryRanges: Array<{ key: SalaryRange; min: number; max: number | null }> = [
- { key: '<3000', min: 0, max: 3000 },
- { key: '3000-5000', min: 3000, max: 5000 },
- { key: '5000-8000', min: 5000, max: 8000 },
- { key: '8000-12000', min: 8000, max: 12000 },
- { key: '12000+', min: 12000, max: null }
- ];
- // 统计各薪资范围人数
- const salaryStats: Record<SalaryRange, number> = {
- '<3000': 0,
- '3000-5000': 0,
- '5000-8000': 0,
- '8000-12000': 0,
- '12000+': 0
- };
- rawSalaries.forEach(item => {
- const salary = parseFloat(item.salary);
- for (const range of salaryRanges) {
- if (range.max === null) {
- if (salary >= range.min) {
- salaryStats[range.key]++;
- break;
- }
- } else if (salary >= range.min && salary < range.max) {
- salaryStats[range.key]++;
- break;
- }
- }
- });
- const total = rawSalaries.length;
- const stats = salaryRanges
- .map(range => ({
- key: range.key,
- value: salaryStats[range.key],
- percentage: total > 0 ? (salaryStats[range.key] / total) * 100 : 0
- }))
- .filter(item => item.value > 0);
- return {
- companyId,
- stats,
- total
- };
- }
- }
|