记录原始数据变更操作日志
This commit is contained in:
+100
-21
@@ -524,7 +524,7 @@ def create_teacher(path: Path, teacher: Teacher) -> dict:
|
|||||||
prune_data_backups(backup_dir.parent)
|
prune_data_backups(backup_dir.parent)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return {"teacher": teacher_to_dict(teacher), "backup_id": backup_dir.name}
|
return {"teacher": teacher_to_dict(teacher), "backup_id": backup_dir.name, "operation": "新增老师档案"}
|
||||||
|
|
||||||
|
|
||||||
def update_teacher(path: Path, old_teacher_id: str, teacher: Teacher) -> dict:
|
def update_teacher(path: Path, old_teacher_id: str, teacher: Teacher) -> dict:
|
||||||
@@ -542,7 +542,7 @@ def update_teacher(path: Path, old_teacher_id: str, teacher: Teacher) -> dict:
|
|||||||
prune_data_backups(backup_dir.parent)
|
prune_data_backups(backup_dir.parent)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return {"teacher": teacher_to_dict(teacher), "backup_id": backup_dir.name}
|
return {"teacher": teacher_to_dict(teacher), "backup_id": backup_dir.name, "operation": "修改老师档案"}
|
||||||
|
|
||||||
|
|
||||||
def teacher_to_dict(teacher: Teacher) -> dict:
|
def teacher_to_dict(teacher: Teacher) -> dict:
|
||||||
@@ -590,7 +590,7 @@ def create_account(path: Path, account: Account) -> dict:
|
|||||||
prune_data_backups(backup_dir.parent)
|
prune_data_backups(backup_dir.parent)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return {"account": account_to_dict(account), "backup_id": backup_dir.name}
|
return {"account": account_to_dict(account), "backup_id": backup_dir.name, "operation": "新增课时账户"}
|
||||||
|
|
||||||
|
|
||||||
def update_account(path: Path, old_student_id: str, account: Account) -> dict:
|
def update_account(path: Path, old_student_id: str, account: Account) -> dict:
|
||||||
@@ -608,7 +608,7 @@ def update_account(path: Path, old_student_id: str, account: Account) -> dict:
|
|||||||
prune_data_backups(backup_dir.parent)
|
prune_data_backups(backup_dir.parent)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return {"account": account_to_dict(account), "backup_id": backup_dir.name}
|
return {"account": account_to_dict(account), "backup_id": backup_dir.name, "operation": "修改课时账户"}
|
||||||
|
|
||||||
|
|
||||||
def render_accounts_text(path: Path, accounts: list[Account]) -> str:
|
def render_accounts_text(path: Path, accounts: list[Account]) -> str:
|
||||||
@@ -691,7 +691,7 @@ def register_class_record_lines(
|
|||||||
prune_data_backups(backup_dir.parent)
|
prune_data_backups(backup_dir.parent)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return {"registered": len(record_lines), "lines": record_lines, "backup_id": backup_dir.name}
|
return {"registered": len(record_lines), "lines": record_lines, "backup_id": backup_dir.name, "operation": "登记上课记录"}
|
||||||
|
|
||||||
|
|
||||||
def register_payment_lines(
|
def register_payment_lines(
|
||||||
@@ -726,7 +726,7 @@ def register_payment_lines(
|
|||||||
prune_data_backups(backup_dir.parent)
|
prune_data_backups(backup_dir.parent)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return {"registered": len(registered), "lines": registered, "backup_id": backup_dir.name}
|
return {"registered": len(registered), "lines": registered, "backup_id": backup_dir.name, "operation": "登记缴费记录"}
|
||||||
|
|
||||||
|
|
||||||
def default_admin_tasks() -> dict:
|
def default_admin_tasks() -> dict:
|
||||||
@@ -1358,18 +1358,96 @@ def append_operation_log(path: Path, operation: str, status: str, **fields: obje
|
|||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
now = datetime.now().isoformat(timespec="seconds")
|
now = datetime.now().isoformat(timespec="seconds")
|
||||||
log_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{sha1_text(json.dumps(fields, ensure_ascii=False, sort_keys=True), 8)}"
|
log_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{sha1_text(json.dumps(fields, ensure_ascii=False, sort_keys=True), 8)}"
|
||||||
row = {
|
row = localize_operation_log_item({
|
||||||
"id": log_id,
|
"id": log_id,
|
||||||
"created_at": now,
|
"created_at": now,
|
||||||
"operation": operation,
|
"operation": operation,
|
||||||
"status": status,
|
"status": status,
|
||||||
**fields,
|
**fields,
|
||||||
}
|
})
|
||||||
with path.open("a", encoding="utf-8") as handle:
|
with path.open("a", encoding="utf-8") as handle:
|
||||||
handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n")
|
handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n")
|
||||||
return log_id
|
return log_id
|
||||||
|
|
||||||
|
|
||||||
|
OPERATION_LABELS = {
|
||||||
|
"history_course_summary_import": "历史课程小结导入",
|
||||||
|
"course_summary_manual_register": "课程小结登记",
|
||||||
|
"course_summary_ingest": "课程小结接收",
|
||||||
|
"admin_task_approve": "审核批准",
|
||||||
|
"admin_task_reject": "审核驳回",
|
||||||
|
"admin_course_summary_update_time": "课程小结补齐时间",
|
||||||
|
"admin_course_summary_delete": "课程小结删除",
|
||||||
|
"register-class-records": "登记上课记录",
|
||||||
|
"register-payments": "登记缴费记录",
|
||||||
|
"admin-create-account": "新增课时账户",
|
||||||
|
"admin-update-account": "修改课时账户",
|
||||||
|
"admin-create-teacher": "新增老师档案",
|
||||||
|
"admin-update-teacher": "修改老师档案",
|
||||||
|
"admin-approve-correction": "审核批准上课记录纠错",
|
||||||
|
"admin-approve-deletion": "审核批准上课记录删除",
|
||||||
|
"admin-update-course-summary-time": "课程小结补齐时间",
|
||||||
|
"admin-delete-course-summary": "课程小结删除",
|
||||||
|
}
|
||||||
|
|
||||||
|
STATUS_LABELS = {
|
||||||
|
"completed": "完成",
|
||||||
|
"approved": "已批准",
|
||||||
|
"rejected": "已驳回",
|
||||||
|
"updated": "已更新",
|
||||||
|
"deleted": "已删除",
|
||||||
|
"duplicate": "重复",
|
||||||
|
"auto_registered": "自动入账",
|
||||||
|
"review": "待审核",
|
||||||
|
"conflict": "冲突",
|
||||||
|
"pending": "待处理",
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_LABELS = {
|
||||||
|
"class_record_correction": "上课记录纠错",
|
||||||
|
"class_record_deletion": "上课记录删除",
|
||||||
|
"course_summary_review": "课程小结审核",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def localize_operation_log_item(item: dict) -> dict:
|
||||||
|
row = dict(item)
|
||||||
|
operation = str(row.get("operation") or "")
|
||||||
|
status = str(row.get("status") or "")
|
||||||
|
task_type = str(row.get("task_type") or "")
|
||||||
|
row["operation"] = OPERATION_LABELS.get(operation, operation)
|
||||||
|
row["status"] = STATUS_LABELS.get(status, status)
|
||||||
|
if task_type:
|
||||||
|
row["task_type"] = TYPE_LABELS.get(task_type, task_type)
|
||||||
|
if row.get("message"):
|
||||||
|
row["message"] = str(row["message"])
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_operation_log_labels(path: Path) -> dict:
|
||||||
|
if not path.exists():
|
||||||
|
return {"updated": 0, "path": str(path)}
|
||||||
|
lines = path.read_text(encoding="utf-8").splitlines()
|
||||||
|
updated_lines: list[str] = []
|
||||||
|
changed = 0
|
||||||
|
for raw_line in lines:
|
||||||
|
if not raw_line.strip():
|
||||||
|
updated_lines.append(raw_line)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
item = json.loads(raw_line)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
updated_lines.append(raw_line)
|
||||||
|
continue
|
||||||
|
localized = localize_operation_log_item(item)
|
||||||
|
if localized != item:
|
||||||
|
changed += 1
|
||||||
|
updated_lines.append(json.dumps(localized, ensure_ascii=False, sort_keys=True))
|
||||||
|
if changed:
|
||||||
|
atomic_write_text(path, "\n".join(updated_lines) + "\n")
|
||||||
|
return {"updated": changed, "path": str(path)}
|
||||||
|
|
||||||
|
|
||||||
def list_operation_logs(path: Path, limit: int = 100, operation: str = "", status_filter: str = "", student: str = "") -> dict:
|
def list_operation_logs(path: Path, limit: int = 100, operation: str = "", status_filter: str = "", student: str = "") -> dict:
|
||||||
rows: list[dict] = []
|
rows: list[dict] = []
|
||||||
if path.exists():
|
if path.exists():
|
||||||
@@ -1380,6 +1458,7 @@ def list_operation_logs(path: Path, limit: int = 100, operation: str = "", statu
|
|||||||
item = json.loads(raw_line)
|
item = json.loads(raw_line)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
continue
|
continue
|
||||||
|
item = localize_operation_log_item(item)
|
||||||
if operation and item.get("operation") != operation:
|
if operation and item.get("operation") != operation:
|
||||||
continue
|
continue
|
||||||
if status_filter and item.get("status") != status_filter:
|
if status_filter and item.get("status") != status_filter:
|
||||||
@@ -1948,8 +2027,8 @@ def register_course_summary_texts(
|
|||||||
result["duplicates"] += 1
|
result["duplicates"] += 1
|
||||||
log_id = append_operation_log(
|
log_id = append_operation_log(
|
||||||
operation_logs_path,
|
operation_logs_path,
|
||||||
"course_summary_manual_register",
|
"课程小结登记",
|
||||||
"duplicate",
|
"重复",
|
||||||
source_id=source_id,
|
source_id=source_id,
|
||||||
student=normalized["student"],
|
student=normalized["student"],
|
||||||
)
|
)
|
||||||
@@ -1987,8 +2066,8 @@ def register_course_summary_texts(
|
|||||||
write_course_summary_state(state_path, state)
|
write_course_summary_state(state_path, state)
|
||||||
log_id = append_operation_log(
|
log_id = append_operation_log(
|
||||||
operation_logs_path,
|
operation_logs_path,
|
||||||
"course_summary_manual_register",
|
"课程小结登记",
|
||||||
"auto_registered",
|
"自动入账",
|
||||||
source_id=source_id,
|
source_id=source_id,
|
||||||
student=normalized["student"],
|
student=normalized["student"],
|
||||||
teacher=normalized.get("teacher", ""),
|
teacher=normalized.get("teacher", ""),
|
||||||
@@ -2012,8 +2091,8 @@ def register_course_summary_texts(
|
|||||||
student = str((normalized or raw or {}).get("student") or "")
|
student = str((normalized or raw or {}).get("student") or "")
|
||||||
log_id = append_operation_log(
|
log_id = append_operation_log(
|
||||||
operation_logs_path,
|
operation_logs_path,
|
||||||
"course_summary_manual_register",
|
"课程小结登记",
|
||||||
"rejected",
|
"已驳回",
|
||||||
source_id=source_id,
|
source_id=source_id,
|
||||||
student=student,
|
student=student,
|
||||||
error=str(exc),
|
error=str(exc),
|
||||||
@@ -2067,8 +2146,8 @@ def ingest_course_summaries(
|
|||||||
result["duplicates"] += 1
|
result["duplicates"] += 1
|
||||||
log_id = append_operation_log(
|
log_id = append_operation_log(
|
||||||
operation_logs_path,
|
operation_logs_path,
|
||||||
"course_summary_ingest",
|
"课程小结接收",
|
||||||
"duplicate",
|
"重复",
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
source_id=source_id,
|
source_id=source_id,
|
||||||
student=normalized["student"],
|
student=normalized["student"],
|
||||||
@@ -2090,12 +2169,12 @@ def ingest_course_summaries(
|
|||||||
saved_path=str(saved.get("path") or ""),
|
saved_path=str(saved.get("path") or ""),
|
||||||
)
|
)
|
||||||
result["review_pending"] += 1
|
result["review_pending"] += 1
|
||||||
status_value = "review"
|
status_value = "待审核"
|
||||||
task_id = task.get("id")
|
task_id = task.get("id")
|
||||||
else:
|
else:
|
||||||
register_result = register_class_record_lines(classnotes_path, accounts_path, line=proposed_line)
|
register_result = register_class_record_lines(classnotes_path, accounts_path, line=proposed_line)
|
||||||
result["auto_registered"] += 1
|
result["auto_registered"] += 1
|
||||||
status_value = "auto_registered"
|
status_value = "自动入账"
|
||||||
task_id = None
|
task_id = None
|
||||||
backup_id = str(register_result.get("backup_id") or "")
|
backup_id = str(register_result.get("backup_id") or "")
|
||||||
|
|
||||||
@@ -2103,7 +2182,7 @@ def ingest_course_summaries(
|
|||||||
seen_semantic_keys.add(semantic_key)
|
seen_semantic_keys.add(semantic_key)
|
||||||
log_id = append_operation_log(
|
log_id = append_operation_log(
|
||||||
operation_logs_path,
|
operation_logs_path,
|
||||||
"course_summary_ingest",
|
"课程小结接收",
|
||||||
status_value,
|
status_value,
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
source_id=source_id,
|
source_id=source_id,
|
||||||
@@ -2131,8 +2210,8 @@ def ingest_course_summaries(
|
|||||||
source_id = str((normalized or raw).get("source_id") or "")
|
source_id = str((normalized or raw).get("source_id") or "")
|
||||||
log_id = append_operation_log(
|
log_id = append_operation_log(
|
||||||
operation_logs_path,
|
operation_logs_path,
|
||||||
"course_summary_ingest",
|
"课程小结接收",
|
||||||
"rejected",
|
"已驳回",
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
source_id=source_id,
|
source_id=source_id,
|
||||||
student=str((normalized or raw).get("student") or ""),
|
student=str((normalized or raw).get("student") or ""),
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ from ..data import (
|
|||||||
DuplicateRecordError,
|
DuplicateRecordError,
|
||||||
account_summary,
|
account_summary,
|
||||||
account_to_dict,
|
account_to_dict,
|
||||||
|
append_operation_log,
|
||||||
create_account,
|
create_account,
|
||||||
create_teacher,
|
create_teacher,
|
||||||
filter_accounts,
|
filter_accounts,
|
||||||
|
parse_class_record_line,
|
||||||
register_class_record_lines,
|
register_class_record_lines,
|
||||||
register_course_summary_texts,
|
register_course_summary_texts,
|
||||||
register_payment_lines,
|
register_payment_lines,
|
||||||
@@ -47,6 +49,16 @@ async def register_class_records(request: Request, _user: str = Depends(verify_a
|
|||||||
lines=payload.lines,
|
lines=payload.lines,
|
||||||
line=payload.line,
|
line=payload.line,
|
||||||
)
|
)
|
||||||
|
for line in result.get("lines", []):
|
||||||
|
record = parse_class_record_line(str(line))
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
str(result.get("operation") or "登记上课记录"),
|
||||||
|
"完成",
|
||||||
|
student=record.student,
|
||||||
|
proposed_line=str(line),
|
||||||
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
|
)
|
||||||
except DuplicateRecordError as exc:
|
except DuplicateRecordError as exc:
|
||||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
@@ -60,6 +72,16 @@ async def register_payments(request: Request, _user: str = Depends(verify_admin_
|
|||||||
payload = await read_register_payload(request)
|
payload = await read_register_payload(request)
|
||||||
with write_lock:
|
with write_lock:
|
||||||
result = register_payment_lines(ACCOUNTS_PATH, lines=payload.lines, line=payload.line)
|
result = register_payment_lines(ACCOUNTS_PATH, lines=payload.lines, line=payload.line)
|
||||||
|
for line in result.get("lines", []):
|
||||||
|
student = str(line).split("-", 1)[0]
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
str(result.get("operation") or "登记缴费记录"),
|
||||||
|
"完成",
|
||||||
|
student=student,
|
||||||
|
proposed_line=str(line),
|
||||||
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
return {"ok": True, **result}
|
return {"ok": True, **result}
|
||||||
@@ -144,6 +166,15 @@ def admin_create_teacher(payload: TeacherPayload, _user: str = Depends(verify_ad
|
|||||||
try:
|
try:
|
||||||
with write_lock:
|
with write_lock:
|
||||||
result = create_teacher(TEACHERS_PATH, payload_to_teacher(payload))
|
result = create_teacher(TEACHERS_PATH, payload_to_teacher(payload))
|
||||||
|
log_operation = str(result.get("operation") or "新增老师档案")
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
log_operation,
|
||||||
|
"完成",
|
||||||
|
teacher_id=str(result.get("teacher", {}).get("teacher_id") or ""),
|
||||||
|
teacher=str(result.get("teacher", {}).get("name") or ""),
|
||||||
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
return {"ok": True, **result}
|
return {"ok": True, **result}
|
||||||
@@ -158,6 +189,15 @@ def admin_update_teacher(teacher_id: str, payload: TeacherPayload, _user: str =
|
|||||||
teacher_id,
|
teacher_id,
|
||||||
payload_to_teacher(payload, teacher_id=payload.teacher_id.strip() or teacher_id),
|
payload_to_teacher(payload, teacher_id=payload.teacher_id.strip() or teacher_id),
|
||||||
)
|
)
|
||||||
|
log_operation = str(result.get("operation") or "修改老师档案")
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
log_operation,
|
||||||
|
"完成",
|
||||||
|
teacher_id=str(result.get("teacher", {}).get("teacher_id") or ""),
|
||||||
|
teacher=str(result.get("teacher", {}).get("name") or ""),
|
||||||
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
return {"ok": True, **result}
|
return {"ok": True, **result}
|
||||||
@@ -168,6 +208,15 @@ def admin_create_account(payload: AccountPayload, _user: str = Depends(verify_ad
|
|||||||
try:
|
try:
|
||||||
with write_lock:
|
with write_lock:
|
||||||
result = create_account(ACCOUNTS_PATH, payload_to_account(payload))
|
result = create_account(ACCOUNTS_PATH, payload_to_account(payload))
|
||||||
|
log_operation = str(result.get("operation") or "新增课时账户")
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
log_operation,
|
||||||
|
"完成",
|
||||||
|
student_id=str(result.get("account", {}).get("student_id") or ""),
|
||||||
|
student=str(result.get("account", {}).get("student") or ""),
|
||||||
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
return {"ok": True, **result}
|
return {"ok": True, **result}
|
||||||
@@ -178,6 +227,15 @@ def admin_update_account(student_id: str, payload: AccountPayload, _user: str =
|
|||||||
try:
|
try:
|
||||||
with write_lock:
|
with write_lock:
|
||||||
result = update_account(ACCOUNTS_PATH, student_id, payload_to_account(payload))
|
result = update_account(ACCOUNTS_PATH, student_id, payload_to_account(payload))
|
||||||
|
log_operation = str(result.get("operation") or "修改课时账户")
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
log_operation,
|
||||||
|
"完成",
|
||||||
|
student_id=str(result.get("account", {}).get("student_id") or ""),
|
||||||
|
student=str(result.get("account", {}).get("student") or ""),
|
||||||
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
return {"ok": True, **result}
|
return {"ok": True, **result}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from ..data import (
|
|||||||
delete_course_summary,
|
delete_course_summary,
|
||||||
list_admin_tasks,
|
list_admin_tasks,
|
||||||
list_operation_logs,
|
list_operation_logs,
|
||||||
|
migrate_operation_log_labels,
|
||||||
query_course_summaries,
|
query_course_summaries,
|
||||||
reject_admin_task,
|
reject_admin_task,
|
||||||
update_course_summary_time,
|
update_course_summary_time,
|
||||||
@@ -46,6 +47,7 @@ def admin_operation_logs(
|
|||||||
student: str = Query(""),
|
student: str = Query(""),
|
||||||
_user: str = Depends(verify_admin_auth),
|
_user: str = Depends(verify_admin_auth),
|
||||||
):
|
):
|
||||||
|
migrate_operation_log_labels(OPERATION_LOGS_PATH)
|
||||||
return list_operation_logs(
|
return list_operation_logs(
|
||||||
OPERATION_LOGS_PATH,
|
OPERATION_LOGS_PATH,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
@@ -91,8 +93,8 @@ def admin_approve_task(task_id: int, _user: str = Depends(verify_admin_auth)):
|
|||||||
task = result.get("task", {})
|
task = result.get("task", {})
|
||||||
append_operation_log(
|
append_operation_log(
|
||||||
OPERATION_LOGS_PATH,
|
OPERATION_LOGS_PATH,
|
||||||
"admin_task_approve",
|
"审核批准",
|
||||||
"approved",
|
"已批准",
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
task_type=str(task.get("type") or ""),
|
task_type=str(task.get("type") or ""),
|
||||||
student=str(task.get("student") or task.get("corrected", {}).get("student") or ""),
|
student=str(task.get("student") or task.get("corrected", {}).get("student") or ""),
|
||||||
@@ -111,8 +113,8 @@ def admin_reject_task(task_id: int, _user: str = Depends(verify_admin_auth)):
|
|||||||
task = reject_admin_task(ADMIN_TASKS_PATH, task_id)
|
task = reject_admin_task(ADMIN_TASKS_PATH, task_id)
|
||||||
append_operation_log(
|
append_operation_log(
|
||||||
OPERATION_LOGS_PATH,
|
OPERATION_LOGS_PATH,
|
||||||
"admin_task_reject",
|
"审核驳回",
|
||||||
"rejected",
|
"已驳回",
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
task_type=str(task.get("type") or ""),
|
task_type=str(task.get("type") or ""),
|
||||||
student=str(task.get("student") or task.get("corrected", {}).get("student") or ""),
|
student=str(task.get("student") or task.get("corrected", {}).get("student") or ""),
|
||||||
@@ -130,8 +132,8 @@ def admin_update_course_summary_time(summary_id: str, payload: dict, _user: str
|
|||||||
result = update_course_summary_time(COURSE_SUMMARIES_ROOT, summary_id, str(payload.get("time_range") or ""))
|
result = update_course_summary_time(COURSE_SUMMARIES_ROOT, summary_id, str(payload.get("time_range") or ""))
|
||||||
append_operation_log(
|
append_operation_log(
|
||||||
OPERATION_LOGS_PATH,
|
OPERATION_LOGS_PATH,
|
||||||
"admin_course_summary_update_time",
|
"课程小结补齐时间",
|
||||||
"updated",
|
"已更新",
|
||||||
summary_id=summary_id,
|
summary_id=summary_id,
|
||||||
time_range=str(payload.get("time_range") or ""),
|
time_range=str(payload.get("time_range") or ""),
|
||||||
backup_id=str(result.get("backup_id") or ""),
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
@@ -148,8 +150,8 @@ def admin_delete_course_summary(summary_id: str, _user: str = Depends(verify_adm
|
|||||||
result = delete_course_summary(COURSE_SUMMARIES_ROOT, summary_id)
|
result = delete_course_summary(COURSE_SUMMARIES_ROOT, summary_id)
|
||||||
append_operation_log(
|
append_operation_log(
|
||||||
OPERATION_LOGS_PATH,
|
OPERATION_LOGS_PATH,
|
||||||
"admin_course_summary_delete",
|
"课程小结删除",
|
||||||
"deleted",
|
"已删除",
|
||||||
summary_id=summary_id,
|
summary_id=summary_id,
|
||||||
backup_id=str(result.get("backup_id") or ""),
|
backup_id=str(result.get("backup_id") or ""),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -330,17 +330,29 @@
|
|||||||
<div class="quick-actions">
|
<div class="quick-actions">
|
||||||
<select id="logOperation" aria-label="操作类型筛选">
|
<select id="logOperation" aria-label="操作类型筛选">
|
||||||
<option value="">全部操作</option>
|
<option value="">全部操作</option>
|
||||||
<option value="course_summary_ingest">小结接收</option>
|
<option value="登记上课记录">登记上课记录</option>
|
||||||
<option value="admin_task_approve">审核批准</option>
|
<option value="登记缴费记录">登记缴费记录</option>
|
||||||
<option value="admin_task_reject">审核驳回</option>
|
<option value="新增课时账户">新增课时账户</option>
|
||||||
|
<option value="修改课时账户">修改课时账户</option>
|
||||||
|
<option value="新增老师档案">新增老师档案</option>
|
||||||
|
<option value="修改老师档案">修改老师档案</option>
|
||||||
|
<option value="课程小结接收">课程小结接收</option>
|
||||||
|
<option value="课程小结登记">课程小结登记</option>
|
||||||
|
<option value="审核批准">审核批准</option>
|
||||||
|
<option value="审核驳回">审核驳回</option>
|
||||||
|
<option value="课程小结补齐时间">课程小结补齐时间</option>
|
||||||
|
<option value="课程小结删除">课程小结删除</option>
|
||||||
</select>
|
</select>
|
||||||
<select id="logStatus" aria-label="操作结果筛选">
|
<select id="logStatus" aria-label="操作结果筛选">
|
||||||
<option value="">全部结果</option>
|
<option value="">全部结果</option>
|
||||||
<option value="auto_registered">自动入账</option>
|
<option value="完成">完成</option>
|
||||||
<option value="review">待审核</option>
|
<option value="自动入账">自动入账</option>
|
||||||
<option value="duplicate">重复</option>
|
<option value="待审核">待审核</option>
|
||||||
<option value="rejected">失败/驳回</option>
|
<option value="重复">重复</option>
|
||||||
<option value="approved">已批准</option>
|
<option value="已驳回">失败/驳回</option>
|
||||||
|
<option value="已批准">已批准</option>
|
||||||
|
<option value="已更新">已更新</option>
|
||||||
|
<option value="已删除">已删除</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ function statusClass(status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function taskStatusClass(status) {
|
function taskStatusClass(status) {
|
||||||
if (status === "approved" || status === "auto_registered") return "normal";
|
if (["approved", "auto_registered", "已批准", "自动入账", "完成", "已更新", "已删除"].includes(status)) return "normal";
|
||||||
if (status === "rejected" || status === "duplicate") return "closed";
|
if (["rejected", "duplicate", "已驳回", "重复"].includes(status)) return "closed";
|
||||||
if (status === "conflict") return "debt";
|
if (status === "conflict" || status === "冲突") return "debt";
|
||||||
return "warning";
|
return "warning";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,8 +695,12 @@ async function deleteCourseSummary(summaryId) {
|
|||||||
|
|
||||||
function renderLogDetail(item) {
|
function renderLogDetail(item) {
|
||||||
const details = [];
|
const details = [];
|
||||||
|
if (item.task_type) details.push(`类型:${item.task_type}`);
|
||||||
if (item.source_id) details.push(`来源:${item.source_id}`);
|
if (item.source_id) details.push(`来源:${item.source_id}`);
|
||||||
|
if (item.student_id) details.push(`学生ID:${item.student_id}`);
|
||||||
|
if (item.teacher_id) details.push(`教师ID:${item.teacher_id}`);
|
||||||
if (item.teacher || item.subject) details.push(`老师/科目:${item.teacher || ""} ${item.subject || ""}`.trim());
|
if (item.teacher || item.subject) details.push(`老师/科目:${item.teacher || ""} ${item.subject || ""}`.trim());
|
||||||
|
if (item.time_range) details.push(`时间:${item.time_range}`);
|
||||||
if (item.proposed_line) details.push(`记录:${item.proposed_line}`);
|
if (item.proposed_line) details.push(`记录:${item.proposed_line}`);
|
||||||
if (Array.isArray(item.reasons) && item.reasons.length) details.push(`原因:${item.reasons.join(";")}`);
|
if (Array.isArray(item.reasons) && item.reasons.length) details.push(`原因:${item.reasons.join(";")}`);
|
||||||
if (item.error) details.push(`错误:${item.error}`);
|
if (item.error) details.push(`错误:${item.error}`);
|
||||||
|
|||||||
@@ -538,8 +538,10 @@ textarea:focus {
|
|||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
max-height: calc(100vh - 292px);
|
max-height: calc(100vh - 292px);
|
||||||
overflow: auto;
|
overflow-x: auto;
|
||||||
overscroll-behavior: contain;
|
overflow-y: auto;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
overscroll-behavior-y: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1047,6 +1049,9 @@ td {
|
|||||||
|
|
||||||
.table-wrap {
|
.table-wrap {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
overscroll-behavior-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-account-grid {
|
.inline-account-grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user