This commit is contained in:
beabigegg
2025-08-29 16:25:46 +08:00
commit b0c86302ff
65 changed files with 19786 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
"""
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