Re_Backend/src/validators/workflow.validator.ts

124 lines
5.1 KiB
TypeScript

import { z } from 'zod';
// Simplified approval level schema - only requires email and tatHours
// Backend will enrich with user details (approverId, approverName, levelName)
const simplifiedApprovalLevelSchema = z.object({
email: z.string().email('Valid email is required'),
tatHours: z.number().positive('TAT hours must be positive'),
isFinalApprover: z.boolean().optional(),
// Optional fields that backend will auto-populate if not provided
levelNumber: z.number().int().min(1).max(10).optional(),
levelName: z.string().optional(),
approverId: z.string().uuid().optional(),
approverEmail: z.string().email().optional(),
approverName: z.string().optional(),
});
// Simplified spectator schema - only requires email
const simplifiedSpectatorSchema = z.object({
email: z.string().email('Valid email is required').optional(),
// Optional fields that backend will auto-populate if not provided
userId: z.string().uuid().optional(),
userEmail: z.string().email().optional(),
userName: z.string().optional(),
participantType: z.enum(['INITIATOR', 'APPROVER', 'SPECTATOR'] as const).optional(),
canComment: z.boolean().optional(),
canViewDocuments: z.boolean().optional(),
canDownloadDocuments: z.boolean().optional(),
notificationEnabled: z.boolean().optional(),
});
export const createWorkflowSchema = z.object({
templateType: z.enum(['CUSTOM', 'TEMPLATE']),
title: z.string().min(1, 'Title is required').max(500, 'Title too long'),
description: z.string().min(1, 'Description is required'),
priority: z.enum(['STANDARD', 'EXPRESS'] as const),
approvalLevels: z.array(simplifiedApprovalLevelSchema)
.min(1, 'At least one approval level is required')
.max(10, 'Maximum 10 approval levels allowed'),
participants: z.array(simplifiedSpectatorSchema).optional(),
spectators: z.array(simplifiedSpectatorSchema).optional(), // Alias for participants
// Additional frontend compatibility fields
approverCount: z.number().optional(),
approvers: z.array(z.any()).optional(),
priorityUi: z.string().optional(),
templateId: z.string().optional(),
ccList: z.array(z.any()).optional(),
isDraft: z.boolean().optional(),
});
export const updateWorkflowSchema = z.object({
title: z.string().min(1).max(500).optional(),
description: z.string().min(1).optional(),
priority: z.enum(['STANDARD', 'EXPRESS'] as const).optional(),
status: z.enum(['DRAFT', 'PENDING', 'APPROVED', 'REJECTED', 'CLOSED', 'PAUSED'] as const).optional(),
conclusionRemark: z.string().optional(),
// For draft updates - allow updating approval levels and participants
approvalLevels: z.array(z.object({
levelNumber: z.number().int().min(1).max(10),
levelName: z.string().optional(),
approverId: z.string().uuid(),
approverEmail: z.string().email(),
approverName: z.string().min(1),
tatHours: z.number().positive(),
isFinalApprover: z.boolean().optional(),
})).optional(),
participants: z.array(z.object({
userId: z.string().uuid(),
userEmail: z.string().email(),
userName: z.string().min(1),
participantType: z.enum(['INITIATOR', 'APPROVER', 'SPECTATOR'] as const),
canComment: z.boolean().optional(),
canViewDocuments: z.boolean().optional(),
canDownloadDocuments: z.boolean().optional(),
notificationEnabled: z.boolean().optional(),
})).optional(),
deleteDocumentIds: z.array(z.string().uuid()).optional(),
isDraft: z.boolean().optional(),
});
// Helper to validate UUID or requestNumber format
// Supports both old format (REQ-YYYY-NNNNN) and new format (REQ-YYYY-MM-XXXX)
const workflowIdValidator = z.string().refine(
(val) => {
// Check if it's a valid UUID
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;
if (uuidRegex.test(val)) {
return true;
}
// Check if it's a valid requestNumber format
// Old format: REQ-YYYY-NNNNN (e.g., REQ-2025-12057) - 5+ digits after year
// New format: REQ-YYYY-MM-XXXX (e.g., REQ-2025-11-0001) - 2-digit month, 4-digit counter
const oldFormatRegex = /^REQ-\d{4}-\d{5,}$/i; // Old: REQ-2025-12057
const newFormatRegex = /^REQ-\d{4}-\d{2}-\d{4}$/i; // New: REQ-2025-11-0001
if (oldFormatRegex.test(val) || newFormatRegex.test(val)) {
return true;
}
return false;
},
{
message: 'Invalid workflow ID - must be a valid UUID or requestNumber (e.g., REQ-2025-11-0001 or REQ-2025-12057)',
}
);
export const workflowParamsSchema = z.object({
id: workflowIdValidator,
});
export const workflowQuerySchema = z.object({
page: z.string().transform(Number).pipe(z.number().int().min(1)).optional(),
limit: z.string().transform(Number).pipe(z.number().int().min(1).max(100)).optional(),
status: z.enum(['DRAFT', 'PENDING', 'APPROVED', 'REJECTED', 'CLOSED', 'PAUSED'] as const).optional(),
priority: z.enum(['STANDARD', 'EXPRESS'] as const).optional(),
sortBy: z.string().optional(),
sortOrder: z.enum(['ASC', 'DESC'] as const).optional(),
});
export const validateCreateWorkflow = (data: any) => {
return createWorkflowSchema.parse(data);
};
export const validateUpdateWorkflow = (data: any) => {
return updateWorkflowSchema.parse(data);
};