81 lines
2.6 KiB
Python
81 lines
2.6 KiB
Python
"""续费检查服务
|
||
|
||
检查即将到期的资产,生成提醒消息并通过通知渠道发送。
|
||
"""
|
||
|
||
from datetime import date, timedelta
|
||
|
||
from sqlmodel import Session, select
|
||
|
||
from app.core.config import settings
|
||
from app.models.asset import Asset, AssetStatus
|
||
from app.services import notification_service
|
||
|
||
# 已注销的资产不参与续费提醒
|
||
_SKIP_STATUS = {AssetStatus.CANCELLED}
|
||
|
||
|
||
def get_expiring_assets(session: Session, threshold_days: int) -> list:
|
||
"""返回 threshold_days 天内到期的资产列表 [(asset, days), ...],按剩余天数升序
|
||
|
||
性能优化:日期范围与状态过滤均下推到 SQL 层。
|
||
"""
|
||
today = date.today()
|
||
deadline = today + timedelta(days=threshold_days)
|
||
assets = session.exec(
|
||
select(Asset).where(
|
||
Asset.expiry_date.is_not(None),
|
||
Asset.expiry_date >= today,
|
||
Asset.expiry_date <= deadline,
|
||
Asset.status.notin_(_SKIP_STATUS),
|
||
)
|
||
).all()
|
||
expiring = [(asset, (asset.expiry_date - today).days) for asset in assets]
|
||
expiring.sort(key=lambda x: x[1])
|
||
return expiring
|
||
|
||
|
||
def build_renewal_message(expiring: list, threshold_days: int) -> str:
|
||
"""生成续费提醒消息(纯文本,兼容 Telegram/邮件)"""
|
||
if not expiring:
|
||
return ""
|
||
lines = [f"⏰ 资产续费提醒({threshold_days} 天内到期,共 {len(expiring)} 项)", ""]
|
||
for asset, days in expiring:
|
||
tag = "(已过期)" if days == 0 else f"({days} 天后)"
|
||
lines.append(f"• {asset.name} [{asset.provider}] {tag} 到期 {asset.expiry_date}")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def check_renewals(session: Session, threshold_days: int = None, send: bool = True) -> dict:
|
||
"""检查续费并(可选)发送提醒
|
||
|
||
threshold_days: 提醒阈值天数,默认取配置 RENEWAL_THRESHOLD_DAYS
|
||
send: 是否实际发送通知(False 时仅返回预览)
|
||
"""
|
||
if threshold_days is None:
|
||
threshold_days = settings.RENEWAL_THRESHOLD_DAYS
|
||
|
||
expiring = get_expiring_assets(session, threshold_days)
|
||
message = build_renewal_message(expiring, threshold_days)
|
||
|
||
result = {
|
||
"threshold_days": threshold_days,
|
||
"expiring_count": len(expiring),
|
||
"expiring": [
|
||
{
|
||
"name": asset.name,
|
||
"provider": asset.provider,
|
||
"days": days,
|
||
"expiry_date": str(asset.expiry_date),
|
||
}
|
||
for asset, days in expiring
|
||
],
|
||
"message": message,
|
||
}
|
||
|
||
if send and message:
|
||
result["notifications"] = notification_service.notify(message)
|
||
else:
|
||
result["notifications"] = []
|
||
return result
|