diff --git a/app/MAINTENANCE.md b/app/MAINTENANCE.md index 05683d6..39f3e33 100644 --- a/app/MAINTENANCE.md +++ b/app/MAINTENANCE.md @@ -149,7 +149,6 @@ git push origin HEAD:<当前分支> ## 课程小结迁移维护 - VPS 是 `classnotes.txt` 和 `学生课时账户.md` 的唯一正式写入方。 -- 本机 `com.xsk.education-management.sync` 常驻同步迁移后应停止,避免旧本地数据覆盖 VPS。 - 本机课程小结采集脚本用 `XSK_INGEST_URL` 和 `XSK_INGEST_TOKEN` 推送批次;失败批次保存在本机 `推送失败队列/`。 - 管理后台的“课程小结审核”处理低置信或冲突小结;“操作记录”追踪接收、自动入账、重复、失败、审核批准和驳回。 - 历史小结导入使用 `scripts/import_course_summaries.py`,历史 `classnotes缺失.txt` 只生成审核任务,不自动扣课时。 diff --git a/app/README.md b/app/README.md index 12450e7..1492d73 100644 --- a/app/README.md +++ b/app/README.md @@ -10,11 +10,8 @@ - `scripts/deploy_to_vps.py`:部署新时空教务管理系统到 VPS。 - `scripts/migrate_text_to_sqlite.py`:一次性把旧纯文本、JSON、JSONL 和课程小结 Markdown 迁移进 SQLite。 - `scripts/import_course_summaries.py`:一次性导入历史课程小结 Markdown。 -- `scripts/sync_to_vps.py`:旧版正式数据同步脚本,迁移后不要继续常驻运行。 - `scripts/smoke_test.js`:轻量前端行为烟测。 - `scripts/install_gitea_backup_hook.py`:安装提交后自动推送到 Gitea 的 Git hook。 -- `scripts/install_launch_agent.py`:安装 Mac 开机常驻同步任务。 -- `launchd/com.xsk.education-management.sync.plist.template`:LaunchAgent 模板。 ## 维护入口 @@ -70,27 +67,6 @@ http://121.199.172.246:18080/ XSK_PYTHON_IMAGE='python:3.12-slim' ``` -## 手动同步数据 - -迁移完成后不要再用本机 `classnotes.txt` 和 `学生课时账户.md` 覆盖 VPS。SQLite 数据库 `/data/xsk_education.db` 是唯一事实源;运行时生成的 `/data/runtime_text_cache/` 只是兼容旧业务逻辑的缓存。下面命令只保留给迁移前或灾难恢复时使用,日常新增课程小结应走 `POST /api/ingest/course-summaries`。 - -```bash -XSK_USE_SSHPASS=1 \ -XSK_SSH_PASSWORD='填写SSH密码' \ -python3 scripts/sync_to_vps.py --once --use-sshpass -``` - -同步文件: - -- `/Users/yangdawei/Desktop/新时空业务源数据/新时空课程记录与课时账户/classnotes.txt` -- `/Users/yangdawei/Desktop/新时空业务源数据/新时空课程记录与课时账户/学生课时账户.md` - -远端数据目录: - -```text -/root/新时空教务管理系统/data/xsk_education.db -``` - ## SQLite 迁移 首次迁移先 dry-run: @@ -188,34 +164,6 @@ cp /root/新时空教务管理系统/data/backups/<备份目录>/学生课时账 docker compose up -d ``` -## 安装自动同步 - -```bash -XSK_USE_SSHPASS=1 \ -XSK_SSH_PASSWORD='填写SSH密码' \ -python3 scripts/install_launch_agent.py --use-sshpass -``` - -日志位置: - -```text -~/Library/Logs/xsk-education-management/sync.log -~/Library/Logs/xsk-education-management/sync.err.log -``` - -查看任务: - -```bash -launchctl list | grep com.xsk.education-management.sync -``` - -卸载任务: - -```bash -launchctl unload ~/Library/LaunchAgents/com.xsk.education-management.sync.plist -rm ~/Library/LaunchAgents/com.xsk.education-management.sync.plist -``` - ## 更换网页访问密码 登录 VPS 后修改 `/root/新时空教务管理系统/app/.env` 中的 `BASIC_AUTH_PASSWORD`,然后重启: diff --git a/app/app/api_utils.py b/app/app/api_utils.py index 8f85404..89fc239 100644 --- a/app/app/api_utils.py +++ b/app/app/api_utils.py @@ -9,7 +9,7 @@ from pydantic import ValidationError from .config import ACCOUNTS_PATH, CLASSNOTES_PATH, TEACHERS_PATH, USE_SQLITE_SOURCE from .data import Account, Payment, Teacher, read_accounts, read_classnotes, read_teachers from .repository import ensure_runtime_cache -from .schemas import AccountPayload, RegisterLinesPayload, TeacherPayload +from .schemas import RegisterLinesPayload, StudentProfilePayload, TeacherPayload async def read_register_payload(request: Request) -> RegisterLinesPayload: @@ -46,7 +46,7 @@ def load_records(): return read_classnotes(CLASSNOTES_PATH) -def load_accounts(): +def load_student_profiles(): if USE_SQLITE_SOURCE: ensure_runtime_cache() if not ACCOUNTS_PATH.exists(): @@ -74,7 +74,7 @@ def file_meta(path: Path) -> dict: } -def payload_to_account(payload: AccountPayload, student_id: str | None = None) -> Account: +def payload_to_student_profile(payload: StudentProfilePayload, student_id: str | None = None) -> Account: return Account( student_id=student_id if student_id is not None else payload.student_id, student=payload.student, diff --git a/app/app/auth.py b/app/app/auth.py index 1f6adda..33e2a9c 100644 --- a/app/app/auth.py +++ b/app/app/auth.py @@ -77,13 +77,6 @@ def is_admin_authenticated( ) or has_valid_basic_auth(credentials, ADMIN_AUTH_PASSWORD) -def is_accounts_authenticated( - request: Request, - credentials: HTTPBasicCredentials | None = None, -) -> bool: - return is_admin_authenticated(request, credentials) - - def verify_records_auth( request: Request, credentials: HTTPBasicCredentials | None = Depends(security), @@ -110,7 +103,7 @@ def verify_admin_auth( return "admin" -def verify_accounts_auth( +def verify_student_profiles_auth( request: Request, credentials: HTTPBasicCredentials | None = Depends(security), ) -> str: diff --git a/app/app/data.py b/app/app/data.py index 11cf92e..8cd6f82 100644 --- a/app/app/data.py +++ b/app/app/data.py @@ -484,12 +484,6 @@ def next_student_id(accounts: list[Account]) -> str: return f"XS{(max(values) if values else 0) + 1:03d}" -def write_accounts(path: Path, accounts: list[Account]) -> None: - original_text = path.read_text(encoding="utf-8") - accounts_by_id = {account.student_id: account for account in accounts} - atomic_write_text(path, replace_account_lines(original_text, accounts_by_id)) - - def next_teacher_id(teachers: list[Teacher]) -> str: values = [] for teacher in teachers: @@ -499,22 +493,6 @@ def next_teacher_id(teachers: list[Teacher]) -> str: return f"T{(max(values) if values else 0) + 1:03d}" -def replace_teacher_lines(original_text: str, teachers_by_id: dict[str, Teacher]) -> str: - lines = original_text.splitlines() - output: list[str] = [] - for line in lines: - stripped = line.strip() - compact = stripped.strip("|").replace(" ", "").replace("|", "") - if stripped.startswith("|") and not set(compact) <= {"-"} and "教师ID" not in stripped: - parts = [part.strip() for part in stripped.strip("|").split("|")] - if parts and parts[0] in teachers_by_id: - output.append(format_teacher_row(teachers_by_id[parts[0]])) - continue - output.append(line) - trailing_newline = "\n" if original_text.endswith("\n") else "" - return "\n".join(output) + trailing_newline - - def append_teacher_line(original_text: str, teacher: Teacher) -> str: if not original_text.strip(): original_text = ( @@ -553,12 +531,6 @@ def replace_single_teacher_line(original_text: str, old_teacher_id: str, teacher return "\n".join(output) + trailing_newline -def write_teachers(path: Path, teachers: list[Teacher]) -> None: - original_text = path.read_text(encoding="utf-8") if path.exists() else "" - teachers_by_id = {teacher.teacher_id: teacher for teacher in teachers} - atomic_write_text(path, replace_teacher_lines(original_text, teachers_by_id)) - - def create_teacher(path: Path, teacher: Teacher) -> dict: teachers = read_teachers(path) teacher = validate_teacher(replace(teacher, teacher_id=next_teacher_id(teachers))) @@ -615,16 +587,6 @@ def teacher_alias_map(teachers: list[Teacher]) -> dict[str, str]: return mapping -def teacher_name_map(teachers: list[Teacher]) -> dict[str, Teacher]: - mapping: dict[str, Teacher] = {} - for teacher in teachers: - mapping[teacher.name] = teacher - if teacher.alias: - mapping[teacher.alias] = teacher - mapping[teacher.teacher_id] = teacher - return mapping - - def create_account(path: Path, account: Account, classnotes_path: Path | None = None) -> dict: accounts = read_accounts(path) records = read_classnotes(classnotes_path) if classnotes_path and classnotes_path.exists() else [] @@ -663,17 +625,6 @@ def update_account(path: Path, old_student_id: str, account: Account, classnotes return {"account": account_to_dict(account), "backup_id": backup_dir.name, "operation": "修改学生档案"} -def render_accounts_text(path: Path, accounts: list[Account]) -> str: - original_text = path.read_text(encoding="utf-8") - accounts_by_id = {account.student_id: account for account in accounts} - return replace_account_lines(original_text, accounts_by_id) - - -def render_accounts_text_from_text(original_text: str, accounts: list[Account]) -> str: - accounts_by_id = {account.student_id: account for account in accounts} - return replace_account_lines(original_text, accounts_by_id) - - def normalize_lines(lines: list[str] | None = None, line: str | None = None) -> list[str]: values: list[str] = [] if line is not None: @@ -781,115 +732,6 @@ def register_payment_lines( return {"registered": len(registered), "lines": registered, "backup_id": backup_dir.name, "operation": "登记缴费记录"} -def normalize_classnote_durations_and_accounts( - classnotes_path: Path, - accounts_path: Path, - operation_logs_path: Path | None = None, -) -> dict: - original_classnotes = classnotes_path.read_text(encoding="utf-8") - original_accounts = accounts_path.read_text(encoding="utf-8") - accounts = read_accounts(accounts_path) - updated_accounts = list(accounts) - account_deltas: defaultdict[str, float] = defaultdict(float) - changed_lines: list[str] = [] - output_lines: list[str] = [] - skipped_lines: list[str] = [] - - for line_number, raw_line in enumerate(original_classnotes.splitlines(), start=1): - stripped = raw_line.strip() - if not stripped or stripped.startswith("#"): - output_lines.append(raw_line) - continue - match = CLASSNOTE_RE.fullmatch(stripped) - if not match: - output_lines.append(raw_line) - skipped_lines.append(f"{line_number}: {stripped}") - continue - - old_minutes = int(round(parse_hours_text(match.group("duration")) * 60)) - true_minutes = parse_time_range_minutes(match.group("time")) - true_duration = duration_text_from_minutes(true_minutes) - if true_minutes == old_minutes and match.group("duration") == true_duration: - output_lines.append(raw_line) - continue - - student = canonical_name(match.group("student")) - account_index = find_account_index(updated_accounts, student) - delta_hours = round((old_minutes - true_minutes) / 60.0, 2) - if delta_hours: - updated_accounts[account_index] = update_account_remaining(updated_accounts[account_index], delta_hours) - account_deltas[updated_accounts[account_index].student_id] += delta_hours - - new_line = ( - f"{match.group('date')}-{match.group('weekday')}-{match.group('time')}-" - f"{student}-{true_duration}-{match.group('teacher').strip()}-{match.group('subject').strip()}" - ) - output_lines.append(new_line) - changed_lines.append(f"{line_number}: {stripped} => {new_line}") - - if not changed_lines: - return { - "updated_records": 0, - "updated_accounts": 0, - "account_deltas": {}, - "skipped_lines": skipped_lines, - "backup_id": "", - } - - trailing_newline = "\n" if original_classnotes.endswith("\n") else "" - new_classnotes = "\n".join(output_lines) + trailing_newline - changed_account_ids = {account_id for account_id, delta in account_deltas.items() if round(delta, 2)} - updated_accounts_by_id = { - account.student_id: account - for account in updated_accounts - if account.student_id in changed_account_ids - } - new_accounts = replace_account_lines(original_accounts, updated_accounts_by_id) - backup_dir = create_data_backup( - "normalize-classnote-durations", - { - accounts_path: original_accounts, - classnotes_path: original_classnotes, - }, - changed_lines, - ) - try: - atomic_write_text(classnotes_path, new_classnotes) - atomic_write_text(accounts_path, new_accounts) - except Exception: - atomic_write_text(classnotes_path, original_classnotes) - atomic_write_text(accounts_path, original_accounts) - raise - try: - prune_data_backups(backup_dir.parent) - except OSError: - pass - - result = { - "updated_records": len(changed_lines), - "updated_accounts": len(updated_accounts_by_id), - "account_deltas": { - account_id: round(delta, 2) - for account_id, delta in sorted(account_deltas.items()) - if round(delta, 2) - }, - "skipped_lines": skipped_lines, - "backup_id": backup_dir.name, - } - if operation_logs_path is not None: - append_operation_log( - operation_logs_path, - "历史上课记录真实时长迁移", - "完成", - backup_id=backup_dir.name, - updated_records=result["updated_records"], - updated_accounts=result["updated_accounts"], - account_deltas=result["account_deltas"], - skipped_lines_count=len(skipped_lines), - ) - return result - - def default_admin_tasks() -> dict: return {"version": 1, "next_id": 1, "items": []} @@ -4813,28 +4655,6 @@ def has_filter_condition(spec: QuerySpec) -> bool: return bool(spec.start_date or spec.end_date or spec.students or spec.teachers or spec.subjects) -def query_records(records: list[ClassRecord], query: str, limit: int = 200) -> dict: - spec = build_query_spec(query, records) - matched = filter_records(records, spec) if has_filter_condition(spec) else [] - shown, normalized_offset, has_more = paginate_items(matched, 0, limit) - return { - "query": { - "raw_query": spec.raw_query, - "date_range": format_date_range(spec), - "students": spec.students, - "teachers": spec.teachers, - "subjects": spec.subjects, - }, - "summary": summarize_records(matched), - "records": [record_to_dict(record) for record in shown], - "total_records": len(matched), - "shown_records": len(shown), - "offset": normalized_offset, - "limit": limit, - "has_more": has_more, - } - - def query_public_records( records: list[ClassRecord], teachers: list[Teacher], diff --git a/app/app/main.py b/app/app/main.py index 28946fc..a7902a0 100644 --- a/app/app/main.py +++ b/app/app/main.py @@ -6,7 +6,7 @@ from fastapi.responses import JSONResponse from .config import OPERATION_LOGS_PATH, USE_SQLITE_SOURCE, write_lock from .data import migrate_operation_log_labels from .repository import ensure_runtime_cache -from .routers import accounts, admin, ai_register, health, ingest, pages, records +from .routers import admin, ai_register, health, ingest, pages, records, student_profiles app = FastAPI(title="新时空教务管理系统", version="1.0.0") @@ -28,7 +28,7 @@ async def value_error_handler(_request: Request, exc: ValueError): app.include_router(pages.router) app.include_router(health.router) app.include_router(records.router) -app.include_router(accounts.router) +app.include_router(student_profiles.router) app.include_router(admin.router) app.include_router(ai_register.router) app.include_router(ingest.router) diff --git a/app/app/repository.py b/app/app/repository.py index 812d8b5..86ef67f 100644 --- a/app/app/repository.py +++ b/app/app/repository.py @@ -71,34 +71,6 @@ def _record_key(record: ClassRecord) -> str: ) -def _class_record_to_line(record: ClassRecord) -> str: - return f"{record.date}-{record.weekday}-{record.time}-{record.student}-{record.duration}-{record.teacher}-{record.subject}" - - -def _format_number(value: float) -> str: - if float(value).is_integer(): - return str(int(value)) - return f"{value:.2f}".rstrip("0").rstrip(".") - - -def _format_payment(payment: Payment) -> str: - return f"{payment.date}:{_format_number(payment.hours)}" - - -def _account_status(stored_status: str, remaining: float) -> str: - if stored_status in {"结课", "退费"}: - return stored_status - if remaining < 0: - return "欠费" - if remaining < 10: - return "预警" - return "正常" - - -def _duration_text_from_minutes(minutes: int) -> str: - return f"{minutes // 60}小时{minutes % 60}分" - - def _parse_course_summary_blocks(root: Path) -> list[dict]: from .data import iter_course_summary_markdown @@ -160,6 +132,8 @@ def _insert_sources( *, allow_balance_adjustments: bool, ) -> dict: + from .data import class_record_to_line + accounts: list[Account] = sources["accounts"] records: list[ClassRecord] = sources["records"] teachers: list[Teacher] = sources["teachers"] @@ -206,7 +180,7 @@ def _insert_sources( for index, record in enumerate(records): key = _record_key(record) if key in seen_record_keys: - raise ValueError(f"课程记录重复: {_class_record_to_line(record)}") + raise ValueError(f"课程记录重复: {class_record_to_line(record)}") seen_record_keys.add(key) account = account_by_student.get(record.student) if account is None: @@ -229,7 +203,7 @@ def _insert_sources( record.student, record.teacher, record.subject, - _class_record_to_line(record), + class_record_to_line(record), index, ), ) @@ -472,7 +446,19 @@ def _accounts_from_db(conn: sqlite3.Connection) -> list[Account]: payments = payment_map.get(student_id, []) paid = round(sum(payment.hours for payment in payments), 2) remaining = round(paid - used_map.get(student_id, 0.0), 2) - status = _account_status(str(row["account_status"]), remaining) + from .data import recalc_account_status + + status = recalc_account_status( + Account( + student_id=student_id, + student=str(row["student"]), + payments=payments, + remaining=remaining, + account_status=str(row["account_status"]), + primary_entry_year=row["primary_entry_year"], + note=str(row["note"] or ""), + ) + ) accounts.append( Account( student_id=student_id, @@ -543,14 +529,11 @@ def _summary_state_from_db(conn: sqlite3.Connection) -> dict: def _write_accounts(path: Path, accounts: list[Account]) -> None: + from .data import format_account_row + lines = [ACCOUNTS_HEADER.rstrip("\n")] for account in accounts: - payments = ",".join(_format_payment(payment) for payment in account.payments) - primary_entry_year = str(account.primary_entry_year or "") - lines.append( - f"| {account.student_id} | {account.student} | {primary_entry_year} | {payments} | " - f"{_format_number(account.remaining)} | {account.account_status} | {account.note} |" - ) + lines.append(format_account_row(account)) atomic_write_text(path, "\n".join(lines).rstrip() + "\n") diff --git a/app/app/routers/health.py b/app/app/routers/health.py index 0e48e4d..821d6e8 100644 --- a/app/app/routers/health.py +++ b/app/app/routers/health.py @@ -2,7 +2,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends -from ..api_utils import file_meta, load_accounts, load_records, load_teachers +from ..api_utils import file_meta, load_records, load_student_profiles, load_teachers from ..auth import verify_records_auth from ..config import ( ACCOUNTS_PATH, @@ -24,7 +24,7 @@ router = APIRouter() @router.get("/api/health") def health(_user: str = Depends(verify_records_auth)): records = load_records() - accounts = load_accounts() + accounts = load_student_profiles() teachers = load_teachers() return { "ok": True, diff --git a/app/app/routers/accounts.py b/app/app/routers/student_profiles.py similarity index 82% rename from app/app/routers/accounts.py rename to app/app/routers/student_profiles.py index 2ebce42..85dc68a 100644 --- a/app/app/routers/accounts.py +++ b/app/app/routers/student_profiles.py @@ -4,8 +4,15 @@ from datetime import date from fastapi import APIRouter, Depends, HTTPException, Query, Request -from ..api_utils import file_meta, load_accounts, load_teachers, payload_to_account, payload_to_teacher, read_register_payload -from ..auth import verify_accounts_auth, verify_admin_auth +from ..api_utils import ( + file_meta, + load_student_profiles, + load_teachers, + payload_to_student_profile, + payload_to_teacher, + read_register_payload, +) +from ..auth import verify_admin_auth, verify_student_profiles_auth from ..config import ( ACCOUNTS_PATH, ADMIN_TASKS_PATH, @@ -23,13 +30,13 @@ from ..data import ( ACCOUNT_STATUSES, TEACHER_STATUSES, DuplicateRecordError, - account_summary, - account_to_dict, + account_summary as student_profile_summary, + account_to_dict as student_profile_to_dict, append_operation_log, - create_account, + create_account as create_student_profile, create_teacher, duration_text_from_hours, - filter_accounts, + filter_accounts as filter_student_profiles, iter_course_summary_markdown, parse_class_record_line, read_classnotes, @@ -37,10 +44,10 @@ from ..data import ( register_course_summary_texts, register_payment_lines, teacher_to_dict, - update_account, + update_account as update_student_profile, update_teacher, ) -from ..schemas import AccountPayload, TeacherPayload +from ..schemas import StudentProfilePayload, TeacherPayload router = APIRouter() @@ -121,8 +128,8 @@ async def register_course_summaries(request: Request, _user: str = Depends(verif @router.get("/api/student-health") -def student_health(_user: str = Depends(verify_accounts_auth)): - accounts = load_accounts() +def student_health(_user: str = Depends(verify_student_profiles_auth)): + student_profiles = load_student_profiles() teachers = load_teachers() records = read_classnotes(CLASSNOTES_PATH) if CLASSNOTES_PATH.exists() else [] current_month_prefix = date.today().strftime("%Y.%m.") @@ -142,14 +149,14 @@ def student_health(_user: str = Depends(verify_accounts_auth)): "source_mode": "sqlite" if USE_SQLITE_SOURCE else "text", "text_root": str(LEGACY_TEXT_ROOT), }, - "students_count": len(accounts), + "students_count": len(student_profiles), "teachers_count": len(teachers), "active_teachers_count": sum(1 for teacher in teachers if teacher.status == "在岗"), "records_count": len(records), "course_summaries_count": course_summaries_count, "current_month_hours": current_month_hours, "current_month_duration": compact_duration_text(current_month_hours), - "student_summary": account_summary(accounts), + "student_summary": student_profile_summary(student_profiles), } @@ -157,22 +164,22 @@ def student_health(_user: str = Depends(verify_accounts_auth)): def students( q: str = Query("", description="学生姓名或学生ID"), status_filter: str = Query("", alias="status", description="学生档案状态"), - _user: str = Depends(verify_accounts_auth), + _user: str = Depends(verify_student_profiles_auth), ): - all_accounts = load_accounts() - rows = filter_accounts(all_accounts, keyword=q, status=status_filter) + all_student_profiles = load_student_profiles() + rows = filter_student_profiles(all_student_profiles, keyword=q, status=status_filter) return { - "summary": account_summary(all_accounts), + "summary": student_profile_summary(all_student_profiles), "count": len(rows), - "students": [account_to_dict(account) for account in rows], + "students": [student_profile_to_dict(student_profile) for student_profile in rows], } @router.get("/api/students/{student}") -def student_detail(student: str, _user: str = Depends(verify_accounts_auth)): - for account in load_accounts(): - if account.student == student or account.student_id == student: - return account_to_dict(account) +def student_detail(student: str, _user: str = Depends(verify_student_profiles_auth)): + for student_profile in load_student_profiles(): + if student_profile.student == student or student_profile.student_id == student: + return student_profile_to_dict(student_profile) raise HTTPException(status_code=404, detail=f"未找到学生档案: {student}") @@ -236,10 +243,10 @@ def admin_update_teacher(teacher_id: str, payload: TeacherPayload, _user: str = @router.post("/api/admin/students") -def admin_create_student(payload: AccountPayload, _user: str = Depends(verify_admin_auth)): +def admin_create_student(payload: StudentProfilePayload, _user: str = Depends(verify_admin_auth)): try: with write_lock: - result = create_account(ACCOUNTS_PATH, payload_to_account(payload), CLASSNOTES_PATH) + result = create_student_profile(ACCOUNTS_PATH, payload_to_student_profile(payload), CLASSNOTES_PATH) log_operation = str(result.get("operation") or "新增学生档案") append_operation_log( OPERATION_LOGS_PATH, @@ -255,10 +262,10 @@ def admin_create_student(payload: AccountPayload, _user: str = Depends(verify_ad @router.put("/api/admin/students/{student_id}") -def admin_update_student(student_id: str, payload: AccountPayload, _user: str = Depends(verify_admin_auth)): +def admin_update_student(student_id: str, payload: StudentProfilePayload, _user: str = Depends(verify_admin_auth)): try: with write_lock: - result = update_account(ACCOUNTS_PATH, student_id, payload_to_account(payload), CLASSNOTES_PATH) + result = update_student_profile(ACCOUNTS_PATH, student_id, payload_to_student_profile(payload), CLASSNOTES_PATH) log_operation = str(result.get("operation") or "修改学生档案") append_operation_log( OPERATION_LOGS_PATH, diff --git a/app/app/schemas.py b/app/app/schemas.py index b636f60..86e2f70 100644 --- a/app/app/schemas.py +++ b/app/app/schemas.py @@ -21,7 +21,7 @@ class PaymentPayload(BaseModel): hours: float -class AccountPayload(BaseModel): +class StudentProfilePayload(BaseModel): student_id: str = "" student: str payments: list[PaymentPayload] = Field(default_factory=list) diff --git a/app/app/static/admin.html b/app/app/static/admin.html index 6ad2cf3..3404e35 100644 --- a/app/app/static/admin.html +++ b/app/app/static/admin.html @@ -238,8 +238,6 @@
- -