2026-10-23 22:55:21 +08:00
|
|
|
import { restoreStudentSchema } from '../students/students.schemas.js';
|
2026-10-17 09:34:23 +08:00
|
|
|
import { listTransferred, restoreTransferred } from './transferred.service.js';
|
2026-10-06 19:41:18 +08:00
|
|
|
import { Router } from 'express';
|
2026-10-17 09:34:23 +08:00
|
|
|
import { sendOk } from '../../lib/http.js';
|
2026-10-06 19:41:18 +08:00
|
|
|
import { idParamSchema } from '../../lib/schemas.js';
|
2026-10-11 11:49:32 +08:00
|
|
|
import { authenticate, getAuthUser, requirePermission } from '../../middleware/auth.js';
|
2026-10-17 09:34:23 +08:00
|
|
|
import { validate, validatedParams, validatedQuery } from '../../middleware/validate.js';
|
2026-10-06 19:41:18 +08:00
|
|
|
import { defineModule } from '../module.types.js';
|
|
|
|
|
import {
|
|
|
|
|
archiveClassParamSchema,
|
|
|
|
|
archivedContentQuerySchema,
|
|
|
|
|
listArchivesQuerySchema,
|
|
|
|
|
suggestedEnrollmentYears,
|
|
|
|
|
} from './archives.schemas.js';
|
|
|
|
|
import * as archiveService from './archives.service.js';
|
|
|
|
|
|
|
|
|
|
const router = Router();
|
|
|
|
|
router.use(authenticate());
|
2026-10-17 09:34:23 +08:00
|
|
|
router.get('/transferred', requirePermission('archive:manage'), async (req, res) =>
|
|
|
|
|
sendOk(res, await listTransferred(getAuthUser(req))),
|
|
|
|
|
);
|
|
|
|
|
router.post(
|
|
|
|
|
'/transferred/:id/restore',
|
2026-10-23 22:55:21 +08:00
|
|
|
requirePermission('roster:manage'),
|
|
|
|
|
validate({ params: idParamSchema, body: restoreStudentSchema }),
|
|
|
|
|
async (req, res) =>
|
|
|
|
|
sendOk(res, await restoreTransferred(getAuthUser(req), String(req.params.id), req.body)),
|
2026-10-17 09:34:23 +08:00
|
|
|
);
|
2026-10-06 19:41:18 +08:00
|
|
|
|
|
|
|
|
/**
|
2026-10-23 22:55:21 +08:00
|
|
|
* 毕业归档(**归档操作按 archive:manage 与全校范围校验**)。
|
2026-10-06 19:41:18 +08:00
|
|
|
*
|
|
|
|
|
* 归档是**打标记 + 只读**:毕业班级的作业与通知原样留在库里,
|
|
|
|
|
* 通过 `GET /:id/classes/:classId/content` 只读查看。
|
2026-10-18 11:53:37 +08:00
|
|
|
* 年度升级走 `POST /api/term/promotion/run`,毕业班归档,其余升一级;本模块读取归档结果。
|
2026-10-06 19:41:18 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** GET /api/archives - 届别列表 */
|
2026-10-11 11:49:32 +08:00
|
|
|
router.get(
|
|
|
|
|
'/',
|
|
|
|
|
requirePermission('archive:manage'),
|
|
|
|
|
validate({ query: listArchivesQuerySchema }),
|
|
|
|
|
async (req, res) => {
|
|
|
|
|
const user = getAuthUser(req);
|
|
|
|
|
sendOk(res, await archiveService.listArchivedYears(user), '获取届别列表成功');
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-10-06 19:41:18 +08:00
|
|
|
|
|
|
|
|
/** GET /api/archives/summary - 归档页顶部汇总 + 可归档的届别建议 */
|
2026-10-11 11:49:32 +08:00
|
|
|
router.get('/summary', requirePermission('archive:manage'), async (req, res) => {
|
2026-10-06 19:41:18 +08:00
|
|
|
sendOk(
|
|
|
|
|
res,
|
|
|
|
|
{
|
2026-10-23 22:55:21 +08:00
|
|
|
...(await archiveService.getArchiveSummary(getAuthUser(req))),
|
2026-10-06 19:41:18 +08:00
|
|
|
suggestedEnrollmentYears: suggestedEnrollmentYears(),
|
|
|
|
|
},
|
|
|
|
|
'获取归档汇总成功',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** GET /api/archives/:id - 某届详情(班级 + 毕业生 + 转出的学生) */
|
2026-10-11 11:49:32 +08:00
|
|
|
router.get(
|
|
|
|
|
'/:id',
|
|
|
|
|
requirePermission('archive:manage'),
|
|
|
|
|
validate({ params: idParamSchema }),
|
|
|
|
|
async (req, res) => {
|
|
|
|
|
const user = getAuthUser(req);
|
|
|
|
|
const { id } = validatedParams<{ id: string }>(req);
|
|
|
|
|
sendOk(res, await archiveService.getArchivedYearDetail(user, id), '获取届别详情成功');
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-10-06 19:41:18 +08:00
|
|
|
|
|
|
|
|
/** GET /api/archives/:id/classes/:classId/content - 归档班级的作业与通知(只读) */
|
|
|
|
|
router.get(
|
|
|
|
|
'/:id/classes/:classId/content',
|
2026-10-11 11:49:32 +08:00
|
|
|
requirePermission('archive:manage'),
|
2026-10-06 19:41:18 +08:00
|
|
|
validate({ params: archiveClassParamSchema, query: archivedContentQuerySchema }),
|
|
|
|
|
async (req, res) => {
|
|
|
|
|
const user = getAuthUser(req);
|
|
|
|
|
const { id, classId } = validatedParams<{ id: string; classId: string }>(req);
|
|
|
|
|
const { limit } = validatedQuery<{ limit?: number }>(req);
|
|
|
|
|
sendOk(
|
|
|
|
|
res,
|
|
|
|
|
await archiveService.getArchivedClassContent(user, id, classId, limit ?? 200),
|
|
|
|
|
'获取归档班级内容成功',
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/** DELETE /api/archives/:id - 撤销归档(管理员误操作的退路,不删任何数据) */
|
2026-10-11 11:49:32 +08:00
|
|
|
router.delete(
|
|
|
|
|
'/:id',
|
|
|
|
|
requirePermission('archive:manage'),
|
|
|
|
|
validate({ params: idParamSchema }),
|
|
|
|
|
async (req, res) => {
|
|
|
|
|
const user = getAuthUser(req);
|
|
|
|
|
const { id } = validatedParams<{ id: string }>(req);
|
|
|
|
|
await archiveService.deleteArchive(user, id);
|
|
|
|
|
sendOk(res, { id }, '已撤销归档,班级与学生回到在读状态');
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-10-06 19:41:18 +08:00
|
|
|
|
|
|
|
|
export const archivesModule = defineModule({
|
|
|
|
|
name: 'archives',
|
|
|
|
|
basePath: '/archives',
|
|
|
|
|
router,
|
|
|
|
|
});
|