fix: 生成月度快照数据修复余额显示
Coze-Commit-Type: user Coze-User-ID: 3722323274763196 Coze-Conversation-ID: 5260473
This commit is contained in:
212
xuexiao/generate_snapshots.py
Normal file
212
xuexiao/generate_snapshots.py
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
生成月度课时快照(MonthlySnapshot)
|
||||||
|
基于现有数据,为每个账户生成当前月份的快照
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
os.environ['DB_TYPE'] = 'sqlite'
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from config import Config
|
||||||
|
from models import db, StudentAccount, RechargeRecord, ConsumptionRecord, MonthlySnapshot
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def generate_snapshots():
|
||||||
|
"""为每个学员账户生成月度快照"""
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Config)
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
# 清空现有快照
|
||||||
|
MonthlySnapshot.query.delete()
|
||||||
|
|
||||||
|
accounts = StudentAccount.query.all()
|
||||||
|
print(f"开始为 {len(accounts)} 个账户生成快照...")
|
||||||
|
|
||||||
|
# 获取所有充值记录并按账户分组
|
||||||
|
recharges_by_account = {}
|
||||||
|
all_recharges = RechargeRecord.query.all()
|
||||||
|
for r in all_recharges:
|
||||||
|
key = (r.student_id, r.course_id)
|
||||||
|
if key not in recharges_by_account:
|
||||||
|
recharges_by_account[key] = []
|
||||||
|
recharges_by_account[key].append(r)
|
||||||
|
|
||||||
|
# 获取所有消课记录并按账户和年月分组
|
||||||
|
consumptions_by_account = {}
|
||||||
|
all_consumptions = ConsumptionRecord.query.all()
|
||||||
|
for c in all_consumptions:
|
||||||
|
key = (c.student_id, c.course_id)
|
||||||
|
if key not in consumptions_by_account:
|
||||||
|
consumptions_by_account[key] = []
|
||||||
|
consumptions_by_account[key].append(c)
|
||||||
|
|
||||||
|
# 按年月统计消课
|
||||||
|
def get_yearly_consumption(student_id, course_id, year, month):
|
||||||
|
"""获取某年某月的消课量"""
|
||||||
|
key = (student_id, course_id)
|
||||||
|
consumptions = consumptions_by_account.get(key, [])
|
||||||
|
total = 0.0
|
||||||
|
for c in consumptions:
|
||||||
|
if c.consume_date and c.consume_date.year == year and c.consume_date.month == month:
|
||||||
|
total += float(c.hours_consumed or 0)
|
||||||
|
return total
|
||||||
|
|
||||||
|
def get_yearly_recharge(student_id, course_id, year, month):
|
||||||
|
"""获取某年某月的充值金额"""
|
||||||
|
key = (student_id, course_id)
|
||||||
|
recharges = recharges_by_account.get(key, [])
|
||||||
|
total_amount = 0.0
|
||||||
|
total_lessons = 0.0
|
||||||
|
total_gift = 0.0
|
||||||
|
for r in recharges:
|
||||||
|
if r.created_at and r.created_at.year == year and r.created_at.month == month:
|
||||||
|
total_amount += float(r.amount or 0)
|
||||||
|
total_lessons += float(r.normal_hours or 0)
|
||||||
|
total_gift += float(r.gifted_hours or 0)
|
||||||
|
return total_amount, total_lessons, total_gift
|
||||||
|
|
||||||
|
def get_cumulative_consumption_until(student_id, course_id, year, month):
|
||||||
|
"""获取截至某年某月的累计消课量"""
|
||||||
|
key = (student_id, course_id)
|
||||||
|
consumptions = consumptions_by_account.get(key, [])
|
||||||
|
total = 0.0
|
||||||
|
for c in consumptions:
|
||||||
|
if c.consume_date:
|
||||||
|
# 计算到指定月份为止
|
||||||
|
if (c.consume_date.year < year) or \
|
||||||
|
(c.consume_date.year == year and c.consume_date.month <= month):
|
||||||
|
total += float(c.hours_consumed or 0)
|
||||||
|
return total
|
||||||
|
|
||||||
|
def get_total_recharge(student_id, course_id):
|
||||||
|
"""获取累计充值总金额"""
|
||||||
|
key = (student_id, course_id)
|
||||||
|
recharges = recharges_by_account.get(key, [])
|
||||||
|
return sum(float(r.amount or 0) for r in recharges)
|
||||||
|
|
||||||
|
for idx, account in enumerate(accounts):
|
||||||
|
if (idx + 1) % 50 == 0:
|
||||||
|
print(f" 处理进度: {idx + 1}/{len(accounts)}")
|
||||||
|
|
||||||
|
student_id = account.student_id
|
||||||
|
course_id = account.course_id
|
||||||
|
|
||||||
|
# 获取总充值金额
|
||||||
|
total_recharge_amount = get_total_recharge(student_id, course_id)
|
||||||
|
|
||||||
|
# 计算截至2026年4月的累计消课
|
||||||
|
cumulative_consumed = get_cumulative_consumption_until(student_id, course_id, 2026, 4)
|
||||||
|
|
||||||
|
# 当前剩余课时
|
||||||
|
current_remaining_normal = float(account.normal_hours or 0)
|
||||||
|
current_remaining_gift = float(account.gifted_hours or 0)
|
||||||
|
|
||||||
|
# 计算总购买课时
|
||||||
|
total_bought_lessons = current_remaining_normal + current_remaining_gift + cumulative_consumed
|
||||||
|
|
||||||
|
# 单价
|
||||||
|
unit_price = float(account.unit_price or 0)
|
||||||
|
if unit_price == 0 and total_bought_lessons > 0:
|
||||||
|
unit_price = total_recharge_amount / total_bought_lessons
|
||||||
|
|
||||||
|
# 月末余额 = 总充值 - 已消课金额
|
||||||
|
end_balance = total_recharge_amount - cumulative_consumed * unit_price
|
||||||
|
|
||||||
|
# 1. 生成 2026年4月 快照(期末快照)
|
||||||
|
consumed_apr = get_yearly_consumption(student_id, course_id, 2026, 4)
|
||||||
|
|
||||||
|
# 月初余额
|
||||||
|
prev_balance_apr = total_recharge_amount - (cumulative_consumed - consumed_apr) * unit_price
|
||||||
|
prev_lessons_apr = total_bought_lessons - cumulative_consumed + consumed_apr - current_remaining_gift
|
||||||
|
|
||||||
|
snap_apr = MonthlySnapshot(
|
||||||
|
account_id=account.id,
|
||||||
|
year=2026,
|
||||||
|
month=4,
|
||||||
|
seq_no=1,
|
||||||
|
prev_lessons=max(0, prev_lessons_apr),
|
||||||
|
prev_gift_lessons=current_remaining_gift,
|
||||||
|
prev_total_lessons=max(0, prev_lessons_apr) + current_remaining_gift,
|
||||||
|
prev_balance=prev_balance_apr,
|
||||||
|
new_signup_lessons=0,
|
||||||
|
new_signup_gift_lessons=0,
|
||||||
|
new_signup_amount=0,
|
||||||
|
renewal_lessons=0,
|
||||||
|
renewal_gift_lessons=0,
|
||||||
|
renewal_amount=0,
|
||||||
|
consumed_lessons=consumed_apr,
|
||||||
|
end_lessons=current_remaining_normal,
|
||||||
|
end_gift_lessons=current_remaining_gift,
|
||||||
|
end_total_lessons=current_remaining_normal + current_remaining_gift,
|
||||||
|
end_balance=end_balance,
|
||||||
|
unit_price=unit_price,
|
||||||
|
notes='From Excel import'
|
||||||
|
)
|
||||||
|
db.session.add(snap_apr)
|
||||||
|
|
||||||
|
# 2. 生成 2026年5月 快照
|
||||||
|
consumed_may = get_yearly_consumption(student_id, course_id, 2026, 5)
|
||||||
|
cumulative_consumed_may = get_cumulative_consumption_until(student_id, course_id, 2026, 5)
|
||||||
|
|
||||||
|
end_balance_may = total_recharge_amount - cumulative_consumed_may * unit_price
|
||||||
|
prev_balance_may = end_balance
|
||||||
|
prev_lessons_may = current_remaining_normal + (consumed_apr if consumed_apr > 0 else 0)
|
||||||
|
|
||||||
|
snap_may = MonthlySnapshot(
|
||||||
|
account_id=account.id,
|
||||||
|
year=2026,
|
||||||
|
month=5,
|
||||||
|
seq_no=1,
|
||||||
|
prev_lessons=current_remaining_normal,
|
||||||
|
prev_gift_lessons=current_remaining_gift,
|
||||||
|
prev_total_lessons=current_remaining_normal + current_remaining_gift,
|
||||||
|
prev_balance=end_balance,
|
||||||
|
new_signup_lessons=0,
|
||||||
|
new_signup_gift_lessons=0,
|
||||||
|
new_signup_amount=0,
|
||||||
|
renewal_lessons=0,
|
||||||
|
renewal_gift_lessons=0,
|
||||||
|
renewal_amount=0,
|
||||||
|
consumed_lessons=consumed_may,
|
||||||
|
end_lessons=current_remaining_normal,
|
||||||
|
end_gift_lessons=current_remaining_gift,
|
||||||
|
end_total_lessons=current_remaining_normal + current_remaining_gift,
|
||||||
|
end_balance=end_balance_may,
|
||||||
|
unit_price=unit_price,
|
||||||
|
notes='From Excel import'
|
||||||
|
)
|
||||||
|
db.session.add(snap_may)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
print(f"完成! 共生成 {MonthlySnapshot.query.count()} 条月度快照")
|
||||||
|
|
||||||
|
# 显示统计
|
||||||
|
print("\n=== 按月份统计 ===")
|
||||||
|
from sqlalchemy import func
|
||||||
|
stats = db.session.query(
|
||||||
|
MonthlySnapshot.year,
|
||||||
|
MonthlySnapshot.month,
|
||||||
|
func.count(MonthlySnapshot.id)
|
||||||
|
).group_by(MonthlySnapshot.year, MonthlySnapshot.month).order_by(
|
||||||
|
MonthlySnapshot.year.desc(), MonthlySnapshot.month.desc()
|
||||||
|
).all()
|
||||||
|
|
||||||
|
for y, m, c in stats:
|
||||||
|
print(f" {y}-{m:02d}: {c} 条记录")
|
||||||
|
|
||||||
|
# 验证数据
|
||||||
|
print("\n=== 样本数据验证 ===")
|
||||||
|
for snap in MonthlySnapshot.query.filter_by(year=2026, month=4).limit(5).all():
|
||||||
|
print(f" Account {snap.account_id}: {snap.year}-{snap.month:02d}")
|
||||||
|
print(f" 月末正课: {snap.end_lessons}, 赠课: {snap.end_gift_lessons}, 总课时: {snap.end_total_lessons}")
|
||||||
|
print(f" 月末余额: {snap.end_balance:.2f}, 单价: {snap.unit_price:.4f}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
generate_snapshots()
|
||||||
Binary file not shown.
Reference in New Issue
Block a user