Ajoute "Charges structurelles par client" (+ par année) sur le dashboard SAS

Réutilise le node-level query déjà exécuté pour caParClient/totalParType
(aucune requête SQL supplémentaire) -- accumule abs(montant_ht) des
lignes type=charge par client (le vendeur/organisme : loyer, assurance,
URSSAF...). Trié par montant décroissant (pas alphabétique), pas de
plafond top-N vu le petit nombre de vendeurs distincts. Coloré en gris
"charge", même couleur que ce type dans "Répartition de l'activité par
type".
This commit is contained in:
2026-09-08 15:30:31 +02:00
parent 96bd9502d2
commit 74d8eb2aa2
3 changed files with 71 additions and 0 deletions
@@ -85,6 +85,8 @@ class DashboardStatsController extends ControllerBase {
$totalParTypeParAnnee = [];
$caParClient = [];
$caParClientParAnnee = [];
$chargeParClient = [];
$chargeParClientParAnnee = [];
$annees = [];
foreach ($nodeRows as $row) {
$montant = $row->montant_ht !== NULL ? (float) $row->montant_ht : 0.0;
@@ -99,6 +101,16 @@ class DashboardStatsController extends ControllerBase {
$client = $row->client ?: '(sans client)';
$caParClient[$client] = ($caParClient[$client] ?? 0) + $montant;
}
// Same "who does this money go to" breakdown as $caParClient above,
// but for charges structurelles instead of entrées -- field_client
// on a charge line is the vendor/organisme (loyer, assurance,
// URSSAF...), not a paying client, but it's the same field/same
// taxonomy, so the same grouping applies. Absolute value, same
// convention as $totalParType.
if ($row->type === 'charge') {
$client = $row->client ?: '(sans client)';
$chargeParClient[$client] = ($chargeParClient[$client] ?? 0) + abs($montant);
}
if (!$this->isAnneeValide($row->annee)) {
continue;
@@ -112,6 +124,11 @@ class DashboardStatsController extends ControllerBase {
$caParClientParAnnee[$row->annee][$client] =
($caParClientParAnnee[$row->annee][$client] ?? 0) + $montant;
}
if ($row->type === 'charge') {
$client = $row->client ?: '(sans client)';
$chargeParClientParAnnee[$row->annee][$client] =
($chargeParClientParAnnee[$row->annee][$client] ?? 0) + abs($montant);
}
// Per-année équivalent of $totalParType above -- backs the
// "Répartition de l'activité par type" small multiples.
if ($row->type !== 'ouverture') {
@@ -148,6 +165,24 @@ class DashboardStatsController extends ControllerBase {
}
}
// No top-N cap here (unlike top clients above) -- structural charges
// only ever go to a handful of recurring vendors (loyer, assurance,
// hébergement, URSSAF...), not the 100+ distinct clients entrées can
// have, so the full list is already short.
arsort($chargeParClient);
$chargeParClientList = [];
foreach ($chargeParClient as $client => $total) {
$chargeParClientList[] = ['client' => $client, 'total' => round($total, 2)];
}
$chargeParClientParAnneeList = [];
foreach ($chargeParClientParAnnee as $annee => $parClient) {
arsort($parClient);
$chargeParClientParAnneeList[$annee] = [];
foreach ($parClient as $client => $total) {
$chargeParClientParAnneeList[$annee][] = ['client' => $client, 'total' => round($total, 2)];
}
}
// --- Aggregate the répartition-level rows in PHP. ---
$soldeParCompte = [];
$soldeParCompteParAnnee = [];
@@ -204,6 +239,7 @@ class DashboardStatsController extends ControllerBase {
ksort($totalParTypeParCompteParAnnee);
ksort($totalParTypeParAnnee);
ksort($topClientsParAnnee);
ksort($chargeParClientParAnneeList);
return new JsonResponse([
'annees' => $anneesList,
@@ -215,6 +251,8 @@ class DashboardStatsController extends ControllerBase {
),
'top_clients' => $topClients,
'top_clients_par_annee' => $topClientsParAnnee,
'charge_par_client' => $chargeParClientList,
'charge_par_client_par_annee' => $chargeParClientParAnneeList,
'solde_par_compte' => array_map(fn ($v) => round($v, 2), $soldeParCompte),
'solde_par_compte_par_annee' => $soldeParCompteParAnnee,
'total_par_type_par_compte' => array_map(