Re_Backend/src/services/template.service.ts

247 lines
7.1 KiB
TypeScript

import { WorkflowTemplate } from '../models/WorkflowTemplate';
import { WorkflowRequest } from '../models/WorkflowRequest';
import { User } from '../models/User';
import { Op } from 'sequelize';
import logger from '../utils/logger';
/**
* Template Service
* Handles CRUD operations for workflow templates
*/
export class TemplateService {
/**
* Create a new workflow template
*/
async createTemplate(
userId: string,
templateData: {
templateName: string;
templateCode?: string;
templateDescription?: string;
templateCategory?: string;
workflowType?: string;
approvalLevelsConfig?: any;
defaultTatHours?: number;
formStepsConfig?: any;
userFieldMappings?: any;
dynamicApproverConfig?: any;
isActive?: boolean;
}
): Promise<WorkflowTemplate> {
try {
// Validate template code uniqueness if provided
if (templateData.templateCode) {
const existing = await WorkflowTemplate.findOne({
where: { templateCode: templateData.templateCode }
});
if (existing) {
throw new Error(`Template code '${templateData.templateCode}' already exists`);
}
}
const template = await WorkflowTemplate.create({
templateName: templateData.templateName,
templateCode: templateData.templateCode,
templateDescription: templateData.templateDescription,
templateCategory: templateData.templateCategory,
workflowType: templateData.workflowType || templateData.templateCode?.toUpperCase(),
approvalLevelsConfig: templateData.approvalLevelsConfig,
defaultTatHours: templateData.defaultTatHours || 24,
formStepsConfig: templateData.formStepsConfig,
userFieldMappings: templateData.userFieldMappings,
dynamicApproverConfig: templateData.dynamicApproverConfig,
isActive: templateData.isActive !== undefined ? templateData.isActive : true,
isSystemTemplate: false, // Admin-created templates are not system templates
usageCount: 0,
createdBy: userId,
});
logger.info(`[TemplateService] Created template: ${template.templateId}`);
return template;
} catch (error) {
logger.error('[TemplateService] Error creating template:', error);
throw error;
}
}
/**
* Get template by ID
*/
async getTemplate(templateId: string): Promise<WorkflowTemplate | null> {
try {
return await WorkflowTemplate.findByPk(templateId, {
include: [{ model: User, as: 'creator' }]
});
} catch (error) {
logger.error('[TemplateService] Error getting template:', error);
throw error;
}
}
/**
* Get template by code
*/
async getTemplateByCode(templateCode: string): Promise<WorkflowTemplate | null> {
try {
return await WorkflowTemplate.findOne({
where: { templateCode },
include: [{ model: User, as: 'creator' }]
});
} catch (error) {
logger.error('[TemplateService] Error getting template by code:', error);
throw error;
}
}
/**
* List all templates with filters
*/
async listTemplates(filters?: {
category?: string;
workflowType?: string;
isActive?: boolean;
isSystemTemplate?: boolean;
search?: string;
}): Promise<WorkflowTemplate[]> {
try {
const where: any = {};
if (filters?.category) {
where.templateCategory = filters.category;
}
if (filters?.workflowType) {
where.workflowType = filters.workflowType;
}
if (filters?.isActive !== undefined) {
where.isActive = filters.isActive;
}
if (filters?.isSystemTemplate !== undefined) {
where.isSystemTemplate = filters.isSystemTemplate;
}
if (filters?.search) {
where[Op.or] = [
{ templateName: { [Op.iLike]: `%${filters.search}%` } },
{ templateCode: { [Op.iLike]: `%${filters.search}%` } },
{ templateDescription: { [Op.iLike]: `%${filters.search}%` } }
];
}
return await WorkflowTemplate.findAll({
where,
include: [{ model: User, as: 'creator' }],
order: [['createdAt', 'DESC']]
});
} catch (error) {
logger.error('[TemplateService] Error listing templates:', error);
throw error;
}
}
/**
* Update template
*/
async updateTemplate(
templateId: string,
userId: string,
updateData: {
templateName?: string;
templateDescription?: string;
templateCategory?: string;
approvalLevelsConfig?: any;
defaultTatHours?: number;
formStepsConfig?: any;
userFieldMappings?: any;
dynamicApproverConfig?: any;
isActive?: boolean;
}
): Promise<WorkflowTemplate> {
try {
const template = await WorkflowTemplate.findByPk(templateId);
if (!template) {
throw new Error('Template not found');
}
// Check if template is system template (system templates should not be modified)
if (template.isSystemTemplate && updateData.approvalLevelsConfig) {
throw new Error('Cannot modify approval levels of system templates');
}
await template.update(updateData);
logger.info(`[TemplateService] Updated template: ${templateId}`);
return template;
} catch (error) {
logger.error('[TemplateService] Error updating template:', error);
throw error;
}
}
/**
* Delete template (soft delete by setting isActive to false)
*/
async deleteTemplate(templateId: string): Promise<void> {
try {
const template = await WorkflowTemplate.findByPk(templateId);
if (!template) {
throw new Error('Template not found');
}
// Check if template is in use
const usageCount = await WorkflowRequest.count({
where: { templateId }
});
if (usageCount > 0) {
throw new Error(`Cannot delete template: ${usageCount} request(s) are using this template`);
}
// System templates cannot be deleted
if (template.isSystemTemplate) {
throw new Error('Cannot delete system templates');
}
// Soft delete by deactivating
await template.update({ isActive: false });
logger.info(`[TemplateService] Deleted (deactivated) template: ${templateId}`);
} catch (error) {
logger.error('[TemplateService] Error deleting template:', error);
throw error;
}
}
/**
* Get active templates for workflow creation
*/
async getActiveTemplates(): Promise<WorkflowTemplate[]> {
try {
return await WorkflowTemplate.findAll({
where: { isActive: true },
order: [['templateName', 'ASC']]
});
} catch (error) {
logger.error('[TemplateService] Error getting active templates:', error);
throw error;
}
}
/**
* Increment usage count when template is used
*/
async incrementUsageCount(templateId: string): Promise<void> {
try {
await WorkflowTemplate.increment('usageCount', {
where: { templateId }
});
} catch (error) {
logger.error('[TemplateService] Error incrementing usage count:', error);
// Don't throw - this is not critical
}
}
}