Django中基于ExpectedPayment模型计算未来12个月预期付款总额
预期付款月度总额计算方案
四类付款计入规则
先明确每类付款是否计入当前统计月份的判断标准,所有规则均默认排除date_start为空的无效数据:
- 一次性付款(period=4):
date_start落在当前统计月份的自然月范围内,即计入当月 - 月度付款(period=1):当前统计月首日 >=
date_start,且(当前统计月首日 <=date_end或date_end为空),即计入当月 - 季度付款(period=2):
- 先满足上述月度付款的时间范围要求
- 计算当前统计月与
date_start所在月的总月份差:(统计年 - 起始年)*12 + (统计月 - 起始月),差值模3等于0即属于付款月,计入当月
该计算方式避开了不同月份天数、闰年的影响,完全符合「从date_start开始每3个月付一次」的要求,比如date_start为2月,2、5、8、11月均会被判定为付款月
- 年度付款(period=3):
- 先满足上述月度付款的时间范围要求
- 总月份差模12等于0即属于付款月,计入当月
代码实现
依赖导入
from datetime import datetime from dateutil.relativedelta import relativedelta from django.db.models import Sum, Q
生成未来12个月的统计基准日期
# 生成未来12个月每个月的首日,作为统计基准 month_starts = [] current_month_start = datetime.today().replace(day=1, hour=0, minute=0, second=0, microsecond=0).date() for i in range(12): month_starts.append(current_month_start + relativedelta(months=i))
单月总额计算函数
def calculate_single_month_total(stat_month_start): total_amount = 0 # 统计当月月度付款总额 monthly_total = ExpectedPayment.objects.filter( period=1, date_start__isnull=False, date_start__lte=stat_month_start, Q(date_end__gte=stat_month_start) | Q(date_end__isnull=True) ).aggregate(total=Sum("amount"))["total"] or 0 total_amount += monthly_total # 统计当月一次性付款总额 stat_month_end = stat_month_start + relativedelta(months=1, days=-1) onetime_total = ExpectedPayment.objects.filter( period=4, date_start__isnull=False, date_start__range=(stat_month_start, stat_month_end) ).aggregate(total=Sum("amount"))["total"] or 0 total_amount += onetime_total # 统计当月季度付款总额 quarter_payments = ExpectedPayment.objects.filter( period=2, date_start__isnull=False, date_start__lte=stat_month_start, Q(date_end__gte=stat_month_start) | Q(date_end__isnull=True) ) quarter_total = 0 for payment in quarter_payments: start_y = payment.date_start.year start_m = payment.date_start.month month_diff = (stat_month_start.year - start_y) * 12 + (stat_month_start.month - start_m) if month_diff % 3 == 0: quarter_total += payment.amount total_amount += quarter_total # 统计当年度付款总额 year_payments = ExpectedPayment.objects.filter( period=3, date_start__isnull=False, date_start__lte=stat_month_start, Q(date_end__gte=stat_month_start) | Q(date_end__isnull=True) ) year_total = 0 for payment in year_payments: start_y = payment.date_start.year start_m = payment.date_start.month month_diff = (stat_month_start.year - start_y) * 12 + (stat_month_start.month - start_m) if month_diff % 12 == 0: year_total += payment.amount total_amount += year_total return total_amount
批量计算未来12个月总额
# 返回结构为 年月字符串: 总额 的字典 monthly_payment_stats = {} for month_start in month_starts: month_key = month_start.strftime("%Y-%m") monthly_payment_stats[month_key] = calculate_single_month_total(month_start)
性能优化建议
如果数据量较大(万级以上),可以将季度、年度的月份差判断逻辑迁移到数据库层,通过annotate结合ExtractYear、ExtractMonth数据库函数计算差值,无需拉取全量数据到内存循环判断,可大幅提升查询效率。
内容的提问来源于stack exchange,提问作者Spoontech
相关产品推荐
相关产品推荐

