Files
performance-evaluation-system/backend/src/services/CalculationService.ts
2026-04-11 11:51:54 +08:00

84 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pool from '../config/database';
import { PerformanceLevel } from '../dao/PerformanceDAO';
export interface AttendanceInput {
leaveDays: number;
lateTimes: number;
lackCardTimes: number;
}
export interface LevelAndReward {
level: PerformanceLevel;
rewardPunish: string;
}
export interface ConsecutiveLowScoreResult {
consecutiveMonths: number;
warning: 'none' | 'written_warning' | 'dismissal';
}
/**
* Calculate attendance score.
* Base: 10 points. Deduct 5 per leave day, 2 per late/lack-card occurrence. Minimum 0.
* Requirements: 11.1, 11.2, 11.3, 11.5
*/
export function calculateAttendanceScore(input: AttendanceInput): number {
const { leaveDays, lateTimes, lackCardTimes } = input;
const deduction = leaveDays * 5 + lateTimes * 2 + lackCardTimes * 2;
return Math.max(0, 10 - deduction);
}
/**
* Calculate performance level and reward/punishment description based on total score.
* Requirements: 5.1, 5.2, 5.3, 5.4, 5.5
*/
export function calculateLevelAndReward(totalScore: number): LevelAndReward {
if (totalScore >= 90) {
return { level: 'excellent', rewardPunish: '优秀,按公司规定给予奖励' };
} else if (totalScore >= 80) {
return { level: 'qualified', rewardPunish: '合格扣除当月绩效工资100元' };
} else if (totalScore >= 70) {
return { level: 'qualified', rewardPunish: '合格扣除当月绩效工资200元' };
} else if (totalScore >= 60) {
return { level: 'need_motivation', rewardPunish: '需激励扣除当月绩效工资300元' };
} else {
return { level: 'unqualified', rewardPunish: '不合格扣除当月绩效工资600元' };
}
}
/**
* Check consecutive low score warning for an employee.
* Queries the most recent completed performance records and counts consecutive months below 60.
* Requirements: 5.6, 5.7
*/
export async function checkConsecutiveLowScore(
userId: number,
lookbackMonths: number = 3
): Promise<ConsecutiveLowScoreResult> {
const [rows] = await pool.query<any[]>(
`SELECT total_score FROM performance_month
WHERE user_id = ? AND status = 'completed' AND total_score IS NOT NULL
ORDER BY month DESC
LIMIT ?`,
[userId, lookbackMonths]
);
let consecutiveMonths = 0;
for (const row of rows) {
if (row.total_score < 60) {
consecutiveMonths++;
} else {
break;
}
}
let warning: ConsecutiveLowScoreResult['warning'] = 'none';
if (consecutiveMonths >= 3) {
warning = 'dismissal';
} else if (consecutiveMonths >= 2) {
warning = 'written_warning';
}
return { consecutiveMonths, warning };
}