This commit is contained in:
Flatlogic Bot 2025-11-26 09:11:28 +00:00
parent 00cb8e93bb
commit 8cc108c2d6
5 changed files with 161 additions and 43 deletions

View File

@ -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();
});

View 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;

View File

@ -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) {

View File

@ -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 {
<th>Total Salary Cost</th>
<th>Total Monthly Cost</th>
<th>Total Annual Cost</th>
<th>Gross Revenue</th>
<th>Discounted Revenue</th>
<th>Actions</th>
</tr>
</thead>
@ -370,6 +382,8 @@ try {
<td>&euro;<?php echo number_format($row['totalSalaryCostWithLabor'], 2); ?></td>
<td>&euro;<?php echo number_format($row['totalMonthlyCost'], 2); ?></td>
<td>&euro;<?php echo number_format($row['totalAnnualCost'], 2); ?></td>
<td>&euro;<?php echo number_format($row['grossRevenue'], 2); ?></td>
<td>&euro;<?php echo number_format($row['discountedRevenue'], 2); ?></td>
<td>
<div class="d-flex">
<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 (&euro;)</label>
<input type="number" step="0.01" class="form-control" id="topusPerMonth" name="topusPerMonth" value="0">
</div>
<div class="col-md-6 mb-3">
<label for="grossRevenue" class="form-label">Gross Revenue (&euro;)</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 (&euro;)</label>
<input type="number" step="0.01" class="form-control" id="discountedRevenue" name="discountedRevenue" value="0">
</div>
</div>
</div>
<div class="modal-footer">
@ -532,6 +554,14 @@ try {
<label for="edit-topusPerMonth" class="form-label">Topus/Month (&euro;)</label>
<input type="number" step="0.01" class="form-control" id="edit-topusPerMonth" name="topusPerMonth">
</div>
<div class="col-md-6 mb-3">
<label for="edit-grossRevenue" class="form-label">Gross Revenue (&euro;)</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 (&euro;)</label>
<input type="number" step="0.01" class="form-control" id="edit-discountedRevenue" name="discountedRevenue">
</div>
</div>
</div>
<div class="modal-footer">
@ -613,6 +643,14 @@ try {
<label class="form-label">Total Annual Cost (&euro;)</label>
<input type="text" class="form-control" id="view-totalAnnualCost" readonly>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Gross Revenue (&euro;)</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 (&euro;)</label>
<input type="text" class="form-control" id="view-discountedRevenue" readonly>
</div>
</div>
</div>
<div class="modal-footer">

View File

@ -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->execute([':pid' => $project_id]);
$latest_forecast = $forecast_stmt->fetch(PDO::FETCH_ASSOC);
if ($latest_forecast) {
// Calculate monthly costs
$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) {
// Ensure month format is consistent
$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;
}
}
}
@ -200,7 +234,13 @@ if (!$project) {
<td class="fw-bold bg-body-tertiary"><?php echo $metric; ?></td>
<?php foreach ($months as $month): ?>
<td class="text-end">
&euro;<?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 '&euro;' . number_format($financial_data[$metric][$month] ?? 0, 2);
}
?>
</td>
<?php endforeach; ?>
</tr>