Auto commit: 2025-11-27T13:28:19.787Z

This commit is contained in:
Flatlogic Bot 2025-11-27 13:28:19 +00:00
parent ffe0142f5f
commit 8172a27692
3 changed files with 145 additions and 98 deletions

View File

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

View File

@ -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) . '%';

View File

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