208 lines
6.1 KiB
TypeScript
208 lines
6.1 KiB
TypeScript
/**
|
|
* 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<EnrichedApprovalLevel[]> {
|
|
const enriched: EnrichedApprovalLevel[] = [];
|
|
const processedEmails = new Set<string>();
|
|
|
|
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<EnrichedSpectator[]> {
|
|
if (!spectators || spectators.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const enriched: EnrichedSpectator[] = [];
|
|
const processedEmails = new Set<string>();
|
|
|
|
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<any> {
|
|
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;
|
|
}
|
|
|