v2.1: 新增通知提醒功能和安全增强
功能: - 通知提醒系统: 还款日提醒、逾期提醒、系统消息横幅、登录时通知 - 通知中心UI: 铃铛图标 + 下拉列表 + 未读徽章 - 定时任务: APScheduler 每小时检查即将到期的还款 - 邮件通知: SMTP 发送还款提醒和安全警告 - 异常登录通知: 3/5/10次失败触发网页和邮件通知 安全: - 登录安全告警: 异常登录次数达到阈值时发送通知 - 密码重置链接: 一次性使用, 5分钟有效期 - 登录频率限制: IP级和账户级限制 技术: - 后端: FastAPI + SQLAlchemy + PostgreSQL - 前端: 纯 HTML/CSS/JS - 部署: Docker Compose (新增 build 配置)
This commit is contained in:
79
backend/app/services/calculator.py
Normal file
79
backend/app/services/calculator.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
def calculate_monthly_rate(interest_rate: float, rate_type: str) -> float:
|
||||
if rate_type == "annual":
|
||||
return interest_rate / 100 / 12
|
||||
return interest_rate / 100
|
||||
|
||||
|
||||
def generate_equal_installment_plan(
|
||||
amount: int, interest_rate: float, rate_type: str,
|
||||
period_count: int, start_date: datetime
|
||||
) -> list[dict]:
|
||||
mr = calculate_monthly_rate(interest_rate, rate_type)
|
||||
if mr == 0:
|
||||
principal_each = amount // period_count
|
||||
plans = []
|
||||
for i in range(period_count):
|
||||
due = start_date + relativedelta(months=i + 1)
|
||||
p = principal_each if i < period_count - 1 else amount - principal_each * (period_count - 1)
|
||||
plans.append({"due_date": due, "principal_due": p, "interest_due": 0, "total_due": p})
|
||||
return plans
|
||||
|
||||
mp = amount * mr * (1 + mr) ** period_count / ((1 + mr) ** period_count - 1)
|
||||
plans = []
|
||||
remaining = amount
|
||||
for i in range(period_count):
|
||||
due = start_date + relativedelta(months=i + 1)
|
||||
interest = int(remaining * mr)
|
||||
principal = int(mp) - interest
|
||||
if i == period_count - 1:
|
||||
principal = remaining
|
||||
interest = int(remaining * mr)
|
||||
remaining -= principal
|
||||
total = principal + interest
|
||||
plans.append({"due_date": due, "principal_due": principal, "interest_due": interest, "total_due": total})
|
||||
return plans
|
||||
|
||||
|
||||
def generate_interest_first_plan(
|
||||
amount: int, interest_rate: float, rate_type: str,
|
||||
period_count: int, start_date: datetime
|
||||
) -> list[dict]:
|
||||
mr = calculate_monthly_rate(interest_rate, rate_type)
|
||||
plans = []
|
||||
for i in range(period_count):
|
||||
due = start_date + relativedelta(months=i + 1)
|
||||
interest = int(amount * mr)
|
||||
principal = amount if i == period_count - 1 else 0
|
||||
total = principal + interest
|
||||
plans.append({"due_date": due, "principal_due": principal, "interest_due": interest, "total_due": total})
|
||||
return plans
|
||||
|
||||
|
||||
def generate_lump_sum_plan(
|
||||
amount: int, interest_rate: float, rate_type: str,
|
||||
period_count: int, period_unit: str, start_date: datetime
|
||||
) -> list[dict]:
|
||||
if period_unit == "year":
|
||||
total_interest = int(amount * interest_rate / 100 * period_count)
|
||||
else:
|
||||
total_interest = int(amount * interest_rate / 100 / 12 * period_count)
|
||||
due = start_date + relativedelta(months=period_count) if period_unit == "month" else start_date + relativedelta(years=period_count)
|
||||
return [{"due_date": due, "principal_due": amount, "interest_due": total_interest, "total_due": amount + total_interest}]
|
||||
|
||||
|
||||
def generate_repayment_plan(
|
||||
amount: int, interest_rate: float, rate_type: str,
|
||||
repayment_method: str, period_count: int, period_unit: str,
|
||||
start_date: datetime
|
||||
) -> list[dict]:
|
||||
if repayment_method == "equal_installment":
|
||||
return generate_equal_installment_plan(amount, interest_rate, rate_type, period_count, start_date)
|
||||
elif repayment_method == "interest_first":
|
||||
return generate_interest_first_plan(amount, interest_rate, rate_type, period_count, start_date)
|
||||
elif repayment_method == "lump_sum":
|
||||
return generate_lump_sum_plan(amount, interest_rate, rate_type, period_count, period_unit, start_date)
|
||||
return []
|
||||
Reference in New Issue
Block a user