new Approval Updates
Deploy / deploy (push) Failing after 3m34s

This commit is contained in:
twotalesanimation
2026-06-11 12:30:28 +02:00
parent b9610454d9
commit 3f0c5d1dbe
19 changed files with 2015 additions and 70 deletions
+178 -1
View File
@@ -4,7 +4,8 @@ import { auth } from "@/auth";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { ShotStatus, ShotPriority } from "@prisma/client";
import { ShotStatus, ShotPriority, ShotApprovalStatus } from "@prisma/client";
import { recalcShotStatus } from "@/lib/shot-status";
const createShotSchema = z.object({
scene: z.string().min(1).max(50).regex(/^[A-Z0-9_]+$/i, "Alphanumeric and underscore only"),
@@ -201,6 +202,8 @@ const updateShotSchema = z.object({
description: z.string().optional(),
status: z.nativeEnum(ShotStatus).optional(),
priority: z.nativeEnum(ShotPriority).optional(),
shotApprovalStatus: z.nativeEnum(ShotApprovalStatus).optional(),
sharedWithClient: z.boolean().optional(),
fps: z.number().optional(),
frameStart: z.number().int().optional().nullable(),
frameEnd: z.number().int().optional().nullable(),
@@ -569,3 +572,177 @@ export async function renameFootagePlate(plateId: string, label: string) {
return { success: true };
}
// ── Internal Approval ─────────────────────────────────────────────────────────
/**
* Supervisor/Producer/Admin: mark a shot as internally approved.
* Sets shotApprovalStatus = INTERNALLY_APPROVED, sharedWithClient = false.
* Shot status becomes READY_FOR_CLIENT.
*/
export async function internallyApproveShot(shotId: string) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) {
throw new Error("Insufficient permissions");
}
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { projectId: true },
});
if (!shot) throw new Error("Shot not found");
await db.shot.update({
where: { id: shotId },
data: {
shotApprovalStatus: "INTERNALLY_APPROVED",
sharedWithClient: false,
status: "READY_FOR_CLIENT",
},
});
revalidatePath(`/projects/${shot.projectId}`);
revalidatePath(`/projects/${shot.projectId}/shots/${shotId}`);
revalidatePath(`/shot-status`);
return { success: true };
}
// ── Share / Unshare With Client ───────────────────────────────────────────────
/**
* Producer/Supervisor/Admin: share an internally-approved shot with the client.
* Sets sharedWithClient = true → status becomes CLIENT_REVIEW.
*/
export async function shareWithClient(shotId: string) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) {
throw new Error("Insufficient permissions");
}
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { projectId: true, shotApprovalStatus: true },
});
if (!shot) throw new Error("Shot not found");
if (shot.shotApprovalStatus !== "INTERNALLY_APPROVED") {
throw new Error("Shot must be internally approved before sharing with client");
}
await db.shot.update({
where: { id: shotId },
data: { sharedWithClient: true, status: "CLIENT_REVIEW" },
});
revalidatePath(`/projects/${shot.projectId}`);
revalidatePath(`/projects/${shot.projectId}/shots/${shotId}`);
revalidatePath(`/shot-status`);
return { success: true };
}
/**
* Producer/Supervisor/Admin: remove a shot from client review.
* Sets sharedWithClient = false → status becomes READY_FOR_CLIENT.
*/
export async function unshareFromClient(shotId: string) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
if (!["ADMIN", "PRODUCER", "SUPERVISOR"].includes(session.user.role)) {
throw new Error("Insufficient permissions");
}
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { projectId: true },
});
if (!shot) throw new Error("Shot not found");
await db.shot.update({
where: { id: shotId },
data: { sharedWithClient: false, status: "READY_FOR_CLIENT" },
});
revalidatePath(`/projects/${shot.projectId}`);
revalidatePath(`/projects/${shot.projectId}/shots/${shotId}`);
revalidatePath(`/shot-status`);
return { success: true };
}
// ── Shot-Level Client Approval ────────────────────────────────────────────────
/**
* Internal: handle client approving the shot.
* Sets shotApprovalStatus = CLIENT_APPROVED → status becomes COMPLETE.
*/
export async function clientApproveShot(shotId: string) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
if (!["ADMIN", "PRODUCER", "SUPERVISOR", "CLIENT"].includes(session.user.role)) {
throw new Error("Insufficient permissions");
}
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { projectId: true, sharedWithClient: true },
});
if (!shot) throw new Error("Shot not found");
if (!shot.sharedWithClient) throw new Error("Shot is not shared with client");
await db.shot.update({
where: { id: shotId },
data: { shotApprovalStatus: "CLIENT_APPROVED", status: "COMPLETE" },
});
revalidatePath(`/projects/${shot.projectId}`);
revalidatePath(`/projects/${shot.projectId}/shots/${shotId}`);
revalidatePath(`/shot-status`);
return { success: true };
}
/**
* Internal: handle client requesting changes on a shot.
* Resets shotApprovalStatus = PENDING, sharedWithClient = false.
* Recalculates shot status (will become REVISIONS if tasks set to CHANGES).
*/
export async function clientRequestShotChanges(shotId: string, taskId?: string) {
const session = await auth();
if (!session?.user) throw new Error("Unauthorized");
if (!["ADMIN", "PRODUCER", "SUPERVISOR", "CLIENT"].includes(session.user.role)) {
throw new Error("Insufficient permissions");
}
const shot = await db.shot.findUnique({
where: { id: shotId },
select: { projectId: true },
});
if (!shot) throw new Error("Shot not found");
await db.$transaction(async (tx) => {
// Reset shot approval
await tx.shot.update({
where: { id: shotId },
data: { shotApprovalStatus: "PENDING", sharedWithClient: false },
});
// If a specific task is called out, mark it CHANGES; otherwise mark all non-DONE tasks
if (taskId) {
await tx.task.update({
where: { id: taskId },
data: { status: "CHANGES" },
});
} else {
await tx.task.updateMany({
where: { shotId, status: { not: "DONE" } },
data: { status: "CHANGES" },
});
}
await recalcShotStatus(shotId, tx);
});
revalidatePath(`/projects/${shot.projectId}`);
revalidatePath(`/projects/${shot.projectId}/shots/${shotId}`);
revalidatePath(`/shot-status`);
return { success: true };
}