""" 알림 시스템 퇴근 시간 알림, 점심시간 알림 등 """ from datetime import datetime, timedelta from typing import Optional from PyQt5.QtCore import QTimer, QObject, pyqtSignal from core.settings_keys import ( NOTIF_CLOCK_OUT, NOTIF_LUNCH, NOTIF_OVERTIME, NOTIF_HEALTH, ) from core.i18n import tr class Notifier(QObject): """알림 시스템 클래스""" notification_signal = pyqtSignal(str, str) # (제목, 메시지) def __init__(self, parent=None, db=None): super().__init__(parent) self.db = db # 설정 키 가드용 (None이면 모든 알림 활성) self.timer = QTimer() self.timer.timeout.connect(self.check_notifications) self.timer.start(60000) # 1분마다 체크 # 알림 상태 추적 self.notified_30min = False self.notified_lunch = False self.notified_overtime = False self.notified_health = False self.notified_weekly = False self.notified_threshold = False self.last_check_date = datetime.now().date() def _enabled(self, key: str) -> bool: """설정에서 해당 알림이 켜져 있는지. db 없으면 기본 켜짐.""" if self.db is None: return True val = self.db.get_setting(key, 'true') return str(val).lower() in ('1', 'true', 'yes') def check_notifications(self): """알림 체크""" now = datetime.now() current_date = now.date() # 날짜가 바뀌면 알림 상태 리셋 if current_date != self.last_check_date: self.reset_notifications() self.last_check_date = current_date def check_clock_out_soon(self, clock_out_time: datetime, current_time: Optional[datetime] = None): """ 퇴근 30분 전 알림 Args: clock_out_time: 예상 퇴근 시간 current_time: 현재 시간 (None이면 지금) """ if current_time is None: current_time = datetime.now() if not self._enabled(NOTIF_CLOCK_OUT): return time_diff = clock_out_time - current_time # 사용자 설정 N분 이내 알림 (기본 30, 설정에서 1~120 범위) threshold_min = 30 if self.db is not None: try: threshold_min = int(self.db.get_setting('notification_before_minutes', '30') or '30') threshold_min = max(1, min(120, threshold_min)) except (ValueError, TypeError): threshold_min = 30 threshold_sec = threshold_min * 60 if 0 < time_diff.total_seconds() <= threshold_sec and not self.notified_30min: minutes_left = int(time_diff.total_seconds() / 60) self.notification_signal.emit( tr('notif.clock_out_soon.title'), tr('notif.clock_out_soon.body', minutes=minutes_left), ) self.notified_30min = True def check_lunch_reminder(self, clock_in_time: datetime, lunch_enabled: bool, current_time: Optional[datetime] = None): """ 점심시간 등록 알림 Args: clock_in_time: 출근 시간 lunch_enabled: 점심시간 등록 여부 current_time: 현재 시간 """ if current_time is None: current_time = datetime.now() if not self._enabled(NOTIF_LUNCH): return # 이미 점심 등록했거나, 이미 알림 보냈으면 스킵 if lunch_enabled or self.notified_lunch: return # 출근 후 4시간 경과 (점심시간으로 추정) time_since_clock_in = current_time - clock_in_time if time_since_clock_in.total_seconds() >= 4 * 3600: self.notification_signal.emit( tr('notif.lunch_reminder.title'), tr('notif.lunch_reminder.body'), ) self.notified_lunch = True def check_overtime_earning(self, overtime_minutes: int): """ 연장근무 적립 알림 Args: overtime_minutes: 예상 연장근무 시간 (분) """ if not self._enabled(NOTIF_OVERTIME): return if overtime_minutes >= 30 and not self.notified_overtime: hours = overtime_minutes // 60 mins = overtime_minutes % 60 from utils.time_format import format_hours_minutes time_str = format_hours_minutes(overtime_minutes, omit_zero_minutes=True) self.notification_signal.emit( tr('notif.overtime_earning.title'), tr('notif.overtime_earning.body', time_str=time_str), ) self.notified_overtime = True def notify_overtime_threshold(self, total_overtime_hours: float): """연장근무 누적 알림 (20시간 이상)""" if not self._enabled(NOTIF_OVERTIME): return if total_overtime_hours >= 20 and not self.notified_threshold: self.notification_signal.emit( tr('notif.overtime_threshold.title'), tr('notif.overtime_threshold.body', hours=total_overtime_hours), ) self.notified_threshold = True def notify_health_warning(self, consecutive_overtime_days: int): """건강 경고 (연속 연장근무 일수)""" if not self._enabled(NOTIF_HEALTH): return if consecutive_overtime_days >= 3 and not self.notified_health: self.notification_signal.emit( tr('notif.health.title'), tr('notif.health.body', days=consecutive_overtime_days), ) self.notified_health = True def notify_weekly_hours(self, total_hours: float): """주 52시간 경고""" if not self._enabled(NOTIF_HEALTH): return if total_hours > 52 and not self.notified_weekly: self.notification_signal.emit( tr('notif.weekly_52.title'), tr('notif.weekly_52.body', hours=total_hours), ) self.notified_weekly = True def reset_notifications(self): """알림 상태 리셋 (날짜 변경 시)""" self.notified_30min = False self.notified_lunch = False self.notified_overtime = False self.notified_health = False self.notified_weekly = False self.notified_threshold = False # 테스트 코드 if __name__ == "__main__": from PyQt5.QtWidgets import QApplication, QMessageBox import sys app = QApplication(sys.argv) notifier = Notifier() # 시그널 연결 (테스트) def show_notification(title, message): QMessageBox.information(None, title, message) notifier.notification_signal.connect(show_notification) # 테스트: 퇴근 30분 전 clock_out = datetime.now() + timedelta(minutes=25) notifier.check_clock_out_soon(clock_out) print("Notifier test completed")