first commit

This commit is contained in:
2026-04-11 11:51:54 +08:00
commit b12a84e388
99 changed files with 19620 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
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 };
}