From 8172a27692c2d7a16333149ec33787e736a03437 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 27 Nov 2025 13:28:19 +0000 Subject: [PATCH] Auto commit: 2025-11-27T13:28:19.787Z --- assets/js/project_details.js | 21 +++- project_details.php | 15 +-- save_override.php | 207 +++++++++++++++++++++-------------- 3 files changed, 145 insertions(+), 98 deletions(-) diff --git a/assets/js/project_details.js b/assets/js/project_details.js index 37e5272..528d2c5 100644 --- a/assets/js/project_details.js +++ b/assets/js/project_details.js @@ -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 = 'Overridden'; + const buttonCell = this.parentElement; + buttonCell.innerHTML = 'Overridden'; 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}`); }); } }); diff --git a/project_details.php b/project_details.php index 9606cce..30e5510 100644 --- a/project_details.php +++ b/project_details.php @@ -276,19 +276,20 @@ if (!$project) { - > + > 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; \ No newline at end of file