diff --git a/src/pages/sales/InvoicesPage.tsx b/src/pages/sales/InvoicesPage.tsx index 7b44715..17b06eb 100644 --- a/src/pages/sales/InvoicesPage.tsx +++ b/src/pages/sales/InvoicesPage.tsx @@ -79,7 +79,7 @@ export type StatusCode = keyof typeof FACTURE_STATUS_LABELS; // TYPES // ============================================ -type FilterType = 'all' | 'paid' | 'pending' | 'validated'; +type FilterType = 'all' | 'paid' | 'pending' | 'validated' | 'partial'; interface FactureWithReglement extends Facture { statut_reglement: string; @@ -143,63 +143,100 @@ const KPI_CONFIG: KPIConfig[] = [ filter: (factures) => factures, tooltip: { content: "Total des factures sur la période.", source: "Ventes > Factures" } }, - { - id: 'pending', - title: 'Nombre de Factures', - icon: FileText, - color: 'orange', - getValue: (factures) => factures.length, - getSubtitle: (factures) => { - const paid = factures.filter(f => f.statut === 4); - const paymentRate = factures.length > 0 ? ((paid.length / factures.length) * 100).toFixed(1) : '0'; - return `${paymentRate}% payées`; - }, - getChange: (factures, value, period, allFactures) => { - const previousPeriodFactures = getPreviousPeriodItems(allFactures, period); - const countChange = Number(value) - previousPeriodFactures.length; - return countChange !== 0 ? `${countChange > 0 ? '+' : ''}${countChange}` : '0'; - }, - getTrend: (factures, value, period, allFactures) => { - const previousPeriodFactures = getPreviousPeriodItems(allFactures, period); - return Number(value) >= previousPeriodFactures.length ? 'up' : 'down'; - }, - filter: (factures) => factures.filter(f => f.statut === 1), - }, { id: 'paid', - title: 'Montant Payé', + title: 'Payés', icon: CheckCircle, color: 'green', getValue: (factures) => { - const paid = factures.filter(f => f.statut === 4); - return Math.round(paid.reduce((sum, item) => sum + item.total_ht, 0)); + const soldees = factures.filter(f => (f as any).statut_display === 6); + return Math.round(soldees.reduce((sum, item) => sum + item.total_ttc, 0)); }, getSubtitle: (factures) => { - const paid = factures.filter(f => f.statut === 4); - return `${paid.length} facture${paid.length > 1 ? 's' : ''}`; + const soldees = factures.filter(f => (f as any).statut_display === 6); + return `${soldees.length} facture${soldees.length > 1 ? 's' : ''}`; }, - getChange: (factures) => { - const paid = factures.filter(f => f.statut === 4); - return `${paid.length}/${factures.length}`; + getChange: (factures, value, period, allFactures) => { + const soldees = factures.filter(f => (f as any).statut_display === 6); + const totalFacture = factures.length; + return totalFacture > 0 ? `${soldees.length}/${totalFacture}` : '0'; }, getTrend: (factures, value, period, allFactures) => { const previousPeriodFactures = getPreviousPeriodItems(allFactures, period); - const previousPaid = previousPeriodFactures.filter(f => f.statut === 4); - const previousPaidAmount = previousPaid.reduce((sum, item) => sum + item.total_ht, 0); - return Number(value) >= previousPaidAmount ? 'up' : 'down'; + const previousSoldees = previousPeriodFactures.filter(f => (f as any).statut_display === 6); + const previousTotal = previousSoldees.reduce((sum, item) => sum + item.total_ttc, 0); + return Number(value) >= previousTotal ? 'up' : 'down'; }, - filter: (factures) => factures.filter(f => f.statut === 4), - tooltip: { content: "Montant des factures payées sur la période.", source: "Ventes > Factures" } + filter: (factures) => factures.filter(f => (f as any).statut_display === 6), + tooltip: { content: "Montant des factures entièrement réglées.", source: "Ventes > Factures" } + }, + { + id: 'partial', + title: 'Partiellement réglées', + icon: AlertTriangle, + color: 'orange', + getValue: (factures) => { + const partielles = factures.filter(f => (f as any).statut_display === 7); + return Math.round(partielles.reduce((sum, item) => sum + ((item as any).reste_a_regler || item.total_ttc), 0)); + }, + getSubtitle: (factures) => { + const partielles = factures.filter(f => (f as any).statut_display === 7); + return `${partielles.length} facture${partielles.length > 1 ? 's' : ''}`; + }, + getChange: (factures, value, period, allFactures) => { + const partielles = factures.filter(f => (f as any).statut_display === 7); + const totalFacture = factures.length; + return totalFacture > 0 ? `${partielles.length}/${totalFacture}` : '0'; + }, + getTrend: (factures, value, period, allFactures) => { + const previousPeriodFactures = getPreviousPeriodItems(allFactures, period); + const previousPartielles = previousPeriodFactures.filter(f => (f as any).statut_display === 7); + const previousTotal = previousPartielles.reduce((sum, item) => sum + ((item as any).reste_a_regler || item.total_ttc), 0); + return Number(value) <= previousTotal ? 'up' : 'down'; + }, + filter: (factures) => factures.filter(f => (f as any).statut_display === 7), + tooltip: { content: "Montant restant à régler sur les factures partiellement payées.", source: "Ventes > Factures" } + }, + { + id: 'pending', + title: 'Non réglées', + icon: FileText, + color: 'red', + getValue: (factures) => { + // Factures ni soldées (6) ni partiellement payées (7) + const nonReglees = factures.filter(f => (f as any).statut_display !== 6 && (f as any).statut_display !== 7); + return nonReglees.length; + }, + getSubtitle: (factures) => { + const nonReglees = factures.filter(f => (f as any).statut_display !== 6 && (f as any).statut_display !== 7); + const totalRestant = nonReglees.reduce((sum, item) => sum + ((item as any).reste_a_regler || item.total_ttc), 0); + return `${totalRestant.toLocaleString('fr-FR')}€ à régler`; + }, + getChange: (factures, value, period, allFactures) => { + const previousPeriodFactures = getPreviousPeriodItems(allFactures, period); + const previousNonReglees = previousPeriodFactures.filter(f => (f as any).statut_display !== 6 && (f as any).statut_display !== 7); + return previousNonReglees.length > 0 + ? (((Number(value) - previousNonReglees.length) / previousNonReglees.length) * 100).toFixed(1) + : '0'; + }, + getTrend: (factures, value, period, allFactures) => { + const previousPeriodFactures = getPreviousPeriodItems(allFactures, period); + const previousNonReglees = previousPeriodFactures.filter(f => (f as any).statut_display !== 6 && (f as any).statut_display !== 7); + // Moins de non réglées = mieux + return Number(value) <= previousNonReglees.length ? 'up' : 'down'; + }, + filter: (factures) => factures.filter(f => (f as any).statut_display !== 6 && (f as any).statut_display !== 7), + tooltip: { content: "Nombre de factures non réglées.", source: "Ventes > Factures" } }, { id: 'validated', - title: 'Factures Validées', + title: 'Validées', icon: CheckCircle, color: 'purple', getValue: (factures) => factures.filter(f => f.valide === 1).length, getSubtitle: (factures) => { - const pending = factures.filter(f => f.statut === 1); - return `${pending.length} en attente`; + const nonValidees = factures.filter(f => f.valide !== 1); + return `${nonValidees.length} non validée${nonValidees.length > 1 ? 's' : ''}`; }, getChange: (factures) => { const validated = factures.filter(f => f.valide === 1); @@ -211,6 +248,7 @@ const KPI_CONFIG: KPIConfig[] = [ return Number(value) >= previousValidated.length ? 'up' : 'down'; }, filter: (factures) => factures.filter(f => f.valide === 1), + tooltip: { content: "Nombre de factures validées.", source: "Ventes > Factures" } }, ]; @@ -356,6 +394,7 @@ const InvoicesPage = () => { }); }, [factures, reglementsMap]); + // ============================================ // Client actuellement sélectionné // ============================================ @@ -376,25 +415,26 @@ const InvoicesPage = () => { // ============================================ const kpis = useMemo(() => { - const periodFilteredFactures = filterItemByPeriod(factures, period, 'date'); + // Utiliser facturesWithReglement au lieu de factures + const periodFilteredFactures = filterItemByPeriod(facturesWithReglement, period, 'date'); - return KPI_CONFIG.map(config => { - const value = config.getValue(periodFilteredFactures); - return { - id: config.id, - title: config.title, - value: config.id === 'all' || config.id === 'paid' ? `${value.toLocaleString('fr-FR')}€` : value, - change: config.getChange(periodFilteredFactures, value, period, factures), - trend: config.getTrend(periodFilteredFactures, value, period, factures), - icon: config.icon, - subtitle: config.getSubtitle(periodFilteredFactures, value), - color: config.color, - tooltip: config.tooltip, - isActive: activeFilter === config.id, - onClick: () => setActiveFilter(prev => (prev === config.id ? 'all' : config.id)), - }; - }); - }, [factures, period, activeFilter]); + return KPI_CONFIG.map(config => { + const value = config.getValue(periodFilteredFactures); + return { + id: config.id, + title: config.title, + value: config.id === 'all' || config.id === 'paid' || config.id === 'partial' ? `${value.toLocaleString('fr-FR')}€` : value, + change: config.getChange(periodFilteredFactures, value, period, facturesWithReglement), // aussi ici + trend: config.getTrend(periodFilteredFactures, value, period, facturesWithReglement), // et ici + icon: config.icon, + subtitle: config.getSubtitle(periodFilteredFactures, value), + color: config.color, + tooltip: config.tooltip, + isActive: activeFilter === config.id, + onClick: () => setActiveFilter(prev => (prev === config.id ? 'all' : config.id)), + }; + }); +}, [facturesWithReglement, period, activeFilter]); // ============================================ // Filtrage combiné : Période + KPI + Filtres avancés @@ -403,13 +443,13 @@ const InvoicesPage = () => { const filteredFactures = useMemo(() => { // 1. Filtrer par période let result: FactureWithReglement[] = filterItemByPeriod(facturesWithReglement, period, 'date'); - + // 2. Filtrer par KPI actif const kpiConfig = KPI_CONFIG.find(k => k.id === activeFilter); if (kpiConfig && activeFilter !== 'all') { result = kpiConfig.filter(result) as FactureWithReglement[]; } - + // 3. Filtres avancés (statut) if (activeFilters.status && activeFilters.status.length > 0) { result = result.filter(item => @@ -429,6 +469,7 @@ const InvoicesPage = () => { return [...result].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); }, [facturesWithReglement, period, activeFilter, activeFilters, clientCommercialMap]); + // ============================================ // Factures sélectionnables // ============================================