add the title in email subject and statd implementing admin templates

This commit is contained in:
laxmanhalaki 2026-01-22 19:21:26 +05:30
parent d1ae0ffaec
commit be220bbb0c
8 changed files with 305 additions and 46 deletions

View File

@ -0,0 +1,56 @@
import { Request, Response } from 'express';
import { WorkflowTemplate } from '../models';
import logger from '../utils/logger';
export const createTemplate = async (req: Request, res: Response) => {
try {
const { name, description, category, priority, estimatedTime, approvers, suggestedSLA } = req.body;
const userId = (req as any).user?.userId;
const template = await WorkflowTemplate.create({
name,
description,
category,
priority,
estimatedTime,
approvers,
suggestedSLA,
createdBy: userId,
isActive: true
});
res.status(201).json({
success: true,
message: 'Workflow template created successfully',
data: template
});
} catch (error) {
logger.error('Error creating workflow template:', error);
res.status(500).json({
success: false,
message: 'Failed to create workflow template',
error: error instanceof Error ? error.message : 'Unknown error'
});
}
};
export const getTemplates = async (req: Request, res: Response) => {
try {
const templates = await WorkflowTemplate.findAll({
where: { isActive: true },
order: [['createdAt', 'DESC']]
});
res.status(200).json({
success: true,
data: templates
});
} catch (error) {
logger.error('Error fetching workflow templates:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch workflow templates',
error: error instanceof Error ? error.message : 'Unknown error'
});
}
};

View File

@ -0,0 +1,84 @@
import { QueryInterface, DataTypes } from 'sequelize';
export async function up(queryInterface: QueryInterface): Promise<void> {
// Drop table if exists to ensure schema is correct (CASCADE to remove FKs)
await queryInterface.dropTable('workflow_templates', { cascade: true });
// Create Enum for Template Priority
await queryInterface.sequelize.query(`DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'enum_template_priority') THEN
CREATE TYPE enum_template_priority AS ENUM ('low', 'medium', 'high');
END IF;
END$$;`);
await queryInterface.createTable('workflow_templates', {
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4
},
name: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
category: {
type: DataTypes.STRING,
defaultValue: 'General'
},
priority: {
type: 'enum_template_priority',
defaultValue: 'medium'
},
estimated_time: {
type: DataTypes.STRING,
defaultValue: 'Variable'
},
approvers: {
type: DataTypes.JSONB,
defaultValue: []
},
suggested_sla: {
type: DataTypes.INTEGER,
defaultValue: 24
},
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
created_by: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'user_id'
}
},
fields: {
type: DataTypes.JSONB,
defaultValue: {}
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
});
// Add index on created_by
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "workflow_templates_created_by" ON "workflow_templates" ("created_by");');
}
export async function down(queryInterface: QueryInterface): Promise<void> {
await queryInterface.dropTable('workflow_templates');
await queryInterface.sequelize.query('DROP TYPE IF EXISTS enum_template_priority;');
}

View File

@ -0,0 +1,99 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '@config/database';
import { User } from './User';
export class WorkflowTemplate extends Model {
public id!: string;
public name!: string;
public description!: string;
public category!: string;
public priority!: 'low' | 'medium' | 'high';
public estimatedTime!: string;
public approvers!: any[];
public suggestedSLA!: number;
public isActive!: boolean;
public createdBy!: string;
public fields!: any;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
WorkflowTemplate.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
category: {
type: DataTypes.STRING,
defaultValue: 'General'
},
priority: {
type: DataTypes.ENUM('low', 'medium', 'high'),
defaultValue: 'medium'
},
estimatedTime: {
type: DataTypes.STRING,
defaultValue: 'Variable',
field: 'estimated_time'
},
approvers: {
type: DataTypes.JSONB,
defaultValue: []
},
suggestedSLA: {
type: DataTypes.INTEGER,
defaultValue: 24,
comment: 'In hours',
field: 'suggested_sla'
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
field: 'is_active'
},
createdBy: {
type: DataTypes.UUID,
allowNull: true,
field: 'created_by',
references: {
model: 'users',
key: 'user_id'
}
},
fields: {
type: DataTypes.JSONB,
defaultValue: {}
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
field: 'created_at'
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
field: 'updated_at'
}
},
{
sequelize,
tableName: 'workflow_templates',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
}
);

View File

@ -16,6 +16,7 @@ import { Notification } from './Notification';
import ConclusionRemark from './ConclusionRemark'; import ConclusionRemark from './ConclusionRemark';
import RequestSummary from './RequestSummary'; import RequestSummary from './RequestSummary';
import SharedSummary from './SharedSummary'; import SharedSummary from './SharedSummary';
import { WorkflowTemplate } from './WorkflowTemplate';
// Define associations // Define associations
const defineAssociations = () => { const defineAssociations = () => {
@ -138,7 +139,8 @@ export {
Notification, Notification,
ConclusionRemark, ConclusionRemark,
RequestSummary, RequestSummary,
SharedSummary SharedSummary,
WorkflowTemplate
}; };
// Export default sequelize instance // Export default sequelize instance

View File

@ -13,6 +13,7 @@ import dashboardRoutes from './dashboard.routes';
import notificationRoutes from './notification.routes'; import notificationRoutes from './notification.routes';
import conclusionRoutes from './conclusion.routes'; import conclusionRoutes from './conclusion.routes';
import aiRoutes from './ai.routes'; import aiRoutes from './ai.routes';
import workflowTemplateRoutes from './workflowTemplate.routes';
const router = Router(); const router = Router();
@ -40,6 +41,7 @@ router.use('/notifications', notificationRoutes);
router.use('/conclusions', conclusionRoutes); router.use('/conclusions', conclusionRoutes);
router.use('/ai', aiRoutes); router.use('/ai', aiRoutes);
router.use('/summaries', summaryRoutes); router.use('/summaries', summaryRoutes);
router.use('/templates', workflowTemplateRoutes);
// TODO: Add other route modules as they are implemented // TODO: Add other route modules as they are implemented
// router.use('/approvals', approvalRoutes); // router.use('/approvals', approvalRoutes);

View File

@ -0,0 +1,14 @@
import { Router } from 'express';
import { createTemplate, getTemplates } from '../controllers/workflowTemplate.controller';
import { authenticateToken } from '../middlewares/auth.middleware';
import { requireAdmin } from '../middlewares/authorization.middleware';
const router = Router();
// Public route to get templates (authenticated users)
router.get('/', authenticateToken, getTemplates);
// Admin only route to create templates
router.post('/', authenticateToken, requireAdmin, createTemplate);
export default router;

View File

@ -49,13 +49,13 @@ async function checkAndCreateDatabase(): Promise<boolean> {
if (result.rows.length === 0) { if (result.rows.length === 0) {
console.log(`📦 Database '${DB_NAME}' not found. Creating...`); console.log(`📦 Database '${DB_NAME}' not found. Creating...`);
// Create database // Create database
await client.query(`CREATE DATABASE "${DB_NAME}"`); await client.query(`CREATE DATABASE "${DB_NAME}"`);
console.log(`✅ Database '${DB_NAME}' created successfully!`); console.log(`✅ Database '${DB_NAME}' created successfully!`);
await client.end(); await client.end();
// Connect to new database and install extensions // Connect to new database and install extensions
const newDbClient = new Client({ const newDbClient = new Client({
host: DB_HOST, host: DB_HOST,
@ -64,13 +64,13 @@ async function checkAndCreateDatabase(): Promise<boolean> {
password: DB_PASSWORD, password: DB_PASSWORD,
database: DB_NAME, database: DB_NAME,
}); });
await newDbClient.connect(); await newDbClient.connect();
console.log('📦 Installing uuid-ossp extension...'); console.log('📦 Installing uuid-ossp extension...');
await newDbClient.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); await newDbClient.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
console.log('✅ Extension installed!'); console.log('✅ Extension installed!');
await newDbClient.end(); await newDbClient.end();
return true; // Database was created return true; // Database was created
} else { } else {
console.log(`✅ Database '${DB_NAME}' already exists.`); console.log(`✅ Database '${DB_NAME}' already exists.`);
@ -87,7 +87,7 @@ async function checkAndCreateDatabase(): Promise<boolean> {
async function runMigrations(): Promise<void> { async function runMigrations(): Promise<void> {
try { try {
console.log('🔄 Checking and running pending migrations...'); console.log('🔄 Checking and running pending migrations...');
// Import all migrations using require for CommonJS compatibility // Import all migrations using require for CommonJS compatibility
// Some migrations use module.exports, others use export // Some migrations use module.exports, others use export
const m0 = require('../migrations/2025103000-create-users'); const m0 = require('../migrations/2025103000-create-users');
@ -120,7 +120,8 @@ async function runMigrations(): Promise<void> {
const m27 = require('../migrations/20250127-migrate-in-progress-to-pending'); const m27 = require('../migrations/20250127-migrate-in-progress-to-pending');
const m28 = require('../migrations/20250130-migrate-to-vertex-ai'); const m28 = require('../migrations/20250130-migrate-to-vertex-ai');
const m29 = require('../migrations/20251203-add-user-notification-preferences'); const m29 = require('../migrations/20251203-add-user-notification-preferences');
const m30 = require('../migrations/20260122-create-workflow-templates');
const migrations = [ const migrations = [
{ name: '2025103000-create-users', module: m0 }, { name: '2025103000-create-users', module: m0 },
{ name: '2025103001-create-workflow-requests', module: m1 }, { name: '2025103001-create-workflow-requests', module: m1 },
@ -152,10 +153,11 @@ async function runMigrations(): Promise<void> {
{ name: '20250127-migrate-in-progress-to-pending', module: m27 }, { name: '20250127-migrate-in-progress-to-pending', module: m27 },
{ name: '20250130-migrate-to-vertex-ai', module: m28 }, { name: '20250130-migrate-to-vertex-ai', module: m28 },
{ name: '20251203-add-user-notification-preferences', module: m29 }, { name: '20251203-add-user-notification-preferences', module: m29 },
{ name: '20260122-create-workflow-templates', module: m30 },
]; ];
const queryInterface = sequelize.getQueryInterface(); const queryInterface = sequelize.getQueryInterface();
// Ensure migrations tracking table exists // Ensure migrations tracking table exists
const tables = await queryInterface.showAllTables(); const tables = await queryInterface.showAllTables();
if (!tables.includes('migrations')) { if (!tables.includes('migrations')) {
@ -167,34 +169,34 @@ async function runMigrations(): Promise<void> {
) )
`); `);
} }
// Get already executed migrations // Get already executed migrations
const executedResults = await sequelize.query<{ name: string }>( const executedResults = await sequelize.query<{ name: string }>(
'SELECT name FROM migrations ORDER BY id', 'SELECT name FROM migrations ORDER BY id',
{ type: QueryTypes.SELECT } { type: QueryTypes.SELECT }
); );
const executedMigrations = executedResults.map(r => r.name); const executedMigrations = executedResults.map(r => r.name);
// Find pending migrations // Find pending migrations
const pendingMigrations = migrations.filter( const pendingMigrations = migrations.filter(
m => !executedMigrations.includes(m.name) m => !executedMigrations.includes(m.name)
); );
if (pendingMigrations.length === 0) { if (pendingMigrations.length === 0) {
console.log('✅ Migrations up-to-date'); console.log('✅ Migrations up-to-date');
return; return;
} }
console.log(`🔄 Running ${pendingMigrations.length} pending migration(s)...`); console.log(`🔄 Running ${pendingMigrations.length} pending migration(s)...`);
// Run each pending migration // Run each pending migration
for (const migration of pendingMigrations) { for (const migration of pendingMigrations) {
try { try {
console.log(`${migration.name}`); console.log(`${migration.name}`);
// Call the up function - works for both module.exports and export styles // Call the up function - works for both module.exports and export styles
await migration.module.up(queryInterface); await migration.module.up(queryInterface);
// Mark as executed // Mark as executed
await sequelize.query( await sequelize.query(
'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING', 'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING',
@ -209,7 +211,7 @@ async function runMigrations(): Promise<void> {
throw error; throw error;
} }
} }
console.log(`✅ Applied ${pendingMigrations.length} migration(s)`); console.log(`✅ Applied ${pendingMigrations.length} migration(s)`);
} catch (error: any) { } catch (error: any) {
console.error('❌ Migration failed:', error.message); console.error('❌ Migration failed:', error.message);
@ -246,9 +248,9 @@ async function autoSetup(): Promise<void> {
console.log('\n========================================'); console.log('\n========================================');
console.log('✅ Setup completed successfully!'); console.log('✅ Setup completed successfully!');
console.log('========================================\n'); console.log('========================================\n');
console.log('📝 Note: Admin configurations will be auto-seeded on server start if table is empty.\n'); console.log('📝 Note: Admin configurations will be auto-seeded on server start if table is empty.\n');
if (wasCreated) { if (wasCreated) {
console.log('💡 Next steps:'); console.log('💡 Next steps:');
console.log(' 1. Server will start automatically'); console.log(' 1. Server will start automatically');
@ -256,7 +258,7 @@ async function autoSetup(): Promise<void> {
console.log(' 3. Run this SQL to make yourself admin:'); console.log(' 3. Run this SQL to make yourself admin:');
console.log(` UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@royalenfield.com';\n`); console.log(` UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@royalenfield.com';\n`);
} }
} catch (error: any) { } catch (error: any) {
console.error('\n========================================'); console.error('\n========================================');
console.error('❌ Setup failed!'); console.error('❌ Setup failed!');

View File

@ -101,7 +101,7 @@ export class EmailNotificationService {
}; };
const html = getRequestCreatedEmail(data); const html = getRequestCreatedEmail(data);
const subject = `[${requestData.requestNumber}] Request Created Successfully`; const subject = `${requestData.requestNumber} - ${requestData.title} - Request Created Successfully`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: initiatorData.email, to: initiatorData.email,
@ -144,9 +144,9 @@ export class EmailNotificationService {
// Multi-level approval email // Multi-level approval email
const chainData: ApprovalChainItem[] = approvalChain.map((level: any) => ({ const chainData: ApprovalChainItem[] = approvalChain.map((level: any) => ({
name: level.approverName || level.approverEmail, name: level.approverName || level.approverEmail,
status: level.status === 'APPROVED' ? 'approved' status: level.status === 'APPROVED' ? 'approved'
: level.levelNumber === approverData.levelNumber ? 'current' : level.levelNumber === approverData.levelNumber ? 'current'
: level.levelNumber < approverData.levelNumber ? 'pending' : level.levelNumber < approverData.levelNumber ? 'pending'
: 'awaiting', : 'awaiting',
date: level.approvedAt ? this.formatDate(level.approvedAt) : undefined, date: level.approvedAt ? this.formatDate(level.approvedAt) : undefined,
levelNumber: level.levelNumber levelNumber: level.levelNumber
@ -170,7 +170,7 @@ export class EmailNotificationService {
}; };
const html = getMultiApproverRequestEmail(data); const html = getMultiApproverRequestEmail(data);
const subject = `[${requestData.requestNumber}] Multi-Level Approval Request - Your Turn`; const subject = `${requestData.requestNumber} - ${requestData.title} - Multi-Level Approval Request - Your Turn`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: approverData.email, to: approverData.email,
@ -198,7 +198,7 @@ export class EmailNotificationService {
}; };
const html = getApprovalRequestEmail(data); const html = getApprovalRequestEmail(data);
const subject = `[${requestData.requestNumber}] Approval Request - Action Required`; const subject = `${requestData.requestNumber} - ${requestData.title} - Approval Request - Action Required`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: approverData.email, to: approverData.email,
@ -252,7 +252,7 @@ export class EmailNotificationService {
}; };
const html = getApprovalConfirmationEmail(data); const html = getApprovalConfirmationEmail(data);
const subject = `[${requestData.requestNumber}] Request Approved${isFinalApproval ? ' - All Approvals Complete' : ''}`; const subject = `${requestData.requestNumber} - ${requestData.title} - Request Approved${isFinalApproval ? ' - All Approvals Complete' : ''}`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: initiatorData.email, to: initiatorData.email,
@ -303,7 +303,7 @@ export class EmailNotificationService {
}; };
const html = getRejectionNotificationEmail(data); const html = getRejectionNotificationEmail(data);
const subject = `[${requestData.requestNumber}] Request Rejected`; const subject = `${requestData.requestNumber} - ${requestData.title} - Request Rejected`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: initiatorData.email, to: initiatorData.email,
@ -344,9 +344,9 @@ export class EmailNotificationService {
} }
// Determine urgency level based on threshold // Determine urgency level based on threshold
const urgencyLevel = tatInfo.thresholdPercentage >= 75 ? 'high' const urgencyLevel = tatInfo.thresholdPercentage >= 75 ? 'high'
: tatInfo.thresholdPercentage >= 50 ? 'medium' : tatInfo.thresholdPercentage >= 50 ? 'medium'
: 'low'; : 'low';
// Get initiator name - try from requestData first, then fetch if needed // Get initiator name - try from requestData first, then fetch if needed
let initiatorName = requestData.initiatorName || requestData.initiator?.displayName || 'Initiator'; let initiatorName = requestData.initiatorName || requestData.initiator?.displayName || 'Initiator';
@ -379,7 +379,7 @@ export class EmailNotificationService {
}; };
const html = getTATReminderEmail(data); const html = getTATReminderEmail(data);
const subject = `[${requestData.requestNumber}] TAT Reminder - ${tatInfo.thresholdPercentage}% Elapsed`; const subject = `${requestData.requestNumber} - ${requestData.title} - TAT Reminder - ${tatInfo.thresholdPercentage}% Elapsed`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: approverData.email, to: approverData.email,
@ -449,7 +449,7 @@ export class EmailNotificationService {
}; };
const html = getTATBreachedEmail(data); const html = getTATBreachedEmail(data);
const subject = `[${requestData.requestNumber}] TAT BREACHED - Immediate Action Required`; const subject = `${requestData.requestNumber} - ${requestData.title} - TAT BREACHED - Immediate Action Required`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: approverData.email, to: approverData.email,
@ -496,8 +496,8 @@ export class EmailNotificationService {
} }
const isAutoResumed = !resumedByData || resumedByData.userId === 'system'; const isAutoResumed = !resumedByData || resumedByData.userId === 'system';
const resumedByText = isAutoResumed const resumedByText = isAutoResumed
? 'automatically' ? 'automatically'
: `by ${resumedByData.displayName || resumedByData.email}`; : `by ${resumedByData.displayName || resumedByData.email}`;
const data: WorkflowResumedData = { const data: WorkflowResumedData = {
@ -509,7 +509,7 @@ export class EmailNotificationService {
resumedTime: this.formatTime(new Date()), resumedTime: this.formatTime(new Date()),
pausedDuration: pauseDuration, pausedDuration: pauseDuration,
currentApprover: approverData.displayName || approverData.email, currentApprover: approverData.displayName || approverData.email,
newTATDeadline: requestData.tatDeadline newTATDeadline: requestData.tatDeadline
? this.formatDate(requestData.tatDeadline) + ' ' + this.formatTime(requestData.tatDeadline) ? this.formatDate(requestData.tatDeadline) + ' ' + this.formatTime(requestData.tatDeadline)
: 'To be determined', : 'To be determined',
isApprover: true, isApprover: true,
@ -518,7 +518,7 @@ export class EmailNotificationService {
}; };
const html = getWorkflowResumedEmail(data); const html = getWorkflowResumedEmail(data);
const subject = `[${requestData.requestNumber}] Workflow Resumed - Action Required`; const subject = `${requestData.requestNumber} - ${requestData.title} - Workflow Resumed - Action Required`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: approverData.email, to: approverData.email,
@ -565,8 +565,8 @@ export class EmailNotificationService {
} }
const isAutoResumed = !resumedByData || resumedByData.userId === 'system' || !resumedByData.userId; const isAutoResumed = !resumedByData || resumedByData.userId === 'system' || !resumedByData.userId;
const resumedByText = isAutoResumed const resumedByText = isAutoResumed
? 'automatically' ? 'automatically'
: `by ${resumedByData.displayName || resumedByData.email || resumedByData.name || 'User'}`; : `by ${resumedByData.displayName || resumedByData.email || resumedByData.name || 'User'}`;
const data: WorkflowResumedData = { const data: WorkflowResumedData = {
@ -578,7 +578,7 @@ export class EmailNotificationService {
resumedTime: this.formatTime(new Date()), resumedTime: this.formatTime(new Date()),
pausedDuration: pauseDuration, pausedDuration: pauseDuration,
currentApprover: approverData?.displayName || approverData?.email || 'Current Approver', currentApprover: approverData?.displayName || approverData?.email || 'Current Approver',
newTATDeadline: requestData.tatDeadline newTATDeadline: requestData.tatDeadline
? this.formatDate(requestData.tatDeadline) + ' ' + this.formatTime(requestData.tatDeadline) ? this.formatDate(requestData.tatDeadline) + ' ' + this.formatTime(requestData.tatDeadline)
: 'To be determined', : 'To be determined',
isApprover: false, // This is for initiator isApprover: false, // This is for initiator
@ -587,7 +587,7 @@ export class EmailNotificationService {
}; };
const html = getWorkflowResumedEmail(data); const html = getWorkflowResumedEmail(data);
const subject = `[${requestData.requestNumber}] Workflow Resumed`; const subject = `${requestData.requestNumber} - ${requestData.title} - Workflow Resumed`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: initiatorData.email, to: initiatorData.email,
@ -665,7 +665,7 @@ export class EmailNotificationService {
}; };
const html = getRequestClosedEmail(data); const html = getRequestClosedEmail(data);
const subject = `[${requestData.requestNumber}] Request Closed`; const subject = `${requestData.requestNumber} - ${requestData.title} - Request Closed`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: recipientData.email, to: recipientData.email,
@ -690,7 +690,7 @@ export class EmailNotificationService {
closureData: any closureData: any
): Promise<void> { ): Promise<void> {
logger.info(`📧 Sending Request Closed emails to ${participants.length} participants`); logger.info(`📧 Sending Request Closed emails to ${participants.length} participants`);
for (const participant of participants) { for (const participant of participants) {
await this.sendRequestClosed(requestData, participant, closureData); await this.sendRequestClosed(requestData, participant, closureData);
// Small delay to avoid rate limiting // Small delay to avoid rate limiting
@ -734,7 +734,7 @@ export class EmailNotificationService {
}; };
const html = getApproverSkippedEmail(data); const html = getApproverSkippedEmail(data);
const subject = `[${requestData.requestNumber}] Approver Skipped`; const subject = `${requestData.requestNumber} - ${requestData.title} - Approver Skipped`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: skippedApproverData.email, to: skippedApproverData.email,
@ -794,7 +794,7 @@ export class EmailNotificationService {
}; };
const html = getWorkflowPausedEmail(data); const html = getWorkflowPausedEmail(data);
const subject = `[${requestData.requestNumber}] Workflow Paused`; const subject = `${requestData.requestNumber} - ${requestData.title} - Workflow Paused`;
const result = await emailService.sendEmail({ const result = await emailService.sendEmail({
to: recipientData.email, to: recipientData.email,