import { useState, useEffect } from 'react'; import { API } from '@/api/API'; import { settlementService } from '@/services/settlement.service'; import { Loader2 } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Progress } from '@/components/ui/progress'; import { ArrowLeft, IndianRupee, CheckCircle, XCircle, Upload, FileText, User, AlertCircle, Wallet, Receipt, TrendingUp, TrendingDown, Building, CreditCard, Send, Users, Plus, Edit2, Trash2, Save, Paperclip, FileDown } from 'lucide-react'; import { toast } from 'sonner'; import { DocumentPreviewModal } from '@/components/ui/DocumentPreviewModal'; import { formatDateTime } from '@/lib/dateUtils'; import { BankDetailsModal } from '@/features/onboarding/components/BankDetailsModal'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; const ALL_DEPARTMENTS = [ 'Warranty Department', 'Accessories Department', 'Sales Department', 'RTO Department', 'Service Department', 'Parts Department', 'Finance Department', 'Insurance Department', 'Inventory Department', 'Marketing Department', 'HR Department', 'IT Department', 'Legal Department', 'Quality Department', 'Logistics Department', 'Customer Relations Department' ]; const DEPARTMENT_CLAIM_PREFIX = '[DEPARTMENT_CLAIM]'; const FINANCE_VALIDATED_PREFIX = '[FINANCE_VALIDATED]'; interface FinanceFnFDetailsPageProps { fnfId: string; onBack: () => void; } // Removing mock data functions as we use live API const SETTLEMENT_CHECKLIST = [ { id: 'calculations', label: 'Verified All Department Calculations' }, { id: 'bank', label: 'Confirmed Bank Account Details' }, { id: 'docs', label: 'Reviewed All Supporting Documents' }, { id: 'sap', label: 'Synced Final Dues with SAP' }, { id: 'noc', label: 'Received All Mandatory NOCs' } ]; interface FinancialLineItem { id: string; department: string; description: string; amount: number; } export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPageProps) { const [fnfCase, setFnfCase] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('overview'); // Initialize editable line items const [payableItems, setPayableItems] = useState([]); const [receivableItems, setReceivableItems] = useState([]); const [deductionItems, setDeductionItems] = useState([]); const [previewDocument, setPreviewDocument] = useState(null); const [bankDetails, setBankDetails] = useState([]); const [isBankModalOpen, setIsBankModalOpen] = useState(false); const [editingBank, setEditingBank] = useState(null); const [checklist, setChecklist] = useState([]); useEffect(() => { fetchDepartments(); fetchFnFDetails(); }, [fnfId]); const fetchDepartments = async () => { // We use the local ALL_DEPARTMENTS constant as the source of truth // But we check if the server has a different list try { const response = await API.getSettlementDepartments(); const data = response.data as any; if (data && data.success && data.departments?.length > 0) { // If needed, we could set a state here, but for now we stick to the standardized 16 } } catch (error) { console.error("Fetch departments error:", error); } }; const normalizeDepartment = (name: string) => { if (!name) return name; let inputName = name.trim(); // Exact match first const exactMatch = ALL_DEPARTMENTS.find(d => d.toLowerCase() === inputName.toLowerCase()); if (exactMatch) return exactMatch; // Smart mapping for shorthands const mapping: Record = { 'sales': 'Sales Department', 'service': 'Service Department', 'spares': 'Parts Department', 'parts': 'Parts Department', 'spares / parts': 'Parts Department', 'finance': 'Finance Department', 'accounts': 'Finance Department', 'warranty': 'Warranty Department', 'marketing': 'Marketing Department', 'hr': 'HR Department', 'it': 'IT Department', 'legal': 'Legal Department', 'logistics': 'Logistics Department', 'quality': 'Quality Department', 'fdd': 'Finance Department', 'apparel': 'Accessories Department', 'accessories': 'Accessories Department', 'dms': 'IT Department', 'rto': 'Admin Department', 'admin': 'Admin Department', 'admin / dd-admin': 'Admin Department' }; const mapped = mapping[inputName.toLowerCase().replace(' department', '')]; if (mapped) return mapped; return name; }; const isDepartmentClaimLine = (description?: string, sourceType?: string) => sourceType === 'DepartmentClaim' || (typeof description === 'string' && (description.startsWith(DEPARTMENT_CLAIM_PREFIX) || description.includes('Clearance:'))); const isAutoSeededDeptMirror = (li: any) => li?.sourceType === 'FinanceValidated' && typeof li?.description === 'string' && li.description.includes('Auto-seeded from department claim'); const isFinanceValidatedLine = (description?: string, sourceType?: string) => sourceType === 'FinanceValidated' || (typeof description === 'string' && description.startsWith(FINANCE_VALIDATED_PREFIX)); const cleanLineItemDescription = (description?: string) => (description || '') .replace(DEPARTMENT_CLAIM_PREFIX, '') .replace(FINANCE_VALIDATED_PREFIX, '') .trim(); const fetchFnFDetails = async (showLoader: boolean = true) => { try { if (showLoader) setLoading(true); const response = await API.getFnFSettlementById(fnfId); const data = response.data as any; if (data.success) { const s = data.fnf; const mappedCase = { id: s.id, caseNumber: s.settlementId || s.resignation?.resignationId || s.terminationRequest?.requestId || s.id.substring(0, 8), dealerName: s.outlet?.dealer?.fullName || s.dealer?.fullName || 'N/A', dealerCode: s.outlet?.code || s.dealer?.dealerCode?.dealerCode || 'N/A', location: s.outlet?.city || s.outlet?.location || 'N/A', terminationType: s.resignationId ? 'Resignation' : 'Termination', submittedDate: formatDateTime(s.createdAt), createdAt: s.createdAt, dueDate: s.settlementDate ? formatDateTime(s.settlementDate) : 'TBD', status: s.status, dealerId: s.outlet?.dealer?.id || s.dealerId, originalRequestId: s.resignation?.resignationId || s.terminationRequest?.requestId || s.terminationRequest?.id || "N/A", salesCode: s.dealer?.dealerCode?.salesCode || s.outlet?.dealer?.dealerProfile?.dealerCode?.salesCode || 'N/A', serviceCode: s.dealer?.dealerCode?.serviceCode || s.outlet?.dealer?.dealerProfile?.dealerCode?.serviceCode || 'N/A', gearCode: s.dealer?.dealerCode?.gearCode || s.outlet?.dealer?.dealerProfile?.dealerCode?.gearCode || 'N/A', gmaCode: s.dealer?.dealerCode?.gmaCode || s.outlet?.dealer?.dealerProfile?.dealerCode?.gmaCode || 'N/A', allLineItems: (s.lineItems || []).filter((li: any) => li.isActive !== false), departmentResponses: ALL_DEPARTMENTS.map((deptName: string) => { const c = (s.clearances || []).find((clearance: any) => normalizeDepartment(clearance.department) === deptName); const lines = (s.lineItems || []).filter((li: any) => li.isActive !== false); const claimLines = lines.filter( (li: any) => normalizeDepartment(li.department) === deptName && isDepartmentClaimLine(li.description, li.sourceType), ); const seededMirrorLines = lines.filter( (li: any) => normalizeDepartment(li.department) === deptName && isAutoSeededDeptMirror(li), ); const relatedItems = claimLines.length > 0 ? claimLines : seededMirrorLines; // Calculate departmental net let deptPayables = 0; let deptRecoveries = 0; relatedItems.forEach((li: any) => { const amt = Math.abs(parseFloat(li.amount) || 0); if (li.itemType === 'Payable') deptPayables += amt; else deptRecoveries += amt; // Receivables & Deductions }); const netAmount = deptPayables - deptRecoveries; const hasDuesAmount = Math.abs(netAmount) > 0; const rawStatus = c?.status || 'Pending'; const normalizedStatus = hasDuesAmount ? 'Dues Pending' : (rawStatus === 'Cleared' ? 'NOC Submitted' : rawStatus); /** Net payable to dealer vs receivable from dealer — drives UI colors */ const duesFlow = netAmount > 0 ? 'payable' as const : netAmount < 0 ? 'recovery' as const : null; return { id: c?.id || `dept-${deptName}`, departmentName: deptName, status: normalizedStatus, remarks: c?.remarks || '-', submittedDate: c?.clearedAt ? formatDateTime(c.clearedAt) : '-', amount: Math.abs(netAmount), duesFlow, amountType: netAmount > 0 ? 'Payable to dealer' : netAmount < 0 ? 'Receivable from dealer' : null, supportingDocument: c?.supportingDocument || null }; }), documents: [ { name: 'Resignation Letter.pdf', size: 'N/A', uploadedOn: formatDateTime(s.createdAt), type: 'Resignation', url: '#' }, ...(s.clearances || []) .filter((c: any) => c.supportingDocument) .map((c: any) => ({ name: c.supportingDocument.split('/').pop(), size: 'N/A', uploadedOn: formatDateTime(c.clearedAt), type: `${c.department} Proof`, url: c.supportingDocument })), ...(s.clearanceDocuments || []).map((doc: any) => ({ name: doc.name || doc.supportingDocument?.split('/').pop() || 'Document', size: 'N/A', uploadedOn: formatDateTime(doc.clearedAt || s.createdAt), type: 'Finance Upload', url: doc.supportingDocument })) ] }; setFnfCase(mappedCase); // Sync bank details from the pre-fetched data inside the settlement object const preFetchedBankDetails = s.bankDetails || s.dealer?.bankDetails || s.outlet?.dealer?.dealerProfile?.bankDetails; if (preFetchedBankDetails && preFetchedBankDetails.length > 0) { setBankDetails(preFetchedBankDetails); } else if (s.outlet?.dealer?.id || s.dealerId) { fetchBankDetails(s.outlet?.dealer?.id || s.dealerId); } // Split line items into categories const pItems: FinancialLineItem[] = []; const rItems: FinancialLineItem[] = []; const dItems: FinancialLineItem[] = []; const allLineItems = (s.lineItems || []).filter((li: any) => li.isActive !== false); const hasFinanceValidatedLines = allLineItems.some((li: any) => isFinanceValidatedLine(li.description, li.sourceType)); const calculationLineItems = hasFinanceValidatedLines ? allLineItems.filter((li: any) => isFinanceValidatedLine(li.description, li.sourceType)) : allLineItems.filter((li: any) => !isDepartmentClaimLine(li.description, li.sourceType)); calculationLineItems.forEach((li: any) => { const item: FinancialLineItem = { id: li.id, department: normalizeDepartment(li.department), description: cleanLineItemDescription(li.description || li.remarks || ''), amount: Math.abs(li.amount) }; if (li.itemType === 'Payable') { pItems.push(item); } else if (li.itemType === 'Deduction') { dItems.push(item); } else { rItems.push(item); } }); setPayableItems(pItems); setReceivableItems(rItems); setDeductionItems(dItems); // Populate settlement details from backend setSettlementDetails({ verificationTransactionId: s.transactionReference || '', settlementAmount: (s.settlementAmount || calculateDynamicSettlement().settlementAmount).toString(), settlementDate: s.settlementDate ? new Date(s.settlementDate).toISOString().split('T')[0] : new Date().toISOString().split('T')[0], paymentMode: s.paymentMode || '', bankReference: '', // Optional field verificationRemarks: s.remarks || '', adjustments: '0' }); } } catch (error) { console.error('Fetch F&F error:', error); toast.error('Failed to fetch settlement details'); } finally { if (showLoader) setLoading(false); } }; const fetchBankDetails = async (dealerId: string) => { try { const response = await API.getDealerBankDetails(dealerId); const data = response.data as any; if (data.success) { setBankDetails(data.bankDetails || []); } } catch (error) { console.error('Fetch bank details error:', error); } }; const handleUpsertBank = async (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const data = Object.fromEntries(formData.entries()); try { const dealerId = fnfCase?.dealerId; const response = await API.saveBankDetail(dealerId, { ...data, id: editingBank?.id, isPrimary: formData.get('isPrimary') === 'on' }) as any; if (response.data.success) { toast.success('Bank details saved'); fetchBankDetails(dealerId); setIsBankModalOpen(false); setEditingBank(null); } } catch (error) { toast.error('Failed to save bank details'); } }; const handleDeleteBank = async (id: string) => { if (!confirm('Are you sure you want to delete this bank account?')) return; try { const response = await API.deleteBankDetail(id) as any; if (response.data.success) { toast.success('Bank detail deleted'); fetchBankDetails(fnfCase?.dealerId); } } catch (error) { toast.error('Failed to delete bank details'); } }; const toggleChecklist = (id: string) => { setChecklist(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] ); }; // Form states for adding new items const [newPayable, setNewPayable] = useState({ department: '', description: '', amount: '' }); const [newReceivable, setNewReceivable] = useState({ department: '', description: '', amount: '' }); const [newDeduction, setNewDeduction] = useState({ department: '', description: '', amount: '' }); // Edit mode states const [editingPayableId, setEditingPayableId] = useState(null); const [editingReceivableId, setEditingReceivableId] = useState(null); const [editingDeductionId, setEditingDeductionId] = useState(null); const [editingPayableDrafts, setEditingPayableDrafts] = useState>({}); const [editingReceivableDrafts, setEditingReceivableDrafts] = useState>({}); const [editingDeductionDrafts, setEditingDeductionDrafts] = useState>({}); // Calculate dynamic settlement const calculateDynamicSettlement = () => { const payables = payableItems.reduce((sum, item) => sum + (Number(item.amount) || 0), 0); const receivables = receivableItems.reduce((sum, item) => sum + (Number(item.amount) || 0), 0); const deductions = deductionItems.reduce((sum, item) => sum + (Number(item.amount) || 0), 0); const netSettlement = payables - receivables - deductions; return { payables, receivables, deductions, netSettlement, settlementAmount: Math.abs(netSettlement), settlementType: netSettlement > 0 ? 'Payable to Dealer' : netSettlement < 0 ? 'Receivable from Dealer' : 'No Settlement Required' }; }; const settlement = calculateDynamicSettlement(); const departmentReconciliation = ALL_DEPARTMENTS.map((dept) => { const claim = (fnfCase?.departmentResponses || []).find((d: any) => d.departmentName === dept); const claimAmount = Number(claim?.amount) || 0; const claimType = claim?.amountType || '-'; const validatedPayable = payableItems .filter((item) => normalizeDepartment(item.department) === dept) .reduce((sum, item) => sum + (Number(item.amount) || 0), 0); const validatedReceivable = receivableItems .filter((item) => normalizeDepartment(item.department) === dept) .reduce((sum, item) => sum + (Number(item.amount) || 0), 0); const validatedDeduction = deductionItems .filter((item) => normalizeDepartment(item.department) === dept) .reduce((sum, item) => sum + (Number(item.amount) || 0), 0); const validatedNet = validatedPayable - validatedReceivable - validatedDeduction; const validatedAmount = Math.abs(validatedNet); const validatedType = validatedNet > 0 ? 'Payable' : validatedNet < 0 ? 'Receivable' : '-'; const variance = validatedAmount - claimAmount; return { department: dept, claimAmount, claimType, validatedAmount, validatedType, variance }; }); const [settlementDetails, setSettlementDetails] = useState({ verificationTransactionId: '', settlementAmount: settlement.settlementAmount.toString(), settlementDate: new Date().toISOString().split('T')[0], paymentMode: '', bankReference: '', verificationRemarks: '', adjustments: '0' }); // Handlers for Payables const handleAddPayable = async () => { if (!newPayable.department || !newPayable.description || !newPayable.amount) { toast.error('Please fill in all fields'); return; } try { const response = await API.addLineItem(fnfId, { department: newPayable.department, description: newPayable.description, amount: Math.abs(parseFloat(newPayable.amount)), itemType: 'Payable' }); const data = response.data as any; if (data.success) { setPayableItems([...payableItems, { id: data.lineItem.id, department: data.lineItem.department, description: data.lineItem.description, amount: Math.abs(data.lineItem.amount) }]); setNewPayable({ department: '', description: '', amount: '' }); toast.success('Payable item added'); fetchFnFDetails(); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to add payable item'); } }; const handleUpdatePayable = async (id: string, field: keyof FinancialLineItem, value: string | number) => { setEditingPayableDrafts((prev) => { const base = prev[id] || payableItems.find((item) => item.id === id); if (!base) return prev; return { ...prev, [id]: { ...base, [field]: field === 'amount' ? Number(value) || 0 : value } as FinancialLineItem }; }); }; const handleSavePayableEdit = async (id: string) => { const draft = editingPayableDrafts[id]; if (!draft) { setEditingPayableId(null); return; } setPayableItems((prev) => prev.map((item) => (item.id === id ? draft : item))); try { await API.updateLineItem(id, { department: draft.department, description: draft.description, amount: -Math.abs(Number(draft.amount) || 0) }); setEditingPayableId(null); setEditingPayableDrafts((prev) => { const next = { ...prev }; delete next[id]; return next; }); toast.success('Changes saved'); fetchFnFDetails(false); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to update item'); fetchFnFDetails(false); } }; const handleDeletePayable = async (id: string) => { try { const response = await API.deleteLineItem(id); const data = response.data as any; if (data.success) { setPayableItems(payableItems.filter(item => item.id !== id)); toast.info('Payable item removed'); fetchFnFDetails(); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to delete item'); } }; // Handlers for Receivables const handleAddReceivable = async () => { if (!newReceivable.department || !newReceivable.description || !newReceivable.amount) { toast.error('Please fill in all fields'); return; } try { const response = await API.addLineItem(fnfId, { department: newReceivable.department, description: newReceivable.description, amount: Math.abs(parseFloat(newReceivable.amount)), itemType: 'Receivable' }); const data = response.data as any; if (data.success) { setReceivableItems([...receivableItems, { id: data.lineItem.id, department: data.lineItem.department, description: data.lineItem.description, amount: data.lineItem.amount }]); setNewReceivable({ department: '', description: '', amount: '' }); toast.success('Receivable item added'); fetchFnFDetails(); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to add receivable item'); } }; const handleUpdateReceivable = async (id: string, field: keyof FinancialLineItem, value: string | number) => { setEditingReceivableDrafts((prev) => { const base = prev[id] || receivableItems.find((item) => item.id === id); if (!base) return prev; return { ...prev, [id]: { ...base, [field]: field === 'amount' ? Number(value) || 0 : value } as FinancialLineItem }; }); }; const handleSaveReceivableEdit = async (id: string) => { const draft = editingReceivableDrafts[id]; if (!draft) { setEditingReceivableId(null); return; } setReceivableItems((prev) => prev.map((item) => (item.id === id ? draft : item))); try { await API.updateLineItem(id, { department: draft.department, description: draft.description, amount: Math.abs(Number(draft.amount) || 0) }); setEditingReceivableId(null); setEditingReceivableDrafts((prev) => { const next = { ...prev }; delete next[id]; return next; }); toast.success('Changes saved'); fetchFnFDetails(false); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to update item'); fetchFnFDetails(false); } }; const handleDeleteReceivable = async (id: string) => { try { await API.deleteLineItem(id); setReceivableItems(receivableItems.filter(item => item.id !== id)); toast.info('Receivable item removed'); fetchFnFDetails(); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to delete item'); } }; // Handlers for Deductions const handleAddDeduction = async () => { if (!newDeduction.department || !newDeduction.description || !newDeduction.amount) { toast.error('Please fill in all fields'); return; } try { const response = await API.addLineItem(fnfId, { department: newDeduction.department, description: newDeduction.description, amount: Math.abs(parseFloat(newDeduction.amount)), itemType: 'Deduction' }); const data = response.data as any; if (data.success) { setDeductionItems([...deductionItems, { id: data.lineItem.id, department: data.lineItem.department, description: data.lineItem.description, amount: data.lineItem.amount }]); setNewDeduction({ department: '', description: '', amount: '' }); toast.success('Deduction item added'); fetchFnFDetails(); } } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to add deduction item'); } }; const handleUpdateDeduction = async (id: string, field: keyof FinancialLineItem, value: string | number) => { setEditingDeductionDrafts((prev) => { const base = prev[id] || deductionItems.find((item) => item.id === id); if (!base) return prev; return { ...prev, [id]: { ...base, [field]: field === 'amount' ? Number(value) || 0 : value } as FinancialLineItem }; }); }; const handleSaveDeductionEdit = async (id: string) => { const draft = editingDeductionDrafts[id]; if (!draft) { setEditingDeductionId(null); return; } setDeductionItems((prev) => prev.map((item) => (item.id === id ? draft : item))); try { await API.updateLineItem(id, { department: draft.department, description: draft.description, amount: Math.abs(Number(draft.amount) || 0) }); setEditingDeductionId(null); setEditingDeductionDrafts((prev) => { const next = { ...prev }; delete next[id]; return next; }); toast.success('Changes saved'); fetchFnFDetails(false); } catch (error: any) { toast.error(error.response?.data?.message || 'Failed to update item'); fetchFnFDetails(false); } }; const handleDeleteDeduction = async (id: string) => { try { await API.deleteLineItem(id); setDeductionItems(deductionItems.filter(item => item.id !== id)); toast.info('Deduction item removed'); fetchFnFDetails(); } catch (error) { toast.error('Failed to delete item'); } }; const handleFileUpload = async (event: React.ChangeEvent) => { const files = event.target.files; if (files && files.length > 0) { setLoading(true); try { let successCount = 0; for (let i = 0; i < files.length; i++) { const formData = new FormData(); formData.append('file', files[i]); const response: any = await API.uploadFnFDocument(fnfId, formData); if (response.data?.success) successCount++; } toast.success(`${successCount} document(s) uploaded successfully`); fetchFnFDetails(false); // Fetch latest documents } catch (error) { toast.error('Failed to upload document(s)'); } finally { setLoading(false); } } }; const [submitting, setSubmitting] = useState(false); const handleApproveSettlement = async () => { if (!settlementDetails.verificationTransactionId || !settlementDetails.settlementDate || !settlementDetails.paymentMode) { toast.error('Please fill in all required settlement details'); return; } try { setSubmitting(true); const adjustedAmount = (settlement.settlementAmount || 0) + parseFloat(settlementDetails.adjustments || '0'); await settlementService.updateFnF(fnfId, { status: 'Completed', finalSettlementAmount: adjustedAmount, settlementDate: settlementDetails.settlementDate, paymentMode: settlementDetails.paymentMode, transactionReference: settlementDetails.verificationTransactionId, remarks: settlementDetails.verificationRemarks || 'Approved by Finance' }); toast.success(`F&F Settlement approved and completed for ${fnfCase.dealerName}`); setTimeout(() => onBack(), 1500); } catch (error: any) { console.error('Approve settlement error:', error); toast.error(error.message || 'Failed to approve settlement'); } finally { setSubmitting(false); } }; const handleRejectSettlement = () => { if (!settlementDetails.verificationRemarks) { toast.error('Please provide remarks for rejection'); return; } toast.error(`F&F Settlement rejected for ${fnfCase.dealerName}`); setTimeout(() => onBack(), 1500); }; const handleRequestClarification = () => { if (!settlementDetails.verificationRemarks) { toast.error('Please provide details for clarification request'); return; } toast.info(`Clarification request sent for ${fnfCase.dealerName}`); setTimeout(() => onBack(), 1500); }; if (loading) { return (
); } if (!fnfCase) { return (

Settlement case not found

); } return (
{/* Header */}

F&F Settlement Review

Full & Final Settlement for {fnfCase.dealerName}

{/* Status Banner */}

Settlement Pending Finance Approval

Case: {fnfCase.caseNumber} • Due: {fnfCase.dueDate}

{fnfCase.status} {fnfCase.terminationType}
{/* Settlement Summary Card */}
{settlement.settlementType === 'Payable to Dealer' ? ( ) : settlement.settlementType === 'Receivable from Dealer' ? ( ) : ( )}

{settlement.settlementType}

{settlement.settlementType === 'No Settlement Required' ? '₹0' : `₹${settlement.settlementAmount.toLocaleString('en-IN')}`}

Net Settlement Amount

{settlement.settlementType === 'Payable to Dealer' ? 'Company will pay to dealer' : settlement.settlementType === 'Receivable from Dealer' ? 'Dealer must pay to company' : 'No payment required'}

{/* Left Column - Case Details & Financial Info */}
Overview Financial Departments Documents Bank Details {/* Case Information */} Case Information

{fnfCase.caseNumber}

{fnfCase.dealerCode}

{fnfCase.dealerName}

{fnfCase.location}

{fnfCase.terminationType}
{fnfCase.status}

{fnfCase.submittedDate}

{fnfCase.dueDate}

{(() => { const submitted = new Date(fnfCase.createdAt); const today = new Date(); const diffTime = Math.abs(today.getTime() - submitted.getTime()); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return `${diffDays} day${diffDays !== 1 ? 's' : ''}`; })()}

{fnfCase.salesCode}

{fnfCase.serviceCode}

{fnfCase.gearCode}

{fnfCase.gmaCode}

{/* Settlement Calculation Overview */} Settlement Calculation Summary
Total Payables (to Dealer) + ₹{settlement.payables.toLocaleString('en-IN')}
Total Receivables (from Dealer) - ₹{settlement.receivables.toLocaleString('en-IN')}
Total Deductions - ₹{settlement.deductions.toLocaleString('en-IN')}
Net Settlement

{settlement.settlementType}

{settlement.settlementType === 'No Settlement Required' ? '₹0' : `₹${settlement.settlementAmount.toLocaleString('en-IN')}`}

Calculation Formula

Net Settlement = Payables - Receivables - Deductions
All amounts are editable in the Financial tab

Department Claim vs Finance Validation Finance validated values are used for final settlement totals. Department Department Claim Finance Validated Variance {departmentReconciliation.map((row) => ( {row.department} {row.claimAmount > 0 ? `${row.claimType} ₹${row.claimAmount.toLocaleString('en-IN')}` : '-'} {row.validatedAmount > 0 ? `${row.validatedType} ₹${row.validatedAmount.toLocaleString('en-IN')}` : '-'} 0 ? 'text-red-600' : 'text-green-600'}> {row.claimAmount === 0 && row.validatedAmount === 0 ? '-' : `₹${row.variance.toLocaleString('en-IN')}`} ))}
{/* Payables - Editable */}
Payables to Dealer (Editable) Add or modify amounts company owes to dealer
{/* Existing Payables */} Department Description Amount (₹) Actions {payableItems.map((item) => ( {editingPayableId === item.id ? ( ) : ( {normalizeDepartment(item.department)} )} {editingPayableId === item.id ? ( handleUpdatePayable(item.id, 'description', e.target.value)} className="h-8" /> ) : ( {item.description} )} {editingPayableId === item.id ? ( handleUpdatePayable(item.id, 'amount', e.target.value)} className="h-8 text-right" /> ) : ( ₹{item.amount.toLocaleString('en-IN')} )}
{editingPayableId === item.id ? ( ) : ( )}
))}
{/* Add New Payable */}

Add New Payable Item:

setNewPayable({ ...newPayable, description: e.target.value })} className="col-span-5" /> setNewPayable({ ...newPayable, amount: e.target.value })} className="col-span-3" />
{/* Total */}
Total Payables ₹{settlement.payables.toLocaleString('en-IN')}
{/* Receivables - Editable */}
Receivables from Dealer (Editable) Add or modify amounts dealer owes to company
{/* Existing Receivables */} Department Description Amount (₹) Actions {receivableItems.map((item) => ( {editingReceivableId === item.id ? ( ) : ( {normalizeDepartment(item.department)} )} {editingReceivableId === item.id ? ( handleUpdateReceivable(item.id, 'description', e.target.value)} className="h-8" /> ) : ( {item.description} )} {editingReceivableId === item.id ? ( handleUpdateReceivable(item.id, 'amount', e.target.value)} className="h-8 text-right" /> ) : ( ₹{item.amount.toLocaleString('en-IN')} )}
{editingReceivableId === item.id ? ( ) : ( )}
))}
{/* Add New Receivable */}

Add New Receivable Item:

setNewReceivable({ ...newReceivable, description: e.target.value })} className="col-span-5" /> setNewReceivable({ ...newReceivable, amount: e.target.value })} className="col-span-3" />
{/* Total */}
Total Receivables ₹{settlement.receivables.toLocaleString('en-IN')}
{/* Deductions - Editable */}
Deductions (Editable) Add or modify pending claims and deductions
{/* Existing Deductions */} Department Description Amount (₹) Actions {deductionItems.map((item) => ( {editingDeductionId === item.id ? ( ) : ( {normalizeDepartment(item.department)} )} {editingDeductionId === item.id ? ( handleUpdateDeduction(item.id, 'description', e.target.value)} className="h-8" /> ) : ( {item.description} )} {editingDeductionId === item.id ? ( handleUpdateDeduction(item.id, 'amount', e.target.value)} className="h-8 text-right" /> ) : ( ₹{item.amount.toLocaleString('en-IN')} )}
{editingDeductionId === item.id ? ( ) : ( )}
))}
{/* Add New Deduction */}

Add New Deduction Item:

setNewDeduction({ ...newDeduction, description: e.target.value })} className="col-span-5" /> setNewDeduction({ ...newDeduction, amount: e.target.value })} className="col-span-3" />
{/* Total */}
Total Deductions ₹{settlement.deductions.toLocaleString('en-IN')}
{/* Final Settlement Summary */} Final Settlement Summary
Total Payables (to Dealer) + ₹{settlement.payables.toLocaleString('en-IN')}
Total Receivables (from Dealer) - ₹{settlement.receivables.toLocaleString('en-IN')}
Total Deductions - ₹{settlement.deductions.toLocaleString('en-IN')}

Net Settlement

{settlement.settlementType}

{settlement.settlementType === 'No Settlement Required' ? '₹0' : `₹${settlement.settlementAmount.toLocaleString('en-IN')}`}

Calculation Formula

Net Settlement = Payables - Receivables - Deductions
{settlement.netSettlement > 0 && 'Positive value means company pays to dealer'} {settlement.netSettlement < 0 && 'Negative value means dealer pays to company'} {settlement.netSettlement === 0 && 'Zero means no payment required from either party'}

{/* Progress Summary */} Department Response Progress {fnfCase.departmentResponses.filter((d: any) => d.status !== 'Pending').length} of {fnfCase.departmentResponses.length} departments have responded d.status !== 'Pending').length / fnfCase.departmentResponses.length) * 100} className="h-3" />

NOC Submitted

{fnfCase.departmentResponses.filter((d: any) => d.status === 'NOC Submitted').length}

Dues Pending

{fnfCase.departmentResponses.filter((d: any) => d.status === 'Dues Pending').length}

Awaiting Response

{fnfCase.departmentResponses.filter((d: any) => d.status === 'Pending').length}

{/* Department Responses Table */} All Department Responses Status of NOC and dues clearance from all 16 departments (read-only for Finance; updates are done by department stakeholders). Department Status Amount Type Amount Submitted Date Remarks {fnfCase.departmentResponses.map((dept: any) => ( {dept.departmentName} {dept.status} {dept.amountType ? ( {dept.amountType} ) : ( '-' )} {dept.amount ? ( ₹{dept.amount.toLocaleString('en-IN')} ) : ( '-' )} {dept.submittedDate || '-'}
{dept.remarks || '-'} {dept.supportingDocument && ( )}
))}
{/* Important Notes */}

Department Response Guidelines

  • NOC Submitted: Department has no outstanding dues and provided clearance
  • Dues Pending: Department has identified amounts to be recovered or paid
  • Pending: Department has not yet responded to the F&F request
{/* Submitted Documents */} Submitted Documents
{fnfCase.documents.map((doc: any, index: number) => (

{doc.name}

{doc.size} • {doc.type} • Uploaded on {doc.uploadedOn}

{doc.url && doc.url !== '#' && ( )}
))}
{/* Upload Additional Documents */} Upload Settlement Verification Documents Upload bank receipts, settlement proofs, or any additional documents

Click to upload or drag and drop

PDF, DOC, DOCX, PNG, JPG, XLSX (max 10MB)

Dealer Bank Account Details Manage bank accounts for settlement transfer
{bankDetails.length > 0 ? ( bankDetails.map((bank: any) => ( {bank.isPrimary && (
Primary
)}

{bank.accountHolderName}

{bank.bankName}

{bank.ifscCode}

{bank.accountNumber}

)) ) : (

No bank details found

)}
{/* Right Column - Settlement Verification Form */}
Settlement Verification Enter settlement transaction details {fnfCase.status === 'Completed' ? (
Settlement Completed

This settlement has been finalized and processed.

Settlement Date {formatDateTime(settlementDetails.settlementDate)}
Payment Mode {settlementDetails.paymentMode}
Transaction ID {settlementDetails.verificationTransactionId}
Final Amount ₹{parseFloat(settlementDetails.settlementAmount).toLocaleString()}
{settlementDetails.verificationRemarks && (
{settlementDetails.verificationRemarks}
)}
) : ( <> {/* Settlement Checklist */}

Compliance Checklist

{SETTLEMENT_CHECKLIST.map(item => (
toggleChecklist(item.id)} className="w-4 h-4 mt-1 rounded border-slate-300 text-re-red focus:ring-re-red" />
))}
setSettlementDetails({ ...settlementDetails, paymentMode: e.target.value })} />
setSettlementDetails({ ...settlementDetails, verificationTransactionId: e.target.value })} />
setSettlementDetails({ ...settlementDetails, bankReference: e.target.value })} />
setSettlementDetails({ ...settlementDetails, settlementAmount: e.target.value })} />
{ const adjustments = e.target.value; const adjustedAmount = settlement.settlementAmount + parseFloat(adjustments || '0'); setSettlementDetails({ ...settlementDetails, adjustments, settlementAmount: adjustedAmount.toString() }); }} /> {parseFloat(settlementDetails.adjustments) !== 0 && (

Adjusted amount: ₹{settlementDetails.settlementAmount}

)}
setSettlementDetails({ ...settlementDetails, settlementDate: e.target.value })} />