import type { Role } from "@prisma/client"; export type Permission = | "create_po" | "submit_po" | "edit_own_draft_po" | "view_own_pos" | "view_all_pos" | "approve_po" | "reject_po" | "cancel_po" | "request_edits" | "request_vendor_id" | "process_payment" | "confirm_receipt" | "view_analytics" | "export_reports" | "manage_users" | "manage_vendors" | "create_vendor" | "manage_vessels_accounts" | "manage_products" | "manage_sites" | "manage_delivery_locations" // ── Crewing (feature-flagged) — mirrors Crewing-Implementation-Spec §6 ────── | "raise_requisition" | "request_relief_cover" | "convert_relief_to_requisition" | "cancel_requisition" | "view_requisitions" | "manage_candidates" | "record_reference_check" | "record_interview_result" | "request_interview_waiver" | "approve_interview_waiver" | "approve_salary_structure" | "select_candidate" | "onboard_crew" | "sign_off_crew" | "view_crew_records" | "upload_crew_records" | "issue_ppe" | "apply_leave" | "decide_leave" | "record_attendance" | "view_attendance" | "verify_site_records" | "verify_bank_epf" | "raise_appraisal" | "verify_appraisal" | "approve_appraisal" | "generate_wage_report" | "approve_wage_report" | "view_wage_report" | "manage_ranks" // Office/admin crew management — direct placement (no requisition), crew CRUD, // and per-vessel rank-strength config. Held by Manager + Admin (+ SuperUser). | "manage_crew"; // Purchasing / admin permissions (the original PPMS matrix). SITE_STAFF is a // crewing-only role and holds no purchasing permissions. const PO_ROLE_PERMISSIONS: Record = { TECHNICAL: ["create_po", "submit_po", "edit_own_draft_po", "view_own_pos", "confirm_receipt", "create_vendor"], MANNING: ["create_po", "submit_po", "edit_own_draft_po", "view_own_pos", "confirm_receipt", "create_vendor"], ACCOUNTS: ["view_all_pos", "process_payment", "manage_vendors", "create_vendor"], MANAGER: [ "create_po", "submit_po", "edit_own_draft_po", "view_own_pos", "view_all_pos", "approve_po", "reject_po", "cancel_po", "request_edits", "request_vendor_id", "view_analytics", "export_reports", "manage_vendors", "create_vendor", "manage_vessels_accounts", "manage_products", "manage_sites", "manage_delivery_locations", "confirm_receipt", "process_payment" ], SUPERUSER: [ "create_po", "submit_po", "edit_own_draft_po", "view_own_pos", "view_all_pos", "approve_po", "reject_po", "cancel_po", "request_edits", "request_vendor_id", "process_payment", "confirm_receipt", "view_analytics", "export_reports", "create_vendor", "manage_delivery_locations", ], AUDITOR: ["view_own_pos", "view_all_pos", "view_analytics", "export_reports"], ADMIN: [ "view_own_pos", "view_all_pos", "view_analytics", "export_reports", "manage_users", "manage_vendors", "create_vendor", "manage_vessels_accounts", "manage_products", "manage_sites", "manage_delivery_locations", ], SITE_STAFF: [], }; // Crewing permissions — a verbatim transcription of the §6 grant matrix in // wiki Crewing-Implementation-Spec. Gating these is harmless until the screens // land (the module is behind NEXT_PUBLIC_CREWING_ENABLED). Notes from the spec: // MPO (MANNING) has NO attendance/leave; decide_leave/approve_* and selection are // Manager-only; manage_ranks is Manager + Admin (not SuperUser). const CREWING_ROLE_PERMISSIONS: Record = { TECHNICAL: [], SITE_STAFF: [ "request_relief_cover", "sign_off_crew", "view_crew_records", "upload_crew_records", "issue_ppe", "apply_leave", "record_attendance", "view_attendance", "raise_appraisal", ], MANNING: [ "raise_requisition", "convert_relief_to_requisition", "cancel_requisition", "view_requisitions", "manage_candidates", "record_reference_check", "record_interview_result", "request_interview_waiver", "onboard_crew", "sign_off_crew", "view_crew_records", "upload_crew_records", "issue_ppe", "verify_site_records", "verify_appraisal", ], ACCOUNTS: ["view_crew_records", "verify_bank_epf", "view_wage_report"], MANAGER: [ "raise_requisition", "convert_relief_to_requisition", "cancel_requisition", "view_requisitions", "manage_candidates", "record_reference_check", "record_interview_result", "approve_interview_waiver", "approve_salary_structure", "select_candidate", "onboard_crew", "sign_off_crew", "view_crew_records", "upload_crew_records", "issue_ppe", "apply_leave", "decide_leave", "view_attendance", "verify_site_records", "raise_appraisal", "verify_appraisal", "approve_appraisal", "generate_wage_report", "approve_wage_report", "view_wage_report", "manage_ranks", "manage_crew", ], SUPERUSER: [ "raise_requisition", "request_relief_cover", "convert_relief_to_requisition", "cancel_requisition", "view_requisitions", "manage_candidates", "record_reference_check", "record_interview_result", "request_interview_waiver", "approve_interview_waiver", "approve_salary_structure", "select_candidate", "onboard_crew", "sign_off_crew", "view_crew_records", "upload_crew_records", "issue_ppe", "apply_leave", "decide_leave", "record_attendance", "view_attendance", "verify_site_records", "verify_bank_epf", "raise_appraisal", "verify_appraisal", "approve_appraisal", "generate_wage_report", "approve_wage_report", "view_wage_report", "manage_crew", ], AUDITOR: ["view_requisitions", "view_crew_records", "view_attendance", "view_wage_report"], ADMIN: ["view_requisitions", "view_crew_records", "view_wage_report", "manage_ranks", "manage_crew"], }; const ROLE_PERMISSIONS: Record = Object.fromEntries( (Object.keys(PO_ROLE_PERMISSIONS) as Role[]).map((role) => [ role, [...PO_ROLE_PERMISSIONS[role], ...CREWING_ROLE_PERMISSIONS[role]], ]) ) as Record; export function hasPermission(role: Role, permission: Permission): boolean { return ROLE_PERMISSIONS[role]?.includes(permission) ?? false; } export function requirePermission(role: Role, permission: Permission): void { if (!hasPermission(role, permission)) { throw new Error(`Forbidden: role ${role} lacks permission ${permission}`); } } export function getPermissions(role: Role): Permission[] { return ROLE_PERMISSIONS[role] ?? []; }