fix: checklist step 5, secondary-profile migrations and Linux database paths (#390) #394

Merged
maximus merged 5 commits from issue-390-manual-flag-profile-migrations into main 2026-10-07 02:00:03 +00:00
16 changed files with 5447 additions and 57 deletions
Showing only changes of commit b0206ae56b - Show all commits

View file

@ -66,6 +66,9 @@ hmac = "0.12"
ed25519-dalek = { version = "2", features = ["pkcs8", "rand_core"] } ed25519-dalek = { version = "2", features = ["pkcs8", "rand_core"] }
# HTTP mock server for balance_commands fetch_price tests (Issue #155). # HTTP mock server for balance_commands fetch_price tests (Issue #155).
mockito = "1.6" mockito = "1.6"
# MockRuntime for the profile database folder test (#390). The `test`
# feature adds no crate (`test = []` in tauri's manifest).
tauri = { version = "2", features = ["test"] }
[features] [features]
# Dev-only escape hatch: when enabled, the SR_DEV_EDITION env var forces the # Dev-only escape hatch: when enabled, the SR_DEV_EDITION env var forces the

View file

@ -9,8 +9,9 @@
//! `Authorization`, `Accept`, and `User-Agent` headers. //! `Authorization`, `Accept`, and `User-Agent` headers.
//! //!
//! Database access pattern: //! Database access pattern:
//! - All reads use `rusqlite::Connection::open(app_data_dir / db_filename)`, //! - All reads open the file `profile_db_path` resolves (`profile_commands.rs`):
//! matching the existing `repair_migrations` helper in `profile_commands.rs`. //! the folder where tauri-plugin-sql keeps the databases, which is NOT
//! `app_data_dir` on Linux (#390).
//! - The frontend passes `db_filename` (the active profile DB), exactly //! - The frontend passes `db_filename` (the active profile DB), exactly
//! like it does for `repair_migrations` and `delete_profile_db`. Keeps //! like it does for `repair_migrations` and `delete_profile_db`. Keeps
//! the active-profile resolution where it already lives (in TS) and //! the active-profile resolution where it already lives (in TS) and
@ -258,16 +259,10 @@ pub fn compute_account_return(
let start_date = parse_iso_date(&period_start, "period_start")?; let start_date = parse_iso_date(&period_start, "period_start")?;
let end_date = parse_iso_date(&period_end, "period_end")?; let end_date = parse_iso_date(&period_end, "period_end")?;
let app_dir = app let db_path = super::profile_commands::profile_db_path(&app, &db_filename)?;
.path()
.app_data_dir()
.map_err(|e| format!("Cannot get app data dir: {}", e))?;
let db_path = app_dir.join(&db_filename);
if !db_path.exists() { if !db_path.exists() {
return Err(format!( // The file name only: the folder holds the OS user name.
"Profile database not found: {}", return Err(format!("Profile database not found: {}", db_filename));
db_path.display()
));
} }
let conn = Connection::open(&db_path) let conn = Connection::open(&db_path)

View file

@ -3,10 +3,12 @@ use rand::RngCore;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256, Sha384}; use sha2::{Digest, Sha256, Sha384};
use std::fs; use std::fs;
use std::path::{Path, PathBuf};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use tauri::Manager; use tauri::Manager;
use crate::database; use crate::database;
use crate::database::profile_db;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Profile { pub struct Profile {
@ -32,6 +34,49 @@ fn get_profiles_path(app: &tauri::AppHandle) -> Result<std::path::PathBuf, Strin
Ok(app_dir.join("profiles.json")) Ok(app_dir.join("profiles.json"))
} }
/// Folder where tauri-plugin-sql keeps every profile database.
///
/// The plugin opens `sqlite:<file>` under `app_config_dir()` (tauri-plugin-sql
/// 2.3.2, `wrapper.rs`: `DbPool::connect` then `path_mapper`), not under
/// `app_data_dir()`, where `profiles.json` and the licence files live. Both
/// are the same folder on Windows and macOS, not on Linux (`~/.config/<id>`
/// and `~/.local/share/<id>`): until #390, every command that looked for a
/// profile database under `app_data_dir()` found nothing on Linux.
pub(crate) fn profile_db_dir<R: tauri::Runtime>(app: &tauri::AppHandle<R>) -> Result<PathBuf, String> {
app.path()
.app_config_dir()
.map_err(|e| format!("Cannot get app config dir: {}", e))
}
/// Path of a profile database, from the bare file name the webview passes.
pub(crate) fn profile_db_path<R: tauri::Runtime>(
app: &tauri::AppHandle<R>,
db_filename: &str,
) -> Result<PathBuf, String> {
profile_db_path_in(&profile_db_dir(app)?, db_filename)
}
/// Accept only the two shapes the app ever gives a profile database:
/// `simpl_resultat.db` and `profile_<hex>.db` (ProfileContext, since v0.3.0).
/// `Path::join` with an absolute value would replace the folder, and these
/// commands write and delete.
pub(crate) fn profile_db_path_in(dir: &Path, db_filename: &str) -> Result<PathBuf, String> {
if !is_profile_db_filename(db_filename) {
return Err("invalid_profile_db_filename".to_string());
}
Ok(dir.join(db_filename))
}
fn is_profile_db_filename(name: &str) -> bool {
if name == database::DEFAULT_PROFILE_DB {
return true;
}
match name.strip_prefix("profile_").and_then(|rest| rest.strip_suffix(".db")) {
Some(id) => !id.is_empty() && id.bytes().all(|b| b.is_ascii_hexdigit()),
None => false,
}
}
fn make_default_config() -> ProfilesConfig { fn make_default_config() -> ProfilesConfig {
let now = chrono_now(); let now = chrono_now();
let default_id = "default".to_string(); let default_id = "default".to_string();
@ -92,35 +137,63 @@ pub fn save_profiles(app: tauri::AppHandle, config: ProfilesConfig) -> Result<()
fs::write(&path, json).map_err(|e| format!("Cannot write profiles.json: {}", e)) fs::write(&path, json).map_err(|e| format!("Cannot write profiles.json: {}", e))
} }
/// Delete a secondary profile's database, with its SQLite side files and the
/// copies a catch-up took (#390): the guide promises that deleting a profile
/// deletes all its data.
#[tauri::command] #[tauri::command]
pub fn delete_profile_db(app: tauri::AppHandle, db_filename: String) -> Result<(), String> { pub fn delete_profile_db(app: tauri::AppHandle, db_filename: String) -> Result<(), String> {
if db_filename == "simpl_resultat.db" { if db_filename == database::DEFAULT_PROFILE_DB {
return Err("Cannot delete the default profile database".to_string()); return Err("Cannot delete the default profile database".to_string());
} }
let db_path = profile_db_path(&app, &db_filename)?;
delete_profile_files(&db_path, &db_filename)
}
let app_dir = app fn delete_profile_files(db_path: &Path, db_filename: &str) -> Result<(), String> {
.path() for suffix in ["", "-wal", "-shm", "-journal"] {
.app_data_dir() let path = db_path.with_file_name(format!("{}{}", db_filename, suffix));
.map_err(|e| format!("Cannot get app data dir: {}", e))?; if path.exists() {
let db_path = app_dir.join(&db_filename); fs::remove_file(&path).map_err(|e| format!("Cannot delete database file: {}", e))?;
}
if db_path.exists() { }
fs::remove_file(&db_path) if let Some(dir) = db_path.parent() {
.map_err(|e| format!("Cannot delete database file: {}", e))?; profile_db::remove_backups(
&dir.join(profile_db::MIGRATION_BACKUP_DIR),
db_filename.trim_end_matches(".db"),
)?;
} }
Ok(()) Ok(())
} }
/// SQL that creates a secondary profile, run by the webview through the
/// plugin (`initializeNewProfileDb`).
#[tauri::command] #[tauri::command]
pub fn get_new_profile_init_sql() -> Result<Vec<String>, String> { pub fn get_new_profile_init_sql() -> Result<Vec<String>, String> {
// Brand-new profiles ship with the v1 IPC-aligned category taxonomy: the // The consolidated schema is the whole database: tauri-plugin-sql runs no
// consolidated schema bakes the v1 seed (categories + keywords + the // migration for a `profile_<id>.db`, only the default profile's URL is
// categories_schema_version='v1' preference) directly. The legacy v2 seed // registered (lib.rs). It carries the v1 IPC-aligned taxonomy (categories,
// migration still runs first because tauri-plugin-sql applies every // keywords, categories_schema_version='v1'). The second statement writes
// declared migration on `Database.load`; the consolidated script then // the complete migration history, so later versions reach the profile
// deletes the v2 rows and re-inserts the v1 rows in the 1000+ id range. // through `prepare_profile_db` without inferring anything (#390).
Ok(vec![database::CONSOLIDATED_SCHEMA.to_string()]) Ok(vec![
database::CONSOLIDATED_SCHEMA.to_string(),
profile_db::history_sql(&crate::migrations()),
])
}
/// Bring a profile database up to date and repair legacy values before the
/// webview opens it (#390, ADR 0021). Secondary profiles catch up on the
/// migrations they missed; every profile gets its text manual-categorization
/// flags repaired. Sync on purpose: Tauri runs sync commands on the main
/// thread, which serializes two opens of the same profile (React StrictMode
/// mounts twice in development).
#[tauri::command]
pub fn prepare_profile_db(
app: tauri::AppHandle,
db_filename: String,
) -> Result<profile_db::ProfilePreparation, String> {
let db_path = profile_db_path(&app, &db_filename)?;
profile_db::prepare_at(&db_path, &db_filename, &crate::migrations())
} }
// Argon2id parameters for PIN hashing (same as export_import_commands.rs) // Argon2id parameters for PIN hashing (same as export_import_commands.rs)
@ -225,11 +298,7 @@ fn hex_decode(hex: &str) -> Result<Vec<u8>, String> {
/// of destructive migrations (e.g., migration 2 which DELETEs categories/keywords). /// of destructive migrations (e.g., migration 2 which DELETEs categories/keywords).
#[tauri::command] #[tauri::command]
pub fn repair_migrations(app: tauri::AppHandle, db_filename: String) -> Result<bool, String> { pub fn repair_migrations(app: tauri::AppHandle, db_filename: String) -> Result<bool, String> {
let app_dir = app let db_path = profile_db_path(&app, &db_filename)?;
.path()
.app_data_dir()
.map_err(|e| format!("Cannot get app data dir: {}", e))?;
let db_path = app_dir.join(&db_filename);
if !db_path.exists() { if !db_path.exists() {
return Ok(false); return Ok(false);
@ -375,4 +444,90 @@ mod tests {
let decoded = hex_decode(&encoded).unwrap(); let decoded = hex_decode(&encoded).unwrap();
assert_eq!(original, decoded); assert_eq!(original, decoded);
} }
// -- profile database location (#390) ------------------------------------
#[test]
fn profile_databases_live_where_the_sql_plugin_opens_them() {
// tauri-plugin-sql resolves `sqlite:<file>` under app_config_dir().
// On Linux (the CI platform) that differs from app_data_dir(), so
// going back to the data folder turns this red.
let app = tauri::test::mock_app();
let path = profile_db_path(app.handle(), "profile_0a1b2c3d.db").unwrap();
assert_eq!(path.parent(), Some(app.path().app_config_dir().unwrap().as_path()));
}
#[test]
fn profile_db_path_accepts_the_two_shapes_the_app_uses() {
let dir = Path::new("/profiles");
assert_eq!(
profile_db_path_in(dir, "simpl_resultat.db"),
Ok(dir.join("simpl_resultat.db"))
);
assert_eq!(
profile_db_path_in(dir, "profile_0a1b2c3d.db"),
Ok(dir.join("profile_0a1b2c3d.db"))
);
}
#[test]
fn profile_db_path_refuses_anything_else() {
let dir = Path::new("/profiles");
for name in [
"",
"/etc/passwd",
"../simpl_resultat.db",
"profile_.db",
"profile_0a1b/../x.db",
"profile_0a1b2c3d.sqlite",
"profile_0a1b2c3d.db.bak",
"PROFILE_0a1b2c3d.db",
"profile_xyz.db",
"C:\\Users\\x\\profile_0a1b2c3d.db",
] {
assert_eq!(
profile_db_path_in(dir, name),
Err("invalid_profile_db_filename".to_string()),
"{:?}",
name
);
}
}
#[test]
fn deleting_a_profile_removes_its_files_and_only_its_files() {
let dir = std::env::temp_dir().join(format!("simpl-resultat-delete-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
let backups = dir.join(profile_db::MIGRATION_BACKUP_DIR);
fs::create_dir_all(&backups).unwrap();
for name in [
"profile_0a1b2c3d.db",
"profile_0a1b2c3d.db-wal",
"profile_0a1b2c3d.db-shm",
"profile_99999999.db",
] {
fs::write(dir.join(name), b"x").unwrap();
}
for name in ["profile_0a1b2c3d-before-v7.db", "profile_99999999-before-v17.db"] {
fs::write(backups.join(name), b"x").unwrap();
}
delete_profile_files(&dir.join("profile_0a1b2c3d.db"), "profile_0a1b2c3d.db").unwrap();
let mut left: Vec<String> = fs::read_dir(&dir)
.unwrap()
.chain(fs::read_dir(&backups).unwrap())
.map(|e| e.unwrap().file_name().into_string().unwrap())
.collect();
left.sort();
assert_eq!(
left,
vec![
"migration-backups".to_string(),
"profile_99999999-before-v17.db".to_string(),
"profile_99999999.db".to_string(),
]
);
let _ = fs::remove_dir_all(&dir);
}
} }

View file

@ -15,12 +15,11 @@ CREATE TABLE IF NOT EXISTS import_sources (
column_mapping TEXT NOT NULL, column_mapping TEXT NOT NULL,
skip_lines INTEGER NOT NULL DEFAULT 0, skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1, has_header INTEGER NOT NULL DEFAULT 1,
-- Full import format (migration v17). These four columns are INERT on the -- Full import format (migration v17). A secondary profile receives these
-- production path: this script runs AFTER tauri-plugin-sql has applied every -- four columns from this script, not from v17: tauri-plugin-sql runs no
-- migration and only uses CREATE TABLE IF NOT EXISTS, so a brand-new profile -- migration on a `profile_<id>.db` (#390, ADR 0021). A parity test compares
-- actually receives them from v17. They are mirrored here to keep this file -- this definition with the v1->v17 chain so the DEFAULT and CHECK of the
-- the tested reference definition -- a parity test compares it against the -- two cannot drift apart.
-- v1->v17 chain so the DEFAULT and CHECK of the two cannot drift apart.
-- `amount_mode` admits 'absolute_indicator' from the start so the third mode -- `amount_mode` admits 'absolute_indicator' from the start so the third mode
-- can ship without another migration. `template_id` is a provenance tag -- can ship without another migration. `template_id` is a provenance tag
-- only, never re-read as format: the eight format columns above are -- only, never re-read as format: the eight format columns above are
@ -374,9 +373,9 @@ INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('balance_starter_pro
-- ============================================================================ -- ============================================================================
-- Seed v1 — IPC Statistique Canada-aligned, 3 levels, Canada/Québec -- Seed v1 — IPC Statistique Canada-aligned, 3 levels, Canada/Québec
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- Reset any pre-existing category data (possible when tauri-plugin-sql runs -- Reset any pre-existing category data. Defensive only: tauri-plugin-sql runs
-- the historical v2 seed migration on this fresh DB before this script -- no migration on a secondary profile's database, so no v2 seed precedes this
-- executes). Keywords/categories are wiped and re-inserted with the v1 IDs -- script (#390). Keywords/categories are wiped and re-inserted with the v1 IDs
-- (1000+ range) so existing references in migrations v3-v7 stay inert. -- (1000+ range) so existing references in migrations v3-v7 stay inert.
-- ============================================================================ -- ============================================================================
DELETE FROM keywords; DELETE FROM keywords;

View file

@ -0,0 +1,824 @@
-- Consolidated schema for new profile databases
-- This file bakes in the base schema + all migrations (v3-v8) and pre-seeds
-- the v1 IPC-aligned category taxonomy so that brand-new profiles immediately
-- use the new standard. Existing profiles keep their v2 seed and are only
-- tagged via the migration (categories_schema_version = 'v2').
-- Used ONLY for initializing new profile databases (not for the default profile).
CREATE TABLE IF NOT EXISTS import_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
date_format TEXT NOT NULL DEFAULT '%d/%m/%Y',
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
column_mapping TEXT NOT NULL,
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS imported_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER NOT NULL,
filename TEXT NOT NULL,
file_hash TEXT NOT NULL,
import_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
row_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'completed',
notes TEXT,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE CASCADE,
UNIQUE(source_id, filename)
);
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
parent_id INTEGER,
color TEXT,
icon TEXT,
type TEXT NOT NULL DEFAULT 'expense',
is_active INTEGER NOT NULL DEFAULT 1,
is_inputable INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
i18n_key TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
normalized_name TEXT NOT NULL,
category_id INTEGER,
is_active INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
category_id INTEGER NOT NULL,
supplier_id INTEGER,
priority INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
UNIQUE(keyword, category_id)
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
description TEXT NOT NULL,
amount REAL NOT NULL,
category_id INTEGER,
supplier_id INTEGER,
source_id INTEGER,
file_id INTEGER,
original_description TEXT,
notes TEXT,
is_manually_categorized INTEGER NOT NULL DEFAULT 0,
is_split INTEGER NOT NULL DEFAULT 0,
parent_transaction_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE SET NULL,
FOREIGN KEY (file_id) REFERENCES imported_files(id) ON DELETE SET NULL,
FOREIGN KEY (parent_transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS adjustments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
date DATE NOT NULL,
is_recurring INTEGER NOT NULL DEFAULT 0,
recurrence_rule TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS adjustment_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
adjustment_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
description TEXT,
FOREIGN KEY (adjustment_id) REFERENCES adjustments(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS budget_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
amount REAL NOT NULL,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(category_id, year, month)
);
CREATE TABLE IF NOT EXISTS budget_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS budget_template_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
template_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (template_id) REFERENCES budget_templates(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(template_id, category_id)
);
CREATE TABLE IF NOT EXISTS import_config_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
column_mapping TEXT NOT NULL,
amount_mode TEXT NOT NULL DEFAULT 'single',
sign_convention TEXT NOT NULL DEFAULT 'negative_expense',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_preferences (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(date);
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id);
CREATE INDEX IF NOT EXISTS idx_transactions_supplier ON transactions(supplier_id);
CREATE INDEX IF NOT EXISTS idx_transactions_source ON transactions(source_id);
CREATE INDEX IF NOT EXISTS idx_transactions_file ON transactions(file_id);
CREATE INDEX IF NOT EXISTS idx_transactions_parent ON transactions(parent_transaction_id);
CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_categories_type ON categories(type);
CREATE INDEX IF NOT EXISTS idx_suppliers_category ON suppliers(category_id);
CREATE INDEX IF NOT EXISTS idx_suppliers_normalized ON suppliers(normalized_name);
CREATE INDEX IF NOT EXISTS idx_keywords_category ON keywords(category_id);
CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON keywords(keyword);
CREATE INDEX IF NOT EXISTS idx_budget_entries_period ON budget_entries(year, month);
CREATE INDEX IF NOT EXISTS idx_adjustment_entries_adjustment ON adjustment_entries(adjustment_id);
CREATE INDEX IF NOT EXISTS idx_imported_files_source ON imported_files(source_id);
-- ============================================================================
-- Balance sheet (Bilan) — Migration v9 mirror for new profiles
-- ============================================================================
-- 5 tables + 7 indexes + seeded categories. Kept in sync with
-- `balance_schema.sql` (the source of truth applied by Migration v9 in lib.rs).
-- New profiles created from this consolidated schema get the balance feature
-- preinstalled without needing to replay v9 separately.
CREATE TABLE IF NOT EXISTS balance_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE,
i18n_key TEXT NOT NULL,
kind TEXT NOT NULL CHECK(kind IN ('simple','priced')),
sort_order INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
is_seed INTEGER NOT NULL DEFAULT 0,
asset_type TEXT CHECK(asset_type IS NULL OR asset_type IN ('stock','crypto')),
-- User-facing override for the category label (migration v12). When set,
-- the UI shows this verbatim; otherwise it falls back to t(i18n_key).
custom_label TEXT
);
CREATE TABLE IF NOT EXISTS balance_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
balance_category_id INTEGER NOT NULL,
name TEXT NOT NULL,
symbol TEXT,
currency TEXT NOT NULL DEFAULT 'CAD' CHECK(currency = 'CAD'),
notes TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
archived_at DATETIME,
-- Fiscal envelope / tax shelter (migration v12). NULL = no envelope (e.g.
-- a chequing account or crypto wallet). NOT an automobile type.
vehicle_type TEXT CHECK(vehicle_type IS NULL OR vehicle_type IN ('unregistered','tfsa','rrsp','rrif','fhsa','resp')),
-- Entry mode (migration v15). 'simple' = one denormalized value per
-- snapshot line; 'detailed' = a basket of per-security holdings (see
-- balance_snapshot_holdings). Accounts under a priced category are backfilled
-- to 'detailed' below. `detailed_since` is the authoritative pivot date from
-- which detailed entry is expected — NULL until the conversion flow sets it.
kind TEXT NOT NULL DEFAULT 'simple' CHECK (kind IN ('simple','detailed')),
detailed_since DATE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (balance_category_id) REFERENCES balance_categories(id) ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS balance_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_date DATE NOT NULL UNIQUE,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS balance_snapshot_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_id INTEGER NOT NULL,
account_id INTEGER NOT NULL,
quantity REAL,
unit_price REAL,
value REAL NOT NULL,
price_source TEXT,
price_fetched_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (snapshot_id) REFERENCES balance_snapshots(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE RESTRICT,
UNIQUE(snapshot_id, account_id),
CHECK (
(quantity IS NULL AND unit_price IS NULL)
OR (quantity IS NOT NULL AND unit_price IS NOT NULL)
)
);
CREATE TABLE IF NOT EXISTS balance_account_transfers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
transaction_id INTEGER NOT NULL,
direction TEXT NOT NULL CHECK(direction IN ('in','out')),
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE CASCADE,
FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE RESTRICT,
UNIQUE(transaction_id, account_id)
);
-- Détail par titre (Bilan Étape 2, migration v14). Kept in sync with
-- `balance_holdings_schema.sql` (the source of truth applied by Migration v14
-- in lib.rs). balance_securities is the instrument catalogue (symbol normalized
-- upper/trim, COLLATE NOCASE UNIQUE); balance_snapshot_holdings is the per-title
-- breakdown of a detailed account's snapshot line.
CREATE TABLE IF NOT EXISTS balance_securities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL COLLATE NOCASE UNIQUE,
name TEXT,
currency TEXT NOT NULL DEFAULT 'CAD',
asset_type TEXT NOT NULL CHECK (asset_type IN ('stock','crypto')),
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS balance_snapshot_holdings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_line_id INTEGER NOT NULL REFERENCES balance_snapshot_lines(id) ON DELETE CASCADE,
security_id INTEGER NOT NULL REFERENCES balance_securities(id) ON DELETE RESTRICT,
quantity REAL NOT NULL,
unit_price REAL NOT NULL,
value REAL NOT NULL,
book_cost REAL,
price_source TEXT,
price_fetched_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (snapshot_line_id, security_id)
);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_category ON balance_accounts(balance_category_id);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_active ON balance_accounts(is_active) WHERE is_active = 1;
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_snapshot ON balance_snapshot_lines(snapshot_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_account ON balance_snapshot_lines(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_account ON balance_account_transfers(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_transaction ON balance_account_transfers(transaction_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshots_date ON balance_snapshots(snapshot_date);
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_holdings_line ON balance_snapshot_holdings(snapshot_line_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_holdings_security ON balance_snapshot_holdings(security_id);
-- Asset classes only (Bilan axe véhicule, Étape 1). The former `tfsa` / `rrsp`
-- seeds were fiscal envelopes, not asset classes — they now live on the account
-- as `vehicle_type` (see balance_accounts.vehicle_type). New profiles ship with
-- exactly 5 asset classes: Liquidités, Fonds/FNB, Actions, Crypto, Autres.
INSERT OR IGNORE INTO balance_categories (key, i18n_key, kind, sort_order, is_seed, asset_type) VALUES
('cash', 'balance.category.cash', 'simple', 10, 1, NULL),
('fund', 'balance.category.fund', 'simple', 40, 1, NULL),
('other', 'balance.category.other', 'simple', 50, 1, NULL),
('stock', 'balance.category.stock', 'priced', 60, 1, 'stock'),
('crypto', 'balance.category.crypto', 'priced', 70, 1, 'crypto');
-- Starter accounts (Issue #179): 4 plain accounts seeded for new profiles so
-- /balance lands non-empty. They are NOT marked as seed (no is_seed column on
-- balance_accounts) — once created they are indistinguishable from
-- user-created accounts and can be renamed/archived freely. Existing profiles
-- get the same 4 proposed via StarterAccountsModal on first /balance visit.
--
-- Bilan axe véhicule (Étape 1): the CELI / REER starters are no longer linked
-- to a `tfsa` / `rrsp` category (those seeds are gone). They now attach to the
-- `other` asset class and carry the envelope in `vehicle_type`. Linking them to
-- a deleted key would make the subselect return NULL → NOT NULL violation →
-- broken new-profile init, so the asset class MUST resolve to `other`.
INSERT INTO balance_accounts (balance_category_id, name, currency, is_active, vehicle_type) VALUES
((SELECT id FROM balance_categories WHERE key = 'cash'), 'Compte chèque', 'CAD', 1, NULL),
((SELECT id FROM balance_categories WHERE key = 'other'), 'CELI', 'CAD', 1, 'tfsa'),
((SELECT id FROM balance_categories WHERE key = 'other'), 'REER', 'CAD', 1, 'rrsp'),
((SELECT id FROM balance_categories WHERE key = 'other'), 'Compte non-enregistré', 'CAD', 1, NULL);
-- Détail par titre (migration v15 mirror): accounts under a priced category are
-- detailed-entry by default. The 4 starters above all sit under simple asset
-- classes (cash / other), so this is currently a no-op — kept at parity with the
-- v15 backfill so any future priced starter is stamped correctly.
UPDATE balance_accounts SET kind = 'detailed'
WHERE balance_category_id IN (SELECT id FROM balance_categories WHERE kind = 'priced');
-- Default preferences (new profiles ship with the v1 IPC taxonomy)
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('language', 'fr');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('theme', 'light');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('currency', 'EUR');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('date_format', 'DD/MM/YYYY');
INSERT OR REPLACE INTO user_preferences (key, value) VALUES ('categories_schema_version', 'v1');
-- Suppress StarterAccountsModal on first /balance visit for new profiles
-- (Issue #179). The 4 starter accounts are already seeded above, so the
-- modal would only show 4 collision rows with no actionable choice. Pre-
-- writing the pref skips that briefly-empty UX entirely. Suggestion S1
-- from PR #185 review (#187).
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('balance_starter_proposed', '{"shown_at":"seed","accepted":[]}');
-- ============================================================================
-- Seed v1 — IPC Statistique Canada-aligned, 3 levels, Canada/Québec
-- ----------------------------------------------------------------------------
-- Reset any pre-existing category data (possible when tauri-plugin-sql runs
-- the historical v2 seed migration on this fresh DB before this script
-- executes). Keywords/categories are wiped and re-inserted with the v1 IDs
-- (1000+ range) so existing references in migrations v3-v7 stay inert.
-- ============================================================================
DELETE FROM keywords;
UPDATE transactions SET category_id = NULL;
DELETE FROM categories;
-- LEVEL 1 — Roots (9 IPC components + Revenus + Transferts)
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1000, 'Revenus', NULL, 'income', '#16a34a', 0, 1, 'categoriesSeed.revenus.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1100, 'Alimentation', NULL, 'expense', '#ea580c', 0, 2, 'categoriesSeed.alimentation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1200, 'Logement', NULL, 'expense', '#dc2626', 0, 3, 'categoriesSeed.logement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1300, 'Ménage & ameublement', NULL, 'expense', '#ca8a04', 0, 4, 'categoriesSeed.menage.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1400, 'Vêtements & chaussures', NULL, 'expense', '#d946ef', 0, 5, 'categoriesSeed.vetements.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1500, 'Transport', NULL, 'expense', '#2563eb', 0, 6, 'categoriesSeed.transport.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1600, 'Santé & soins personnels', NULL, 'expense', '#f43f5e', 0, 7, 'categoriesSeed.sante.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1700, 'Loisirs, formation & lecture', NULL, 'expense', '#8b5cf6', 0, 8, 'categoriesSeed.loisirs.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1800, 'Boissons, tabac & cannabis', NULL, 'expense', '#7c3aed', 0, 9, 'categoriesSeed.consommation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1900, 'Finances & obligations', NULL, 'expense', '#6b7280', 0, 10, 'categoriesSeed.finances.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1950, 'Transferts & placements', NULL, 'transfer', '#0ea5e9', 0, 11, 'categoriesSeed.transferts.root');
-- 1000 — Revenus
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1010, 'Emploi', 1000, 'income', '#22c55e', 0, 1, 'categoriesSeed.revenus.emploi.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1011, 'Paie régulière', 1010, 'income', '#22c55e', 1, 1, 'categoriesSeed.revenus.emploi.paie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1012, 'Primes & bonus', 1010, 'income', '#4ade80', 1, 2, 'categoriesSeed.revenus.emploi.primes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1013, 'Travail autonome', 1010, 'income', '#86efac', 1, 3, 'categoriesSeed.revenus.emploi.autonome');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1020, 'Gouvernemental', 1000, 'income', '#16a34a', 0, 2, 'categoriesSeed.revenus.gouvernemental.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1021, 'Remboursement impôt', 1020, 'income', '#16a34a', 1, 1, 'categoriesSeed.revenus.gouvernemental.remboursementImpot');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1022, 'Allocations familiales', 1020, 'income', '#15803d', 1, 2, 'categoriesSeed.revenus.gouvernemental.allocations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1023, 'Crédits TPS/TVQ', 1020, 'income', '#166534', 1, 3, 'categoriesSeed.revenus.gouvernemental.credits');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1024, 'Assurance-emploi / RQAP', 1020, 'income', '#14532d', 1, 4, 'categoriesSeed.revenus.gouvernemental.assuranceEmploi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1030, 'Revenus de placement', 1000, 'income', '#10b981', 0, 3, 'categoriesSeed.revenus.placement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1031, 'Intérêts & dividendes', 1030, 'income', '#10b981', 1, 1, 'categoriesSeed.revenus.placement.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1032, 'Gains en capital', 1030, 'income', '#059669', 1, 2, 'categoriesSeed.revenus.placement.capital');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1033, 'Revenus locatifs', 1030, 'income', '#047857', 1, 3, 'categoriesSeed.revenus.placement.locatifs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1090, 'Autres revenus', 1000, 'income', '#84cc16', 1, 9, 'categoriesSeed.revenus.autres');
-- 1100 — Alimentation
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1110, 'Épicerie & marché', 1100, 'expense', '#ea580c', 0, 1, 'categoriesSeed.alimentation.epicerie.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1111, 'Épicerie régulière', 1110, 'expense', '#ea580c', 1, 1, 'categoriesSeed.alimentation.epicerie.reguliere');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1112, 'Boucherie & poissonnerie', 1110, 'expense', '#c2410c', 1, 2, 'categoriesSeed.alimentation.epicerie.boucherie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1113, 'Boulangerie & pâtisserie', 1110, 'expense', '#9a3412', 1, 3, 'categoriesSeed.alimentation.epicerie.boulangerie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1114, 'Dépanneur', 1110, 'expense', '#7c2d12', 1, 4, 'categoriesSeed.alimentation.epicerie.depanneur');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1115, 'Marché & produits spécialisés', 1110, 'expense', '#fb923c', 1, 5, 'categoriesSeed.alimentation.epicerie.marche');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1120, 'Restauration', 1100, 'expense', '#f97316', 0, 2, 'categoriesSeed.alimentation.restauration.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1121, 'Restaurant', 1120, 'expense', '#f97316', 1, 1, 'categoriesSeed.alimentation.restauration.restaurant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1122, 'Café & boulangerie rapide', 1120, 'expense', '#fb923c', 1, 2, 'categoriesSeed.alimentation.restauration.cafe');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1123, 'Restauration rapide', 1120, 'expense', '#fdba74', 1, 3, 'categoriesSeed.alimentation.restauration.fastfood');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1124, 'Livraison à domicile', 1120, 'expense', '#fed7aa', 1, 4, 'categoriesSeed.alimentation.restauration.livraison');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1125, 'Cantine & cafétéria', 1120, 'expense', '#ffedd5', 1, 5, 'categoriesSeed.alimentation.restauration.cantine');
-- 1200 — Logement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1210, 'Habitation principale', 1200, 'expense', '#dc2626', 0, 1, 'categoriesSeed.logement.habitation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1211, 'Loyer', 1210, 'expense', '#dc2626', 1, 1, 'categoriesSeed.logement.habitation.loyer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1212, 'Hypothèque', 1210, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.logement.habitation.hypotheque');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1213, 'Taxes municipales & scolaires', 1210, 'expense', '#991b1b', 1, 3, 'categoriesSeed.logement.habitation.taxes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1214, 'Charges de copropriété', 1210, 'expense', '#7f1d1d', 1, 4, 'categoriesSeed.logement.habitation.copropriete');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1220, 'Services publics', 1200, 'expense', '#ef4444', 0, 2, 'categoriesSeed.logement.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1221, 'Électricité', 1220, 'expense', '#ef4444', 1, 1, 'categoriesSeed.logement.services.electricite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1222, 'Gaz naturel', 1220, 'expense', '#f87171', 1, 2, 'categoriesSeed.logement.services.gaz');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1223, 'Chauffage (mazout, propane)', 1220, 'expense', '#fca5a5', 1, 3, 'categoriesSeed.logement.services.chauffage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1224, 'Eau & égouts', 1220, 'expense', '#fecaca', 1, 4, 'categoriesSeed.logement.services.eau');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1230, 'Communications', 1200, 'expense', '#6366f1', 0, 3, 'categoriesSeed.logement.communications.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1231, 'Internet résidentiel', 1230, 'expense', '#6366f1', 1, 1, 'categoriesSeed.logement.communications.internet');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1232, 'Téléphonie mobile', 1230, 'expense', '#818cf8', 1, 2, 'categoriesSeed.logement.communications.mobile');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1233, 'Téléphonie résidentielle', 1230, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.logement.communications.residentielle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1234, 'Câblodistribution & streaming TV', 1230, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.logement.communications.streamingTv');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1240, 'Entretien & réparations', 1200, 'expense', '#e11d48', 0, 4, 'categoriesSeed.logement.entretien.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1241, 'Entretien général', 1240, 'expense', '#e11d48', 1, 1, 'categoriesSeed.logement.entretien.general');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1242, 'Rénovations', 1240, 'expense', '#be123c', 1, 2, 'categoriesSeed.logement.entretien.renovations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1243, 'Matériaux & outils', 1240, 'expense', '#9f1239', 1, 3, 'categoriesSeed.logement.entretien.materiaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1244, 'Aménagement paysager', 1240, 'expense', '#881337', 1, 4, 'categoriesSeed.logement.entretien.paysager');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1250, 'Assurance habitation', 1200, 'expense', '#14b8a6', 1, 5, 'categoriesSeed.logement.assurance');
-- 1300 — Ménage & ameublement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1310, 'Ameublement', 1300, 'expense', '#ca8a04', 0, 1, 'categoriesSeed.menage.ameublement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1311, 'Meubles', 1310, 'expense', '#ca8a04', 1, 1, 'categoriesSeed.menage.ameublement.meubles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1312, 'Électroménagers', 1310, 'expense', '#a16207', 1, 2, 'categoriesSeed.menage.ameublement.electromenagers');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1313, 'Décoration', 1310, 'expense', '#854d0e', 1, 3, 'categoriesSeed.menage.ameublement.decoration');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1320, 'Fournitures ménagères', 1300, 'expense', '#eab308', 0, 2, 'categoriesSeed.menage.fournitures.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1321, 'Produits d''entretien', 1320, 'expense', '#eab308', 1, 1, 'categoriesSeed.menage.fournitures.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1322, 'Literie & linge de maison', 1320, 'expense', '#facc15', 1, 2, 'categoriesSeed.menage.fournitures.literie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1323, 'Vaisselle & ustensiles', 1320, 'expense', '#fde047', 1, 3, 'categoriesSeed.menage.fournitures.vaisselle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1330, 'Services domestiques', 1300, 'expense', '#fbbf24', 0, 3, 'categoriesSeed.menage.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1331, 'Ménage & nettoyage', 1330, 'expense', '#fbbf24', 1, 1, 'categoriesSeed.menage.services.nettoyage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1332, 'Buanderie & pressing', 1330, 'expense', '#fcd34d', 1, 2, 'categoriesSeed.menage.services.buanderie');
-- 1400 — Vêtements & chaussures
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1410, 'Vêtements adultes', 1400, 'expense', '#d946ef', 1, 1, 'categoriesSeed.vetements.adultes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1420, 'Vêtements enfants', 1400, 'expense', '#c026d3', 1, 2, 'categoriesSeed.vetements.enfants');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1430, 'Chaussures', 1400, 'expense', '#a21caf', 1, 3, 'categoriesSeed.vetements.chaussures');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1440, 'Accessoires & bijoux', 1400, 'expense', '#86198f', 1, 4, 'categoriesSeed.vetements.accessoires');
-- 1500 — Transport
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1510, 'Véhicule personnel', 1500, 'expense', '#2563eb', 0, 1, 'categoriesSeed.transport.vehicule.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1511, 'Achat / location véhicule', 1510, 'expense', '#2563eb', 1, 1, 'categoriesSeed.transport.vehicule.achat');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1512, 'Essence', 1510, 'expense', '#1d4ed8', 1, 2, 'categoriesSeed.transport.vehicule.essence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1513, 'Entretien & réparations auto', 1510, 'expense', '#1e40af', 1, 3, 'categoriesSeed.transport.vehicule.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1514, 'Immatriculation & permis', 1510, 'expense', '#1e3a8a', 1, 4, 'categoriesSeed.transport.vehicule.immatriculation');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1515, 'Stationnement & péages', 1510, 'expense', '#3b82f6', 1, 5, 'categoriesSeed.transport.vehicule.stationnement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1516, 'Assurance auto', 1510, 'expense', '#60a5fa', 1, 6, 'categoriesSeed.transport.vehicule.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1520, 'Transport public', 1500, 'expense', '#0ea5e9', 0, 2, 'categoriesSeed.transport.public.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1521, 'Autobus & métro', 1520, 'expense', '#0ea5e9', 1, 1, 'categoriesSeed.transport.public.autobus');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1522, 'Train de banlieue', 1520, 'expense', '#0284c7', 1, 2, 'categoriesSeed.transport.public.train');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1523, 'Taxi & covoiturage', 1520, 'expense', '#0369a1', 1, 3, 'categoriesSeed.transport.public.taxi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1530, 'Voyages longue distance', 1500, 'expense', '#38bdf8', 0, 3, 'categoriesSeed.transport.voyages.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1531, 'Avion', 1530, 'expense', '#38bdf8', 1, 1, 'categoriesSeed.transport.voyages.avion');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1532, 'Train & autocar', 1530, 'expense', '#7dd3fc', 1, 2, 'categoriesSeed.transport.voyages.trainAutocar');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1533, 'Hébergement', 1530, 'expense', '#bae6fd', 1, 3, 'categoriesSeed.transport.voyages.hebergement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1534, 'Location véhicule voyage', 1530, 'expense', '#e0f2fe', 1, 4, 'categoriesSeed.transport.voyages.location');
-- 1600 — Santé & soins personnels
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1610, 'Soins médicaux', 1600, 'expense', '#f43f5e', 0, 1, 'categoriesSeed.sante.medicaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1611, 'Pharmacie', 1610, 'expense', '#f43f5e', 1, 1, 'categoriesSeed.sante.medicaux.pharmacie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1612, 'Consultations médicales', 1610, 'expense', '#e11d48', 1, 2, 'categoriesSeed.sante.medicaux.consultations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1613, 'Dentiste & orthodontiste', 1610, 'expense', '#be123c', 1, 3, 'categoriesSeed.sante.medicaux.dentiste');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1614, 'Optométrie & lunettes', 1610, 'expense', '#9f1239', 1, 4, 'categoriesSeed.sante.medicaux.optometrie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1615, 'Thérapies (physio, psycho, etc.)', 1610, 'expense', '#881337', 1, 5, 'categoriesSeed.sante.medicaux.therapies');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1616, 'Assurance santé complémentaire', 1610, 'expense', '#fb7185', 1, 6, 'categoriesSeed.sante.medicaux.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1620, 'Soins personnels', 1600, 'expense', '#fb7185', 0, 2, 'categoriesSeed.sante.personnels.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1621, 'Coiffure & esthétique', 1620, 'expense', '#fb7185', 1, 1, 'categoriesSeed.sante.personnels.coiffure');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1622, 'Produits de soins corporels', 1620, 'expense', '#fda4af', 1, 2, 'categoriesSeed.sante.personnels.soins');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1630, 'Assurance vie & invalidité', 1600, 'expense', '#14b8a6', 1, 3, 'categoriesSeed.sante.assuranceVie');
-- 1700 — Loisirs, formation & lecture
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1710, 'Divertissement', 1700, 'expense', '#8b5cf6', 0, 1, 'categoriesSeed.loisirs.divertissement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1711, 'Cinéma & spectacles', 1710, 'expense', '#8b5cf6', 1, 1, 'categoriesSeed.loisirs.divertissement.cinema');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1712, 'Jeux vidéo & consoles', 1710, 'expense', '#a78bfa', 1, 2, 'categoriesSeed.loisirs.divertissement.jeux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1713, 'Streaming vidéo', 1710, 'expense', '#c4b5fd', 1, 3, 'categoriesSeed.loisirs.divertissement.streamingVideo');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1714, 'Streaming musique & audio', 1710, 'expense', '#ddd6fe', 1, 4, 'categoriesSeed.loisirs.divertissement.streamingMusique');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1715, 'Jouets & passe-temps', 1710, 'expense', '#ede9fe', 1, 5, 'categoriesSeed.loisirs.divertissement.jouets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1720, 'Sports & plein air', 1700, 'expense', '#22c55e', 0, 2, 'categoriesSeed.loisirs.sports.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1721, 'Abonnements sportifs', 1720, 'expense', '#22c55e', 1, 1, 'categoriesSeed.loisirs.sports.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1722, 'Équipement sportif', 1720, 'expense', '#4ade80', 1, 2, 'categoriesSeed.loisirs.sports.equipement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1723, 'Parcs & activités plein air', 1720, 'expense', '#86efac', 1, 3, 'categoriesSeed.loisirs.sports.parcs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1730, 'Formation & éducation', 1700, 'expense', '#6366f1', 0, 3, 'categoriesSeed.loisirs.formation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1731, 'Scolarité (frais)', 1730, 'expense', '#6366f1', 1, 1, 'categoriesSeed.loisirs.formation.scolarite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1732, 'Matériel scolaire', 1730, 'expense', '#818cf8', 1, 2, 'categoriesSeed.loisirs.formation.materiel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1733, 'Cours & certifications', 1730, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.loisirs.formation.cours');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1734, 'Abonnements professionnels', 1730, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.loisirs.formation.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1740, 'Lecture & médias', 1700, 'expense', '#ec4899', 0, 4, 'categoriesSeed.loisirs.lecture.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1741, 'Livres', 1740, 'expense', '#ec4899', 1, 1, 'categoriesSeed.loisirs.lecture.livres');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1742, 'Journaux & magazines', 1740, 'expense', '#f472b6', 1, 2, 'categoriesSeed.loisirs.lecture.journaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1750, 'Animaux de compagnie', 1700, 'expense', '#a855f7', 0, 5, 'categoriesSeed.loisirs.animaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1751, 'Nourriture & accessoires animaux', 1750, 'expense', '#a855f7', 1, 1, 'categoriesSeed.loisirs.animaux.nourriture');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1752, 'Vétérinaire', 1750, 'expense', '#c084fc', 1, 2, 'categoriesSeed.loisirs.animaux.veterinaire');
-- 1800 — Boissons, tabac & cannabis
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1810, 'Alcool (SAQ, microbrasseries)', 1800, 'expense', '#7c3aed', 1, 1, 'categoriesSeed.consommation.alcool');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1820, 'Cannabis (SQDC)', 1800, 'expense', '#6d28d9', 1, 2, 'categoriesSeed.consommation.cannabis');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1830, 'Tabac', 1800, 'expense', '#5b21b6', 1, 3, 'categoriesSeed.consommation.tabac');
-- 1900 — Finances & obligations
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1910, 'Frais bancaires', 1900, 'expense', '#6b7280', 0, 1, 'categoriesSeed.finances.fraisBancaires.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1911, 'Frais de compte', 1910, 'expense', '#6b7280', 1, 1, 'categoriesSeed.finances.fraisBancaires.compte');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1912, 'Intérêts & frais de crédit', 1910, 'expense', '#9ca3af', 1, 2, 'categoriesSeed.finances.fraisBancaires.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1913, 'Frais de change', 1910, 'expense', '#d1d5db', 1, 3, 'categoriesSeed.finances.fraisBancaires.change');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1920, 'Impôts & taxes', 1900, 'expense', '#dc2626', 0, 2, 'categoriesSeed.finances.impots.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1921, 'Impôt fédéral', 1920, 'expense', '#dc2626', 1, 1, 'categoriesSeed.finances.impots.federal');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1922, 'Impôt provincial', 1920, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.finances.impots.provincial');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1923, 'Acomptes provisionnels', 1920, 'expense', '#991b1b', 1, 3, 'categoriesSeed.finances.impots.acomptes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1930, 'Dons & cotisations', 1900, 'expense', '#ec4899', 0, 3, 'categoriesSeed.finances.dons.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1931, 'Dons de charité', 1930, 'expense', '#ec4899', 1, 1, 'categoriesSeed.finances.dons.charite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1932, 'Cotisations professionnelles', 1930, 'expense', '#f472b6', 1, 2, 'categoriesSeed.finances.dons.professionnelles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1933, 'Cotisations syndicales', 1930, 'expense', '#f9a8d4', 1, 3, 'categoriesSeed.finances.dons.syndicales');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1940, 'Cadeaux', 1900, 'expense', '#f43f5e', 1, 4, 'categoriesSeed.finances.cadeaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1945, 'Retrait cash', 1900, 'expense', '#57534e', 1, 5, 'categoriesSeed.finances.cash');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1946, 'Achats divers non catégorisés', 1900, 'expense', '#78716c', 1, 6, 'categoriesSeed.finances.divers');
-- 1950 — Transferts & placements
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1960, 'Épargne & placements', 1950, 'transfer', '#0ea5e9', 0, 1, 'categoriesSeed.transferts.epargne.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1961, 'REER / RRSP', 1960, 'transfer', '#0ea5e9', 1, 1, 'categoriesSeed.transferts.epargne.reer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1962, 'CELI / TFSA', 1960, 'transfer', '#0284c7', 1, 2, 'categoriesSeed.transferts.epargne.celi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1963, 'REEE / RESP', 1960, 'transfer', '#0369a1', 1, 3, 'categoriesSeed.transferts.epargne.reee');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1964, 'Compte non-enregistré', 1960, 'transfer', '#075985', 1, 4, 'categoriesSeed.transferts.epargne.nonEnregistre');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1965, 'Fonds d''urgence', 1960, 'transfer', '#0c4a6e', 1, 5, 'categoriesSeed.transferts.epargne.urgence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1970, 'Remboursement de dette', 1950, 'transfer', '#7c3aed', 0, 2, 'categoriesSeed.transferts.dette.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1971, 'Paiement carte crédit', 1970, 'transfer', '#7c3aed', 1, 1, 'categoriesSeed.transferts.dette.carteCredit');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1972, 'Remboursement prêt étudiant', 1970, 'transfer', '#8b5cf6', 1, 2, 'categoriesSeed.transferts.dette.etudiant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1973, 'Remboursement prêt perso', 1970, 'transfer', '#a78bfa', 1, 3, 'categoriesSeed.transferts.dette.personnel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1980, 'Transferts internes', 1950, 'transfer', '#64748b', 1, 3, 'categoriesSeed.transferts.internes');
-- ============================================================================
-- Keywords — Canadian suppliers (150+ entries)
-- ============================================================================
-- Alimentation > Épicerie régulière (1111)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('METRO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IGA', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MAXI', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUPER C', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LOBLAWS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROVIGO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ADONIS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WHOLE FOODS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AVRIL', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RACHELLE-BERY', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COSTCO', 1111, 50);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WALMART', 1111, 50);
-- Épicerie > Boucherie (1112)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCHERIE', 1112, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('POISSONNERIE', 1112, 0);
-- Épicerie > Boulangerie (1113)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOULANGERIE', 1113, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PATISSERIE', 1113, 0);
-- Épicerie > Dépanneur (1114)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPANNEUR', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('7-ELEVEN', 1114, 0);
-- Restauration > Restaurant (1121)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RESTAURANT', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRASSERIE', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BISTRO', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUSHI', 1121, 0);
-- Restauration > Café (1122)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STARBUCKS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIM HORTONS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SECOND CUP', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VAN HOUTTE', 1122, 0);
-- Restauration > Fast food (1123)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MCDONALD', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUBWAY', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('A&W', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BURGER KING', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KFC', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOMINOS', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PIZZA HUT', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELLE PROVINCE', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ST-HUBERT', 1123, 0);
-- Restauration > Livraison (1124)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DD/DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER EATS', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SKIPTHEDISHES', 1124, 0);
-- Logement > Hypothèque (1212)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MTG/HYP', 1212, 0);
-- Logement > Taxes municipales (1213)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('M-ST-HILAIRE TX', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXES MUNICIPALES', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CSS PATRIOT', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXE SCOLAIRE', 1213, 0);
-- Électricité (1221)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO-QUEBEC', 1221, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO QUEBEC', 1221, 0);
-- Gaz (1222)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ENERGIR', 1222, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAZ METRO', 1222, 0);
-- Internet (1231)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIDEOTRON', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL INTERNET', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORICOM', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COGECO', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EBOX', 1231, 0);
-- Mobile (1232)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIZZ', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KOODO', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PUBLIC MOBILE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIRGIN', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL MOBILITE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TELUS MOBILE', 1232, 0);
-- Entretien maison (1241)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IKEA', 1241, 0);
-- Matériaux & outils (1243)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANADIAN TIRE', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANAC', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RONA', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HOME DEPOT', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BMR', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRINCESS AUTO', 1243, 0);
-- Assurance habitation (1250)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELAIR', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRYSM', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INTACT ASSURANCE', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DESJARDINS ASSURANCE', 1250, 0);
-- Meubles (1311)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TANGUAY', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEON', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STRUCTUBE', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRICK', 1311, 0);
-- Électroménagers (1312)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BEST BUY', 1312, 0);
-- Décoration (1313)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCLAIR', 1313, 0);
-- Vêtements (1410)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIQLO', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WINNERS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SIMONS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARKS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('H&M', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OLD NAVY', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAP', 1410, 0);
-- Transport — Essence (1512)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SHELL', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ESSO', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ULTRAMAR', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO-CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CREVIER', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HARNOIS', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD ESSENCE', 1512, 10);
-- Permis / SAAQ (1514)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAAQ', 1514, 0);
-- Transport public — autobus/métro (1521)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STM', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RTC', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STL', 1521, 0);
-- Train de banlieue (1522)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE MONT-SAINT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE SAINT-HUBERT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE CENTRALE', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EXO', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REM', 1522, 0);
-- Taxi / Uber (1523)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LYFT', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXI', 1523, 0);
-- Avion (1531)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR CANADA', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WESTJET', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR TRANSAT', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PORTER', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AEROPORTS DE MONTREAL', 1531, 0);
-- Hébergement (1533)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIRBNB', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HILTON', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARRIOTT', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOOKING.COM', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NORWEGIAN CRUISE', 1533, 0);
-- Pharmacie (1611)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('JEAN COUTU', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FAMILIPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHARMAPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROXIM', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIPRIX', 1611, 0);
-- Thérapies (1615)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHYSIOACTIF', 1615, 0);
-- Cinéma & spectacles (1711)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEPLEX', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEMA DU PARC', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TICKETMASTER', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CLUB SODA', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEPOINTDEVENTE', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EVENBRITE', 1711, 0);
-- Jeux vidéo (1712)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STEAMGAMES', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PLAYSTATION', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NINTENDO', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('XBOX', 1712, 0);
-- Streaming vidéo (1713)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NETFLIX', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRIMEVIDEO', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DISNEY PLUS', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRAVE', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE TV', 1713, 0);
-- Streaming musique (1714)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPOTIFY', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('YOUTUBE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIDAL', 1714, 0);
-- Jouets & hobbies (1715)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEGO', 1715, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TOYS R US', 1715, 0);
-- Équipement sportif (1722)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MOUNTAIN EQUIPMENT', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LA CORDEE', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DECATHLON', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPORTS EXPERTS', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ATMOSPHERE', 1722, 0);
-- Parcs & activités (1723)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SEPAQ', 1723, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BLOC SHOP', 1723, 0);
-- Lecture (1741)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RENAUD-BRAY', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INDIGO', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARCHAMBAULT', 1741, 0);
-- Animaux — nourriture (1751)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MONDOU', 1751, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PET SMART', 1751, 0);
-- Alcool (1810)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAQ', 1810, 0);
-- Cannabis (1820)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SQDC', 1820, 0);
-- Frais bancaires (1911)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROGRAMME PERFORMANCE', 1911, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FRAIS MENSUELS', 1911, 0);
-- Impôts (1921, 1922)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GOUV. QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REVENU QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARC IMPOT', 1921, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRA TAX', 1921, 0);
-- Dons de charité (1931)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OXFAM', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CENTRAIDE', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FPA', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CROIX-ROUGE', 1931, 0);
-- Cotisations professionnelles (1932)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORDRE DES COMPTABL', 1932, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CPA CANADA', 1932, 0);
-- Cadeaux (1940)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DANS UN JARDIN', 1940, 0);
-- Divers (1946) — catch-all marketplace
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMAZON', 1946, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMZN', 1946, 0);
-- Placements (1964)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WS INVESTMENTS', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WEALTHSIMPLE', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PEAK INVESTMENT', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DYNAMIC FUND', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIDELITY', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AGF', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('QUESTRADE', 1964, 0);
-- Paie (1011)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAY/PAY', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPOT PAIE', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAYROLL', 1011, 0);

View file

@ -0,0 +1,184 @@
-- Consolidated schema for new profile databases
-- This file bakes in the base schema + all migrations (v3-v6)
-- Used ONLY for initializing new profile databases (not for the default profile)
CREATE TABLE IF NOT EXISTS import_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
date_format TEXT NOT NULL DEFAULT '%d/%m/%Y',
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
column_mapping TEXT NOT NULL,
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS imported_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER NOT NULL,
filename TEXT NOT NULL,
file_hash TEXT NOT NULL,
import_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
row_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'completed',
notes TEXT,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE CASCADE,
UNIQUE(source_id, filename)
);
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
parent_id INTEGER,
color TEXT,
icon TEXT,
type TEXT NOT NULL DEFAULT 'expense',
is_active INTEGER NOT NULL DEFAULT 1,
is_inputable INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
normalized_name TEXT NOT NULL,
category_id INTEGER,
is_active INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
category_id INTEGER NOT NULL,
supplier_id INTEGER,
priority INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
UNIQUE(keyword, category_id)
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
description TEXT NOT NULL,
amount REAL NOT NULL,
category_id INTEGER,
supplier_id INTEGER,
source_id INTEGER,
file_id INTEGER,
original_description TEXT,
notes TEXT,
is_manually_categorized INTEGER NOT NULL DEFAULT 0,
is_split INTEGER NOT NULL DEFAULT 0,
parent_transaction_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE SET NULL,
FOREIGN KEY (file_id) REFERENCES imported_files(id) ON DELETE SET NULL,
FOREIGN KEY (parent_transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS adjustments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
date DATE NOT NULL,
is_recurring INTEGER NOT NULL DEFAULT 0,
recurrence_rule TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS adjustment_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
adjustment_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
description TEXT,
FOREIGN KEY (adjustment_id) REFERENCES adjustments(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS budget_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
amount REAL NOT NULL,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(category_id, year, month)
);
CREATE TABLE IF NOT EXISTS budget_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS budget_template_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
template_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (template_id) REFERENCES budget_templates(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(template_id, category_id)
);
CREATE TABLE IF NOT EXISTS import_config_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
column_mapping TEXT NOT NULL,
amount_mode TEXT NOT NULL DEFAULT 'single',
sign_convention TEXT NOT NULL DEFAULT 'negative_expense',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_preferences (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(date);
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id);
CREATE INDEX IF NOT EXISTS idx_transactions_supplier ON transactions(supplier_id);
CREATE INDEX IF NOT EXISTS idx_transactions_source ON transactions(source_id);
CREATE INDEX IF NOT EXISTS idx_transactions_file ON transactions(file_id);
CREATE INDEX IF NOT EXISTS idx_transactions_parent ON transactions(parent_transaction_id);
CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_categories_type ON categories(type);
CREATE INDEX IF NOT EXISTS idx_suppliers_category ON suppliers(category_id);
CREATE INDEX IF NOT EXISTS idx_suppliers_normalized ON suppliers(normalized_name);
CREATE INDEX IF NOT EXISTS idx_keywords_category ON keywords(category_id);
CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON keywords(keyword);
CREATE INDEX IF NOT EXISTS idx_budget_entries_period ON budget_entries(year, month);
CREATE INDEX IF NOT EXISTS idx_adjustment_entries_adjustment ON adjustment_entries(adjustment_id);
CREATE INDEX IF NOT EXISTS idx_imported_files_source ON imported_files(source_id);
-- Default preferences
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('language', 'fr');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('theme', 'light');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('currency', 'EUR');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('date_format', 'DD/MM/YYYY');

View file

@ -0,0 +1,657 @@
-- Consolidated schema for new profile databases
-- This file bakes in the base schema + all migrations (v3-v8) and pre-seeds
-- the v1 IPC-aligned category taxonomy so that brand-new profiles immediately
-- use the new standard. Existing profiles keep their v2 seed and are only
-- tagged via the migration (categories_schema_version = 'v2').
-- Used ONLY for initializing new profile databases (not for the default profile).
CREATE TABLE IF NOT EXISTS import_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
date_format TEXT NOT NULL DEFAULT '%d/%m/%Y',
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
column_mapping TEXT NOT NULL,
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS imported_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER NOT NULL,
filename TEXT NOT NULL,
file_hash TEXT NOT NULL,
import_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
row_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'completed',
notes TEXT,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE CASCADE,
UNIQUE(source_id, filename)
);
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
parent_id INTEGER,
color TEXT,
icon TEXT,
type TEXT NOT NULL DEFAULT 'expense',
is_active INTEGER NOT NULL DEFAULT 1,
is_inputable INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
i18n_key TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
normalized_name TEXT NOT NULL,
category_id INTEGER,
is_active INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
category_id INTEGER NOT NULL,
supplier_id INTEGER,
priority INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
UNIQUE(keyword, category_id)
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
description TEXT NOT NULL,
amount REAL NOT NULL,
category_id INTEGER,
supplier_id INTEGER,
source_id INTEGER,
file_id INTEGER,
original_description TEXT,
notes TEXT,
is_manually_categorized INTEGER NOT NULL DEFAULT 0,
is_split INTEGER NOT NULL DEFAULT 0,
parent_transaction_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE SET NULL,
FOREIGN KEY (file_id) REFERENCES imported_files(id) ON DELETE SET NULL,
FOREIGN KEY (parent_transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS adjustments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
date DATE NOT NULL,
is_recurring INTEGER NOT NULL DEFAULT 0,
recurrence_rule TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS adjustment_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
adjustment_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
description TEXT,
FOREIGN KEY (adjustment_id) REFERENCES adjustments(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS budget_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
amount REAL NOT NULL,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(category_id, year, month)
);
CREATE TABLE IF NOT EXISTS budget_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS budget_template_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
template_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (template_id) REFERENCES budget_templates(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(template_id, category_id)
);
CREATE TABLE IF NOT EXISTS import_config_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
column_mapping TEXT NOT NULL,
amount_mode TEXT NOT NULL DEFAULT 'single',
sign_convention TEXT NOT NULL DEFAULT 'negative_expense',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_preferences (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(date);
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id);
CREATE INDEX IF NOT EXISTS idx_transactions_supplier ON transactions(supplier_id);
CREATE INDEX IF NOT EXISTS idx_transactions_source ON transactions(source_id);
CREATE INDEX IF NOT EXISTS idx_transactions_file ON transactions(file_id);
CREATE INDEX IF NOT EXISTS idx_transactions_parent ON transactions(parent_transaction_id);
CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_categories_type ON categories(type);
CREATE INDEX IF NOT EXISTS idx_suppliers_category ON suppliers(category_id);
CREATE INDEX IF NOT EXISTS idx_suppliers_normalized ON suppliers(normalized_name);
CREATE INDEX IF NOT EXISTS idx_keywords_category ON keywords(category_id);
CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON keywords(keyword);
CREATE INDEX IF NOT EXISTS idx_budget_entries_period ON budget_entries(year, month);
CREATE INDEX IF NOT EXISTS idx_adjustment_entries_adjustment ON adjustment_entries(adjustment_id);
CREATE INDEX IF NOT EXISTS idx_imported_files_source ON imported_files(source_id);
-- Default preferences (new profiles ship with the v1 IPC taxonomy)
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('language', 'fr');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('theme', 'light');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('currency', 'EUR');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('date_format', 'DD/MM/YYYY');
INSERT OR REPLACE INTO user_preferences (key, value) VALUES ('categories_schema_version', 'v1');
-- ============================================================================
-- Seed v1 — IPC Statistique Canada-aligned, 3 levels, Canada/Québec
-- ----------------------------------------------------------------------------
-- Reset any pre-existing category data (possible when tauri-plugin-sql runs
-- the historical v2 seed migration on this fresh DB before this script
-- executes). Keywords/categories are wiped and re-inserted with the v1 IDs
-- (1000+ range) so existing references in migrations v3-v7 stay inert.
-- ============================================================================
DELETE FROM keywords;
UPDATE transactions SET category_id = NULL;
DELETE FROM categories;
-- LEVEL 1 — Roots (9 IPC components + Revenus + Transferts)
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1000, 'Revenus', NULL, 'income', '#16a34a', 0, 1, 'categoriesSeed.revenus.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1100, 'Alimentation', NULL, 'expense', '#ea580c', 0, 2, 'categoriesSeed.alimentation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1200, 'Logement', NULL, 'expense', '#dc2626', 0, 3, 'categoriesSeed.logement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1300, 'Ménage & ameublement', NULL, 'expense', '#ca8a04', 0, 4, 'categoriesSeed.menage.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1400, 'Vêtements & chaussures', NULL, 'expense', '#d946ef', 0, 5, 'categoriesSeed.vetements.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1500, 'Transport', NULL, 'expense', '#2563eb', 0, 6, 'categoriesSeed.transport.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1600, 'Santé & soins personnels', NULL, 'expense', '#f43f5e', 0, 7, 'categoriesSeed.sante.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1700, 'Loisirs, formation & lecture', NULL, 'expense', '#8b5cf6', 0, 8, 'categoriesSeed.loisirs.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1800, 'Boissons, tabac & cannabis', NULL, 'expense', '#7c3aed', 0, 9, 'categoriesSeed.consommation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1900, 'Finances & obligations', NULL, 'expense', '#6b7280', 0, 10, 'categoriesSeed.finances.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1950, 'Transferts & placements', NULL, 'transfer', '#0ea5e9', 0, 11, 'categoriesSeed.transferts.root');
-- 1000 — Revenus
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1010, 'Emploi', 1000, 'income', '#22c55e', 0, 1, 'categoriesSeed.revenus.emploi.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1011, 'Paie régulière', 1010, 'income', '#22c55e', 1, 1, 'categoriesSeed.revenus.emploi.paie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1012, 'Primes & bonus', 1010, 'income', '#4ade80', 1, 2, 'categoriesSeed.revenus.emploi.primes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1013, 'Travail autonome', 1010, 'income', '#86efac', 1, 3, 'categoriesSeed.revenus.emploi.autonome');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1020, 'Gouvernemental', 1000, 'income', '#16a34a', 0, 2, 'categoriesSeed.revenus.gouvernemental.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1021, 'Remboursement impôt', 1020, 'income', '#16a34a', 1, 1, 'categoriesSeed.revenus.gouvernemental.remboursementImpot');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1022, 'Allocations familiales', 1020, 'income', '#15803d', 1, 2, 'categoriesSeed.revenus.gouvernemental.allocations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1023, 'Crédits TPS/TVQ', 1020, 'income', '#166534', 1, 3, 'categoriesSeed.revenus.gouvernemental.credits');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1024, 'Assurance-emploi / RQAP', 1020, 'income', '#14532d', 1, 4, 'categoriesSeed.revenus.gouvernemental.assuranceEmploi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1030, 'Revenus de placement', 1000, 'income', '#10b981', 0, 3, 'categoriesSeed.revenus.placement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1031, 'Intérêts & dividendes', 1030, 'income', '#10b981', 1, 1, 'categoriesSeed.revenus.placement.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1032, 'Gains en capital', 1030, 'income', '#059669', 1, 2, 'categoriesSeed.revenus.placement.capital');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1033, 'Revenus locatifs', 1030, 'income', '#047857', 1, 3, 'categoriesSeed.revenus.placement.locatifs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1090, 'Autres revenus', 1000, 'income', '#84cc16', 1, 9, 'categoriesSeed.revenus.autres');
-- 1100 — Alimentation
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1110, 'Épicerie & marché', 1100, 'expense', '#ea580c', 0, 1, 'categoriesSeed.alimentation.epicerie.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1111, 'Épicerie régulière', 1110, 'expense', '#ea580c', 1, 1, 'categoriesSeed.alimentation.epicerie.reguliere');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1112, 'Boucherie & poissonnerie', 1110, 'expense', '#c2410c', 1, 2, 'categoriesSeed.alimentation.epicerie.boucherie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1113, 'Boulangerie & pâtisserie', 1110, 'expense', '#9a3412', 1, 3, 'categoriesSeed.alimentation.epicerie.boulangerie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1114, 'Dépanneur', 1110, 'expense', '#7c2d12', 1, 4, 'categoriesSeed.alimentation.epicerie.depanneur');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1115, 'Marché & produits spécialisés', 1110, 'expense', '#fb923c', 1, 5, 'categoriesSeed.alimentation.epicerie.marche');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1120, 'Restauration', 1100, 'expense', '#f97316', 0, 2, 'categoriesSeed.alimentation.restauration.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1121, 'Restaurant', 1120, 'expense', '#f97316', 1, 1, 'categoriesSeed.alimentation.restauration.restaurant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1122, 'Café & boulangerie rapide', 1120, 'expense', '#fb923c', 1, 2, 'categoriesSeed.alimentation.restauration.cafe');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1123, 'Restauration rapide', 1120, 'expense', '#fdba74', 1, 3, 'categoriesSeed.alimentation.restauration.fastfood');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1124, 'Livraison à domicile', 1120, 'expense', '#fed7aa', 1, 4, 'categoriesSeed.alimentation.restauration.livraison');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1125, 'Cantine & cafétéria', 1120, 'expense', '#ffedd5', 1, 5, 'categoriesSeed.alimentation.restauration.cantine');
-- 1200 — Logement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1210, 'Habitation principale', 1200, 'expense', '#dc2626', 0, 1, 'categoriesSeed.logement.habitation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1211, 'Loyer', 1210, 'expense', '#dc2626', 1, 1, 'categoriesSeed.logement.habitation.loyer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1212, 'Hypothèque', 1210, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.logement.habitation.hypotheque');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1213, 'Taxes municipales & scolaires', 1210, 'expense', '#991b1b', 1, 3, 'categoriesSeed.logement.habitation.taxes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1214, 'Charges de copropriété', 1210, 'expense', '#7f1d1d', 1, 4, 'categoriesSeed.logement.habitation.copropriete');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1220, 'Services publics', 1200, 'expense', '#ef4444', 0, 2, 'categoriesSeed.logement.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1221, 'Électricité', 1220, 'expense', '#ef4444', 1, 1, 'categoriesSeed.logement.services.electricite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1222, 'Gaz naturel', 1220, 'expense', '#f87171', 1, 2, 'categoriesSeed.logement.services.gaz');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1223, 'Chauffage (mazout, propane)', 1220, 'expense', '#fca5a5', 1, 3, 'categoriesSeed.logement.services.chauffage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1224, 'Eau & égouts', 1220, 'expense', '#fecaca', 1, 4, 'categoriesSeed.logement.services.eau');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1230, 'Communications', 1200, 'expense', '#6366f1', 0, 3, 'categoriesSeed.logement.communications.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1231, 'Internet résidentiel', 1230, 'expense', '#6366f1', 1, 1, 'categoriesSeed.logement.communications.internet');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1232, 'Téléphonie mobile', 1230, 'expense', '#818cf8', 1, 2, 'categoriesSeed.logement.communications.mobile');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1233, 'Téléphonie résidentielle', 1230, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.logement.communications.residentielle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1234, 'Câblodistribution & streaming TV', 1230, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.logement.communications.streamingTv');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1240, 'Entretien & réparations', 1200, 'expense', '#e11d48', 0, 4, 'categoriesSeed.logement.entretien.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1241, 'Entretien général', 1240, 'expense', '#e11d48', 1, 1, 'categoriesSeed.logement.entretien.general');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1242, 'Rénovations', 1240, 'expense', '#be123c', 1, 2, 'categoriesSeed.logement.entretien.renovations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1243, 'Matériaux & outils', 1240, 'expense', '#9f1239', 1, 3, 'categoriesSeed.logement.entretien.materiaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1244, 'Aménagement paysager', 1240, 'expense', '#881337', 1, 4, 'categoriesSeed.logement.entretien.paysager');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1250, 'Assurance habitation', 1200, 'expense', '#14b8a6', 1, 5, 'categoriesSeed.logement.assurance');
-- 1300 — Ménage & ameublement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1310, 'Ameublement', 1300, 'expense', '#ca8a04', 0, 1, 'categoriesSeed.menage.ameublement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1311, 'Meubles', 1310, 'expense', '#ca8a04', 1, 1, 'categoriesSeed.menage.ameublement.meubles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1312, 'Électroménagers', 1310, 'expense', '#a16207', 1, 2, 'categoriesSeed.menage.ameublement.electromenagers');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1313, 'Décoration', 1310, 'expense', '#854d0e', 1, 3, 'categoriesSeed.menage.ameublement.decoration');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1320, 'Fournitures ménagères', 1300, 'expense', '#eab308', 0, 2, 'categoriesSeed.menage.fournitures.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1321, 'Produits d''entretien', 1320, 'expense', '#eab308', 1, 1, 'categoriesSeed.menage.fournitures.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1322, 'Literie & linge de maison', 1320, 'expense', '#facc15', 1, 2, 'categoriesSeed.menage.fournitures.literie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1323, 'Vaisselle & ustensiles', 1320, 'expense', '#fde047', 1, 3, 'categoriesSeed.menage.fournitures.vaisselle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1330, 'Services domestiques', 1300, 'expense', '#fbbf24', 0, 3, 'categoriesSeed.menage.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1331, 'Ménage & nettoyage', 1330, 'expense', '#fbbf24', 1, 1, 'categoriesSeed.menage.services.nettoyage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1332, 'Buanderie & pressing', 1330, 'expense', '#fcd34d', 1, 2, 'categoriesSeed.menage.services.buanderie');
-- 1400 — Vêtements & chaussures
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1410, 'Vêtements adultes', 1400, 'expense', '#d946ef', 1, 1, 'categoriesSeed.vetements.adultes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1420, 'Vêtements enfants', 1400, 'expense', '#c026d3', 1, 2, 'categoriesSeed.vetements.enfants');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1430, 'Chaussures', 1400, 'expense', '#a21caf', 1, 3, 'categoriesSeed.vetements.chaussures');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1440, 'Accessoires & bijoux', 1400, 'expense', '#86198f', 1, 4, 'categoriesSeed.vetements.accessoires');
-- 1500 — Transport
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1510, 'Véhicule personnel', 1500, 'expense', '#2563eb', 0, 1, 'categoriesSeed.transport.vehicule.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1511, 'Achat / location véhicule', 1510, 'expense', '#2563eb', 1, 1, 'categoriesSeed.transport.vehicule.achat');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1512, 'Essence', 1510, 'expense', '#1d4ed8', 1, 2, 'categoriesSeed.transport.vehicule.essence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1513, 'Entretien & réparations auto', 1510, 'expense', '#1e40af', 1, 3, 'categoriesSeed.transport.vehicule.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1514, 'Immatriculation & permis', 1510, 'expense', '#1e3a8a', 1, 4, 'categoriesSeed.transport.vehicule.immatriculation');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1515, 'Stationnement & péages', 1510, 'expense', '#3b82f6', 1, 5, 'categoriesSeed.transport.vehicule.stationnement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1516, 'Assurance auto', 1510, 'expense', '#60a5fa', 1, 6, 'categoriesSeed.transport.vehicule.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1520, 'Transport public', 1500, 'expense', '#0ea5e9', 0, 2, 'categoriesSeed.transport.public.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1521, 'Autobus & métro', 1520, 'expense', '#0ea5e9', 1, 1, 'categoriesSeed.transport.public.autobus');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1522, 'Train de banlieue', 1520, 'expense', '#0284c7', 1, 2, 'categoriesSeed.transport.public.train');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1523, 'Taxi & covoiturage', 1520, 'expense', '#0369a1', 1, 3, 'categoriesSeed.transport.public.taxi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1530, 'Voyages longue distance', 1500, 'expense', '#38bdf8', 0, 3, 'categoriesSeed.transport.voyages.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1531, 'Avion', 1530, 'expense', '#38bdf8', 1, 1, 'categoriesSeed.transport.voyages.avion');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1532, 'Train & autocar', 1530, 'expense', '#7dd3fc', 1, 2, 'categoriesSeed.transport.voyages.trainAutocar');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1533, 'Hébergement', 1530, 'expense', '#bae6fd', 1, 3, 'categoriesSeed.transport.voyages.hebergement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1534, 'Location véhicule voyage', 1530, 'expense', '#e0f2fe', 1, 4, 'categoriesSeed.transport.voyages.location');
-- 1600 — Santé & soins personnels
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1610, 'Soins médicaux', 1600, 'expense', '#f43f5e', 0, 1, 'categoriesSeed.sante.medicaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1611, 'Pharmacie', 1610, 'expense', '#f43f5e', 1, 1, 'categoriesSeed.sante.medicaux.pharmacie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1612, 'Consultations médicales', 1610, 'expense', '#e11d48', 1, 2, 'categoriesSeed.sante.medicaux.consultations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1613, 'Dentiste & orthodontiste', 1610, 'expense', '#be123c', 1, 3, 'categoriesSeed.sante.medicaux.dentiste');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1614, 'Optométrie & lunettes', 1610, 'expense', '#9f1239', 1, 4, 'categoriesSeed.sante.medicaux.optometrie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1615, 'Thérapies (physio, psycho, etc.)', 1610, 'expense', '#881337', 1, 5, 'categoriesSeed.sante.medicaux.therapies');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1616, 'Assurance santé complémentaire', 1610, 'expense', '#fb7185', 1, 6, 'categoriesSeed.sante.medicaux.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1620, 'Soins personnels', 1600, 'expense', '#fb7185', 0, 2, 'categoriesSeed.sante.personnels.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1621, 'Coiffure & esthétique', 1620, 'expense', '#fb7185', 1, 1, 'categoriesSeed.sante.personnels.coiffure');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1622, 'Produits de soins corporels', 1620, 'expense', '#fda4af', 1, 2, 'categoriesSeed.sante.personnels.soins');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1630, 'Assurance vie & invalidité', 1600, 'expense', '#14b8a6', 1, 3, 'categoriesSeed.sante.assuranceVie');
-- 1700 — Loisirs, formation & lecture
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1710, 'Divertissement', 1700, 'expense', '#8b5cf6', 0, 1, 'categoriesSeed.loisirs.divertissement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1711, 'Cinéma & spectacles', 1710, 'expense', '#8b5cf6', 1, 1, 'categoriesSeed.loisirs.divertissement.cinema');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1712, 'Jeux vidéo & consoles', 1710, 'expense', '#a78bfa', 1, 2, 'categoriesSeed.loisirs.divertissement.jeux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1713, 'Streaming vidéo', 1710, 'expense', '#c4b5fd', 1, 3, 'categoriesSeed.loisirs.divertissement.streamingVideo');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1714, 'Streaming musique & audio', 1710, 'expense', '#ddd6fe', 1, 4, 'categoriesSeed.loisirs.divertissement.streamingMusique');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1715, 'Jouets & passe-temps', 1710, 'expense', '#ede9fe', 1, 5, 'categoriesSeed.loisirs.divertissement.jouets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1720, 'Sports & plein air', 1700, 'expense', '#22c55e', 0, 2, 'categoriesSeed.loisirs.sports.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1721, 'Abonnements sportifs', 1720, 'expense', '#22c55e', 1, 1, 'categoriesSeed.loisirs.sports.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1722, 'Équipement sportif', 1720, 'expense', '#4ade80', 1, 2, 'categoriesSeed.loisirs.sports.equipement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1723, 'Parcs & activités plein air', 1720, 'expense', '#86efac', 1, 3, 'categoriesSeed.loisirs.sports.parcs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1730, 'Formation & éducation', 1700, 'expense', '#6366f1', 0, 3, 'categoriesSeed.loisirs.formation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1731, 'Scolarité (frais)', 1730, 'expense', '#6366f1', 1, 1, 'categoriesSeed.loisirs.formation.scolarite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1732, 'Matériel scolaire', 1730, 'expense', '#818cf8', 1, 2, 'categoriesSeed.loisirs.formation.materiel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1733, 'Cours & certifications', 1730, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.loisirs.formation.cours');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1734, 'Abonnements professionnels', 1730, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.loisirs.formation.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1740, 'Lecture & médias', 1700, 'expense', '#ec4899', 0, 4, 'categoriesSeed.loisirs.lecture.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1741, 'Livres', 1740, 'expense', '#ec4899', 1, 1, 'categoriesSeed.loisirs.lecture.livres');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1742, 'Journaux & magazines', 1740, 'expense', '#f472b6', 1, 2, 'categoriesSeed.loisirs.lecture.journaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1750, 'Animaux de compagnie', 1700, 'expense', '#a855f7', 0, 5, 'categoriesSeed.loisirs.animaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1751, 'Nourriture & accessoires animaux', 1750, 'expense', '#a855f7', 1, 1, 'categoriesSeed.loisirs.animaux.nourriture');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1752, 'Vétérinaire', 1750, 'expense', '#c084fc', 1, 2, 'categoriesSeed.loisirs.animaux.veterinaire');
-- 1800 — Boissons, tabac & cannabis
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1810, 'Alcool (SAQ, microbrasseries)', 1800, 'expense', '#7c3aed', 1, 1, 'categoriesSeed.consommation.alcool');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1820, 'Cannabis (SQDC)', 1800, 'expense', '#6d28d9', 1, 2, 'categoriesSeed.consommation.cannabis');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1830, 'Tabac', 1800, 'expense', '#5b21b6', 1, 3, 'categoriesSeed.consommation.tabac');
-- 1900 — Finances & obligations
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1910, 'Frais bancaires', 1900, 'expense', '#6b7280', 0, 1, 'categoriesSeed.finances.fraisBancaires.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1911, 'Frais de compte', 1910, 'expense', '#6b7280', 1, 1, 'categoriesSeed.finances.fraisBancaires.compte');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1912, 'Intérêts & frais de crédit', 1910, 'expense', '#9ca3af', 1, 2, 'categoriesSeed.finances.fraisBancaires.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1913, 'Frais de change', 1910, 'expense', '#d1d5db', 1, 3, 'categoriesSeed.finances.fraisBancaires.change');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1920, 'Impôts & taxes', 1900, 'expense', '#dc2626', 0, 2, 'categoriesSeed.finances.impots.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1921, 'Impôt fédéral', 1920, 'expense', '#dc2626', 1, 1, 'categoriesSeed.finances.impots.federal');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1922, 'Impôt provincial', 1920, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.finances.impots.provincial');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1923, 'Acomptes provisionnels', 1920, 'expense', '#991b1b', 1, 3, 'categoriesSeed.finances.impots.acomptes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1930, 'Dons & cotisations', 1900, 'expense', '#ec4899', 0, 3, 'categoriesSeed.finances.dons.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1931, 'Dons de charité', 1930, 'expense', '#ec4899', 1, 1, 'categoriesSeed.finances.dons.charite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1932, 'Cotisations professionnelles', 1930, 'expense', '#f472b6', 1, 2, 'categoriesSeed.finances.dons.professionnelles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1933, 'Cotisations syndicales', 1930, 'expense', '#f9a8d4', 1, 3, 'categoriesSeed.finances.dons.syndicales');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1940, 'Cadeaux', 1900, 'expense', '#f43f5e', 1, 4, 'categoriesSeed.finances.cadeaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1945, 'Retrait cash', 1900, 'expense', '#57534e', 1, 5, 'categoriesSeed.finances.cash');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1946, 'Achats divers non catégorisés', 1900, 'expense', '#78716c', 1, 6, 'categoriesSeed.finances.divers');
-- 1950 — Transferts & placements
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1960, 'Épargne & placements', 1950, 'transfer', '#0ea5e9', 0, 1, 'categoriesSeed.transferts.epargne.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1961, 'REER / RRSP', 1960, 'transfer', '#0ea5e9', 1, 1, 'categoriesSeed.transferts.epargne.reer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1962, 'CELI / TFSA', 1960, 'transfer', '#0284c7', 1, 2, 'categoriesSeed.transferts.epargne.celi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1963, 'REEE / RESP', 1960, 'transfer', '#0369a1', 1, 3, 'categoriesSeed.transferts.epargne.reee');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1964, 'Compte non-enregistré', 1960, 'transfer', '#075985', 1, 4, 'categoriesSeed.transferts.epargne.nonEnregistre');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1965, 'Fonds d''urgence', 1960, 'transfer', '#0c4a6e', 1, 5, 'categoriesSeed.transferts.epargne.urgence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1970, 'Remboursement de dette', 1950, 'transfer', '#7c3aed', 0, 2, 'categoriesSeed.transferts.dette.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1971, 'Paiement carte crédit', 1970, 'transfer', '#7c3aed', 1, 1, 'categoriesSeed.transferts.dette.carteCredit');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1972, 'Remboursement prêt étudiant', 1970, 'transfer', '#8b5cf6', 1, 2, 'categoriesSeed.transferts.dette.etudiant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1973, 'Remboursement prêt perso', 1970, 'transfer', '#a78bfa', 1, 3, 'categoriesSeed.transferts.dette.personnel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1980, 'Transferts internes', 1950, 'transfer', '#64748b', 1, 3, 'categoriesSeed.transferts.internes');
-- ============================================================================
-- Keywords — Canadian suppliers (150+ entries)
-- ============================================================================
-- Alimentation > Épicerie régulière (1111)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('METRO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IGA', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MAXI', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUPER C', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LOBLAWS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROVIGO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ADONIS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WHOLE FOODS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AVRIL', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RACHELLE-BERY', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COSTCO', 1111, 50);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WALMART', 1111, 50);
-- Épicerie > Boucherie (1112)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCHERIE', 1112, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('POISSONNERIE', 1112, 0);
-- Épicerie > Boulangerie (1113)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOULANGERIE', 1113, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PATISSERIE', 1113, 0);
-- Épicerie > Dépanneur (1114)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPANNEUR', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('7-ELEVEN', 1114, 0);
-- Restauration > Restaurant (1121)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RESTAURANT', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRASSERIE', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BISTRO', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUSHI', 1121, 0);
-- Restauration > Café (1122)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STARBUCKS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIM HORTONS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SECOND CUP', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VAN HOUTTE', 1122, 0);
-- Restauration > Fast food (1123)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MCDONALD', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUBWAY', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('A&W', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BURGER KING', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KFC', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOMINOS', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PIZZA HUT', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELLE PROVINCE', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ST-HUBERT', 1123, 0);
-- Restauration > Livraison (1124)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DD/DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER EATS', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SKIPTHEDISHES', 1124, 0);
-- Logement > Hypothèque (1212)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MTG/HYP', 1212, 0);
-- Logement > Taxes municipales (1213)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('M-ST-HILAIRE TX', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXES MUNICIPALES', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CSS PATRIOT', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXE SCOLAIRE', 1213, 0);
-- Électricité (1221)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO-QUEBEC', 1221, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO QUEBEC', 1221, 0);
-- Gaz (1222)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ENERGIR', 1222, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAZ METRO', 1222, 0);
-- Internet (1231)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIDEOTRON', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL INTERNET', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORICOM', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COGECO', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EBOX', 1231, 0);
-- Mobile (1232)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIZZ', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KOODO', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PUBLIC MOBILE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIRGIN', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL MOBILITE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TELUS MOBILE', 1232, 0);
-- Entretien maison (1241)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IKEA', 1241, 0);
-- Matériaux & outils (1243)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANADIAN TIRE', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANAC', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RONA', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HOME DEPOT', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BMR', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRINCESS AUTO', 1243, 0);
-- Assurance habitation (1250)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELAIR', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRYSM', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INTACT ASSURANCE', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DESJARDINS ASSURANCE', 1250, 0);
-- Meubles (1311)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TANGUAY', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEON', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STRUCTUBE', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRICK', 1311, 0);
-- Électroménagers (1312)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BEST BUY', 1312, 0);
-- Décoration (1313)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCLAIR', 1313, 0);
-- Vêtements (1410)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIQLO', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WINNERS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SIMONS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARKS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('H&M', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OLD NAVY', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAP', 1410, 0);
-- Transport — Essence (1512)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SHELL', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ESSO', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ULTRAMAR', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO-CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CREVIER', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HARNOIS', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD ESSENCE', 1512, 10);
-- Permis / SAAQ (1514)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAAQ', 1514, 0);
-- Transport public — autobus/métro (1521)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STM', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RTC', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STL', 1521, 0);
-- Train de banlieue (1522)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE MONT-SAINT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE SAINT-HUBERT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE CENTRALE', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EXO', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REM', 1522, 0);
-- Taxi / Uber (1523)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LYFT', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXI', 1523, 0);
-- Avion (1531)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR CANADA', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WESTJET', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR TRANSAT', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PORTER', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AEROPORTS DE MONTREAL', 1531, 0);
-- Hébergement (1533)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIRBNB', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HILTON', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARRIOTT', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOOKING.COM', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NORWEGIAN CRUISE', 1533, 0);
-- Pharmacie (1611)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('JEAN COUTU', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FAMILIPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHARMAPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROXIM', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIPRIX', 1611, 0);
-- Thérapies (1615)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHYSIOACTIF', 1615, 0);
-- Cinéma & spectacles (1711)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEPLEX', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEMA DU PARC', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TICKETMASTER', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CLUB SODA', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEPOINTDEVENTE', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EVENBRITE', 1711, 0);
-- Jeux vidéo (1712)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STEAMGAMES', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PLAYSTATION', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NINTENDO', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('XBOX', 1712, 0);
-- Streaming vidéo (1713)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NETFLIX', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRIMEVIDEO', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DISNEY PLUS', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRAVE', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE TV', 1713, 0);
-- Streaming musique (1714)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPOTIFY', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('YOUTUBE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIDAL', 1714, 0);
-- Jouets & hobbies (1715)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEGO', 1715, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TOYS R US', 1715, 0);
-- Équipement sportif (1722)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MOUNTAIN EQUIPMENT', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LA CORDEE', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DECATHLON', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPORTS EXPERTS', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ATMOSPHERE', 1722, 0);
-- Parcs & activités (1723)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SEPAQ', 1723, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BLOC SHOP', 1723, 0);
-- Lecture (1741)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RENAUD-BRAY', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INDIGO', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARCHAMBAULT', 1741, 0);
-- Animaux — nourriture (1751)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MONDOU', 1751, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PET SMART', 1751, 0);
-- Alcool (1810)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAQ', 1810, 0);
-- Cannabis (1820)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SQDC', 1820, 0);
-- Frais bancaires (1911)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROGRAMME PERFORMANCE', 1911, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FRAIS MENSUELS', 1911, 0);
-- Impôts (1921, 1922)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GOUV. QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REVENU QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARC IMPOT', 1921, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRA TAX', 1921, 0);
-- Dons de charité (1931)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OXFAM', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CENTRAIDE', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FPA', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CROIX-ROUGE', 1931, 0);
-- Cotisations professionnelles (1932)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORDRE DES COMPTABL', 1932, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CPA CANADA', 1932, 0);
-- Cadeaux (1940)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DANS UN JARDIN', 1940, 0);
-- Divers (1946) — catch-all marketplace
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMAZON', 1946, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMZN', 1946, 0);
-- Placements (1964)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WS INVESTMENTS', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WEALTHSIMPLE', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PEAK INVESTMENT', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DYNAMIC FUND', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIDELITY', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AGF', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('QUESTRADE', 1964, 0);
-- Paie (1011)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAY/PAY', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPOT PAIE', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAYROLL', 1011, 0);

View file

@ -0,0 +1,747 @@
-- Consolidated schema for new profile databases
-- This file bakes in the base schema + all migrations (v3-v8) and pre-seeds
-- the v1 IPC-aligned category taxonomy so that brand-new profiles immediately
-- use the new standard. Existing profiles keep their v2 seed and are only
-- tagged via the migration (categories_schema_version = 'v2').
-- Used ONLY for initializing new profile databases (not for the default profile).
CREATE TABLE IF NOT EXISTS import_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
date_format TEXT NOT NULL DEFAULT '%d/%m/%Y',
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
column_mapping TEXT NOT NULL,
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS imported_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER NOT NULL,
filename TEXT NOT NULL,
file_hash TEXT NOT NULL,
import_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
row_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'completed',
notes TEXT,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE CASCADE,
UNIQUE(source_id, filename)
);
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
parent_id INTEGER,
color TEXT,
icon TEXT,
type TEXT NOT NULL DEFAULT 'expense',
is_active INTEGER NOT NULL DEFAULT 1,
is_inputable INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
i18n_key TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
normalized_name TEXT NOT NULL,
category_id INTEGER,
is_active INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
category_id INTEGER NOT NULL,
supplier_id INTEGER,
priority INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
UNIQUE(keyword, category_id)
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
description TEXT NOT NULL,
amount REAL NOT NULL,
category_id INTEGER,
supplier_id INTEGER,
source_id INTEGER,
file_id INTEGER,
original_description TEXT,
notes TEXT,
is_manually_categorized INTEGER NOT NULL DEFAULT 0,
is_split INTEGER NOT NULL DEFAULT 0,
parent_transaction_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE SET NULL,
FOREIGN KEY (file_id) REFERENCES imported_files(id) ON DELETE SET NULL,
FOREIGN KEY (parent_transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS adjustments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
date DATE NOT NULL,
is_recurring INTEGER NOT NULL DEFAULT 0,
recurrence_rule TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS adjustment_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
adjustment_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
description TEXT,
FOREIGN KEY (adjustment_id) REFERENCES adjustments(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS budget_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
amount REAL NOT NULL,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(category_id, year, month)
);
CREATE TABLE IF NOT EXISTS budget_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS budget_template_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
template_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (template_id) REFERENCES budget_templates(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(template_id, category_id)
);
CREATE TABLE IF NOT EXISTS import_config_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
column_mapping TEXT NOT NULL,
amount_mode TEXT NOT NULL DEFAULT 'single',
sign_convention TEXT NOT NULL DEFAULT 'negative_expense',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_preferences (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(date);
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id);
CREATE INDEX IF NOT EXISTS idx_transactions_supplier ON transactions(supplier_id);
CREATE INDEX IF NOT EXISTS idx_transactions_source ON transactions(source_id);
CREATE INDEX IF NOT EXISTS idx_transactions_file ON transactions(file_id);
CREATE INDEX IF NOT EXISTS idx_transactions_parent ON transactions(parent_transaction_id);
CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_categories_type ON categories(type);
CREATE INDEX IF NOT EXISTS idx_suppliers_category ON suppliers(category_id);
CREATE INDEX IF NOT EXISTS idx_suppliers_normalized ON suppliers(normalized_name);
CREATE INDEX IF NOT EXISTS idx_keywords_category ON keywords(category_id);
CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON keywords(keyword);
CREATE INDEX IF NOT EXISTS idx_budget_entries_period ON budget_entries(year, month);
CREATE INDEX IF NOT EXISTS idx_adjustment_entries_adjustment ON adjustment_entries(adjustment_id);
CREATE INDEX IF NOT EXISTS idx_imported_files_source ON imported_files(source_id);
-- ============================================================================
-- Balance sheet (Bilan) — Migration v9 mirror for new profiles
-- ============================================================================
-- 5 tables + 7 indexes + seeded categories. Kept in sync with
-- `balance_schema.sql` (the source of truth applied by Migration v9 in lib.rs).
-- New profiles created from this consolidated schema get the balance feature
-- preinstalled without needing to replay v9 separately.
CREATE TABLE IF NOT EXISTS balance_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE,
i18n_key TEXT NOT NULL,
kind TEXT NOT NULL CHECK(kind IN ('simple','priced')),
sort_order INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
is_seed INTEGER NOT NULL DEFAULT 0,
asset_type TEXT CHECK(asset_type IS NULL OR asset_type IN ('stock','crypto'))
);
CREATE TABLE IF NOT EXISTS balance_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
balance_category_id INTEGER NOT NULL,
name TEXT NOT NULL,
symbol TEXT,
currency TEXT NOT NULL DEFAULT 'CAD' CHECK(currency = 'CAD'),
notes TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
archived_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (balance_category_id) REFERENCES balance_categories(id) ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS balance_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_date DATE NOT NULL UNIQUE,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS balance_snapshot_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_id INTEGER NOT NULL,
account_id INTEGER NOT NULL,
quantity REAL,
unit_price REAL,
value REAL NOT NULL,
price_source TEXT,
price_fetched_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (snapshot_id) REFERENCES balance_snapshots(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE RESTRICT,
UNIQUE(snapshot_id, account_id),
CHECK (
(quantity IS NULL AND unit_price IS NULL)
OR (quantity IS NOT NULL AND unit_price IS NOT NULL)
)
);
CREATE TABLE IF NOT EXISTS balance_account_transfers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
transaction_id INTEGER NOT NULL,
direction TEXT NOT NULL CHECK(direction IN ('in','out')),
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE CASCADE,
FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE RESTRICT,
UNIQUE(transaction_id, account_id)
);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_category ON balance_accounts(balance_category_id);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_active ON balance_accounts(is_active) WHERE is_active = 1;
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_snapshot ON balance_snapshot_lines(snapshot_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_account ON balance_snapshot_lines(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_account ON balance_account_transfers(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_transaction ON balance_account_transfers(transaction_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshots_date ON balance_snapshots(snapshot_date);
INSERT OR IGNORE INTO balance_categories (key, i18n_key, kind, sort_order, is_seed, asset_type) VALUES
('cash', 'balance.category.cash', 'simple', 10, 1, NULL),
('tfsa', 'balance.category.tfsa', 'simple', 20, 1, NULL),
('rrsp', 'balance.category.rrsp', 'simple', 30, 1, NULL),
('fund', 'balance.category.fund', 'simple', 40, 1, NULL),
('other', 'balance.category.other', 'simple', 50, 1, NULL),
('stock', 'balance.category.stock', 'priced', 60, 1, 'stock'),
('crypto', 'balance.category.crypto', 'priced', 70, 1, 'crypto');
-- Default preferences (new profiles ship with the v1 IPC taxonomy)
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('language', 'fr');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('theme', 'light');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('currency', 'EUR');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('date_format', 'DD/MM/YYYY');
INSERT OR REPLACE INTO user_preferences (key, value) VALUES ('categories_schema_version', 'v1');
-- ============================================================================
-- Seed v1 — IPC Statistique Canada-aligned, 3 levels, Canada/Québec
-- ----------------------------------------------------------------------------
-- Reset any pre-existing category data (possible when tauri-plugin-sql runs
-- the historical v2 seed migration on this fresh DB before this script
-- executes). Keywords/categories are wiped and re-inserted with the v1 IDs
-- (1000+ range) so existing references in migrations v3-v7 stay inert.
-- ============================================================================
DELETE FROM keywords;
UPDATE transactions SET category_id = NULL;
DELETE FROM categories;
-- LEVEL 1 — Roots (9 IPC components + Revenus + Transferts)
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1000, 'Revenus', NULL, 'income', '#16a34a', 0, 1, 'categoriesSeed.revenus.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1100, 'Alimentation', NULL, 'expense', '#ea580c', 0, 2, 'categoriesSeed.alimentation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1200, 'Logement', NULL, 'expense', '#dc2626', 0, 3, 'categoriesSeed.logement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1300, 'Ménage & ameublement', NULL, 'expense', '#ca8a04', 0, 4, 'categoriesSeed.menage.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1400, 'Vêtements & chaussures', NULL, 'expense', '#d946ef', 0, 5, 'categoriesSeed.vetements.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1500, 'Transport', NULL, 'expense', '#2563eb', 0, 6, 'categoriesSeed.transport.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1600, 'Santé & soins personnels', NULL, 'expense', '#f43f5e', 0, 7, 'categoriesSeed.sante.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1700, 'Loisirs, formation & lecture', NULL, 'expense', '#8b5cf6', 0, 8, 'categoriesSeed.loisirs.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1800, 'Boissons, tabac & cannabis', NULL, 'expense', '#7c3aed', 0, 9, 'categoriesSeed.consommation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1900, 'Finances & obligations', NULL, 'expense', '#6b7280', 0, 10, 'categoriesSeed.finances.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1950, 'Transferts & placements', NULL, 'transfer', '#0ea5e9', 0, 11, 'categoriesSeed.transferts.root');
-- 1000 — Revenus
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1010, 'Emploi', 1000, 'income', '#22c55e', 0, 1, 'categoriesSeed.revenus.emploi.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1011, 'Paie régulière', 1010, 'income', '#22c55e', 1, 1, 'categoriesSeed.revenus.emploi.paie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1012, 'Primes & bonus', 1010, 'income', '#4ade80', 1, 2, 'categoriesSeed.revenus.emploi.primes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1013, 'Travail autonome', 1010, 'income', '#86efac', 1, 3, 'categoriesSeed.revenus.emploi.autonome');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1020, 'Gouvernemental', 1000, 'income', '#16a34a', 0, 2, 'categoriesSeed.revenus.gouvernemental.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1021, 'Remboursement impôt', 1020, 'income', '#16a34a', 1, 1, 'categoriesSeed.revenus.gouvernemental.remboursementImpot');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1022, 'Allocations familiales', 1020, 'income', '#15803d', 1, 2, 'categoriesSeed.revenus.gouvernemental.allocations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1023, 'Crédits TPS/TVQ', 1020, 'income', '#166534', 1, 3, 'categoriesSeed.revenus.gouvernemental.credits');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1024, 'Assurance-emploi / RQAP', 1020, 'income', '#14532d', 1, 4, 'categoriesSeed.revenus.gouvernemental.assuranceEmploi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1030, 'Revenus de placement', 1000, 'income', '#10b981', 0, 3, 'categoriesSeed.revenus.placement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1031, 'Intérêts & dividendes', 1030, 'income', '#10b981', 1, 1, 'categoriesSeed.revenus.placement.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1032, 'Gains en capital', 1030, 'income', '#059669', 1, 2, 'categoriesSeed.revenus.placement.capital');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1033, 'Revenus locatifs', 1030, 'income', '#047857', 1, 3, 'categoriesSeed.revenus.placement.locatifs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1090, 'Autres revenus', 1000, 'income', '#84cc16', 1, 9, 'categoriesSeed.revenus.autres');
-- 1100 — Alimentation
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1110, 'Épicerie & marché', 1100, 'expense', '#ea580c', 0, 1, 'categoriesSeed.alimentation.epicerie.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1111, 'Épicerie régulière', 1110, 'expense', '#ea580c', 1, 1, 'categoriesSeed.alimentation.epicerie.reguliere');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1112, 'Boucherie & poissonnerie', 1110, 'expense', '#c2410c', 1, 2, 'categoriesSeed.alimentation.epicerie.boucherie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1113, 'Boulangerie & pâtisserie', 1110, 'expense', '#9a3412', 1, 3, 'categoriesSeed.alimentation.epicerie.boulangerie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1114, 'Dépanneur', 1110, 'expense', '#7c2d12', 1, 4, 'categoriesSeed.alimentation.epicerie.depanneur');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1115, 'Marché & produits spécialisés', 1110, 'expense', '#fb923c', 1, 5, 'categoriesSeed.alimentation.epicerie.marche');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1120, 'Restauration', 1100, 'expense', '#f97316', 0, 2, 'categoriesSeed.alimentation.restauration.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1121, 'Restaurant', 1120, 'expense', '#f97316', 1, 1, 'categoriesSeed.alimentation.restauration.restaurant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1122, 'Café & boulangerie rapide', 1120, 'expense', '#fb923c', 1, 2, 'categoriesSeed.alimentation.restauration.cafe');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1123, 'Restauration rapide', 1120, 'expense', '#fdba74', 1, 3, 'categoriesSeed.alimentation.restauration.fastfood');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1124, 'Livraison à domicile', 1120, 'expense', '#fed7aa', 1, 4, 'categoriesSeed.alimentation.restauration.livraison');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1125, 'Cantine & cafétéria', 1120, 'expense', '#ffedd5', 1, 5, 'categoriesSeed.alimentation.restauration.cantine');
-- 1200 — Logement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1210, 'Habitation principale', 1200, 'expense', '#dc2626', 0, 1, 'categoriesSeed.logement.habitation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1211, 'Loyer', 1210, 'expense', '#dc2626', 1, 1, 'categoriesSeed.logement.habitation.loyer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1212, 'Hypothèque', 1210, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.logement.habitation.hypotheque');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1213, 'Taxes municipales & scolaires', 1210, 'expense', '#991b1b', 1, 3, 'categoriesSeed.logement.habitation.taxes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1214, 'Charges de copropriété', 1210, 'expense', '#7f1d1d', 1, 4, 'categoriesSeed.logement.habitation.copropriete');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1220, 'Services publics', 1200, 'expense', '#ef4444', 0, 2, 'categoriesSeed.logement.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1221, 'Électricité', 1220, 'expense', '#ef4444', 1, 1, 'categoriesSeed.logement.services.electricite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1222, 'Gaz naturel', 1220, 'expense', '#f87171', 1, 2, 'categoriesSeed.logement.services.gaz');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1223, 'Chauffage (mazout, propane)', 1220, 'expense', '#fca5a5', 1, 3, 'categoriesSeed.logement.services.chauffage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1224, 'Eau & égouts', 1220, 'expense', '#fecaca', 1, 4, 'categoriesSeed.logement.services.eau');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1230, 'Communications', 1200, 'expense', '#6366f1', 0, 3, 'categoriesSeed.logement.communications.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1231, 'Internet résidentiel', 1230, 'expense', '#6366f1', 1, 1, 'categoriesSeed.logement.communications.internet');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1232, 'Téléphonie mobile', 1230, 'expense', '#818cf8', 1, 2, 'categoriesSeed.logement.communications.mobile');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1233, 'Téléphonie résidentielle', 1230, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.logement.communications.residentielle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1234, 'Câblodistribution & streaming TV', 1230, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.logement.communications.streamingTv');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1240, 'Entretien & réparations', 1200, 'expense', '#e11d48', 0, 4, 'categoriesSeed.logement.entretien.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1241, 'Entretien général', 1240, 'expense', '#e11d48', 1, 1, 'categoriesSeed.logement.entretien.general');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1242, 'Rénovations', 1240, 'expense', '#be123c', 1, 2, 'categoriesSeed.logement.entretien.renovations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1243, 'Matériaux & outils', 1240, 'expense', '#9f1239', 1, 3, 'categoriesSeed.logement.entretien.materiaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1244, 'Aménagement paysager', 1240, 'expense', '#881337', 1, 4, 'categoriesSeed.logement.entretien.paysager');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1250, 'Assurance habitation', 1200, 'expense', '#14b8a6', 1, 5, 'categoriesSeed.logement.assurance');
-- 1300 — Ménage & ameublement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1310, 'Ameublement', 1300, 'expense', '#ca8a04', 0, 1, 'categoriesSeed.menage.ameublement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1311, 'Meubles', 1310, 'expense', '#ca8a04', 1, 1, 'categoriesSeed.menage.ameublement.meubles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1312, 'Électroménagers', 1310, 'expense', '#a16207', 1, 2, 'categoriesSeed.menage.ameublement.electromenagers');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1313, 'Décoration', 1310, 'expense', '#854d0e', 1, 3, 'categoriesSeed.menage.ameublement.decoration');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1320, 'Fournitures ménagères', 1300, 'expense', '#eab308', 0, 2, 'categoriesSeed.menage.fournitures.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1321, 'Produits d''entretien', 1320, 'expense', '#eab308', 1, 1, 'categoriesSeed.menage.fournitures.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1322, 'Literie & linge de maison', 1320, 'expense', '#facc15', 1, 2, 'categoriesSeed.menage.fournitures.literie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1323, 'Vaisselle & ustensiles', 1320, 'expense', '#fde047', 1, 3, 'categoriesSeed.menage.fournitures.vaisselle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1330, 'Services domestiques', 1300, 'expense', '#fbbf24', 0, 3, 'categoriesSeed.menage.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1331, 'Ménage & nettoyage', 1330, 'expense', '#fbbf24', 1, 1, 'categoriesSeed.menage.services.nettoyage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1332, 'Buanderie & pressing', 1330, 'expense', '#fcd34d', 1, 2, 'categoriesSeed.menage.services.buanderie');
-- 1400 — Vêtements & chaussures
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1410, 'Vêtements adultes', 1400, 'expense', '#d946ef', 1, 1, 'categoriesSeed.vetements.adultes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1420, 'Vêtements enfants', 1400, 'expense', '#c026d3', 1, 2, 'categoriesSeed.vetements.enfants');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1430, 'Chaussures', 1400, 'expense', '#a21caf', 1, 3, 'categoriesSeed.vetements.chaussures');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1440, 'Accessoires & bijoux', 1400, 'expense', '#86198f', 1, 4, 'categoriesSeed.vetements.accessoires');
-- 1500 — Transport
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1510, 'Véhicule personnel', 1500, 'expense', '#2563eb', 0, 1, 'categoriesSeed.transport.vehicule.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1511, 'Achat / location véhicule', 1510, 'expense', '#2563eb', 1, 1, 'categoriesSeed.transport.vehicule.achat');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1512, 'Essence', 1510, 'expense', '#1d4ed8', 1, 2, 'categoriesSeed.transport.vehicule.essence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1513, 'Entretien & réparations auto', 1510, 'expense', '#1e40af', 1, 3, 'categoriesSeed.transport.vehicule.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1514, 'Immatriculation & permis', 1510, 'expense', '#1e3a8a', 1, 4, 'categoriesSeed.transport.vehicule.immatriculation');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1515, 'Stationnement & péages', 1510, 'expense', '#3b82f6', 1, 5, 'categoriesSeed.transport.vehicule.stationnement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1516, 'Assurance auto', 1510, 'expense', '#60a5fa', 1, 6, 'categoriesSeed.transport.vehicule.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1520, 'Transport public', 1500, 'expense', '#0ea5e9', 0, 2, 'categoriesSeed.transport.public.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1521, 'Autobus & métro', 1520, 'expense', '#0ea5e9', 1, 1, 'categoriesSeed.transport.public.autobus');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1522, 'Train de banlieue', 1520, 'expense', '#0284c7', 1, 2, 'categoriesSeed.transport.public.train');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1523, 'Taxi & covoiturage', 1520, 'expense', '#0369a1', 1, 3, 'categoriesSeed.transport.public.taxi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1530, 'Voyages longue distance', 1500, 'expense', '#38bdf8', 0, 3, 'categoriesSeed.transport.voyages.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1531, 'Avion', 1530, 'expense', '#38bdf8', 1, 1, 'categoriesSeed.transport.voyages.avion');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1532, 'Train & autocar', 1530, 'expense', '#7dd3fc', 1, 2, 'categoriesSeed.transport.voyages.trainAutocar');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1533, 'Hébergement', 1530, 'expense', '#bae6fd', 1, 3, 'categoriesSeed.transport.voyages.hebergement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1534, 'Location véhicule voyage', 1530, 'expense', '#e0f2fe', 1, 4, 'categoriesSeed.transport.voyages.location');
-- 1600 — Santé & soins personnels
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1610, 'Soins médicaux', 1600, 'expense', '#f43f5e', 0, 1, 'categoriesSeed.sante.medicaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1611, 'Pharmacie', 1610, 'expense', '#f43f5e', 1, 1, 'categoriesSeed.sante.medicaux.pharmacie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1612, 'Consultations médicales', 1610, 'expense', '#e11d48', 1, 2, 'categoriesSeed.sante.medicaux.consultations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1613, 'Dentiste & orthodontiste', 1610, 'expense', '#be123c', 1, 3, 'categoriesSeed.sante.medicaux.dentiste');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1614, 'Optométrie & lunettes', 1610, 'expense', '#9f1239', 1, 4, 'categoriesSeed.sante.medicaux.optometrie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1615, 'Thérapies (physio, psycho, etc.)', 1610, 'expense', '#881337', 1, 5, 'categoriesSeed.sante.medicaux.therapies');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1616, 'Assurance santé complémentaire', 1610, 'expense', '#fb7185', 1, 6, 'categoriesSeed.sante.medicaux.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1620, 'Soins personnels', 1600, 'expense', '#fb7185', 0, 2, 'categoriesSeed.sante.personnels.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1621, 'Coiffure & esthétique', 1620, 'expense', '#fb7185', 1, 1, 'categoriesSeed.sante.personnels.coiffure');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1622, 'Produits de soins corporels', 1620, 'expense', '#fda4af', 1, 2, 'categoriesSeed.sante.personnels.soins');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1630, 'Assurance vie & invalidité', 1600, 'expense', '#14b8a6', 1, 3, 'categoriesSeed.sante.assuranceVie');
-- 1700 — Loisirs, formation & lecture
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1710, 'Divertissement', 1700, 'expense', '#8b5cf6', 0, 1, 'categoriesSeed.loisirs.divertissement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1711, 'Cinéma & spectacles', 1710, 'expense', '#8b5cf6', 1, 1, 'categoriesSeed.loisirs.divertissement.cinema');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1712, 'Jeux vidéo & consoles', 1710, 'expense', '#a78bfa', 1, 2, 'categoriesSeed.loisirs.divertissement.jeux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1713, 'Streaming vidéo', 1710, 'expense', '#c4b5fd', 1, 3, 'categoriesSeed.loisirs.divertissement.streamingVideo');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1714, 'Streaming musique & audio', 1710, 'expense', '#ddd6fe', 1, 4, 'categoriesSeed.loisirs.divertissement.streamingMusique');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1715, 'Jouets & passe-temps', 1710, 'expense', '#ede9fe', 1, 5, 'categoriesSeed.loisirs.divertissement.jouets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1720, 'Sports & plein air', 1700, 'expense', '#22c55e', 0, 2, 'categoriesSeed.loisirs.sports.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1721, 'Abonnements sportifs', 1720, 'expense', '#22c55e', 1, 1, 'categoriesSeed.loisirs.sports.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1722, 'Équipement sportif', 1720, 'expense', '#4ade80', 1, 2, 'categoriesSeed.loisirs.sports.equipement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1723, 'Parcs & activités plein air', 1720, 'expense', '#86efac', 1, 3, 'categoriesSeed.loisirs.sports.parcs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1730, 'Formation & éducation', 1700, 'expense', '#6366f1', 0, 3, 'categoriesSeed.loisirs.formation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1731, 'Scolarité (frais)', 1730, 'expense', '#6366f1', 1, 1, 'categoriesSeed.loisirs.formation.scolarite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1732, 'Matériel scolaire', 1730, 'expense', '#818cf8', 1, 2, 'categoriesSeed.loisirs.formation.materiel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1733, 'Cours & certifications', 1730, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.loisirs.formation.cours');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1734, 'Abonnements professionnels', 1730, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.loisirs.formation.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1740, 'Lecture & médias', 1700, 'expense', '#ec4899', 0, 4, 'categoriesSeed.loisirs.lecture.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1741, 'Livres', 1740, 'expense', '#ec4899', 1, 1, 'categoriesSeed.loisirs.lecture.livres');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1742, 'Journaux & magazines', 1740, 'expense', '#f472b6', 1, 2, 'categoriesSeed.loisirs.lecture.journaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1750, 'Animaux de compagnie', 1700, 'expense', '#a855f7', 0, 5, 'categoriesSeed.loisirs.animaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1751, 'Nourriture & accessoires animaux', 1750, 'expense', '#a855f7', 1, 1, 'categoriesSeed.loisirs.animaux.nourriture');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1752, 'Vétérinaire', 1750, 'expense', '#c084fc', 1, 2, 'categoriesSeed.loisirs.animaux.veterinaire');
-- 1800 — Boissons, tabac & cannabis
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1810, 'Alcool (SAQ, microbrasseries)', 1800, 'expense', '#7c3aed', 1, 1, 'categoriesSeed.consommation.alcool');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1820, 'Cannabis (SQDC)', 1800, 'expense', '#6d28d9', 1, 2, 'categoriesSeed.consommation.cannabis');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1830, 'Tabac', 1800, 'expense', '#5b21b6', 1, 3, 'categoriesSeed.consommation.tabac');
-- 1900 — Finances & obligations
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1910, 'Frais bancaires', 1900, 'expense', '#6b7280', 0, 1, 'categoriesSeed.finances.fraisBancaires.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1911, 'Frais de compte', 1910, 'expense', '#6b7280', 1, 1, 'categoriesSeed.finances.fraisBancaires.compte');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1912, 'Intérêts & frais de crédit', 1910, 'expense', '#9ca3af', 1, 2, 'categoriesSeed.finances.fraisBancaires.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1913, 'Frais de change', 1910, 'expense', '#d1d5db', 1, 3, 'categoriesSeed.finances.fraisBancaires.change');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1920, 'Impôts & taxes', 1900, 'expense', '#dc2626', 0, 2, 'categoriesSeed.finances.impots.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1921, 'Impôt fédéral', 1920, 'expense', '#dc2626', 1, 1, 'categoriesSeed.finances.impots.federal');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1922, 'Impôt provincial', 1920, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.finances.impots.provincial');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1923, 'Acomptes provisionnels', 1920, 'expense', '#991b1b', 1, 3, 'categoriesSeed.finances.impots.acomptes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1930, 'Dons & cotisations', 1900, 'expense', '#ec4899', 0, 3, 'categoriesSeed.finances.dons.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1931, 'Dons de charité', 1930, 'expense', '#ec4899', 1, 1, 'categoriesSeed.finances.dons.charite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1932, 'Cotisations professionnelles', 1930, 'expense', '#f472b6', 1, 2, 'categoriesSeed.finances.dons.professionnelles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1933, 'Cotisations syndicales', 1930, 'expense', '#f9a8d4', 1, 3, 'categoriesSeed.finances.dons.syndicales');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1940, 'Cadeaux', 1900, 'expense', '#f43f5e', 1, 4, 'categoriesSeed.finances.cadeaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1945, 'Retrait cash', 1900, 'expense', '#57534e', 1, 5, 'categoriesSeed.finances.cash');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1946, 'Achats divers non catégorisés', 1900, 'expense', '#78716c', 1, 6, 'categoriesSeed.finances.divers');
-- 1950 — Transferts & placements
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1960, 'Épargne & placements', 1950, 'transfer', '#0ea5e9', 0, 1, 'categoriesSeed.transferts.epargne.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1961, 'REER / RRSP', 1960, 'transfer', '#0ea5e9', 1, 1, 'categoriesSeed.transferts.epargne.reer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1962, 'CELI / TFSA', 1960, 'transfer', '#0284c7', 1, 2, 'categoriesSeed.transferts.epargne.celi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1963, 'REEE / RESP', 1960, 'transfer', '#0369a1', 1, 3, 'categoriesSeed.transferts.epargne.reee');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1964, 'Compte non-enregistré', 1960, 'transfer', '#075985', 1, 4, 'categoriesSeed.transferts.epargne.nonEnregistre');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1965, 'Fonds d''urgence', 1960, 'transfer', '#0c4a6e', 1, 5, 'categoriesSeed.transferts.epargne.urgence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1970, 'Remboursement de dette', 1950, 'transfer', '#7c3aed', 0, 2, 'categoriesSeed.transferts.dette.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1971, 'Paiement carte crédit', 1970, 'transfer', '#7c3aed', 1, 1, 'categoriesSeed.transferts.dette.carteCredit');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1972, 'Remboursement prêt étudiant', 1970, 'transfer', '#8b5cf6', 1, 2, 'categoriesSeed.transferts.dette.etudiant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1973, 'Remboursement prêt perso', 1970, 'transfer', '#a78bfa', 1, 3, 'categoriesSeed.transferts.dette.personnel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1980, 'Transferts internes', 1950, 'transfer', '#64748b', 1, 3, 'categoriesSeed.transferts.internes');
-- ============================================================================
-- Keywords — Canadian suppliers (150+ entries)
-- ============================================================================
-- Alimentation > Épicerie régulière (1111)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('METRO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IGA', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MAXI', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUPER C', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LOBLAWS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROVIGO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ADONIS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WHOLE FOODS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AVRIL', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RACHELLE-BERY', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COSTCO', 1111, 50);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WALMART', 1111, 50);
-- Épicerie > Boucherie (1112)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCHERIE', 1112, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('POISSONNERIE', 1112, 0);
-- Épicerie > Boulangerie (1113)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOULANGERIE', 1113, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PATISSERIE', 1113, 0);
-- Épicerie > Dépanneur (1114)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPANNEUR', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('7-ELEVEN', 1114, 0);
-- Restauration > Restaurant (1121)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RESTAURANT', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRASSERIE', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BISTRO', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUSHI', 1121, 0);
-- Restauration > Café (1122)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STARBUCKS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIM HORTONS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SECOND CUP', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VAN HOUTTE', 1122, 0);
-- Restauration > Fast food (1123)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MCDONALD', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUBWAY', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('A&W', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BURGER KING', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KFC', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOMINOS', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PIZZA HUT', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELLE PROVINCE', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ST-HUBERT', 1123, 0);
-- Restauration > Livraison (1124)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DD/DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER EATS', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SKIPTHEDISHES', 1124, 0);
-- Logement > Hypothèque (1212)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MTG/HYP', 1212, 0);
-- Logement > Taxes municipales (1213)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('M-ST-HILAIRE TX', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXES MUNICIPALES', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CSS PATRIOT', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXE SCOLAIRE', 1213, 0);
-- Électricité (1221)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO-QUEBEC', 1221, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO QUEBEC', 1221, 0);
-- Gaz (1222)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ENERGIR', 1222, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAZ METRO', 1222, 0);
-- Internet (1231)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIDEOTRON', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL INTERNET', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORICOM', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COGECO', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EBOX', 1231, 0);
-- Mobile (1232)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIZZ', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KOODO', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PUBLIC MOBILE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIRGIN', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL MOBILITE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TELUS MOBILE', 1232, 0);
-- Entretien maison (1241)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IKEA', 1241, 0);
-- Matériaux & outils (1243)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANADIAN TIRE', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANAC', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RONA', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HOME DEPOT', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BMR', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRINCESS AUTO', 1243, 0);
-- Assurance habitation (1250)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELAIR', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRYSM', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INTACT ASSURANCE', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DESJARDINS ASSURANCE', 1250, 0);
-- Meubles (1311)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TANGUAY', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEON', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STRUCTUBE', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRICK', 1311, 0);
-- Électroménagers (1312)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BEST BUY', 1312, 0);
-- Décoration (1313)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCLAIR', 1313, 0);
-- Vêtements (1410)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIQLO', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WINNERS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SIMONS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARKS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('H&M', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OLD NAVY', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAP', 1410, 0);
-- Transport — Essence (1512)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SHELL', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ESSO', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ULTRAMAR', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO-CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CREVIER', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HARNOIS', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD ESSENCE', 1512, 10);
-- Permis / SAAQ (1514)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAAQ', 1514, 0);
-- Transport public — autobus/métro (1521)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STM', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RTC', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STL', 1521, 0);
-- Train de banlieue (1522)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE MONT-SAINT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE SAINT-HUBERT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE CENTRALE', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EXO', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REM', 1522, 0);
-- Taxi / Uber (1523)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LYFT', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXI', 1523, 0);
-- Avion (1531)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR CANADA', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WESTJET', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR TRANSAT', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PORTER', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AEROPORTS DE MONTREAL', 1531, 0);
-- Hébergement (1533)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIRBNB', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HILTON', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARRIOTT', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOOKING.COM', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NORWEGIAN CRUISE', 1533, 0);
-- Pharmacie (1611)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('JEAN COUTU', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FAMILIPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHARMAPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROXIM', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIPRIX', 1611, 0);
-- Thérapies (1615)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHYSIOACTIF', 1615, 0);
-- Cinéma & spectacles (1711)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEPLEX', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEMA DU PARC', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TICKETMASTER', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CLUB SODA', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEPOINTDEVENTE', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EVENBRITE', 1711, 0);
-- Jeux vidéo (1712)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STEAMGAMES', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PLAYSTATION', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NINTENDO', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('XBOX', 1712, 0);
-- Streaming vidéo (1713)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NETFLIX', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRIMEVIDEO', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DISNEY PLUS', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRAVE', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE TV', 1713, 0);
-- Streaming musique (1714)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPOTIFY', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('YOUTUBE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIDAL', 1714, 0);
-- Jouets & hobbies (1715)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEGO', 1715, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TOYS R US', 1715, 0);
-- Équipement sportif (1722)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MOUNTAIN EQUIPMENT', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LA CORDEE', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DECATHLON', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPORTS EXPERTS', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ATMOSPHERE', 1722, 0);
-- Parcs & activités (1723)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SEPAQ', 1723, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BLOC SHOP', 1723, 0);
-- Lecture (1741)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RENAUD-BRAY', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INDIGO', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARCHAMBAULT', 1741, 0);
-- Animaux — nourriture (1751)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MONDOU', 1751, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PET SMART', 1751, 0);
-- Alcool (1810)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAQ', 1810, 0);
-- Cannabis (1820)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SQDC', 1820, 0);
-- Frais bancaires (1911)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROGRAMME PERFORMANCE', 1911, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FRAIS MENSUELS', 1911, 0);
-- Impôts (1921, 1922)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GOUV. QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REVENU QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARC IMPOT', 1921, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRA TAX', 1921, 0);
-- Dons de charité (1931)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OXFAM', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CENTRAIDE', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FPA', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CROIX-ROUGE', 1931, 0);
-- Cotisations professionnelles (1932)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORDRE DES COMPTABL', 1932, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CPA CANADA', 1932, 0);
-- Cadeaux (1940)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DANS UN JARDIN', 1940, 0);
-- Divers (1946) — catch-all marketplace
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMAZON', 1946, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMZN', 1946, 0);
-- Placements (1964)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WS INVESTMENTS', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WEALTHSIMPLE', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PEAK INVESTMENT', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DYNAMIC FUND', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIDELITY', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AGF', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('QUESTRADE', 1964, 0);
-- Paie (1011)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAY/PAY', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPOT PAIE', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAYROLL', 1011, 0);

View file

@ -0,0 +1,764 @@
-- Consolidated schema for new profile databases
-- This file bakes in the base schema + all migrations (v3-v8) and pre-seeds
-- the v1 IPC-aligned category taxonomy so that brand-new profiles immediately
-- use the new standard. Existing profiles keep their v2 seed and are only
-- tagged via the migration (categories_schema_version = 'v2').
-- Used ONLY for initializing new profile databases (not for the default profile).
CREATE TABLE IF NOT EXISTS import_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
date_format TEXT NOT NULL DEFAULT '%d/%m/%Y',
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
column_mapping TEXT NOT NULL,
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS imported_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER NOT NULL,
filename TEXT NOT NULL,
file_hash TEXT NOT NULL,
import_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
row_count INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'completed',
notes TEXT,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE CASCADE,
UNIQUE(source_id, filename)
);
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
parent_id INTEGER,
color TEXT,
icon TEXT,
type TEXT NOT NULL DEFAULT 'expense',
is_active INTEGER NOT NULL DEFAULT 1,
is_inputable INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
i18n_key TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
normalized_name TEXT NOT NULL,
category_id INTEGER,
is_active INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
category_id INTEGER NOT NULL,
supplier_id INTEGER,
priority INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
UNIQUE(keyword, category_id)
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
description TEXT NOT NULL,
amount REAL NOT NULL,
category_id INTEGER,
supplier_id INTEGER,
source_id INTEGER,
file_id INTEGER,
original_description TEXT,
notes TEXT,
is_manually_categorized INTEGER NOT NULL DEFAULT 0,
is_split INTEGER NOT NULL DEFAULT 0,
parent_transaction_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL,
FOREIGN KEY (source_id) REFERENCES import_sources(id) ON DELETE SET NULL,
FOREIGN KEY (file_id) REFERENCES imported_files(id) ON DELETE SET NULL,
FOREIGN KEY (parent_transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS adjustments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
date DATE NOT NULL,
is_recurring INTEGER NOT NULL DEFAULT 0,
recurrence_rule TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS adjustment_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
adjustment_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
description TEXT,
FOREIGN KEY (adjustment_id) REFERENCES adjustments(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS budget_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
amount REAL NOT NULL,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(category_id, year, month)
);
CREATE TABLE IF NOT EXISTS budget_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS budget_template_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
template_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (template_id) REFERENCES budget_templates(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(template_id, category_id)
);
CREATE TABLE IF NOT EXISTS import_config_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
delimiter TEXT NOT NULL DEFAULT ';',
encoding TEXT NOT NULL DEFAULT 'utf-8',
date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
skip_lines INTEGER NOT NULL DEFAULT 0,
has_header INTEGER NOT NULL DEFAULT 1,
column_mapping TEXT NOT NULL,
amount_mode TEXT NOT NULL DEFAULT 'single',
sign_convention TEXT NOT NULL DEFAULT 'negative_expense',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_preferences (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(date);
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id);
CREATE INDEX IF NOT EXISTS idx_transactions_supplier ON transactions(supplier_id);
CREATE INDEX IF NOT EXISTS idx_transactions_source ON transactions(source_id);
CREATE INDEX IF NOT EXISTS idx_transactions_file ON transactions(file_id);
CREATE INDEX IF NOT EXISTS idx_transactions_parent ON transactions(parent_transaction_id);
CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_categories_type ON categories(type);
CREATE INDEX IF NOT EXISTS idx_suppliers_category ON suppliers(category_id);
CREATE INDEX IF NOT EXISTS idx_suppliers_normalized ON suppliers(normalized_name);
CREATE INDEX IF NOT EXISTS idx_keywords_category ON keywords(category_id);
CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON keywords(keyword);
CREATE INDEX IF NOT EXISTS idx_budget_entries_period ON budget_entries(year, month);
CREATE INDEX IF NOT EXISTS idx_adjustment_entries_adjustment ON adjustment_entries(adjustment_id);
CREATE INDEX IF NOT EXISTS idx_imported_files_source ON imported_files(source_id);
-- ============================================================================
-- Balance sheet (Bilan) — Migration v9 mirror for new profiles
-- ============================================================================
-- 5 tables + 7 indexes + seeded categories. Kept in sync with
-- `balance_schema.sql` (the source of truth applied by Migration v9 in lib.rs).
-- New profiles created from this consolidated schema get the balance feature
-- preinstalled without needing to replay v9 separately.
CREATE TABLE IF NOT EXISTS balance_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE,
i18n_key TEXT NOT NULL,
kind TEXT NOT NULL CHECK(kind IN ('simple','priced')),
sort_order INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
is_seed INTEGER NOT NULL DEFAULT 0,
asset_type TEXT CHECK(asset_type IS NULL OR asset_type IN ('stock','crypto'))
);
CREATE TABLE IF NOT EXISTS balance_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
balance_category_id INTEGER NOT NULL,
name TEXT NOT NULL,
symbol TEXT,
currency TEXT NOT NULL DEFAULT 'CAD' CHECK(currency = 'CAD'),
notes TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
archived_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (balance_category_id) REFERENCES balance_categories(id) ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS balance_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_date DATE NOT NULL UNIQUE,
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS balance_snapshot_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_id INTEGER NOT NULL,
account_id INTEGER NOT NULL,
quantity REAL,
unit_price REAL,
value REAL NOT NULL,
price_source TEXT,
price_fetched_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (snapshot_id) REFERENCES balance_snapshots(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE RESTRICT,
UNIQUE(snapshot_id, account_id),
CHECK (
(quantity IS NULL AND unit_price IS NULL)
OR (quantity IS NOT NULL AND unit_price IS NOT NULL)
)
);
CREATE TABLE IF NOT EXISTS balance_account_transfers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
transaction_id INTEGER NOT NULL,
direction TEXT NOT NULL CHECK(direction IN ('in','out')),
notes TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE CASCADE,
FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE RESTRICT,
UNIQUE(transaction_id, account_id)
);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_category ON balance_accounts(balance_category_id);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_active ON balance_accounts(is_active) WHERE is_active = 1;
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_snapshot ON balance_snapshot_lines(snapshot_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_account ON balance_snapshot_lines(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_account ON balance_account_transfers(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_transaction ON balance_account_transfers(transaction_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshots_date ON balance_snapshots(snapshot_date);
INSERT OR IGNORE INTO balance_categories (key, i18n_key, kind, sort_order, is_seed, asset_type) VALUES
('cash', 'balance.category.cash', 'simple', 10, 1, NULL),
('tfsa', 'balance.category.tfsa', 'simple', 20, 1, NULL),
('rrsp', 'balance.category.rrsp', 'simple', 30, 1, NULL),
('fund', 'balance.category.fund', 'simple', 40, 1, NULL),
('other', 'balance.category.other', 'simple', 50, 1, NULL),
('stock', 'balance.category.stock', 'priced', 60, 1, 'stock'),
('crypto', 'balance.category.crypto', 'priced', 70, 1, 'crypto');
-- Starter accounts (Issue #179): 4 plain accounts seeded for new profiles so
-- /balance lands non-empty. They are NOT marked as seed (no is_seed column on
-- balance_accounts) — once created they are indistinguishable from
-- user-created accounts and can be renamed/archived freely. Existing profiles
-- get the same 4 proposed via StarterAccountsModal on first /balance visit.
INSERT INTO balance_accounts (balance_category_id, name, currency, is_active) VALUES
((SELECT id FROM balance_categories WHERE key = 'cash'), 'Compte chèque', 'CAD', 1),
((SELECT id FROM balance_categories WHERE key = 'tfsa'), 'CELI', 'CAD', 1),
((SELECT id FROM balance_categories WHERE key = 'rrsp'), 'REER', 'CAD', 1),
((SELECT id FROM balance_categories WHERE key = 'other'), 'Compte non-enregistré', 'CAD', 1);
-- Default preferences (new profiles ship with the v1 IPC taxonomy)
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('language', 'fr');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('theme', 'light');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('currency', 'EUR');
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('date_format', 'DD/MM/YYYY');
INSERT OR REPLACE INTO user_preferences (key, value) VALUES ('categories_schema_version', 'v1');
-- Suppress StarterAccountsModal on first /balance visit for new profiles
-- (Issue #179). The 4 starter accounts are already seeded above, so the
-- modal would only show 4 collision rows with no actionable choice. Pre-
-- writing the pref skips that briefly-empty UX entirely. Suggestion S1
-- from PR #185 review (#187).
INSERT OR IGNORE INTO user_preferences (key, value) VALUES ('balance_starter_proposed', '{"shown_at":"seed","accepted":[]}');
-- ============================================================================
-- Seed v1 — IPC Statistique Canada-aligned, 3 levels, Canada/Québec
-- ----------------------------------------------------------------------------
-- Reset any pre-existing category data (possible when tauri-plugin-sql runs
-- the historical v2 seed migration on this fresh DB before this script
-- executes). Keywords/categories are wiped and re-inserted with the v1 IDs
-- (1000+ range) so existing references in migrations v3-v7 stay inert.
-- ============================================================================
DELETE FROM keywords;
UPDATE transactions SET category_id = NULL;
DELETE FROM categories;
-- LEVEL 1 — Roots (9 IPC components + Revenus + Transferts)
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1000, 'Revenus', NULL, 'income', '#16a34a', 0, 1, 'categoriesSeed.revenus.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1100, 'Alimentation', NULL, 'expense', '#ea580c', 0, 2, 'categoriesSeed.alimentation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1200, 'Logement', NULL, 'expense', '#dc2626', 0, 3, 'categoriesSeed.logement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1300, 'Ménage & ameublement', NULL, 'expense', '#ca8a04', 0, 4, 'categoriesSeed.menage.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1400, 'Vêtements & chaussures', NULL, 'expense', '#d946ef', 0, 5, 'categoriesSeed.vetements.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1500, 'Transport', NULL, 'expense', '#2563eb', 0, 6, 'categoriesSeed.transport.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1600, 'Santé & soins personnels', NULL, 'expense', '#f43f5e', 0, 7, 'categoriesSeed.sante.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1700, 'Loisirs, formation & lecture', NULL, 'expense', '#8b5cf6', 0, 8, 'categoriesSeed.loisirs.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1800, 'Boissons, tabac & cannabis', NULL, 'expense', '#7c3aed', 0, 9, 'categoriesSeed.consommation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1900, 'Finances & obligations', NULL, 'expense', '#6b7280', 0, 10, 'categoriesSeed.finances.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1950, 'Transferts & placements', NULL, 'transfer', '#0ea5e9', 0, 11, 'categoriesSeed.transferts.root');
-- 1000 — Revenus
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1010, 'Emploi', 1000, 'income', '#22c55e', 0, 1, 'categoriesSeed.revenus.emploi.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1011, 'Paie régulière', 1010, 'income', '#22c55e', 1, 1, 'categoriesSeed.revenus.emploi.paie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1012, 'Primes & bonus', 1010, 'income', '#4ade80', 1, 2, 'categoriesSeed.revenus.emploi.primes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1013, 'Travail autonome', 1010, 'income', '#86efac', 1, 3, 'categoriesSeed.revenus.emploi.autonome');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1020, 'Gouvernemental', 1000, 'income', '#16a34a', 0, 2, 'categoriesSeed.revenus.gouvernemental.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1021, 'Remboursement impôt', 1020, 'income', '#16a34a', 1, 1, 'categoriesSeed.revenus.gouvernemental.remboursementImpot');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1022, 'Allocations familiales', 1020, 'income', '#15803d', 1, 2, 'categoriesSeed.revenus.gouvernemental.allocations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1023, 'Crédits TPS/TVQ', 1020, 'income', '#166534', 1, 3, 'categoriesSeed.revenus.gouvernemental.credits');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1024, 'Assurance-emploi / RQAP', 1020, 'income', '#14532d', 1, 4, 'categoriesSeed.revenus.gouvernemental.assuranceEmploi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1030, 'Revenus de placement', 1000, 'income', '#10b981', 0, 3, 'categoriesSeed.revenus.placement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1031, 'Intérêts & dividendes', 1030, 'income', '#10b981', 1, 1, 'categoriesSeed.revenus.placement.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1032, 'Gains en capital', 1030, 'income', '#059669', 1, 2, 'categoriesSeed.revenus.placement.capital');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1033, 'Revenus locatifs', 1030, 'income', '#047857', 1, 3, 'categoriesSeed.revenus.placement.locatifs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1090, 'Autres revenus', 1000, 'income', '#84cc16', 1, 9, 'categoriesSeed.revenus.autres');
-- 1100 — Alimentation
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1110, 'Épicerie & marché', 1100, 'expense', '#ea580c', 0, 1, 'categoriesSeed.alimentation.epicerie.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1111, 'Épicerie régulière', 1110, 'expense', '#ea580c', 1, 1, 'categoriesSeed.alimentation.epicerie.reguliere');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1112, 'Boucherie & poissonnerie', 1110, 'expense', '#c2410c', 1, 2, 'categoriesSeed.alimentation.epicerie.boucherie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1113, 'Boulangerie & pâtisserie', 1110, 'expense', '#9a3412', 1, 3, 'categoriesSeed.alimentation.epicerie.boulangerie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1114, 'Dépanneur', 1110, 'expense', '#7c2d12', 1, 4, 'categoriesSeed.alimentation.epicerie.depanneur');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1115, 'Marché & produits spécialisés', 1110, 'expense', '#fb923c', 1, 5, 'categoriesSeed.alimentation.epicerie.marche');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1120, 'Restauration', 1100, 'expense', '#f97316', 0, 2, 'categoriesSeed.alimentation.restauration.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1121, 'Restaurant', 1120, 'expense', '#f97316', 1, 1, 'categoriesSeed.alimentation.restauration.restaurant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1122, 'Café & boulangerie rapide', 1120, 'expense', '#fb923c', 1, 2, 'categoriesSeed.alimentation.restauration.cafe');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1123, 'Restauration rapide', 1120, 'expense', '#fdba74', 1, 3, 'categoriesSeed.alimentation.restauration.fastfood');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1124, 'Livraison à domicile', 1120, 'expense', '#fed7aa', 1, 4, 'categoriesSeed.alimentation.restauration.livraison');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1125, 'Cantine & cafétéria', 1120, 'expense', '#ffedd5', 1, 5, 'categoriesSeed.alimentation.restauration.cantine');
-- 1200 — Logement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1210, 'Habitation principale', 1200, 'expense', '#dc2626', 0, 1, 'categoriesSeed.logement.habitation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1211, 'Loyer', 1210, 'expense', '#dc2626', 1, 1, 'categoriesSeed.logement.habitation.loyer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1212, 'Hypothèque', 1210, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.logement.habitation.hypotheque');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1213, 'Taxes municipales & scolaires', 1210, 'expense', '#991b1b', 1, 3, 'categoriesSeed.logement.habitation.taxes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1214, 'Charges de copropriété', 1210, 'expense', '#7f1d1d', 1, 4, 'categoriesSeed.logement.habitation.copropriete');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1220, 'Services publics', 1200, 'expense', '#ef4444', 0, 2, 'categoriesSeed.logement.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1221, 'Électricité', 1220, 'expense', '#ef4444', 1, 1, 'categoriesSeed.logement.services.electricite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1222, 'Gaz naturel', 1220, 'expense', '#f87171', 1, 2, 'categoriesSeed.logement.services.gaz');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1223, 'Chauffage (mazout, propane)', 1220, 'expense', '#fca5a5', 1, 3, 'categoriesSeed.logement.services.chauffage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1224, 'Eau & égouts', 1220, 'expense', '#fecaca', 1, 4, 'categoriesSeed.logement.services.eau');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1230, 'Communications', 1200, 'expense', '#6366f1', 0, 3, 'categoriesSeed.logement.communications.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1231, 'Internet résidentiel', 1230, 'expense', '#6366f1', 1, 1, 'categoriesSeed.logement.communications.internet');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1232, 'Téléphonie mobile', 1230, 'expense', '#818cf8', 1, 2, 'categoriesSeed.logement.communications.mobile');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1233, 'Téléphonie résidentielle', 1230, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.logement.communications.residentielle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1234, 'Câblodistribution & streaming TV', 1230, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.logement.communications.streamingTv');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1240, 'Entretien & réparations', 1200, 'expense', '#e11d48', 0, 4, 'categoriesSeed.logement.entretien.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1241, 'Entretien général', 1240, 'expense', '#e11d48', 1, 1, 'categoriesSeed.logement.entretien.general');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1242, 'Rénovations', 1240, 'expense', '#be123c', 1, 2, 'categoriesSeed.logement.entretien.renovations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1243, 'Matériaux & outils', 1240, 'expense', '#9f1239', 1, 3, 'categoriesSeed.logement.entretien.materiaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1244, 'Aménagement paysager', 1240, 'expense', '#881337', 1, 4, 'categoriesSeed.logement.entretien.paysager');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1250, 'Assurance habitation', 1200, 'expense', '#14b8a6', 1, 5, 'categoriesSeed.logement.assurance');
-- 1300 — Ménage & ameublement
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1310, 'Ameublement', 1300, 'expense', '#ca8a04', 0, 1, 'categoriesSeed.menage.ameublement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1311, 'Meubles', 1310, 'expense', '#ca8a04', 1, 1, 'categoriesSeed.menage.ameublement.meubles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1312, 'Électroménagers', 1310, 'expense', '#a16207', 1, 2, 'categoriesSeed.menage.ameublement.electromenagers');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1313, 'Décoration', 1310, 'expense', '#854d0e', 1, 3, 'categoriesSeed.menage.ameublement.decoration');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1320, 'Fournitures ménagères', 1300, 'expense', '#eab308', 0, 2, 'categoriesSeed.menage.fournitures.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1321, 'Produits d''entretien', 1320, 'expense', '#eab308', 1, 1, 'categoriesSeed.menage.fournitures.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1322, 'Literie & linge de maison', 1320, 'expense', '#facc15', 1, 2, 'categoriesSeed.menage.fournitures.literie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1323, 'Vaisselle & ustensiles', 1320, 'expense', '#fde047', 1, 3, 'categoriesSeed.menage.fournitures.vaisselle');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1330, 'Services domestiques', 1300, 'expense', '#fbbf24', 0, 3, 'categoriesSeed.menage.services.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1331, 'Ménage & nettoyage', 1330, 'expense', '#fbbf24', 1, 1, 'categoriesSeed.menage.services.nettoyage');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1332, 'Buanderie & pressing', 1330, 'expense', '#fcd34d', 1, 2, 'categoriesSeed.menage.services.buanderie');
-- 1400 — Vêtements & chaussures
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1410, 'Vêtements adultes', 1400, 'expense', '#d946ef', 1, 1, 'categoriesSeed.vetements.adultes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1420, 'Vêtements enfants', 1400, 'expense', '#c026d3', 1, 2, 'categoriesSeed.vetements.enfants');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1430, 'Chaussures', 1400, 'expense', '#a21caf', 1, 3, 'categoriesSeed.vetements.chaussures');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1440, 'Accessoires & bijoux', 1400, 'expense', '#86198f', 1, 4, 'categoriesSeed.vetements.accessoires');
-- 1500 — Transport
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1510, 'Véhicule personnel', 1500, 'expense', '#2563eb', 0, 1, 'categoriesSeed.transport.vehicule.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1511, 'Achat / location véhicule', 1510, 'expense', '#2563eb', 1, 1, 'categoriesSeed.transport.vehicule.achat');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1512, 'Essence', 1510, 'expense', '#1d4ed8', 1, 2, 'categoriesSeed.transport.vehicule.essence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1513, 'Entretien & réparations auto', 1510, 'expense', '#1e40af', 1, 3, 'categoriesSeed.transport.vehicule.entretien');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1514, 'Immatriculation & permis', 1510, 'expense', '#1e3a8a', 1, 4, 'categoriesSeed.transport.vehicule.immatriculation');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1515, 'Stationnement & péages', 1510, 'expense', '#3b82f6', 1, 5, 'categoriesSeed.transport.vehicule.stationnement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1516, 'Assurance auto', 1510, 'expense', '#60a5fa', 1, 6, 'categoriesSeed.transport.vehicule.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1520, 'Transport public', 1500, 'expense', '#0ea5e9', 0, 2, 'categoriesSeed.transport.public.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1521, 'Autobus & métro', 1520, 'expense', '#0ea5e9', 1, 1, 'categoriesSeed.transport.public.autobus');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1522, 'Train de banlieue', 1520, 'expense', '#0284c7', 1, 2, 'categoriesSeed.transport.public.train');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1523, 'Taxi & covoiturage', 1520, 'expense', '#0369a1', 1, 3, 'categoriesSeed.transport.public.taxi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1530, 'Voyages longue distance', 1500, 'expense', '#38bdf8', 0, 3, 'categoriesSeed.transport.voyages.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1531, 'Avion', 1530, 'expense', '#38bdf8', 1, 1, 'categoriesSeed.transport.voyages.avion');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1532, 'Train & autocar', 1530, 'expense', '#7dd3fc', 1, 2, 'categoriesSeed.transport.voyages.trainAutocar');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1533, 'Hébergement', 1530, 'expense', '#bae6fd', 1, 3, 'categoriesSeed.transport.voyages.hebergement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1534, 'Location véhicule voyage', 1530, 'expense', '#e0f2fe', 1, 4, 'categoriesSeed.transport.voyages.location');
-- 1600 — Santé & soins personnels
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1610, 'Soins médicaux', 1600, 'expense', '#f43f5e', 0, 1, 'categoriesSeed.sante.medicaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1611, 'Pharmacie', 1610, 'expense', '#f43f5e', 1, 1, 'categoriesSeed.sante.medicaux.pharmacie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1612, 'Consultations médicales', 1610, 'expense', '#e11d48', 1, 2, 'categoriesSeed.sante.medicaux.consultations');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1613, 'Dentiste & orthodontiste', 1610, 'expense', '#be123c', 1, 3, 'categoriesSeed.sante.medicaux.dentiste');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1614, 'Optométrie & lunettes', 1610, 'expense', '#9f1239', 1, 4, 'categoriesSeed.sante.medicaux.optometrie');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1615, 'Thérapies (physio, psycho, etc.)', 1610, 'expense', '#881337', 1, 5, 'categoriesSeed.sante.medicaux.therapies');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1616, 'Assurance santé complémentaire', 1610, 'expense', '#fb7185', 1, 6, 'categoriesSeed.sante.medicaux.assurance');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1620, 'Soins personnels', 1600, 'expense', '#fb7185', 0, 2, 'categoriesSeed.sante.personnels.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1621, 'Coiffure & esthétique', 1620, 'expense', '#fb7185', 1, 1, 'categoriesSeed.sante.personnels.coiffure');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1622, 'Produits de soins corporels', 1620, 'expense', '#fda4af', 1, 2, 'categoriesSeed.sante.personnels.soins');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1630, 'Assurance vie & invalidité', 1600, 'expense', '#14b8a6', 1, 3, 'categoriesSeed.sante.assuranceVie');
-- 1700 — Loisirs, formation & lecture
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1710, 'Divertissement', 1700, 'expense', '#8b5cf6', 0, 1, 'categoriesSeed.loisirs.divertissement.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1711, 'Cinéma & spectacles', 1710, 'expense', '#8b5cf6', 1, 1, 'categoriesSeed.loisirs.divertissement.cinema');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1712, 'Jeux vidéo & consoles', 1710, 'expense', '#a78bfa', 1, 2, 'categoriesSeed.loisirs.divertissement.jeux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1713, 'Streaming vidéo', 1710, 'expense', '#c4b5fd', 1, 3, 'categoriesSeed.loisirs.divertissement.streamingVideo');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1714, 'Streaming musique & audio', 1710, 'expense', '#ddd6fe', 1, 4, 'categoriesSeed.loisirs.divertissement.streamingMusique');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1715, 'Jouets & passe-temps', 1710, 'expense', '#ede9fe', 1, 5, 'categoriesSeed.loisirs.divertissement.jouets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1720, 'Sports & plein air', 1700, 'expense', '#22c55e', 0, 2, 'categoriesSeed.loisirs.sports.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1721, 'Abonnements sportifs', 1720, 'expense', '#22c55e', 1, 1, 'categoriesSeed.loisirs.sports.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1722, 'Équipement sportif', 1720, 'expense', '#4ade80', 1, 2, 'categoriesSeed.loisirs.sports.equipement');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1723, 'Parcs & activités plein air', 1720, 'expense', '#86efac', 1, 3, 'categoriesSeed.loisirs.sports.parcs');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1730, 'Formation & éducation', 1700, 'expense', '#6366f1', 0, 3, 'categoriesSeed.loisirs.formation.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1731, 'Scolarité (frais)', 1730, 'expense', '#6366f1', 1, 1, 'categoriesSeed.loisirs.formation.scolarite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1732, 'Matériel scolaire', 1730, 'expense', '#818cf8', 1, 2, 'categoriesSeed.loisirs.formation.materiel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1733, 'Cours & certifications', 1730, 'expense', '#a5b4fc', 1, 3, 'categoriesSeed.loisirs.formation.cours');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1734, 'Abonnements professionnels', 1730, 'expense', '#c7d2fe', 1, 4, 'categoriesSeed.loisirs.formation.abonnements');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1740, 'Lecture & médias', 1700, 'expense', '#ec4899', 0, 4, 'categoriesSeed.loisirs.lecture.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1741, 'Livres', 1740, 'expense', '#ec4899', 1, 1, 'categoriesSeed.loisirs.lecture.livres');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1742, 'Journaux & magazines', 1740, 'expense', '#f472b6', 1, 2, 'categoriesSeed.loisirs.lecture.journaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1750, 'Animaux de compagnie', 1700, 'expense', '#a855f7', 0, 5, 'categoriesSeed.loisirs.animaux.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1751, 'Nourriture & accessoires animaux', 1750, 'expense', '#a855f7', 1, 1, 'categoriesSeed.loisirs.animaux.nourriture');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1752, 'Vétérinaire', 1750, 'expense', '#c084fc', 1, 2, 'categoriesSeed.loisirs.animaux.veterinaire');
-- 1800 — Boissons, tabac & cannabis
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1810, 'Alcool (SAQ, microbrasseries)', 1800, 'expense', '#7c3aed', 1, 1, 'categoriesSeed.consommation.alcool');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1820, 'Cannabis (SQDC)', 1800, 'expense', '#6d28d9', 1, 2, 'categoriesSeed.consommation.cannabis');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1830, 'Tabac', 1800, 'expense', '#5b21b6', 1, 3, 'categoriesSeed.consommation.tabac');
-- 1900 — Finances & obligations
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1910, 'Frais bancaires', 1900, 'expense', '#6b7280', 0, 1, 'categoriesSeed.finances.fraisBancaires.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1911, 'Frais de compte', 1910, 'expense', '#6b7280', 1, 1, 'categoriesSeed.finances.fraisBancaires.compte');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1912, 'Intérêts & frais de crédit', 1910, 'expense', '#9ca3af', 1, 2, 'categoriesSeed.finances.fraisBancaires.interets');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1913, 'Frais de change', 1910, 'expense', '#d1d5db', 1, 3, 'categoriesSeed.finances.fraisBancaires.change');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1920, 'Impôts & taxes', 1900, 'expense', '#dc2626', 0, 2, 'categoriesSeed.finances.impots.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1921, 'Impôt fédéral', 1920, 'expense', '#dc2626', 1, 1, 'categoriesSeed.finances.impots.federal');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1922, 'Impôt provincial', 1920, 'expense', '#b91c1c', 1, 2, 'categoriesSeed.finances.impots.provincial');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1923, 'Acomptes provisionnels', 1920, 'expense', '#991b1b', 1, 3, 'categoriesSeed.finances.impots.acomptes');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1930, 'Dons & cotisations', 1900, 'expense', '#ec4899', 0, 3, 'categoriesSeed.finances.dons.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1931, 'Dons de charité', 1930, 'expense', '#ec4899', 1, 1, 'categoriesSeed.finances.dons.charite');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1932, 'Cotisations professionnelles', 1930, 'expense', '#f472b6', 1, 2, 'categoriesSeed.finances.dons.professionnelles');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1933, 'Cotisations syndicales', 1930, 'expense', '#f9a8d4', 1, 3, 'categoriesSeed.finances.dons.syndicales');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1940, 'Cadeaux', 1900, 'expense', '#f43f5e', 1, 4, 'categoriesSeed.finances.cadeaux');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1945, 'Retrait cash', 1900, 'expense', '#57534e', 1, 5, 'categoriesSeed.finances.cash');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1946, 'Achats divers non catégorisés', 1900, 'expense', '#78716c', 1, 6, 'categoriesSeed.finances.divers');
-- 1950 — Transferts & placements
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1960, 'Épargne & placements', 1950, 'transfer', '#0ea5e9', 0, 1, 'categoriesSeed.transferts.epargne.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1961, 'REER / RRSP', 1960, 'transfer', '#0ea5e9', 1, 1, 'categoriesSeed.transferts.epargne.reer');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1962, 'CELI / TFSA', 1960, 'transfer', '#0284c7', 1, 2, 'categoriesSeed.transferts.epargne.celi');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1963, 'REEE / RESP', 1960, 'transfer', '#0369a1', 1, 3, 'categoriesSeed.transferts.epargne.reee');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1964, 'Compte non-enregistré', 1960, 'transfer', '#075985', 1, 4, 'categoriesSeed.transferts.epargne.nonEnregistre');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1965, 'Fonds d''urgence', 1960, 'transfer', '#0c4a6e', 1, 5, 'categoriesSeed.transferts.epargne.urgence');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1970, 'Remboursement de dette', 1950, 'transfer', '#7c3aed', 0, 2, 'categoriesSeed.transferts.dette.root');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1971, 'Paiement carte crédit', 1970, 'transfer', '#7c3aed', 1, 1, 'categoriesSeed.transferts.dette.carteCredit');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1972, 'Remboursement prêt étudiant', 1970, 'transfer', '#8b5cf6', 1, 2, 'categoriesSeed.transferts.dette.etudiant');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1973, 'Remboursement prêt perso', 1970, 'transfer', '#a78bfa', 1, 3, 'categoriesSeed.transferts.dette.personnel');
INSERT INTO categories (id, name, parent_id, type, color, is_inputable, sort_order, i18n_key) VALUES (1980, 'Transferts internes', 1950, 'transfer', '#64748b', 1, 3, 'categoriesSeed.transferts.internes');
-- ============================================================================
-- Keywords — Canadian suppliers (150+ entries)
-- ============================================================================
-- Alimentation > Épicerie régulière (1111)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('METRO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IGA', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MAXI', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUPER C', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LOBLAWS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROVIGO', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ADONIS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WHOLE FOODS', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AVRIL', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RACHELLE-BERY', 1111, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COSTCO', 1111, 50);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WALMART', 1111, 50);
-- Épicerie > Boucherie (1112)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCHERIE', 1112, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('POISSONNERIE', 1112, 0);
-- Épicerie > Boulangerie (1113)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOULANGERIE', 1113, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PATISSERIE', 1113, 0);
-- Épicerie > Dépanneur (1114)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPANNEUR', 1114, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('7-ELEVEN', 1114, 0);
-- Restauration > Restaurant (1121)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RESTAURANT', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRASSERIE', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BISTRO', 1121, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUSHI', 1121, 0);
-- Restauration > Café (1122)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STARBUCKS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIM HORTONS', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SECOND CUP', 1122, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VAN HOUTTE', 1122, 0);
-- Restauration > Fast food (1123)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MCDONALD', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SUBWAY', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('A&W', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BURGER KING', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KFC', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOMINOS', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PIZZA HUT', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELLE PROVINCE', 1123, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ST-HUBERT', 1123, 0);
-- Restauration > Livraison (1124)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DD/DOORDASH', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER EATS', 1124, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SKIPTHEDISHES', 1124, 0);
-- Logement > Hypothèque (1212)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MTG/HYP', 1212, 0);
-- Logement > Taxes municipales (1213)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('M-ST-HILAIRE TX', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXES MUNICIPALES', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CSS PATRIOT', 1213, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXE SCOLAIRE', 1213, 0);
-- Électricité (1221)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO-QUEBEC', 1221, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HYDRO QUEBEC', 1221, 0);
-- Gaz (1222)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ENERGIR', 1222, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAZ METRO', 1222, 0);
-- Internet (1231)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIDEOTRON', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL INTERNET', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORICOM', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COGECO', 1231, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EBOX', 1231, 0);
-- Mobile (1232)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIZZ', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('KOODO', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PUBLIC MOBILE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('VIRGIN', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELL MOBILITE', 1232, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TELUS MOBILE', 1232, 0);
-- Entretien maison (1241)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('IKEA', 1241, 0);
-- Matériaux & outils (1243)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANADIAN TIRE', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CANAC', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RONA', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HOME DEPOT', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BMR', 1243, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRINCESS AUTO', 1243, 0);
-- Assurance habitation (1250)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BELAIR', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRYSM', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INTACT ASSURANCE', 1250, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DESJARDINS ASSURANCE', 1250, 0);
-- Meubles (1311)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TANGUAY', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEON', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STRUCTUBE', 1311, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BRICK', 1311, 0);
-- Électroménagers (1312)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BEST BUY', 1312, 0);
-- Décoration (1313)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOUCLAIR', 1313, 0);
-- Vêtements (1410)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIQLO', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WINNERS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SIMONS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARKS', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('H&M', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OLD NAVY', 1410, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GAP', 1410, 0);
-- Transport — Essence (1512)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SHELL', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ESSO', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ULTRAMAR', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO-CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PETRO CANADA', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CREVIER', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HARNOIS', 1512, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('COUCHE-TARD ESSENCE', 1512, 10);
-- Permis / SAAQ (1514)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAAQ', 1514, 0);
-- Transport public — autobus/métro (1521)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STM', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RTC', 1521, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STL', 1521, 0);
-- Train de banlieue (1522)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE MONT-SAINT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE SAINT-HUBERT', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GARE CENTRALE', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EXO', 1522, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REM', 1522, 0);
-- Taxi / Uber (1523)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UBER', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LYFT', 1523, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TAXI', 1523, 0);
-- Avion (1531)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR CANADA', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WESTJET', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIR TRANSAT', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PORTER', 1531, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AEROPORTS DE MONTREAL', 1531, 0);
-- Hébergement (1533)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AIRBNB', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('HILTON', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MARRIOTT', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BOOKING.COM', 1533, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NORWEGIAN CRUISE', 1533, 0);
-- Pharmacie (1611)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('JEAN COUTU', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FAMILIPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHARMAPRIX', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROXIM', 1611, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('UNIPRIX', 1611, 0);
-- Thérapies (1615)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PHYSIOACTIF', 1615, 0);
-- Cinéma & spectacles (1711)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEPLEX', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CINEMA DU PARC', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TICKETMASTER', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CLUB SODA', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEPOINTDEVENTE', 1711, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('EVENBRITE', 1711, 0);
-- Jeux vidéo (1712)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('STEAMGAMES', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PLAYSTATION', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NINTENDO', 1712, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('XBOX', 1712, 0);
-- Streaming vidéo (1713)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('NETFLIX', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PRIMEVIDEO', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DISNEY PLUS', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRAVE', 1713, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE TV', 1713, 0);
-- Streaming musique (1714)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPOTIFY', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('APPLE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('YOUTUBE MUSIC', 1714, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TIDAL', 1714, 0);
-- Jouets & hobbies (1715)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LEGO', 1715, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('TOYS R US', 1715, 0);
-- Équipement sportif (1722)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MOUNTAIN EQUIPMENT', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('LA CORDEE', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DECATHLON', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SPORTS EXPERTS', 1722, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ATMOSPHERE', 1722, 0);
-- Parcs & activités (1723)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SEPAQ', 1723, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('BLOC SHOP', 1723, 0);
-- Lecture (1741)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('RENAUD-BRAY', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('INDIGO', 1741, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARCHAMBAULT', 1741, 0);
-- Animaux — nourriture (1751)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('MONDOU', 1751, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PET SMART', 1751, 0);
-- Alcool (1810)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SAQ', 1810, 0);
-- Cannabis (1820)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('SQDC', 1820, 0);
-- Frais bancaires (1911)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PROGRAMME PERFORMANCE', 1911, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FRAIS MENSUELS', 1911, 0);
-- Impôts (1921, 1922)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('GOUV. QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('REVENU QUEBEC', 1922, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ARC IMPOT', 1921, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CRA TAX', 1921, 0);
-- Dons de charité (1931)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('OXFAM', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CENTRAIDE', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FPA', 1931, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CROIX-ROUGE', 1931, 0);
-- Cotisations professionnelles (1932)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('ORDRE DES COMPTABL', 1932, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('CPA CANADA', 1932, 0);
-- Cadeaux (1940)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DANS UN JARDIN', 1940, 0);
-- Divers (1946) — catch-all marketplace
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMAZON', 1946, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AMZN', 1946, 0);
-- Placements (1964)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WS INVESTMENTS', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('WEALTHSIMPLE', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PEAK INVESTMENT', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DYNAMIC FUND', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('FIDELITY', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('AGF', 1964, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('QUESTRADE', 1964, 0);
-- Paie (1011)
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAY/PAY', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('DEPOT PAIE', 1011, 0);
INSERT INTO keywords (keyword, category_id, priority) VALUES ('PAYROLL', 1011, 0);

View file

@ -0,0 +1,232 @@
-- Migration v2: Seed categories and keywords from SommaireDepense.csv
-- Clear existing data (FK order: keywords -> categories)
DELETE FROM keywords;
UPDATE transactions SET category_id = NULL;
DELETE FROM categories;
-- ==========================================
-- Parent categories (Regroupement level)
-- ==========================================
INSERT INTO categories (id, name, type, sort_order) VALUES (1, 'Revenus', 'income', 1);
INSERT INTO categories (id, name, type, sort_order) VALUES (2, 'Dépenses récurrentes', 'expense', 2);
INSERT INTO categories (id, name, type, sort_order) VALUES (3, 'Dépenses ponctuelles', 'expense', 3);
INSERT INTO categories (id, name, type, sort_order) VALUES (4, 'Maison', 'expense', 4);
INSERT INTO categories (id, name, type, sort_order) VALUES (5, 'Placements', 'transfer', 5);
INSERT INTO categories (id, name, type, sort_order) VALUES (6, 'Autres', 'expense', 6);
-- ==========================================
-- Child categories
-- ==========================================
-- Revenus children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (10, 'Paie', 1, 'income', '#22c55e', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (11, 'Autres revenus', 1, 'income', '#4ade80', 2);
-- Dépenses récurrentes children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (20, 'Loyer', 2, 'expense', '#ef4444', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (21, 'Électricité', 2, 'expense', '#f59e0b', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (22, 'Épicerie', 2, 'expense', '#10b981', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (23, 'Dons', 2, 'expense', '#ec4899', 4);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (24, 'Restaurant', 2, 'expense', '#f97316', 5);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (25, 'Frais bancaires', 2, 'expense', '#6b7280', 6);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (26, 'Jeux, Films & Livres', 2, 'expense', '#8b5cf6', 7);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (27, 'Abonnements Musique', 2, 'expense', '#06b6d4', 8);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (28, 'Transport en commun', 2, 'expense', '#3b82f6', 9);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (29, 'Internet & Télécom', 2, 'expense', '#6366f1', 10);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (30, 'Animaux', 2, 'expense', '#a855f7', 11);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (31, 'Assurances', 2, 'expense', '#14b8a6', 12);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (32, 'Pharmacie', 2, 'expense', '#f43f5e', 13);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (33, 'Taxes municipales', 2, 'expense', '#78716c', 14);
-- Dépenses ponctuelles children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (40, 'Voiture', 3, 'expense', '#64748b', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (41, 'Amazon', 3, 'expense', '#f59e0b', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (42, 'Électroniques', 3, 'expense', '#3b82f6', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (43, 'Alcool', 3, 'expense', '#7c3aed', 4);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (44, 'Cadeaux', 3, 'expense', '#ec4899', 5);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (45, 'Vêtements', 3, 'expense', '#d946ef', 6);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (46, 'CPA', 3, 'expense', '#0ea5e9', 7);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (47, 'Voyage', 3, 'expense', '#f97316', 8);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (48, 'Sports & Plein air', 3, 'expense', '#22c55e', 9);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (49, 'Spectacles & sorties', 3, 'expense', '#e11d48', 10);
-- Maison children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (50, 'Hypothèque', 4, 'expense', '#dc2626', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (51, 'Achats maison', 4, 'expense', '#ea580c', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (52, 'Entretien maison', 4, 'expense', '#ca8a04', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (53, 'Électroménagers & Meubles', 4, 'expense', '#0d9488', 4);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (54, 'Outils', 4, 'expense', '#b45309', 5);
-- Placements children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (60, 'Placements', 5, 'transfer', '#2563eb', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (61, 'Transferts', 5, 'transfer', '#7c3aed', 2);
-- Autres children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (70, 'Impôts', 6, 'expense', '#dc2626', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (71, 'Paiement CC', 6, 'transfer', '#6b7280', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (72, 'Retrait cash', 6, 'expense', '#57534e', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (73, 'Projets', 6, 'expense', '#0ea5e9', 4);
-- ==========================================
-- Keywords
-- ==========================================
-- Paie (10)
INSERT INTO keywords (keyword, category_id) VALUES ('PAY/PAY', 10);
-- Électricité (21)
INSERT INTO keywords (keyword, category_id) VALUES ('HYDRO-QUEBEC', 21);
-- Épicerie (22)
INSERT INTO keywords (keyword, category_id) VALUES ('METRO', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('IGA', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('MAXI', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('SUPER C', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('BOUCHERIE LAFLECHE', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('BOULANGERIE JARRY', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('DOLLARAMA', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('WALMART', 22);
-- Dons (23)
INSERT INTO keywords (keyword, category_id) VALUES ('OXFAM', 23);
INSERT INTO keywords (keyword, category_id) VALUES ('CENTRAIDE', 23);
INSERT INTO keywords (keyword, category_id) VALUES ('FPA', 23);
-- Restaurant (24)
INSERT INTO keywords (keyword, category_id) VALUES ('SUBWAY', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('MCDONALD', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('A&W', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('DD/DOORDASH', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('DOORDASH', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('SUSHI', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('DOMINOS', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('BELLE PROVINCE', 24);
-- Frais bancaires (25)
INSERT INTO keywords (keyword, category_id) VALUES ('PROGRAMME PERFORMANCE', 25);
-- Jeux, Films & Livres (26)
INSERT INTO keywords (keyword, category_id) VALUES ('STEAMGAMES', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('PLAYSTATION', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('PRIMEVIDEO', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('NINTENDO', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('RENAUD-BRAY', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('CINEMA DU PARC', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('LEGO', 26);
-- Abonnements Musique (27)
INSERT INTO keywords (keyword, category_id) VALUES ('SPOTIFY', 27);
-- Transport en commun (28)
INSERT INTO keywords (keyword, category_id) VALUES ('STM', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('GARE MONT-SAINT', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('GARE SAINT-HUBERT', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('GARE CENTRALE', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('REM', 28);
-- Internet & Télécom (29)
INSERT INTO keywords (keyword, category_id) VALUES ('VIDEOTRON', 29);
INSERT INTO keywords (keyword, category_id) VALUES ('ORICOM', 29);
-- Animaux (30)
INSERT INTO keywords (keyword, category_id) VALUES ('MONDOU', 30);
-- Assurances (31)
INSERT INTO keywords (keyword, category_id) VALUES ('BELAIR', 31);
INSERT INTO keywords (keyword, category_id) VALUES ('PRYSM', 31);
INSERT INTO keywords (keyword, category_id) VALUES ('INS/ASS', 31);
-- Pharmacie (32)
INSERT INTO keywords (keyword, category_id) VALUES ('JEAN COUTU', 32);
INSERT INTO keywords (keyword, category_id) VALUES ('FAMILIPRIX', 32);
INSERT INTO keywords (keyword, category_id) VALUES ('PHARMAPRIX', 32);
-- Taxes municipales (33)
INSERT INTO keywords (keyword, category_id) VALUES ('M-ST-HILAIRE TX', 33);
INSERT INTO keywords (keyword, category_id) VALUES ('CSS PATRIOT', 33);
-- Voiture (40)
INSERT INTO keywords (keyword, category_id) VALUES ('SHELL', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('ESSO', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('ULTRAMAR', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('PETRO-CANADA', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('SAAQ', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('CREVIER', 40);
-- Amazon (41)
INSERT INTO keywords (keyword, category_id) VALUES ('AMAZON', 41);
INSERT INTO keywords (keyword, category_id) VALUES ('AMZN', 41);
-- Électroniques (42)
INSERT INTO keywords (keyword, category_id) VALUES ('MICROSOFT', 42);
INSERT INTO keywords (keyword, category_id) VALUES ('ADDISON ELECTRONIQUE', 42);
-- Alcool (43)
INSERT INTO keywords (keyword, category_id) VALUES ('SAQ', 43);
INSERT INTO keywords (keyword, category_id) VALUES ('SQDC', 43);
-- Cadeaux (44)
INSERT INTO keywords (keyword, category_id) VALUES ('DANS UN JARDIN', 44);
-- Vêtements (45)
INSERT INTO keywords (keyword, category_id) VALUES ('UNIQLO', 45);
INSERT INTO keywords (keyword, category_id) VALUES ('WINNERS', 45);
INSERT INTO keywords (keyword, category_id) VALUES ('SIMONS', 45);
-- CPA (46)
INSERT INTO keywords (keyword, category_id) VALUES ('ORDRE DES COMPTABL', 46);
-- Voyage (47)
INSERT INTO keywords (keyword, category_id) VALUES ('NORWEGIAN CRUISE', 47);
INSERT INTO keywords (keyword, category_id) VALUES ('AEROPORTS DE MONTREAL', 47);
INSERT INTO keywords (keyword, category_id) VALUES ('HILTON', 47);
-- Sports & Plein air (48)
INSERT INTO keywords (keyword, category_id) VALUES ('BLOC SHOP', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('SEPAQ', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('LA CORDEE', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('MOUNTAIN EQUIPMENT', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('PHYSIOACTIF', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('DECATHLON', 48);
-- Spectacles & sorties (49)
INSERT INTO keywords (keyword, category_id) VALUES ('TICKETMASTER', 49);
INSERT INTO keywords (keyword, category_id) VALUES ('CLUB SODA', 49);
INSERT INTO keywords (keyword, category_id) VALUES ('LEPOINTDEVENTE', 49);
-- Hypothèque (50)
INSERT INTO keywords (keyword, category_id) VALUES ('MTG/HYP', 50);
-- Achats maison (51)
INSERT INTO keywords (keyword, category_id) VALUES ('CANADIAN TIRE', 51);
INSERT INTO keywords (keyword, category_id) VALUES ('CANAC', 51);
INSERT INTO keywords (keyword, category_id) VALUES ('RONA', 51);
-- Entretien maison (52)
INSERT INTO keywords (keyword, category_id) VALUES ('IKEA', 52);
-- Électroménagers & Meubles (53)
INSERT INTO keywords (keyword, category_id) VALUES ('TANGUAY', 53);
INSERT INTO keywords (keyword, category_id) VALUES ('BOUCLAIR', 53);
-- Outils (54)
INSERT INTO keywords (keyword, category_id) VALUES ('BMR', 54);
INSERT INTO keywords (keyword, category_id) VALUES ('HOME DEPOT', 54);
INSERT INTO keywords (keyword, category_id) VALUES ('PRINCESS AUTO', 54);
-- Placements (60)
INSERT INTO keywords (keyword, category_id) VALUES ('DYNAMIC FUND', 60);
INSERT INTO keywords (keyword, category_id) VALUES ('FIDELITY', 60);
INSERT INTO keywords (keyword, category_id) VALUES ('AGF', 60);
-- Transferts (61)
INSERT INTO keywords (keyword, category_id) VALUES ('WS INVESTMENTS', 61);
INSERT INTO keywords (keyword, category_id) VALUES ('PEAK INVESTMENT', 61);
-- Impôts (70)
INSERT INTO keywords (keyword, category_id) VALUES ('GOUV. QUEBEC', 70);
-- Projets (73)
INSERT INTO keywords (keyword, category_id) VALUES ('CLAUDE.AI', 73);
INSERT INTO keywords (keyword, category_id) VALUES ('NAME-CHEAP', 73);

View file

@ -0,0 +1,241 @@
-- Migration v2: Seed categories and keywords from SommaireDepense.csv
-- Clear existing data (FK order: keywords -> categories)
DELETE FROM keywords;
UPDATE transactions SET category_id = NULL;
DELETE FROM categories;
-- ==========================================
-- Parent categories (Regroupement level)
-- ==========================================
INSERT INTO categories (id, name, type, sort_order) VALUES (1, 'Revenus', 'income', 1);
INSERT INTO categories (id, name, type, sort_order) VALUES (2, 'Dépenses récurrentes', 'expense', 2);
INSERT INTO categories (id, name, type, sort_order) VALUES (3, 'Dépenses ponctuelles', 'expense', 3);
INSERT INTO categories (id, name, type, sort_order) VALUES (4, 'Maison', 'expense', 4);
INSERT INTO categories (id, name, type, sort_order) VALUES (5, 'Placements', 'transfer', 5);
INSERT INTO categories (id, name, type, sort_order) VALUES (6, 'Autres', 'expense', 6);
-- ==========================================
-- Child categories
-- ==========================================
-- Revenus children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (10, 'Paie', 1, 'income', '#22c55e', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (11, 'Autres revenus', 1, 'income', '#4ade80', 2);
-- Dépenses récurrentes children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (20, 'Loyer', 2, 'expense', '#ef4444', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (21, 'Électricité', 2, 'expense', '#f59e0b', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (22, 'Épicerie', 2, 'expense', '#10b981', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (23, 'Dons', 2, 'expense', '#ec4899', 4);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (24, 'Restaurant', 2, 'expense', '#f97316', 5);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (25, 'Frais bancaires', 2, 'expense', '#6b7280', 6);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (26, 'Jeux, Films & Livres', 2, 'expense', '#8b5cf6', 7);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (27, 'Abonnements Musique', 2, 'expense', '#06b6d4', 8);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (28, 'Transport en commun', 2, 'expense', '#3b82f6', 9);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (29, 'Internet & Télécom', 2, 'expense', '#6366f1', 10);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (30, 'Animaux', 2, 'expense', '#a855f7', 11);
INSERT INTO categories (id, name, parent_id, type, color, sort_order, is_inputable) VALUES (31, 'Assurances', 2, 'expense', '#14b8a6', 12, 0);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (32, 'Pharmacie', 2, 'expense', '#f43f5e', 13);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (33, 'Taxes municipales', 2, 'expense', '#78716c', 14);
-- Dépenses ponctuelles children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (40, 'Voiture', 3, 'expense', '#64748b', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (41, 'Amazon', 3, 'expense', '#f59e0b', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (42, 'Électroniques', 3, 'expense', '#3b82f6', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (43, 'Alcool', 3, 'expense', '#7c3aed', 4);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (44, 'Cadeaux', 3, 'expense', '#ec4899', 5);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (45, 'Vêtements', 3, 'expense', '#d946ef', 6);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (46, 'CPA', 3, 'expense', '#0ea5e9', 7);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (47, 'Voyage', 3, 'expense', '#f97316', 8);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (48, 'Sports & Plein air', 3, 'expense', '#22c55e', 9);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (49, 'Spectacles & sorties', 3, 'expense', '#e11d48', 10);
-- Maison children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (50, 'Hypothèque', 4, 'expense', '#dc2626', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (51, 'Achats maison', 4, 'expense', '#ea580c', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (52, 'Entretien maison', 4, 'expense', '#ca8a04', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (53, 'Électroménagers & Meubles', 4, 'expense', '#0d9488', 4);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (54, 'Outils', 4, 'expense', '#b45309', 5);
-- Placements children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (60, 'Placements', 5, 'transfer', '#2563eb', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (61, 'Transferts', 5, 'transfer', '#7c3aed', 2);
-- Autres children
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (70, 'Impôts', 6, 'expense', '#dc2626', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (71, 'Paiement CC', 6, 'transfer', '#6b7280', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (72, 'Retrait cash', 6, 'expense', '#57534e', 3);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (73, 'Projets', 6, 'expense', '#0ea5e9', 4);
-- ==========================================
-- Grandchild categories (Level 3 — under Assurances)
-- ==========================================
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (310, 'Assurance-auto', 31, 'expense', '#14b8a6', 1);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (311, 'Assurance-habitation', 31, 'expense', '#0d9488', 2);
INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES (312, 'Assurance-vie', 31, 'expense', '#0f766e', 3);
-- ==========================================
-- Keywords
-- ==========================================
-- Paie (10)
INSERT INTO keywords (keyword, category_id) VALUES ('PAY/PAY', 10);
-- Électricité (21)
INSERT INTO keywords (keyword, category_id) VALUES ('HYDRO-QUEBEC', 21);
-- Épicerie (22)
INSERT INTO keywords (keyword, category_id) VALUES ('METRO', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('IGA', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('MAXI', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('SUPER C', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('BOUCHERIE LAFLECHE', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('BOULANGERIE JARRY', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('DOLLARAMA', 22);
INSERT INTO keywords (keyword, category_id) VALUES ('WALMART', 22);
-- Dons (23)
INSERT INTO keywords (keyword, category_id) VALUES ('OXFAM', 23);
INSERT INTO keywords (keyword, category_id) VALUES ('CENTRAIDE', 23);
INSERT INTO keywords (keyword, category_id) VALUES ('FPA', 23);
-- Restaurant (24)
INSERT INTO keywords (keyword, category_id) VALUES ('SUBWAY', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('MCDONALD', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('A&W', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('DD/DOORDASH', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('DOORDASH', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('SUSHI', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('DOMINOS', 24);
INSERT INTO keywords (keyword, category_id) VALUES ('BELLE PROVINCE', 24);
-- Frais bancaires (25)
INSERT INTO keywords (keyword, category_id) VALUES ('PROGRAMME PERFORMANCE', 25);
-- Jeux, Films & Livres (26)
INSERT INTO keywords (keyword, category_id) VALUES ('STEAMGAMES', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('PLAYSTATION', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('PRIMEVIDEO', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('NINTENDO', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('RENAUD-BRAY', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('CINEMA DU PARC', 26);
INSERT INTO keywords (keyword, category_id) VALUES ('LEGO', 26);
-- Abonnements Musique (27)
INSERT INTO keywords (keyword, category_id) VALUES ('SPOTIFY', 27);
-- Transport en commun (28)
INSERT INTO keywords (keyword, category_id) VALUES ('STM', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('GARE MONT-SAINT', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('GARE SAINT-HUBERT', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('GARE CENTRALE', 28);
INSERT INTO keywords (keyword, category_id) VALUES ('REM', 28);
-- Internet & Télécom (29)
INSERT INTO keywords (keyword, category_id) VALUES ('VIDEOTRON', 29);
INSERT INTO keywords (keyword, category_id) VALUES ('ORICOM', 29);
-- Animaux (30)
INSERT INTO keywords (keyword, category_id) VALUES ('MONDOU', 30);
-- Assurance-auto (310)
INSERT INTO keywords (keyword, category_id) VALUES ('BELAIR', 310);
-- Assurance-habitation (311)
INSERT INTO keywords (keyword, category_id) VALUES ('PRYSM', 311);
-- Assurance-vie (312)
INSERT INTO keywords (keyword, category_id) VALUES ('INS/ASS', 312);
-- Pharmacie (32)
INSERT INTO keywords (keyword, category_id) VALUES ('JEAN COUTU', 32);
INSERT INTO keywords (keyword, category_id) VALUES ('FAMILIPRIX', 32);
INSERT INTO keywords (keyword, category_id) VALUES ('PHARMAPRIX', 32);
-- Taxes municipales (33)
INSERT INTO keywords (keyword, category_id) VALUES ('M-ST-HILAIRE TX', 33);
INSERT INTO keywords (keyword, category_id) VALUES ('CSS PATRIOT', 33);
-- Voiture (40)
INSERT INTO keywords (keyword, category_id) VALUES ('SHELL', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('ESSO', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('ULTRAMAR', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('PETRO-CANADA', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('SAAQ', 40);
INSERT INTO keywords (keyword, category_id) VALUES ('CREVIER', 40);
-- Amazon (41)
INSERT INTO keywords (keyword, category_id) VALUES ('AMAZON', 41);
INSERT INTO keywords (keyword, category_id) VALUES ('AMZN', 41);
-- Électroniques (42)
INSERT INTO keywords (keyword, category_id) VALUES ('MICROSOFT', 42);
INSERT INTO keywords (keyword, category_id) VALUES ('ADDISON ELECTRONIQUE', 42);
-- Alcool (43)
INSERT INTO keywords (keyword, category_id) VALUES ('SAQ', 43);
INSERT INTO keywords (keyword, category_id) VALUES ('SQDC', 43);
-- Cadeaux (44)
INSERT INTO keywords (keyword, category_id) VALUES ('DANS UN JARDIN', 44);
-- Vêtements (45)
INSERT INTO keywords (keyword, category_id) VALUES ('UNIQLO', 45);
INSERT INTO keywords (keyword, category_id) VALUES ('WINNERS', 45);
INSERT INTO keywords (keyword, category_id) VALUES ('SIMONS', 45);
-- CPA (46)
INSERT INTO keywords (keyword, category_id) VALUES ('ORDRE DES COMPTABL', 46);
-- Voyage (47)
INSERT INTO keywords (keyword, category_id) VALUES ('NORWEGIAN CRUISE', 47);
INSERT INTO keywords (keyword, category_id) VALUES ('AEROPORTS DE MONTREAL', 47);
INSERT INTO keywords (keyword, category_id) VALUES ('HILTON', 47);
-- Sports & Plein air (48)
INSERT INTO keywords (keyword, category_id) VALUES ('BLOC SHOP', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('SEPAQ', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('LA CORDEE', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('MOUNTAIN EQUIPMENT', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('PHYSIOACTIF', 48);
INSERT INTO keywords (keyword, category_id) VALUES ('DECATHLON', 48);
-- Spectacles & sorties (49)
INSERT INTO keywords (keyword, category_id) VALUES ('TICKETMASTER', 49);
INSERT INTO keywords (keyword, category_id) VALUES ('CLUB SODA', 49);
INSERT INTO keywords (keyword, category_id) VALUES ('LEPOINTDEVENTE', 49);
-- Hypothèque (50)
INSERT INTO keywords (keyword, category_id) VALUES ('MTG/HYP', 50);
-- Achats maison (51)
INSERT INTO keywords (keyword, category_id) VALUES ('CANADIAN TIRE', 51);
INSERT INTO keywords (keyword, category_id) VALUES ('CANAC', 51);
INSERT INTO keywords (keyword, category_id) VALUES ('RONA', 51);
-- Entretien maison (52)
INSERT INTO keywords (keyword, category_id) VALUES ('IKEA', 52);
-- Électroménagers & Meubles (53)
INSERT INTO keywords (keyword, category_id) VALUES ('TANGUAY', 53);
INSERT INTO keywords (keyword, category_id) VALUES ('BOUCLAIR', 53);
-- Outils (54)
INSERT INTO keywords (keyword, category_id) VALUES ('BMR', 54);
INSERT INTO keywords (keyword, category_id) VALUES ('HOME DEPOT', 54);
INSERT INTO keywords (keyword, category_id) VALUES ('PRINCESS AUTO', 54);
-- Placements (60)
INSERT INTO keywords (keyword, category_id) VALUES ('DYNAMIC FUND', 60);
INSERT INTO keywords (keyword, category_id) VALUES ('FIDELITY', 60);
INSERT INTO keywords (keyword, category_id) VALUES ('AGF', 60);
-- Transferts (61)
INSERT INTO keywords (keyword, category_id) VALUES ('WS INVESTMENTS', 61);
INSERT INTO keywords (keyword, category_id) VALUES ('PEAK INVESTMENT', 61);
-- Impôts (70)
INSERT INTO keywords (keyword, category_id) VALUES ('GOUV. QUEBEC', 70);
-- Projets (73)
INSERT INTO keywords (keyword, category_id) VALUES ('CLAUDE.AI', 73);
INSERT INTO keywords (keyword, category_id) VALUES ('NAME-CHEAP', 73);

View file

@ -1,5 +1,11 @@
pub mod profile_db;
pub const SCHEMA: &str = include_str!("schema.sql"); pub const SCHEMA: &str = include_str!("schema.sql");
pub const SEED_CATEGORIES: &str = include_str!("seed_categories.sql"); pub const SEED_CATEGORIES: &str = include_str!("seed_categories.sql");
pub const CONSOLIDATED_SCHEMA: &str = include_str!("consolidated_schema.sql"); pub const CONSOLIDATED_SCHEMA: &str = include_str!("consolidated_schema.sql");
pub const BALANCE_SCHEMA: &str = include_str!("balance_schema.sql"); pub const BALANCE_SCHEMA: &str = include_str!("balance_schema.sql");
pub const BALANCE_HOLDINGS_SCHEMA: &str = include_str!("balance_holdings_schema.sql"); pub const BALANCE_HOLDINGS_SCHEMA: &str = include_str!("balance_holdings_schema.sql");
/// File name of the default profile's database: the only one tauri-plugin-sql
/// migrates itself (`add_migrations` in lib.rs).
pub const DEFAULT_PROFILE_DB: &str = "simpl_resultat.db";

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,12 @@ use tauri::{Emitter, Manager};
use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_sql::{Migration, MigrationKind}; use tauri_plugin_sql::{Migration, MigrationKind};
#[cfg_attr(mobile, tauri::mobile_entry_point)] /// Every migration, in order. tauri-plugin-sql applies them to the default
pub fn run() { /// profile on load; `database::profile_db` catches the secondary profiles up
let migrations = vec![ /// with the same list (#390). The SQL bytes are checksummed by sqlx and pinned
/// by a test: never edit an existing entry, append a new version.
pub(crate) fn migrations() -> Vec<Migration> {
vec![
Migration { Migration {
version: 1, version: 1,
description: "create initial schema", description: "create initial schema",
@ -365,7 +368,12 @@ pub fn run() {
WHERE column_mapping LIKE '%debitAmount%';", WHERE column_mapping LIKE '%debitAmount%';",
kind: MigrationKind::Up, kind: MigrationKind::Up,
}, },
]; ]
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let migrations = migrations();
tauri::Builder::default() tauri::Builder::default()
// Single-instance plugin MUST be registered first. With the `deep-link` // Single-instance plugin MUST be registered first. With the `deep-link`
@ -429,7 +437,7 @@ pub fn run() {
}) })
.plugin( .plugin(
tauri_plugin_sql::Builder::default() tauri_plugin_sql::Builder::default()
.add_migrations("sqlite:simpl_resultat.db", migrations) .add_migrations(&format!("sqlite:{}", database::DEFAULT_PROFILE_DB), migrations)
.build(), .build(),
) )
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
@ -452,6 +460,7 @@ pub fn run() {
commands::hash_pin, commands::hash_pin,
commands::verify_pin, commands::verify_pin,
commands::repair_migrations, commands::repair_migrations,
commands::prepare_profile_db,
commands::validate_license_key, commands::validate_license_key,
commands::store_license, commands::store_license,
commands::remove_license, commands::remove_license,
@ -3867,12 +3876,10 @@ mod tests {
#[test] #[test]
fn consolidated_schema_matches_v17_chain_on_import_sources_at_parity() { fn consolidated_schema_matches_v17_chain_on_import_sources_at_parity() {
// The consolidated schema is the tested reference definition for new // The consolidated schema is the definition a secondary profile is
// profiles. Without this test the DEFAULT or the CHECK of the four v17 // created with: tauri-plugin-sql runs no migration on its database
// columns could drift between the two definitions unnoticed — the // (#390). Without this test the DEFAULT or the CHECK of the four v17
// columns are inert on the production path (this script runs after every // columns could drift between the two definitions unnoticed.
// migration and only uses CREATE TABLE IF NOT EXISTS), so nothing else
// would surface the divergence.
let chain = db_pre_v17(); let chain = db_pre_v17();
chain.execute_batch(V17_SQL).expect("apply v17"); chain.execute_batch(V17_SQL).expect("apply v17");
let consolidated = consolidated_db(); let consolidated = consolidated_db();

View file

@ -12,7 +12,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
// Ordered log of every fake-db operation's start/end, shared with the fake db. // Ordered log of every fake-db operation's start/end, shared with the fake db.
const events: string[] = []; const events: string[] = [];
// Hoisted handle so the (hoisted) vi.mock factory can reach the per-test db. // Hoisted handle so the (hoisted) vi.mock factory can reach the per-test db.
const h = vi.hoisted(() => ({ fakeDb: null as unknown as FakeDb })); const h = vi.hoisted(() => ({
fakeDb: null as unknown as FakeDb,
// What `prepare_profile_db` answers (#390); a test may make it throw.
prepare: async (): Promise<unknown> => null,
}));
interface FakeDb { interface FakeDb {
execute: (q: string, p?: unknown[]) => Promise<{ rowsAffected: number }>; execute: (q: string, p?: unknown[]) => Promise<{ rowsAffected: number }>;
@ -23,9 +27,21 @@ interface FakeDb {
vi.mock("@tauri-apps/plugin-sql", () => ({ vi.mock("@tauri-apps/plugin-sql", () => ({
default: { load: vi.fn(async () => h.fakeDb) }, default: { load: vi.fn(async () => h.fakeDb) },
})); }));
vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn(async () => false) })); vi.mock("@tauri-apps/api/core", () => ({
invoke: vi.fn(async (command: string) => (command === "prepare_profile_db" ? h.prepare() : false)),
}));
vi.mock("./logService", () => ({ logInfo: vi.fn(), logError: vi.fn() }));
import { connectToProfile, getDb, withTransaction } from "./db"; import Database from "@tauri-apps/plugin-sql";
import { invoke } from "@tauri-apps/api/core";
import { logError, logInfo } from "./logService";
import {
connectToProfile,
describePreparation,
getDb,
withTransaction,
type ProfilePreparation,
} from "./db";
// Each op records start, yields a real macrotask (a window for interleaving if // Each op records start, yields a real macrotask (a window for interleaving if
// the lock were broken), then records end. The lock must keep them ordered. // the lock were broken), then records end. The lock must keep them ordered.
@ -50,6 +66,7 @@ function makeFakeDb(): FakeDb {
beforeEach(async () => { beforeEach(async () => {
events.length = 0; events.length = 0;
h.fakeDb = makeFakeDb(); h.fakeDb = makeFakeDb();
h.prepare = async () => null;
await connectToProfile("test.db"); await connectToProfile("test.db");
}); });
@ -121,3 +138,101 @@ describe("db serialization (database-locked fix)", () => {
); );
}); });
}); });
// ---------------------------------------------------------------------------
// Profile preparation (#390)
// ---------------------------------------------------------------------------
function report(overrides: Partial<ProfilePreparation> = {}): ProfilePreparation {
return { catch_up: null, catch_up_error: null, repaired_flags: 0, elapsed_ms: 12, ...overrides };
}
function catchUp(overrides: Partial<NonNullable<ProfilePreparation["catch_up"]>> = {}) {
return {
floor: 6,
recorded: [],
applied: [],
fk_repaired: [],
fk_violations: [],
backup_file: null,
...overrides,
};
}
describe("a profile is prepared before the plugin opens it (#390)", () => {
beforeEach(() => {
vi.mocked(invoke).mockClear();
vi.mocked(Database.load).mockClear();
vi.mocked(logInfo).mockClear();
vi.mocked(logError).mockClear();
});
it("prepares after the checksum repair and before Database.load", async () => {
await connectToProfile("profile_0a1b2c3d.db");
const invoked = vi.mocked(invoke).mock;
expect(invoked.calls.map((call) => call[0])).toEqual(["repair_migrations", "prepare_profile_db"]);
expect(invoked.calls[1][1]).toEqual({ dbFilename: "profile_0a1b2c3d.db" });
expect(invoked.invocationCallOrder[1]).toBeLessThan(
vi.mocked(Database.load).mock.invocationCallOrder[0]
);
});
it("still opens the profile when the preparation fails, and says so in the Journal", async () => {
h.prepare = async () => {
throw "invalid_profile_db_filename";
};
await connectToProfile("profile_0a1b2c3d.db");
expect(Database.load).toHaveBeenCalledWith("sqlite:profile_0a1b2c3d.db");
expect(logError).toHaveBeenCalledWith(
"Profile profile_0a1b2c3d.db: database preparation failed: invalid_profile_db_filename"
);
});
it("writes what changed to the Journal", async () => {
h.prepare = async () =>
report({
catch_up: catchUp({ applied: [7, 8, 17], backup_file: "profile_0a1b2c3d-before-v7.db" }),
repaired_flags: 2,
});
await connectToProfile("profile_0a1b2c3d.db");
expect(vi.mocked(logInfo).mock.calls.map((call) => call[0])).toEqual([
"Profile profile_0a1b2c3d.db: database updated with versions 7 to 17 in 12 ms, copy of the previous state kept as profile_0a1b2c3d-before-v7.db",
"Profile profile_0a1b2c3d.db: 2 manual categorization flag(s) repaired",
]);
expect(logError).not.toHaveBeenCalled();
});
});
describe("describePreparation (#390)", () => {
it("says nothing when nothing changed", () => {
expect(describePreparation("x.db", report({ catch_up: catchUp() }))).toEqual({ info: [], errors: [] });
});
it("tolerates a missing report", () => {
expect(describePreparation("x.db", null)).toEqual({ info: [], errors: [] });
expect(describePreparation("x.db", false as never)).toEqual({ info: [], errors: [] });
});
it("notes a history written without any update", () => {
const lines = describePreparation("x.db", report({ catch_up: catchUp({ recorded: [1, 2, 17] }) }));
expect(lines.info).toEqual(["Profile x.db: migration history recorded up to version 17"]);
});
it("names the rows a version detached from a missing parent", () => {
const lines = describePreparation(
"x.db",
report({ catch_up: catchUp({ applied: [7], fk_repaired: [["categories", 3]] }) })
);
expect(lines.info).toContain("Profile x.db: 3 row(s) of categories detached from a missing parent");
});
it("reports a stopped catch-up as an error", () => {
const lines = describePreparation("x.db", report({ catch_up_error: "migration_failed:v12: boom" }));
expect(lines.errors).toEqual(["Profile x.db: database update stopped: migration_failed:v12: boom"]);
});
});

View file

@ -1,5 +1,6 @@
import Database from "@tauri-apps/plugin-sql"; import Database from "@tauri-apps/plugin-sql";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { logError, logInfo } from "./logService";
let rawInstance: Database | null = null; let rawInstance: Database | null = null;
let serializedInstance: Database | null = null; let serializedInstance: Database | null = null;
@ -103,6 +104,61 @@ export async function withTransaction<T>(
}); });
} }
/** What `prepare_profile_db` did to one profile database (#390). */
export interface ProfilePreparation {
catch_up: {
floor: number | null;
recorded: number[];
applied: number[];
fk_repaired: Array<[string, number]>;
fk_violations: Array<[string, number]>;
backup_file: string | null;
} | null;
catch_up_error: string | null;
repaired_flags: number;
elapsed_ms: number;
}
/**
* Journal lines for one preparation: only what changed. Names files, never
* folders: an error line can be attached to a feedback report, and the
* folder holds the OS user name.
*/
export function describePreparation(
dbFilename: string,
report: ProfilePreparation | null | undefined
): { info: string[]; errors: string[] } {
const info: string[] = [];
const errors: string[] = [];
if (!report || typeof report !== "object") return { info, errors };
const catchUp = report.catch_up;
if (catchUp && catchUp.applied.length > 0) {
const first = catchUp.applied[0];
const last = catchUp.applied[catchUp.applied.length - 1];
info.push(
`Profile ${dbFilename}: database updated with versions ${first} to ${last} in ${report.elapsed_ms} ms` +
(catchUp.backup_file ? `, copy of the previous state kept as ${catchUp.backup_file}` : "")
);
} else if (catchUp && catchUp.recorded.length > 0) {
info.push(
`Profile ${dbFilename}: migration history recorded up to version ${catchUp.recorded[catchUp.recorded.length - 1]}`
);
}
for (const [table, rows] of catchUp?.fk_repaired ?? []) {
info.push(`Profile ${dbFilename}: ${rows} row(s) of ${table} detached from a missing parent`);
}
for (const [table, rows] of catchUp?.fk_violations ?? []) {
info.push(`Profile ${dbFilename}: ${rows} row(s) of ${table} point to a missing row`);
}
if (report.catch_up_error) {
errors.push(`Profile ${dbFilename}: database update stopped: ${report.catch_up_error}`);
}
if (report.repaired_flags > 0) {
info.push(`Profile ${dbFilename}: ${report.repaired_flags} manual categorization flag(s) repaired`);
}
return { info, errors };
}
export async function connectToProfile(dbFilename: string): Promise<void> { export async function connectToProfile(dbFilename: string): Promise<void> {
if (rawInstance) { if (rawInstance) {
await rawInstance.close(); await rawInstance.close();
@ -118,6 +174,19 @@ export async function connectToProfile(dbFilename: string): Promise<void> {
} catch (e) { } catch (e) {
console.error("Migration repair failed:", e); console.error("Migration repair failed:", e);
} }
// Catch a secondary profile up on the migrations tauri-plugin-sql never
// runs for it, and repair legacy values in every profile (#390). Fails
// open, like the repair above: the profile still opens.
try {
const report = await invoke<ProfilePreparation>("prepare_profile_db", { dbFilename });
const lines = describePreparation(dbFilename, report);
lines.info.forEach((line) => logInfo(line));
lines.errors.forEach((line) => logError(line));
} catch (e) {
logError(
`Profile ${dbFilename}: database preparation failed: ${e instanceof Error ? e.message : String(e)}`
);
}
rawInstance = await Database.load(`sqlite:${dbFilename}`); rawInstance = await Database.load(`sqlite:${dbFilename}`);
serializedInstance = serialize(rawInstance); serializedInstance = serialize(rawInstance);
} }