Files
NextEdu/src/modules/lesson-preparation/actions-substitutes.ts
SpecialX 20023e13fd feat(lesson-preparation): add AI evaluation, analytics, attachments, calendar, comments, review, substitutes, formative, and version diff
- Add actions-ai-evaluation, actions-analytics, actions-attachments, actions-calendar, actions-comments, actions-formative, actions-questions, actions-review, actions-substitutes

- Add corresponding data-access layers for each new action module

- Add calendar-view, curriculum-map-view, version-diff-viewer components

- Add editor-slice, selection-slice, version-slice hooks for state management

- Add document-diff and scope-check lib utilities

- Add default-question-service and external-questions-bridge services
2026-07-03 10:25:21 +08:00

167 lines
5.1 KiB
TypeScript

/**
* M12 代课教师机制 - Server Actions
*/
"use server";
import { getTranslations } from "next-intl/server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import {
getSubstitutesByPlanId,
getActiveSubstitutesByTeacherId,
createSubstitute,
updateSubstituteStatus,
deleteSubstitute,
canTeacherAccessPlan,
} from "./data-access-substitutes";
import type { LessonPlanSubstitute } from "./data-access-substitutes";
import type { ActionState } from "@/shared/types/action-state";
import { getAuthContext, requirePermission } from "@/shared/lib/auth-guard";
import { handleActionError } from "@/shared/lib/action-utils";
import { Permissions } from "@/shared/types/permissions";
const createSubstituteSchema = z.object({
planId: z.string().min(1, "error.planIdRequired"),
originalTeacherId: z.string().min(1, "error.teacherIdRequired"),
substituteTeacherId: z.string().min(1, "error.teacherIdRequired"),
startDate: z.string().refine((v) => !Number.isNaN(new Date(v).getTime()), "error.invalidDate"),
endDate: z.string().refine((v) => !Number.isNaN(new Date(v).getTime()), "error.invalidDate").optional(),
reason: z.string().max(255, "error.reasonTooLong").optional(),
});
const updateSubstituteStatusSchema = z.object({
substituteId: z.string().min(1, "error.substituteIdRequired"),
status: z.enum(["active", "expired", "cancelled"]),
});
/**
* 查询课案代课教师列表
*/
export async function getLessonPlanSubstitutesAction(
planId: string,
): Promise<ActionState<{ items: LessonPlanSubstitute[] }>> {
try {
await requirePermission(Permissions.LESSON_PLAN_READ);
const items = await getSubstitutesByPlanId(planId);
return { success: true, data: { items } };
} catch (e) {
return handleActionError(e);
}
}
/**
* 查询当前教师的代课任务
*/
export async function getMySubstitutesAction(): Promise<
ActionState<{ items: LessonPlanSubstitute[] }>
> {
try {
await requirePermission(Permissions.LESSON_PLAN_READ);
const auth = await getAuthContext();
if (!auth.userId) {
return { success: false, message: "Unauthorized" };
}
const items = await getActiveSubstitutesByTeacherId(auth.userId);
return { success: true, data: { items } };
} catch (e) {
return handleActionError(e);
}
}
/**
* 创建代课教师映射
*/
export async function createSubstituteAction(
input: Record<string, unknown>,
): Promise<ActionState<{ substitute: LessonPlanSubstitute }>> {
try {
await requirePermission(Permissions.LESSON_PLAN_UPDATE);
const t = await getTranslations("lessonPreparation");
const parseResult = createSubstituteSchema.safeParse(input);
if (!parseResult.success) {
return {
success: false,
message: t("error.invalidInput"),
errors: Object.fromEntries(
Object.entries(parseResult.error.flatten().fieldErrors),
),
};
}
const auth = await getAuthContext();
if (!auth.userId) {
return { success: false, message: t("error.unauthorized") };
}
const substitute = await createSubstitute(
{
planId: parseResult.data.planId,
originalTeacherId: parseResult.data.originalTeacherId,
substituteTeacherId: parseResult.data.substituteTeacherId,
startDate: new Date(parseResult.data.startDate),
endDate: parseResult.data.endDate
? new Date(parseResult.data.endDate)
: undefined,
reason: parseResult.data.reason,
},
auth.userId,
);
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
return { success: true, data: { substitute } };
} catch (e) {
return handleActionError(e);
}
}
/**
* 更新代课教师状态
*/
export async function updateSubstituteStatusAction(
input: Record<string, unknown>,
): Promise<ActionState<null>> {
try {
await requirePermission(Permissions.LESSON_PLAN_UPDATE);
const parseResult = updateSubstituteStatusSchema.safeParse(input);
if (!parseResult.success) {
return { success: false, message: "Invalid input" };
}
await updateSubstituteStatus(parseResult.data.substituteId, parseResult.data.status);
return { success: true, data: null };
} catch (e) {
return handleActionError(e);
}
}
/**
* 删除代课教师映射
*/
export async function deleteSubstituteAction(
substituteId: string,
): Promise<ActionState<null>> {
try {
await requirePermission(Permissions.LESSON_PLAN_UPDATE);
await deleteSubstitute(substituteId);
return { success: true, data: null };
} catch (e) {
return handleActionError(e);
}
}
/**
* 检查当前教师对某课案的访问权限(用于路由层校验)
*/
export async function canAccessPlanAction(
planId: string,
): Promise<ActionState<{ canAccess: boolean }>> {
try {
await requirePermission(Permissions.LESSON_PLAN_READ);
const auth = await getAuthContext();
if (!auth.userId) {
return { success: false, message: "Unauthorized" };
}
const canAccess = await canTeacherAccessPlan(planId, auth.userId);
return { success: true, data: { canAccess } };
} catch (e) {
return handleActionError(e);
}
}