"""全局自检:路由、数据、财务展示。用法: python scripts/global_check.py""" import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) ISSUES = [] def issue(level, code, msg): ISSUES.append((level, code, msg)) print(f'[{level}] {code}: {msg}') def main(): from app import create_app from models import ( db, StudentAccount, Course, MonthlySnapshot, RechargeRecord, ConsumptionRecord, RefundRecord, ) app = create_app() with app.app_context(): # 数据健康 n_recharge = RechargeRecord.query.count() n_consume = ConsumptionRecord.query.count() n_snap = MonthlySnapshot.query.count() n_course_price = Course.query.filter(Course.price_per_hour > 0).count() n_orig = StudentAccount.query.filter( StudentAccount.original_price_per_lesson > 0, ).count() if n_snap > 100 and n_recharge == 0 and n_consume == 0: issue( 'INFO', 'DATA-01', '业务数据主要来自 Excel 快照(充值/消课表为空),统计页应看「课时核对表」月份', ) if n_snap > 0 and n_orig == 0: issue( 'WARN', 'DATA-02', '账户未写入 original_price_per_lesson;已支持从快照单价回填,请重启 app', ) if n_course_price == 0: issue( 'WARN', 'DATA-03', '课程表 price_per_hour 均为 0,建议在「课程管理」维护原价单价', ) # 路由冒烟 client = app.test_client() client.post('/login', data={'username': 'admin', 'password': 'admin123'}) paths = [ '/students', '/students/detail/1', '/statistics', '/keshibiao/snapshots?year=2026&month=4', ] for p in paths: r = client.get(p, follow_redirects=True) if r.status_code >= 500: issue('FAIL', 'HTTP', f'{p} -> {r.status_code}') # 财务计算抽样 from finance_display import account_refund_display, build_refund_preview from keshi_display import attach_latest_snapshots, snapshot_lessons_display, get_latest_period py, pm = get_latest_period() acc = StudentAccount.query.first() if acc: snap = attach_latest_snapshots([acc], py, pm).get(acc.id) lg = snapshot_lessons_display(snap, acc) if snap else {} d = account_refund_display(acc, lg, snap) if d['kind'] == 'no_price' and snap and float(snap.unit_price or 0) > 0: issue( 'WARN', 'FIN-01', '快照有单价但应退仍为「-」,请重启以执行单价回填', ) print('\n=== 汇总 ===') if not ISSUES: print('未发现阻塞性问题。') else: fails = [i for i in ISSUES if i[0] == 'FAIL'] print(f'共 {len(ISSUES)} 条(失败 {len(fails)})') return 1 if any(i[0] == 'FAIL' for i in ISSUES) else 0 if __name__ == '__main__': sys.exit(main())