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;
|
||||
@ -120,6 +120,7 @@ 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 },
|
||||
@ -152,6 +153,7 @@ 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();
|
||||
|
||||
@ -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,
|
||||
@ -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,
|
||||
@ -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,
|
||||
@ -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,
|
||||
@ -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,
|
||||
@ -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