Add per-compte dashboard: reste à verser between entrées and versements
New /dashboard/compte page, one compte associé (freelance) at a time via a selector. Focused on entree/versement only (not achat/hébergement/ sous-traitant): reuses the same entrée<->versement reconciliation algorithm as the /lignes badges (a versement can settle several entrées at once, split equally), aggregated per compte to surface outstanding "reste à verser", over-paid entrées, and versements with no linked entrée at all -- plus solde/entrées-vs-versements/top-clients charts for context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
{#
|
||||
Dashboard par compte associé (freelance) : un compte à la fois, choisi
|
||||
dans le sélecteur. Se concentre sur entrée client / versement freelance
|
||||
uniquement -- l'objectif principal est de faire ressortir les versements
|
||||
pas (encore) compensés par une entrée, pas de donner une vue comptable
|
||||
complète (voir /dashboard pour ça).
|
||||
|
||||
{% verbatim %} ci-dessous : syntaxe Vue, pas Twig -- voir
|
||||
figli-compta-dashboard.html.twig pour la même remarque.
|
||||
#}
|
||||
<nav class="figli-page-nav">
|
||||
<a href="{{ path('figli_compta_ledger.home') }}" class="{{ current_route == 'figli_compta_ledger.home' ? 'is-active' : '' }}">Grand livre</a>
|
||||
<a href="{{ path('figli_compta_ledger.dashboard') }}" class="{{ current_route == 'figli_compta_ledger.dashboard' ? 'is-active' : '' }}">Dashboard</a>
|
||||
<a href="{{ path('figli_compta_ledger.dashboard_compte') }}" class="{{ current_route == 'figli_compta_ledger.dashboard_compte' ? 'is-active' : '' }}">Par compte</a>
|
||||
</nav>
|
||||
{% verbatim %}
|
||||
<div id="figli-dashboard-app">
|
||||
<p v-if="loading">Chargement des données…</p>
|
||||
<p v-else-if="error" class="figli-error">Erreur de chargement du tableau de bord : {{ error }}</p>
|
||||
<template v-else>
|
||||
<div class="figli-compte-selector">
|
||||
<label for="figli-compte-select">Compte associé</label>
|
||||
<select id="figli-compte-select" :value="selectedCompte" @change="selectCompte($event.target.value)">
|
||||
<option v-for="c in comptes" :key="c" :value="c">{{ c }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="figli-summary-row">
|
||||
<div class="figli-summary-card">
|
||||
<div class="figli-summary-label">Solde actuel</div>
|
||||
<div class="figli-summary-value" :class="soldeActuel < 0 ? 'is-negative' : 'is-positive'">{{ formatEurRound(soldeActuel) }}</div>
|
||||
</div>
|
||||
<div class="figli-summary-card" :class="{'is-alert': totalResteAVerser > 0.01}">
|
||||
<div class="figli-summary-label">Reste à verser</div>
|
||||
<div class="figli-summary-value" :class="totalResteAVerser > 0.01 ? 'is-negative' : ''">{{ formatEurRound(totalResteAVerser) }}</div>
|
||||
</div>
|
||||
<div class="figli-summary-card" v-if="totalSurVerse > 0.01">
|
||||
<div class="figli-summary-label">Sur-versé</div>
|
||||
<div class="figli-summary-value is-negative">{{ formatEurRound(totalSurVerse) }}</div>
|
||||
</div>
|
||||
<div class="figli-summary-card" :class="{'is-alert': totalNonLies > 0.01}" v-if="versementsNonLies.length">
|
||||
<div class="figli-summary-label">Versements non liés</div>
|
||||
<div class="figli-summary-value is-negative">{{ formatEurRound(totalNonLies) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="figli-chart-section">
|
||||
<h2>Reste à verser -- entrées non (entièrement) compensées</h2>
|
||||
<p class="figli-note">Pour {{ selectedCompte }} : entrées client dont la part attribuée n'a pas encore été entièrement reversée. Triées de la plus ancienne à la plus récente.</p>
|
||||
<table v-if="resteAVerserPositif.length" class="figli-recon-table">
|
||||
<thead>
|
||||
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Attribué</th><th>Déjà versé</th><th>Reste à verser</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in resteAVerserPositif" :key="r.entree.id">
|
||||
<td>{{ formatDate(r.entree.date) }}</td>
|
||||
<td><a :href="ligneHref(r.entree.client)">{{ r.entree.client || '—' }}</a></td>
|
||||
<td>{{ r.entree.libelle || '—' }}</td>
|
||||
<td>{{ formatEur(r.montantAttribue) }}</td>
|
||||
<td>{{ formatEur(r.dejaVerse) }}</td>
|
||||
<td class="is-negative">{{ formatEur(r.residual) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr><td colspan="5">Total</td><td class="is-negative">{{ formatEurRound(totalResteAVerser) }}</td></tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p v-else class="figli-note">Rien en attente -- toutes les entrées de ce compte sont compensées.</p>
|
||||
</section>
|
||||
|
||||
<section class="figli-chart-section" v-if="surVerseRows.length">
|
||||
<h2>Entrées sur-versées</h2>
|
||||
<p class="figli-note">Le montant versé pour ces entrées dépasse la part attribuée à {{ selectedCompte }} -- à vérifier.</p>
|
||||
<table class="figli-recon-table">
|
||||
<thead>
|
||||
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Attribué</th><th>Versé</th><th>Sur-versé</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in surVerseRows" :key="r.entree.id">
|
||||
<td>{{ formatDate(r.entree.date) }}</td>
|
||||
<td><a :href="ligneHref(r.entree.client)">{{ r.entree.client || '—' }}</a></td>
|
||||
<td>{{ r.entree.libelle || '—' }}</td>
|
||||
<td>{{ formatEur(r.montantAttribue) }}</td>
|
||||
<td>{{ formatEur(r.dejaVerse) }}</td>
|
||||
<td class="is-negative">{{ formatEur(-r.residual) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="figli-chart-section" v-if="versementsNonLies.length">
|
||||
<h2>Versements sans entrée liée</h2>
|
||||
<p class="figli-note">Versements de {{ selectedCompte }} qui ne pointent vers aucune entrée client -- ni "reste à verser" ni "sur-versé" ci-dessus ne les couvre.</p>
|
||||
<table class="figli-recon-table">
|
||||
<thead>
|
||||
<tr><th>Date</th><th>Client</th><th>Libellé</th><th>Montant</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in versementsNonLies" :key="r.id">
|
||||
<td>{{ formatDate(r.date) }}</td>
|
||||
<td><a :href="ligneHref(r.client)">{{ r.client || '—' }}</a></td>
|
||||
<td>{{ r.libelle || '—' }}</td>
|
||||
<td class="is-negative">{{ formatEur(r.parCompte[selectedCompte]) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="figli-chart-section">
|
||||
<h2>Entrées vs versements par année</h2>
|
||||
<p class="figli-note">Part de {{ selectedCompte }} dans les entrées client (vert) et ses versements (rouge), année par année.</p>
|
||||
<year-bars-chart :years="entreeVsVersementYears" :format-value="formatEurRound"></year-bars-chart>
|
||||
</section>
|
||||
|
||||
<section class="figli-chart-section">
|
||||
<h2>Évolution du solde</h2>
|
||||
<p class="figli-note">Solde de clôture de {{ selectedCompte }}, année par année (ouverture comprise).</p>
|
||||
<year-bars-chart :years="evolutionSoldeYears" :format-value="formatEurRound"></year-bars-chart>
|
||||
</section>
|
||||
|
||||
<section class="figli-chart-section" v-if="topClientsItems.length">
|
||||
<h2>Top clients</h2>
|
||||
<p class="figli-note">Clients ayant généré le plus d'entrées attribuées à {{ selectedCompte }}, toutes années confondues.</p>
|
||||
<h-bar-chart :items="topClientsItems" :format-value="formatEurRound"></h-bar-chart>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
{% endverbatim %}
|
||||
@@ -10,6 +10,7 @@
|
||||
<nav class="figli-page-nav">
|
||||
<a href="{{ path('figli_compta_ledger.home') }}" class="{{ current_route == 'figli_compta_ledger.home' ? 'is-active' : '' }}">Grand livre</a>
|
||||
<a href="{{ path('figli_compta_ledger.dashboard') }}" class="{{ current_route == 'figli_compta_ledger.dashboard' ? 'is-active' : '' }}">Dashboard</a>
|
||||
<a href="{{ path('figli_compta_ledger.dashboard_compte') }}" class="{{ current_route == 'figli_compta_ledger.dashboard_compte' ? 'is-active' : '' }}">Par compte</a>
|
||||
</nav>
|
||||
{% verbatim %}
|
||||
<div id="figli-dashboard-app">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<nav class="figli-page-nav">
|
||||
<a href="{{ path('figli_compta_ledger.home') }}" class="{{ current_route == 'figli_compta_ledger.home' ? 'is-active' : '' }}">Grand livre</a>
|
||||
<a href="{{ path('figli_compta_ledger.dashboard') }}" class="{{ current_route == 'figli_compta_ledger.dashboard' ? 'is-active' : '' }}">Dashboard</a>
|
||||
<a href="{{ path('figli_compta_ledger.dashboard_compte') }}" class="{{ current_route == 'figli_compta_ledger.dashboard_compte' ? 'is-active' : '' }}">Par compte</a>
|
||||
</nav>
|
||||
{% verbatim %}
|
||||
<div id="figli-home-app">
|
||||
|
||||
Reference in New Issue
Block a user