84 lines
2.6 KiB
TypeScript
84 lines
2.6 KiB
TypeScript
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 };
|
||
}
|