From 6d3c0cd8d31167083a61f41edb15e9fe7261dbc0 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 29 Oct 2025 19:20:02 +0000 Subject: [PATCH] versao 18 --- Backend/budgets.php | 29 +- Backend/categories.php | 307 ++++++++++++++++++ Backend/db/fix_schema.php | 60 ++++ .../migrations/005_create_clients_table.sql | 5 + .../migrations/005a_insert_default_client.sql | 1 + .../migrations/006_add_client_id_to_users.sql | 3 + .../007_add_client_id_to_macro_areas.sql | 3 + .../008_add_client_id_to_budgets.sql | 3 + .../009_add_client_id_to_expenses.sql | 3 + .../migrations/010_fix_budgets_unique_key.sql | 10 + .../011_create_categories_table.sql | 11 + .../012_add_archived_to_categories.sql | 2 + Backend/delete_expense.php | 6 +- Backend/delete_macro_area.php | 13 +- Backend/edit_expense.php | 21 +- Backend/expenses.php | 18 +- Backend/export.php | 6 +- Backend/includes/header.php | 2 +- Backend/login.php | 14 +- Backend/macro_area_form.php | 17 +- Backend/macro_areas.php | 4 +- Backend/print_macro_areas.php | 4 +- 22 files changed, 483 insertions(+), 59 deletions(-) create mode 100644 Backend/categories.php create mode 100644 Backend/db/fix_schema.php create mode 100644 Backend/db/migrations/005_create_clients_table.sql create mode 100644 Backend/db/migrations/005a_insert_default_client.sql create mode 100644 Backend/db/migrations/006_add_client_id_to_users.sql create mode 100644 Backend/db/migrations/007_add_client_id_to_macro_areas.sql create mode 100644 Backend/db/migrations/008_add_client_id_to_budgets.sql create mode 100644 Backend/db/migrations/009_add_client_id_to_expenses.sql create mode 100644 Backend/db/migrations/010_fix_budgets_unique_key.sql create mode 100644 Backend/db/migrations/011_create_categories_table.sql create mode 100644 Backend/db/migrations/012_add_archived_to_categories.sql diff --git a/Backend/budgets.php b/Backend/budgets.php index cef7f5f..afce185 100644 --- a/Backend/budgets.php +++ b/Backend/budgets.php @@ -11,6 +11,7 @@ if (!isset($_SESSION['user_id'])) { exit; } +$client_id = $_SESSION['client_id']; $user_id = $_SESSION['user_id']; $pdo = db(); $error_message = ''; @@ -26,13 +27,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $posted_month = $_POST['budget_month'] ?? $budget_month_date; try { - $sql = "INSERT INTO budgets (user_id, category, amount, budget_month) VALUES (:user_id, :category, :amount, :budget_month) - ON DUPLICATE KEY UPDATE amount = :amount"; + $sql = "INSERT INTO budgets (client_id, user_id, category, amount, budget_month) VALUES (:client_id, :user_id, :category, :amount, :budget_month) + ON DUPLICATE KEY UPDATE amount = VALUES(amount)"; $stmt = $pdo->prepare($sql); foreach ($budgets as $category => $amount) { if (is_numeric($amount) && $amount >= 0) { $stmt->execute([ + 'client_id' => $client_id, 'user_id' => $user_id, 'category' => $category, 'amount' => $amount, @@ -52,8 +54,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Buscar orçamentos existentes para o mês selecionado $existing_budgets = []; try { - $stmt = $pdo->prepare("SELECT category, amount FROM budgets WHERE user_id = :user_id AND budget_month = :budget_month"); - $stmt->execute(['user_id' => $user_id, 'budget_month' => $budget_month_date]); + $stmt = $pdo->prepare("SELECT category, amount FROM budgets WHERE client_id = :client_id AND budget_month = :budget_month"); + $stmt->execute(['client_id' => $client_id, 'budget_month' => $budget_month_date]); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($results as $row) { $existing_budgets[$row['category']] = $row['amount']; @@ -63,8 +65,10 @@ try { } -// Categorias fixas -$categories = ['Alimentação', 'Transporte', 'Moradia', 'Lazer', 'Saúde', 'Outros']; +// Obter todas as categorias (macro áreas ativas) para os dropdowns +$stmt_categories = $pdo->prepare("SELECT nome, slug FROM macro_areas WHERE client_id = :client_id AND ativo = 1 ORDER BY nome ASC"); +$stmt_categories->execute(['client_id' => $client_id]); +$categories = $stmt_categories->fetchAll(PDO::FETCH_ASSOC); include __DIR__ . '/includes/header.php'; ?> @@ -101,17 +105,20 @@ include __DIR__ . '/includes/header.php';

Orçamentos para

- +
- +
R$ - +
diff --git a/Backend/categories.php b/Backend/categories.php new file mode 100644 index 0000000..4e7a031 --- /dev/null +++ b/Backend/categories.php @@ -0,0 +1,307 @@ +prepare("UPDATE categories SET nome = :nome, macro_area_id = :macro_area_id WHERE id = :id AND client_id = :client_id"); + $stmt->execute(['nome' => $nome, 'macro_area_id' => $macro_area_id, 'id' => $id, 'client_id' => $client_id]); + $success = "Categoria atualizada com sucesso!"; + } else { + // Create + $stmt = $pdo->prepare("INSERT INTO categories (nome, macro_area_id, client_id) VALUES (:nome, :macro_area_id, :client_id)"); + $stmt->execute(['nome' => $nome, 'macro_area_id' => $macro_area_id, 'client_id' => $client_id]); + $success = "Categoria criada com sucesso!"; + } + } + } elseif ($action === 'toggle_archive') { + $id = $_POST['id'] ?? null; + if ($id) { + $stmt = $pdo->prepare("UPDATE categories SET is_archived = 1 - is_archived WHERE id = :id AND client_id = :client_id"); + $stmt->execute(['id' => $id, 'client_id' => $client_id]); + $success = "Status da categoria alterado com sucesso!"; + } + } elseif ($action === 'delete') { + $id = $_POST['id'] ?? null; + if ($id) { + // Check for related expenses + $stmt = $pdo->prepare("SELECT COUNT(*) FROM expenses WHERE category_id = :category_id AND client_id = :client_id"); + $stmt->execute(['category_id' => $id, 'client_id' => $client_id]); + if ($stmt->fetchColumn() > 0) { + $errors[] = "Não é possível excluir a categoria, pois existem despesas associadas a ela."; + } else { + $stmt = $pdo->prepare("DELETE FROM categories WHERE id = :id AND client_id = :client_id"); + $stmt->execute(['id' => $id, 'client_id' => $client_id]); + $success = "Categoria excluída com sucesso!"; + } + } + } + } catch (PDOException $e) { + if ($e->getCode() == '23000') { // Integrity constraint violation + $errors[] = "Erro: Já existe uma categoria com este nome para a macro área selecionada."; + } else { + $errors[] = "Ocorreu um erro no banco de dados: " . $e->getMessage(); + } + } +} + +// Fetch data for display +$macro_areas = $pdo->prepare("SELECT id, nome FROM macro_areas WHERE client_id = :client_id ORDER BY nome"); +$macro_areas->execute(['client_id' => $client_id]); +$macro_areas_list = $macro_areas->fetchAll(); + +$stmt = $pdo->prepare(" + SELECT c.id, c.nome, c.is_archived, c.macro_area_id, m.nome as macro_area_nome + FROM categories c + JOIN macro_areas m ON c.macro_area_id = m.id + WHERE c.client_id = :client_id + ORDER BY m.nome, c.nome +"); +$stmt->execute(['client_id' => $client_id]); +$categories = $stmt->fetchAll(); + +$page_title = "Categorias"; +include 'includes/header.php'; +?> + + + +
+
+

+ + Categorias +

+ +
+ + +
+ +

+ +
+ + +
+ +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Nome da CategoriaMacro ÁreaStatusAções
Nenhuma categoria encontrada.
+ + + + + + +
+ + + +
+ +
+ + + +
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/Backend/db/fix_schema.php b/Backend/db/fix_schema.php new file mode 100644 index 0000000..8eb0e63 --- /dev/null +++ b/Backend/db/fix_schema.php @@ -0,0 +1,60 @@ +query("SHOW COLUMNS FROM `{$table_name}` LIKE 'client_id'"); + if ($res->rowCount() == 0) { + echo "Adding client_id to {$table_name}...\\n"; + $pdo->exec("ALTER TABLE `{$table_name}` ADD COLUMN `client_id` INT NOT NULL DEFAULT 1"); + } else { + echo "client_id already exists in {$table_name}. Setting to default...\\n"; + // This handles cases where the column was added but not populated correctly + $pdo->exec("UPDATE `{$table_name}` SET `client_id` = 1 WHERE `client_id` = 0 OR `client_id` IS NULL"); + } + + // Check and add foreign key + $res = $pdo->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$table_name}' AND COLUMN_NAME = 'client_id' AND REFERENCED_TABLE_NAME = 'clients'"); + if ($res->rowCount() == 0) { + echo "Adding foreign key to {$table_name}...\\n"; + try { + $pdo->exec("ALTER TABLE `{$table_name}` ADD CONSTRAINT `{$fk_name}` FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE"); + } catch (PDOException $e) { + // If the constraint somehow exists with a different name, this might fail. + echo "Could not add foreign key to {$table_name}. It might already exist with a different name. Error: " . $e->getMessage() . "\\n"; + } + } else { + echo "Foreign key on {$table_name}.client_id already exists.\\n"; + } +} + +try { + $pdo = db(); + + fix_table($pdo, 'users', 'fk_users_client_id'); + fix_table($pdo, 'macro_areas', 'fk_macro_areas_client_id'); + fix_table($pdo, 'budgets', 'fk_budgets_client_id'); + fix_table($pdo, 'expenses', 'fk_expenses_client_id'); + + // Mark migrations as executed to prevent them from running again + $migrations_to_mark = [ + '006_add_client_id_to_users.sql', + '007_add_client_id_to_macro_areas.sql', + '008_add_client_id_to_budgets.sql', + '009_add_client_id_to_expenses.sql' + ]; + + $stmt = $pdo->prepare("INSERT IGNORE INTO migrations (migration_name) VALUES (:migration_name)"); + foreach ($migrations_to_mark as $migration_name) { + echo "Marking migration {$migration_name} as executed...\\n"; + $stmt->execute(['migration_name' => $migration_name]); + } + + echo "\\nSchema fix script finished.\\n"; + +} catch (PDOException $e) { + die("Error fixing schema: " . $e->getMessage()); +} + diff --git a/Backend/db/migrations/005_create_clients_table.sql b/Backend/db/migrations/005_create_clients_table.sql new file mode 100644 index 0000000..06231f5 --- /dev/null +++ b/Backend/db/migrations/005_create_clients_table.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS `clients` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/Backend/db/migrations/005a_insert_default_client.sql b/Backend/db/migrations/005a_insert_default_client.sql new file mode 100644 index 0000000..3b284e1 --- /dev/null +++ b/Backend/db/migrations/005a_insert_default_client.sql @@ -0,0 +1 @@ +INSERT INTO `clients` (`id`, `name`) VALUES (1, 'Default Client') ON DUPLICATE KEY UPDATE `name` = `name`; \ No newline at end of file diff --git a/Backend/db/migrations/006_add_client_id_to_users.sql b/Backend/db/migrations/006_add_client_id_to_users.sql new file mode 100644 index 0000000..83c8c45 --- /dev/null +++ b/Backend/db/migrations/006_add_client_id_to_users.sql @@ -0,0 +1,3 @@ +ALTER TABLE `users` +ADD COLUMN `client_id` INT NOT NULL DEFAULT 1 AFTER `id`, +ADD CONSTRAINT `fk_users_client_id` FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE; diff --git a/Backend/db/migrations/007_add_client_id_to_macro_areas.sql b/Backend/db/migrations/007_add_client_id_to_macro_areas.sql new file mode 100644 index 0000000..6537eb6 --- /dev/null +++ b/Backend/db/migrations/007_add_client_id_to_macro_areas.sql @@ -0,0 +1,3 @@ +ALTER TABLE `macro_areas` +ADD COLUMN `client_id` INT NOT NULL DEFAULT 1 AFTER `id`, +ADD CONSTRAINT `fk_macro_areas_client_id` FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE; diff --git a/Backend/db/migrations/008_add_client_id_to_budgets.sql b/Backend/db/migrations/008_add_client_id_to_budgets.sql new file mode 100644 index 0000000..007b17b --- /dev/null +++ b/Backend/db/migrations/008_add_client_id_to_budgets.sql @@ -0,0 +1,3 @@ +ALTER TABLE `budgets` +ADD COLUMN `client_id` INT NOT NULL DEFAULT 1 AFTER `id`, +ADD CONSTRAINT `fk_budgets_client_id` FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE; diff --git a/Backend/db/migrations/009_add_client_id_to_expenses.sql b/Backend/db/migrations/009_add_client_id_to_expenses.sql new file mode 100644 index 0000000..2e7cc4e --- /dev/null +++ b/Backend/db/migrations/009_add_client_id_to_expenses.sql @@ -0,0 +1,3 @@ +ALTER TABLE `expenses` +ADD COLUMN `client_id` INT NOT NULL DEFAULT 1 AFTER `id`, +ADD CONSTRAINT `fk_expenses_client_id` FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE; diff --git a/Backend/db/migrations/010_fix_budgets_unique_key.sql b/Backend/db/migrations/010_fix_budgets_unique_key.sql new file mode 100644 index 0000000..fff73f6 --- /dev/null +++ b/Backend/db/migrations/010_fix_budgets_unique_key.sql @@ -0,0 +1,10 @@ +-- Drop the foreign key constraint first +ALTER TABLE `budgets` DROP FOREIGN KEY `budgets_ibfk_1`; + +-- Now, drop the old unique key and add the new one +ALTER TABLE `budgets` +DROP KEY `user_category_month`, +ADD UNIQUE KEY `client_month_category` (`client_id`, `budget_month`, `category`); + +-- Add the foreign key back +ALTER TABLE `budgets` ADD CONSTRAINT `fk_budgets_user_id` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE; \ No newline at end of file diff --git a/Backend/db/migrations/011_create_categories_table.sql b/Backend/db/migrations/011_create_categories_table.sql new file mode 100644 index 0000000..ea13a8f --- /dev/null +++ b/Backend/db/migrations/011_create_categories_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS `categories` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `nome` VARCHAR(255) NOT NULL, + `macro_area_id` INT NOT NULL, + `client_id` INT NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (`macro_area_id`) REFERENCES `macro_areas`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON DELETE CASCADE, + UNIQUE KEY `client_macro_area_nome` (`client_id`, `macro_area_id`, `nome`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/Backend/db/migrations/012_add_archived_to_categories.sql b/Backend/db/migrations/012_add_archived_to_categories.sql new file mode 100644 index 0000000..891f82f --- /dev/null +++ b/Backend/db/migrations/012_add_archived_to_categories.sql @@ -0,0 +1,2 @@ +ALTER TABLE `categories` +ADD COLUMN `is_archived` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0 = active, 1 = archived' AFTER `client_id`; \ No newline at end of file diff --git a/Backend/delete_expense.php b/Backend/delete_expense.php index 606f266..fb86bad 100644 --- a/Backend/delete_expense.php +++ b/Backend/delete_expense.php @@ -12,7 +12,7 @@ if (!isset($_SESSION['user_id'])) { } $expense_id = $_GET['id'] ?? null; -$user_id = $_SESSION['user_id']; +$client_id = $_SESSION['client_id']; if (!$expense_id) { // Se não houver ID, redireciona de volta @@ -23,11 +23,11 @@ if (!$expense_id) { try { $pdo = db(); // A cláusula WHERE garante que um usuário só pode deletar suas próprias despesas - $sql = "DELETE FROM expenses WHERE id = :id AND user_id = :user_id"; + $sql = "DELETE FROM expenses WHERE id = :id AND client_id = :client_id"; $stmt = $pdo->prepare($sql); $stmt->execute([ 'id' => $expense_id, - 'user_id' => $user_id + 'client_id' => $client_id ]); // Opcional: verificar se alguma linha foi afetada para dar um feedback mais preciso diff --git a/Backend/delete_macro_area.php b/Backend/delete_macro_area.php index 7aef25b..65956ac 100644 --- a/Backend/delete_macro_area.php +++ b/Backend/delete_macro_area.php @@ -5,11 +5,12 @@ require_once 'db/config.php'; // Check if ID is provided if (isset($_GET['id'])) { $id = $_GET['id']; + $client_id = $_SESSION['client_id']; $pdo = db(); // First, find the macro area to get its slug - $stmt = $pdo->prepare("SELECT slug FROM macro_areas WHERE id = ?"); - $stmt->execute([$id]); + $stmt = $pdo->prepare("SELECT slug FROM macro_areas WHERE id = ? AND client_id = ?"); + $stmt->execute([$id, $client_id]); $macro_area = $stmt->fetch(); // If the macro area exists, proceed with checks and deletion @@ -17,8 +18,8 @@ if (isset($_GET['id'])) { $slug = $macro_area['slug']; // Check for dependent expenses - $stmt = $pdo->prepare("SELECT COUNT(*) FROM expenses WHERE category = ?"); - $stmt->execute([$slug]); + $stmt = $pdo->prepare("SELECT COUNT(*) FROM expenses WHERE category = ? AND client_id = ?"); + $stmt->execute([$slug, $client_id]); $expense_count = $stmt->fetchColumn(); if ($expense_count > 0) { @@ -26,8 +27,8 @@ if (isset($_GET['id'])) { $_SESSION['error_message'] = "Não é possível excluir a macro área. Existem {$expense_count} despesas associadas."; } else { // No dependencies, proceed with deletion - $stmt = $pdo->prepare("DELETE FROM macro_areas WHERE id = ?"); - $stmt->execute([$id]); + $stmt = $pdo->prepare("DELETE FROM macro_areas WHERE id = ? AND client_id = ?"); + $stmt->execute([$id, $client_id]); $_SESSION['success_message'] = "Macro área excluída com sucesso."; } } diff --git a/Backend/edit_expense.php b/Backend/edit_expense.php index 8870636..968d4bb 100644 --- a/Backend/edit_expense.php +++ b/Backend/edit_expense.php @@ -12,7 +12,7 @@ if (!isset($_SESSION['user_id'])) { } $expense_id = $_GET['id'] ?? null; -$user_id = $_SESSION['user_id']; +$client_id = $_SESSION['client_id']; $error_message = ''; $expense = null; @@ -25,8 +25,8 @@ $pdo = db(); // Buscar a despesa para garantir que ela pertence ao usuário try { - $stmt = $pdo->prepare("SELECT * FROM expenses WHERE id = :id AND user_id = :user_id"); - $stmt->execute(['id' => $expense_id, 'user_id' => $user_id]); + $stmt = $pdo->prepare("SELECT * FROM expenses WHERE id = :id AND client_id = :client_id"); + $stmt->execute(['id' => $expense_id, 'client_id' => $client_id]); $expense = $stmt->fetch(PDO::FETCH_ASSOC); if (!$expense) { @@ -51,7 +51,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $error_message = 'Todos os campos são obrigatórios.'; } else { try { - $sql = "UPDATE expenses SET description = :description, amount = :amount, category = :category, expense_date = :expense_date WHERE id = :id AND user_id = :user_id"; + $sql = "UPDATE expenses SET description = :description, amount = :amount, category = :category, expense_date = :expense_date WHERE id = :id AND client_id = :client_id"; $stmt = $pdo->prepare($sql); $stmt->execute([ 'description' => $description, @@ -59,7 +59,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'category' => $category, 'expense_date' => $expense_date, 'id' => $expense_id, - 'user_id' => $user_id + 'client_id' => $client_id ]); $_SESSION['success_message'] = 'Despesa atualizada com sucesso!'; @@ -100,13 +100,12 @@ include __DIR__ . '/includes/header.php'; + $selected = ($expense['category'] === $cat['slug']) ? 'selected' : ''; + echo "