/** * Claim Data Mapper Utilities * Maps API response data to ClaimManagementRequest structure for frontend components */ import { isClaimManagementRequest } from './claimRequestUtils'; /** * User roles in a claim management request */ export type RequestRole = 'INITIATOR' | 'DEALER' | 'DEPARTMENT_LEAD' | 'APPROVER' | 'SPECTATOR'; /** * Claim Management Request structure for frontend */ export interface ClaimManagementRequest { // Activity Information activityInfo: { activityName: string; activityType: string; requestedDate?: string; location: string; period?: { startDate: string; endDate: string; }; estimatedBudget?: number; closedExpenses?: number; closedExpensesBreakdown?: Array<{ description: string; amount: number }>; description?: string; }; // Dealer Information dealerInfo: { dealerCode: string; dealerName: string; email?: string; phone?: string; address?: string; }; // Proposal Details (Step 1) proposalDetails?: { proposalDocumentUrl?: string; costBreakup: Array<{ description: string; amount: number }>; totalEstimatedBudget: number; timelineMode?: 'date' | 'days'; expectedCompletionDate?: string; expectedCompletionDays?: number; dealerComments?: string; submittedAt?: string; }; // IO Details (Step 3) - from internal_orders table ioDetails?: { ioNumber?: string; ioRemark?: string; availableBalance?: number; blockedAmount?: number; remainingBalance?: number; organizedBy?: string; organizedAt?: string; }; // DMS Details (Step 7) dmsDetails?: { eInvoiceNumber?: string; eInvoiceDate?: string; dmsNumber?: string; creditNoteNumber?: string; creditNoteDate?: string; creditNoteAmount?: number; }; // Claim Amount claimAmount: { estimated: number; closed: number; }; } /** * Role-based visibility configuration */ export interface RoleVisibility { showDealerInfo: boolean; showProposalDetails: boolean; showIODetails: boolean; showDMSDetails: boolean; showClaimAmount: boolean; canEditClaimAmount: boolean; } /** * Map API request data to ClaimManagementRequest structure */ export function mapToClaimManagementRequest( apiRequest: any, _currentUserId: string ): ClaimManagementRequest | null { try { if (!isClaimManagementRequest(apiRequest)) { return null; } // Extract claim details from API response const claimDetails = apiRequest.claimDetails || {}; const proposalDetails = apiRequest.proposalDetails || {}; const completionDetails = apiRequest.completionDetails || {}; const internalOrder = apiRequest.internalOrder || apiRequest.internal_order || {}; // Extract new normalized tables const budgetTracking = apiRequest.budgetTracking || apiRequest.budget_tracking || {}; const invoice = apiRequest.invoice || {}; const creditNote = apiRequest.creditNote || apiRequest.credit_note || {}; const completionExpenses = apiRequest.completionExpenses || apiRequest.completion_expenses || []; // Debug: Log raw claim details to help troubleshoot console.debug('[claimDataMapper] Raw claimDetails:', claimDetails); console.debug('[claimDataMapper] Raw apiRequest:', { hasClaimDetails: !!apiRequest.claimDetails, hasProposalDetails: !!apiRequest.proposalDetails, hasCompletionDetails: !!apiRequest.completionDetails, hasBudgetTracking: !!budgetTracking, hasInvoice: !!invoice, hasCreditNote: !!creditNote, hasCompletionExpenses: Array.isArray(completionExpenses) && completionExpenses.length > 0, workflowType: apiRequest.workflowType, }); // Map activity information (matching ActivityInformationCard expectations) // Handle both camelCase and snake_case field names from Sequelize const periodStartDate = claimDetails.periodStartDate || claimDetails.period_start_date; const periodEndDate = claimDetails.periodEndDate || claimDetails.period_end_date; const activityName = claimDetails.activityName || claimDetails.activity_name || ''; const activityType = claimDetails.activityType || claimDetails.activity_type || ''; const location = claimDetails.location || ''; console.debug('[claimDataMapper] Mapped activity fields:', { activityName, activityType, location, hasActivityName: !!activityName, hasActivityType: !!activityType, hasLocation: !!location, }); // Get budget values from budgetTracking table (new source of truth) const estimatedBudget = budgetTracking.proposalEstimatedBudget || budgetTracking.proposal_estimated_budget || budgetTracking.initialEstimatedBudget || budgetTracking.initial_estimated_budget || claimDetails.estimatedBudget || claimDetails.estimated_budget; // Get closed expenses - check multiple sources with proper number conversion const closedExpensesRaw = budgetTracking?.closedExpenses || budgetTracking?.closed_expenses || completionDetails?.totalClosedExpenses || completionDetails?.total_closed_expenses || claimDetails?.closedExpenses || claimDetails?.closed_expenses; // Convert to number and handle 0 as valid value const closedExpenses = closedExpensesRaw !== null && closedExpensesRaw !== undefined ? Number(closedExpensesRaw) : undefined; // Get closed expenses breakdown from new completionExpenses table const closedExpensesBreakdown = Array.isArray(completionExpenses) && completionExpenses.length > 0 ? completionExpenses.map((exp: any) => ({ description: exp.description || exp.itemDescription || '', amount: Number(exp.amount) || 0 })) : (completionDetails?.closedExpenses || completionDetails?.closed_expenses || completionDetails?.closedExpensesBreakdown || []); const activityInfo = { activityName, activityType, requestedDate: claimDetails.activityDate || claimDetails.activity_date || apiRequest.createdAt, // Use activityDate as requestedDate, fallback to createdAt location, period: (periodStartDate && periodEndDate) ? { startDate: periodStartDate, endDate: periodEndDate, } : undefined, estimatedBudget, closedExpenses, closedExpensesBreakdown, description: apiRequest.description || '', // Get description from workflow request }; // Map dealer information (matching DealerInformationCard expectations) // Dealer info should always be available from claimDetails (created during claim request creation) // Handle both camelCase and snake_case from Sequelize JSON serialization const dealerInfo = { dealerCode: claimDetails?.dealerCode || claimDetails?.dealer_code || claimDetails?.DealerCode || '', dealerName: claimDetails?.dealerName || claimDetails?.dealer_name || claimDetails?.DealerName || '', email: claimDetails?.dealerEmail || claimDetails?.dealer_email || claimDetails?.DealerEmail || '', phone: claimDetails?.dealerPhone || claimDetails?.dealer_phone || claimDetails?.DealerPhone || '', address: claimDetails?.dealerAddress || claimDetails?.dealer_address || claimDetails?.DealerAddress || '', }; // Log warning if dealer info is missing (should always be present for claim management requests) if (!dealerInfo.dealerCode || !dealerInfo.dealerName) { console.warn('[claimDataMapper] Dealer information is missing from claimDetails:', { hasClaimDetails: !!claimDetails, dealerCode: dealerInfo.dealerCode, dealerName: dealerInfo.dealerName, rawClaimDetails: claimDetails, availableKeys: claimDetails ? Object.keys(claimDetails) : [], }); } // Map proposal details const proposal = proposalDetails ? { proposalDocumentUrl: proposalDetails.proposalDocumentUrl || proposalDetails.proposal_document_url, costBreakup: proposalDetails.costBreakup || proposalDetails.cost_breakup || [], totalEstimatedBudget: proposalDetails.totalEstimatedBudget || proposalDetails.total_estimated_budget || 0, timelineMode: proposalDetails.timelineMode || proposalDetails.timeline_mode, expectedCompletionDate: proposalDetails.expectedCompletionDate || proposalDetails.expected_completion_date, expectedCompletionDays: proposalDetails.expectedCompletionDays || proposalDetails.expected_completion_days, dealerComments: proposalDetails.dealerComments || proposalDetails.dealer_comments, submittedAt: proposalDetails.submittedAt || proposalDetails.submitted_at, } : undefined; // Map IO details from dedicated internal_orders table const ioDetails = { ioNumber: internalOrder.ioNumber || internalOrder.io_number || claimDetails.ioNumber || claimDetails.io_number, ioRemark: internalOrder.ioRemark || internalOrder.io_remark || '', availableBalance: internalOrder.ioAvailableBalance || internalOrder.io_available_balance || claimDetails.ioAvailableBalance || claimDetails.io_available_balance, blockedAmount: internalOrder.ioBlockedAmount || internalOrder.io_blocked_amount || claimDetails.ioBlockedAmount || claimDetails.io_blocked_amount, remainingBalance: internalOrder.ioRemainingBalance || internalOrder.io_remaining_balance || claimDetails.ioRemainingBalance || claimDetails.io_remaining_balance, organizedBy: internalOrder.organizer?.displayName || internalOrder.organizer?.name || internalOrder.organizedBy || '', organizedAt: internalOrder.organizedAt || internalOrder.organized_at || '', }; // Map DMS details from new invoice and credit note tables const dmsDetails = { eInvoiceNumber: invoice.invoiceNumber || invoice.invoice_number || claimDetails.eInvoiceNumber || claimDetails.e_invoice_number, eInvoiceDate: invoice.invoiceDate || invoice.invoice_date || claimDetails.eInvoiceDate || claimDetails.e_invoice_date, dmsNumber: invoice.dmsNumber || invoice.dms_number || claimDetails.dmsNumber || claimDetails.dms_number, creditNoteNumber: creditNote.creditNoteNumber || creditNote.credit_note_number || claimDetails.creditNoteNumber || claimDetails.credit_note_number, creditNoteDate: creditNote.creditNoteDate || creditNote.credit_note_date || claimDetails.creditNoteDate || claimDetails.credit_note_date, creditNoteAmount: creditNote.creditNoteAmount ? Number(creditNote.creditNoteAmount) : (creditNote.credit_note_amount ? Number(creditNote.credit_note_amount) : (creditNote.creditNoteAmount ? Number(creditNote.creditNoteAmount) : (claimDetails.creditNoteAmount ? Number(claimDetails.creditNoteAmount) : (claimDetails.credit_note_amount ? Number(claimDetails.credit_note_amount) : undefined)))), }; // Map claim amounts const claimAmount = { estimated: activityInfo.estimatedBudget || 0, closed: activityInfo.closedExpenses || 0, }; return { activityInfo, dealerInfo, proposalDetails: proposal, ioDetails: Object.keys(ioDetails).some(k => ioDetails[k as keyof typeof ioDetails]) ? ioDetails : undefined, dmsDetails: Object.keys(dmsDetails).some(k => dmsDetails[k as keyof typeof dmsDetails]) ? dmsDetails : undefined, claimAmount, }; } catch (error) { console.error('[claimDataMapper] Error mapping claim data:', error); return null; } } /** * Determine user's role in the request */ export function determineUserRole(apiRequest: any, currentUserId: string): RequestRole { try { // Check if user is the initiator if (apiRequest.initiatorId === currentUserId || apiRequest.initiator?.userId === currentUserId || apiRequest.requestedBy?.userId === currentUserId) { return 'INITIATOR'; } // Check if user is a dealer (participant with DEALER type) const participants = apiRequest.participants || []; const dealerParticipant = participants.find((p: any) => (p.userId === currentUserId || p.user?.userId === currentUserId) && (p.participantType === 'DEALER' || p.type === 'DEALER') ); if (dealerParticipant) { return 'DEALER'; } // Check if user is a department lead (approver at level 3) const approvalLevels = apiRequest.approvalLevels || []; const deptLeadLevel = approvalLevels.find((level: any) => level.levelNumber === 3 && (level.approverId === currentUserId || level.approver?.userId === currentUserId) ); if (deptLeadLevel) { return 'DEPARTMENT_LEAD'; } // Check if user is an approver const approverLevel = approvalLevels.find((level: any) => (level.approverId === currentUserId || level.approver?.userId === currentUserId) && level.status === 'PENDING' ); if (approverLevel) { return 'APPROVER'; } // Default to spectator return 'SPECTATOR'; } catch (error) { console.error('[claimDataMapper] Error determining user role:', error); return 'SPECTATOR'; } } /** * Get role-based visibility settings */ export function getRoleBasedVisibility(role: RequestRole): RoleVisibility { switch (role) { case 'INITIATOR': return { showDealerInfo: true, showProposalDetails: true, showIODetails: true, showDMSDetails: true, showClaimAmount: true, canEditClaimAmount: false, // Can only edit in specific scenarios }; case 'DEALER': return { showDealerInfo: true, showProposalDetails: true, showIODetails: false, showDMSDetails: false, showClaimAmount: true, canEditClaimAmount: false, }; case 'DEPARTMENT_LEAD': return { showDealerInfo: true, showProposalDetails: true, showIODetails: true, showDMSDetails: true, showClaimAmount: true, canEditClaimAmount: false, }; case 'APPROVER': return { showDealerInfo: true, showProposalDetails: true, showIODetails: true, showDMSDetails: true, showClaimAmount: true, canEditClaimAmount: false, }; case 'SPECTATOR': default: return { showDealerInfo: false, showProposalDetails: false, showIODetails: false, showDMSDetails: false, showClaimAmount: false, canEditClaimAmount: false, }; } }