diff --git a/assets/js/main.js b/assets/js/main.js index 26d50d9..d41d1e3 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -32,6 +32,8 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('edit-ticketRestaurant').value = data.ticketRestaurant; document.getElementById('edit-metlife').value = data.metlife; document.getElementById('edit-topusPerMonth').value = data.topusPerMonth; + document.getElementById('edit-grossRevenue').value = data.grossRevenue; + document.getElementById('edit-discountedRevenue').value = data.discountedRevenue; editResourceModal.show(); }); @@ -63,6 +65,8 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('view-totalSalaryCostWithLabor').value = '€' + parseFloat(data.totalSalaryCostWithLabor).toFixed(2); document.getElementById('view-totalMonthlyCost').value = '€' + parseFloat(data.totalMonthlyCost).toFixed(2); document.getElementById('view-totalAnnualCost').value = '€' + parseFloat(data.totalAnnualCost).toFixed(2); + document.getElementById('view-grossRevenue').value = '€' + parseFloat(data.grossRevenue).toFixed(2); + document.getElementById('view-discountedRevenue').value = '€' + parseFloat(data.discountedRevenue).toFixed(2); viewResourceModal.show(); }); diff --git a/db/migrations/007_add_revenue_fields_to_roster.sql b/db/migrations/007_add_revenue_fields_to_roster.sql new file mode 100644 index 0000000..8590309 --- /dev/null +++ b/db/migrations/007_add_revenue_fields_to_roster.sql @@ -0,0 +1,3 @@ +ALTER TABLE `roster` +ADD COLUMN `grossRevenue` DECIMAL(10, 2) NOT NULL DEFAULT 0.00, +ADD COLUMN `discountedRevenue` DECIMAL(10, 2) NOT NULL DEFAULT 0.00; diff --git a/export_project_finance.php b/export_project_finance.php index dc0af68..e249f00 100644 --- a/export_project_finance.php +++ b/export_project_finance.php @@ -36,34 +36,67 @@ try { } $months = get_month_range($project['startDate'], $project['endDate']); - foreach ($metrics as $metric) { - foreach ($months as $month) { - $financial_data[$metric][$month] = 0; - } - } + // --- Financial Calculations --- + + // 1. Fetch base monthly data + $monthly_costs = []; + $monthly_wip = []; $forecast_stmt = $pdo->prepare("SELECT id FROM forecasting WHERE projectId = :pid ORDER BY versionNumber DESC LIMIT 1"); $forecast_stmt->execute([':pid' => $project_id]); $latest_forecast = $forecast_stmt->fetch(PDO::FETCH_ASSOC); if ($latest_forecast) { - $cost_sql = " - SELECT fa.month, SUM(fa.allocatedDays * r.totalMonthlyCost) as totalCost - FROM forecastAllocation fa - JOIN roster r ON fa.rosterId = r.id - WHERE fa.forecastingId = :fid - GROUP BY fa.month - "; + $fid = $latest_forecast['id']; + + // Base Monthly Cost + $cost_sql = "SELECT fa.month, SUM(fa.allocatedDays * r.totalMonthlyCost) FROM forecastAllocation fa JOIN roster r ON fa.rosterId = r.id WHERE fa.forecastingId = :fid GROUP BY fa.month"; $cost_stmt = $pdo->prepare($cost_sql); - $cost_stmt->execute([':fid' => $latest_forecast['id']]); + $cost_stmt->execute([':fid' => $fid]); $monthly_costs = $cost_stmt->fetchAll(PDO::FETCH_KEY_PAIR); - foreach ($monthly_costs as $month => $cost) { - $formatted_month = date('Y-m-01', strtotime($month)); - if (isset($financial_data['Cost'][$formatted_month])) { - $financial_data['Cost'][$formatted_month] = $cost; - } - } + // Base Monthly WIP + $wip_sql = "SELECT fa.month, SUM(fa.allocatedDays * r.discountedRevenue) FROM forecastAllocation fa JOIN roster r ON fa.rosterId = r.id WHERE fa.forecastingId = :fid GROUP BY fa.month"; + $wip_stmt = $pdo->prepare($wip_sql); + $wip_stmt->execute([':fid' => $fid]); + $monthly_wip = $wip_stmt->fetchAll(PDO::FETCH_KEY_PAIR); + } + + // Base Monthly Billings + $billing_stmt = $pdo->prepare("SELECT month, amount FROM billingMonthly WHERE projectId = :pid"); + $billing_stmt->execute([':pid' => $project_id]); + $monthly_billing = $billing_stmt->fetchAll(PDO::FETCH_KEY_PAIR); + + // 2. Calculate cumulative values month by month + $cumulative_billing = 0; + $cumulative_wip = 0; + $cumulative_cost = 0; + + foreach ($months as $month) { + // Normalize month keys from fetched data + $cost = $monthly_costs[$month] ?? 0; + $wip = $monthly_wip[$month] ?? 0; + $billing = $monthly_billing[$month] ?? 0; + + // Cumulative calculations + $cumulative_billing += $billing; + $cumulative_wip += $wip; + $cumulative_cost += $cost; + + $financial_data['Billings'][$month] = $cumulative_billing; + $financial_data['WIP'][$month] = $cumulative_wip; + $financial_data['Cost'][$month] = $cumulative_cost; + + // Other metrics are 0 for now + $financial_data['Opening Balance'][$month] = 0; + $financial_data['Expenses'][$month] = 0; + + // Calculated metrics (NSR and Margin) + $nsr = $financial_data['WIP'][$month] + $financial_data['Billings'][$month] - $financial_data['Opening Balance'][$month] - $financial_data['Expenses'][$month]; + $financial_data['NSR'][$month] = $nsr; + + $margin = ($nsr != 0) ? (($nsr - $financial_data['Cost'][$month]) / $nsr) : 0; + $financial_data['Margin'][$month] = $margin; } } } catch (PDOException $e) { diff --git a/index.php b/index.php index b88a5b3..18d9d7f 100644 --- a/index.php +++ b/index.php @@ -19,13 +19,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' $ticketRestaurant = (float)($_POST['ticketRestaurant'] ?? 0); $metlife = (float)($_POST['metlife'] ?? 0); $topusPerMonth = (float)($_POST['topusPerMonth'] ?? 0); + $grossRevenue = (float)($_POST['grossRevenue'] ?? 0); + $discountedRevenue = (float)($_POST['discountedRevenue'] ?? 0); // Auto-calculations $totalSalaryCostWithLabor = $newAmendedSalary + $employerContributions; $totalMonthlyCost = $totalSalaryCostWithLabor + $cars + $ticketRestaurant + $metlife + $topusPerMonth; $totalAnnualCost = $totalMonthlyCost * 14; - $insert_sql = "INSERT INTO roster (sapCode, fullNameEn, legalEntity, functionBusinessUnit, costCenterCode, `level`, newAmendedSalary, employerContributions, cars, ticketRestaurant, metlife, topusPerMonth, totalSalaryCostWithLabor, totalMonthlyCost, totalAnnualCost) VALUES (:sapCode, :fullNameEn, :legalEntity, :functionBusinessUnit, :costCenterCode, :level, :newAmendedSalary, :employerContributions, :cars, :ticketRestaurant, :metlife, :topusPerMonth, :totalSalaryCostWithLabor, :totalMonthlyCost, :totalAnnualCost)"; + $insert_sql = "INSERT INTO roster (sapCode, fullNameEn, legalEntity, functionBusinessUnit, costCenterCode, `level`, newAmendedSalary, employerContributions, cars, ticketRestaurant, metlife, topusPerMonth, totalSalaryCostWithLabor, totalMonthlyCost, totalAnnualCost, grossRevenue, discountedRevenue) VALUES (:sapCode, :fullNameEn, :legalEntity, :functionBusinessUnit, :costCenterCode, :level, :newAmendedSalary, :employerContributions, :cars, :ticketRestaurant, :metlife, :topusPerMonth, :totalSalaryCostWithLabor, :totalMonthlyCost, :totalAnnualCost, :grossRevenue, :discountedRevenue)"; $stmt = $pdo_form->prepare($insert_sql); $stmt->execute([ @@ -43,7 +45,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' ':topusPerMonth' => $topusPerMonth, ':totalSalaryCostWithLabor' => $totalSalaryCostWithLabor, ':totalMonthlyCost' => $totalMonthlyCost, - ':totalAnnualCost' => $totalAnnualCost + ':totalAnnualCost' => $totalAnnualCost, + ':grossRevenue' => $grossRevenue, + ':discountedRevenue' => $discountedRevenue ]); // To prevent form resubmission on refresh, redirect @@ -90,6 +94,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' $ticketRestaurant = (float)($_POST['ticketRestaurant'] ?? 0); $metlife = (float)($_POST['metlife'] ?? 0); $topusPerMonth = (float)($_POST['topusPerMonth'] ?? 0); + $grossRevenue = (float)($_POST['grossRevenue'] ?? 0); + $discountedRevenue = (float)($_POST['discountedRevenue'] ?? 0); // Auto-calculations $totalSalaryCostWithLabor = $newAmendedSalary + $employerContributions; @@ -111,7 +117,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' topusPerMonth = :topusPerMonth, totalSalaryCostWithLabor = :totalSalaryCostWithLabor, totalMonthlyCost = :totalMonthlyCost, - totalAnnualCost = :totalAnnualCost + totalAnnualCost = :totalAnnualCost, + grossRevenue = :grossRevenue, + discountedRevenue = :discountedRevenue WHERE id = :id"; $stmt = $pdo_update->prepare($update_sql); @@ -131,7 +139,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' ':topusPerMonth' => $topusPerMonth, ':totalSalaryCostWithLabor' => $totalSalaryCostWithLabor, ':totalMonthlyCost' => $totalMonthlyCost, - ':totalAnnualCost' => $totalAnnualCost + ':totalAnnualCost' => $totalAnnualCost, + ':grossRevenue' => $grossRevenue, + ':discountedRevenue' => $discountedRevenue ]); header("Location: " . $_SERVER['PHP_SELF']); @@ -344,6 +354,8 @@ try { Total Salary Cost Total Monthly Cost Total Annual Cost + Gross Revenue + Discounted Revenue Actions @@ -370,6 +382,8 @@ try { € + € + €
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +