test bug in facture
This commit is contained in:
parent
78e75ce41c
commit
a2421c86ee
1 changed files with 100 additions and 59 deletions
|
|
@ -79,7 +79,7 @@ export type StatusCode = keyof typeof FACTURE_STATUS_LABELS;
|
||||||
// TYPES
|
// TYPES
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
type FilterType = 'all' | 'paid' | 'pending' | 'validated';
|
type FilterType = 'all' | 'paid' | 'pending' | 'validated' | 'partial';
|
||||||
|
|
||||||
interface FactureWithReglement extends Facture {
|
interface FactureWithReglement extends Facture {
|
||||||
statut_reglement: string;
|
statut_reglement: string;
|
||||||
|
|
@ -143,63 +143,100 @@ const KPI_CONFIG: KPIConfig[] = [
|
||||||
filter: (factures) => factures,
|
filter: (factures) => factures,
|
||||||
tooltip: { content: "Total des factures sur la période.", source: "Ventes > 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',
|
id: 'paid',
|
||||||
title: 'Montant Payé',
|
title: 'Payés',
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
color: 'green',
|
color: 'green',
|
||||||
getValue: (factures) => {
|
getValue: (factures) => {
|
||||||
const paid = factures.filter(f => f.statut === 4);
|
const soldees = factures.filter(f => (f as any).statut_display === 6);
|
||||||
return Math.round(paid.reduce((sum, item) => sum + item.total_ht, 0));
|
return Math.round(soldees.reduce((sum, item) => sum + item.total_ttc, 0));
|
||||||
},
|
},
|
||||||
getSubtitle: (factures) => {
|
getSubtitle: (factures) => {
|
||||||
const paid = factures.filter(f => f.statut === 4);
|
const soldees = factures.filter(f => (f as any).statut_display === 6);
|
||||||
return `${paid.length} facture${paid.length > 1 ? 's' : ''}`;
|
return `${soldees.length} facture${soldees.length > 1 ? 's' : ''}`;
|
||||||
},
|
},
|
||||||
getChange: (factures) => {
|
getChange: (factures, value, period, allFactures) => {
|
||||||
const paid = factures.filter(f => f.statut === 4);
|
const soldees = factures.filter(f => (f as any).statut_display === 6);
|
||||||
return `${paid.length}/${factures.length}`;
|
const totalFacture = factures.length;
|
||||||
|
return totalFacture > 0 ? `${soldees.length}/${totalFacture}` : '0';
|
||||||
},
|
},
|
||||||
getTrend: (factures, value, period, allFactures) => {
|
getTrend: (factures, value, period, allFactures) => {
|
||||||
const previousPeriodFactures = getPreviousPeriodItems(allFactures, period);
|
const previousPeriodFactures = getPreviousPeriodItems(allFactures, period);
|
||||||
const previousPaid = previousPeriodFactures.filter(f => f.statut === 4);
|
const previousSoldees = previousPeriodFactures.filter(f => (f as any).statut_display === 6);
|
||||||
const previousPaidAmount = previousPaid.reduce((sum, item) => sum + item.total_ht, 0);
|
const previousTotal = previousSoldees.reduce((sum, item) => sum + item.total_ttc, 0);
|
||||||
return Number(value) >= previousPaidAmount ? 'up' : 'down';
|
return Number(value) >= previousTotal ? 'up' : 'down';
|
||||||
},
|
},
|
||||||
filter: (factures) => factures.filter(f => f.statut === 4),
|
filter: (factures) => factures.filter(f => (f as any).statut_display === 6),
|
||||||
tooltip: { content: "Montant des factures payées sur la période.", source: "Ventes > Factures" }
|
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',
|
id: 'validated',
|
||||||
title: 'Factures Validées',
|
title: 'Validées',
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
color: 'purple',
|
color: 'purple',
|
||||||
getValue: (factures) => factures.filter(f => f.valide === 1).length,
|
getValue: (factures) => factures.filter(f => f.valide === 1).length,
|
||||||
getSubtitle: (factures) => {
|
getSubtitle: (factures) => {
|
||||||
const pending = factures.filter(f => f.statut === 1);
|
const nonValidees = factures.filter(f => f.valide !== 1);
|
||||||
return `${pending.length} en attente`;
|
return `${nonValidees.length} non validée${nonValidees.length > 1 ? 's' : ''}`;
|
||||||
},
|
},
|
||||||
getChange: (factures) => {
|
getChange: (factures) => {
|
||||||
const validated = factures.filter(f => f.valide === 1);
|
const validated = factures.filter(f => f.valide === 1);
|
||||||
|
|
@ -211,6 +248,7 @@ const KPI_CONFIG: KPIConfig[] = [
|
||||||
return Number(value) >= previousValidated.length ? 'up' : 'down';
|
return Number(value) >= previousValidated.length ? 'up' : 'down';
|
||||||
},
|
},
|
||||||
filter: (factures) => factures.filter(f => f.valide === 1),
|
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]);
|
}, [factures, reglementsMap]);
|
||||||
|
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Client actuellement sélectionné
|
// Client actuellement sélectionné
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
@ -376,25 +415,26 @@ const InvoicesPage = () => {
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
const kpis = useMemo(() => {
|
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 => {
|
return KPI_CONFIG.map(config => {
|
||||||
const value = config.getValue(periodFilteredFactures);
|
const value = config.getValue(periodFilteredFactures);
|
||||||
return {
|
return {
|
||||||
id: config.id,
|
id: config.id,
|
||||||
title: config.title,
|
title: config.title,
|
||||||
value: config.id === 'all' || config.id === 'paid' ? `${value.toLocaleString('fr-FR')}€` : value,
|
value: config.id === 'all' || config.id === 'paid' || config.id === 'partial' ? `${value.toLocaleString('fr-FR')}€` : value,
|
||||||
change: config.getChange(periodFilteredFactures, value, period, factures),
|
change: config.getChange(periodFilteredFactures, value, period, facturesWithReglement), // aussi ici
|
||||||
trend: config.getTrend(periodFilteredFactures, value, period, factures),
|
trend: config.getTrend(periodFilteredFactures, value, period, facturesWithReglement), // et ici
|
||||||
icon: config.icon,
|
icon: config.icon,
|
||||||
subtitle: config.getSubtitle(periodFilteredFactures, value),
|
subtitle: config.getSubtitle(periodFilteredFactures, value),
|
||||||
color: config.color,
|
color: config.color,
|
||||||
tooltip: config.tooltip,
|
tooltip: config.tooltip,
|
||||||
isActive: activeFilter === config.id,
|
isActive: activeFilter === config.id,
|
||||||
onClick: () => setActiveFilter(prev => (prev === config.id ? 'all' : config.id)),
|
onClick: () => setActiveFilter(prev => (prev === config.id ? 'all' : config.id)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [factures, period, activeFilter]);
|
}, [facturesWithReglement, period, activeFilter]);
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Filtrage combiné : Période + KPI + Filtres avancés
|
// Filtrage combiné : Période + KPI + Filtres avancés
|
||||||
|
|
@ -403,13 +443,13 @@ const InvoicesPage = () => {
|
||||||
const filteredFactures = useMemo(() => {
|
const filteredFactures = useMemo(() => {
|
||||||
// 1. Filtrer par période
|
// 1. Filtrer par période
|
||||||
let result: FactureWithReglement[] = filterItemByPeriod(facturesWithReglement, period, 'date');
|
let result: FactureWithReglement[] = filterItemByPeriod(facturesWithReglement, period, 'date');
|
||||||
|
|
||||||
// 2. Filtrer par KPI actif
|
// 2. Filtrer par KPI actif
|
||||||
const kpiConfig = KPI_CONFIG.find(k => k.id === activeFilter);
|
const kpiConfig = KPI_CONFIG.find(k => k.id === activeFilter);
|
||||||
if (kpiConfig && activeFilter !== 'all') {
|
if (kpiConfig && activeFilter !== 'all') {
|
||||||
result = kpiConfig.filter(result) as FactureWithReglement[];
|
result = kpiConfig.filter(result) as FactureWithReglement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Filtres avancés (statut)
|
// 3. Filtres avancés (statut)
|
||||||
if (activeFilters.status && activeFilters.status.length > 0) {
|
if (activeFilters.status && activeFilters.status.length > 0) {
|
||||||
result = result.filter(item =>
|
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());
|
return [...result].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
}, [facturesWithReglement, period, activeFilter, activeFilters, clientCommercialMap]);
|
}, [facturesWithReglement, period, activeFilter, activeFilters, clientCommercialMap]);
|
||||||
|
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Factures sélectionnables
|
// Factures sélectionnables
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue