i male base url issue resolved
This commit is contained in:
parent
061dc8e260
commit
ac31b9ba57
@ -11,6 +11,9 @@ import {
|
|||||||
getSmtpConfig,
|
getSmtpConfig,
|
||||||
isSmtpEnabled
|
isSmtpEnabled
|
||||||
} from '../../services/smtpConfig.service.js';
|
} from '../../services/smtpConfig.service.js';
|
||||||
|
import { getFrontendBaseUrl } from './frontendUrl.js';
|
||||||
|
|
||||||
|
export { getFrontendBaseUrl } from './frontendUrl.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@ -208,7 +211,7 @@ export const sendOpportunityEmail = async (
|
|||||||
location: string,
|
location: string,
|
||||||
applicationId: string
|
applicationId: string
|
||||||
) => {
|
) => {
|
||||||
const link = `http://localhost:5173/questionnaire/${applicationId}`;
|
const link = `${getFrontendBaseUrl()}/questionnaire/${applicationId}`;
|
||||||
await sendEmail(to, 'Action Required: Royal Enfield Dealership Opportunity', 'OPPORTUNITY', {
|
await sendEmail(to, 'Action Required: Royal Enfield Dealership Opportunity', 'OPPORTUNITY', {
|
||||||
applicantName,
|
applicantName,
|
||||||
location,
|
location,
|
||||||
@ -286,7 +289,7 @@ export const sendShortlistedEmail = async (
|
|||||||
location: string,
|
location: string,
|
||||||
applicationId: string
|
applicationId: string
|
||||||
) => {
|
) => {
|
||||||
const portalLink = 'http://localhost:5173/login';
|
const portalLink = `${getFrontendBaseUrl()}/login`;
|
||||||
await sendEmail(to, `Congratulations! You are Shortlisted: ${applicationId}`, 'APPLICANT_SHORTLISTED', {
|
await sendEmail(to, `Congratulations! You are Shortlisted: ${applicationId}`, 'APPLICANT_SHORTLISTED', {
|
||||||
applicantName,
|
applicantName,
|
||||||
location,
|
location,
|
||||||
|
|||||||
7
src/common/utils/frontendUrl.ts
Normal file
7
src/common/utils/frontendUrl.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Portal base URL for emails, WhatsApp, SLA alerts, and notifications.
|
||||||
|
* Set `FRONTEND_URL` in backend `.env` (e.g. https://dealeronboarding-uat.royalenfield.com).
|
||||||
|
*/
|
||||||
|
export function getFrontendBaseUrl(): string {
|
||||||
|
return (process.env.FRONTEND_URL || 'http://localhost:5173').replace(/\/$/, '');
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { Server as SocketServer } from 'socket.io';
|
import { Server as SocketServer } from 'socket.io';
|
||||||
import { Server as HTTPServer } from 'http';
|
import { Server as HTTPServer } from 'http';
|
||||||
import logger from './logger.js';
|
import logger from './logger.js';
|
||||||
|
import { getFrontendBaseUrl } from './frontendUrl.js';
|
||||||
|
|
||||||
let io: SocketServer | null = null;
|
let io: SocketServer | null = null;
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ let io: SocketServer | null = null;
|
|||||||
export const initSocket = (httpServer: HTTPServer) => {
|
export const initSocket = (httpServer: HTTPServer) => {
|
||||||
io = new SocketServer(httpServer, {
|
io = new SocketServer(httpServer, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
|
origin: getFrontendBaseUrl(),
|
||||||
methods: ['GET', 'POST', 'PUT'],
|
methods: ['GET', 'POST', 'PUT'],
|
||||||
credentials: true
|
credentials: true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Op } from 'sequelize';
|
|||||||
import { sendEmail } from './email.service.js';
|
import { sendEmail } from './email.service.js';
|
||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
import {
|
import {
|
||||||
|
import { getFrontendBaseUrl } from './frontendUrl.js';
|
||||||
APPLICATION_STAGES,
|
APPLICATION_STAGES,
|
||||||
TERMINATION_STAGES,
|
TERMINATION_STAGES,
|
||||||
CONSTITUTIONAL_STAGES,
|
CONSTITUTIONAL_STAGES,
|
||||||
@ -13,8 +14,6 @@ import {
|
|||||||
|
|
||||||
const { RequestParticipant, User, Outlet, District, Dealer } = db;
|
const { RequestParticipant, User, Outlet, District, Dealer } = db;
|
||||||
|
|
||||||
const frontendBase = () => process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
|
|
||||||
/** Dealer acknowledgement + internal reviewers after resignation is created. */
|
/** Dealer acknowledgement + internal reviewers after resignation is created. */
|
||||||
export async function notifyResignationSubmittedEmails(resignation: any): Promise<void> {
|
export async function notifyResignationSubmittedEmails(resignation: any): Promise<void> {
|
||||||
const dealerUser = await User.findByPk(resignation.dealerId, {
|
const dealerUser = await User.findByPk(resignation.dealerId, {
|
||||||
@ -22,7 +21,7 @@ export async function notifyResignationSubmittedEmails(resignation: any): Promis
|
|||||||
});
|
});
|
||||||
if (!dealerUser?.email) return;
|
if (!dealerUser?.email) return;
|
||||||
|
|
||||||
const base = frontendBase();
|
const base = getFrontendBaseUrl();
|
||||||
const resignationCode = resignation.resignationId || resignation.id;
|
const resignationCode = resignation.resignationId || resignation.id;
|
||||||
const lwd =
|
const lwd =
|
||||||
resignation.lastOperationalDateSales ||
|
resignation.lastOperationalDateSales ||
|
||||||
@ -99,7 +98,7 @@ export async function notifyConstitutionalSubmittedEmails(request: any, dealerDi
|
|||||||
include: [{ model: User, as: 'user', attributes: ['id', 'email', 'fullName', 'mobileNumber'] }]
|
include: [{ model: User, as: 'user', attributes: ['id', 'email', 'fullName', 'mobileNumber'] }]
|
||||||
});
|
});
|
||||||
|
|
||||||
const base = frontendBase();
|
const base = getFrontendBaseUrl();
|
||||||
const link = `${base}/constitutional-change/${request.id}`;
|
const link = `${base}/constitutional-change/${request.id}`;
|
||||||
|
|
||||||
for (const p of participants) {
|
for (const p of participants) {
|
||||||
@ -128,7 +127,7 @@ export async function notifyRelocationSubmittedEmails(
|
|||||||
request: any,
|
request: any,
|
||||||
submitter: { email: string; fullName?: string | null }
|
submitter: { email: string; fullName?: string | null }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const base = frontendBase();
|
const base = getFrontendBaseUrl();
|
||||||
const code = request.requestId || request.id;
|
const code = request.requestId || request.id;
|
||||||
const dealerName = submitter.fullName?.trim() || 'Dealer';
|
const dealerName = submitter.fullName?.trim() || 'Dealer';
|
||||||
|
|
||||||
|
|||||||
@ -339,7 +339,7 @@ const processStageDecision = async (params: {
|
|||||||
// we still need to trigger notifications for the NEXT person in the sequence.
|
// we still need to trigger notifications for the NEXT person in the sequence.
|
||||||
try {
|
try {
|
||||||
const { notifyStakeholdersOnTransition } = await import('../../common/utils/workflow-email-notifications.js');
|
const { notifyStakeholdersOnTransition } = await import('../../common/utils/workflow-email-notifications.js');
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
|
|
||||||
await notifyStakeholdersOnTransition(
|
await notifyStakeholdersOnTransition(
|
||||||
application.id,
|
application.id,
|
||||||
@ -583,7 +583,7 @@ export const scheduleInterview = async (req: AuthRequest, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
scheduledAt: formatIST(scheduledDateObj),
|
scheduledAt: formatIST(scheduledDateObj),
|
||||||
meetLink,
|
meetLink,
|
||||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: applicantPhone,
|
phone: applicantPhone,
|
||||||
ctaLabel: 'View application'
|
ctaLabel: 'View application'
|
||||||
}
|
}
|
||||||
@ -710,7 +710,7 @@ export const scheduleInterview = async (req: AuthRequest, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
scheduledAt: formatIST(scheduledDateObj),
|
scheduledAt: formatIST(scheduledDateObj),
|
||||||
meetLink,
|
meetLink,
|
||||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: pPhone || '',
|
phone: pPhone || '',
|
||||||
ctaLabel: 'View application'
|
ctaLabel: 'View application'
|
||||||
}
|
}
|
||||||
@ -813,7 +813,7 @@ export const updateInterview = async (req: AuthRequest, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
scheduledAt: formatIST(interview.scheduleDate),
|
scheduledAt: formatIST(interview.scheduleDate),
|
||||||
meetLink,
|
meetLink,
|
||||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: application.mobileNumber || '',
|
phone: application.mobileNumber || '',
|
||||||
ctaLabel: 'View Schedule'
|
ctaLabel: 'View Schedule'
|
||||||
}
|
}
|
||||||
@ -840,7 +840,7 @@ export const updateInterview = async (req: AuthRequest, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
scheduledAt: formatIST(interview.scheduleDate),
|
scheduledAt: formatIST(interview.scheduleDate),
|
||||||
meetLink,
|
meetLink,
|
||||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: panelist.mobileNumber || '',
|
phone: panelist.mobileNumber || '',
|
||||||
ctaLabel: 'Open Assessment'
|
ctaLabel: 'Open Assessment'
|
||||||
}
|
}
|
||||||
@ -1043,6 +1043,7 @@ export const submitLevel2Feedback = async (req: AuthRequest, res: Response) => {
|
|||||||
// --- AI Summary ---
|
// --- AI Summary ---
|
||||||
|
|
||||||
import { ExternalMocksService } from '../../common/utils/externalMocks.service.js';
|
import { ExternalMocksService } from '../../common/utils/externalMocks.service.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export const generateAiSummary = async (req: AuthRequest, res: Response) => {
|
export const generateAiSummary = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { getIO } from '../../common/utils/socket.js';
|
|||||||
import * as InAppPushNotificationService from '../../common/utils/notification.service.js';
|
import * as InAppPushNotificationService from '../../common/utils/notification.service.js';
|
||||||
import { NotificationService as EmailNotificationService } from '../../services/NotificationService.js';
|
import { NotificationService as EmailNotificationService } from '../../services/NotificationService.js';
|
||||||
import logger from '../../common/utils/logger.js';
|
import logger from '../../common/utils/logger.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
const getDocumentModel = (requestType: string) => {
|
const getDocumentModel = (requestType: string) => {
|
||||||
@ -190,7 +191,7 @@ export const addWorknote = async (req: AuthRequest, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
let worknoteLink = `${portalBase}/dashboard`;
|
let worknoteLink = `${portalBase}/dashboard`;
|
||||||
let applicationIdLabel = String(resolvedId).slice(0, 12);
|
let applicationIdLabel = String(resolvedId).slice(0, 12);
|
||||||
let dealerNameLabel = 'Record';
|
let dealerNameLabel = 'Record';
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const { EorChecklist, EorChecklistItem, OnboardingDocument, RelocationDocument }
|
|||||||
import { AuthRequest } from '../../types/express.types.js';
|
import { AuthRequest } from '../../types/express.types.js';
|
||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
import { ROLES } from '../../common/config/constants.js';
|
import { ROLES } from '../../common/config/constants.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
/** Default EOR rows for relocation (SRS 12.2.8) — must stay aligned with relocation required-doc labels. */
|
/** Default EOR rows for relocation (SRS 12.2.8) — must stay aligned with relocation required-doc labels. */
|
||||||
export const RELOCATION_EOR_DEFAULT_ITEMS = [
|
export const RELOCATION_EOR_DEFAULT_ITEMS = [
|
||||||
@ -355,7 +356,7 @@ export const submitAudit = async (req: AuthRequest, res: Response) => {
|
|||||||
placeholders: {
|
placeholders: {
|
||||||
applicantName: app?.applicantName || '',
|
applicantName: app?.applicantName || '',
|
||||||
applicationId: app?.applicationId || '',
|
applicationId: app?.applicationId || '',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${checklist.applicationId}`,
|
link: `${getFrontendBaseUrl()}/applications/${checklist.applicationId}`,
|
||||||
ctaLabel: 'View Application'
|
ctaLabel: 'View Application'
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[EOR] Completion notify failed:', e));
|
}).catch((e: any) => console.error('[EOR] Completion notify failed:', e));
|
||||||
@ -379,7 +380,7 @@ export const submitAudit = async (req: AuthRequest, res: Response) => {
|
|||||||
placeholders: {
|
placeholders: {
|
||||||
applicantName: '',
|
applicantName: '',
|
||||||
applicationId: checklist.relocationId,
|
applicationId: checklist.relocationId,
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/relocation-requests/${checklist.relocationId}`,
|
link: `${getFrontendBaseUrl()}/relocation-requests/${checklist.relocationId}`,
|
||||||
ctaLabel: 'View Request'
|
ctaLabel: 'View Request'
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[EOR] Relocation notify failed:', e));
|
}).catch((e: any) => console.error('[EOR] Relocation notify failed:', e));
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { WorkflowService } from '../../services/WorkflowService.js';
|
|||||||
import { pickApplicationAuditContext, safeAuditLogCreate } from '../../services/applicationAuditLog.service.js';
|
import { pickApplicationAuditContext, safeAuditLogCreate } from '../../services/applicationAuditLog.service.js';
|
||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
import { formatDueDateDaysFromNow, DEFAULT_FDD_DOCUMENT_CHECKLIST } from '../../constants/onboarding-email-defaults.js';
|
import { formatDueDateDaysFromNow, DEFAULT_FDD_DOCUMENT_CHECKLIST } from '../../constants/onboarding-email-defaults.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export const getAssignment = async (req: Request, res: Response) => {
|
export const getAssignment = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
@ -105,14 +106,14 @@ export const assignAgency = async (req: AuthRequest, res: Response) => {
|
|||||||
applicationId: application.applicationId,
|
applicationId: application.applicationId,
|
||||||
dealerName: application.applicantName || '',
|
dealerName: application.applicantName || '',
|
||||||
participantType: 'FDD Partner',
|
participantType: 'FDD Partner',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
ctaLabel: 'View Assignment',
|
ctaLabel: 'View Assignment',
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[FDD] Agency notify failed:', e));
|
}).catch((e: any) => console.error('[FDD] Agency notify failed:', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
const portalBaseFdd = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBaseFdd = getFrontendBaseUrl();
|
||||||
if (application.email) {
|
if (application.email) {
|
||||||
const applicantAcct = await db.User.findOne({
|
const applicantAcct = await db.User.findOne({
|
||||||
where: { email: application.email },
|
where: { email: application.email },
|
||||||
@ -221,7 +222,7 @@ export const uploadReport = async (req: AuthRequest, res: Response) => {
|
|||||||
dealerName: application.applicantName || '',
|
dealerName: application.applicantName || '',
|
||||||
requestId: application.applicationId,
|
requestId: application.applicationId,
|
||||||
targetStage: 'FDD Review',
|
targetStage: 'FDD Review',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: ''
|
phone: ''
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[FDD] Finance/Admin notify failed:', e));
|
}).catch((e: any) => console.error('[FDD] Finance/Admin notify failed:', e));
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { AuthRequest } from '../../types/express.types.js';
|
|||||||
import { AUDIT_ACTIONS, APPLICATION_STATUS, APPLICATION_STAGES, ROLES } from '../../common/config/constants.js';
|
import { AUDIT_ACTIONS, APPLICATION_STATUS, APPLICATION_STAGES, ROLES } from '../../common/config/constants.js';
|
||||||
import { WorkflowService } from '../../services/WorkflowService.js';
|
import { WorkflowService } from '../../services/WorkflowService.js';
|
||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const LOA_STAGE_CODE = 'LOA_APPROVAL';
|
const LOA_STAGE_CODE = 'LOA_APPROVAL';
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ export const createRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
dealerName: application.applicantName || application.applicationId,
|
dealerName: application.applicantName || application.applicationId,
|
||||||
requestId: application.applicationId,
|
requestId: application.applicationId,
|
||||||
targetStage: 'LOA Approval',
|
targetStage: 'LOA Approval',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOA] DD-Head notify failed:', e));
|
}).catch((e: any) => console.error('[LOA] DD-Head notify failed:', e));
|
||||||
@ -243,7 +244,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
placeholders: {
|
placeholders: {
|
||||||
applicantName: app?.applicantName || '',
|
applicantName: app?.applicantName || '',
|
||||||
applicationId: app?.applicationId || '',
|
applicationId: app?.applicationId || '',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||||
ctaLabel: 'View Application'
|
ctaLabel: 'View Application'
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOA] team notify failed:', e));
|
}).catch((e: any) => console.error('[LOA] team notify failed:', e));
|
||||||
@ -254,7 +255,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
include: [{ model: DealerCode, as: 'dealerCode', required: false }]
|
include: [{ model: DealerCode, as: 'dealerCode', required: false }]
|
||||||
});
|
});
|
||||||
if (appFull?.email) {
|
if (appFull?.email) {
|
||||||
const portalBaseLoa = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBaseLoa = getFrontendBaseUrl();
|
||||||
const dc = (appFull as any).dealerCode;
|
const dc = (appFull as any).dealerCode;
|
||||||
const dealerCodeStr = dc?.salesCode || dc?.serviceCode || 'Available on portal after SAP sync';
|
const dealerCodeStr = dc?.salesCode || dc?.serviceCode || 'Available on portal after SAP sync';
|
||||||
const applicantAcct = await User.findOne({
|
const applicantAcct = await User.findOne({
|
||||||
@ -324,7 +325,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
dealerName: '',
|
dealerName: '',
|
||||||
requestId,
|
requestId,
|
||||||
targetStage: 'NBH LOA Approval',
|
targetStage: 'NBH LOA Approval',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOA] NBH notify failed:', e));
|
}).catch((e: any) => console.error('[LOA] NBH notify failed:', e));
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { WorkflowService } from '../../services/WorkflowService.js';
|
|||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
import { sendEmail } from '../../common/utils/email.service.js';
|
import { sendEmail } from '../../common/utils/email.service.js';
|
||||||
import { formatDueDateDaysFromNow, DEFAULT_STATUTORY_DOCUMENT_CHECKLIST } from '../../constants/onboarding-email-defaults.js';
|
import { formatDueDateDaysFromNow, DEFAULT_STATUTORY_DOCUMENT_CHECKLIST } from '../../constants/onboarding-email-defaults.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const LOI_STAGE_CODE = 'LOI_APPROVAL';
|
const LOI_STAGE_CODE = 'LOI_APPROVAL';
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ export const acknowledgeRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
progressPercentage: 90
|
progressPercentage: 90
|
||||||
});
|
});
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const deposit =
|
const deposit =
|
||||||
(await SecurityDeposit.findOne({
|
(await SecurityDeposit.findOne({
|
||||||
where: { applicationId: application.id, depositType: 'SECURITY_DEPOSIT' }
|
where: { applicationId: application.id, depositType: 'SECURITY_DEPOSIT' }
|
||||||
@ -182,7 +183,7 @@ export const createRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
dealerName: application.applicantName || application.applicationId,
|
dealerName: application.applicantName || application.applicationId,
|
||||||
requestId: application.applicationId,
|
requestId: application.applicationId,
|
||||||
targetStage: 'LOI Approval',
|
targetStage: 'LOI Approval',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOI] DD-Head notify failed:', e));
|
}).catch((e: any) => console.error('[LOI] DD-Head notify failed:', e));
|
||||||
@ -336,7 +337,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
placeholders: {
|
placeholders: {
|
||||||
dealerName: application2?.applicantName || '',
|
dealerName: application2?.applicantName || '',
|
||||||
requestId: application2?.applicationId || '',
|
requestId: application2?.applicationId || '',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||||
ctaLabel: 'View Application'
|
ctaLabel: 'View Application'
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOI] finance/admin notify failed:', e));
|
}).catch((e: any) => console.error('[LOI] finance/admin notify failed:', e));
|
||||||
@ -358,7 +359,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
|||||||
dealerName: '',
|
dealerName: '',
|
||||||
requestId: String(request.applicationId),
|
requestId: String(request.applicationId),
|
||||||
targetStage: 'NBH LOI Approval',
|
targetStage: 'NBH LOI Approval',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOI] NBH notify failed:', e));
|
}).catch((e: any) => console.error('[LOI] NBH notify failed:', e));
|
||||||
@ -474,7 +475,7 @@ export const generateDocument = async (req: AuthRequest, res: Response) => {
|
|||||||
{
|
{
|
||||||
applicantName: application.applicantName || 'Applicant',
|
applicantName: application.applicantName || 'Applicant',
|
||||||
applicationId: application.applicationId,
|
applicationId: application.applicationId,
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/prospect-login`,
|
link: `${getFrontendBaseUrl()}/prospect-login`,
|
||||||
ctaLabel: 'View Your Application'
|
ctaLabel: 'View Your Application'
|
||||||
}
|
}
|
||||||
).catch((e: any) => console.error('[LOI] Applicant email failed:', e));
|
).catch((e: any) => console.error('[LOI] Applicant email failed:', e));
|
||||||
@ -493,7 +494,7 @@ export const generateDocument = async (req: AuthRequest, res: Response) => {
|
|||||||
placeholders: {
|
placeholders: {
|
||||||
applicantName: application.applicantName || '',
|
applicantName: application.applicantName || '',
|
||||||
applicationId: application.applicationId,
|
applicationId: application.applicationId,
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
ctaLabel: 'View Application'
|
ctaLabel: 'View Application'
|
||||||
}
|
}
|
||||||
}).catch((e: any) => console.error('[LOI] stakeholder notify failed:', e));
|
}).catch((e: any) => console.error('[LOI] stakeholder notify failed:', e));
|
||||||
@ -501,7 +502,7 @@ export const generateDocument = async (req: AuthRequest, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (application.email) {
|
if (application.email) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
await NotificationService.notify(null, application.email, {
|
await NotificationService.notify(null, application.email, {
|
||||||
title: `Statutory documents — ${application.applicationId}`,
|
title: `Statutory documents — ${application.applicationId}`,
|
||||||
message: 'Please upload statutory and compliance documents on the Dealer Portal.',
|
message: 'Please upload statutory and compliance documents on the Dealer Portal.',
|
||||||
|
|||||||
@ -217,6 +217,10 @@ export const submitApplication = async (req: AuthRequest, res: Response) => {
|
|||||||
applicantName: displayApplicantName,
|
applicantName: displayApplicantName,
|
||||||
location: displayLocation,
|
location: displayLocation,
|
||||||
applicationId,
|
applicationId,
|
||||||
|
link: isOpportunityAvailable
|
||||||
|
? `${getFrontendBaseUrl()}/questionnaire/${applicationId}`
|
||||||
|
: undefined,
|
||||||
|
ctaLabel: isOpportunityAvailable ? 'Complete Questionnaire' : undefined,
|
||||||
phone
|
phone
|
||||||
}
|
}
|
||||||
}).catch((err: any) => console.error('[Onboarding] WhatsApp ack failed:', err));
|
}).catch((err: any) => console.error('[Onboarding] WhatsApp ack failed:', err));
|
||||||
@ -722,7 +726,7 @@ export const uploadDocuments = async (req: any, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (application.email) {
|
if (application.email) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
await NotificationService.notify(null, application.email, {
|
await NotificationService.notify(null, application.email, {
|
||||||
title: `Document received — ${application.applicationId}`,
|
title: `Document received — ${application.applicationId}`,
|
||||||
message: `We received your upload: ${documentType}.`,
|
message: `We received your upload: ${documentType}.`,
|
||||||
@ -846,7 +850,7 @@ export const bulkShortlist = async (req: AuthRequest, res: Response) => {
|
|||||||
application.applicationId
|
application.applicationId
|
||||||
).catch(err => console.error('Failed to send shortlist email:', err));
|
).catch(err => console.error('Failed to send shortlist email:', err));
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const applicantUser = await User.findOne({
|
const applicantUser = await User.findOne({
|
||||||
where: { email: application.email },
|
where: { email: application.email },
|
||||||
attributes: ['id', 'mobileNumber']
|
attributes: ['id', 'mobileNumber']
|
||||||
@ -1149,7 +1153,7 @@ export const assignArchitectureTeam = async (req: AuthRequest, res: Response) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const architect = await User.findByPk(targetUserId, { attributes: ['fullName'] });
|
const architect = await User.findByPk(targetUserId, { attributes: ['fullName'] });
|
||||||
const portalBaseArch = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBaseArch = getFrontendBaseUrl();
|
||||||
if (application.email) {
|
if (application.email) {
|
||||||
await NotificationService.notify(null, application.email, {
|
await NotificationService.notify(null, application.email, {
|
||||||
title: `Architecture & site inputs — ${application.applicationId}`,
|
title: `Architecture & site inputs — ${application.applicationId}`,
|
||||||
@ -1215,6 +1219,7 @@ export const updateArchitectureStatus = async (req: AuthRequest, res: Response)
|
|||||||
};
|
};
|
||||||
|
|
||||||
import { ExternalMocksService } from '../../common/utils/externalMocks.service.js';
|
import { ExternalMocksService } from '../../common/utils/externalMocks.service.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
@ -1746,7 +1751,7 @@ export const sendBulkDocumentReminders = async (req: AuthRequest, res: Response)
|
|||||||
applicationId: app.applicationId,
|
applicationId: app.applicationId,
|
||||||
pendingDocuments: pendingDocuments || undefined,
|
pendingDocuments: pendingDocuments || undefined,
|
||||||
dueDate: dueDate || formatDueDateDaysFromNow(7),
|
dueDate: dueDate || formatDueDateDaysFromNow(7),
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${app.id}`
|
link: `${getFrontendBaseUrl()}/applications/${app.id}`
|
||||||
});
|
});
|
||||||
await safeAuditLogCreate({
|
await safeAuditLogCreate({
|
||||||
userId: req.user?.id || null,
|
userId: req.user?.id || null,
|
||||||
@ -1798,7 +1803,7 @@ export const sendBulkLoiAckReminders = async (req: AuthRequest, res: Response) =
|
|||||||
await NotificationService.sendLoiAcknowledgementReminder(app.email, app.phone, app.applicantName, {
|
await NotificationService.sendLoiAcknowledgementReminder(app.email, app.phone, app.applicantName, {
|
||||||
applicationId: app.applicationId,
|
applicationId: app.applicationId,
|
||||||
dueDate: dueDate || formatDueDateDaysFromNow(7),
|
dueDate: dueDate || formatDueDateDaysFromNow(7),
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/prospect-login`
|
link: `${getFrontendBaseUrl()}/prospect-login`
|
||||||
});
|
});
|
||||||
sent++;
|
sent++;
|
||||||
}
|
}
|
||||||
@ -1842,7 +1847,7 @@ export const rejectOnboardingDocument = async (req: AuthRequest, res: Response)
|
|||||||
await doc.update({ status: 'rejected' });
|
await doc.update({ status: 'rejected' });
|
||||||
|
|
||||||
if (application.email) {
|
if (application.email) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
await NotificationService.notify(null, application.email, {
|
await NotificationService.notify(null, application.email, {
|
||||||
title: `Document re-upload required — ${application.applicationId}`,
|
title: `Document re-upload required — ${application.applicationId}`,
|
||||||
message: `A document was not accepted: ${doc.documentType}.`,
|
message: `A document was not accepted: ${doc.documentType}.`,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { OFFBOARDING_ACTIONS, REQUEST_TYPES } from '../../common/config/constant
|
|||||||
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
||||||
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
||||||
import { notifyConstitutionalSubmittedEmails } from '../../common/utils/workflow-email-notifications.js';
|
import { notifyConstitutionalSubmittedEmails } from '../../common/utils/workflow-email-notifications.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const STRUCTURE_TARGET_VALUES = new Set<string>(
|
const STRUCTURE_TARGET_VALUES = new Set<string>(
|
||||||
CONSTITUTIONAL_STRUCTURE_TARGET_OPTIONS.map((o) => o.value as string)
|
CONSTITUTIONAL_STRUCTURE_TARGET_OPTIONS.map((o) => o.value as string)
|
||||||
@ -674,7 +675,7 @@ export const takeAction = async (req: AuthRequest, res: Response) => {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
|
|
||||||
for (const p of remainingParticipants) {
|
for (const p of remainingParticipants) {
|
||||||
const u = (p as any).user;
|
const u = (p as any).user;
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
|
|||||||
import { OFFBOARDING_ACTIONS } from '../../common/config/constants.js';
|
import { OFFBOARDING_ACTIONS } from '../../common/config/constants.js';
|
||||||
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
||||||
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
// Removed generateResignationId and moved to NomenclatureService
|
// Removed generateResignationId and moved to NomenclatureService
|
||||||
const resolveResignationUuid = async (id: string) => {
|
const resolveResignationUuid = async (id: string) => {
|
||||||
@ -330,7 +331,7 @@ export const uploadResignationDocument = async (req: AuthRequest, res: Response,
|
|||||||
// SRS §7.5.2 — When Legal uploads the acceptance letter, notify DD-Admin and ASM
|
// SRS §7.5.2 — When Legal uploads the acceptance letter, notify DD-Admin and ASM
|
||||||
// so they can communicate official closure to the dealer (field hierarchy).
|
// so they can communicate official closure to the dealer (field hierarchy).
|
||||||
if (stage === RESIGNATION_STAGES.LEGAL && resignation.currentStage === RESIGNATION_STAGES.LEGAL) {
|
if (stage === RESIGNATION_STAGES.LEGAL && resignation.currentStage === RESIGNATION_STAGES.LEGAL) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const resignationCode = resignation.resignationId || resignation.id;
|
const resignationCode = resignation.resignationId || resignation.id;
|
||||||
setImmediate(() =>
|
setImmediate(() =>
|
||||||
notifyStakeholdersOnTransition(
|
notifyStakeholdersOnTransition(
|
||||||
|
|||||||
@ -11,8 +11,9 @@ import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorkno
|
|||||||
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
|
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
|
||||||
import { NomenclatureService } from '../../common/utils/nomenclature.js';
|
import { NomenclatureService } from '../../common/utils/nomenclature.js';
|
||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
|
|
||||||
const LINE_ITEM_DESCRIPTION_PREFIX = {
|
const LINE_ITEM_DESCRIPTION_PREFIX = {
|
||||||
DEPARTMENT_CLAIM: '[DEPARTMENT_CLAIM]',
|
DEPARTMENT_CLAIM: '[DEPARTMENT_CLAIM]',
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { OFFBOARDING_ACTIONS, REQUEST_TYPES } from '../../common/config/constant
|
|||||||
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
||||||
import { NotificationService } from '../../services/NotificationService.js';
|
import { NotificationService } from '../../services/NotificationService.js';
|
||||||
import { sendEmail } from '../../common/utils/email.service.js';
|
import { sendEmail } from '../../common/utils/email.service.js';
|
||||||
|
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const resolveTerminationUuid = async (id: string) => {
|
const resolveTerminationUuid = async (id: string) => {
|
||||||
const { resolvedId } = await resolveEntityUuidByType(db as any, id, 'termination');
|
const { resolvedId } = await resolveEntityUuidByType(db as any, id, 'termination');
|
||||||
@ -101,7 +102,7 @@ export const createTermination = async (req: AuthRequest, res: Response, next: N
|
|||||||
dealerName: '',
|
dealerName: '',
|
||||||
requestId: termination.requestId,
|
requestId: termination.requestId,
|
||||||
reason: reason || '',
|
reason: reason || '',
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/termination/${termination.id}`,
|
link: `${getFrontendBaseUrl()}/termination/${termination.id}`,
|
||||||
ctaLabel: 'Review Request',
|
ctaLabel: 'Review Request',
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
@ -758,7 +759,7 @@ export const issueScn = async (req: AuthRequest, res: Response, next: NextFuncti
|
|||||||
{
|
{
|
||||||
dealerName: dealerUser.fullName || 'Dealer',
|
dealerName: dealerUser.fullName || 'Dealer',
|
||||||
requestId: termination.requestId,
|
requestId: termination.requestId,
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/dealer-termination/${termination.id}`,
|
link: `${getFrontendBaseUrl()}/dealer-termination/${termination.id}`,
|
||||||
ctaLabel: 'View Notice'
|
ctaLabel: 'View Notice'
|
||||||
}
|
}
|
||||||
).catch((e: any) => logger.error('[Termination] SCN email to dealer failed:', e));
|
).catch((e: any) => logger.error('[Termination] SCN email to dealer failed:', e));
|
||||||
@ -777,7 +778,7 @@ export const issueScn = async (req: AuthRequest, res: Response, next: NextFuncti
|
|||||||
placeholders: {
|
placeholders: {
|
||||||
dealerName: dealerUser?.fullName || '',
|
dealerName: dealerUser?.fullName || '',
|
||||||
requestId: termination.requestId,
|
requestId: termination.requestId,
|
||||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/termination/${termination.id}`,
|
link: `${getFrontendBaseUrl()}/termination/${termination.id}`,
|
||||||
ctaLabel: 'View Case'
|
ctaLabel: 'View Case'
|
||||||
}
|
}
|
||||||
}).catch((e: any) => logger.error('[Termination] SCN admin/legal notify failed:', e));
|
}).catch((e: any) => logger.error('[Termination] SCN admin/legal notify failed:', e));
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import terminationRoutes from './modules/termination/termination.routes.js';
|
|||||||
// Import common middleware & utils
|
// Import common middleware & utils
|
||||||
import errorHandler from './common/middleware/errorHandler.js';
|
import errorHandler from './common/middleware/errorHandler.js';
|
||||||
import logger from './common/utils/logger.js';
|
import logger from './common/utils/logger.js';
|
||||||
|
import { getFrontendBaseUrl } from './common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@ -63,14 +64,14 @@ app.use(helmet({
|
|||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
directives: {
|
directives: {
|
||||||
...helmet.contentSecurityPolicy.getDefaultDirectives(),
|
...helmet.contentSecurityPolicy.getDefaultDirectives(),
|
||||||
"frame-ancestors": ["'self'", process.env.FRONTEND_URL || 'http://localhost:5173'],
|
"frame-ancestors": ["'self'", getFrontendBaseUrl()],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
crossOriginEmbedderPolicy: false,
|
crossOriginEmbedderPolicy: false,
|
||||||
crossOriginResourcePolicy: { policy: "cross-origin" }
|
crossOriginResourcePolicy: { policy: "cross-origin" }
|
||||||
}));
|
}));
|
||||||
const allowedOrigins = new Set<string>([
|
const allowedOrigins = new Set<string>([
|
||||||
process.env.FRONTEND_URL || 'http://localhost:5173',
|
getFrontendBaseUrl(),
|
||||||
'http://localhost:5173'
|
'http://localhost:5173'
|
||||||
]);
|
]);
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
|||||||
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
||||||
import { mapConstitutionalChangeTypeToDealerProfile } from '../common/utils/constitutionalNormalize.js';
|
import { mapConstitutionalChangeTypeToDealerProfile } from '../common/utils/constitutionalNormalize.js';
|
||||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export class ConstitutionalWorkflowService {
|
export class ConstitutionalWorkflowService {
|
||||||
private static normalizeDocLabel(input: string): string {
|
private static normalizeDocLabel(input: string): string {
|
||||||
@ -197,7 +198,7 @@ export class ConstitutionalWorkflowService {
|
|||||||
|
|
||||||
const dealerUser = await db.User.findByPk(request.dealerId, { attributes: ['id', 'email', 'fullName'] });
|
const dealerUser = await db.User.findByPk(request.dealerId, { attributes: ['id', 'email', 'fullName'] });
|
||||||
if (dealerUser?.email) {
|
if (dealerUser?.email) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const remarkText = String(remarks ?? '').trim() || 'N/A';
|
const remarkText = String(remarks ?? '').trim() || 'N/A';
|
||||||
|
|
||||||
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { sendEmail } from '../common/utils/email.service.js';
|
import { sendEmail } from '../common/utils/email.service.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
import db from '../database/models/index.js';
|
import db from '../database/models/index.js';
|
||||||
const { Notification, PushSubscription } = db;
|
const { Notification, PushSubscription } = db;
|
||||||
|
|
||||||
@ -147,7 +148,6 @@ export class NotificationService {
|
|||||||
applicantName: string,
|
applicantName: string,
|
||||||
options?: { location?: string; link?: string }
|
options?: { location?: string; link?: string }
|
||||||
) {
|
) {
|
||||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
await this.notify(null, email, {
|
await this.notify(null, email, {
|
||||||
title: 'Action Required: Complete your Dealership Questionnaire',
|
title: 'Action Required: Complete your Dealership Questionnaire',
|
||||||
message: `Hi ${applicantName}, please complete the questionnaire to proceed with your application.`,
|
message: `Hi ${applicantName}, please complete the questionnaire to proceed with your application.`,
|
||||||
@ -157,8 +157,8 @@ export class NotificationService {
|
|||||||
applicantName,
|
applicantName,
|
||||||
phone,
|
phone,
|
||||||
location: options?.location ?? '',
|
location: options?.location ?? '',
|
||||||
link: options?.link ?? `${base}/login`,
|
link: options?.link ?? `${getFrontendBaseUrl()}/prospect-login`,
|
||||||
ctaLabel: 'Complete Now'
|
ctaLabel: 'Complete Questionnaire'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -172,7 +172,6 @@ export class NotificationService {
|
|||||||
applicantName: string,
|
applicantName: string,
|
||||||
options: { applicationId: string; pendingDocuments?: string; dueDate?: string; link?: string }
|
options: { applicationId: string; pendingDocuments?: string; dueDate?: string; link?: string }
|
||||||
) {
|
) {
|
||||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
const channels: ('email' | 'whatsapp')[] = ['email'];
|
const channels: ('email' | 'whatsapp')[] = ['email'];
|
||||||
if (phone) channels.push('whatsapp');
|
if (phone) channels.push('whatsapp');
|
||||||
await this.notify(null, email, {
|
await this.notify(null, email, {
|
||||||
@ -187,7 +186,7 @@ export class NotificationService {
|
|||||||
options.pendingDocuments ||
|
options.pendingDocuments ||
|
||||||
'Please sign in to the Dealer Portal to view your personalised document checklist.',
|
'Please sign in to the Dealer Portal to view your personalised document checklist.',
|
||||||
dueDate: options.dueDate || 'within seven calendar days',
|
dueDate: options.dueDate || 'within seven calendar days',
|
||||||
link: options.link || `${base}/applications`,
|
link: options.link || `${getFrontendBaseUrl()}/applications`,
|
||||||
ctaLabel: 'Upload documents',
|
ctaLabel: 'Upload documents',
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
@ -203,7 +202,6 @@ export class NotificationService {
|
|||||||
applicantName: string,
|
applicantName: string,
|
||||||
options: { applicationId: string; link?: string; dueDate?: string }
|
options: { applicationId: string; link?: string; dueDate?: string }
|
||||||
) {
|
) {
|
||||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
const channels: ('email' | 'whatsapp')[] = ['email'];
|
const channels: ('email' | 'whatsapp')[] = ['email'];
|
||||||
if (phone) channels.push('whatsapp');
|
if (phone) channels.push('whatsapp');
|
||||||
await this.notify(null, email, {
|
await this.notify(null, email, {
|
||||||
@ -215,7 +213,7 @@ export class NotificationService {
|
|||||||
applicantName,
|
applicantName,
|
||||||
applicationId: options.applicationId,
|
applicationId: options.applicationId,
|
||||||
dueDate: options.dueDate || 'within seven calendar days',
|
dueDate: options.dueDate || 'within seven calendar days',
|
||||||
link: options.link || `${base}/prospect-login`,
|
link: options.link || `${getFrontendBaseUrl()}/prospect-login`,
|
||||||
ctaLabel: 'Acknowledge LOI',
|
ctaLabel: 'Acknowledge LOI',
|
||||||
phone: phone || ''
|
phone: phone || ''
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
msUntilLwdMorning
|
msUntilLwdMorning
|
||||||
} from '../common/utils/offboardingLwd.js';
|
} from '../common/utils/offboardingLwd.js';
|
||||||
import logger from '../common/utils/logger.js';
|
import logger from '../common/utils/logger.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const FNF_PUSH_ROLES = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.DD_ADMIN, ROLES.SUPER_ADMIN];
|
const FNF_PUSH_ROLES = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.DD_ADMIN, ROLES.SUPER_ADMIN];
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ export class OffboardingLwdReminderService {
|
|||||||
lwd: Date | string;
|
lwd: Date | string;
|
||||||
detailPath: string;
|
detailPath: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const lwdStr = formatLwdDisplay(params.lwd);
|
const lwdStr = formatLwdDisplay(params.lwd);
|
||||||
const link = `${portalBase}${params.detailPath}`;
|
const link = `${portalBase}${params.detailPath}`;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { safeAuditLogCreate } from './applicationAuditLog.service.js';
|
|||||||
import { pickApplicationAuditContext } from './applicationAuditLog.service.js';
|
import { pickApplicationAuditContext } from './applicationAuditLog.service.js';
|
||||||
import logger from '../common/utils/logger.js';
|
import logger from '../common/utils/logger.js';
|
||||||
import { getQuestionnaireReminderSettings } from './questionnaireReminderSettings.js';
|
import { getQuestionnaireReminderSettings } from './questionnaireReminderSettings.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
const TEMPLATE = 'QUESTIONNAIRE_REMINDER';
|
const TEMPLATE = 'QUESTIONNAIRE_REMINDER';
|
||||||
|
|
||||||
@ -67,8 +68,7 @@ export class QuestionnaireReminderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async sendReminderForApplication(application: any, source: 'scheduled' | 'manual' = 'scheduled') {
|
static async sendReminderForApplication(application: any, source: 'scheduled' | 'manual' = 'scheduled') {
|
||||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const link = `${getFrontendBaseUrl()}/questionnaire/${application.applicationId}`;
|
||||||
const link = `${base}/questionnaire/${application.applicationId}`;
|
|
||||||
|
|
||||||
await NotificationService.sendQuestionnaireReminder(
|
await NotificationService.sendQuestionnaireReminder(
|
||||||
application.email,
|
application.email,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
|||||||
import logger from '../common/utils/logger.js';
|
import logger from '../common/utils/logger.js';
|
||||||
import { NotificationService } from './NotificationService.js';
|
import { NotificationService } from './NotificationService.js';
|
||||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export class RelocationWorkflowService {
|
export class RelocationWorkflowService {
|
||||||
/**
|
/**
|
||||||
@ -102,7 +103,7 @@ export class RelocationWorkflowService {
|
|||||||
await request.reload();
|
await request.reload();
|
||||||
const dealerUser = await User.findByPk(request.dealerId, { attributes: ['id', 'email', 'fullName'] });
|
const dealerUser = await User.findByPk(request.dealerId, { attributes: ['id', 'email', 'fullName'] });
|
||||||
if (dealerUser?.email) {
|
if (dealerUser?.email) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const stageLabel = request.currentStage || request.status || targetStatus;
|
const stageLabel = request.currentStage || request.status || targetStatus;
|
||||||
|
|
||||||
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
|||||||
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
||||||
import { NomenclatureService } from '../common/utils/nomenclature.js';
|
import { NomenclatureService } from '../common/utils/nomenclature.js';
|
||||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
|
|
||||||
export class ResignationWorkflowService {
|
export class ResignationWorkflowService {
|
||||||
@ -96,7 +97,7 @@ export class ResignationWorkflowService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
|
|
||||||
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { resolveRecipientsForRoles } from './slaGeographyResolver.js';
|
|||||||
import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.js';
|
import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.js';
|
||||||
import type { WorkflowActivityRequestType } from '../common/utils/workflowWorknote.js';
|
import type { WorkflowActivityRequestType } from '../common/utils/workflowWorknote.js';
|
||||||
import {
|
import {
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
claimSlaNotificationDispatch,
|
claimSlaNotificationDispatch,
|
||||||
SlaDispatchKeys,
|
SlaDispatchKeys,
|
||||||
updateSlaDispatchRecipientCount,
|
updateSlaDispatchRecipientCount,
|
||||||
@ -496,7 +497,7 @@ export class SLAService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static linkForEntity(entityType: string, entityId: string): string {
|
private static linkForEntity(entityType: string, entityId: string): string {
|
||||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const base = getFrontendBaseUrl();
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
case 'application':
|
case 'application':
|
||||||
return `${base}/applications/${entityId}`;
|
return `${base}/applications/${entityId}`;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Op } from 'sequelize';
|
|||||||
import { slaConfigLookupNames } from '../common/config/slaStageCatalog.js';
|
import { slaConfigLookupNames } from '../common/config/slaStageCatalog.js';
|
||||||
import { computeSlaTrackView, formatSlaDuration, type SlaBucket } from '../common/utils/slaMetrics.js';
|
import { computeSlaTrackView, formatSlaDuration, type SlaBucket } from '../common/utils/slaMetrics.js';
|
||||||
import { SlaStatusService } from './SlaStatusService.js';
|
import { SlaStatusService } from './SlaStatusService.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export type { SlaBucket };
|
export type { SlaBucket };
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ function moduleFromEntityType(entityType: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function linkForEntity(entityType: string, entityId: string): string {
|
function linkForEntity(entityType: string, entityId: string): string {
|
||||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const base = getFrontendBaseUrl();
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
case 'application':
|
case 'application':
|
||||||
return `${base}/applications/${entityId}`;
|
return `${base}/applications/${entityId}`;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
|||||||
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
||||||
import { ParticipantService } from './ParticipantService.js';
|
import { ParticipantService } from './ParticipantService.js';
|
||||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export class TerminationWorkflowService {
|
export class TerminationWorkflowService {
|
||||||
/**
|
/**
|
||||||
@ -103,7 +104,7 @@ export class TerminationWorkflowService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
const dealerPortalLink = `${portalBase}/termination/${termination.id}`;
|
const dealerPortalLink = `${portalBase}/termination/${termination.id}`;
|
||||||
const isScnIssued = targetStage === TERMINATION_STAGES.SCN_ISSUED;
|
const isScnIssued = targetStage === TERMINATION_STAGES.SCN_ISSUED;
|
||||||
|
|
||||||
@ -263,7 +264,7 @@ export class TerminationWorkflowService {
|
|||||||
attributes: ['id', 'email', 'fullName', 'mobileNumber']
|
attributes: ['id', 'email', 'fullName', 'mobileNumber']
|
||||||
});
|
});
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
const portalBase = getFrontendBaseUrl();
|
||||||
for (const u of adminUsers) {
|
for (const u of adminUsers) {
|
||||||
const phone = u.mobileNumber || null;
|
const phone = u.mobileNumber || null;
|
||||||
await NotificationService.notify(u.id, u.email, {
|
await NotificationService.notify(u.id, u.email, {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from '../common/config/constants.js';
|
} from '../common/config/constants.js';
|
||||||
import { NotificationService } from './NotificationService.js';
|
import { NotificationService } from './NotificationService.js';
|
||||||
import { pickApplicationAuditContext, safeAuditLogCreate } from './applicationAuditLog.service.js';
|
import { pickApplicationAuditContext, safeAuditLogCreate } from './applicationAuditLog.service.js';
|
||||||
|
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||||
|
|
||||||
export class WorkflowService {
|
export class WorkflowService {
|
||||||
/**
|
/**
|
||||||
@ -150,11 +151,13 @@ export class WorkflowService {
|
|||||||
if (targetStatus === 'LOA Issued') templateCode = 'LOA_ISSUED';
|
if (targetStatus === 'LOA Issued') templateCode = 'LOA_ISSUED';
|
||||||
if (targetStatus === 'Dealer Code Generated') templateCode = 'DEALER_CODE_READY';
|
if (targetStatus === 'Dealer Code Generated') templateCode = 'DEALER_CODE_READY';
|
||||||
|
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
|
|
||||||
let ctaLabel = 'View application';
|
let ctaLabel = 'View application';
|
||||||
if (templateCode === 'LOI_ISSUED') ctaLabel = 'View LOI';
|
if (templateCode === 'LOI_ISSUED') ctaLabel = 'View LOI';
|
||||||
else if (templateCode === 'LOA_ISSUED') ctaLabel = 'View LOA';
|
else if (templateCode === 'LOA_ISSUED') ctaLabel = 'View LOA';
|
||||||
|
if (['Rejected', 'LOI Rejected', 'LOA Rejected', 'Disqualified'].includes(targetStatus)) {
|
||||||
|
templateCode = 'APPLICANT_REJECTED';
|
||||||
|
ctaLabel = 'View application';
|
||||||
|
}
|
||||||
|
|
||||||
await NotificationService.notify(targetUserId, application.email, {
|
await NotificationService.notify(targetUserId, application.email, {
|
||||||
title: `Onboarding Update: ${targetStatus}`,
|
title: `Onboarding Update: ${targetStatus}`,
|
||||||
@ -165,10 +168,12 @@ export class WorkflowService {
|
|||||||
status: targetStatus,
|
status: targetStatus,
|
||||||
applicantName: application.applicantName,
|
applicantName: application.applicantName,
|
||||||
applicationId: application.applicationId,
|
applicationId: application.applicationId,
|
||||||
|
location: application.preferredLocation || application.city || '',
|
||||||
|
rejectionReason: reason || 'N/A',
|
||||||
reason: reason || 'N/A',
|
reason: reason || 'N/A',
|
||||||
salesCode: application.dealerCode?.salesCode || 'N/A',
|
salesCode: application.dealerCode?.salesCode || 'N/A',
|
||||||
serviceCode: application.dealerCode?.serviceCode || 'N/A',
|
serviceCode: application.dealerCode?.serviceCode || 'N/A',
|
||||||
link: `${portalBase}/applications/${application.id}`,
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||||
ctaLabel,
|
ctaLabel,
|
||||||
phone: user?.mobileNumber || user?.phone || application?.mobileNumber || application?.phone || ''
|
phone: user?.mobileNumber || user?.phone || application?.mobileNumber || application?.phone || ''
|
||||||
},
|
},
|
||||||
@ -179,8 +184,6 @@ export class WorkflowService {
|
|||||||
// Stakeholder Notifications
|
// Stakeholder Notifications
|
||||||
tasks.push((async () => {
|
tasks.push((async () => {
|
||||||
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
||||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
|
|
||||||
let actionUserFullName = 'System';
|
let actionUserFullName = 'System';
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const actionUser = await User.findByPk(userId, { attributes: ['fullName'] });
|
const actionUser = await User.findByPk(userId, { attributes: ['fullName'] });
|
||||||
@ -198,7 +201,7 @@ export class WorkflowService {
|
|||||||
actionUserFullName,
|
actionUserFullName,
|
||||||
action: reason || `Transitioned to ${targetStatus}`,
|
action: reason || `Transitioned to ${targetStatus}`,
|
||||||
remarks: reason || 'N/A',
|
remarks: reason || 'N/A',
|
||||||
link: `${portalBase}/applications/${application.id}`
|
link: `${getFrontendBaseUrl()}/applications/${application.id}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})().catch(e => console.error('[WorkflowService] stakeholder notify failed:', e)));
|
})().catch(e => console.error('[WorkflowService] stakeholder notify failed:', e)));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user