from datetime import datetime, timedelta from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from backend.app.core.error_codes import ErrorCode from backend.app.core.exceptions import AppException from backend.app.repositories.config_repository import ConfigRepository from backend.app.repositories.reminder_repository import ReminderRepository from backend.app.services.audit_service import audit_service class ReminderService: def __init__(self) -> None: self.repository = ReminderRepository() self.config_repository = ConfigRepository() def list_reminders(self, session: Session | None = None, filters: dict | None = None) -> dict: filters = filters or {} if session is not None: try: reminders = self.repository.list_reminders(session, filters) page_no = filters.get("page_no", 1) page_size = filters.get("page_size", 20) start = max(page_no - 1, 0) * page_size page_list = reminders[start : start + page_size] return { "total": len(reminders), "page_no": page_no, "page_size": page_size, "list": [self._map_reminder(item) for item in page_list], } except SQLAlchemyError: pass return { "total": 1, "page_no": 1, "page_size": 20, "list": [ { "reminder_id": 6001, "title": "订单待审批", "type": "order_approve", "biz_type": "sales_order", "biz_id": 9001, "receiver_user_id": 1, "status": "pending", "sent_at": "2026-05-14 11:00:00", "created_at": "2026-05-14 11:00:00", } ], } def read_reminder(self, reminder_id: int, session: Session | None = None) -> dict: if session is not None: try: reminder = self.repository.get_reminder(session, reminder_id) if reminder is None: raise AppException(code=ErrorCode.NOT_FOUND, message="提醒不存在", status_code=404) before_status = reminder.status reminder.status = "read" session.add(reminder) audit_service.write_log( session, { "operate_type": "reminder_read", "biz_type": reminder.biz_type, "biz_id": reminder.biz_id, "before_value": {"status": before_status}, "after_value": {"status": reminder.status}, "remark": f"提醒 {reminder_id} 标记已读", }, ) session.commit() return {"reminder_id": reminder.id, "status": reminder.status} except AppException: session.rollback() raise except SQLAlchemyError: session.rollback() return {"reminder_id": reminder_id, "status": "read"} def check_arrears(self, session: Session | None = None) -> dict: if session is not None: try: rows = self.repository.list_overdue_arrears(session) created_count = 0 for arrears, customer, order in rows: if arrears.due_date is None or arrears.due_date >= datetime.now().date(): continue receiver_user_id = (customer.salesman_id if customer and customer.salesman_id else 0) or 0 if receiver_user_id <= 0: continue existed = self.repository.find_active_reminder( session, "arrears", "customer_arrears", arrears.id, receiver_user_id, ) if existed is not None: continue title = f"客户欠款逾期提醒 - {customer.customer_name if customer else '未知客户'}" content = ( f"客户存在逾期欠款,欠款金额 {float(arrears.arrears_amount or 0):.2f} 元," f"订单编号 {order.order_no if order else '-'},请及时跟进。" ) self.repository.create_reminder( session, { "reminder_type": "arrears", "biz_type": "customer_arrears", "biz_id": arrears.id, "receiver_user_id": receiver_user_id, "reminder_title": title, "reminder_content": content, "status": "pending", "sent_at": datetime.now(), }, ) arrears.reminded_at = datetime.now() arrears.status = "overdue" session.add(arrears) created_count += 1 session.commit() return {"checked": True, "created_count": created_count} except AppException: session.rollback() raise except SQLAlchemyError: session.rollback() return {"checked": True, "created_count": 0} def check_inactive_customers(self, session: Session | None = None) -> dict: if session is not None: try: inactive_days = int(self._get_config_value(session, "inactive_customer_days", "90")) amount_threshold = float(self._get_config_value(session, "inactive_customer_amount_threshold", "1000")) inactive_before = datetime.now() - timedelta(days=inactive_days) rows = self.repository.list_inactive_customers(session, inactive_before) created_count = 0 for customer, latest_order in rows: if float(customer.credit_limit or 0) < amount_threshold: continue receiver_user_id = (customer.salesman_id or 0) if receiver_user_id <= 0: continue existed = self.repository.find_active_reminder( session, "inactive_customer", "customer", customer.id, receiver_user_id, ) if existed is not None: continue last_order_time = ( latest_order.created_at.strftime("%Y-%m-%d %H:%M:%S") if latest_order and latest_order.created_at else "暂无订单" ) self.repository.create_reminder( session, { "reminder_type": "inactive_customer", "biz_type": "customer", "biz_id": customer.id, "receiver_user_id": receiver_user_id, "reminder_title": f"沉默客户提醒 - {customer.customer_name}", "reminder_content": f"客户超过 {inactive_days} 天未下单,最近订单时间:{last_order_time}。", "status": "pending", "sent_at": datetime.now(), }, ) created_count += 1 session.commit() return {"checked": True, "created_count": created_count} except AppException: session.rollback() raise except SQLAlchemyError: session.rollback() return {"checked": True, "created_count": 0} def check_logistics_timeout(self, session: Session | None = None) -> dict: if session is not None: try: timeout_hours = int(self._get_config_value(session, "logistics_timeout_hours", "48")) timeout_before = datetime.now() - timedelta(hours=timeout_hours) orders = self.repository.list_logistics_timeout_candidates(session, timeout_before) created_count = 0 for order in orders: receiver_user_id = (order.salesman_id or 0) if receiver_user_id <= 0: continue existed = self.repository.find_active_reminder( session, "logistics_timeout", "sales_order", order.id, receiver_user_id, ) if existed is not None: continue self.repository.create_reminder( session, { "reminder_type": "logistics_timeout", "biz_type": "sales_order", "biz_id": order.id, "receiver_user_id": receiver_user_id, "reminder_title": f"物流超时提醒 - {order.order_no}", "reminder_content": f"订单 {order.order_no} 超过 {timeout_hours} 小时未完成物流流转,请及时跟进。", "status": "pending", "sent_at": datetime.now(), }, ) created_count += 1 session.commit() return {"checked": True, "created_count": created_count} except AppException: session.rollback() raise except SQLAlchemyError: session.rollback() return {"checked": True, "created_count": 0} def _get_config_value(self, session: Session, config_key: str, default: str) -> str: config = self.config_repository.get_by_key(session, config_key) return config.config_value if config is not None and config.config_value else default def _map_reminder(self, reminder) -> dict: return { "reminder_id": reminder.id, "title": reminder.reminder_title, "type": reminder.reminder_type, "biz_type": reminder.biz_type, "biz_id": reminder.biz_id, "receiver_user_id": reminder.receiver_user_id, "status": reminder.status, "sent_at": reminder.sent_at.strftime("%Y-%m-%d %H:%M:%S") if reminder.sent_at else "", "created_at": reminder.created_at.strftime("%Y-%m-%d %H:%M:%S") if reminder.created_at else "", } reminder_service = ReminderService()