i male base url issue resolved
This commit is contained in:
parent
061dc8e260
commit
ac31b9ba57
@ -11,6 +11,9 @@ import {
|
||||
getSmtpConfig,
|
||||
isSmtpEnabled
|
||||
} from '../../services/smtpConfig.service.js';
|
||||
import { getFrontendBaseUrl } from './frontendUrl.js';
|
||||
|
||||
export { getFrontendBaseUrl } from './frontendUrl.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@ -208,7 +211,7 @@ export const sendOpportunityEmail = async (
|
||||
location: 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', {
|
||||
applicantName,
|
||||
location,
|
||||
@ -286,7 +289,7 @@ export const sendShortlistedEmail = async (
|
||||
location: string,
|
||||
applicationId: string
|
||||
) => {
|
||||
const portalLink = 'http://localhost:5173/login';
|
||||
const portalLink = `${getFrontendBaseUrl()}/login`;
|
||||
await sendEmail(to, `Congratulations! You are Shortlisted: ${applicationId}`, 'APPLICANT_SHORTLISTED', {
|
||||
applicantName,
|
||||
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 HTTPServer } from 'http';
|
||||
import logger from './logger.js';
|
||||
import { getFrontendBaseUrl } from './frontendUrl.js';
|
||||
|
||||
let io: SocketServer | null = null;
|
||||
|
||||
@ -11,7 +12,7 @@ let io: SocketServer | null = null;
|
||||
export const initSocket = (httpServer: HTTPServer) => {
|
||||
io = new SocketServer(httpServer, {
|
||||
cors: {
|
||||
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
|
||||
origin: getFrontendBaseUrl(),
|
||||
methods: ['GET', 'POST', 'PUT'],
|
||||
credentials: true
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { Op } from 'sequelize';
|
||||
import { sendEmail } from './email.service.js';
|
||||
import { NotificationService } from '../../services/NotificationService.js';
|
||||
import {
|
||||
import { getFrontendBaseUrl } from './frontendUrl.js';
|
||||
APPLICATION_STAGES,
|
||||
TERMINATION_STAGES,
|
||||
CONSTITUTIONAL_STAGES,
|
||||
@ -13,8 +14,6 @@ import {
|
||||
|
||||
const { RequestParticipant, User, Outlet, District, Dealer } = db;
|
||||
|
||||
const frontendBase = () => process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
|
||||
/** Dealer acknowledgement + internal reviewers after resignation is created. */
|
||||
export async function notifyResignationSubmittedEmails(resignation: any): Promise<void> {
|
||||
const dealerUser = await User.findByPk(resignation.dealerId, {
|
||||
@ -22,7 +21,7 @@ export async function notifyResignationSubmittedEmails(resignation: any): Promis
|
||||
});
|
||||
if (!dealerUser?.email) return;
|
||||
|
||||
const base = frontendBase();
|
||||
const base = getFrontendBaseUrl();
|
||||
const resignationCode = resignation.resignationId || resignation.id;
|
||||
const lwd =
|
||||
resignation.lastOperationalDateSales ||
|
||||
@ -99,7 +98,7 @@ export async function notifyConstitutionalSubmittedEmails(request: any, dealerDi
|
||||
include: [{ model: User, as: 'user', attributes: ['id', 'email', 'fullName', 'mobileNumber'] }]
|
||||
});
|
||||
|
||||
const base = frontendBase();
|
||||
const base = getFrontendBaseUrl();
|
||||
const link = `${base}/constitutional-change/${request.id}`;
|
||||
|
||||
for (const p of participants) {
|
||||
@ -128,7 +127,7 @@ export async function notifyRelocationSubmittedEmails(
|
||||
request: any,
|
||||
submitter: { email: string; fullName?: string | null }
|
||||
): Promise<void> {
|
||||
const base = frontendBase();
|
||||
const base = getFrontendBaseUrl();
|
||||
const code = request.requestId || request.id;
|
||||
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.
|
||||
try {
|
||||
const { notifyStakeholdersOnTransition } = await import('../../common/utils/workflow-email-notifications.js');
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
|
||||
await notifyStakeholdersOnTransition(
|
||||
application.id,
|
||||
@ -583,7 +583,7 @@ export const scheduleInterview = async (req: AuthRequest, res: Response) => {
|
||||
type,
|
||||
scheduledAt: formatIST(scheduledDateObj),
|
||||
meetLink,
|
||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: applicantPhone,
|
||||
ctaLabel: 'View application'
|
||||
}
|
||||
@ -710,7 +710,7 @@ export const scheduleInterview = async (req: AuthRequest, res: Response) => {
|
||||
type,
|
||||
scheduledAt: formatIST(scheduledDateObj),
|
||||
meetLink,
|
||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: pPhone || '',
|
||||
ctaLabel: 'View application'
|
||||
}
|
||||
@ -813,7 +813,7 @@ export const updateInterview = async (req: AuthRequest, res: Response) => {
|
||||
type,
|
||||
scheduledAt: formatIST(interview.scheduleDate),
|
||||
meetLink,
|
||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: application.mobileNumber || '',
|
||||
ctaLabel: 'View Schedule'
|
||||
}
|
||||
@ -840,7 +840,7 @@ export const updateInterview = async (req: AuthRequest, res: Response) => {
|
||||
type,
|
||||
scheduledAt: formatIST(interview.scheduleDate),
|
||||
meetLink,
|
||||
appLink: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: panelist.mobileNumber || '',
|
||||
ctaLabel: 'Open Assessment'
|
||||
}
|
||||
@ -1043,6 +1043,7 @@ export const submitLevel2Feedback = async (req: AuthRequest, res: Response) => {
|
||||
// --- AI Summary ---
|
||||
|
||||
import { ExternalMocksService } from '../../common/utils/externalMocks.service.js';
|
||||
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
export const generateAiSummary = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
|
||||
@ -16,6 +16,7 @@ import { getIO } from '../../common/utils/socket.js';
|
||||
import * as InAppPushNotificationService from '../../common/utils/notification.service.js';
|
||||
import { NotificationService as EmailNotificationService } from '../../services/NotificationService.js';
|
||||
import logger from '../../common/utils/logger.js';
|
||||
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
// --- Helpers ---
|
||||
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 applicationIdLabel = String(resolvedId).slice(0, 12);
|
||||
let dealerNameLabel = 'Record';
|
||||
|
||||
@ -5,6 +5,7 @@ const { EorChecklist, EorChecklistItem, OnboardingDocument, RelocationDocument }
|
||||
import { AuthRequest } from '../../types/express.types.js';
|
||||
import { NotificationService } from '../../services/NotificationService.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. */
|
||||
export const RELOCATION_EOR_DEFAULT_ITEMS = [
|
||||
@ -355,7 +356,7 @@ export const submitAudit = async (req: AuthRequest, res: Response) => {
|
||||
placeholders: {
|
||||
applicantName: app?.applicantName || '',
|
||||
applicationId: app?.applicationId || '',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${checklist.applicationId}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${checklist.applicationId}`,
|
||||
ctaLabel: 'View Application'
|
||||
}
|
||||
}).catch((e: any) => console.error('[EOR] Completion notify failed:', e));
|
||||
@ -379,7 +380,7 @@ export const submitAudit = async (req: AuthRequest, res: Response) => {
|
||||
placeholders: {
|
||||
applicantName: '',
|
||||
applicationId: checklist.relocationId,
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/relocation-requests/${checklist.relocationId}`,
|
||||
link: `${getFrontendBaseUrl()}/relocation-requests/${checklist.relocationId}`,
|
||||
ctaLabel: 'View Request'
|
||||
}
|
||||
}).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 { NotificationService } from '../../services/NotificationService.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) => {
|
||||
try {
|
||||
@ -105,14 +106,14 @@ export const assignAgency = async (req: AuthRequest, res: Response) => {
|
||||
applicationId: application.applicationId,
|
||||
dealerName: application.applicantName || '',
|
||||
participantType: 'FDD Partner',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
ctaLabel: 'View Assignment',
|
||||
phone: phone || ''
|
||||
}
|
||||
}).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) {
|
||||
const applicantAcct = await db.User.findOne({
|
||||
where: { email: application.email },
|
||||
@ -221,7 +222,7 @@ export const uploadReport = async (req: AuthRequest, res: Response) => {
|
||||
dealerName: application.applicantName || '',
|
||||
requestId: application.applicationId,
|
||||
targetStage: 'FDD Review',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: ''
|
||||
}
|
||||
}).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 { WorkflowService } from '../../services/WorkflowService.js';
|
||||
import { NotificationService } from '../../services/NotificationService.js';
|
||||
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
const LOA_STAGE_CODE = 'LOA_APPROVAL';
|
||||
|
||||
@ -101,7 +102,7 @@ export const createRequest = async (req: AuthRequest, res: Response) => {
|
||||
dealerName: application.applicantName || application.applicationId,
|
||||
requestId: application.applicationId,
|
||||
targetStage: 'LOA Approval',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: phone || ''
|
||||
}
|
||||
}).catch((e: any) => console.error('[LOA] DD-Head notify failed:', e));
|
||||
@ -243,7 +244,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
||||
placeholders: {
|
||||
applicantName: app?.applicantName || '',
|
||||
applicationId: app?.applicationId || '',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||
ctaLabel: 'View Application'
|
||||
}
|
||||
}).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 }]
|
||||
});
|
||||
if (appFull?.email) {
|
||||
const portalBaseLoa = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBaseLoa = getFrontendBaseUrl();
|
||||
const dc = (appFull as any).dealerCode;
|
||||
const dealerCodeStr = dc?.salesCode || dc?.serviceCode || 'Available on portal after SAP sync';
|
||||
const applicantAcct = await User.findOne({
|
||||
@ -324,7 +325,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
||||
dealerName: '',
|
||||
requestId,
|
||||
targetStage: 'NBH LOA Approval',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||
phone: phone || ''
|
||||
}
|
||||
}).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 { sendEmail } from '../../common/utils/email.service.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';
|
||||
|
||||
@ -96,7 +97,7 @@ export const acknowledgeRequest = async (req: AuthRequest, res: Response) => {
|
||||
progressPercentage: 90
|
||||
});
|
||||
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
const deposit =
|
||||
(await SecurityDeposit.findOne({
|
||||
where: { applicationId: application.id, depositType: 'SECURITY_DEPOSIT' }
|
||||
@ -182,7 +183,7 @@ export const createRequest = async (req: AuthRequest, res: Response) => {
|
||||
dealerName: application.applicantName || application.applicationId,
|
||||
requestId: application.applicationId,
|
||||
targetStage: 'LOI Approval',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
phone: phone || ''
|
||||
}
|
||||
}).catch((e: any) => console.error('[LOI] DD-Head notify failed:', e));
|
||||
@ -336,7 +337,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
||||
placeholders: {
|
||||
dealerName: application2?.applicantName || '',
|
||||
requestId: application2?.applicationId || '',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||
ctaLabel: 'View Application'
|
||||
}
|
||||
}).catch((e: any) => console.error('[LOI] finance/admin notify failed:', e));
|
||||
@ -358,7 +359,7 @@ export const approveRequest = async (req: AuthRequest, res: Response) => {
|
||||
dealerName: '',
|
||||
requestId: String(request.applicationId),
|
||||
targetStage: 'NBH LOI Approval',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${request.applicationId}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${request.applicationId}`,
|
||||
phone: phone || ''
|
||||
}
|
||||
}).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',
|
||||
applicationId: application.applicationId,
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/prospect-login`,
|
||||
link: `${getFrontendBaseUrl()}/prospect-login`,
|
||||
ctaLabel: 'View Your Application'
|
||||
}
|
||||
).catch((e: any) => console.error('[LOI] Applicant email failed:', e));
|
||||
@ -493,7 +494,7 @@ export const generateDocument = async (req: AuthRequest, res: Response) => {
|
||||
placeholders: {
|
||||
applicantName: application.applicantName || '',
|
||||
applicationId: application.applicationId,
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
ctaLabel: 'View Application'
|
||||
}
|
||||
}).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) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
await NotificationService.notify(null, application.email, {
|
||||
title: `Statutory documents — ${application.applicationId}`,
|
||||
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,
|
||||
location: displayLocation,
|
||||
applicationId,
|
||||
link: isOpportunityAvailable
|
||||
? `${getFrontendBaseUrl()}/questionnaire/${applicationId}`
|
||||
: undefined,
|
||||
ctaLabel: isOpportunityAvailable ? 'Complete Questionnaire' : undefined,
|
||||
phone
|
||||
}
|
||||
}).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) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
await NotificationService.notify(null, application.email, {
|
||||
title: `Document received — ${application.applicationId}`,
|
||||
message: `We received your upload: ${documentType}.`,
|
||||
@ -846,7 +850,7 @@ export const bulkShortlist = async (req: AuthRequest, res: Response) => {
|
||||
application.applicationId
|
||||
).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({
|
||||
where: { email: application.email },
|
||||
attributes: ['id', 'mobileNumber']
|
||||
@ -1149,7 +1153,7 @@ export const assignArchitectureTeam = async (req: AuthRequest, res: Response) =>
|
||||
});
|
||||
|
||||
const architect = await User.findByPk(targetUserId, { attributes: ['fullName'] });
|
||||
const portalBaseArch = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBaseArch = getFrontendBaseUrl();
|
||||
if (application.email) {
|
||||
await NotificationService.notify(null, application.email, {
|
||||
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 { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
export const generateDealerCodes = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
@ -1746,7 +1751,7 @@ export const sendBulkDocumentReminders = async (req: AuthRequest, res: Response)
|
||||
applicationId: app.applicationId,
|
||||
pendingDocuments: pendingDocuments || undefined,
|
||||
dueDate: dueDate || formatDueDateDaysFromNow(7),
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${app.id}`
|
||||
link: `${getFrontendBaseUrl()}/applications/${app.id}`
|
||||
});
|
||||
await safeAuditLogCreate({
|
||||
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, {
|
||||
applicationId: app.applicationId,
|
||||
dueDate: dueDate || formatDueDateDaysFromNow(7),
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/prospect-login`
|
||||
link: `${getFrontendBaseUrl()}/prospect-login`
|
||||
});
|
||||
sent++;
|
||||
}
|
||||
@ -1842,7 +1847,7 @@ export const rejectOnboardingDocument = async (req: AuthRequest, res: Response)
|
||||
await doc.update({ status: 'rejected' });
|
||||
|
||||
if (application.email) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
await NotificationService.notify(null, application.email, {
|
||||
title: `Document re-upload required — ${application.applicationId}`,
|
||||
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 { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
||||
import { notifyConstitutionalSubmittedEmails } from '../../common/utils/workflow-email-notifications.js';
|
||||
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
const STRUCTURE_TARGET_VALUES = new Set<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) {
|
||||
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 { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
|
||||
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
|
||||
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
// Removed generateResignationId and moved to NomenclatureService
|
||||
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
|
||||
// so they can communicate official closure to the dealer (field hierarchy).
|
||||
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;
|
||||
setImmediate(() =>
|
||||
notifyStakeholdersOnTransition(
|
||||
|
||||
@ -11,8 +11,9 @@ import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorkno
|
||||
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
|
||||
import { NomenclatureService } from '../../common/utils/nomenclature.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 = {
|
||||
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 { NotificationService } from '../../services/NotificationService.js';
|
||||
import { sendEmail } from '../../common/utils/email.service.js';
|
||||
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
|
||||
|
||||
const resolveTerminationUuid = async (id: string) => {
|
||||
const { resolvedId } = await resolveEntityUuidByType(db as any, id, 'termination');
|
||||
@ -101,7 +102,7 @@ export const createTermination = async (req: AuthRequest, res: Response, next: N
|
||||
dealerName: '',
|
||||
requestId: termination.requestId,
|
||||
reason: reason || '',
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/termination/${termination.id}`,
|
||||
link: `${getFrontendBaseUrl()}/termination/${termination.id}`,
|
||||
ctaLabel: 'Review Request',
|
||||
phone: phone || ''
|
||||
}
|
||||
@ -758,7 +759,7 @@ export const issueScn = async (req: AuthRequest, res: Response, next: NextFuncti
|
||||
{
|
||||
dealerName: dealerUser.fullName || 'Dealer',
|
||||
requestId: termination.requestId,
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/dealer-termination/${termination.id}`,
|
||||
link: `${getFrontendBaseUrl()}/dealer-termination/${termination.id}`,
|
||||
ctaLabel: 'View Notice'
|
||||
}
|
||||
).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: {
|
||||
dealerName: dealerUser?.fullName || '',
|
||||
requestId: termination.requestId,
|
||||
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/termination/${termination.id}`,
|
||||
link: `${getFrontendBaseUrl()}/termination/${termination.id}`,
|
||||
ctaLabel: 'View Case'
|
||||
}
|
||||
}).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 errorHandler from './common/middleware/errorHandler.js';
|
||||
import logger from './common/utils/logger.js';
|
||||
import { getFrontendBaseUrl } from './common/utils/frontendUrl.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@ -63,14 +64,14 @@ app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
...helmet.contentSecurityPolicy.getDefaultDirectives(),
|
||||
"frame-ancestors": ["'self'", process.env.FRONTEND_URL || 'http://localhost:5173'],
|
||||
"frame-ancestors": ["'self'", getFrontendBaseUrl()],
|
||||
},
|
||||
},
|
||||
crossOriginEmbedderPolicy: false,
|
||||
crossOriginResourcePolicy: { policy: "cross-origin" }
|
||||
}));
|
||||
const allowedOrigins = new Set<string>([
|
||||
process.env.FRONTEND_URL || 'http://localhost:5173',
|
||||
getFrontendBaseUrl(),
|
||||
'http://localhost:5173'
|
||||
]);
|
||||
app.use(cors({
|
||||
|
||||
@ -5,6 +5,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
||||
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
||||
import { mapConstitutionalChangeTypeToDealerProfile } from '../common/utils/constitutionalNormalize.js';
|
||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
export class ConstitutionalWorkflowService {
|
||||
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'] });
|
||||
if (dealerUser?.email) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
const remarkText = String(remarks ?? '').trim() || 'N/A';
|
||||
|
||||
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { sendEmail } from '../common/utils/email.service.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
import db from '../database/models/index.js';
|
||||
const { Notification, PushSubscription } = db;
|
||||
|
||||
@ -147,7 +148,6 @@ export class NotificationService {
|
||||
applicantName: string,
|
||||
options?: { location?: string; link?: string }
|
||||
) {
|
||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
await this.notify(null, email, {
|
||||
title: 'Action Required: Complete your Dealership Questionnaire',
|
||||
message: `Hi ${applicantName}, please complete the questionnaire to proceed with your application.`,
|
||||
@ -157,8 +157,8 @@ export class NotificationService {
|
||||
applicantName,
|
||||
phone,
|
||||
location: options?.location ?? '',
|
||||
link: options?.link ?? `${base}/login`,
|
||||
ctaLabel: 'Complete Now'
|
||||
link: options?.link ?? `${getFrontendBaseUrl()}/prospect-login`,
|
||||
ctaLabel: 'Complete Questionnaire'
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -172,7 +172,6 @@ export class NotificationService {
|
||||
applicantName: string,
|
||||
options: { applicationId: string; pendingDocuments?: string; dueDate?: string; link?: string }
|
||||
) {
|
||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const channels: ('email' | 'whatsapp')[] = ['email'];
|
||||
if (phone) channels.push('whatsapp');
|
||||
await this.notify(null, email, {
|
||||
@ -187,7 +186,7 @@ export class NotificationService {
|
||||
options.pendingDocuments ||
|
||||
'Please sign in to the Dealer Portal to view your personalised document checklist.',
|
||||
dueDate: options.dueDate || 'within seven calendar days',
|
||||
link: options.link || `${base}/applications`,
|
||||
link: options.link || `${getFrontendBaseUrl()}/applications`,
|
||||
ctaLabel: 'Upload documents',
|
||||
phone: phone || ''
|
||||
}
|
||||
@ -203,7 +202,6 @@ export class NotificationService {
|
||||
applicantName: string,
|
||||
options: { applicationId: string; link?: string; dueDate?: string }
|
||||
) {
|
||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const channels: ('email' | 'whatsapp')[] = ['email'];
|
||||
if (phone) channels.push('whatsapp');
|
||||
await this.notify(null, email, {
|
||||
@ -215,7 +213,7 @@ export class NotificationService {
|
||||
applicantName,
|
||||
applicationId: options.applicationId,
|
||||
dueDate: options.dueDate || 'within seven calendar days',
|
||||
link: options.link || `${base}/prospect-login`,
|
||||
link: options.link || `${getFrontendBaseUrl()}/prospect-login`,
|
||||
ctaLabel: 'Acknowledge LOI',
|
||||
phone: phone || ''
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
msUntilLwdMorning
|
||||
} from '../common/utils/offboardingLwd.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];
|
||||
|
||||
@ -45,7 +46,7 @@ export class OffboardingLwdReminderService {
|
||||
lwd: Date | string;
|
||||
detailPath: string;
|
||||
}): Promise<void> {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
const lwdStr = formatLwdDisplay(params.lwd);
|
||||
const link = `${portalBase}${params.detailPath}`;
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { safeAuditLogCreate } from './applicationAuditLog.service.js';
|
||||
import { pickApplicationAuditContext } from './applicationAuditLog.service.js';
|
||||
import logger from '../common/utils/logger.js';
|
||||
import { getQuestionnaireReminderSettings } from './questionnaireReminderSettings.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
const TEMPLATE = 'QUESTIONNAIRE_REMINDER';
|
||||
|
||||
@ -67,8 +68,7 @@ export class QuestionnaireReminderService {
|
||||
}
|
||||
|
||||
static async sendReminderForApplication(application: any, source: 'scheduled' | 'manual' = 'scheduled') {
|
||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const link = `${base}/questionnaire/${application.applicationId}`;
|
||||
const link = `${getFrontendBaseUrl()}/questionnaire/${application.applicationId}`;
|
||||
|
||||
await NotificationService.sendQuestionnaireReminder(
|
||||
application.email,
|
||||
|
||||
@ -6,6 +6,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
||||
import logger from '../common/utils/logger.js';
|
||||
import { NotificationService } from './NotificationService.js';
|
||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
export class RelocationWorkflowService {
|
||||
/**
|
||||
@ -102,7 +103,7 @@ export class RelocationWorkflowService {
|
||||
await request.reload();
|
||||
const dealerUser = await User.findByPk(request.dealerId, { attributes: ['id', 'email', 'fullName'] });
|
||||
if (dealerUser?.email) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
const stageLabel = request.currentStage || request.status || targetStatus;
|
||||
|
||||
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 { NomenclatureService } from '../common/utils/nomenclature.js';
|
||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
|
||||
export class ResignationWorkflowService {
|
||||
@ -96,7 +97,7 @@ export class ResignationWorkflowService {
|
||||
});
|
||||
|
||||
if (user) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
|
||||
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 type { WorkflowActivityRequestType } from '../common/utils/workflowWorknote.js';
|
||||
import {
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
claimSlaNotificationDispatch,
|
||||
SlaDispatchKeys,
|
||||
updateSlaDispatchRecipientCount,
|
||||
@ -496,7 +497,7 @@ export class SLAService {
|
||||
}
|
||||
|
||||
private static linkForEntity(entityType: string, entityId: string): string {
|
||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const base = getFrontendBaseUrl();
|
||||
switch (entityType) {
|
||||
case 'application':
|
||||
return `${base}/applications/${entityId}`;
|
||||
|
||||
@ -4,6 +4,7 @@ import { Op } from 'sequelize';
|
||||
import { slaConfigLookupNames } from '../common/config/slaStageCatalog.js';
|
||||
import { computeSlaTrackView, formatSlaDuration, type SlaBucket } from '../common/utils/slaMetrics.js';
|
||||
import { SlaStatusService } from './SlaStatusService.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
export type { SlaBucket };
|
||||
|
||||
@ -20,7 +21,7 @@ function moduleFromEntityType(entityType: string): string {
|
||||
}
|
||||
|
||||
function linkForEntity(entityType: string, entityId: string): string {
|
||||
const base = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const base = getFrontendBaseUrl();
|
||||
switch (entityType) {
|
||||
case 'application':
|
||||
return `${base}/applications/${entityId}`;
|
||||
|
||||
@ -11,6 +11,7 @@ import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.
|
||||
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
|
||||
import { ParticipantService } from './ParticipantService.js';
|
||||
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
export class TerminationWorkflowService {
|
||||
/**
|
||||
@ -103,7 +104,7 @@ export class TerminationWorkflowService {
|
||||
});
|
||||
|
||||
if (user) {
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
const dealerPortalLink = `${portalBase}/termination/${termination.id}`;
|
||||
const isScnIssued = targetStage === TERMINATION_STAGES.SCN_ISSUED;
|
||||
|
||||
@ -263,7 +264,7 @@ export class TerminationWorkflowService {
|
||||
attributes: ['id', 'email', 'fullName', 'mobileNumber']
|
||||
});
|
||||
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
const portalBase = getFrontendBaseUrl();
|
||||
for (const u of adminUsers) {
|
||||
const phone = u.mobileNumber || null;
|
||||
await NotificationService.notify(u.id, u.email, {
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
} from '../common/config/constants.js';
|
||||
import { NotificationService } from './NotificationService.js';
|
||||
import { pickApplicationAuditContext, safeAuditLogCreate } from './applicationAuditLog.service.js';
|
||||
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';
|
||||
|
||||
export class WorkflowService {
|
||||
/**
|
||||
@ -150,11 +151,13 @@ export class WorkflowService {
|
||||
if (targetStatus === 'LOA Issued') templateCode = 'LOA_ISSUED';
|
||||
if (targetStatus === 'Dealer Code Generated') templateCode = 'DEALER_CODE_READY';
|
||||
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
|
||||
let ctaLabel = 'View application';
|
||||
if (templateCode === 'LOI_ISSUED') ctaLabel = 'View LOI';
|
||||
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, {
|
||||
title: `Onboarding Update: ${targetStatus}`,
|
||||
@ -165,10 +168,12 @@ export class WorkflowService {
|
||||
status: targetStatus,
|
||||
applicantName: application.applicantName,
|
||||
applicationId: application.applicationId,
|
||||
location: application.preferredLocation || application.city || '',
|
||||
rejectionReason: reason || 'N/A',
|
||||
reason: reason || 'N/A',
|
||||
salesCode: application.dealerCode?.salesCode || 'N/A',
|
||||
serviceCode: application.dealerCode?.serviceCode || 'N/A',
|
||||
link: `${portalBase}/applications/${application.id}`,
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`,
|
||||
ctaLabel,
|
||||
phone: user?.mobileNumber || user?.phone || application?.mobileNumber || application?.phone || ''
|
||||
},
|
||||
@ -179,8 +184,6 @@ export class WorkflowService {
|
||||
// Stakeholder Notifications
|
||||
tasks.push((async () => {
|
||||
const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
|
||||
const portalBase = process.env.FRONTEND_URL || 'http://localhost:5173';
|
||||
|
||||
let actionUserFullName = 'System';
|
||||
if (userId) {
|
||||
const actionUser = await User.findByPk(userId, { attributes: ['fullName'] });
|
||||
@ -198,7 +201,7 @@ export class WorkflowService {
|
||||
actionUserFullName,
|
||||
action: reason || `Transitioned to ${targetStatus}`,
|
||||
remarks: reason || 'N/A',
|
||||
link: `${portalBase}/applications/${application.id}`
|
||||
link: `${getFrontendBaseUrl()}/applications/${application.id}`
|
||||
}
|
||||
);
|
||||
})().catch(e => console.error('[WorkflowService] stakeholder notify failed:', e)));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user