141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
|
|
"""表格列排序:解析参数、SQL 排序、内存排序"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from typing import Any, Callable, Optional
|
|||
|
|
|
|||
|
|
from sqlalchemy import func
|
|||
|
|
from sqlalchemy.orm import Query
|
|||
|
|
|
|||
|
|
from models import MonthlySnapshot, Student, Course
|
|||
|
|
|
|||
|
|
|
|||
|
|
def parse_sort(
|
|||
|
|
sort: Optional[str],
|
|||
|
|
order: Optional[str],
|
|||
|
|
allowed: set[str],
|
|||
|
|
default: str = '',
|
|||
|
|
) -> tuple[str, str]:
|
|||
|
|
s = (sort or '').strip()
|
|||
|
|
o = (order or 'asc').strip().lower()
|
|||
|
|
if o not in ('asc', 'desc'):
|
|||
|
|
o = 'asc'
|
|||
|
|
if s not in allowed:
|
|||
|
|
s = default
|
|||
|
|
return s, o
|
|||
|
|
|
|||
|
|
|
|||
|
|
def toggle_order(current_sort: str, field: str, current_order: str) -> str:
|
|||
|
|
return 'desc' if current_sort == field and current_order == 'asc' else 'asc'
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _course_label(acc) -> str:
|
|||
|
|
if not acc or not getattr(acc, 'course', None):
|
|||
|
|
return ''
|
|||
|
|
c = acc.course
|
|||
|
|
return (c.course_code or c.name or '').lower()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _num(val) -> float:
|
|||
|
|
if val is None:
|
|||
|
|
return float('-inf')
|
|||
|
|
try:
|
|||
|
|
return float(val)
|
|||
|
|
except (TypeError, ValueError):
|
|||
|
|
return float('-inf')
|
|||
|
|
|
|||
|
|
|
|||
|
|
def sort_rows_in_memory(
|
|||
|
|
rows: list,
|
|||
|
|
sort: str,
|
|||
|
|
order: str,
|
|||
|
|
key_map: dict[str, Callable[[Any], Any]],
|
|||
|
|
) -> list:
|
|||
|
|
if not sort or sort not in key_map:
|
|||
|
|
return rows
|
|||
|
|
reverse = order == 'desc'
|
|||
|
|
return sorted(rows, key=key_map[sort], reverse=reverse)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def apply_snapshot_query_sort(
|
|||
|
|
query: Query,
|
|||
|
|
sort: str,
|
|||
|
|
order: str,
|
|||
|
|
) -> Query:
|
|||
|
|
"""课时余额列表:数据库分页前排序"""
|
|||
|
|
desc = order == 'desc'
|
|||
|
|
col = func.coalesce
|
|||
|
|
|
|||
|
|
mapping = {
|
|||
|
|
'student': Student.name,
|
|||
|
|
'course': Course.course_code,
|
|||
|
|
'end_lessons': col(MonthlySnapshot.end_lessons, MonthlySnapshot.prev_lessons),
|
|||
|
|
'end_gift': col(MonthlySnapshot.end_gift_lessons, 0),
|
|||
|
|
'end_total': col(MonthlySnapshot.end_total_lessons, 0),
|
|||
|
|
'end_balance': col(MonthlySnapshot.end_balance, MonthlySnapshot.prev_balance),
|
|||
|
|
'unit_price': col(MonthlySnapshot.unit_price, 0),
|
|||
|
|
'consumed': col(MonthlySnapshot.consumed_lessons, 0),
|
|||
|
|
}
|
|||
|
|
expr = mapping.get(sort)
|
|||
|
|
if expr is None:
|
|||
|
|
return query.order_by(
|
|||
|
|
MonthlySnapshot.seq_no.asc(),
|
|||
|
|
Student.name.asc(),
|
|||
|
|
MonthlySnapshot.id.asc(),
|
|||
|
|
)
|
|||
|
|
return query.order_by(expr.desc() if desc else expr.asc(), MonthlySnapshot.id.asc())
|
|||
|
|
|
|||
|
|
|
|||
|
|
def snapshot_row_sort_keys(disp: dict, snap: MonthlySnapshot) -> dict[str, Callable]:
|
|||
|
|
"""内存排序用(预览表、账户流水等)"""
|
|||
|
|
return {
|
|||
|
|
'end_lessons': lambda r: _num(
|
|||
|
|
(r.get('disp') or {}).get('end_lessons')
|
|||
|
|
if isinstance(r.get('disp'), dict)
|
|||
|
|
else getattr(r.get('snap'), 'end_lessons', None),
|
|||
|
|
),
|
|||
|
|
'end_gift': lambda r: _num((r.get('disp') or {}).get('end_gift_lessons')),
|
|||
|
|
'end_total': lambda r: _num((r.get('disp') or {}).get('end_total_lessons')),
|
|||
|
|
'end_balance': lambda r: _num((r.get('disp') or {}).get('end_balance')),
|
|||
|
|
'unit_price': lambda r: _num((r.get('disp') or {}).get('unit_price')),
|
|||
|
|
'consumed': lambda r: _num(getattr(r.get('snap'), 'consumed_lessons', None)),
|
|||
|
|
'student': lambda r: (getattr(getattr(r.get('acc'), 'student', None), 'name', '') or ''),
|
|||
|
|
'course': lambda r: _course_label(r.get('acc')),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def account_ledger_sort_keys() -> dict[str, Callable]:
|
|||
|
|
return {
|
|||
|
|
'course': lambda x: _course_label(x[0]),
|
|||
|
|
'end_lessons': lambda x: _num(x[1].get('end_lessons')),
|
|||
|
|
'end_gift': lambda x: _num(x[1].get('end_gift_lessons')),
|
|||
|
|
'end_total': lambda x: _num(x[1].get('end_total_lessons')),
|
|||
|
|
'end_balance': lambda x: _num(x[1].get('end_balance')),
|
|||
|
|
'unit_price': lambda x: _num(x[1].get('unit_price')),
|
|||
|
|
'refund_due': lambda x: _num(x[2].get('refund_due') if x[2].get('has_original') else -1),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def preview_row_sort_keys() -> dict[str, Callable]:
|
|||
|
|
return {
|
|||
|
|
'student': lambda r: (r.get('student_name') or '').lower(),
|
|||
|
|
'course': lambda r: (r.get('course') or '').lower(),
|
|||
|
|
'end_lessons': lambda r: _num(r.get('end_lessons')),
|
|||
|
|
'end_gift': lambda r: _num(r.get('end_gift_lessons')),
|
|||
|
|
'end_total': lambda r: _num(r.get('end_total_lessons')),
|
|||
|
|
'end_balance': lambda r: _num(r.get('end_balance')),
|
|||
|
|
'unit_price': lambda r: _num(r.get('unit_price')),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def monthly_snapshot_sort_keys() -> dict[str, Callable]:
|
|||
|
|
return {
|
|||
|
|
'period': lambda s: (s.year, s.month),
|
|||
|
|
'unit_price': lambda s: _num(s.unit_price),
|
|||
|
|
'end_lessons': lambda s: _num(s.end_lessons),
|
|||
|
|
'end_gift': lambda s: _num(s.end_gift_lessons),
|
|||
|
|
'end_total': lambda s: _num(s.end_total_lessons),
|
|||
|
|
'end_balance': lambda s: _num(s.end_balance),
|
|||
|
|
'consumed': lambda s: _num(s.consumed_lessons),
|
|||
|
|
'consumed_amount': lambda s: _num(s.consumed_amount),
|
|||
|
|
}
|