Files
OJ2/apps/api
yuetsh d7a6414735 fix(题单): complete_time 翻回「只设不清」;完成题单后那个必然报错的 tab
三件事,都是上一轮结论没查扎实留下的。

## complete_time

f9354b0 把「退回未完成时清空 complete_time」定成两边统一的行为,理由写的是
「快照里 complete_time 非空 ⟺ is_completed」。那个检查只做了一个方向(已完成但
没有完成时间 = 0 条),反方向没查 —— 实际有 4 条 is_completed=false 却留着
complete_time,全在题单 8:那批人 2025-11-14 在它还只有 6 题时完成过,老师后来
加到 12 题,进度退回未完成,完成时间保留了下来。

旧栈是故意不清的(problemset/models.py:218 只有 `if is_completed and not
complete_time` 这一条赋值),语义是「曾经完成于」。清空是重写时在学生路径上引入的,
上一版又把它推广到了后台路径。

翻回只设不清。「未完成 + 有完成时间」是允许的组合。反过来清空的代价不可逆:往一个
100 人已完成的题单里加一道题、再改主意删掉,这 100 个人的历史完成时间就一起被冲成
「现在」—— 上一轮的实跑已经在题单 9 上复现过。

## 两处注释订正

「旧后端不做加题后的重算」这个说法是错的,本仓早先的注释里有,上一版我照搬进了
services/problemset.ts 和提交信息。旧栈用 signals 做了,而且两件事都做:
problemset/signals.py 在 ProblemSetProblem 的 post_save / post_delete 上重算全部
参与者进度、再重算该题单全部奖章。重写时 views 里翻不到显式调用就当成没做,于是
奖章那一半漏了 —— 53 条应发未发正是这么来的。

另外补上 53 条里那 23 条的出处:旧栈的管理命令 fix_problemset_progress 按实际 AC
补 progress_detail,而 signals 不挂在 Progress 上,所以进度补了、奖章没补。

## 用户进度 tab

detail.vue 的 showTabs 写的是「超管 或 自己完成了题单」,而它渲染的
UserProgressView 调的是 requireTeacher 的接口。两边正好错开:

- 学生做完题单 → tab 出现 → 点进去 403(实跑确认:已完成该题单的 student 拿到
  403 permission-denied,devadmin 拿到 200)。而 loadUserProgress 没有 try/catch、
  loading.value = false 又写在 await 之后,转圈永远停不下来。
- Teacher Admin 看不到这一栏,尽管他们才是它的目标用户、也是唯一调得动的角色。

条件换成 isTeacherOrAbove,取数补 try/finally。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
2026-08-31 08:42:40 -06:00
..