Move specs and masterplan to docs/archive/, add architecture.md with full technical overview, create 5 ADRs (Tauri v2, useReducer, sqlx migrations, AES-256-GCM encryption, multi-profile DB), and move guide-utilisateur.md into docs/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1057 lines
36 KiB
Markdown
1057 lines
36 KiB
Markdown
# Simpl-Résultat - Spécification Détaillée v2
|
||
|
||
## Table des matières
|
||
1. [Résumé exécutif](#résumé-exécutif)
|
||
2. [Description générale enrichie](#description-générale-enrichie)
|
||
3. [Architecture proposée](#architecture-proposée)
|
||
4. [Spécifications fonctionnelles détaillées](#spécifications-fonctionnelles-détaillées)
|
||
5. [Modèle de données](#modèle-de-données)
|
||
6. [Interface utilisateur](#interface-utilisateur)
|
||
7. [Points faibles identifiés et recommandations](#points-faibles-identifiés-et-recommandations)
|
||
8. [Considérations techniques](#considérations-techniques)
|
||
9. [Plan de développement suggéré](#plan-de-développement-suggéré)
|
||
|
||
---
|
||
|
||
## Résumé exécutif
|
||
|
||
**Simpl-Résultat** est une application de bureau locale permettant aux utilisateurs de suivre leurs dépenses personnelles à partir d'exports CSV bancaires. L'application vise à catégoriser automatiquement les transactions, permettre des ajustements manuels, et fournir des rapports visuels pour le suivi budgétaire.
|
||
|
||
### Objectifs principaux
|
||
- Centraliser les données financières de multiples sources (comptes chèques, épargne, cartes de crédit)
|
||
- Automatiser la catégorisation des dépenses via mots-clés
|
||
- Permettre un suivi budgétaire mensuel avec comparaisons historiques
|
||
- Offrir une expérience utilisateur simple et locale (pas de cloud)
|
||
|
||
---
|
||
|
||
## Description générale enrichie
|
||
|
||
### Vision du produit
|
||
Une application de finances personnelles **100% locale** (privacy-first) qui transforme des exports CSV bancaires bruts en insights financiers actionnables, sans nécessiter de connexion à des services tiers ou de partage de données sensibles.
|
||
|
||
### Proposition de valeur
|
||
| Aspect | Description |
|
||
|--------|-------------|
|
||
| **Confidentialité** | Données stockées localement uniquement |
|
||
| **Simplicité** | Import CSV sans configuration bancaire complexe |
|
||
| **Flexibilité** | Support multi-sources, multi-formats |
|
||
| **Automatisation** | Catégorisation intelligente par mots-clés |
|
||
| **Contrôle** | Écritures d'ajustement et personnalisation complète |
|
||
|
||
### Utilisateurs cibles
|
||
- Particuliers souhaitant suivre leur budget sans services cloud
|
||
- Utilisateurs soucieux de la confidentialité de leurs données financières
|
||
- Personnes ayant plusieurs comptes bancaires à consolider
|
||
|
||
---
|
||
|
||
## Architecture proposée
|
||
|
||
### Stack technologique recommandé
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ PRÉSENTATION │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ Electron / Tauri (Frontend) │ │
|
||
│ │ React/Vue/Svelte + TailwindCSS │ │
|
||
│ │ Charts: Recharts / Chart.js │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ LOGIQUE │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ Service Layer (TypeScript) │ │
|
||
│ │ • ImportService • CategoryService │ │
|
||
│ │ • TransactionService • BudgetService │ │
|
||
│ │ • ReportingService • AdjustmentService │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ DONNÉES │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ SQLite (Base locale) │ │
|
||
│ │ + better-sqlite3 / sql.js │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ Fichiers de configuration │ │
|
||
│ │ JSON (préférences) │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Justification des choix
|
||
|
||
| Composant | Option recommandée | Raison |
|
||
|-----------|-------------------|--------|
|
||
| **Framework desktop** | **Tauri** (P1) ou Electron | Tauri = plus léger, Rust backend, meilleure sécurité. Electron = écosystème plus mature |
|
||
| **Frontend** | Svelte ou React | Svelte = bundle léger, React = plus de ressources/libs |
|
||
| **Base de données** | SQLite | Locale, performante, pas de serveur, portable |
|
||
| **Charts** | Recharts ou Chart.js | Intégration React facile, bonne personnalisation |
|
||
| **Parsing CSV** | PapaParse | Robuste, gère les edge cases (quotes, encoding) |
|
||
|
||
### Architecture des fichiers
|
||
|
||
```
|
||
simpl-resultat/
|
||
├── src/
|
||
│ ├── main/ # Process principal (Electron/Tauri)
|
||
│ │ ├── database/
|
||
│ │ │ ├── schema.sql
|
||
│ │ │ ├── migrations/
|
||
│ │ │ └── repository/
|
||
│ │ ├── services/
|
||
│ │ │ ├── import.service.ts
|
||
│ │ │ ├── category.service.ts
|
||
│ │ │ ├── transaction.service.ts
|
||
│ │ │ ├── budget.service.ts
|
||
│ │ │ ├── adjustment.service.ts
|
||
│ │ │ └── report.service.ts
|
||
│ │ └── ipc/ # Communication main/renderer
|
||
│ │
|
||
│ ├── renderer/ # Interface utilisateur
|
||
│ │ ├── components/
|
||
│ │ ├── pages/
|
||
│ │ ├── stores/
|
||
│ │ └── utils/
|
||
│ │
|
||
│ └── shared/ # Types partagés
|
||
│ ├── types/
|
||
│ └── constants/
|
||
│
|
||
├── data/ # Données utilisateur (gitignored)
|
||
│ ├── database.sqlite
|
||
│ └── backups/
|
||
│
|
||
├── imports/ # Dossiers d'import configurés
|
||
│ ├── carte_credit/
|
||
│ ├── compte_cheque/
|
||
│ └── epargne/
|
||
│
|
||
└── config/
|
||
├── categories_default.json
|
||
└── import_profiles.json
|
||
```
|
||
|
||
---
|
||
|
||
## Spécifications fonctionnelles détaillées
|
||
|
||
### 1. Module d'importation
|
||
|
||
#### 1.1 Configuration des sources
|
||
|
||
```typescript
|
||
interface ImportSource {
|
||
id: string;
|
||
name: string; // Ex: "Carte de crédit Visa"
|
||
directoryPath: string; // Chemin du répertoire surveillé
|
||
filePattern: string; // Ex: "export_cc*.csv"
|
||
encoding: 'utf-8' | 'iso-8859-1' | 'windows-1252';
|
||
delimiter: ',' | ';' | '\t';
|
||
hasHeader: boolean;
|
||
dateFormat: string; // Ex: "YYYY-MM-DD", "DD/MM/YYYY"
|
||
columnMapping: ColumnMapping;
|
||
amountRule: AmountRule;
|
||
skipLines?: number; // Lignes à ignorer en début de fichier
|
||
lastImportDate?: Date;
|
||
}
|
||
|
||
interface ColumnMapping {
|
||
date: number | string; // Index ou nom de colonne
|
||
description: number | string;
|
||
transactionCode?: number | string;
|
||
debitAmount?: number | string;
|
||
creditAmount?: number | string;
|
||
amount?: number | string; // Si montant unique
|
||
}
|
||
|
||
interface AmountRule {
|
||
type: 'separate' | 'combined';
|
||
// Si combined:
|
||
signConvention?: 'negative_is_debit' | 'negative_is_credit';
|
||
}
|
||
```
|
||
|
||
#### 1.2 Processus d'importation
|
||
|
||
```
|
||
[Sélection source] → [Détection fichiers] → [Preview données]
|
||
↓ ↓
|
||
[Validation mapping] ← [Ajustement mapping si nécessaire]
|
||
↓
|
||
[Détection doublons] → [Confirmation import]
|
||
↓
|
||
[Import en base] → [Catégorisation auto] → [Rapport d'import]
|
||
```
|
||
|
||
#### 1.3 Gestion des doublons
|
||
|
||
**Critères de détection de doublon:**
|
||
- Date + Description + Montant identiques
|
||
- Ou: Hash SHA256 de la ligne complète déjà présent
|
||
|
||
**Options utilisateur:**
|
||
- Ignorer les doublons (défaut)
|
||
- Forcer l'import
|
||
- Demander confirmation pour chaque
|
||
|
||
### 2. Module de catégorisation
|
||
|
||
#### 2.1 Structure hiérarchique
|
||
|
||
```typescript
|
||
interface Category {
|
||
id: string;
|
||
name: string;
|
||
type: 'recurring' | 'occasional' | 'special' | 'transfer' | 'income';
|
||
parentId?: string; // Pour sous-catégories
|
||
color?: string; // Pour les graphiques
|
||
icon?: string;
|
||
isSystem: boolean; // Non supprimable si true
|
||
}
|
||
|
||
interface Supplier {
|
||
id: string;
|
||
name: string;
|
||
categoryId: string;
|
||
keywords: string[]; // Mots-clés pour matching
|
||
priority: number; // En cas de conflit de matching
|
||
}
|
||
```
|
||
|
||
#### 2.2 Algorithme de matching
|
||
|
||
```
|
||
Pour chaque transaction non catégorisée:
|
||
1. Normaliser la description (minuscules, sans accents, trim)
|
||
2. Pour chaque fournisseur (trié par priorité DESC):
|
||
a. Pour chaque mot-clé du fournisseur:
|
||
- Si description.contains(keyword) → MATCH
|
||
b. Si MATCH → Assigner catégorie du fournisseur, STOP
|
||
3. Si aucun match → Catégorie "Autres dépenses"
|
||
```
|
||
|
||
**Améliorations suggérées:**
|
||
- Support des expressions régulières
|
||
- Matching par montant (ex: "Netflix" toujours ~15.99$)
|
||
- Apprentissage des assignations manuelles
|
||
|
||
#### 2.3 Interface de gestion des non-matchés
|
||
|
||
| Élément | Description |
|
||
|---------|-------------|
|
||
| Liste des transactions | Triées par date, regroupables par description similaire |
|
||
| Recherche rapide | Filtrer par texte dans description |
|
||
| Action rapide | Créer mot-clé à partir de la sélection |
|
||
| Action groupée | Catégoriser plusieurs transactions similaires en un clic |
|
||
|
||
### 3. Module d'ajustements
|
||
|
||
#### 3.1 Types d'écritures
|
||
|
||
```typescript
|
||
interface Adjustment {
|
||
id: string;
|
||
originalTransactionId: string;
|
||
adjustmentDate: Date; // Fin du mois concerné
|
||
effectiveMonth: string; // Format "YYYY-MM"
|
||
entries: AdjustmentEntry[];
|
||
reason?: string;
|
||
createdAt: Date;
|
||
}
|
||
|
||
interface AdjustmentEntry {
|
||
categoryId: string;
|
||
amount: number; // Positif = augmente cette catégorie
|
||
}
|
||
```
|
||
|
||
#### 3.2 Exemple d'usage
|
||
|
||
Transaction Amazon de 150$:
|
||
- Original: 100% → "Achats divers"
|
||
- Ajustement:
|
||
- -150$ "Achats divers"
|
||
- +80$ "Sport"
|
||
- +70$ "Musique"
|
||
|
||
### 4. Module Budget
|
||
|
||
#### 4.1 Structure
|
||
|
||
```typescript
|
||
interface BudgetEntry {
|
||
id: string;
|
||
categoryId: string;
|
||
month: string; // "YYYY-MM"
|
||
plannedAmount: number;
|
||
notes?: string;
|
||
}
|
||
|
||
interface BudgetTemplate {
|
||
id: string;
|
||
name: string;
|
||
entries: Omit<BudgetEntry, 'id' | 'month'>[];
|
||
}
|
||
```
|
||
|
||
#### 4.2 Fonctionnalités
|
||
|
||
- **Templates**: Créer des budgets types réutilisables
|
||
- **Copie**: Dupliquer le budget d'un mois vers un autre
|
||
- **Alertes**: Notification visuelle si dépassement (>80%, >100%)
|
||
- **Carry-over**: Option pour reporter le surplus/déficit
|
||
|
||
### 5. Module Reporting
|
||
|
||
#### 5.1 Rapports disponibles
|
||
|
||
| Rapport | Description | Visualisation |
|
||
|---------|-------------|---------------|
|
||
| Suivi mensuel 12 mois | Évolution des dépenses par catégorie | Graphique barres empilées |
|
||
| Mois courant vs précédent | Comparaison détaillée | Tableau + barres horizontales |
|
||
| Cumulatif YTD | Année courante vs précédente | Graphique ligne + aires |
|
||
| Répartition | Distribution par catégorie | Pie chart / Treemap |
|
||
| Tendances | Évolution sur période personnalisée | Graphique ligne |
|
||
| Budget vs Réel | Écarts budgétaires | Barres avec indicateurs |
|
||
|
||
#### 5.2 Filtres et dimensions
|
||
|
||
- Par source (compte)
|
||
- Par catégorie / sous-catégorie
|
||
- Par type de dépense
|
||
- Par période personnalisée
|
||
- Inclusion/exclusion des transferts
|
||
|
||
---
|
||
|
||
## Modèle de données
|
||
|
||
### Schéma SQL
|
||
|
||
```sql
|
||
-- Sources d'import
|
||
CREATE TABLE import_sources (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
directory_path TEXT NOT NULL,
|
||
file_pattern TEXT DEFAULT '*.csv',
|
||
encoding TEXT DEFAULT 'utf-8',
|
||
delimiter TEXT DEFAULT ',',
|
||
has_header INTEGER DEFAULT 1,
|
||
date_format TEXT NOT NULL,
|
||
column_mapping TEXT NOT NULL, -- JSON
|
||
amount_rule TEXT NOT NULL, -- JSON
|
||
skip_lines INTEGER DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Fichiers importés (pour éviter les doublons)
|
||
CREATE TABLE imported_files (
|
||
id TEXT PRIMARY KEY,
|
||
source_id TEXT NOT NULL REFERENCES import_sources(id),
|
||
filename TEXT NOT NULL,
|
||
file_hash TEXT NOT NULL, -- SHA256
|
||
row_count INTEGER NOT NULL,
|
||
imported_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(source_id, file_hash)
|
||
);
|
||
|
||
-- Catégories
|
||
CREATE TABLE categories (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
type TEXT NOT NULL CHECK(type IN ('recurring','occasional','special','transfer','income')),
|
||
parent_id TEXT REFERENCES categories(id),
|
||
color TEXT,
|
||
icon TEXT,
|
||
is_system INTEGER DEFAULT 0,
|
||
display_order INTEGER DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Fournisseurs
|
||
CREATE TABLE suppliers (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
category_id TEXT NOT NULL REFERENCES categories(id),
|
||
priority INTEGER DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Mots-clés
|
||
CREATE TABLE keywords (
|
||
id TEXT PRIMARY KEY,
|
||
supplier_id TEXT NOT NULL REFERENCES suppliers(id) ON DELETE CASCADE,
|
||
keyword TEXT NOT NULL,
|
||
is_regex INTEGER DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(supplier_id, keyword)
|
||
);
|
||
|
||
-- Transactions
|
||
CREATE TABLE transactions (
|
||
id TEXT PRIMARY KEY,
|
||
source_id TEXT NOT NULL REFERENCES import_sources(id),
|
||
imported_file_id TEXT NOT NULL REFERENCES imported_files(id),
|
||
transaction_date DATE NOT NULL,
|
||
description TEXT NOT NULL,
|
||
transaction_code TEXT,
|
||
debit_amount REAL DEFAULT 0, -- Note: SQLite REAL = 8 bytes IEEE float
|
||
credit_amount REAL DEFAULT 0, -- Pour précision crypto, stocker en INTEGER (satoshis/cents)
|
||
-- Alternative crypto-safe:
|
||
-- debit_amount_raw INTEGER DEFAULT 0, -- Montant × 10^8
|
||
-- credit_amount_raw INTEGER DEFAULT 0,
|
||
currency TEXT DEFAULT 'CAD',
|
||
supplier_id TEXT REFERENCES suppliers(id),
|
||
category_id TEXT REFERENCES categories(id),
|
||
is_manually_categorized INTEGER DEFAULT 0,
|
||
raw_data TEXT, -- Ligne CSV originale
|
||
row_hash TEXT NOT NULL, -- Pour détection doublons
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(row_hash)
|
||
);
|
||
|
||
-- Index pour performance
|
||
CREATE INDEX idx_transactions_date ON transactions(transaction_date);
|
||
CREATE INDEX idx_transactions_category ON transactions(category_id);
|
||
CREATE INDEX idx_transactions_month ON transactions(strftime('%Y-%m', transaction_date));
|
||
|
||
-- Écritures d'ajustement
|
||
CREATE TABLE adjustments (
|
||
id TEXT PRIMARY KEY,
|
||
original_transaction_id TEXT NOT NULL REFERENCES transactions(id),
|
||
adjustment_date DATE NOT NULL,
|
||
effective_month TEXT NOT NULL, -- Format YYYY-MM
|
||
reason TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE TABLE adjustment_entries (
|
||
id TEXT PRIMARY KEY,
|
||
adjustment_id TEXT NOT NULL REFERENCES adjustments(id) ON DELETE CASCADE,
|
||
category_id TEXT NOT NULL REFERENCES categories(id),
|
||
amount REAL NOT NULL
|
||
);
|
||
|
||
-- Budget
|
||
CREATE TABLE budget_entries (
|
||
id TEXT PRIMARY KEY,
|
||
category_id TEXT NOT NULL REFERENCES categories(id),
|
||
month TEXT NOT NULL, -- Format YYYY-MM
|
||
planned_amount REAL NOT NULL,
|
||
notes TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE(category_id, month)
|
||
);
|
||
|
||
-- Templates de budget
|
||
CREATE TABLE budget_templates (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE TABLE budget_template_entries (
|
||
id TEXT PRIMARY KEY,
|
||
template_id TEXT NOT NULL REFERENCES budget_templates(id) ON DELETE CASCADE,
|
||
category_id TEXT NOT NULL REFERENCES categories(id),
|
||
planned_amount REAL NOT NULL
|
||
);
|
||
|
||
-- Préférences utilisateur
|
||
CREATE TABLE user_preferences (
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT NOT NULL,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Valeurs par défaut suggérées:
|
||
-- 'language' : 'fr' | 'en'
|
||
-- 'fiscal_year_start_month' : '1' (janvier par défaut, '4' pour avril, etc.)
|
||
-- 'fiscal_year_start_day' : '1'
|
||
-- 'default_currency' : 'CAD'
|
||
-- 'date_format' : 'DD/MM/YYYY' | 'YYYY-MM-DD' | 'MM/DD/YYYY'
|
||
-- 'decimal_separator' : ',' | '.'
|
||
-- 'theme' : 'light' | 'dark' | 'system'
|
||
```
|
||
|
||
---
|
||
|
||
## Interface utilisateur
|
||
|
||
### Navigation principale
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ [Logo] Simpl-Résultat [⚙️ Paramètres] [?] │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 📊 Tableau de bord │
|
||
│ 📥 Importation │
|
||
│ 💳 Transactions │
|
||
│ 🏷️ Catégories │
|
||
│ 📝 Ajustements │
|
||
│ 💰 Budget │
|
||
│ 📈 Rapports │
|
||
│ │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ [Version Free/Premium] │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Écrans principaux
|
||
|
||
#### Tableau de bord
|
||
- Résumé du mois courant
|
||
- Graphique dépenses vs budget
|
||
- Alertes (transactions non catégorisées, dépassements)
|
||
- Raccourcis actions fréquentes
|
||
|
||
#### Importation
|
||
- Liste des sources configurées
|
||
- Bouton "Nouvelle source"
|
||
- Pour chaque source: dernière import, fichiers en attente
|
||
- Assistant d'import avec preview
|
||
|
||
#### Transactions
|
||
- Liste paginée avec recherche/filtres
|
||
- Colonnes: Date, Description, Catégorie, Débit, Crédit, Source
|
||
- Actions: Modifier catégorie, Voir détails, Créer ajustement
|
||
|
||
---
|
||
|
||
## Points faibles identifiés et recommandations
|
||
|
||
### 🔴 Points critiques
|
||
|
||
| # | Problème | Impact | Recommandation |
|
||
|---|----------|--------|----------------|
|
||
| 1 | **Pas de gestion des devises** | Impossible de consolider des comptes multi-devises | Ajouter un champ `currency` aux sources et transactions, prévoir conversion |
|
||
| 2 | **Pas de backup/export** | Perte de données si crash | Implémenter export SQLite + JSON, backup auto |
|
||
| 3 | **Pas de gestion des revenus** | Budget incomplet sans les entrées | Ajouter type "income" aux catégories, rapport balance |
|
||
| 4 | **Règle montant unique simpliste** | Banques ont des formats variés | Support regex, formules personnalisées |
|
||
| 5 | **Pas de gestion de l'encodage** | CSV corrompus si mauvais encoding | Détection auto (chardet) + sélection manuelle |
|
||
|
||
### 🟠 Points importants
|
||
|
||
| # | Problème | Impact | Recommandation |
|
||
|---|----------|--------|----------------|
|
||
| 6 | **Pas de détection de doublons explicite** | Imports multiples du même fichier | Hash des lignes, table des fichiers importés |
|
||
| 7 | **Matching mots-clés basique** | Faux positifs/négatifs fréquents | Priorités, regex, matching partiel pondéré |
|
||
| 8 | **Pas de gestion des dates de valeur** | Date opération ≠ date valeur | Ajouter champ optionnel `value_date` |
|
||
| 9 | **Forfait freemium flou** | Confusion utilisateur | Détailler précisément les limitations |
|
||
| 10 | **Pas de recherche/filtre** | Difficile de retrouver une transaction | Recherche full-text, filtres avancés |
|
||
|
||
### 🟡 Améliorations suggérées
|
||
|
||
| # | Amélioration | Bénéfice |
|
||
|---|--------------|----------|
|
||
| 11 | **Import récurrent automatisé** | Surveiller les dossiers, import auto |
|
||
| 12 | **Règles de catégorisation avancées** | Combinaison montant + description + source |
|
||
| 13 | **Mode sombre** | Confort utilisateur |
|
||
| 14 | **Raccourcis clavier** | Productivité power users |
|
||
| 15 | **Export PDF des rapports** | Partage/archivage facile |
|
||
| 16 | **Graphiques interactifs** | Drill-down par clic |
|
||
| 17 | **Objectifs d'épargne** | Fonctionnalité motivante |
|
||
| 18 | **Tags personnalisés** | Catégorisation orthogonale (ex: "Vacances 2024") |
|
||
| 19 | **Notes sur transactions** | Contexte personnel |
|
||
| 20 | **Historique des modifications** | Audit trail |
|
||
|
||
### 🔵 Décisions prises
|
||
|
||
| Question | Décision | Notes |
|
||
|----------|----------|-------|
|
||
| **Multilinguisme** | ✅ Français + Anglais | i18n dès le départ |
|
||
| **Période fiscale** | ✅ Configurable | Année calendaire par défaut, option personnalisée |
|
||
| **Précision décimale** | ✅ 8 décimales | Support crypto-friendly |
|
||
| **Pièces jointes** | ❌ V2 | Hors scope pour MVP |
|
||
| **Licence freemium** | ✅ Code d'activation + serveur | Validation en ligne |
|
||
|
||
### 🔵 Questions encore ouvertes
|
||
|
||
1. **Synchronisation multi-appareils?** Local seulement ou option sync future?
|
||
2. **Comptes joints?** Gestion multi-utilisateurs sur mêmes données?
|
||
3. **API/Plugins?** Extensibilité future?
|
||
4. **Format date système?** Respect des préférences OS ou configurable?
|
||
5. **Transactions programmées?** Dépenses récurrentes futures?
|
||
|
||
---
|
||
|
||
## Considérations techniques
|
||
|
||
### Internationalisation (i18n)
|
||
|
||
Structure recommandée pour le support FR/EN :
|
||
|
||
```
|
||
src/
|
||
└── locales/
|
||
├── fr.json
|
||
├── en.json
|
||
└── index.ts
|
||
```
|
||
|
||
```json
|
||
// fr.json
|
||
{
|
||
"app.name": "Simpl-Résultat",
|
||
"nav.dashboard": "Tableau de bord",
|
||
"nav.import": "Importation",
|
||
"nav.transactions": "Transactions",
|
||
"transaction.date": "Date",
|
||
"transaction.description": "Description",
|
||
"transaction.debit": "Débit",
|
||
"transaction.credit": "Crédit",
|
||
"category.other": "Autres dépenses",
|
||
"budget.planned": "Prévu",
|
||
"budget.actual": "Réel",
|
||
"budget.remaining": "Restant"
|
||
}
|
||
```
|
||
|
||
```json
|
||
// en.json
|
||
{
|
||
"app.name": "Simpl-Result",
|
||
"nav.dashboard": "Dashboard",
|
||
"nav.import": "Import",
|
||
"nav.transactions": "Transactions",
|
||
"transaction.date": "Date",
|
||
"transaction.description": "Description",
|
||
"transaction.debit": "Debit",
|
||
"transaction.credit": "Credit",
|
||
"category.other": "Other expenses",
|
||
"budget.planned": "Planned",
|
||
"budget.actual": "Actual",
|
||
"budget.remaining": "Remaining"
|
||
}
|
||
```
|
||
|
||
Librairies recommandées :
|
||
- **Svelte** : svelte-i18n
|
||
- **React** : react-i18next
|
||
- **Python/Flet** : gettext ou babel
|
||
|
||
### Architecture de licence Freemium
|
||
|
||
```
|
||
┌─────────────────┐ ┌──────────────────────┐
|
||
│ Application │────▶│ Serveur licence │
|
||
│ (locale) │◀────│ (ton serveur) │
|
||
└─────────────────┘ └──────────────────────┘
|
||
│ │
|
||
│ POST /api/activate │
|
||
│ {license_key, hw_id} │
|
||
│ │
|
||
│ Response: │
|
||
│ {valid, features, │
|
||
│ expires_at} │
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────┐ ┌──────────────────────┐
|
||
│ License cache │ │ Base de données │
|
||
│ (local, chiffré)│ │ - licenses │
|
||
│ │ │ - activations │
|
||
└─────────────────┘ └──────────────────────┘
|
||
```
|
||
|
||
#### Schéma serveur (PostgreSQL/SQLite)
|
||
|
||
```sql
|
||
-- Licences générées à l'achat
|
||
CREATE TABLE licenses (
|
||
id UUID PRIMARY KEY,
|
||
license_key VARCHAR(32) UNIQUE NOT NULL, -- Ex: XXXX-XXXX-XXXX-XXXX
|
||
email VARCHAR(255),
|
||
plan VARCHAR(20) NOT NULL, -- 'premium', 'lifetime'
|
||
max_activations INT DEFAULT 3,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
expires_at TIMESTAMP, -- NULL = lifetime
|
||
is_revoked BOOLEAN DEFAULT FALSE
|
||
);
|
||
|
||
-- Activations (machines)
|
||
CREATE TABLE activations (
|
||
id UUID PRIMARY KEY,
|
||
license_id UUID REFERENCES licenses(id),
|
||
hardware_id VARCHAR(64) NOT NULL, -- Hash unique de la machine
|
||
app_version VARCHAR(20),
|
||
os_info VARCHAR(100),
|
||
activated_at TIMESTAMP DEFAULT NOW(),
|
||
last_seen_at TIMESTAMP DEFAULT NOW(),
|
||
is_active BOOLEAN DEFAULT TRUE,
|
||
UNIQUE(license_id, hardware_id)
|
||
);
|
||
```
|
||
|
||
#### Génération du Hardware ID
|
||
|
||
```typescript
|
||
// Côté client - génère un ID unique par machine
|
||
import { machineIdSync } from 'node-machine-id';
|
||
import * as crypto from 'crypto';
|
||
|
||
function getHardwareId(): string {
|
||
const machineId = machineIdSync();
|
||
// Hash pour anonymisation
|
||
return crypto
|
||
.createHash('sha256')
|
||
.update(machineId + 'simpl-resultat-salt')
|
||
.digest('hex')
|
||
.substring(0, 32);
|
||
}
|
||
```
|
||
|
||
#### API Serveur (exemple Node/Express)
|
||
|
||
```typescript
|
||
// POST /api/license/activate
|
||
app.post('/api/license/activate', async (req, res) => {
|
||
const { licenseKey, hardwareId, appVersion, osInfo } = req.body;
|
||
|
||
// 1. Vérifier la licence
|
||
const license = await db.licenses.findByKey(licenseKey);
|
||
if (!license || license.is_revoked) {
|
||
return res.status(400).json({ error: 'invalid_license' });
|
||
}
|
||
|
||
// 2. Vérifier expiration
|
||
if (license.expires_at && license.expires_at < new Date()) {
|
||
return res.status(400).json({ error: 'license_expired' });
|
||
}
|
||
|
||
// 3. Vérifier nombre d'activations
|
||
const activeCount = await db.activations.countActive(license.id);
|
||
const existing = await db.activations.findByHardware(license.id, hardwareId);
|
||
|
||
if (!existing && activeCount >= license.max_activations) {
|
||
return res.status(400).json({
|
||
error: 'max_activations_reached',
|
||
max: license.max_activations
|
||
});
|
||
}
|
||
|
||
// 4. Créer ou mettre à jour l'activation
|
||
await db.activations.upsert({
|
||
licenseId: license.id,
|
||
hardwareId,
|
||
appVersion,
|
||
osInfo,
|
||
lastSeenAt: new Date()
|
||
});
|
||
|
||
// 5. Retourner le token de session
|
||
const token = generateSignedToken({
|
||
licenseId: license.id,
|
||
plan: license.plan,
|
||
features: getPlanFeatures(license.plan),
|
||
expiresAt: license.expires_at
|
||
});
|
||
|
||
res.json({
|
||
valid: true,
|
||
token,
|
||
plan: license.plan,
|
||
features: getPlanFeatures(license.plan)
|
||
});
|
||
});
|
||
|
||
function getPlanFeatures(plan: string): string[] {
|
||
const features = {
|
||
free: ['import', 'basic_categories', 'monthly_report'],
|
||
premium: ['import', 'custom_categories', 'adjustments',
|
||
'all_reports', 'export_pdf', 'budget_templates']
|
||
};
|
||
return features[plan] || features.free;
|
||
}
|
||
```
|
||
|
||
#### Côté client - Vérification
|
||
|
||
```typescript
|
||
class LicenseManager {
|
||
private cache: LicenseCache;
|
||
|
||
async checkLicense(): Promise<LicenseStatus> {
|
||
// 1. Vérifier le cache local d'abord
|
||
const cached = await this.cache.get();
|
||
if (cached && !this.isExpired(cached) && !this.needsRevalidation(cached)) {
|
||
return cached;
|
||
}
|
||
|
||
// 2. Sinon, valider en ligne
|
||
try {
|
||
const response = await this.validateOnline();
|
||
await this.cache.set(response);
|
||
return response;
|
||
} catch (e) {
|
||
// 3. Mode hors-ligne : grace period de 7 jours
|
||
if (cached && this.withinGracePeriod(cached)) {
|
||
return { ...cached, offline: true };
|
||
}
|
||
return { valid: false, plan: 'free' };
|
||
}
|
||
}
|
||
|
||
private needsRevalidation(cached: LicenseCache): boolean {
|
||
// Revalider toutes les 72h
|
||
const lastCheck = new Date(cached.lastValidated);
|
||
const hoursSince = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60);
|
||
return hoursSince > 72;
|
||
}
|
||
|
||
private withinGracePeriod(cached: LicenseCache): boolean {
|
||
const lastCheck = new Date(cached.lastValidated);
|
||
const daysSince = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60 * 24);
|
||
return daysSince <= 7;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Sécurité
|
||
|
||
| Risque | Mitigation |
|
||
|--------|------------|
|
||
| Clé partagée | Limite d'activations (3 machines) |
|
||
| Crack local | Fonctionnalités premium côté validation, pas juste UI |
|
||
| Man-in-the-middle | HTTPS + signature des tokens |
|
||
| Reverse engineering | Obfuscation (limité mais aide) |
|
||
| Serveur down | Grace period 7 jours + mode dégradé |
|
||
|
||
**Note réaliste** : Aucune protection n'est parfaite pour une app desktop. L'objectif est de rendre le crack plus difficile que l'achat pour l'utilisateur moyen, pas d'être inviolable.
|
||
|
||
### Précision décimale (8 décimales)
|
||
|
||
Pour supporter les cryptomonnaies (Bitcoin = 8 décimales, certains tokens = plus), deux approches :
|
||
|
||
**Approche 1 : Stockage INTEGER (recommandé)**
|
||
|
||
```typescript
|
||
// Stocker les montants en plus petite unité (satoshis, cents × 10^6)
|
||
const DECIMAL_PLACES = 8;
|
||
const MULTIPLIER = 10 ** DECIMAL_PLACES; // 100_000_000
|
||
|
||
function toStorageAmount(displayAmount: number): bigint {
|
||
return BigInt(Math.round(displayAmount * MULTIPLIER));
|
||
}
|
||
|
||
function toDisplayAmount(storageAmount: bigint): number {
|
||
return Number(storageAmount) / MULTIPLIER;
|
||
}
|
||
|
||
// Exemple
|
||
const btcAmount = 0.00123456;
|
||
const stored = toStorageAmount(btcAmount); // 123456n
|
||
const display = toDisplayAmount(stored); // 0.00123456
|
||
```
|
||
|
||
Avantages :
|
||
- Pas de floating point errors (0.1 + 0.2 ≠ 0.3)
|
||
- Calculs exacts
|
||
- SQLite INTEGER = 64 bits signés = jusqu'à ~92 milliards en unités de base
|
||
|
||
**Approche 2 : TEXT avec Decimal.js**
|
||
|
||
```typescript
|
||
import Decimal from 'decimal.js';
|
||
|
||
Decimal.set({ precision: 20 });
|
||
|
||
const amount = new Decimal('0.12345678');
|
||
const total = amount.plus('0.00000001'); // Exact: 0.12345679
|
||
|
||
// Stockage en TEXT dans SQLite
|
||
db.run('INSERT INTO transactions (amount_text) VALUES (?)',
|
||
[amount.toString()]);
|
||
```
|
||
|
||
**Formatage d'affichage**
|
||
|
||
```typescript
|
||
function formatAmount(amount: number, currency: string): string {
|
||
const decimals = currency === 'BTC' ? 8
|
||
: currency === 'ETH' ? 8
|
||
: 2; // CAD, USD, EUR
|
||
|
||
return new Intl.NumberFormat('fr-CA', {
|
||
minimumFractionDigits: decimals > 2 ? 2 : decimals,
|
||
maximumFractionDigits: decimals,
|
||
}).format(amount);
|
||
}
|
||
|
||
// 0.00123456 BTC → "0,00123456"
|
||
// 1234.50 CAD → "1 234,50"
|
||
```
|
||
|
||
### Performance
|
||
|
||
```typescript
|
||
// Indexes recommandés pour queries fréquentes
|
||
const CRITICAL_QUERIES = {
|
||
// Dashboard: dépenses du mois courant par catégorie
|
||
monthlyByCategory: `
|
||
SELECT category_id, SUM(debit_amount) as total
|
||
FROM transactions
|
||
WHERE strftime('%Y-%m', transaction_date) = ?
|
||
GROUP BY category_id
|
||
`,
|
||
// Budget vs Réel
|
||
budgetComparison: `
|
||
SELECT
|
||
b.category_id,
|
||
b.planned_amount,
|
||
COALESCE(SUM(t.debit_amount), 0) as actual_amount
|
||
FROM budget_entries b
|
||
LEFT JOIN transactions t
|
||
ON t.category_id = b.category_id
|
||
AND strftime('%Y-%m', t.transaction_date) = b.month
|
||
WHERE b.month = ?
|
||
GROUP BY b.category_id
|
||
`
|
||
};
|
||
```
|
||
|
||
### Sécurité
|
||
|
||
| Risque | Mitigation |
|
||
|--------|------------|
|
||
| Données sensibles en clair | Chiffrement SQLite optionnel (SQLCipher) |
|
||
| Injection CSV | Sanitization des imports |
|
||
| Accès fichiers arbitraires | Sandbox des chemins d'import |
|
||
|
||
### Tests
|
||
|
||
```
|
||
tests/
|
||
├── unit/
|
||
│ ├── services/
|
||
│ │ ├── import.service.test.ts
|
||
│ │ └── categorization.service.test.ts
|
||
│ └── utils/
|
||
│ └── csv-parser.test.ts
|
||
├── integration/
|
||
│ ├── database.test.ts
|
||
│ └── import-flow.test.ts
|
||
└── e2e/
|
||
├── import-wizard.test.ts
|
||
└── reporting.test.ts
|
||
```
|
||
|
||
---
|
||
|
||
## Plan de développement suggéré
|
||
|
||
### Phase 1 - MVP (4-6 semaines)
|
||
|
||
**Objectif**: Import et visualisation basique
|
||
|
||
- [ ] Setup projet (Tauri + React/Svelte)
|
||
- [ ] Schéma BDD SQLite
|
||
- [ ] Import CSV basique (1 format fixe)
|
||
- [ ] Liste transactions
|
||
- [ ] Catégories par défaut
|
||
- [ ] Matching mots-clés simple
|
||
- [ ] Rapport mensuel simple (tableau)
|
||
|
||
### Phase 2 - Core (4-6 semaines)
|
||
|
||
**Objectif**: Fonctionnalités complètes freemium
|
||
|
||
- [ ] Configuration sources multiples
|
||
- [ ] Assistant mapping colonnes
|
||
- [ ] Gestion catégories/fournisseurs
|
||
- [ ] Interface transactions non-matchées
|
||
- [ ] Budget mensuel
|
||
- [ ] 3 rapports de base
|
||
- [ ] Graphiques simples
|
||
|
||
### Phase 3 - Premium (4-6 semaines)
|
||
|
||
**Objectif**: Différenciation payant
|
||
|
||
- [ ] Écritures d'ajustement
|
||
- [ ] Personnalisation catégories
|
||
- [ ] Rapports avancés
|
||
- [ ] Export PDF
|
||
- [ ] Graphiques interactifs
|
||
- [ ] Templates budget
|
||
|
||
### Phase 4 - Polish (2-4 semaines)
|
||
|
||
**Objectif**: Production-ready
|
||
|
||
- [ ] Tests complets
|
||
- [ ] Installateurs (Windows, macOS, Linux)
|
||
- [ ] Documentation utilisateur
|
||
- [ ] Système de licence
|
||
- [ ] Backup/restore
|
||
- [ ] Performance tuning
|
||
|
||
---
|
||
|
||
## Annexe: Catégories par défaut suggérées
|
||
|
||
```json
|
||
{
|
||
"categories": [
|
||
{
|
||
"name": "Logement",
|
||
"type": "recurring",
|
||
"subcategories": ["Loyer/Hypothèque", "Électricité", "Chauffage", "Internet", "Assurance habitation", "Taxes"]
|
||
},
|
||
{
|
||
"name": "Transport",
|
||
"type": "recurring",
|
||
"subcategories": ["Essence", "Transport en commun", "Assurance auto", "Entretien véhicule", "Stationnement"]
|
||
},
|
||
{
|
||
"name": "Alimentation",
|
||
"type": "recurring",
|
||
"subcategories": ["Épicerie", "Restaurant", "Livraison", "Café"]
|
||
},
|
||
{
|
||
"name": "Santé",
|
||
"type": "occasional",
|
||
"subcategories": ["Pharmacie", "Médecin", "Dentiste", "Optométriste", "Assurance santé"]
|
||
},
|
||
{
|
||
"name": "Loisirs",
|
||
"type": "occasional",
|
||
"subcategories": ["Sport", "Culture", "Streaming", "Jeux", "Hobbies"]
|
||
},
|
||
{
|
||
"name": "Shopping",
|
||
"type": "occasional",
|
||
"subcategories": ["Vêtements", "Électronique", "Maison", "Cadeaux"]
|
||
},
|
||
{
|
||
"name": "Services",
|
||
"type": "recurring",
|
||
"subcategories": ["Téléphone", "Abonnements", "Banque"]
|
||
},
|
||
{
|
||
"name": "Transferts",
|
||
"type": "transfer",
|
||
"subcategories": ["Entre comptes", "Épargne", "Investissement"]
|
||
},
|
||
{
|
||
"name": "Revenus",
|
||
"type": "income",
|
||
"subcategories": ["Salaire", "Freelance", "Remboursements", "Autres revenus"]
|
||
},
|
||
{
|
||
"name": "Autres dépenses",
|
||
"type": "occasional",
|
||
"subcategories": []
|
||
}
|
||
]
|
||
}
|
||
```
|