feat(admin): R176-R180 公示表与新闻撤回 + 管理端多管理员权限(C 层)+ 交卷冻结
- R176 成绩公示表引擎(lib/publicitySheet.js + lib/assets/publicity-template.xlsx 官方模板基底) 与指导教师回填(lib/teacherBackfill.js);赛项名称/编号改为配置项 - R177 新闻「发布 ↔ 撤回」互斥:撤回只置 is_draft=1,不删除数据 - R178 C 层 RBAC:lib/adminPermissions.js(13 权限点 / 70 条路由规则,唯一 SoT) + middleware/auth.js 三段判定(默认拒绝 · is_super 单点 · token_version) + controller/service/adminUser.js + lib/auditLog.js + model/adminAuditLog.js - R178 B1 交卷冻结:service.exam.assertNotSubmitted(bizCode 4006 单一判据源) - R178 B④ 提交口径拆分:formalSubmitted / submitted / answeredCount 三字段 - R178 成绩总览补小组编号:resolveTeamLabels(与选手管理同源,含兜底与三态) - R181 生产迁移配套:admins +7 列、admin_audit_logs(scripts/migrate_r178_admin_rbac.py)
This commit is contained in:
@@ -389,6 +389,12 @@ class ExamController extends Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔴 交卷冻结(R178·B①):已正式交卷者不得再改写答案。
|
||||
// 位置刻意在判分之前 —— 已冻结的答卷不该再做一次无意义的 autoScore。
|
||||
// 与 service.saveAnswers 内的兜底调用是**同一个函数**(单一判据源),
|
||||
// 不是两套条件;异常由下方 catch 按 bizCode 翻译成 4006。
|
||||
await service.exam.assertNotSubmitted(aggregation.aggregation_uid);
|
||||
|
||||
// 参数校验
|
||||
const rule = {
|
||||
answers: { type: 'array', required: true, min: 1 },
|
||||
@@ -452,6 +458,15 @@ class ExamController extends Controller {
|
||||
savedCount: savedRecords.length,
|
||||
}, '答案提交成功');
|
||||
} catch (err) {
|
||||
// 业务性拒绝(如「已交卷,答卷已冻结」)不该被伪装成 500:
|
||||
// service 层用 bizCode 标记期望的错误码,这里原样翻译给前端。
|
||||
if (err && err.bizCode) {
|
||||
ctx.logger.warn(
|
||||
`[submitAnswers] 业务拒绝 code=${err.bizCode} msg=${err.bizMessage || err.message}`
|
||||
);
|
||||
ctx.body = ctx.helper.error(err.bizCode, err.bizMessage || err.message);
|
||||
return;
|
||||
}
|
||||
ctx.logger.error('提交答案失败:', err);
|
||||
ctx.body = ctx.helper.error(500, '提交答案失败');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user