From 73cf51aa26144d16975710e56c41221c32febf06 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 26 Nov 2025 10:23:27 +0000 Subject: [PATCH] Final v2 (Expenses) --- assets/js/expenses.js | 30 +++++ .../008_create_expenses_monthly_table.sql | 8 ++ expenses.php | 121 ++++++++++++++++++ export_expenses.php | 46 +++++++ project_details.php | 12 +- save_expenses.php | 36 ++++++ 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 assets/js/expenses.js create mode 100644 db/migrations/008_create_expenses_monthly_table.sql create mode 100644 expenses.php create mode 100644 export_expenses.php create mode 100644 save_expenses.php diff --git a/assets/js/expenses.js b/assets/js/expenses.js new file mode 100644 index 0000000..9954047 --- /dev/null +++ b/assets/js/expenses.js @@ -0,0 +1,30 @@ +function initExpensesPage(projectId) { + document.querySelectorAll('.expenses-amount').forEach(input => { + input.addEventListener('blur', function() { + const month = this.dataset.month; + const amount = this.value; + + const formData = new FormData(); + formData.append('projectId', projectId); + formData.append('month', month); + formData.append('amount', amount); + + fetch('save_expenses.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // Maybe show a small success indicator + } else { + alert('Error saving expenses amount: ' + data.error); + } + }) + .catch(error => { + console.error('Error:', error); + alert('An unexpected error occurred.'); + }); + }); + }); +} diff --git a/db/migrations/008_create_expenses_monthly_table.sql b/db/migrations/008_create_expenses_monthly_table.sql new file mode 100644 index 0000000..743a0f2 --- /dev/null +++ b/db/migrations/008_create_expenses_monthly_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS `expensesMonthly` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `projectId` INT NOT NULL, + `month` DATE NOT NULL, + `amount` DECIMAL(10, 2) NOT NULL DEFAULT 0.00, + FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE CASCADE, + UNIQUE KEY `project_month` (`projectId`, `month`) +); diff --git a/expenses.php b/expenses.php new file mode 100644 index 0000000..9f67fad --- /dev/null +++ b/expenses.php @@ -0,0 +1,121 @@ +exec($sql); + } + + $stmt = $pdo->prepare("SELECT * FROM projects WHERE id = :id"); + $stmt->execute([':id' => $projectId]); + $project = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$project) { + header("Location: projects.php"); + exit(); + } + + // Initialize expenses records if they don't exist + $startDate = new DateTime($project['startDate']); + $endDate = new DateTime($project['endDate']); + $currentMonth = clone $startDate; + + while ($currentMonth <= $endDate) { + $monthStr = $currentMonth->format('Y-m-01'); + $stmt = $pdo->prepare("SELECT COUNT(*) FROM expensesMonthly WHERE projectId = :projectId AND month = :month"); + $stmt->execute([':projectId' => $projectId, ':month' => $monthStr]); + $count = $stmt->fetchColumn(); + + if ($count == 0) { + $insertStmt = $pdo->prepare("INSERT INTO expensesMonthly (projectId, month, amount) VALUES (:projectId, :month, 0)"); + $insertStmt->execute([':projectId' => $projectId, ':month' => $monthStr]); + } + + $currentMonth->modify('+1 month'); + } + + // Fetch expenses data + $stmt = $pdo->prepare("SELECT * FROM expensesMonthly WHERE projectId = :projectId ORDER BY month"); + $stmt->execute([':projectId' => $projectId]); + $expensesData = $stmt->fetchAll(PDO::FETCH_ASSOC); + +} catch (PDOException $e) { + $db_error = "Database error: " . $e->getMessage(); +} + +?> + + + + + + Expenses - <?php echo htmlspecialchars($project['name']); ?> + + + + +
+
+ + + +
+ + +
+
+ + + + + ' . $currentMonth->format('M Y') . ''; + $currentMonth->modify('+1 month'); + } + ?> + + + + + + '; + } + ?> + + +
Expenses
Expenses
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/export_expenses.php b/export_expenses.php new file mode 100644 index 0000000..a1b46af --- /dev/null +++ b/export_expenses.php @@ -0,0 +1,46 @@ +prepare("SELECT name FROM projects WHERE id = :id"); + $stmt->execute([':id' => $projectId]); + $project = $stmt->fetch(PDO::FETCH_ASSOC); + $projectName = $project ? $project['name'] : 'project'; + + $stmt = $pdo->prepare("SELECT month, amount FROM expensesMonthly WHERE projectId = :projectId ORDER BY month"); + $stmt->execute([':projectId' => $projectId]); + $expensesData = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $filename = "expenses_" . strtolower(str_replace(' ', '_', $projectName)) . ".csv"; + + header('Content-Type: text/csv'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + + $output = fopen('php://output', 'w'); + + // Header row + $header = ['Expenses']; + foreach ($expensesData as $row) { + $header[] = date("M Y", strtotime($row['month'])); + } + fputcsv($output, $header); + + // Data row + $data = ['Expenses']; + foreach ($expensesData as $row) { + $data[] = $row['amount']; + } + fputcsv($output, $data); + + fclose($output); + exit(); + +} catch (PDOException $e) { + die("Database error: " . $e->getMessage()); +} diff --git a/project_details.php b/project_details.php index f36259c..e599aa6 100644 --- a/project_details.php +++ b/project_details.php @@ -98,9 +98,15 @@ if ($project_id) { $billing_stmt->execute([':pid' => $project_id]); $monthly_billing = $billing_stmt->fetchAll(PDO::FETCH_KEY_PAIR); + // Base Monthly Expenses + $expenses_stmt = $pdo->prepare("SELECT month, amount FROM expensesMonthly WHERE projectId = :pid"); + $expenses_stmt->execute([':pid' => $project_id]); + $monthly_expenses = $expenses_stmt->fetchAll(PDO::FETCH_KEY_PAIR); + // 2. Calculate cumulative values month by month $cumulative_billing = 0; $cumulative_cost = 0; + $cumulative_expenses = 0; $previous_month_wip = 0; foreach ($months as $month) { @@ -108,11 +114,12 @@ if ($project_id) { $cost = $monthly_costs[$month] ?? 0; $base_monthly_wip = $monthly_wip[$month] ?? 0; $billing = $monthly_billing[$month] ?? 0; - $expenses = 0; // Placeholder for expenses + $expenses = $monthly_expenses[$month] ?? 0; // Cumulative calculations $cumulative_billing += $billing; $cumulative_cost += $cost; + $cumulative_expenses += $expenses; // WIP Calculation (new formula) // current month WIP = previous month WIP + Month Expenses + base_monthly_wip - month Billing @@ -122,7 +129,7 @@ if ($project_id) { $financial_data['Billings'][$month] = $cumulative_billing; $financial_data['Cost'][$month] = $cumulative_cost; $financial_data['Opening Balance'][$month] = 0; // Placeholder - $financial_data['Expenses'][$month] = $expenses; + $financial_data['Expenses'][$month] = $cumulative_expenses; // Calculated metrics (NSR and Margin) $nsr = $financial_data['WIP'][$month] + $financial_data['Billings'][$month] - $financial_data['Opening Balance'][$month] - $financial_data['Expenses'][$month]; @@ -177,6 +184,7 @@ if (!$project) {

Project:

Billing + Expenses Forecasting
diff --git a/save_expenses.php b/save_expenses.php new file mode 100644 index 0000000..84abab6 --- /dev/null +++ b/save_expenses.php @@ -0,0 +1,36 @@ + false, 'error' => 'Invalid request']; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $projectId = $_POST['projectId'] ?? null; + $month = $_POST['month'] ?? null; + $amount = $_POST['amount'] ?? null; + + if ($projectId && $month && $amount !== null) { + try { + $pdo = db(); + $stmt = $pdo->prepare("UPDATE expensesMonthly SET amount = :amount WHERE projectId = :projectId AND month = :month"); + $stmt->execute([ + ':amount' => $amount, + ':projectId' => $projectId, + ':month' => $month + ]); + + if ($stmt->rowCount() > 0) { + $response['success'] = true; + unset($response['error']); + } else { + $response['error'] = 'No record found to update.'; + } + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } else { + $response['error'] = 'Missing required fields.'; + } +} + +echo json_encode($response);