Files
KPI-management/app/services/notify_service.py
DonaldFang 方士碩 f810ddc2ea Initial commit: KPI Management System Backend
Features:
- FastAPI backend with JWT authentication
- MySQL database with SQLAlchemy ORM
- KPI workflow: draft → pending → approved → evaluation → completed
- Ollama LLM API integration for AI features
- Gitea API integration for version control
- Complete API endpoints for KPI, dashboard, notifications

Tables: KPI_D_* prefix naming convention

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 16:20:57 +08:00

189 lines
5.8 KiB
Python

"""
通知服務
"""
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,
)