Initial commit

This commit is contained in:
Olivier CROGUENNEC
2026-06-13 14:57:15 +02:00
commit 48ed7fe65e
209 changed files with 49979 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
// Standalone DB initializer (run with: npm run db:init)
import 'dotenv/config';
import db from './index.js';
console.log('SQLite database initialized.');
console.log(`Tables: ${db
.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
.all()
.map(r => r.name)
.join(', ')}`);
db.close();
+253
View File
@@ -0,0 +1,253 @@
-- =====================================================================
-- Crowdlending Tracker - SQLite schema
-- =====================================================================
-- Conventions:
-- * Monetary amounts: REAL (cents-level precision OK for personal use)
-- * Dates: TEXT in ISO 8601 (YYYY-MM-DD) for stable sort & SQLite date()
-- * All tables use AUTOINCREMENT-free INTEGER PK (rowid alias)
-- * created_at/updated_at: TEXT ISO timestamp, set by app
-- =====================================================================
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
-- ---------------------------------------------------------------------
-- USERS (login accounts)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
display_name TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ---------------------------------------------------------------------
-- INVESTISSEURS (profils d'investissement sous un même login)
-- Ex.: "Monsieur", "Madame", "SCI Croguennec", "PEA-PME", ...
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS investisseurs (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
nom TEXT NOT NULL, -- nom complet (famille) ou raison sociale (entreprise)
prenom TEXT, -- prénom (famille uniquement)
type TEXT NOT NULL DEFAULT 'famille'
CHECK(type IN ('famille','entreprise')),
type_fiscal TEXT, -- 'PP', 'PM', 'SCI', 'SCPI'...
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, nom)
);
CREATE INDEX IF NOT EXISTS idx_investisseurs_user ON investisseurs(user_id);
-- ---------------------------------------------------------------------
-- PLATEFORMES (ClubFunding, October, Lendix, La Première Brique, ...)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS plateformes (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
nom TEXT NOT NULL,
url TEXT,
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
domiciliation TEXT NOT NULL DEFAULT 'france',
fiscalite TEXT NOT NULL DEFAULT 'flat_tax',
taux_fiscalite_locale REAL,
methode_remboursement TEXT NOT NULL DEFAULT 'portefeuille',
investisseur_id INTEGER REFERENCES investisseurs(id) ON DELETE SET NULL,
date_ouverture TEXT,
logo_filename TEXT,
UNIQUE(user_id, nom, investisseur_id)
);
CREATE INDEX IF NOT EXISTS idx_plateformes_user ON plateformes(user_id);
-- ---------------------------------------------------------------------
-- DEPOTS_RETRAITS (mouvements de cash sur les plateformes)
-- type: 'depot' (versement) | 'retrait' (retrait)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS depots_retraits (
id INTEGER PRIMARY KEY,
investisseur_id INTEGER NOT NULL REFERENCES investisseurs(id) ON DELETE CASCADE,
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE RESTRICT,
date_operation TEXT NOT NULL, -- ISO YYYY-MM-DD
type TEXT NOT NULL CHECK(type IN ('depot','retrait')),
montant REAL NOT NULL CHECK(montant >= 0),
libelle TEXT,
reference TEXT, -- ref bancaire / plateforme
source TEXT NOT NULL DEFAULT 'manuel', -- 'manuel' | 'import_excel'
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_depret_inv ON depots_retraits(investisseur_id);
CREATE INDEX IF NOT EXISTS idx_depret_plat ON depots_retraits(plateforme_id);
CREATE INDEX IF NOT EXISTS idx_depret_date ON depots_retraits(date_operation);
-- ---------------------------------------------------------------------
-- INVESTISSEMENTS (CF Investissements - liste des projets souscrits)
-- statut: 'en_cours' | 'rembourse' | 'en_retard' | 'procedure' | 'cloture'
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS investissements (
id INTEGER PRIMARY KEY,
investisseur_id INTEGER NOT NULL REFERENCES investisseurs(id) ON DELETE CASCADE,
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE RESTRICT,
nom_projet TEXT NOT NULL,
emetteur TEXT, -- nom de la société emprunteuse
date_souscription TEXT NOT NULL,
date_premiere_echeance TEXT, -- date de la 1ère échéance (intérêts / remboursement)
date_cible TEXT, -- date contractuelle du dernier versement (calculée)
montant_investi REAL NOT NULL CHECK(montant_investi > 0),
taux_interet REAL, -- en % annuel (ex. 9.5)
duree_mois INTEGER,
type_remb TEXT, -- 'in_fine' | 'amortissable' | 'differe'
freq_interets TEXT NOT NULL DEFAULT 'mensuel', -- 'mensuel' | 'trimestriel' | 'in_fine'
statut TEXT NOT NULL DEFAULT 'en_cours'
CHECK(statut IN ('en_cours','rembourse','en_retard','procedure','cloture')),
reference TEXT, -- ID projet sur la plateforme
source TEXT NOT NULL DEFAULT 'manuel',
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_inv_inv ON investissements(investisseur_id);
CREATE INDEX IF NOT EXISTS idx_inv_plat ON investissements(plateforme_id);
CREATE INDEX IF NOT EXISTS idx_inv_statut ON investissements(statut);
CREATE INDEX IF NOT EXISTS idx_inv_date ON investissements(date_souscription);
-- ---------------------------------------------------------------------
-- REMBOURSEMENTS (échéances perçues, réelles)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS remboursements (
id INTEGER PRIMARY KEY,
investissement_id INTEGER NOT NULL REFERENCES investissements(id) ON DELETE CASCADE,
date_remb TEXT NOT NULL,
capital REAL NOT NULL DEFAULT 0,
interets_bruts REAL NOT NULL DEFAULT 0, -- intérêts AVANT prélèvements
prelev_sociaux REAL NOT NULL DEFAULT 0, -- 17.2 % typiquement
prelev_forfaitaire REAL NOT NULL DEFAULT 0, -- 12.8 % (PFU IR)
cashback REAL NOT NULL DEFAULT 0, -- remboursement non taxé (bonus plateforme, etc.)
interets_nets REAL NOT NULL DEFAULT 0, -- interets_bruts - prelev_sociaux - prelev_forfaitaire
net_recu REAL NOT NULL DEFAULT 0, -- capital + cashback + interets_nets
statut TEXT NOT NULL DEFAULT 'paye'
CHECK(statut IN ('paye','retard','partiel','impaye')),
source TEXT NOT NULL DEFAULT 'manuel',
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_remb_inv ON remboursements(investissement_id);
CREATE INDEX IF NOT EXISTS idx_remb_date ON remboursements(date_remb);
CREATE INDEX IF NOT EXISTS idx_remb_statut ON remboursements(statut);
-- ---------------------------------------------------------------------
-- SIMUL_REMBOURSEMENTS (échéances prévisionnelles / théoriques)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS simul_remboursements (
id INTEGER PRIMARY KEY,
investissement_id INTEGER NOT NULL REFERENCES investissements(id) ON DELETE CASCADE,
numero_echeance INTEGER NOT NULL,
date_prevue TEXT NOT NULL,
capital_prevu REAL NOT NULL DEFAULT 0,
interets_prevus REAL NOT NULL DEFAULT 0,
total_prevu REAL NOT NULL DEFAULT 0,
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(investissement_id, numero_echeance)
);
CREATE INDEX IF NOT EXISTS idx_simul_inv ON simul_remboursements(investissement_id);
CREATE INDEX IF NOT EXISTS idx_simul_date ON simul_remboursements(date_prevue);
-- ---------------------------------------------------------------------
-- IMPORTS (historique des imports Excel pour traçabilité)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS imports (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
investisseur_id INTEGER REFERENCES investisseurs(id) ON DELETE SET NULL,
module TEXT NOT NULL, -- 'depots_retraits' | 'investissements' | ...
filename TEXT NOT NULL,
rows_total INTEGER NOT NULL DEFAULT 0,
rows_inserted INTEGER NOT NULL DEFAULT 0,
rows_skipped INTEGER NOT NULL DEFAULT 0,
mapping_json TEXT, -- JSON: mappage colonnes
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_imports_user ON imports(user_id);
-- ---------------------------------------------------------------------
-- CATEGORIES_PLATEFORME (tags libres associés à une plateforme)
-- Semées par défaut à la première utilisation (voir route /categories)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS categories_plateforme (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
nom TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, nom)
);
CREATE INDEX IF NOT EXISTS idx_catplat_user ON categories_plateforme(user_id);
-- Junction : une plateforme peut avoir plusieurs catégories
CREATE TABLE IF NOT EXISTS plateforme_categories (
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE CASCADE,
categorie_id INTEGER NOT NULL REFERENCES categories_plateforme(id) ON DELETE CASCADE,
PRIMARY KEY(plateforme_id, categorie_id)
);
-- ---------------------------------------------------------------------
-- TAUX_PFU (historique Flat Tax France — table de référence globale)
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS taux_pfu (
id INTEGER PRIMARY KEY,
annee INTEGER NOT NULL UNIQUE,
pfu_total REAL NOT NULL, -- ex. 30.0
impot_revenu REAL NOT NULL, -- ex. 12.8
prelev_sociaux REAL NOT NULL, -- ex. 17.2
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_pfu_annee ON taux_pfu(annee);
-- ---------------------------------------------------------------------
-- VUES utiles
-- ---------------------------------------------------------------------
-- Solde courant par (investisseur, plateforme)
CREATE VIEW IF NOT EXISTS v_solde_plateforme AS
SELECT
dr.investisseur_id,
dr.plateforme_id,
SUM(CASE WHEN dr.type='depot' THEN dr.montant ELSE 0 END) AS total_depots,
SUM(CASE WHEN dr.type='retrait' THEN dr.montant ELSE 0 END) AS total_retraits,
SUM(CASE WHEN dr.type='depot' THEN dr.montant
WHEN dr.type='retrait' THEN -dr.montant END) AS solde_net
FROM depots_retraits dr
GROUP BY dr.investisseur_id, dr.plateforme_id;
-- Synthèse investissements par investisseur
CREATE VIEW IF NOT EXISTS v_synthese_inv AS
SELECT
i.investisseur_id,
COUNT(*) AS nb_projets,
SUM(i.montant_investi) AS total_investi,
SUM(CASE WHEN i.statut='en_cours' THEN i.montant_investi ELSE 0 END) AS encours,
SUM(CASE WHEN i.statut='rembourse' THEN i.montant_investi ELSE 0 END) AS rembourse,
SUM(CASE WHEN i.statut IN ('en_retard','procedure') THEN i.montant_investi ELSE 0 END) AS en_defaut
FROM investissements i
GROUP BY i.investisseur_id;
-- Intérêts perçus par année (pour le 2778-SD)
CREATE VIEW IF NOT EXISTS v_interets_annuels AS
SELECT
i.investisseur_id,
substr(r.date_remb,1,4) AS annee,
SUM(r.interets_bruts) AS interets_bruts,
SUM(r.prelev_sociaux) AS prelev_sociaux,
SUM(r.prelev_forfaitaire) AS prelev_forfaitaire,
SUM(r.net_recu) AS net_recu
FROM remboursements r
JOIN investissements i ON i.id = r.investissement_id
WHERE r.statut IN ('paye','partiel')
GROUP BY i.investisseur_id, substr(r.date_remb,1,4);