'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Button } from 'primereact/button'; import { InputText } from 'primereact/inputtext'; import { Card } from 'primereact/card'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { Tag } from 'primereact/tag'; import { Dialog } from 'primereact/dialog'; import { Calendar } from 'primereact/calendar'; import { InputTextarea } from 'primereact/inputtextarea'; import { Dropdown } from 'primereact/dropdown'; import { Chip } from 'primereact/chip'; import { ProgressBar } from 'primereact/progressbar'; import { factureService } from '../../../../services/api'; import { formatDate, formatCurrency } from '../../../../utils/formatters'; import type { Facture } from '../../../../types/btp'; import { StatutFacture } from '../../../../types/btp'; import factureActionsService from '../../../../services/factureActionsService'; const FacturesRetardPage = () => { const [factures, setFactures] = useState([]); const [loading, setLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [selectedFactures, setSelectedFactures] = useState([]); const [actionDialog, setActionDialog] = useState(false); const [selectedFacture, setSelectedFacture] = useState(null); const [actionType, setActionType] = useState<'urgent_reminder' | 'legal_notice' | 'suspend_client'>('urgent_reminder'); const [urgentReminderData, setUrgentReminderData] = useState({ method: 'RECOMMANDE', deadline: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), message: '', penaltyRate: 0 }); const [legalNoticeData, setLegalNoticeData] = useState({ type: 'MISE_EN_DEMEURE', deadline: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000), lawyer: '', content: '' }); const toast = useRef(null); const dt = useRef>(null); const reminderMethods = [ { label: 'Courrier recommandé', value: 'RECOMMANDE' }, { label: 'Huissier', value: 'HUISSIER' }, { label: 'Avocat', value: 'AVOCAT' }, { label: 'Email urgent', value: 'EMAIL_URGENT' }, { label: 'Téléphone + Courrier', value: 'TELEPHONE_COURRIER' } ]; const legalNoticeTypes = [ { label: 'Mise en demeure', value: 'MISE_EN_DEMEURE' }, { label: 'Commandement de payer', value: 'COMMANDEMENT' }, { label: 'Assignation en référé', value: 'REFERE' }, { label: 'Procédure simplifiée', value: 'PROCEDURE_SIMPLIFIEE' } ]; useEffect(() => { loadFactures(); }, []); const loadFactures = async () => { try { setLoading(true); const data = await factureService.getAll(); // Filtrer les factures en retard (échéance dépassée + statut ECHUE) const facturesEnRetard = data.filter(facture => { const today = new Date(); const echeanceDate = new Date(facture.dateEcheance); return (facture.statut === StatutFacture.ECHUE || (echeanceDate < today && facture.statut !== StatutFacture.PAYEE && !facture.datePaiement)); }); setFactures(facturesEnRetard); } catch (error) { console.error('Erreur lors du chargement des factures:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les factures en retard', life: 3000 }); } finally { setLoading(false); } }; const getDaysOverdue = (dateEcheance: string | Date) => { const today = new Date(); const dueDate = new Date(dateEcheance); const diffTime = today.getTime() - dueDate.getTime(); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return Math.max(0, diffDays); }; const getOverdueCategory = (dateEcheance: string | Date) => { const days = getDaysOverdue(dateEcheance); if (days <= 15) return { category: 'RETARD RÉCENT', color: 'orange', severity: 'warning' as const, priority: 'MOYENNE' }; if (days <= 45) return { category: 'RETARD IMPORTANT', color: 'red', severity: 'danger' as const, priority: 'ÉLEVÉE' }; return { category: 'RETARD CRITIQUE', color: 'darkred', severity: 'danger' as const, priority: 'URGENTE' }; }; const calculateInterestPenalty = (amount: number, days: number, rate: number = 10) => { // Calcul des pénalités de retard (taux annuel) const dailyRate = rate / 365 / 100; return amount * dailyRate * days; }; const sendUrgentReminder = (facture: Facture) => { setSelectedFacture(facture); setActionType('urgent_reminder'); const days = getDaysOverdue(facture.dateEcheance); setUrgentReminderData({ method: 'RECOMMANDE', deadline: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), message: `RELANCE URGENTE\n\nMadame, Monsieur,\n\nMalgré nos précédents rappels, nous constatons que la facture ${facture.numero} d'un montant de ${formatCurrency(facture.montantTTC || 0)} n'a toujours pas été réglée.\n\nCette facture est en retard de ${days} jours depuis son échéance du ${formatDate(facture.dateEcheance)}.\n\nEn l'absence de règlement sous 8 jours, nous nous verrons contraints d'engager une procédure de recouvrement contentieux.\n\nPénalités de retard applicables: ${formatCurrency(calculateInterestPenalty(facture.montantTTC || 0, days))}\n\nNous vous demandons de bien vouloir régulariser cette situation dans les plus brefs délais.`, penaltyRate: 10 }); setActionDialog(true); }; const issueLegalNotice = (facture: Facture) => { setSelectedFacture(facture); setActionType('legal_notice'); setLegalNoticeData({ type: 'MISE_EN_DEMEURE', deadline: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000), lawyer: '', content: `MISE EN DEMEURE DE PAYER\n\nPar la présente, nous vous mettons en demeure de procéder au règlement de la facture ${facture.numero} d'un montant de ${formatCurrency(facture.montantTTC || 0)}, échue depuis le ${formatDate(facture.dateEcheance)}.\n\nVous disposez d'un délai de 15 jours à compter de la réception de cette mise en demeure pour procéder au règlement.\n\nÀ défaut, nous nous réservons le droit d'engager contre vous une procédure judiciaire en recouvrement de créances, sans autre préavis.` }); setActionDialog(true); }; const suspendClient = (facture: Facture) => { setSelectedFacture(facture); setActionType('suspend_client'); setActionDialog(true); }; const handleAction = async () => { if (!selectedFacture) return; try { let message = ''; switch (actionType) { case 'urgent_reminder': await factureActionsService.sendUrgentRelance( selectedFacture.id, urgentReminderData.message ); message = 'Relance urgente envoyée avec succès'; break; case 'legal_notice': const delaiJours = Math.ceil((legalNoticeData.deadline.getTime() - Date.now()) / (1000 * 60 * 60 * 24)); await factureActionsService.sendMiseEnDemeure({ factureId: selectedFacture.id, delaiPaiement: delaiJours, mentionsLegales: legalNoticeData.content, fraisDossier: 0 }); message = 'Mise en demeure émise avec succès'; break; case 'suspend_client': await factureActionsService.suspendClient({ clientId: selectedFacture.client.id, motif: 'Factures impayées en retard', temporaire: false }); message = 'Client suspendu pour impayés'; break; } setActionDialog(false); toast.current?.show({ severity: 'success', summary: 'Succès', detail: `${message} (simulation)`, life: 3000 }); } catch (error) { console.error('Erreur lors de l\'action:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible d\'effectuer l\'action', life: 3000 }); } }; const exportCSV = () => { dt.current?.exportCSV(); }; const bulkLegalAction = async () => { if (selectedFactures.length === 0) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez sélectionner au moins une facture', life: 3000 }); return; } // Simulation de procédure contentieuse en lot console.log('Procédure contentieuse en lot pour', selectedFactures.length, 'factures'); setSelectedFactures([]); toast.current?.show({ severity: 'success', summary: 'Succès', detail: `Procédure contentieuse engagée pour ${selectedFactures.length} facture(s) (simulation)`, life: 3000 }); }; const generateOverdueAnalysis = () => { const totalOverdue = factures.reduce((sum, f) => sum + (f.montantTTC || 0), 0); const totalPenalties = factures.reduce((sum, f) => sum + calculateInterestPenalty(f.montantTTC || 0, getDaysOverdue(f.dateEcheance)), 0); const criticalOverdue = factures.filter(f => getDaysOverdue(f.dateEcheance) > 45); const recentOverdue = factures.filter(f => getDaysOverdue(f.dateEcheance) <= 15); const report = ` === ANALYSE FACTURES EN RETARD === Date du rapport: ${new Date().toLocaleDateString('fr-FR')} SITUATION CRITIQUE: - Nombre total de factures en retard: ${factures.length} - Montant total en retard: ${formatCurrency(totalOverdue)} - Pénalités de retard calculées: ${formatCurrency(totalPenalties)} - Impact sur la trésorerie: ${formatCurrency(totalOverdue + totalPenalties)} RÉPARTITION PAR GRAVITÉ: - Retard récent (≤15 jours): ${recentOverdue.length} factures, ${formatCurrency(recentOverdue.reduce((sum, f) => sum + (f.montantTTC || 0), 0))} - Retard critique (>45 jours): ${criticalOverdue.length} factures, ${formatCurrency(criticalOverdue.reduce((sum, f) => sum + (f.montantTTC || 0), 0))} FACTURES PRIORITAIRES (>45 jours): ${criticalOverdue.map(f => { const days = getDaysOverdue(f.dateEcheance); const penalty = calculateInterestPenalty(f.montantTTC || 0, days); return ` - ${f.numero} - ${f.objet} Client: ${f.client ? `${f.client.prenom} ${f.client.nom}` : 'N/A'} Montant: ${formatCurrency(f.montantTTC || 0)} Retard: ${days} jours Pénalités: ${formatCurrency(penalty)} Total dû: ${formatCurrency((f.montantTTC || 0) + penalty)} `; }).join('')} CLIENTS À RISQUE: ${getHighRiskClients()} ACTIONS RECOMMANDÉES: - Mise en demeure immédiate pour les retards >45 jours - Suspension des prestations pour les clients récidivistes - Engagement de procédures contentieuses si nécessaire - Révision des conditions de paiement accordées - Application systématique des pénalités de retard `; const blob = new Blob([report], { type: 'text/plain;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `analyse_retards_critiques_${new Date().toISOString().split('T')[0]}.txt`; link.click(); toast.current?.show({ severity: 'success', summary: 'Analyse générée', detail: 'Le rapport d\'analyse a été téléchargé', life: 3000 }); }; const getHighRiskClients = () => { const clientStats = {}; factures.forEach(f => { if (f.client) { const clientKey = `${f.client.prenom} ${f.client.nom}`; if (!clientStats[clientKey]) { clientStats[clientKey] = { count: 0, value: 0, maxDays: 0, totalPenalties: 0 }; } const days = getDaysOverdue(f.dateEcheance); clientStats[clientKey].count++; clientStats[clientKey].value += f.montantTTC || 0; clientStats[clientKey].maxDays = Math.max(clientStats[clientKey].maxDays, days); clientStats[clientKey].totalPenalties += calculateInterestPenalty(f.montantTTC || 0, days); } }); return Object.entries(clientStats) .sort((a: [string, any], b: [string, any]) => b[1].value - a[1].value) .slice(0, 5) .map(([client, data]: [string, any]) => `- ${client}: ${data.count} factures, ${formatCurrency(data.value)}, retard max: ${data.maxDays} jours, pénalités: ${formatCurrency(data.totalPenalties)}`) .join('\n'); }; const leftToolbarTemplate = () => { return (
Factures en retard ({factures.length})
sum + (f.montantTTC || 0), 0))}`} className="bg-red-100 text-red-800" /> sum + calculateInterestPenalty(f.montantTTC || 0, getDaysOverdue(f.dateEcheance)), 0))}`} className="bg-red-200 text-red-900" />
); }; const rightToolbarTemplate = () => { return (