---
titre: Solde de compte et trésorerie
statut: Draft v1 — décrit l’implémentation actuelle au code (2026-05-22)
date: 2026-05-22
---

# 07 — Solde et trésorerie

Ce document explique **comment le solde est défini et calculé** dans VIIZIA tel qu’implémenté aujourd’hui, à quoi correspondent les champs affichés dans l’interface, et où se situent les **limites** (notamment par rapport à un relevé bancaire).

Référence modèle : colonnes `initial_balance` et `current_balance` sur `bank_account`, entité **`transaction`** — voir [02-modele-donnees.md](02-modele-donnees.md).

---

## 1. Champs sur le compte bancaire (`bank_account`)

| Champ (BDD) | Rôle métier |
|---|---|
| **`initial_balance`** | **Solde initial** du compte, en **plus petite unité de la devise** (centimes pour EUR). Peut être **négatif** (découvert, CB, etc.). Repère comptable : *« à la date de référence choisie »* (voir § 4), pas nécessairement la date du tout premier mouvement dans le système. |
| **`current_balance`** | **Solde courant** recalculé par l’application (voir § 2). **Pas un champ saisi librement** en usage normal après que des transactions existent : il est dérivé. |

Les montants sont des **BIGINT** (chaînes côté entité Doctrine pour certains getters), **sans virgule** : « 123,45 € » → `12345` avec `currency = EUR` et `currency_minor_units = 2`.

---

## 2. Formule du solde courant (par compte)

Pour un **`BankAccount` donné** :

$$\textbf{solde\_courant} = \textbf{solde\_initial} + \sum_{\text{mouvements actifs}} \textbf{montant\_signé}$$

Implémentation : `App\Service\BankAccountBalanceCalculator::recalculate()`.

Étapes dans le code :

1. Lecture de **`initial_balance`** (entier centimes).
2. Agrégation SQL des transactions du compte : `App\Repository\TransactionRepository::sumAmountByBankAccount()`.
3. Pour chaque transaction **non supprimée** (`deleted_at IS NULL`) :
   - le champ **`amount`** est **toujours positif** en base ;
   - le signe appliqué au solde vient de **`direction`** (`TransactionDirection`) :
     - **`In`** (entrée) → contribution **+amount** ;
     - **`Out`** (dépense) → contribution **−amount**.

Formule équivalente en SQL (schéma simplifié) :

```sql
SUM(CASE WHEN direction = 'out' THEN -amount ELSE amount END)
```

Les transactions **soft-deleted** (`deleted_at` renseigné) sont **exclues** de la somme.

---

## 3. Quand le solde courant est recalculé ?

Le commentaire historique sur `BankAccountBalanceCalculator` évoquait un *listener* générique ; **en l’état du code**, le recalcul est **explicite** aux endroits suivants :

| Déclencheur | Fichier / contexte |
|---|---|
| Création d’un compte | `BankAccountController::new` — `current_balance` est initialisé à **`initial_balance`** (aucune transaction encore). |
| Sauvegarde du compte | `BankAccountController::edit` — après flush, **`recalculate`** puis second flush pour persister **`current_balance`**. |
| Import CSV terminé | `CsvImportRunner` — après `flush` des lignes importées, **`recalculate($bankAccount)`** puis `flush`. |
| Validation d’import PDF (parcours review) | `PdfImportReviewService` — **recalcul** du compte concerné selon le flux. |
| Création / modification / suppression logique d’une transaction | `TransactionController` — après `flush`, **`recalculate($bankAccount)`** puis `flush`. |

**Conséquence** : toute autre voie qui créerait ou modifierait des transactions **sans** appeler `BankAccountBalanceCalculator::recalculate()` laisserait **`current_balance` faux** jusqu’au prochain recalcul sur ce compte.

---

## 4. Point de départ métier : cohérence avec la banque

Le **solde initial** n’est pas « magique » : il doit refléter **le solde réel du compte à une date de référence** **telle que** la somme des mouvements enregistrés dans VIIZIA + ce solde initial reproduise le solde bancaire **à la fin de l’historique chargé**.

En pratique :

- Si vous importez / saisissez **toute** l’historique depuis l’ouverture du compte, le solde initial est en général **0** ou le solde **à la veille du premier mouvement** importé.
- Si vous ne chargez que **une partie** de l’historique (cas fréquent), le **solde initial doit être le solde réel à la date précédant le premier mouvement présent** dans l’app (pour que « initial + mouvements = solde réel aux dates suivantes », sous réserve que tous les mouvements nécessaires soient bien dans le fichier).

Une divergence avec le relevé peut venir :

- mouvements manquants (non importés) ;
- mouvements en double ;
- erreur sur **entrée vs dépense** (`direction`) ;
- erreur ou changement du **solde initial** alors que des transactions existent déjà — voir § 7 (limite UX actuelle).

---

## 5. Trésorerie consolidée et graphiques (tableau de bord)

Pour le tableau de bord organisé (**plusieurs comptes** dans le périmètre des filtres), un **solde d’ouverture de période** est calculé **sans relecture du champ `current_balance` en base** : on refait la logique **initial + somme des transactions actives dont la date d’imputation (`booking_date`) est strictement avant le début de la période**.

Référence : `DashboardDataProvider::consolidatedOpeningBalanceCents()` et `TransactionRepository::sumSignedAmountByBankAccountStrictlyBefore()`.

Important :

- C’est une **somme brute** sur les comptes filtrés : **pas de conversion de devise** consolidée dans ce calcul (voir doc inline du dashboard si multi-devises).
- Les graphiques « évolution » utilisent ces mêmes **notions de période** et filtres liste (catégories, tags, etc., selon le code applicable au moment de la requête agrégée).

---

## 6. Transactions : règles utiles au solde

- **`booking_date`** : date retenue pour le **rangement chronologique** et les **soldes cumulés par période** (dont ouverture de période au § 5). Ce n’est pas forcément toujours la « date valeur » (`value_date`).
- **`direction` + amount positif** : modèle décisionnel documenté dans `TransactionDirection` — ne pas encoder le débit comme montant négatif en base ; utiliser **`Out`** + montant positif.

---

## 7. Points d’attention

### 7.1 Sauvegarde du compte bancaire après des transactions

À chaque mise à jour réussie depuis `BankAccountController::edit`, le **`current_balance`** est recalculé via `BankAccountBalanceCalculator::recalculate()` (formule § 2).

Toute extension (script, import hors `CsvImportRunner`, etc.) qui modifie des transactions **sans** appeler ensuite `BankAccountBalanceCalculator::recalculate()` pour le compte concerné peut désynchroniser l’UI.

---

## 8. Synthèse en une phrase

**Le solde courant stocké est `solde_initial` plus la somme signée (`In` − `Out`) des transactions actives du compte ; les transactions soft-supprimées ne comptent pas ; les recalculs sont déclenchés explicitement après import CSV, validation PDF transactionnelle, CRUD transaction — et désormais aussi après mise à jour du formulaire du compte bancaire (pas automatiquement sur « toute » mutation Doctrine générique).**

---

## Changelog

| Date | Changement |
|---|---|
| 2026-05-22 | Création du document + correction contrôleur compte (`recalculate` après sauvegarde). |
