225 lines
9.0 KiB
Python
225 lines
9.0 KiB
Python
"""
|
||
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 |