90 lines
3.5 KiB
TypeScript
90 lines
3.5 KiB
TypeScript
/**
|
||
* 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<Form16ViewerConfig> {
|
||
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<boolean> {
|
||
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<boolean> {
|
||
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);
|
||
}
|