/** * User Enrichment Service * * Handles automatic user lookup/creation and data enrichment for workflow creation */ import { User } from '@models/User'; import logger from '@utils/logger'; import { UserService } from './user.service'; const userService = new UserService(); interface SimplifiedApprovalLevel { email: string; tatHours: number; isFinalApprover?: boolean; levelNumber?: number; levelName?: string; approverId?: string; approverEmail?: string; approverName?: string; } interface EnrichedApprovalLevel { levelNumber: number; levelName: string; approverId: string; approverEmail: string; approverName: string; tatHours: number; isFinalApprover: boolean; } interface SimplifiedSpectator { email: string; userId?: string; userEmail?: string; userName?: string; } interface EnrichedSpectator { userId: string; userEmail: string; userName: string; participantType: 'SPECTATOR'; canComment: boolean; canViewDocuments: boolean; canDownloadDocuments: boolean; notificationEnabled: boolean; } /** * Enrich approval levels with user data from database/AD * @param approvalLevels - Simplified approval levels (only email + tatHours required) * @returns Enriched approval levels with full user data */ export async function enrichApprovalLevels( approvalLevels: SimplifiedApprovalLevel[] ): Promise { const enriched: EnrichedApprovalLevel[] = []; const processedEmails = new Set(); for (let i = 0; i < approvalLevels.length; i++) { const level = approvalLevels[i]; const email = level.email.toLowerCase(); // Check for duplicate emails if (processedEmails.has(email)) { throw new Error(`Duplicate approver email found: ${email}. Each approver must have a unique email.`); } processedEmails.add(email); try { // Find or create user from AD let user = await User.findOne({ where: { email } }); if (!user) { logger.info(`[UserEnrichment] User not found in DB, attempting to sync from AD: ${email}`); // Try to fetch and create user from AD try { user = await userService.ensureUserExists({ email }) as any; } catch (adError: any) { logger.error(`[UserEnrichment] Failed to sync user from AD: ${email}`, adError); throw new Error(`Approver email '${email}' not found in organization directory. Please verify the email address.`); } } const userId = (user as any).userId; const displayName = (user as any).displayName || (user as any).email; const designation = (user as any).designation || (user as any).jobTitle; const department = (user as any).department; // Auto-generate level name let levelName = level.levelName; if (!levelName) { if (designation) { levelName = `${designation} Approval`; } else if (department) { levelName = `${department} Approval`; } else { levelName = `Level ${i + 1} Approval`; } } // Auto-detect final approver (last level) const isFinalApprover = level.isFinalApprover !== undefined ? level.isFinalApprover : (i === approvalLevels.length - 1); enriched.push({ levelNumber: level.levelNumber || (i + 1), levelName, approverId: userId, approverEmail: email, approverName: displayName, tatHours: level.tatHours, isFinalApprover, }); logger.info(`[UserEnrichment] Enriched approval level ${i + 1}: ${email} -> ${displayName} (${levelName})`); } catch (error: any) { logger.error(`[UserEnrichment] Failed to enrich approval level for ${email}:`, error); throw error; } } return enriched; } /** * Enrich spectators with user data from database/AD * @param spectators - Simplified spectators (only email required) * @returns Enriched spectators with full user data */ export async function enrichSpectators( spectators: SimplifiedSpectator[] ): Promise { if (!spectators || spectators.length === 0) { return []; } const enriched: EnrichedSpectator[] = []; const processedEmails = new Set(); for (const spectator of spectators) { const email = spectator.email.toLowerCase(); // Check for duplicate emails if (processedEmails.has(email)) { throw new Error(`Duplicate spectator email found: ${email}. Each spectator must have a unique email.`); } processedEmails.add(email); try { // Find or create user from AD let user = await User.findOne({ where: { email } }); if (!user) { logger.info(`[UserEnrichment] User not found in DB, attempting to sync from AD: ${email}`); try { user = await userService.ensureUserExists({ email }) as any; } catch (adError: any) { logger.error(`[UserEnrichment] Failed to sync user from AD: ${email}`, adError); throw new Error(`Spectator email '${email}' not found in organization directory. Please verify the email address.`); } } const userId = (user as any).userId; const displayName = (user as any).displayName || (user as any).email; enriched.push({ userId, userEmail: email, userName: displayName, participantType: 'SPECTATOR', canComment: true, canViewDocuments: true, canDownloadDocuments: false, notificationEnabled: true, }); logger.info(`[UserEnrichment] Enriched spectator: ${email} -> ${displayName}`); } catch (error: any) { logger.error(`[UserEnrichment] Failed to enrich spectator ${email}:`, error); throw error; } } return enriched; } /** * Validate and ensure initiator exists in database * @param initiatorId - User ID of the initiator * @returns User object if valid * @throws Error if initiator not found or invalid */ export async function validateInitiator(initiatorId: string): Promise { const user = await User.findByPk(initiatorId); if (!user) { throw new Error(`Invalid initiator: User with ID '${initiatorId}' not found. Please ensure you are logged in with a valid account.`); } return user; }