import { NotificationPlatform, NotificationStatus, ApprovalStatus, ActivityStatus, ContractStatus, ComplianceStatus, User, Invite, CreateNotificationRequest, } from '../types'; import { apiClient } from './httpClient'; import { getCurrentUserSub, getCurrentUserClaims, getCurrentUserTenantId, } from '../auth/AuthProvider'; export const CONSOLE_PORTAL_URL = 'https://console.ifri.khongisa.co.za/'; export const PARTNER_PORTAL_URL = 'https://partner.ifri.khongisa.co.za/'; export async function dispatchNotification(payload: CreateNotificationRequest): Promise { const tenantId = getCurrentUserTenantId(); const orgId = payload.organisationId !== undefined && payload.organisationId !== '' && payload.organisationId !== null ? Number(payload.organisationId) : (tenantId !== null ? tenantId : 1); await apiClient.post('/v1/api/communications/notifications', { organisationId: orgId, recipient: payload.recipient, platform: payload.platform, subject: payload.subject || null, destination: payload.destination || 'In-App User Inbox', message: payload.message, status: payload.status !== undefined ? payload.status : undefined, }); } export async function fetchDirectoryUsers(page = 1, pageSize = 200): Promise { try { const res = await apiClient.post(`/v1/api/users/${page}/${pageSize}`, {}); return res.data || []; } catch (err) { console.warn('[NotificationRules] Failed to fetch users directory:', err); return []; } } export function isApprovedOrActiveStatus(status: number | string | undefined | null): boolean { if (status === undefined || status === null) return false; const numStatus = Number(status); if ( numStatus === ApprovalStatus.Approved || numStatus === ActivityStatus.Active || numStatus === ContractStatus.Active || numStatus === ContractStatus.Executed || numStatus === ComplianceStatus.Met || numStatus === ComplianceStatus.Exceeded ) { return true; } const strStatus = String(status).toLowerCase().trim(); return ( strStatus === 'approved' || strStatus === 'active' || strStatus === 'executed' || strStatus === 'met' || strStatus === 'exceeded' || strStatus === 'verified' ); } export function getApprovedOrActiveStatusLabel(status: number | string | undefined | null): string { if (status === undefined || status === null) return 'Active'; const numStatus = Number(status); if (numStatus === ApprovalStatus.Approved) return 'Approved'; if (numStatus === ActivityStatus.Active) return 'Active'; if (numStatus === ContractStatus.Active) return 'Active'; if (numStatus === ComplianceStatus.Met) return 'Compliant (Met)'; const str = String(status); return str.charAt(0).toUpperCase() + str.slice(1); } export async function getTenantManagersAndExecutives(): Promise { try { const tenantId = getCurrentUserTenantId(); const users = await fetchDirectoryUsers(1, 200); const managersAndExecutives = users.filter((u) => { const roleStr = String(u.role || '').toLowerCase(); const isManagerOrExec = roleStr.includes('manager') || roleStr.includes('executive') || roleStr.includes('director') || roleStr === '2' || roleStr === '4'; const matchesTenant = !tenantId || !u.tenant_id || Number(u.tenant_id) === Number(tenantId); return isManagerOrExec && matchesTenant; }); if (managersAndExecutives.length > 0) { return managersAndExecutives; } const elevatedUsers = users.filter((u) => { const roleStr = String(u.role || '').toLowerCase(); return ( roleStr.includes('admin') || roleStr.includes('supervisor') || roleStr === '1' || roleStr === '3' ); }); if (elevatedUsers.length > 0) { return elevatedUsers; } } catch (err) { console.warn('[NotificationRules] Directory lookup failed, using authenticated identity fallback:', err); } const currentSub = getCurrentUserSub(); const currentClaims = getCurrentUserClaims(); if (currentSub) { return [ { id: currentSub, email: (currentClaims.email as string) || (currentClaims.preferred_username as string) || 'manager@pfm.org.za', status: ActivityStatus.Active, lastLoginAt: null, role: 'Manager', name: (currentClaims.name as string) || 'Tenant Manager', }, ]; } return []; } export interface RecordStatusChangeNotificationParams { recordType: string; recordId: string | number; newStatus: number | string; title?: string; previousStatus?: number | string; } export async function notifyTenantManagersAndExecutivesOnRecordStatusChange( params: RecordStatusChangeNotificationParams ): Promise { const { recordType, recordId, newStatus, title } = params; if (recordType.toLowerCase() === 'user' || recordType.toLowerCase() === 'users') { return; } if (!isApprovedOrActiveStatus(newStatus)) { return; } const statusLabel = getApprovedOrActiveStatusLabel(newStatus); const orgId = getCurrentUserTenantId() || 1; const managersAndExecutives = await getTenantManagersAndExecutives(); if (managersAndExecutives.length === 0) { return; } const recordDescriptor = title ? `${recordType} "${title}" (#${recordId})` : `${recordType} #${recordId}`; const inAppSubject = `[${statusLabel}] ${recordType} #${recordId} is now ${statusLabel}`; const inAppMessage = `### Statutory Record Update\n\nThe record **${recordDescriptor}** has officially transitioned to **${statusLabel}** status.\n\n- **Record Category:** ${recordType}\n- **Identifier:** #${recordId}\n- **Updated Status:** ${statusLabel}\n- **Timestamp:** ${new Date().toLocaleString()}`; const emailSubject = `[PFM Governance Alert] ${recordType} #${recordId} Status Transition: ${statusLabel}`; const emailMessage = `### Formal Statutory Notification\n\nThis is an automated notification to Tenant Managers and Executives regarding an approved record status update.\n\n**Record Details:**\n- **Category:** ${recordType}\n- **Reference Identifier:** #${recordId}\n${title ? `- **Title:** ${title}\n` : ''}- **New Lifecycle Status:** **${statusLabel}**\n- **Effective Timestamp:** ${new Date().toUTCString()}`; for (const recipient of managersAndExecutives) { if (!recipient.id) continue; const recipientEmail = recipient.email || 'manager@pfm.org.za'; try { await dispatchNotification({ organisationId: orgId, recipient: recipient.id, platform: NotificationPlatform.InApp, subject: inAppSubject, destination: recipientEmail, message: inAppMessage, status: NotificationStatus.Delivered, }); } catch {} try { await dispatchNotification({ organisationId: orgId, recipient: recipient.id, platform: NotificationPlatform.Email, subject: emailSubject, destination: `To: ${recipientEmail} | From: PFM Automated Governance System`, message: emailMessage, status: NotificationStatus.Queued, }); } catch {} } } export interface InviteApprovedNotificationParams { invite: Partial & { id: number | string; recipient: string; invitedOrganisationId?: number | string; organisationId?: number | string; awardId?: number | string; contractId?: number | string; message?: string; }; partnerUser?: User | null; partnerEmail?: string; } export async function notifyInviteApproved( params: InviteApprovedNotificationParams ): Promise { const { invite, partnerUser, partnerEmail } = params; if (!invite.recipient) return; const orgId = invite.invitedOrganisationId || invite.organisationId || getCurrentUserTenantId() || 1; const targetEmail = partnerEmail || partnerUser?.email || invite.recipient; try { await dispatchNotification({ organisationId: orgId, recipient: invite.recipient, platform: NotificationPlatform.InApp, subject: 'Onboarding Invitation Approved: Access to Partner Portal Granted', destination: targetEmail, message: `Your statutory onboarding invitation has been formally Approved. Access the Partner Portal at ${PARTNER_PORTAL_URL}.`, status: NotificationStatus.Delivered, }); } catch {} } export interface NewUserCreatedNotificationParams { userId: string; email: string; isPartner: boolean; roleName?: string; tenantId?: number | string | null; } export async function notifyNewUserCreated( params: NewUserCreatedNotificationParams ): Promise { const { userId, email, isPartner, tenantId } = params; if (!userId || !email) return; const orgId = tenantId ? Number(tenantId) : (getCurrentUserTenantId() || 1); const targetUrl = isPartner ? PARTNER_PORTAL_URL : CONSOLE_PORTAL_URL; try { await dispatchNotification({ organisationId: orgId, recipient: userId, platform: NotificationPlatform.InApp, subject: 'Welcome to PFM - Your Account Has Been Created', destination: email, message: `Your user account has been created. Please sign in at ${targetUrl}.`, status: NotificationStatus.Delivered, }); } catch {} }