-- ===================================================================== -- 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 CASCADE, 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' | 'perte_definitive' | 'prolongation' -- --------------------------------------------------------------------- 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 CASCADE, 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','perte_definitive','prolongation')), 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);