from apscheduler.schedulers.asyncio import AsyncIOScheduler from sqlalchemy import select, and_ from datetime import datetime, timedelta from app.core.database import async_session from app.core.config import get_settings from app.models.models import ( Debt, Notification, NotificationType, User, DebtStatus ) from app.services.notification_service import create_notification from app.services.email_service import send_email, build_repayment_reminder_html settings = get_settings() scheduler = AsyncIOScheduler() async def check_upcoming_repayments(): async with async_session() as db: now = datetime.utcnow() reminder_days = settings.REMINDER_DAYS_BEFORE check_window = now + timedelta(days=reminder_days) result = await db.execute( select(Debt).where( and_( Debt.status == DebtStatus.ACTIVE.value, Debt.schedule_data.isnot(None), ) ) ) debts = result.scalars().all() for debt in debts: schedule = debt.schedule_data or [] paid_periods = debt.paid_periods or [] user_result = await db.execute(select(User).where(User.id == debt.user_id)) user = user_result.scalar_one_or_none() if not user: continue for item in schedule: p = item.get("p") if p in paid_periods: continue try: due_date = datetime.strptime(item["date"], "%Y-%m-%d") except (ValueError, KeyError): continue if due_date > check_window: continue is_overdue = due_date < now amount_yuan = item.get("pay", 0) days_left = (due_date - now).days if is_overdue: ntype = NotificationType.REPAYMENT_OVERDUE.value title = f"逾期提醒:{debt.creditor_name} 第{p}期还款已逾期" msg = f"您在 {debt.creditor_name} 的第{p}期 ¥{amount_yuan:,.2f} 还款已于 {item['date']} 逾期,请尽快处理。" else: ntype = NotificationType.REPAYMENT_DUE.value title = f"还款提醒:{debt.creditor_name} 第{p}期将在 {days_left} 天后到期" msg = f"您在 {debt.creditor_name} 的第{p}期 ¥{amount_yuan:,.2f} 将于 {item['date']} 到期,请按时还款。" existing = await db.execute( select(Notification).where( and_( Notification.user_id == user.id, Notification.related_debt_id == debt.id, Notification.title == title, ) ) ) if existing.scalar_one_or_none(): continue await create_notification( db, user.id, title, msg, ntype, related_debt_id=debt.id, ) if user.email and "@" in user.email: html = build_repayment_reminder_html( user.nickname or user.email, debt.creditor_name, amount_yuan, item["date"], days_left, is_overdue, ) await send_email(user.email, title, html) await db.commit() def start_scheduler(): scheduler.add_job( check_upcoming_repayments, "interval", minutes=settings.NOTIFICATION_CHECK_INTERVAL_MINUTES, id="repayment_check", replace_existing=True, ) scheduler.start()