diff --git a/web/modules/custom/figli_compta_ledger/js/dashboard.js b/web/modules/custom/figli_compta_ledger/js/dashboard.js index 78c2bfe..5dffe89 100644 --- a/web/modules/custom/figli_compta_ledger/js/dashboard.js +++ b/web/modules/custom/figli_compta_ledger/js/dashboard.js @@ -176,6 +176,27 @@ return { annee, items }; }).filter((y) => y.items.length); }, + // Charges structurelles by client (i.e. by vendor: loyer, + // assurance, hébergement, URSSAF...) -- sorted by value descending + // server-side already (no alphabetical option asked for here, + // unlike the versements-par-compte chart), no top-N cap needed + // (see DashboardStatsController::stats()'s own comment -- only a + // handful of distinct vendors ever show up on a "charge" line). + // `type: 'charge'` on every item is just so colorFor="typeColor" + // (same method the type-breakdown chart uses) paints every bar the + // same "charge" grey -- every row here is that type by definition. + chargeParClientItems() { + if (!this.stats) return []; + return this.stats.charge_par_client.map((c) => ({ label: c.client, value: c.total, type: 'charge' })); + }, + chargeParClientParAnnee() { + if (!this.stats) return []; + return this.stats.annees.map((annee) => { + const items = (this.stats.charge_par_client_par_annee[annee] || []) + .map((c) => ({ label: c.client, value: c.total, type: 'charge' })); + return { annee, items }; + }).filter((y) => y.items.length); + }, // total_par_type_par_compte is already in the stats response -- // it's the same répartition-level query /dashboard/compte uses for // its own (single-compte) type breakdown, just never sliced down diff --git a/web/modules/custom/figli_compta_ledger/src/Controller/DashboardStatsController.php b/web/modules/custom/figli_compta_ledger/src/Controller/DashboardStatsController.php index 65dd75b..575a472 100644 --- a/web/modules/custom/figli_compta_ledger/src/Controller/DashboardStatsController.php +++ b/web/modules/custom/figli_compta_ledger/src/Controller/DashboardStatsController.php @@ -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( diff --git a/web/modules/custom/figli_compta_ledger/templates/figli-compta-dashboard.html.twig b/web/modules/custom/figli_compta_ledger/templates/figli-compta-dashboard.html.twig index 7280cea..e73a875 100644 --- a/web/modules/custom/figli_compta_ledger/templates/figli-compta-dashboard.html.twig +++ b/web/modules/custom/figli_compta_ledger/templates/figli-compta-dashboard.html.twig @@ -60,6 +60,18 @@ + + Charges structurelles par client + Montant total (HT, valeur absolue) des charges structurelles par vendeur/organisme, toutes années confondues. + + + + {{ y.annee }} + + + + + Total des versements par compte Montant total versé à chaque compte associé, plus les salaires/stages et sous-traitants, toutes années confondues.
Montant total (HT, valeur absolue) des charges structurelles par vendeur/organisme, toutes années confondues.
Montant total versé à chaque compte associé, plus les salaires/stages et sous-traitants, toutes années confondues.