+WIP+NSR
This commit is contained in:
parent
00cb8e93bb
commit
8cc108c2d6
@ -32,6 +32,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
document.getElementById('edit-ticketRestaurant').value = data.ticketRestaurant;
|
document.getElementById('edit-ticketRestaurant').value = data.ticketRestaurant;
|
||||||
document.getElementById('edit-metlife').value = data.metlife;
|
document.getElementById('edit-metlife').value = data.metlife;
|
||||||
document.getElementById('edit-topusPerMonth').value = data.topusPerMonth;
|
document.getElementById('edit-topusPerMonth').value = data.topusPerMonth;
|
||||||
|
document.getElementById('edit-grossRevenue').value = data.grossRevenue;
|
||||||
|
document.getElementById('edit-discountedRevenue').value = data.discountedRevenue;
|
||||||
|
|
||||||
editResourceModal.show();
|
editResourceModal.show();
|
||||||
});
|
});
|
||||||
@ -63,6 +65,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
document.getElementById('view-totalSalaryCostWithLabor').value = '€' + parseFloat(data.totalSalaryCostWithLabor).toFixed(2);
|
document.getElementById('view-totalSalaryCostWithLabor').value = '€' + parseFloat(data.totalSalaryCostWithLabor).toFixed(2);
|
||||||
document.getElementById('view-totalMonthlyCost').value = '€' + parseFloat(data.totalMonthlyCost).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-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();
|
viewResourceModal.show();
|
||||||
});
|
});
|
||||||
|
|||||||
3
db/migrations/007_add_revenue_fields_to_roster.sql
Normal file
3
db/migrations/007_add_revenue_fields_to_roster.sql
Normal file
@ -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;
|
||||||
@ -36,34 +36,67 @@ try {
|
|||||||
}
|
}
|
||||||
$months = get_month_range($project['startDate'], $project['endDate']);
|
$months = get_month_range($project['startDate'], $project['endDate']);
|
||||||
|
|
||||||
foreach ($metrics as $metric) {
|
// --- Financial Calculations ---
|
||||||
foreach ($months as $month) {
|
|
||||||
$financial_data[$metric][$month] = 0;
|
// 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 = $pdo->prepare("SELECT id FROM forecasting WHERE projectId = :pid ORDER BY versionNumber DESC LIMIT 1");
|
||||||
$forecast_stmt->execute([':pid' => $project_id]);
|
$forecast_stmt->execute([':pid' => $project_id]);
|
||||||
$latest_forecast = $forecast_stmt->fetch(PDO::FETCH_ASSOC);
|
$latest_forecast = $forecast_stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($latest_forecast) {
|
if ($latest_forecast) {
|
||||||
$cost_sql = "
|
$fid = $latest_forecast['id'];
|
||||||
SELECT fa.month, SUM(fa.allocatedDays * r.totalMonthlyCost) as totalCost
|
|
||||||
FROM forecastAllocation fa
|
// Base Monthly Cost
|
||||||
JOIN roster r ON fa.rosterId = r.id
|
$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";
|
||||||
WHERE fa.forecastingId = :fid
|
|
||||||
GROUP BY fa.month
|
|
||||||
";
|
|
||||||
$cost_stmt = $pdo->prepare($cost_sql);
|
$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);
|
$monthly_costs = $cost_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
foreach ($monthly_costs as $month => $cost) {
|
// Base Monthly WIP
|
||||||
$formatted_month = date('Y-m-01', strtotime($month));
|
$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";
|
||||||
if (isset($financial_data['Cost'][$formatted_month])) {
|
$wip_stmt = $pdo->prepare($wip_sql);
|
||||||
$financial_data['Cost'][$formatted_month] = $cost;
|
$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) {
|
} catch (PDOException $e) {
|
||||||
|
|||||||
46
index.php
46
index.php
@ -19,13 +19,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
$ticketRestaurant = (float)($_POST['ticketRestaurant'] ?? 0);
|
$ticketRestaurant = (float)($_POST['ticketRestaurant'] ?? 0);
|
||||||
$metlife = (float)($_POST['metlife'] ?? 0);
|
$metlife = (float)($_POST['metlife'] ?? 0);
|
||||||
$topusPerMonth = (float)($_POST['topusPerMonth'] ?? 0);
|
$topusPerMonth = (float)($_POST['topusPerMonth'] ?? 0);
|
||||||
|
$grossRevenue = (float)($_POST['grossRevenue'] ?? 0);
|
||||||
|
$discountedRevenue = (float)($_POST['discountedRevenue'] ?? 0);
|
||||||
|
|
||||||
// Auto-calculations
|
// Auto-calculations
|
||||||
$totalSalaryCostWithLabor = $newAmendedSalary + $employerContributions;
|
$totalSalaryCostWithLabor = $newAmendedSalary + $employerContributions;
|
||||||
$totalMonthlyCost = $totalSalaryCostWithLabor + $cars + $ticketRestaurant + $metlife + $topusPerMonth;
|
$totalMonthlyCost = $totalSalaryCostWithLabor + $cars + $ticketRestaurant + $metlife + $topusPerMonth;
|
||||||
$totalAnnualCost = $totalMonthlyCost * 14;
|
$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 = $pdo_form->prepare($insert_sql);
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
@ -43,7 +45,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
':topusPerMonth' => $topusPerMonth,
|
':topusPerMonth' => $topusPerMonth,
|
||||||
':totalSalaryCostWithLabor' => $totalSalaryCostWithLabor,
|
':totalSalaryCostWithLabor' => $totalSalaryCostWithLabor,
|
||||||
':totalMonthlyCost' => $totalMonthlyCost,
|
':totalMonthlyCost' => $totalMonthlyCost,
|
||||||
':totalAnnualCost' => $totalAnnualCost
|
':totalAnnualCost' => $totalAnnualCost,
|
||||||
|
':grossRevenue' => $grossRevenue,
|
||||||
|
':discountedRevenue' => $discountedRevenue
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// To prevent form resubmission on refresh, redirect
|
// 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);
|
$ticketRestaurant = (float)($_POST['ticketRestaurant'] ?? 0);
|
||||||
$metlife = (float)($_POST['metlife'] ?? 0);
|
$metlife = (float)($_POST['metlife'] ?? 0);
|
||||||
$topusPerMonth = (float)($_POST['topusPerMonth'] ?? 0);
|
$topusPerMonth = (float)($_POST['topusPerMonth'] ?? 0);
|
||||||
|
$grossRevenue = (float)($_POST['grossRevenue'] ?? 0);
|
||||||
|
$discountedRevenue = (float)($_POST['discountedRevenue'] ?? 0);
|
||||||
|
|
||||||
// Auto-calculations
|
// Auto-calculations
|
||||||
$totalSalaryCostWithLabor = $newAmendedSalary + $employerContributions;
|
$totalSalaryCostWithLabor = $newAmendedSalary + $employerContributions;
|
||||||
@ -111,7 +117,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
topusPerMonth = :topusPerMonth,
|
topusPerMonth = :topusPerMonth,
|
||||||
totalSalaryCostWithLabor = :totalSalaryCostWithLabor,
|
totalSalaryCostWithLabor = :totalSalaryCostWithLabor,
|
||||||
totalMonthlyCost = :totalMonthlyCost,
|
totalMonthlyCost = :totalMonthlyCost,
|
||||||
totalAnnualCost = :totalAnnualCost
|
totalAnnualCost = :totalAnnualCost,
|
||||||
|
grossRevenue = :grossRevenue,
|
||||||
|
discountedRevenue = :discountedRevenue
|
||||||
WHERE id = :id";
|
WHERE id = :id";
|
||||||
|
|
||||||
$stmt = $pdo_update->prepare($update_sql);
|
$stmt = $pdo_update->prepare($update_sql);
|
||||||
@ -131,7 +139,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
':topusPerMonth' => $topusPerMonth,
|
':topusPerMonth' => $topusPerMonth,
|
||||||
':totalSalaryCostWithLabor' => $totalSalaryCostWithLabor,
|
':totalSalaryCostWithLabor' => $totalSalaryCostWithLabor,
|
||||||
':totalMonthlyCost' => $totalMonthlyCost,
|
':totalMonthlyCost' => $totalMonthlyCost,
|
||||||
':totalAnnualCost' => $totalAnnualCost
|
':totalAnnualCost' => $totalAnnualCost,
|
||||||
|
':grossRevenue' => $grossRevenue,
|
||||||
|
':discountedRevenue' => $discountedRevenue
|
||||||
]);
|
]);
|
||||||
|
|
||||||
header("Location: " . $_SERVER['PHP_SELF']);
|
header("Location: " . $_SERVER['PHP_SELF']);
|
||||||
@ -344,6 +354,8 @@ try {
|
|||||||
<th>Total Salary Cost</th>
|
<th>Total Salary Cost</th>
|
||||||
<th>Total Monthly Cost</th>
|
<th>Total Monthly Cost</th>
|
||||||
<th>Total Annual Cost</th>
|
<th>Total Annual Cost</th>
|
||||||
|
<th>Gross Revenue</th>
|
||||||
|
<th>Discounted Revenue</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -370,6 +382,8 @@ try {
|
|||||||
<td>€<?php echo number_format($row['totalSalaryCostWithLabor'], 2); ?></td>
|
<td>€<?php echo number_format($row['totalSalaryCostWithLabor'], 2); ?></td>
|
||||||
<td>€<?php echo number_format($row['totalMonthlyCost'], 2); ?></td>
|
<td>€<?php echo number_format($row['totalMonthlyCost'], 2); ?></td>
|
||||||
<td>€<?php echo number_format($row['totalAnnualCost'], 2); ?></td>
|
<td>€<?php echo number_format($row['totalAnnualCost'], 2); ?></td>
|
||||||
|
<td>€<?php echo number_format($row['grossRevenue'], 2); ?></td>
|
||||||
|
<td>€<?php echo number_format($row['discountedRevenue'], 2); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<button class="btn btn-sm btn-outline-info me-2 view-btn"
|
<button class="btn btn-sm btn-outline-info me-2 view-btn"
|
||||||
@ -457,6 +471,14 @@ try {
|
|||||||
<label for="topusPerMonth" class="form-label">Topus/Month (€)</label>
|
<label for="topusPerMonth" class="form-label">Topus/Month (€)</label>
|
||||||
<input type="number" step="0.01" class="form-control" id="topusPerMonth" name="topusPerMonth" value="0">
|
<input type="number" step="0.01" class="form-control" id="topusPerMonth" name="topusPerMonth" value="0">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="grossRevenue" class="form-label">Gross Revenue (€)</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="grossRevenue" name="grossRevenue" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="discountedRevenue" class="form-label">Discounted Revenue (€)</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="discountedRevenue" name="discountedRevenue" value="0">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -532,6 +554,14 @@ try {
|
|||||||
<label for="edit-topusPerMonth" class="form-label">Topus/Month (€)</label>
|
<label for="edit-topusPerMonth" class="form-label">Topus/Month (€)</label>
|
||||||
<input type="number" step="0.01" class="form-control" id="edit-topusPerMonth" name="topusPerMonth">
|
<input type="number" step="0.01" class="form-control" id="edit-topusPerMonth" name="topusPerMonth">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit-grossRevenue" class="form-label">Gross Revenue (€)</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="edit-grossRevenue" name="grossRevenue">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="edit-discountedRevenue" class="form-label">Discounted Revenue (€)</label>
|
||||||
|
<input type="number" step="0.01" class="form-control" id="edit-discountedRevenue" name="discountedRevenue">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -613,6 +643,14 @@ try {
|
|||||||
<label class="form-label">Total Annual Cost (€)</label>
|
<label class="form-label">Total Annual Cost (€)</label>
|
||||||
<input type="text" class="form-control" id="view-totalAnnualCost" readonly>
|
<input type="text" class="form-control" id="view-totalAnnualCost" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Gross Revenue (€)</label>
|
||||||
|
<input type="text" class="form-control" id="view-grossRevenue" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Discounted Revenue (€)</label>
|
||||||
|
<input type="text" class="form-control" id="view-discountedRevenue" readonly>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|||||||
@ -67,33 +67,67 @@ if ($project_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the latest forecast version
|
// --- 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 = $pdo->prepare("SELECT id FROM forecasting WHERE projectId = :pid ORDER BY versionNumber DESC LIMIT 1");
|
||||||
$forecast_stmt->execute([':pid' => $project_id]);
|
$forecast_stmt->execute([':pid' => $project_id]);
|
||||||
$latest_forecast = $forecast_stmt->fetch(PDO::FETCH_ASSOC);
|
$latest_forecast = $forecast_stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($latest_forecast) {
|
if ($latest_forecast) {
|
||||||
// Calculate monthly costs
|
$fid = $latest_forecast['id'];
|
||||||
$cost_sql = "
|
|
||||||
SELECT
|
// Base Monthly Cost
|
||||||
fa.month,
|
$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";
|
||||||
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
|
|
||||||
";
|
|
||||||
$cost_stmt = $pdo->prepare($cost_sql);
|
$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);
|
$monthly_costs = $cost_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
foreach ($monthly_costs as $month => $cost) {
|
// Base Monthly WIP
|
||||||
// Ensure month format is consistent
|
$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";
|
||||||
$formatted_month = date('Y-m-01', strtotime($month));
|
$wip_stmt = $pdo->prepare($wip_sql);
|
||||||
if (isset($financial_data['Cost'][$formatted_month])) {
|
$wip_stmt->execute([':fid' => $fid]);
|
||||||
$financial_data['Cost'][$formatted_month] = $cost;
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,7 +234,13 @@ if (!$project) {
|
|||||||
<td class="fw-bold bg-body-tertiary"><?php echo $metric; ?></td>
|
<td class="fw-bold bg-body-tertiary"><?php echo $metric; ?></td>
|
||||||
<?php foreach ($months as $month): ?>
|
<?php foreach ($months as $month): ?>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
€<?php echo number_format($financial_data[$metric][$month] ?? 0, 2); ?>
|
<?php
|
||||||
|
if ($metric === 'Margin') {
|
||||||
|
echo number_format(($financial_data[$metric][$month] ?? 0) * 100, 2) . '%';
|
||||||
|
} else {
|
||||||
|
echo '€' . number_format($financial_data[$metric][$month] ?? 0, 2);
|
||||||
|
}
|
||||||
|
?>
|
||||||
</td>
|
</td>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user