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 { const [rows] = await pool.query( `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 }; }