import { z } from "zod" /** * 公告类型与目标受众的联合校验: * - type=school:targetGradeId / targetClassId 必须为空 * - type=grade:targetGradeId 必填,targetClassId 必须为空 * - type=class:targetClassId 必填 * * 避免"创建无受众公告"的数据完整性问题。 */ const refineAudience = ( data: { type?: "school" | "grade" | "class" targetGradeId?: string | null targetClassId?: string | null }, ctx: z.RefinementCtx ): void => { const type = data.type ?? "school" const hasGrade = (data.targetGradeId ?? "").trim().length > 0 const hasClass = (data.targetClassId ?? "").trim().length > 0 if (type === "school" && (hasGrade || hasClass)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["targetGradeId"], message: "全校公告不应指定目标年级或班级", }) return } if (type === "grade" && !hasGrade) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["targetGradeId"], message: "年级公告必须指定目标年级", }) return } if (type === "class" && !hasClass) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["targetClassId"], message: "班级公告必须指定目标班级", }) return } } export const CreateAnnouncementSchema = z .object({ title: z.string().trim().min(1).max(255), content: z.string().trim().min(1), type: z.enum(["school", "grade", "class"]).optional(), status: z.enum(["draft", "published", "archived"]).optional(), targetGradeId: z.string().trim().optional().nullable(), targetClassId: z.string().trim().optional().nullable(), publishedAt: z.string().optional().nullable(), }) .superRefine(refineAudience) .transform((v) => ({ title: v.title, content: v.content, type: v.type ?? "school", status: v.status ?? "draft", targetGradeId: v.targetGradeId && v.targetGradeId.length > 0 ? v.targetGradeId : null, targetClassId: v.targetClassId && v.targetClassId.length > 0 ? v.targetClassId : null, publishedAt: v.publishedAt && v.publishedAt.length > 0 ? v.publishedAt : null, })) export type CreateAnnouncementInput = z.infer export const UpdateAnnouncementSchema = z .object({ title: z.string().trim().min(1).max(255), content: z.string().trim().min(1), type: z.enum(["school", "grade", "class"]).optional(), status: z.enum(["draft", "published", "archived"]).optional(), targetGradeId: z.string().trim().optional().nullable(), targetClassId: z.string().trim().optional().nullable(), publishedAt: z.string().optional().nullable(), }) .superRefine(refineAudience) .transform((v) => ({ title: v.title, content: v.content, type: v.type ?? "school", status: v.status ?? "draft", targetGradeId: v.targetGradeId && v.targetGradeId.length > 0 ? v.targetGradeId : null, targetClassId: v.targetClassId && v.targetClassId.length > 0 ? v.targetClassId : null, publishedAt: v.publishedAt && v.publishedAt.length > 0 ? v.publishedAt : null, })) export type UpdateAnnouncementInput = z.infer