Auto commit: 2025-11-27T13:28:19.787Z
This commit is contained in:
parent
ffe0142f5f
commit
8172a27692
@ -31,11 +31,20 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Get response text for error message
|
||||
return response.text().then(text => {
|
||||
throw new Error(`HTTP error! status: ${response.status}, response: ${text}`);
|
||||
});
|
||||
}
|
||||
return response.json(); // Directly parse JSON
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Replace button with "Overridden" badge
|
||||
this.parentElement.innerHTML = '<span class="badge bg-success">Overridden</span>';
|
||||
const buttonCell = this.parentElement;
|
||||
buttonCell.innerHTML = '<span class="badge bg-success">Overridden</span>';
|
||||
|
||||
updateMetricCell('WIP', month, data.wip);
|
||||
updateMetricCell('Opening Balance', month, data.openingBalance);
|
||||
@ -45,22 +54,22 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
updateMetricCell('NSR', month, data.nsr);
|
||||
updateMetricCell('Margin', month, data.margin);
|
||||
|
||||
|
||||
// Show the next override button
|
||||
const nextButtonCell = this.parentElement.nextElementSibling;
|
||||
const nextButtonCell = buttonCell.nextElementSibling;
|
||||
if (nextButtonCell) {
|
||||
const nextButton = nextButtonCell.querySelector('.btn-override');
|
||||
if (nextButton) {
|
||||
nextButton.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
alert('Error saving override: ' + data.error);
|
||||
alert('Error saving override: ' + (data.message || 'Unknown error.'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An unexpected error occurred.');
|
||||
alert(`An unexpected error occurred: ${error.message}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -276,19 +276,20 @@ if (!$project) {
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$editable_metrics = ['WIP', 'Opening Balance', 'Billings', 'Expenses', 'Cost'];
|
||||
$metrics_to_id = ['WIP', 'Opening Balance', 'Billings', 'Expenses', 'Cost', 'NSR', 'Margin'];
|
||||
foreach ($metrics as $metric): ?>
|
||||
<tr>
|
||||
<td class="fw-bold bg-body-tertiary"><?php echo $metric; ?></td>
|
||||
<?php
|
||||
foreach ($months as $month):
|
||||
$cell_id = '';
|
||||
$refreshable_metrics = array_merge($editable_metrics, ['NSR', 'Margin']);
|
||||
if (in_array($metric, $refreshable_metrics)) {
|
||||
$cell_id = 'id="' . str_replace(' ', '-', strtolower($metric)) . '-' . date('Y-m', strtotime($month)) . '"';
|
||||
foreach ($months as $month):
|
||||
$cell_id_attr = '';
|
||||
if (in_array($metric, $metrics_to_id)) {
|
||||
$metric_id = str_replace(' ', '-', strtolower($metric));
|
||||
$month_id = date('Y-m', strtotime($month));
|
||||
$cell_id_attr = "id=\"{$metric_id}-{$month_id}\"";
|
||||
}
|
||||
?>
|
||||
<td class="text-end" <?php echo $cell_id; ?>>
|
||||
<td class="text-end" <?php echo $cell_id_attr; ?> >
|
||||
<?php
|
||||
if ($metric === 'Margin') {
|
||||
echo number_format(($financial_data[$metric][$month] ?? 0) * 100, 2) . '%';
|
||||
|
||||
@ -1,103 +1,140 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
require_once 'db/config.php';
|
||||
|
||||
$response = ['success' => false, 'error' => 'Invalid request'];
|
||||
// Function to send a consistent JSON response, aggressively cleaning any output.
|
||||
function send_json_response($data) {
|
||||
// Ensure no stray output is included.
|
||||
if (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit(); // Terminate immediately
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$project_id = $_POST['projectId'] ?? null;
|
||||
$month = $_POST['month'] ?? null;
|
||||
// Set a global exception handler to catch any uncaught errors
|
||||
set_exception_handler(function($exception) {
|
||||
error_log($exception->getMessage());
|
||||
send_json_response(['success' => false, 'message' => 'A server error occurred: ' . $exception->getMessage()]);
|
||||
});
|
||||
|
||||
$metrics = [
|
||||
'wip' => $_POST['wip'] ?? null,
|
||||
'opening_balance' => $_POST['openingBalance'] ?? null,
|
||||
'billings' => $_POST['billings'] ?? null,
|
||||
'expenses' => $_POST['expenses'] ?? null,
|
||||
'cost' => $_POST['cost'] ?? null,
|
||||
$response_data = [
|
||||
'success' => false,
|
||||
'message' => 'An unknown error occurred.',
|
||||
// Initialize all fields JS expects to avoid 'undefined' issues
|
||||
'wip' => 0,
|
||||
'openingBalance' => 0,
|
||||
'billings' => 0,
|
||||
'expenses' => 0,
|
||||
'cost' => 0,
|
||||
'nsr' => 0,
|
||||
'margin' => 0,
|
||||
];
|
||||
|
||||
ob_clean(); // Clear any pre-existing output
|
||||
try {
|
||||
$data = $_POST;
|
||||
|
||||
$projectId = $data['projectId'] ?? null;
|
||||
$month = $data['month'] ?? null;
|
||||
|
||||
if (!$projectId || !$month) {
|
||||
send_json_response(['success' => false, 'message' => 'Missing project ID or month.']);
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
$metrics_from_form = [
|
||||
'wip' => $data['wip'] ?? 0,
|
||||
'opening_balance' => $data['openingBalance'] ?? 0,
|
||||
'billing' => $data['billings'] ?? 0,
|
||||
'expenses' => $data['expenses'] ?? 0,
|
||||
'cost' => $data['cost'] ?? 0,
|
||||
];
|
||||
|
||||
if ($project_id && $month && !in_array(null, $metrics, true)) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "INSERT INTO projectFinanceMonthly (projectId, month, metricName, value, is_overridden)
|
||||
VALUES (:pid, :m, :metric, :value, 1)
|
||||
ON DUPLICATE KEY UPDATE value = :value, is_overridden = 1";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$metric_map = [
|
||||
'wip' => 'wip',
|
||||
'opening_balance' => 'opening_balance',
|
||||
'billings' => 'billing',
|
||||
'expenses' => 'expenses',
|
||||
'cost' => 'cost'
|
||||
];
|
||||
// Check if records exist, if not, INSERT them. Otherwise, UPDATE.
|
||||
$select_sql = "SELECT COUNT(*) FROM projectFinanceMonthly WHERE projectId = :projectId AND month = :month AND metricName = :metricName";
|
||||
$select_stmt = $pdo->prepare($select_sql);
|
||||
|
||||
foreach ($metrics as $metricKey => $value) {
|
||||
if (isset($metric_map[$metricKey])) {
|
||||
$metricName = $metric_map[$metricKey];
|
||||
$stmt->execute([
|
||||
':pid' => $project_id,
|
||||
':m' => $month,
|
||||
':metric' => $metricName,
|
||||
':value' => $value
|
||||
]);
|
||||
}
|
||||
}
|
||||
$update_sql = "UPDATE projectFinanceMonthly SET value = :value, is_overridden = 1 WHERE projectId = :projectId AND month = :month AND metricName = :metricName";
|
||||
$update_stmt = $pdo->prepare($update_sql);
|
||||
|
||||
// Use the newly submitted values from POST as the source of truth for calculation
|
||||
$wip = (float)($metrics['wip'] ?? 0);
|
||||
$billings = (float)($metrics['billings'] ?? 0);
|
||||
$opening_balance = (float)($metrics['opening_balance'] ?? 0);
|
||||
$expenses = (float)($metrics['expenses'] ?? 0);
|
||||
$cost = (float)($metrics['cost'] ?? 0);
|
||||
$insert_sql = "INSERT INTO projectFinanceMonthly (projectId, month, metricName, value, is_overridden) VALUES (:projectId, :month, :metricName, :value, 1)";
|
||||
$insert_stmt = $pdo->prepare($insert_sql);
|
||||
|
||||
// Calculate NSR
|
||||
$nsr = $wip + $billings - $opening_balance - $expenses;
|
||||
foreach ($metrics_from_form as $metricName => $value) {
|
||||
$select_stmt->execute([
|
||||
'projectId' => $projectId,
|
||||
'month' => $month,
|
||||
'metricName' => $metricName
|
||||
]);
|
||||
$exists = $select_stmt->fetchColumn() > 0;
|
||||
|
||||
// Calculate Margin
|
||||
$margin = ($nsr != 0) ? (($nsr - $cost) / $nsr) : 0;
|
||||
|
||||
// Save NSR and Margin
|
||||
$stmt->execute([
|
||||
':pid' => $project_id,
|
||||
':m' => $month,
|
||||
':metric' => 'nsr',
|
||||
':value' => $nsr
|
||||
if ($exists) {
|
||||
$update_stmt->execute([
|
||||
'value' => $value,
|
||||
'projectId' => $projectId,
|
||||
'month' => $month,
|
||||
'metricName' => $metricName,
|
||||
]);
|
||||
|
||||
$stmt->execute([
|
||||
':pid' => $project_id,
|
||||
':m' => $month,
|
||||
':metric' => 'margin',
|
||||
':value' => $margin
|
||||
} else {
|
||||
$insert_stmt->execute([
|
||||
'projectId' => $projectId,
|
||||
'month' => $month,
|
||||
'metricName' => $metricName,
|
||||
'value' => $value,
|
||||
]);
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'wip' => $wip,
|
||||
'openingBalance' => $opening_balance,
|
||||
'billings' => $billings,
|
||||
'expenses' => $expenses,
|
||||
'cost' => $cost,
|
||||
'nsr' => $nsr,
|
||||
'margin' => $margin
|
||||
];
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||
error_log($e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$response['error'] = 'Missing required fields.';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
echo json_encode($response, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
error_log('JSON encoding error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Internal server error during JSON encoding.']);
|
||||
$pdo->commit();
|
||||
|
||||
// Recalculate NSR and Margin
|
||||
$billing = floatval($metrics_from_form['billing']);
|
||||
$expenses = floatval($metrics_from_form['expenses']);
|
||||
$cost = floatval($metrics_from_form['cost']);
|
||||
$wip = floatval($metrics_from_form['wip']);
|
||||
$opening_balance = floatval($metrics_from_form['opening_balance']);
|
||||
|
||||
$nsr = $wip + $billing - $opening_balance - $expenses;
|
||||
$margin = ($nsr > 0) ? (($nsr - $cost) / $nsr) : 0;
|
||||
|
||||
|
||||
// Fetch the updated data to send back
|
||||
$stmt = $pdo->prepare("SELECT metricName, value FROM projectFinanceMonthly WHERE projectId = ? AND month = ?");
|
||||
$stmt->execute([$projectId, $month]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$response_data['success'] = true;
|
||||
$response_data['message'] = 'Override saved successfully.';
|
||||
|
||||
foreach ($rows as $row) {
|
||||
// Map db snake_case to form's camelCase
|
||||
if ($row['metricName'] === 'opening_balance') {
|
||||
$response_data['openingBalance'] = $row['value'];
|
||||
} else if ($row['metricName'] === 'billing') {
|
||||
$response_data['billings'] = $row['value'];
|
||||
} else {
|
||||
// Directly map other metrics like 'wip', 'expenses'
|
||||
$response_data[$row['metricName']] = $row['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$response_data['nsr'] = $nsr;
|
||||
$response_data['margin'] = $margin;
|
||||
|
||||
send_json_response($response_data);
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
// Log the error and send a JSON response directly
|
||||
error_log($e->getMessage());
|
||||
send_json_response(['success' => false, 'message' => 'A server error occurred: ' . $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
Loading…
x
Reference in New Issue
Block a user