- 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
167 lines
5.1 KiB
TypeScript
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);
|
|
}
|
|
}
|