115 lines
3.9 KiB
Python
115 lines
3.9 KiB
Python
"""仪表盘路由"""
|
|
from datetime import date, datetime
|
|
|
|
from flask import render_template
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
from models import (
|
|
db, Student, Course, Teacher, Class_, StudentAccount,
|
|
MonthlySnapshot, ConsumptionRecord,
|
|
)
|
|
from utils import login_required
|
|
from keshi_display import get_latest_period, snapshot_lessons_display
|
|
from keshi_stats import period_overview, monthly_trend_from_snapshots
|
|
import business_rules as rules
|
|
|
|
|
|
def register_routes(app):
|
|
|
|
@app.route('/', endpoint='dashboard')
|
|
@app.route('/dashboard', endpoint='dashboard')
|
|
@login_required
|
|
def dashboard():
|
|
today = date.today()
|
|
py, pm = get_latest_period()
|
|
period_label = f'{py}-{pm:02d}' if py and pm else ''
|
|
|
|
total_students = Student.query.filter_by(status=1).count()
|
|
total_courses = Course.query.filter_by(status=1).count()
|
|
total_teachers = Teacher.query.filter_by(status=1).count()
|
|
total_classes = Class_.query.filter_by(status=1).count()
|
|
|
|
month_recharge = 0.0
|
|
month_consumption = 0.0
|
|
if py and pm:
|
|
ov = period_overview(py, pm)
|
|
month_recharge = ov['recharge']
|
|
month_consumption = ov['consumed_hours']
|
|
|
|
today_consumption = ConsumptionRecord.query.filter(
|
|
ConsumptionRecord.consume_date == today,
|
|
).count()
|
|
|
|
recent_recharges = []
|
|
|
|
threshold = rules.LOW_HOURS_WARNING_THRESHOLD
|
|
low_hours_accounts = []
|
|
if py and pm:
|
|
snaps = (
|
|
MonthlySnapshot.query.options(
|
|
joinedload(MonthlySnapshot.account).joinedload(StudentAccount.student),
|
|
joinedload(MonthlySnapshot.account).joinedload(StudentAccount.course),
|
|
)
|
|
.filter(MonthlySnapshot.year == py, MonthlySnapshot.month == pm)
|
|
.all()
|
|
)
|
|
candidates = []
|
|
for s in snaps:
|
|
acc = s.account
|
|
if not acc or not acc.student:
|
|
continue
|
|
disp = snapshot_lessons_display(s, acc)
|
|
total_h = disp.get('end_total_lessons')
|
|
if total_h is None:
|
|
continue
|
|
th = float(total_h)
|
|
if 0 < th <= threshold:
|
|
candidates.append((acc, th, disp))
|
|
candidates.sort(key=lambda x: x[1])
|
|
low_hours_accounts = candidates[:8]
|
|
|
|
recent_consumptions = ConsumptionRecord.query.order_by(
|
|
ConsumptionRecord.consume_date.desc(),
|
|
).limit(8).all()
|
|
|
|
trend = monthly_trend_from_snapshots()
|
|
monthly_chart = []
|
|
max_amount = 1.0
|
|
for item in trend[-6:]:
|
|
amt = float(item['recharge'])
|
|
max_amount = max(max_amount, amt)
|
|
monthly_chart.append({
|
|
'label': item['label'],
|
|
'amount': amt,
|
|
'is_current': item['month'] == period_label,
|
|
})
|
|
if not monthly_chart:
|
|
max_amount = 1.0
|
|
|
|
hour = datetime.now().hour
|
|
if hour < 12:
|
|
greeting = '早上好'
|
|
elif hour < 18:
|
|
greeting = '下午好'
|
|
else:
|
|
greeting = '晚上好'
|
|
|
|
return render_template(
|
|
'dashboard.html',
|
|
greeting=greeting,
|
|
monthly_chart=monthly_chart,
|
|
chart_max=max_amount,
|
|
recent_consumptions=recent_consumptions,
|
|
total_students=total_students,
|
|
total_courses=total_courses,
|
|
total_teachers=total_teachers,
|
|
total_classes=total_classes,
|
|
month_recharge=float(month_recharge),
|
|
month_consumption=float(month_consumption),
|
|
today_consumption=today_consumption,
|
|
recent_recharges=recent_recharges,
|
|
low_hours_accounts=low_hours_accounts,
|
|
period_label=period_label,
|
|
)
|