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:
@@ -176,6 +176,27 @@
|
|||||||
return { annee, items };
|
return { annee, items };
|
||||||
}).filter((y) => y.items.length);
|
}).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 --
|
// total_par_type_par_compte is already in the stats response --
|
||||||
// it's the same répartition-level query /dashboard/compte uses for
|
// it's the same répartition-level query /dashboard/compte uses for
|
||||||
// its own (single-compte) type breakdown, just never sliced down
|
// its own (single-compte) type breakdown, just never sliced down
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ class DashboardStatsController extends ControllerBase {
|
|||||||
$totalParTypeParAnnee = [];
|
$totalParTypeParAnnee = [];
|
||||||
$caParClient = [];
|
$caParClient = [];
|
||||||
$caParClientParAnnee = [];
|
$caParClientParAnnee = [];
|
||||||
|
$chargeParClient = [];
|
||||||
|
$chargeParClientParAnnee = [];
|
||||||
$annees = [];
|
$annees = [];
|
||||||
foreach ($nodeRows as $row) {
|
foreach ($nodeRows as $row) {
|
||||||
$montant = $row->montant_ht !== NULL ? (float) $row->montant_ht : 0.0;
|
$montant = $row->montant_ht !== NULL ? (float) $row->montant_ht : 0.0;
|
||||||
@@ -99,6 +101,16 @@ class DashboardStatsController extends ControllerBase {
|
|||||||
$client = $row->client ?: '(sans client)';
|
$client = $row->client ?: '(sans client)';
|
||||||
$caParClient[$client] = ($caParClient[$client] ?? 0) + $montant;
|
$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)) {
|
if (!$this->isAnneeValide($row->annee)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -112,6 +124,11 @@ class DashboardStatsController extends ControllerBase {
|
|||||||
$caParClientParAnnee[$row->annee][$client] =
|
$caParClientParAnnee[$row->annee][$client] =
|
||||||
($caParClientParAnnee[$row->annee][$client] ?? 0) + $montant;
|
($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
|
// Per-année équivalent of $totalParType above -- backs the
|
||||||
// "Répartition de l'activité par type" small multiples.
|
// "Répartition de l'activité par type" small multiples.
|
||||||
if ($row->type !== 'ouverture') {
|
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. ---
|
// --- Aggregate the répartition-level rows in PHP. ---
|
||||||
$soldeParCompte = [];
|
$soldeParCompte = [];
|
||||||
$soldeParCompteParAnnee = [];
|
$soldeParCompteParAnnee = [];
|
||||||
@@ -204,6 +239,7 @@ class DashboardStatsController extends ControllerBase {
|
|||||||
ksort($totalParTypeParCompteParAnnee);
|
ksort($totalParTypeParCompteParAnnee);
|
||||||
ksort($totalParTypeParAnnee);
|
ksort($totalParTypeParAnnee);
|
||||||
ksort($topClientsParAnnee);
|
ksort($topClientsParAnnee);
|
||||||
|
ksort($chargeParClientParAnneeList);
|
||||||
|
|
||||||
return new JsonResponse([
|
return new JsonResponse([
|
||||||
'annees' => $anneesList,
|
'annees' => $anneesList,
|
||||||
@@ -215,6 +251,8 @@ class DashboardStatsController extends ControllerBase {
|
|||||||
),
|
),
|
||||||
'top_clients' => $topClients,
|
'top_clients' => $topClients,
|
||||||
'top_clients_par_annee' => $topClientsParAnnee,
|
'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' => array_map(fn ($v) => round($v, 2), $soldeParCompte),
|
||||||
'solde_par_compte_par_annee' => $soldeParCompteParAnnee,
|
'solde_par_compte_par_annee' => $soldeParCompteParAnnee,
|
||||||
'total_par_type_par_compte' => array_map(
|
'total_par_type_par_compte' => array_map(
|
||||||
|
|||||||
@@ -60,6 +60,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="figli-chart-section">
|
||||||
|
<h2>Charges structurelles par client</h2>
|
||||||
|
<p class="figli-note">Montant total (HT, valeur absolue) des charges structurelles par vendeur/organisme, toutes années confondues.</p>
|
||||||
|
<h-bar-chart :items="chargeParClientItems" :format-value="formatEurRound" :color-for="typeColor"></h-bar-chart>
|
||||||
|
<div class="figli-year-hbar-grid">
|
||||||
|
<div class="figli-year-hbar-card" v-for="y in chargeParClientParAnnee" :key="y.annee">
|
||||||
|
<div class="figli-year-hbar-title">{{ y.annee }}</div>
|
||||||
|
<h-bar-chart :items="y.items" :format-value="formatEurRound" :color-for="typeColor" compact></h-bar-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="figli-chart-section">
|
<section class="figli-chart-section">
|
||||||
<h2>Total des versements par compte</h2>
|
<h2>Total des versements par compte</h2>
|
||||||
<p class="figli-note">Montant total versé à chaque compte associé, plus les salaires/stages et sous-traitants, toutes années confondues.</p>
|
<p class="figli-note">Montant total versé à chaque compte associé, plus les salaires/stages et sous-traitants, toutes années confondues.</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user