/** * Form 16 permission service – API-driven access based on admin configuration. * Reads submissionViewerEmails and twentySixAsViewerEmails from FORM16_ADMIN_CONFIG. * No hardcoded emails or roles; all driven by stored config. */ import { sequelize } from '../config/database'; import { QueryTypes } from 'sequelize'; import { getDealerCodeForUser } from './form16.service'; const FORM16_CONFIG_KEY = 'FORM16_ADMIN_CONFIG'; export interface Form16ViewerConfig { submissionViewerEmails: string[]; twentySixAsViewerEmails: string[]; } const emptyConfig: Form16ViewerConfig = { submissionViewerEmails: [], twentySixAsViewerEmails: [], }; /** Normalize email for comparison (lowercase, trim). */ function normalizeEmail(email: string): string { return (email || '').trim().toLowerCase(); } /** * Load Form 16 viewer config from admin_configurations (API-driven). * Returns empty arrays if no config or parse error (empty = allow all). */ export async function getForm16ViewerConfig(): Promise { try { const result = await sequelize.query<{ config_value: string }>( `SELECT config_value FROM admin_configurations WHERE config_key = :configKey LIMIT 1`, { replacements: { configKey: FORM16_CONFIG_KEY }, type: QueryTypes.SELECT } ); if (!result?.length || !result[0].config_value) { return emptyConfig; } const parsed = JSON.parse(result[0].config_value); const submission = Array.isArray(parsed.submissionViewerEmails) ? parsed.submissionViewerEmails.map((e: unknown) => normalizeEmail(String(e ?? ''))).filter(Boolean) : []; const twentySixAs = Array.isArray(parsed.twentySixAsViewerEmails) ? parsed.twentySixAsViewerEmails.map((e: unknown) => normalizeEmail(String(e ?? ''))).filter(Boolean) : []; return { submissionViewerEmails: submission, twentySixAsViewerEmails: twentySixAs }; } catch { return emptyConfig; } } /** * Check if user can view Form 16 submission data (Credit Notes, Non-submitted Dealers, etc.). * - Admin: always allowed (full access to everything). * - Dealers: always allowed (they see their own submissions). * - RE users: allowed if submissionViewerEmails is empty, or user email is in submissionViewerEmails, * or user email is in twentySixAsViewerEmails (26AS access implies submission access so sidebar shows both). */ export async function canViewForm16Submission( userEmail: string, userId: string, role?: string ): Promise { if (role === 'ADMIN') return true; const isDealer = (await getDealerCodeForUser(userId)) !== null; if (isDealer) return true; const config = await getForm16ViewerConfig(); const email = normalizeEmail(userEmail); if (!email) return false; if (config.submissionViewerEmails.length === 0 && config.twentySixAsViewerEmails.length === 0) return true; if (config.submissionViewerEmails.includes(email)) return true; if (config.twentySixAsViewerEmails.includes(email)) return true; return false; } /** * Check if user can view 26AS page and 26AS data. * - Admin: always allowed (full access to everything). * - Otherwise: allowed if twentySixAsViewerEmails is empty, or user email is in the list. */ export async function canView26As(userEmail: string, role?: string): Promise { if (role === 'ADMIN') return true; const config = await getForm16ViewerConfig(); const email = normalizeEmail(userEmail); if (config.twentySixAsViewerEmails.length === 0) return true; return config.twentySixAsViewerEmails.includes(email); }