109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
import { WorkflowRequest } from '@models/WorkflowRequest';
|
|
import { Op } from 'sequelize';
|
|
import logger from './logger';
|
|
|
|
/**
|
|
* Generate request number in format: REQ-YYYY-MM-XXXX
|
|
* Counter resets every month
|
|
* Example: REQ-2025-11-0001
|
|
*/
|
|
export const generateRequestNumber = async (): Promise<string> => {
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = (now.getMonth() + 1).toString().padStart(2, '0'); // Month is 0-indexed, so add 1
|
|
|
|
// Build the prefix pattern for current year-month
|
|
const prefix = `REQ-${year}-${month}-`;
|
|
|
|
try {
|
|
// Find the highest counter for the current year-month
|
|
const existingRequests = await WorkflowRequest.findAll({
|
|
where: {
|
|
requestNumber: {
|
|
[Op.like]: `${prefix}%`
|
|
}
|
|
},
|
|
attributes: ['requestNumber'],
|
|
order: [['requestNumber', 'DESC']],
|
|
limit: 1
|
|
});
|
|
|
|
let counter = 1;
|
|
|
|
if (existingRequests.length > 0) {
|
|
// Extract the counter from the last request number
|
|
const lastRequestNumber = (existingRequests[0] as any).requestNumber;
|
|
const lastCounter = parseInt(lastRequestNumber.replace(prefix, ''), 10);
|
|
|
|
if (!isNaN(lastCounter)) {
|
|
counter = lastCounter + 1;
|
|
}
|
|
}
|
|
|
|
// Format counter as 4-digit number (0001, 0002, etc.)
|
|
const counterStr = counter.toString().padStart(4, '0');
|
|
|
|
return `${prefix}${counterStr}`;
|
|
} catch (error) {
|
|
// Fallback to timestamp-based counter if database query fails
|
|
logger.error('Error generating request number:', error);
|
|
const fallbackCounter = Date.now().toString().slice(-4);
|
|
return `${prefix}${fallbackCounter}`;
|
|
}
|
|
};
|
|
|
|
export const calculateTATDays = (tatHours: number): number => {
|
|
return Math.ceil(tatHours / 24);
|
|
};
|
|
|
|
export const calculateElapsedHours = (startTime: Date, endTime?: Date): number => {
|
|
const end = endTime || new Date();
|
|
const diffMs = end.getTime() - startTime.getTime();
|
|
return Math.round((diffMs / (1000 * 60 * 60)) * 100) / 100; // Round to 2 decimal places
|
|
};
|
|
|
|
export const calculateTATPercentage = (elapsedHours: number, totalTatHours: number): number => {
|
|
if (totalTatHours === 0) return 0;
|
|
return Math.min(Math.round((elapsedHours / totalTatHours) * 100), 100);
|
|
};
|
|
|
|
export const isTATBreached = (elapsedHours: number, totalTatHours: number): boolean => {
|
|
return elapsedHours > totalTatHours;
|
|
};
|
|
|
|
export const isTATApproaching = (elapsedHours: number, totalTatHours: number, threshold: number = 80): boolean => {
|
|
const percentage = calculateTATPercentage(elapsedHours, totalTatHours);
|
|
return percentage >= threshold && percentage < 100;
|
|
};
|
|
|
|
export const formatDate = (date: Date): string => {
|
|
return date.toISOString().split('T')[0];
|
|
};
|
|
|
|
export const formatDateTime = (date: Date): string => {
|
|
return date.toISOString();
|
|
};
|
|
|
|
export const isValidEmail = (email: string): boolean => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
};
|
|
|
|
export const isValidUUID = (uuid: string): boolean => {
|
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
return uuidRegex.test(uuid);
|
|
};
|
|
|
|
export const sanitizeString = (str: string): string => {
|
|
return str.trim().replace(/[<>]/g, '');
|
|
};
|
|
|
|
export const generateChecksum = (data: string): string => {
|
|
const crypto = require('crypto');
|
|
return crypto.createHash('sha256').update(data).digest('hex');
|
|
};
|
|
|
|
export const sleep = (ms: number): Promise<void> => {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
};
|