Règles — Transactions
BR-TX-001 — Déduplication par importHash [DB]
Transaction.importHash String @unique
Chaque transaction importée via CSV reçoit un hash déterministe calculé par le parser à partir des champs significatifs (date, montant, libellé, compte). L'import utilise createMany({ skipDuplicates: true }) : une transaction dont le hash existe déjà est silencieusement ignorée.
Résultat : importer deux fois le même fichier CSV est idempotent. Le compteur ignored dans le résultat d'import indique le nombre de lignes sautées.
BR-TX-002 — Convention de signe [APP]
| Signe | Sens |
|---|---|
amount > 0 | Crédit (entrée d'argent) |
amount < 0 | Débit (sortie d'argent) |
Le signe est fixé par le parser au moment de l'import et ne change jamais ensuite. Les calculs analytics filtrent par amount > 0 (crédits) ou amount < 0 (débits) directement.
BR-TX-003 — Transferts internes [APP]
Une transaction isInternal = true représente un transfert entre deux comptes de l'utilisateur. Ces transactions sont exclues de tous les calculs analytics (flux mensuels, répartition par catégorie, résumé).
Détection automatique à l'import
Après chaque import, le système scanne les nouvelles transactions et les compare aux transactions des autres comptes du même utilisateur :
- Montant absolu identique (
|amount_A| === |amount_B|) - Signes opposés (un débit + un crédit)
- Fenêtre temporelle ±2 jours
- Comptes différents
Si une paire est trouvée, les deux transactions sont marquées isInternal = true et liées via counterpartId (mutuellement référencées) dans une transaction Prisma atomique.
Toggle manuel
L'utilisateur peut basculer isInternal via le bouton ToggleInternalButton. Si la transaction a un counterpartId, les deux côtés du transfert sont basculés simultanément (transaction Prisma atomique).
BR-TX-004 — Montants en Decimal(12, 2) [DB]
Transaction.amount Decimal @db.Decimal(12,2)
Transaction.amountEur Decimal @db.Decimal(12,2)
Les montants sont stockés avec 2 décimales en PostgreSQL via le type Decimal Prisma. Pas de Float pour éviter les erreurs d'arrondi sur les comparaisons (amount === -counterpart).
En JavaScript : Number(tx.amount) uniquement pour l'affichage. Jamais pour les comparaisons (préférer la comparaison Prisma { equals: value }).
BR-TX-005 — Taux de change stocké à la date [DB]
Transaction.fxRate Decimal @db.Decimal(18,8) @default(1)
Le taux EUR appliqué au moment de l'import est stocké dans fxRate. amountEur = amount / fxRate (ou amount * fxRate selon la convention devise). Un taux de 1 signifie que la transaction est déjà en EUR.
Voir Règles — FX pour la politique complète des taux de change.
BR-TX-006 — Source d'import [DB]
L'enum ImportSource trace l'origine de chaque transaction :
| Valeur | Origine |
|---|---|
CSV_BOURSO | Export BoursoBank (opérations) |
CSV_REVOLUT | Export Revolut |
CSV_TR | Export Trade Republic |
MANUAL | Saisie manuelle (non implémentée en V1) |
CSV_BOURSO_PEA n'a pas de valeur dédiée dans l'enum — les transactions PEA BoursoBank utilisent CSV_BOURSO.
BR-TX-007 — Pagination par défaut 50 [APP]
Les listes de transactions sont paginées avec pageSize = 50 par défaut. Le paramètre page commence à 1. L'API retourne { transactions, total } pour permettre la pagination côté client.