"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { ApprovalStatus } from "@prisma/client"; import { recalcShotStatus } from "@/lib/shot-status"; import { notifyApprovalChange } from "@/lib/notifications"; import { slackNotifyApproval } from "@/lib/slack"; import { versionLabel } from "@/lib/utils"; const approvalSchema = z.object({ versionId: z.string().cuid(), status: z.nativeEnum(ApprovalStatus), notes: z.string().optional(), }); export async function submitApproval(data: z.infer) { const session = await auth(); if (!session?.user) throw new Error("Unauthorized"); const parsed = approvalSchema.parse(data); // Only supervisors, producers, admins, and clients can approve const allowedRoles = ["ADMIN", "PRODUCER", "SUPERVISOR", "CLIENT"]; if (!allowedRoles.includes(session.user.role)) { throw new Error("Insufficient permissions to review versions"); } // Create the approval record const approval = await db.approval.create({ data: { versionId: parsed.versionId, userId: session.user.id, status: parsed.status, notes: parsed.notes, }, }); // Update version approval status await db.version.update({ where: { id: parsed.versionId }, data: { approvalStatus: parsed.status }, }); // Load version + task + shot + project for downstream effects const version = await db.version.findUnique({ where: { id: parsed.versionId }, include: { task: { include: { shot: true, project: true, }, }, }, }); if (!version) throw new Error("Version not found"); // Update task status based on approval result if (version.task && parsed.status !== "PENDING_REVIEW") { if (parsed.status === "APPROVED") { await db.task.update({ where: { id: version.task.id }, data: { status: "DONE" }, }); } else if (parsed.status === "REJECTED" || parsed.status === "NEEDS_CHANGES") { await db.task.update({ where: { id: version.task.id }, data: { status: "CHANGES" }, }); } // Recalculate shot status from updated task states if (version.task.shot) { await recalcShotStatus(version.task.shot.id).catch(() => {}); } } // Notifications const reviewer = await db.user.findUnique({ where: { id: session.user.id } }); const reviewerName = reviewer?.name ?? "Reviewer"; if (version.task && parsed.status !== "PENDING_REVIEW") { const contextCode = version.task.shot?.shotCode ?? null; await notifyApprovalChange({ artistId: version.task.assignedArtistId ?? version.artistId, shotCode: contextCode ?? version.task.title, versionId: parsed.versionId, status: parsed.status as "APPROVED" | "REJECTED" | "NEEDS_CHANGES", reviewerName, }); // Slack if (version.task.project.slackWebhook) { const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; await slackNotifyApproval(version.task.project.slackWebhook, { shotCode: contextCode ?? version.task.title, versionLabel: versionLabel(version.versionNumber), status: parsed.status as "APPROVED" | "REJECTED" | "NEEDS_CHANGES", reviewerName, projectName: version.task.project.name, reviewUrl: `${appUrl}/review/${parsed.versionId}`, }); } } revalidatePath(`/review/${parsed.versionId}`); if (version.task) { revalidatePath(`/tasks/${version.task.id}`); revalidatePath(`/projects/${version.task.projectId}`); } return { success: true, approval }; } export async function getApprovalHistory(versionId: string) { const session = await auth(); if (!session?.user) throw new Error("Unauthorized"); return db.approval.findMany({ where: { versionId }, include: { user: { select: { id: true, name: true, email: true, image: true, role: true } }, }, orderBy: { createdAt: "desc" }, }); }