KPIs Dashboard â Comment ils sont calculĂ©s
Cette page documente prĂ©cisĂ©ment comment chaque KPI du dashboard lettrage est calculĂ© cĂŽtĂ© API, et explique pourquoi les chiffres peuvent diffĂ©rer de ce qu'on voit dans la page traitement avec les mĂȘmes filtres.
đïž Architecture des endpoints
Le dashboard lettrage appelle deux endpoints distincts selon la situation. Chaque endpoint a ses propres rĂšgles de filtrage â ce n'est pas le mĂȘme code que la page traitement.
getKpis()getDashboardStats()getLignes(), avec pagination et filtres complets. Applique en plus
un filtre par dĂ©faut statutLettrage NOT IN ['exclue'] absent du dashboard.đ§ Filtres construits par endpoint
Page traitement â getLignes()
// Filtre par défaut TOUJOURS appliqué cÎté page traitement :
where.statutLettrage = { notIn: ['exclue'] }
// Filtres optionnels (si passés) :
if (entite) â rĂ©solution via LettrageBank â banque: { in: [...] }
if (sens) â sens: params.sens
if (anneeFiltre) â dateOperation: { gte: 2024-01-01, lt: 2025-01-01 }
if (dateDebut/dateFin) â dateOperation: { gte, lte }
// + filtre 'traite', 'nonLettrable', etc.
getDashboardStats() â Dashboard avec filtres
// Pas de filtre statutLettrage par défaut !
// baseWhere est construit ainsi :
const baseWhere: any = {};
if (banqueFilter) baseWhere.banque = banqueFilter; // si entite != LES_DEUX
if (params.sens) baseWhere.sens = params.sens;
if (params.annee) baseWhere.dateOperation = { gte, lt };
// baseWhere inclut donc : exclue, archivee, non_lettre, PARTIAL, traite
const nonLettrWhere = { ...baseWhere, statutLettrage: 'non_lettre', traite: false };
const partielWhere = { ...baseWhere, statutLettrage: 'PARTIAL', traite: false };
const traiteWhere = { ...baseWhere, statutLettrage: 'traite' };
getKpis() â Dashboard chargement initial (global)
// Aucun filtre d'entité / sens / année
// Toute la base, sans restriction de statutLettrage sur les requĂȘtes globales
count({ where: { statutLettrage: 'non_lettre' } }) // PAS de traite:false
count({ where: { statutLettrage: 'PARTIAL' } })
count({ where: { nonLettrable: true, statutLettrage: { in: ['non_lettre', 'PARTIAL'] } } })
count({ where: { reouvert: true } })
count({ where: { premiereVueAt: { gte: dernierImport.finishedAt } } }) // nouvelles
count({ where: { derniereVueAt: { gte: dernierFinishedAt }, premiereVueAt: { lt: dernierFinishedAt } } }) // MAJ
⥠getKpis() â Les KPIs de la barre du haut
| KPI affiché | Champ retourné | Condition Prisma | Scope |
|---|---|---|---|
| Non lettrées | nonLettrées |
statutLettrage = 'non_lettre' |
Global (tout statut traite) |
| Partielles | partielles |
statutLettrage = 'PARTIAL' |
Global |
| Non lettrables | nonLettrables |
nonLettrable = true AND statutLettrage IN ('non_lettre','PARTIAL') |
Global |
| Réouvertes | reouvertes |
reouvert = true |
Global |
| Nouvelles | nouvelles |
premiereVueAt >= dernierImport.finishedAt |
Global |
| Mises Ă jour | misesAJour |
derniereVueAt >= dernierImport.finishedAt AND premiereVueAt < dernierImport.finishedAt |
Global |
nonLettrées dans getKpis ne filtre PAS sur traite: false.
Il compte donc toutes les lignes avec statutLettrage = 'non_lettre', y compris celles qui ont été marquées traitées
(ce qui ne devrait plus arriver depuis le bugfix T8, mais des données historiques peuvent exister).
đ getDashboardStats() â Le dashboard filtrĂ©
entite, sens, annee.
Construction du baseWhere
entite = 'SA' ou 'PRO' (pas LES_DEUX) : requĂȘte sur LettrageBank
pour trouver les noms de banques de cette entité. Le filtre banque: { in: [...] } est appliqué.sens fourni : where.sens = 'DEBIT' ou 'CREDIT'.annee fourni : dateOperation: { gte: 2024-01-01, lt: 2025-01-01 }.baseWhere n'exclut pas les lignes exclue
ni les archivee. Tous les statuts sont inclus dans les comptages.KPIs calculés
| KPI | Where clause | Note |
|---|---|---|
| total | baseWhere (sans statut) |
Inclut exclue + archivee |
| montantTotal / restantTotal | baseWhere |
Inclut exclue + archivee |
| nonLettrées | baseWhere + statutLettrage='non_lettre' + traite=false |
Correct (traite:false présent) |
| partielles | baseWhere + statutLettrage='PARTIAL' + traite=false |
Correct |
| lettrees | baseWhere + statutLettrage='traite' |
Correct |
| sansContrat | baseWhere + (contrat=null OR contrat='') |
Inclut exclue dans le décompte |
| sansFacture | baseWhere + (facture=null OR facture='') |
Inclut exclue dans le décompte |
| restantNegatif | baseWhere + restant < 0 |
Inclut exclue |
| traitéesAujourdhui | traiteWhere + traiteAt >= aujourd'hui 00:00 |
Correct |
| nouvelles | baseWhere + premiereVueAt >= dernierImport.finishedAt |
Inclut les lignes exclues nouvelles |
| doublons | dernierImport.nbDoublons |
Stocké sur l'import, pas filtré |
Par banque (agrégation JS)
// Chargement de TOUTES les lignes du baseWhere en mémoire :
parBanque = findMany({ where: baseWhere, select: { banque, statutLettrage, traite, montant, restant } })
// Agrégation en JS (pas en SQL) :
for (const l of parBanque) {
entry.total++
if (l.traite) entry.lettrees++ // booléen traite
else if (l.statutLettrage === 'non_lettre') entry.nonLettrées++
else if (l.statutLettrage === 'PARTIAL') entry.partielles++
// exclue / archivee : comptés dans total mais pas dans les 3 catégories
}
traite (champ SQL) et non statutLettrage='traite'.
Ces deux colonnes devraient ĂȘtre synchronisĂ©es depuis les bugfixes, mais les donnĂ©es historiques peuvent avoir des incohĂ©rences.
đ getDashboard() â Dashboard complet (non utilisĂ© actuellement)
getDashboard() existe dans le service mais n'est pas appelĂ©e directement par le frontend â c'est getDashboardStats()
qui est utilisée. Elle contient une logique similaire mais distincte avec un scBaseWhere supplémentaire.
Différence clé : baseWhere vs scBaseWhere
baseWhere: pas de filtre statut (inclut tout)scBaseWhere: exclutarchiveeETexclueâ utilisĂ© poursansContrat- Mais
sansFactureutilisebaseWhere(passcBaseWhere) â incohĂ©rence interne
đš Divergence 1 â Lignes exclues comptĂ©es dans le dashboard
CRITIQUE
La page traitement exclut systématiquement les lignes avec statutLettrage = 'exclue'.
Le dashboard (getDashboardStats) n'a aucun filtre par dĂ©faut sur le statut â il compte tout, y compris les exclues.
Impact concret
| KPI affecté | Impact |
|---|---|
total |
Sur-compté : inclut les lignes exclues et archivées |
montantTotal |
Montant gonflé par les lignes exclues |
sansContrat, sansFacture |
Inclut les lignes exclues sans contrat/facture |
nonLettrées, partielles |
Non affectés (filtrent sur statutLettrage spécifique) |
â ïž Divergence 2 â Non lettrĂ©es : traite:false manquant dans getKpis
IMPORTANT
getDashboardStats et la page traitement sont cohérents sur les non lettrées
(tous deux utilisent traite: false implicitement via le statut).
Mais getKpis() (barre du haut, affichée au chargement) ne filtre PAS sur traite: false.
statutLettrage='non_lettre' devrait avoir traite=false.
Mais des données historiques incohérentes peuvent fausser getKpis. Les deux KPIs ont une sémantique différente,
et leur diffĂ©rence peut ĂȘtre source de confusion pour l'utilisateur.
đš Divergence 3 â Total lignes diffĂ©rent entre dashboard et traitement
CRITIQUELe "total" vu dans le dashboard et le total de lignes dans la page traitement ne comptent pas la mĂȘme chose, mĂȘme avec les mĂȘmes filtres entitĂ©/sens/annĂ©e.
| Source | Ce qui est compté | Statuts inclus |
|---|---|---|
Dashboard â total |
Toutes les lignes du baseWhere | non_lettre + PARTIAL + traite + exclue + archivee |
Page traitement â totalCount |
Lignes avec statut non exclu | non_lettre + PARTIAL + traite (pas exclue) |
Frontend dashboard â totalCount |
Calculé en JS : nonLettrées + partielles |
Seulement les actives non traitées |
nonLettrĂ©es + partielles â c'est donc
encore différent du champ total retourné par l'API, et différent du total de la page traitement.
Trois "totaux" diffĂ©rents pour la mĂȘme donnĂ©e.
â ïž Divergence 4 â Le frontend recalcule son propre total
IMPORTANT
Dans le composant frontend du dashboard, le totalCount affiché n'est pas
le champ total de l'API â c'est une addition en JavaScript.
// Frontend â calcul JS du total affichĂ© dans le dashboard :
const totalCount = (stats?.nonLettrées ?? 0) + (stats?.partielles ?? 0)
// Ce totalCount ne comprend PAS :
// - les lignes traitées (lettrees)
// - les lignes exclues
// - les lignes archivées
Conséquence
- Le "total actif" du dashboard =
nonLettrées + partielles(lignes à traiter) - La page traitement affiche le nombre de lignes dans la liste paginée (excluant
exclue) - Ces deux valeurs sont fondamentalement différentes par design
- Mais l'utilisateur peut les comparer et s'attendre à ce qu'elles soient égales
â ïž Divergence 5 â Scope des filtres entitĂ©/sens/annĂ©e
SECONDAIRELa page traitement propose des filtres supplĂ©mentaires (dateDebut/dateFin, traite, nonLettrable, contrat, factureâŠ) qui n'ont aucun Ă©quivalent dans le dashboard. Donc mĂȘme avec entitĂ©+sens+annĂ©e identiques, la page traitement peut afficher moins de lignes si d'autres filtres sont actifs.
| Filtre | Page traitement | Dashboard |
|---|---|---|
| EntitĂ© (SA/PRO) | â via LettrageBank | â via LettrageBank |
| Sens (DEBIT/CREDIT) | â | â |
| AnnĂ©e | â (anneeFiltre) | â (annee) |
| Date dĂ©but/fin | â | â absent |
| Traitement (traite=true/false) | â | â absent |
| Non lettrable | â | â absent |
| Contrat / Facture | â | â absent |
| Exclusion 'exclue' | â (par dĂ©faut) | â absent |
đ Tableau comparatif complet
Pour chaque KPI affichĂ©, d'oĂč vient la valeur et pourquoi elle peut diffĂ©rer de la page traitement.
| KPI | Endpoint | Filtre statut | Exclue incluse ? | Filtres scope | Page traitement ? |
|---|---|---|---|---|---|
| Non lettrées (barre haute) | getKpis |
non_lettre |
Non | Aucun | â (global) |
| Partielles (barre haute) | getKpis |
PARTIAL |
Non | Aucun | â (global) |
| Non lettrées (dashboard) | getDashboardStats |
non_lettre + traite:false |
Non | entite/sens/annee | â comparable |
| Partielles (dashboard) | getDashboardStats |
PARTIAL + traite:false |
Non | entite/sens/annee | â comparable |
| Total (dashboard) | getDashboardStats |
Aucun | OUI | entite/sens/annee | â (inclut exclue) |
| Lettrees | getDashboardStats |
traite |
Non | entite/sens/annee | â comparable |
| Sans contrat | getDashboardStats |
Aucun | OUI | entite/sens/annee | â lĂ©ger |
| Sans facture | getDashboardStats |
Aucun | OUI | entite/sens/annee | â lĂ©ger |
| Nouvelles | getDashboardStats |
Aucun (premiereVueAt) | OUI | entite/sens/annee | â lĂ©ger |
| Doublons | getDashboardStats |
nbDoublons sur import | Non filtré | Global (pas de scope) | Sans équivalent |
đĄ Recommandations
Fixes prioritaires
statutLettrage: { notIn: ['exclue', 'archivee'] } dans baseWhere de getDashboardStats().
Cela alignera le total et les KPIs secondaires avec la page traitement.
getKpis(), changer { statutLettrage: 'non_lettre' }
en { statutLettrage: 'non_lettre', traite: false }
pour aligner la sémantique avec getDashboardStats.
baseWhere par scBaseWhere
pour le calcul de sansFacture â aligner avec sansContrat qui utilise dĂ©jĂ scBaseWhere.
Ce qui est normal (par design)
- getKpis est global par design â c'est une vue rapide de l'Ă©tat de la base entiĂšre
- Le filtre "traite" de la page traitement n'a pas d'équivalent dans le dashboard (normal)
- dateDebut/dateFin n'existe pas dans getDashboardStats â seule l'annĂ©e est disponible
- Doublons est calculé par import, pas par scope de filtre