Re_Backend/src/services/form16Config.service.ts
2026-03-18 12:59:20 +05:30

147 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Form 16 admin config reader for server-side use (notifications, cron).
* Reads FORM16_ADMIN_CONFIG from admin_configurations and returns merged config with defaults.
*/
import { sequelize } from '../config/database';
import { QueryTypes } from 'sequelize';
import logger from '@utils/logger';
const FORM16_CONFIG_KEY = 'FORM16_ADMIN_CONFIG';
function normalizeRunAtTime(s: string): string {
const [h, m] = s.split(':').map((x) => parseInt(x, 10));
if (Number.isNaN(h) || Number.isNaN(m)) return '09:00';
const hh = Math.max(0, Math.min(23, h));
const mm = Math.max(0, Math.min(59, m));
return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;
}
export interface Form16Notification26AsConfig {
enabled: boolean;
templateRe: string;
templateDealers: string;
}
export interface Form16Config {
notification26AsDataAdded: Form16Notification26AsConfig;
/** RE reminder: prompt 26AS viewers to upload 26AS if missing for the most recently ended quarter. */
reminder26AsUploadEnabled: boolean;
reminder26AsUploadTemplate: string;
/** Start reminders after quarter ends + N days. */
reminder26AsUploadAfterQuarterEndDays: number;
/** Repeat reminders every N days after start. */
reminder26AsUploadEveryDays: number;
notificationForm16SuccessCreditNote: { enabled: boolean; template: string };
notificationForm16Unsuccessful: { enabled: boolean; template: string };
reminderNotificationEnabled: boolean;
reminderNotificationTemplate: string;
/** When to run the reminder job daily (HH:mm, 24h, server timezone). Empty = no scheduled run. */
reminderRunAtTime: string;
alertSubmitForm16Enabled: boolean;
alertSubmitForm16Template: string;
/** When to run the alert job daily (HH:mm, 24h, server timezone). Empty = no scheduled run. */
alertSubmitForm16RunAtTime: string;
/** Dealer reminder: start after quarter ends + N days. */
alertSubmitForm16AfterQuarterEndDays: number;
/** Dealer reminder: repeat every N days after start. */
alertSubmitForm16EveryDays: number;
twentySixAsViewerEmails: string[];
}
const default26As = (): Form16Notification26AsConfig => ({
enabled: true,
templateRe: '26AS data has been added. Please review and use for matching dealer Form 16 submissions.',
templateDealers: 'New 26AS data has been uploaded. You can now submit your Form 16 for the relevant quarter if you havent already.',
});
const defaults: Form16Config = {
notification26AsDataAdded: default26As(),
reminder26AsUploadEnabled: true,
reminder26AsUploadTemplate: 'Reminder: 26AS data for [FinancialYear] [Quarter] is not uploaded yet. Please upload it in 26AS Management.',
reminder26AsUploadAfterQuarterEndDays: 0,
reminder26AsUploadEveryDays: 7,
notificationForm16SuccessCreditNote: { enabled: true, template: 'Form 16 submitted successfully. Credit note: [CreditNoteRef].' },
notificationForm16Unsuccessful: { enabled: true, template: 'Form 16 submission was unsuccessful. Issue: [Issue].' },
reminderNotificationEnabled: true,
reminderNotificationTemplate: 'Reminder: Form 16 submission is pending. [Name], [Request ID]. Please review.',
reminderRunAtTime: '10:00',
alertSubmitForm16Enabled: true,
alertSubmitForm16Template: 'Please submit your Form 16 at your earliest. [Name], due date: [DueDate].',
alertSubmitForm16RunAtTime: '09:00',
alertSubmitForm16AfterQuarterEndDays: 0,
alertSubmitForm16EveryDays: 7,
twentySixAsViewerEmails: [],
};
/**
* Load Form 16 config from admin_configurations for server-side use.
*/
export async function getForm16Config(): Promise<Form16Config> {
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 defaults;
}
const parsed = JSON.parse(result[0].config_value) as Record<string, unknown>;
const merge26As = (): Form16Notification26AsConfig => {
const v = parsed.notification26AsDataAdded;
if (v && typeof v === 'object' && typeof (v as any).enabled === 'boolean') {
const val = v as any;
return {
enabled: val.enabled,
templateRe: typeof val.templateRe === 'string' ? val.templateRe : (typeof val.template === 'string' ? val.template : defaults.notification26AsDataAdded.templateRe),
templateDealers: typeof val.templateDealers === 'string' ? val.templateDealers : defaults.notification26AsDataAdded.templateDealers,
};
}
return defaults.notification26AsDataAdded;
};
return {
notification26AsDataAdded: merge26As(),
reminder26AsUploadEnabled: typeof parsed.reminder26AsUploadEnabled === 'boolean' ? parsed.reminder26AsUploadEnabled : defaults.reminder26AsUploadEnabled,
reminder26AsUploadTemplate: typeof parsed.reminder26AsUploadTemplate === 'string' ? parsed.reminder26AsUploadTemplate : defaults.reminder26AsUploadTemplate,
reminder26AsUploadAfterQuarterEndDays:
typeof parsed.reminder26AsUploadAfterQuarterEndDays === 'number'
? Math.max(0, Math.min(365, parsed.reminder26AsUploadAfterQuarterEndDays))
: defaults.reminder26AsUploadAfterQuarterEndDays,
reminder26AsUploadEveryDays:
typeof parsed.reminder26AsUploadEveryDays === 'number'
? Math.max(1, Math.min(365, parsed.reminder26AsUploadEveryDays))
: defaults.reminder26AsUploadEveryDays,
notificationForm16SuccessCreditNote:
parsed.notificationForm16SuccessCreditNote && typeof (parsed.notificationForm16SuccessCreditNote as any).template === 'string'
? { enabled: (parsed.notificationForm16SuccessCreditNote as any).enabled !== false, template: (parsed.notificationForm16SuccessCreditNote as any).template }
: defaults.notificationForm16SuccessCreditNote,
notificationForm16Unsuccessful:
parsed.notificationForm16Unsuccessful && typeof (parsed.notificationForm16Unsuccessful as any).template === 'string'
? { enabled: (parsed.notificationForm16Unsuccessful as any).enabled !== false, template: (parsed.notificationForm16Unsuccessful as any).template }
: defaults.notificationForm16Unsuccessful,
reminderNotificationEnabled: typeof parsed.reminderNotificationEnabled === 'boolean' ? parsed.reminderNotificationEnabled : defaults.reminderNotificationEnabled,
reminderNotificationTemplate: typeof parsed.reminderNotificationTemplate === 'string' ? parsed.reminderNotificationTemplate : defaults.reminderNotificationTemplate,
reminderRunAtTime: typeof parsed.reminderRunAtTime === 'string' && parsed.reminderRunAtTime.trim() ? (/^\d{1,2}:\d{2}$/.test(parsed.reminderRunAtTime.trim()) ? normalizeRunAtTime(parsed.reminderRunAtTime.trim()) : defaults.reminderRunAtTime) : '',
alertSubmitForm16Enabled: typeof parsed.alertSubmitForm16Enabled === 'boolean' ? parsed.alertSubmitForm16Enabled : defaults.alertSubmitForm16Enabled,
alertSubmitForm16Template: typeof parsed.alertSubmitForm16Template === 'string' ? parsed.alertSubmitForm16Template : defaults.alertSubmitForm16Template,
alertSubmitForm16RunAtTime: typeof parsed.alertSubmitForm16RunAtTime === 'string' && parsed.alertSubmitForm16RunAtTime.trim() ? (/^\d{1,2}:\d{2}$/.test(parsed.alertSubmitForm16RunAtTime.trim()) ? normalizeRunAtTime(parsed.alertSubmitForm16RunAtTime.trim()) : defaults.alertSubmitForm16RunAtTime) : '',
alertSubmitForm16AfterQuarterEndDays:
typeof parsed.alertSubmitForm16AfterQuarterEndDays === 'number'
? Math.max(0, Math.min(365, parsed.alertSubmitForm16AfterQuarterEndDays))
: defaults.alertSubmitForm16AfterQuarterEndDays,
alertSubmitForm16EveryDays:
typeof parsed.alertSubmitForm16EveryDays === 'number'
? Math.max(1, Math.min(365, parsed.alertSubmitForm16EveryDays))
: defaults.alertSubmitForm16EveryDays,
twentySixAsViewerEmails: Array.isArray(parsed.twentySixAsViewerEmails)
? (parsed.twentySixAsViewerEmails as string[]).map((e) => String(e).trim().toLowerCase()).filter(Boolean)
: defaults.twentySixAsViewerEmails,
};
} catch (e) {
logger.warn('[Form16Config] Failed to load config, using defaults:', e);
return defaults;
}
}