89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
|
|
"""全局自检:路由、数据、财务展示。用法: 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())
|