diff --git a/docker/schema_family.sql b/docker/schema_family.sql index 0684f5e..4c21399 100644 --- a/docker/schema_family.sql +++ b/docker/schema_family.sql @@ -165,16 +165,18 @@ CREATE TABLE IF NOT EXISTS pf_import_rules ( budget_item_id INT(11) NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -CREATE TABLE IF NOT EXISTS pf_advances ( - id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, - advance_date date NOT NULL, - payer varchar(50) NOT NULL, - description varchar(255) NOT NULL, - amount decimal(10,2) DEFAULT 0.00, - from_savings tinyint(1) DEFAULT 0, - is_resolved tinyint(1) DEFAULT 0, - created_at datetime DEFAULT current_timestamp() -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `pf_advances` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `advance_date` date NOT NULL, + `payer` varchar(50) NOT NULL, + `description` varchar(255) NOT NULL, + `amount` decimal(10,2) DEFAULT 0.00, + `from_savings` tinyint(1) DEFAULT 0, + `is_resolved` tinyint(1) DEFAULT 0, + `created_at` datetime DEFAULT current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ─── Notes / Memo ───────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS pf_notes ( diff --git a/includes/lang/ca.php b/includes/lang/ca.php index 170097b..3fa029c 100644 --- a/includes/lang/ca.php +++ b/includes/lang/ca.php @@ -156,6 +156,15 @@ return [ 'mod_planka_desc' => 'Gestioneu els vostres projectes en mode Kanban amb Planka.', 'menu_planka' => 'Planka', + // --- SETTINGS : ENFANTS --- + 'set_kids_title' => 'Nens de la llar', + 'set_kids_desc' => 'Gestioneu la llista de nens per associar-los a l\'estalvi, als regals, etc.', + 'set_add_kid_placeholder' => 'Nom del nen', + 'set_btn_add_kid' => 'Afegir', + 'set_err_kid_name' => 'El nom és obligatori.', + 'set_confirm_del_kid' => 'Vols eliminar aquest nen? (El seu historial financer es conservarà als arxius).', + 'set_no_kids' => 'Cap nen configurat per a aquesta llar.', + // ========================================== // FAMILY CALENDAR // ========================================== @@ -385,6 +394,7 @@ return [ 'cat_fixed' => 'Despeses Fixes', 'cat_others' => 'Altres / Imprevistos', 'cat_savings' => 'Estalvis', + 'budget_tab_kids' => 'Nens', // --- BUDGET : SUIVI --- 'bud_rem_school' => 'Escola (Resta estimada)', @@ -565,6 +575,24 @@ return [ 'bud_prev_confirm_copy' => "Vols copiar les dades de %s a %t?\n\n⚠️ Això sobreescriurà tots els valors ja presents per a %t.", 'bud_prev_confirm_transfers' => "Confirmes que %p ha fet totes les transferències per a %m?\n\nAixò actualitzarà l'Estalvi automàticament.", + // --- MODULE BUDGET : AVANCES & TRICOUNT --- + 'bud_adv_title' => 'Avançaments i Tricount Familiar', + 'bud_adv_add' => 'Afegir un avançament', + 'bud_adv_edit' => 'Modificar l\'avançament', + 'bud_adv_who_paid' => 'Qui ha pagat?', + 'bud_adv_payer' => 'Pagador', + 'bud_adv_has_advanced' => '%s ha avançat:', + 'bud_adv_cc_label' => 'al compte comú', + 'bud_adv_livret_label' => 'des del seu llibre d\'estalvis', + 'bud_adv_cc_balance_title' => 'Equilibri del Compte Comú', + 'bud_adv_owed_to' => 'S\'han de pagar **%s €** a **%s**', + 'bud_adv_balanced' => 'Els comptes estan perfectament equilibrats!', + 'bud_adv_saved_badge' => 'Estalvis', + 'bud_adv_already_saved' => 'Aquests diners provenen d\'un llibre d\'estalvis', + 'bud_adv_ph_desc' => 'Ex: Compres, Factura d\'electricitat...', + 'bud_adv_confirm_resolve' => 'Confirmar el reemborsament complet d\'aquest avançament?', + 'bud_adv_confirm_delete' => 'Eliminar definitivament aquest avançament?', + // ========================================== // CADEAUX (GIFTS) // ========================================== diff --git a/includes/lang/en.php b/includes/lang/en.php index 092f725..93cbe4d 100644 --- a/includes/lang/en.php +++ b/includes/lang/en.php @@ -157,6 +157,15 @@ return [ 'mod_planka_desc' => 'Manage your projects in Kanban mode with Planka.', 'menu_planka' => 'Planka', + // --- SETTINGS : KIDS --- + 'set_kids_title' => 'Household Kids', + 'set_kids_desc' => 'Manage the list of kids to associate them with savings, gifts, etc.', + 'set_add_kid_placeholder' => 'Kid\'s first name', + 'set_btn_add_kid' => 'Add', + 'set_err_kid_name' => 'The first name is required.', + 'set_confirm_del_kid' => 'Delete this kid? (Their financial history will remain visible in the archives).', + 'set_no_kids' => 'No kids configured for this household.', + // ========================================== // FAMILY CALENDAR // ========================================== @@ -383,6 +392,7 @@ return [ 'cat_fixed' => 'Fixed costs', 'cat_others' => 'Other / Unexpected', 'cat_savings' => 'Savings', + 'budget_tab_kids' => 'Kids', // --- BUDGET: TRACKING --- 'bud_rem_school' => 'School (estimated remaining)', @@ -548,6 +558,30 @@ return [ 'bud_prev_confirm_copy' => "Do you want to copy data from %s to %t?\n\n⚠️ This will overwrite all values already present for %t.", 'bud_prev_confirm_transfers' => "Confirm that %p has made all their transfers for %m?\n\nThis will automatically update Savings.", 'bud_prev_label_name' => 'Name', + 'bud_adv_title' => 'Advances & Family Tricount', + 'bud_adv_add' => 'Add an Advance', + 'bud_adv_edit' => 'Edit Advance', + 'bud_adv_who_paid' => 'Who paid?', + 'bud_adv_payer' => 'Payer', + 'bud_adv_has_advanced' => '%s advanced:', + 'bud_adv_cc_label' => 'on the joint account', + 'bud_adv_livret_label' => 'from their savings account', + 'bud_adv_cc_balance_title' => 'Joint Account Balance', + 'bud_adv_owed_to' => '%s € are owed to %s', + 'bud_adv_balanced' => 'Accounts are perfectly balanced!', + 'bud_adv_saved_badge' => 'Savings', + 'bud_adv_already_saved' => 'This money comes from a savings account', + 'bud_adv_ph_desc' => 'e.g., Groceries, Electricity bill...', + 'bud_adv_confirm_resolve' => 'Confirm the complete reimbursement of this advance?', + 'bud_adv_confirm_delete' => 'Permanently delete this advance?', + 'date' => 'Date', + 'bud_label_name' => 'Description', + 'bud_amount' => 'Amount', + 'actions' => 'Actions', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'btn_cancel' => 'Cancel', + 'btn_save' => 'Save', // ========================================== // GIFTS diff --git a/includes/lang/fr.php b/includes/lang/fr.php index 63cf574..d06ed7b 100644 --- a/includes/lang/fr.php +++ b/includes/lang/fr.php @@ -156,6 +156,15 @@ return [ 'mod_planka_desc' => 'Gérez vos projets en mode Kanban avec Planka.', 'menu_planka' => 'Planka', + // --- SETTINGS : ENFANTS --- + 'set_kids_title' => 'Enfants du foyer', + 'set_kids_desc' => 'Gérez la liste des enfants pour les associer à l\'épargne, aux cadeaux, etc.', + 'set_add_kid_placeholder' => 'Prénom de l\'enfant', + 'set_btn_add_kid' => 'Ajouter', + 'set_err_kid_name' => 'Le prénom est requis.', + 'set_confirm_del_kid' => 'Supprimer cet enfant ? (Son historique financier restera visible dans les archives).', + 'set_no_kids' => 'Aucun enfant configuré pour ce foyer.', + // ========================================== // FAMILY CALENDAR // ========================================== @@ -385,6 +394,7 @@ return [ 'cat_fixed' => 'Charges Fixes', 'cat_others' => 'Autres / Imprévus', 'cat_savings' => 'Épargne', + 'budget_tab_kids' => 'Enfants', // --- BUDGET : SUIVI --- 'bud_rem_school' => 'École (Reste estimé)', @@ -563,6 +573,22 @@ return [ 'bud_sav_modal_title_add' => 'Saisir un mois', 'bud_sav_ph_name' => 'Nom (ex: Vacances)', 'bud_prev_label_name' => 'Nom', + 'bud_adv_title' => 'Avances & Tricount Familial', + 'bud_adv_add' => 'Ajouter une avance', + 'bud_adv_edit' => 'Modifier l\'avance', + 'bud_adv_who_paid' => 'Qui a payé ?', + 'bud_adv_payer' => 'Payeur', + 'bud_adv_has_advanced' => '%s a avancé :', + 'bud_adv_cc_label' => 'sur le compte commun', + 'bud_adv_livret_label' => 'depuis son livret d\'épargne', + 'bud_adv_cc_balance_title' => 'Équilibre du Compte Commun', + 'bud_adv_owed_to' => '%s € sont dus à %s', + 'bud_adv_balanced' => 'Les comptes sont parfaitement équilibrés !', + 'bud_adv_saved_badge' => 'Épargne', + 'bud_adv_already_saved' => 'Cet argent provient d\'un livret d\'épargne', + 'bud_adv_ph_desc' => 'Ex: Courses, Facture Électricité...', + 'bud_adv_confirm_resolve' => 'Confirmer le remboursement complet de cette avance ?', + 'bud_adv_confirm_delete' => 'Supprimer définitivement cette avance ?', // ========================================== // CADEAUX (GIFTS) diff --git a/migrate.php b/migrate.php index 265871d..d3ace5e 100644 --- a/migrate.php +++ b/migrate.php @@ -225,6 +225,34 @@ foreach ($families as $f) { } catch (\PDOException $e) { echo "Erreur : " . $e->getMessage() . ""; } + + try { + // Récupération de toutes les bases de données de familles via la base meta + $stmtFamilies = $meta_pdo->query("SELECT id, db_name FROM families WHERE is_active = 1"); + while ($fam = $stmtFamilies->fetch(PDO::FETCH_ASSOC)) { + error_log("Migration de la base familiale : " . $fam['db_name']); + + $fam_dsn = "mysql:host=" . DB_HOST . ";dbname=" . $fam['db_name'] . ";charset=utf8mb4"; + $fam_pdo = new PDO($fam_dsn, DB_USER, DB_PASS, $options); + + // Injection incrémentale sécurisée + $fam_pdo->exec(" + CREATE TABLE IF NOT EXISTS `pf_advances` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `advance_date` DATE NOT NULL, + `payer` VARCHAR(100) NOT NULL, + `description` VARCHAR(255) NOT NULL, + `amount` DECIMAL(10,2) DEFAULT 0.00, + `from_savings` TINYINT(1) DEFAULT 0, + `is_resolved` TINYINT(1) DEFAULT 0, + `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + "); + } + error_log("✅ Migration de la table pf_advances terminée sur tous les tenants."); +} catch (Exception $e) { + die("❌ Erreur critique lors de la migration : " . $e->getMessage()); +} } diff --git a/modules/budget/includes/api/save-budget.php b/modules/budget/includes/api/save-budget.php index 71db02a..17aca21 100644 --- a/modules/budget/includes/api/save-budget.php +++ b/modules/budget/includes/api/save-budget.php @@ -5,7 +5,6 @@ require __DIR__ . '/../../../../includes/auth.php'; require __DIR__ . '/../../../../includes/db.php'; require_login(); - $action = $_POST['action'] ?? ''; // ================================================================= @@ -42,7 +41,7 @@ if ($action === 'save_note') { // 1. MISE A JOUR TABLEAU SALAIRES (AJAX) if ($action === 'update_salary_config') { - header('Content-Type: application/json'); // On précise JSON ici + header('Content-Type: application/json'); $year = $_POST['year']; $person = $_POST['person']; $field = $_POST['field']; // salary, mensualite, etc. @@ -50,7 +49,10 @@ if ($action === 'update_salary_config') { // Liste des champs autorisés pour éviter les injections $allowed = ['salary', 'mensualite', 'frais_func', 'eco_perso', 'eco_family']; - if (!in_array($field, $allowed)) { echo json_encode(['error'=>'Champ invalide']); exit; } + if (!in_array($field, $allowed)) { + echo json_encode(['success' => false, 'error' => 'Champ invalide']); + exit; + } $stmt = $pdo->prepare("INSERT INTO pf_salary_config (year, person, $field) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE $field = VALUES($field)"); $stmt->execute([$year, $person, $value]); @@ -63,7 +65,7 @@ if ($action === 'update_allocation') { header('Content-Type: application/json'); $date = $_POST['month_date']; $catId = (int)$_POST['cat_id']; - $personId = (int)$_POST['person_id']; // 🟢 Reçoit l'ID de la personne directement ! + $personId = (int)$_POST['person_id']; $value = floatval($_POST['value']); if ($catId <= 0 || $personId <= 0) { @@ -272,4 +274,91 @@ if ($action === 'validate_transfers') { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } exit; +} + +// ================================================================= +// 7. GESTION DES AVANCES / TRICOUNT (pf_advances) +// ================================================================= + +if ($action === 'save_advance') { + header('Content-Type: application/json'); + try { + $payer = trim($_POST['payer'] ?? ''); + $advance_date = $_POST['advance_date'] ?? date('Y-m-d'); + $description = trim($_POST['description'] ?? ''); + $amount = abs((float)($_POST['amount'] ?? 0)); + $from_savings = isset($_POST['from_savings']) ? 1 : 0; + + if (empty($payer) || empty($description) || $amount <= 0) { + throw new Exception("Champs obligatoires manquants ou invalides."); + } + + $stmt = $pdo->prepare("INSERT INTO pf_advances (advance_date, payer, description, amount, from_savings) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$advance_date, $payer, $description, $amount, $from_savings]); + + echo json_encode(['success' => true]); + } catch (Exception $e) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} + +if ($action === 'update_advance') { + header('Content-Type: application/json'); + try { + $id = (int)($_POST['id'] ?? 0); + $payer = trim($_POST['payer'] ?? ''); + $advance_date = $_POST['advance_date'] ?? date('Y-m-d'); + $description = trim($_POST['description'] ?? ''); + $amount = abs((float)($_POST['amount'] ?? 0)); + $from_savings = isset($_POST['from_savings']) ? 1 : 0; + + if ($id <= 0 || empty($payer) || empty($description) || $amount <= 0) { + throw new Exception("Paramètres invalides fournis."); + } + + $stmt = $pdo->prepare("UPDATE pf_advances SET advance_date = ?, payer = ?, description = ?, amount = ?, from_savings = ? WHERE id = ?"); + $stmt->execute([$advance_date, $payer, $description, $amount, $from_savings, $id]); + + echo json_encode(['success' => true]); + } catch (Exception $e) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} + +if ($action === 'resolve_advance') { + header('Content-Type: application/json'); + try { + $id = (int)($_POST['id'] ?? 0); + if ($id <= 0) throw new Exception("ID invalide fourni."); + + $stmt = $pdo->prepare("UPDATE pf_advances SET is_resolved = 1 WHERE id = ?"); + $stmt->execute([$id]); + + echo json_encode(['success' => true]); + } catch (Exception $e) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; +} + +if ($action === 'delete_advance') { + header('Content-Type: application/json'); + try { + $id = (int)($_POST['id'] ?? 0); + if ($id <= 0) throw new Exception("ID invalide."); + + $stmt = $pdo->prepare("DELETE FROM pf_advances WHERE id = ?"); + $stmt->execute([$id]); + + echo json_encode(['success' => true]); + } catch (Exception $e) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + exit; } \ No newline at end of file diff --git a/modules/budget/views/budget_prev.php b/modules/budget/views/budget_prev.php index 74a2a94..2b47279 100644 --- a/modules/budget/views/budget_prev.php +++ b/modules/budget/views/budget_prev.php @@ -102,6 +102,41 @@ function getTranslatedMonthName($dateString) { $y = date('Y', strtotime($dateString)); return tr('month_' . $m) . ' ' . $y; } + +// --- 🧠 PREPARATION DATA TRICOUNT / AVANCES --- +$stmtAdvancesList = $pdo->query("SELECT * FROM pf_advances WHERE is_resolved = 0 ORDER BY advance_date DESC"); +$activeAdvances = $stmtAdvancesList->fetchAll(PDO::FETCH_ASSOC); + +$advTotal = [$p1_name => 0, $p2_name => 0]; +$livretTotal = [$p1_name => 0, $p2_name => 0]; + +$labelsCC = [$p1_name => [], $p2_name => []]; +$labelsLivret = [$p1_name => [], $p2_name => []]; + +foreach ($activeAdvances as $adv) { + $p = $adv['payer']; + $amt = (float)$adv['amount']; + $labelStr = htmlspecialchars($adv['description']) . ' (' . number_format($amt, 0, ',', ' ') . '€)'; + + if (!isset($advTotal[$p])) { + $advTotal[$p] = 0; $livretTotal[$p] = 0; + $labelsCC[$p] = []; $labelsLivret[$p] = []; + } + + if ($adv['from_savings']) { + $livretTotal[$p] += $amt; + $labelsLivret[$p][] = $labelStr; + } else { + $advTotal[$p] += $amt; + $labelsCC[$p][] = $labelStr; + } +} + +$balanceDiff = abs(($advTotal[$p1_name] ?? 0) - ($advTotal[$p2_name] ?? 0)); +$owedTo = ''; +if (($advTotal[$p1_name] ?? 0) > ($advTotal[$p2_name] ?? 0)) $owedTo = $p1_name; +elseif (($advTotal[$p2_name] ?? 0) > ($advTotal[$p1_name] ?? 0)) $owedTo = $p2_name; + ?>
@@ -288,6 +323,125 @@ function getTranslatedMonthName($dateString) {
+
+
+

+ +
+ +
+
+
+
+
+ +
+ + +
+ +
+ +
+ 0): ?> +
+
+
+ + +
+ +
+ +
+ +
+ +
+
+
+
+ +
+ + +
+ +
+ +
+ 0): ?> +
+
+
+ + +
+ +
+ +
+ +
+ +
+
+ 🏛️ +
+
+ 0.01): ?> + + + + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
+ +
+
- +
- +
- +
- + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ + +
+
+
+

✏️

+ +
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ @@ -451,12 +684,17 @@ function getTranslatedMonthName($dateString) { \ No newline at end of file