""" 通知服務 """ from datetime import datetime from typing import Optional, List from sqlalchemy.orm import Session from app.models.notification import Notification, NotificationPreference from app.models.employee import Employee from app.models.kpi_sheet import KPISheet class NotifyService: """通知服務""" def __init__(self, db: Session): self.db = db def create( self, recipient_id: int, type: str, title: str, content: Optional[str] = None, related_sheet_id: Optional[int] = None, ) -> Notification: """建立通知""" notification = Notification( recipient_id=recipient_id, type=type, title=title, content=content, related_sheet_id=related_sheet_id, ) self.db.add(notification) self.db.commit() self.db.refresh(notification) return notification def get_by_recipient( self, recipient_id: int, skip: int = 0, limit: int = 50 ) -> List[Notification]: """取得通知列表""" return ( self.db.query(Notification) .filter(Notification.recipient_id == recipient_id) .order_by(Notification.created_at.desc()) .offset(skip) .limit(limit) .all() ) def get_unread_count(self, recipient_id: int) -> int: """取得未讀數量""" return ( self.db.query(Notification) .filter( Notification.recipient_id == recipient_id, Notification.is_read == False, ) .count() ) def mark_as_read(self, notification_id: int, recipient_id: int) -> bool: """標記已讀""" notification = ( self.db.query(Notification) .filter( Notification.id == notification_id, Notification.recipient_id == recipient_id, ) .first() ) if not notification: return False notification.is_read = True notification.read_at = datetime.utcnow() self.db.commit() return True def mark_all_as_read(self, recipient_id: int) -> int: """全部標記已讀""" result = ( self.db.query(Notification) .filter( Notification.recipient_id == recipient_id, Notification.is_read == False, ) .update({"is_read": True, "read_at": datetime.utcnow()}) ) self.db.commit() return result def get_preferences(self, employee_id: int) -> Optional[NotificationPreference]: """取得通知偏好""" return ( self.db.query(NotificationPreference) .filter(NotificationPreference.employee_id == employee_id) .first() ) def update_preferences( self, employee_id: int, email_enabled: Optional[bool] = None, in_app_enabled: Optional[bool] = None, reminder_days_before: Optional[int] = None, ) -> NotificationPreference: """更新通知偏好""" pref = self.get_preferences(employee_id) if not pref: pref = NotificationPreference(employee_id=employee_id) self.db.add(pref) if email_enabled is not None: pref.email_enabled = email_enabled if in_app_enabled is not None: pref.in_app_enabled = in_app_enabled if reminder_days_before is not None: pref.reminder_days_before = reminder_days_before self.db.commit() self.db.refresh(pref) return pref # ==================== 事件通知 ==================== def notify_kpi_submitted(self, sheet: KPISheet) -> None: """通知 KPI 已提交""" employee = sheet.employee if not employee.manager_id: return self.create( recipient_id=employee.manager_id, type="kpi_submitted", title="KPI 待審核", content=f"{employee.name} 的 {sheet.period.code} KPI 已提交,請審核。", related_sheet_id=sheet.id, ) def notify_kpi_approved(self, sheet: KPISheet) -> None: """通知 KPI 已核准""" self.create( recipient_id=sheet.employee_id, type="kpi_approved", title="KPI 已核准", content=f"您的 {sheet.period.code} KPI 已審核通過。", related_sheet_id=sheet.id, ) def notify_kpi_rejected(self, sheet: KPISheet, reason: str) -> None: """通知 KPI 已退回""" self.create( recipient_id=sheet.employee_id, type="kpi_rejected", title="KPI 已退回", content=f"您的 {sheet.period.code} KPI 已退回,原因:{reason}", related_sheet_id=sheet.id, ) def notify_self_eval_completed(self, sheet: KPISheet) -> None: """通知自評已完成""" employee = sheet.employee if not employee.manager_id: return self.create( recipient_id=employee.manager_id, type="self_eval_completed", title="員工自評已完成", content=f"{employee.name} 的 {sheet.period.code} 自評已完成,請進行覆核。", related_sheet_id=sheet.id, ) def notify_manager_eval_completed(self, sheet: KPISheet) -> None: """通知主管評核已完成""" self.create( recipient_id=sheet.employee_id, type="manager_eval_completed", title="KPI 評核已完成", content=f"您的 {sheet.period.code} KPI 評核已完成,獎金月數:{sheet.total_score}。", related_sheet_id=sheet.id, )