Files
TODO_list_system/backend/utils/notification_service.py
beabigegg b0c86302ff 1ST
2025-08-29 16:25:46 +08:00

225 lines
9.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Notification Service
處理通知邏輯和摘要資料準備
"""
from datetime import datetime, date, timedelta
from sqlalchemy import and_, or_, func
from models import (
db, TodoItem, TodoItemResponsible, TodoItemFollower,
TodoUserPref, TodoAuditLog
)
from utils.logger import get_logger
logger = get_logger(__name__)
class NotificationService:
"""通知服務類別"""
def get_notification_recipients(self, todo):
"""取得待辦事項的通知收件人清單"""
recipients = set()
# 加入建立者(如果啟用通知)
creator_pref = TodoUserPref.query.filter_by(ad_account=todo.creator_ad).first()
if creator_pref and creator_pref.notification_enabled:
recipients.add(todo.creator_ad)
# 加入負責人(如果啟用通知)
for responsible in todo.responsible_users:
user_pref = TodoUserPref.query.filter_by(ad_account=responsible.ad_account).first()
if user_pref and user_pref.notification_enabled:
recipients.add(responsible.ad_account)
# 加入追蹤人(如果啟用通知)
for follower in todo.followers:
user_pref = TodoUserPref.query.filter_by(ad_account=follower.ad_account).first()
if user_pref and user_pref.notification_enabled:
recipients.add(follower.ad_account)
return list(recipients)
def prepare_digest(self, user_ad, digest_type='weekly'):
"""準備摘要資料"""
try:
# 計算日期範圍
today = date.today()
if digest_type == 'daily':
start_date = today
end_date = today
period_name = '今日'
elif digest_type == 'weekly':
start_date = today - timedelta(days=today.weekday()) # 週一
end_date = start_date + timedelta(days=6) # 週日
period_name = '本週'
elif digest_type == 'monthly':
start_date = today.replace(day=1)
next_month = today.replace(day=28) + timedelta(days=4)
end_date = next_month - timedelta(days=next_month.day)
period_name = '本月'
else:
raise ValueError(f"Unsupported digest type: {digest_type}")
# 基礎查詢 - 使用者相關的待辦事項
base_query = TodoItem.query.filter(
or_(
TodoItem.creator_ad == user_ad,
TodoItem.responsible_users.any(TodoItemResponsible.ad_account == user_ad),
TodoItem.followers.any(TodoItemFollower.ad_account == user_ad)
)
)
# 統計資料
stats = {
'total_todos': base_query.count(),
'completed_todos': base_query.filter(TodoItem.status == 'DONE').count(),
'doing_todos': base_query.filter(TodoItem.status == 'DOING').count(),
'blocked_todos': base_query.filter(TodoItem.status == 'BLOCKED').count(),
'new_todos': base_query.filter(TodoItem.status == 'NEW').count()
}
# 期間內完成的待辦事項
completed_in_period = base_query.filter(
and_(
TodoItem.status == 'DONE',
func.date(TodoItem.completed_at).between(start_date, end_date)
)
).all()
# 期間內建立的待辦事項
created_in_period = base_query.filter(
func.date(TodoItem.created_at).between(start_date, end_date)
).all()
# 即將到期的待辦事項未來7天
upcoming_due = base_query.filter(
and_(
TodoItem.due_date.between(today, today + timedelta(days=7)),
TodoItem.status != 'DONE'
)
).order_by(TodoItem.due_date).all()
# 逾期的待辦事項
overdue = base_query.filter(
and_(
TodoItem.due_date < today,
TodoItem.status != 'DONE'
)
).order_by(TodoItem.due_date).all()
# 高優先級待辦事項
high_priority = base_query.filter(
and_(
TodoItem.priority == 'HIGH',
TodoItem.status != 'DONE'
)
).all()
# 活動記錄(期間內的操作)
activities = TodoAuditLog.query.filter(
and_(
TodoAuditLog.actor_ad == user_ad,
func.date(TodoAuditLog.created_at).between(start_date, end_date)
)
).order_by(TodoAuditLog.created_at.desc()).limit(10).all()
# 組織摘要資料
digest_data = {
'type': digest_type,
'period_name': period_name,
'start_date': start_date,
'end_date': end_date,
'user_ad': user_ad,
'stats': stats,
'completed_in_period': [todo.to_dict() for todo in completed_in_period],
'created_in_period': [todo.to_dict() for todo in created_in_period],
'upcoming_due': [todo.to_dict() for todo in upcoming_due],
'overdue': [todo.to_dict() for todo in overdue],
'high_priority': [todo.to_dict() for todo in high_priority],
'recent_activities': [
{
'action': activity.action,
'created_at': activity.created_at,
'detail': activity.detail,
'todo_id': activity.todo_id
}
for activity in activities
],
'generated_at': datetime.now()
}
return digest_data
except Exception as e:
logger.error(f"Failed to prepare digest for {user_ad}: {str(e)}")
raise
def should_send_notification(self, user_ad, notification_type):
"""檢查是否應該發送通知"""
try:
user_pref = TodoUserPref.query.filter_by(ad_account=user_ad).first()
if not user_pref:
return False
# 檢查通知開關
if notification_type == 'email_reminder':
return user_pref.email_reminder_enabled
elif notification_type == 'weekly_summary':
return user_pref.weekly_summary_enabled
elif notification_type == 'general':
return user_pref.notification_enabled
return False
except Exception as e:
logger.error(f"Error checking notification settings for {user_ad}: {str(e)}")
return False
def get_users_for_batch_notifications(self, notification_type):
"""取得需要接收批量通知的使用者清單"""
try:
if notification_type == 'weekly_summary':
users = db.session.query(TodoUserPref.ad_account).filter(
TodoUserPref.weekly_summary_enabled == True
).all()
elif notification_type == 'email_reminder':
users = db.session.query(TodoUserPref.ad_account).filter(
TodoUserPref.email_reminder_enabled == True
).all()
else:
users = db.session.query(TodoUserPref.ad_account).filter(
TodoUserPref.notification_enabled == True
).all()
return [user[0] for user in users]
except Exception as e:
logger.error(f"Error getting users for batch notifications: {str(e)}")
return []
def create_notification_summary(self, todos, notification_type):
"""建立通知摘要"""
try:
if notification_type == 'due_tomorrow':
return {
'title': '明日到期提醒',
'description': f'您有 {len(todos)} 項待辦事項將於明日到期',
'todos': [todo.to_dict() for todo in todos]
}
elif notification_type == 'overdue':
return {
'title': '逾期提醒',
'description': f'您有 {len(todos)} 項待辦事項已逾期',
'todos': [todo.to_dict() for todo in todos]
}
else:
return {
'title': '待辦事項提醒',
'description': f'您有 {len(todos)} 項待辦事項需要關注',
'todos': [todo.to_dict() for todo in todos]
}
except Exception as e:
logger.error(f"Error creating notification summary: {str(e)}")
return None