add the title in email subject and statd implementing admin templates
This commit is contained in:
parent
d1ae0ffaec
commit
be220bbb0c
56
src/controllers/workflowTemplate.controller.ts
Normal file
56
src/controllers/workflowTemplate.controller.ts
Normal 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'
|
||||
});
|
||||
}
|
||||
};
|
||||
84
src/migrations/20260122-create-workflow-templates.ts
Normal file
84
src/migrations/20260122-create-workflow-templates.ts
Normal 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;');
|
||||
}
|
||||
99
src/models/WorkflowTemplate.ts
Normal file
99
src/models/WorkflowTemplate.ts
Normal 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'
|
||||
}
|
||||
);
|
||||
@ -16,6 +16,7 @@ import { Notification } from './Notification';
|
||||
import ConclusionRemark from './ConclusionRemark';
|
||||
import RequestSummary from './RequestSummary';
|
||||
import SharedSummary from './SharedSummary';
|
||||
import { WorkflowTemplate } from './WorkflowTemplate';
|
||||
|
||||
// Define associations
|
||||
const defineAssociations = () => {
|
||||
@ -138,7 +139,8 @@ export {
|
||||
Notification,
|
||||
ConclusionRemark,
|
||||
RequestSummary,
|
||||
SharedSummary
|
||||
SharedSummary,
|
||||
WorkflowTemplate
|
||||
};
|
||||
|
||||
// Export default sequelize instance
|
||||
|
||||
@ -13,6 +13,7 @@ import dashboardRoutes from './dashboard.routes';
|
||||
import notificationRoutes from './notification.routes';
|
||||
import conclusionRoutes from './conclusion.routes';
|
||||
import aiRoutes from './ai.routes';
|
||||
import workflowTemplateRoutes from './workflowTemplate.routes';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@ -40,6 +41,7 @@ router.use('/notifications', notificationRoutes);
|
||||
router.use('/conclusions', conclusionRoutes);
|
||||
router.use('/ai', aiRoutes);
|
||||
router.use('/summaries', summaryRoutes);
|
||||
router.use('/templates', workflowTemplateRoutes);
|
||||
|
||||
// TODO: Add other route modules as they are implemented
|
||||
// router.use('/approvals', approvalRoutes);
|
||||
|
||||
14
src/routes/workflowTemplate.routes.ts
Normal file
14
src/routes/workflowTemplate.routes.ts
Normal 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;
|
||||
@ -49,13 +49,13 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
console.log(`📦 Database '${DB_NAME}' not found. Creating...`);
|
||||
|
||||
|
||||
// Create database
|
||||
await client.query(`CREATE DATABASE "${DB_NAME}"`);
|
||||
console.log(`✅ Database '${DB_NAME}' created successfully!`);
|
||||
|
||||
|
||||
await client.end();
|
||||
|
||||
|
||||
// Connect to new database and install extensions
|
||||
const newDbClient = new Client({
|
||||
host: DB_HOST,
|
||||
@ -64,13 +64,13 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
password: DB_PASSWORD,
|
||||
database: DB_NAME,
|
||||
});
|
||||
|
||||
|
||||
await newDbClient.connect();
|
||||
console.log('📦 Installing uuid-ossp extension...');
|
||||
await newDbClient.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
|
||||
console.log('✅ Extension installed!');
|
||||
await newDbClient.end();
|
||||
|
||||
|
||||
return true; // Database was created
|
||||
} else {
|
||||
console.log(`✅ Database '${DB_NAME}' already exists.`);
|
||||
@ -87,7 +87,7 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
async function runMigrations(): Promise<void> {
|
||||
try {
|
||||
console.log('🔄 Checking and running pending migrations...');
|
||||
|
||||
|
||||
// Import all migrations using require for CommonJS compatibility
|
||||
// Some migrations use module.exports, others use export
|
||||
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 m28 = require('../migrations/20250130-migrate-to-vertex-ai');
|
||||
const m29 = require('../migrations/20251203-add-user-notification-preferences');
|
||||
|
||||
const m30 = require('../migrations/20260122-create-workflow-templates');
|
||||
|
||||
const migrations = [
|
||||
{ name: '2025103000-create-users', module: m0 },
|
||||
{ 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: '20250130-migrate-to-vertex-ai', module: m28 },
|
||||
{ name: '20251203-add-user-notification-preferences', module: m29 },
|
||||
{ name: '20260122-create-workflow-templates', module: m30 },
|
||||
];
|
||||
|
||||
|
||||
const queryInterface = sequelize.getQueryInterface();
|
||||
|
||||
|
||||
// Ensure migrations tracking table exists
|
||||
const tables = await queryInterface.showAllTables();
|
||||
if (!tables.includes('migrations')) {
|
||||
@ -167,34 +169,34 @@ async function runMigrations(): Promise<void> {
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
// Get already executed migrations
|
||||
const executedResults = await sequelize.query<{ name: string }>(
|
||||
'SELECT name FROM migrations ORDER BY id',
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
const executedMigrations = executedResults.map(r => r.name);
|
||||
|
||||
|
||||
// Find pending migrations
|
||||
const pendingMigrations = migrations.filter(
|
||||
m => !executedMigrations.includes(m.name)
|
||||
);
|
||||
|
||||
|
||||
if (pendingMigrations.length === 0) {
|
||||
console.log('✅ Migrations up-to-date');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log(`🔄 Running ${pendingMigrations.length} pending migration(s)...`);
|
||||
|
||||
|
||||
// Run each pending migration
|
||||
for (const migration of pendingMigrations) {
|
||||
try {
|
||||
console.log(` → ${migration.name}`);
|
||||
|
||||
|
||||
// Call the up function - works for both module.exports and export styles
|
||||
await migration.module.up(queryInterface);
|
||||
|
||||
|
||||
// Mark as executed
|
||||
await sequelize.query(
|
||||
'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING',
|
||||
@ -209,7 +211,7 @@ async function runMigrations(): Promise<void> {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`✅ Applied ${pendingMigrations.length} migration(s)`);
|
||||
} catch (error: any) {
|
||||
console.error('❌ Migration failed:', error.message);
|
||||
@ -246,9 +248,9 @@ async function autoSetup(): Promise<void> {
|
||||
console.log('\n========================================');
|
||||
console.log('✅ Setup completed successfully!');
|
||||
console.log('========================================\n');
|
||||
|
||||
|
||||
console.log('📝 Note: Admin configurations will be auto-seeded on server start if table is empty.\n');
|
||||
|
||||
|
||||
if (wasCreated) {
|
||||
console.log('💡 Next steps:');
|
||||
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(` UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@royalenfield.com';\n`);
|
||||
}
|
||||
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('\n========================================');
|
||||
console.error('❌ Setup failed!');
|
||||
|
||||
@ -101,7 +101,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: initiatorData.email,
|
||||
@ -144,9 +144,9 @@ export class EmailNotificationService {
|
||||
// Multi-level approval email
|
||||
const chainData: ApprovalChainItem[] = approvalChain.map((level: any) => ({
|
||||
name: level.approverName || level.approverEmail,
|
||||
status: level.status === 'APPROVED' ? 'approved'
|
||||
: level.levelNumber === approverData.levelNumber ? 'current'
|
||||
: level.levelNumber < approverData.levelNumber ? 'pending'
|
||||
status: level.status === 'APPROVED' ? 'approved'
|
||||
: level.levelNumber === approverData.levelNumber ? 'current'
|
||||
: level.levelNumber < approverData.levelNumber ? 'pending'
|
||||
: 'awaiting',
|
||||
date: level.approvedAt ? this.formatDate(level.approvedAt) : undefined,
|
||||
levelNumber: level.levelNumber
|
||||
@ -170,7 +170,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: approverData.email,
|
||||
@ -198,7 +198,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: approverData.email,
|
||||
@ -252,7 +252,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: initiatorData.email,
|
||||
@ -303,7 +303,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
const html = getRejectionNotificationEmail(data);
|
||||
const subject = `[${requestData.requestNumber}] Request Rejected`;
|
||||
const subject = `${requestData.requestNumber} - ${requestData.title} - Request Rejected`;
|
||||
|
||||
const result = await emailService.sendEmail({
|
||||
to: initiatorData.email,
|
||||
@ -344,9 +344,9 @@ export class EmailNotificationService {
|
||||
}
|
||||
|
||||
// Determine urgency level based on threshold
|
||||
const urgencyLevel = tatInfo.thresholdPercentage >= 75 ? 'high'
|
||||
: tatInfo.thresholdPercentage >= 50 ? 'medium'
|
||||
: 'low';
|
||||
const urgencyLevel = tatInfo.thresholdPercentage >= 75 ? 'high'
|
||||
: tatInfo.thresholdPercentage >= 50 ? 'medium'
|
||||
: 'low';
|
||||
|
||||
// Get initiator name - try from requestData first, then fetch if needed
|
||||
let initiatorName = requestData.initiatorName || requestData.initiator?.displayName || 'Initiator';
|
||||
@ -379,7 +379,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: approverData.email,
|
||||
@ -449,7 +449,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: approverData.email,
|
||||
@ -496,8 +496,8 @@ export class EmailNotificationService {
|
||||
}
|
||||
|
||||
const isAutoResumed = !resumedByData || resumedByData.userId === 'system';
|
||||
const resumedByText = isAutoResumed
|
||||
? 'automatically'
|
||||
const resumedByText = isAutoResumed
|
||||
? 'automatically'
|
||||
: `by ${resumedByData.displayName || resumedByData.email}`;
|
||||
|
||||
const data: WorkflowResumedData = {
|
||||
@ -509,7 +509,7 @@ export class EmailNotificationService {
|
||||
resumedTime: this.formatTime(new Date()),
|
||||
pausedDuration: pauseDuration,
|
||||
currentApprover: approverData.displayName || approverData.email,
|
||||
newTATDeadline: requestData.tatDeadline
|
||||
newTATDeadline: requestData.tatDeadline
|
||||
? this.formatDate(requestData.tatDeadline) + ' ' + this.formatTime(requestData.tatDeadline)
|
||||
: 'To be determined',
|
||||
isApprover: true,
|
||||
@ -518,7 +518,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
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({
|
||||
to: approverData.email,
|
||||
@ -565,8 +565,8 @@ export class EmailNotificationService {
|
||||
}
|
||||
|
||||
const isAutoResumed = !resumedByData || resumedByData.userId === 'system' || !resumedByData.userId;
|
||||
const resumedByText = isAutoResumed
|
||||
? 'automatically'
|
||||
const resumedByText = isAutoResumed
|
||||
? 'automatically'
|
||||
: `by ${resumedByData.displayName || resumedByData.email || resumedByData.name || 'User'}`;
|
||||
|
||||
const data: WorkflowResumedData = {
|
||||
@ -578,7 +578,7 @@ export class EmailNotificationService {
|
||||
resumedTime: this.formatTime(new Date()),
|
||||
pausedDuration: pauseDuration,
|
||||
currentApprover: approverData?.displayName || approverData?.email || 'Current Approver',
|
||||
newTATDeadline: requestData.tatDeadline
|
||||
newTATDeadline: requestData.tatDeadline
|
||||
? this.formatDate(requestData.tatDeadline) + ' ' + this.formatTime(requestData.tatDeadline)
|
||||
: 'To be determined',
|
||||
isApprover: false, // This is for initiator
|
||||
@ -587,7 +587,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
const html = getWorkflowResumedEmail(data);
|
||||
const subject = `[${requestData.requestNumber}] Workflow Resumed`;
|
||||
const subject = `${requestData.requestNumber} - ${requestData.title} - Workflow Resumed`;
|
||||
|
||||
const result = await emailService.sendEmail({
|
||||
to: initiatorData.email,
|
||||
@ -665,7 +665,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
const html = getRequestClosedEmail(data);
|
||||
const subject = `[${requestData.requestNumber}] Request Closed`;
|
||||
const subject = `${requestData.requestNumber} - ${requestData.title} - Request Closed`;
|
||||
|
||||
const result = await emailService.sendEmail({
|
||||
to: recipientData.email,
|
||||
@ -690,7 +690,7 @@ export class EmailNotificationService {
|
||||
closureData: any
|
||||
): Promise<void> {
|
||||
logger.info(`📧 Sending Request Closed emails to ${participants.length} participants`);
|
||||
|
||||
|
||||
for (const participant of participants) {
|
||||
await this.sendRequestClosed(requestData, participant, closureData);
|
||||
// Small delay to avoid rate limiting
|
||||
@ -734,7 +734,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
const html = getApproverSkippedEmail(data);
|
||||
const subject = `[${requestData.requestNumber}] Approver Skipped`;
|
||||
const subject = `${requestData.requestNumber} - ${requestData.title} - Approver Skipped`;
|
||||
|
||||
const result = await emailService.sendEmail({
|
||||
to: skippedApproverData.email,
|
||||
@ -794,7 +794,7 @@ export class EmailNotificationService {
|
||||
};
|
||||
|
||||
const html = getWorkflowPausedEmail(data);
|
||||
const subject = `[${requestData.requestNumber}] Workflow Paused`;
|
||||
const subject = `${requestData.requestNumber} - ${requestData.title} - Workflow Paused`;
|
||||
|
||||
const result = await emailService.sendEmail({
|
||||
to: recipientData.email,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user