"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { hasPermission, type Permission } from "@/lib/permissions"; import { CREWING_ENABLED } from "@/lib/feature-flags"; import type { Role } from "@prisma/client"; import { revalidatePath } from "next/cache"; type ActionResult = { ok: true } | { error: string }; const PATH = "/crewing/verification"; async function guard(permission: Permission): Promise<{ error: string } | { userId: string; role: Role }> { if (!CREWING_ENABLED) return { error: "Crewing is not enabled" }; const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; if (!hasPermission(session.user.role, permission)) return { error: "Unauthorized" }; return { userId: session.user.id, role: session.user.role }; } // ── Document verification (MPO / Manager) ────────────────────────────────────── export async function verifyDocument(id: string, approve: boolean, remarks?: string): Promise { const g = await guard("verify_site_records"); if ("error" in g) return g; if (!approve && !remarks?.trim()) return { error: "A reason is required to reject" }; const doc = await db.seafarerDocument.findUnique({ where: { id }, select: { crewMemberId: true, verificationStatus: true } }); if (!doc) return { error: "Document not found" }; if (doc.verificationStatus !== "PENDING") return { error: `This document is already ${doc.verificationStatus.toLowerCase()}` }; await db.seafarerDocument.update({ where: { id }, data: { verificationStatus: approve ? "VERIFIED" : "REJECTED", verifiedById: g.userId }, }); await db.crewAction.create({ data: { actionType: approve ? "RECORD_VERIFIED" : "RECORD_REJECTED", actorId: g.userId, crewMemberId: doc.crewMemberId, note: remarks?.trim() || null, metadata: { record: "document" }, }, }); revalidatePath(PATH); revalidatePath(`/crewing/crew/${doc.crewMemberId}`); return { ok: true }; } // ── Bank / EPF verification (Accounts) ───────────────────────────────────────── export async function verifyBankEpf(crewMemberId: string, kind: "bank" | "epf", approve: boolean, remarks?: string): Promise { const g = await guard("verify_bank_epf"); if ("error" in g) return g; if (!approve && !remarks?.trim()) return { error: "A reason is required to reject" }; const status = approve ? "VERIFIED" : "REJECTED"; if (kind === "bank") { const rec = await db.bankDetail.findUnique({ where: { crewMemberId }, select: { id: true, verificationStatus: true } }); if (!rec) return { error: "Bank details not found" }; if (rec.verificationStatus !== "PENDING") return { error: `Bank details already ${rec.verificationStatus.toLowerCase()}` }; await db.bankDetail.update({ where: { crewMemberId }, data: { verificationStatus: status, verifiedById: g.userId } }); } else { const rec = await db.epfDetail.findUnique({ where: { crewMemberId }, select: { id: true, verificationStatus: true } }); if (!rec) return { error: "EPF details not found" }; if (rec.verificationStatus !== "PENDING") return { error: `EPF details already ${rec.verificationStatus.toLowerCase()}` }; await db.epfDetail.update({ where: { crewMemberId }, data: { verificationStatus: status, verifiedById: g.userId } }); } await db.crewAction.create({ data: { actionType: approve ? "RECORD_VERIFIED" : "RECORD_REJECTED", actorId: g.userId, crewMemberId, note: remarks?.trim() || null, metadata: { record: kind }, }, }); revalidatePath(PATH); revalidatePath(`/crewing/crew/${crewMemberId}`); return { ok: true }; }