""" 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