diff --git a/assets/js/project_details.js b/assets/js/project_details.js deleted file mode 100644 index 7925843..0000000 --- a/assets/js/project_details.js +++ /dev/null @@ -1,295 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const dataScript = document.getElementById('financial-data'); - if (!dataScript) return; - - const appData = JSON.parse(dataScript.textContent); - const { projectId, months, metrics, initialFinancialData, baseData, overrides } = appData; - - let state = { - isOverrideActive: false, - overrideMonth: null, - originalTableState: {}, - currentFinancialData: JSON.parse(JSON.stringify(initialFinancialData)) // Deep copy - }; - - const table = document.getElementById('financials-table'); - if (!table) return; - - // UTILITY FUNCTIONS - const formatCurrency = (value) => `€${(value || 0).toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; - const formatMargin = (value) => `${((value || 0) * 100).toFixed(2).replace('.', ',')}%`; - const parseLocaleNumber = (stringNumber) => { - if (typeof stringNumber !== 'string') return stringNumber; - // Remove thousands separators (.), then replace decimal comma with a period. - return Number(String(stringNumber).replace(/\./g, '').replace(',', '.')); - }; - - function debounce(func, wait) { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - } - - - // MAIN LOGIC - function recalculateFinancials(overrideMonth, overrideValues) { - const newData = JSON.parse(JSON.stringify(state.currentFinancialData)); - const overrideMonthIndex = months.indexOf(overrideMonth); - - if (overrideMonthIndex === -1) { - console.error("Override month not found in months array!"); - return state.currentFinancialData; - } - - // Step 1: Apply the override values for the specific override month. - for (const key in overrideValues) { - overrideValues[key] = parseFloat(overrideValues[key] || 0); - } - - newData['Opening Balance'][overrideMonth] = overrideValues['Opening Balance']; - newData.Billings[overrideMonth] = overrideValues.Billings; - newData.WIP[overrideMonth] = overrideValues.WIP; - newData.Expenses[overrideMonth] = overrideValues.Expenses; - newData.Cost[overrideMonth] = overrideValues.Cost; - newData.Payment[overrideMonth] = overrideValues.Payment; - - let nsr = newData.WIP[overrideMonth] + newData.Billings[overrideMonth] - newData['Opening Balance'][overrideMonth] - newData.Expenses[overrideMonth]; - newData.NSR[overrideMonth] = nsr; - let margin = (nsr !== 0) ? ((nsr - newData.Cost[overrideMonth]) / nsr) : 0; - newData.Margin[overrideMonth] = margin; - - // Step 2: Recalculate all subsequent months - for (let i = overrideMonthIndex + 1; i < months.length; i++) { - const month = months[i]; - const prevMonth = months[i - 1]; - - const prevCumulativeBilling = parseFloat(newData.Billings[prevMonth] || 0); - const prevCumulativeCost = parseFloat(newData.Cost[prevMonth] || 0); - const prevCumulativeExpenses = parseFloat(newData.Expenses[prevMonth] || 0); - const prevWIP = parseFloat(newData.WIP[prevMonth] || 0); - - const monthlyCost = parseFloat(baseData.monthly_costs[month] || 0); - const monthlyBilling = parseFloat(baseData.monthly_billing[month] || 0); - const monthlyExpenses = parseFloat(baseData.monthly_expenses[month] || 0); - const monthlyWIPChange = parseFloat(baseData.monthly_wip[month] || 0); - - const newCumulativeBilling = prevCumulativeBilling + monthlyBilling; - const newCumulativeCost = prevCumulativeCost + monthlyCost; - const newCumulativeExpenses = prevCumulativeExpenses + monthlyExpenses; - const newWIP = prevWIP + monthlyExpenses + monthlyWIPChange - monthlyBilling; - - newData.Billings[month] = newCumulativeBilling; - newData.Cost[month] = newCumulativeCost; - newData.Expenses[month] = newCumulativeExpenses; - newData.WIP[month] = newWIP; - - // THE FIX: Carry over the previous month's Net Service Revenue as the next month's Opening Balance. - newData['Opening Balance'][month] = newData.NSR[prevMonth] || 0; - newData.Payment[month] = 0; - - nsr = newWIP + newCumulativeBilling - newData['Opening Balance'][month] - newCumulativeExpenses; - newData.NSR[month] = nsr; - margin = (nsr !== 0) ? ((nsr - newCumulativeCost) / nsr) : 0; - newData.Margin[month] = margin; - } - - return newData; - } - - function updateTable(newData) { - state.currentFinancialData = newData; - for (const metric of metrics) { - for (const month of months) { - const cell = table.querySelector(`td[data-month="${month}"][data-metric="${metric}"]`); - if (cell && cell.firstElementChild?.tagName !== 'INPUT') { - const value = newData[metric][month]; - cell.textContent = metric === 'Margin' ? formatMargin(value) : formatCurrency(value); - } - } - } - } - - function recalculateForOverrideMonth(overrideMonth, overrideValues) { - const newData = JSON.parse(JSON.stringify(state.currentFinancialData)); // Deep copy - - // Use the user's input values for the override month - newData['Opening Balance'][overrideMonth] = overrideValues['Opening Balance']; - newData.Billings[overrideMonth] = overrideValues.Billings; - newData.WIP[overrideMonth] = overrideValues.WIP; - newData.Expenses[overrideMonth] = overrideValues.Expenses; - newData.Cost[overrideMonth] = overrideValues.Cost; - newData.Payment[overrideMonth] = overrideValues.Payment; - - // Recalculate dependent metrics for the override month - const nsr = newData.WIP[overrideMonth] + newData.Billings[overrideMonth] - newData['Opening Balance'][overrideMonth] - newData.Expenses[overrideMonth]; - newData.NSR[overrideMonth] = nsr; - const margin = (nsr !== 0) ? ((nsr - newData.Cost[overrideMonth]) / nsr) : 0; - newData.Margin[overrideMonth] = margin; - - return newData; - } - - function handleInputChange() { - const overrideValues = {}; - const editableMetrics = ['Opening Balance', 'Billings', 'WIP', 'Expenses', 'Cost', 'Payment']; - editableMetrics.forEach(metric => { - const input = table.querySelector(`td[data-month="${state.overrideMonth}"][data-metric="${metric}"] input`); - overrideValues[metric] = parseLocaleNumber(input.value) || 0; - }); - - const recalculatedData = recalculateForOverrideMonth(state.overrideMonth, overrideValues); - updateTable(recalculatedData); - } - - function enterOverrideMode(month) { - if (state.isOverrideActive) return; - - state.isOverrideActive = true; - state.overrideMonth = month; - - const editableMetrics = ['Opening Balance', 'Billings', 'WIP', 'Expenses', 'Cost', 'Payment']; - - metrics.forEach(metric => { - const cell = table.querySelector(`td[data-month="${month}"][data-metric="${metric}"]`); - if (!cell) return; - - const originalValue = state.currentFinancialData[metric][month]; - state.originalTableState[metric] = cell.innerHTML; - - if (editableMetrics.includes(metric)) { - // Ensure originalValue is a number before formatting - const numericValue = (typeof originalValue === 'number') ? originalValue : 0; - // Format to locale string with comma decimal separator for the input value - const localeValue = numericValue.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); - cell.innerHTML = ``; - } - }); - - const debouncedRecalc = debounce(handleInputChange, 200); - table.querySelectorAll(`td[data-month="${month}"] input`).forEach(input => { - input.addEventListener('input', debouncedRecalc); - }); - - const buttonCell = table.querySelector(`th button[data-month="${month}"]`).parentElement; - state.originalTableState['button'] = buttonCell.innerHTML; - buttonCell.innerHTML = ` -
- - -
- `; - } - - async function confirmOverride() { - try { - const overrideValues = {}; - const editableMetrics = ['Opening Balance', 'Billings', 'WIP', 'Expenses', 'Cost', 'Payment']; - editableMetrics.forEach(metric => { - const input = table.querySelector(`td[data-month="${state.overrideMonth}"][data-metric="${metric}"] input`); - if (!input) { - throw new Error(`Could not find input for metric: ${metric} in month ${state.overrideMonth}`); - } - overrideValues[metric] = parseLocaleNumber(input.value) || 0; - }); - - const finalData = recalculateFinancials(state.overrideMonth, overrideValues); - - const monthsToSave = months.slice(months.indexOf(state.overrideMonth)); - const dataToSave = {}; - monthsToSave.forEach(month => { - dataToSave[month] = {}; - metrics.forEach(metric => { - const rawValue = finalData[metric][month]; - let cleanValue = typeof rawValue === 'string' ? parseLocaleNumber(rawValue) : rawValue; - - if (!isFinite(cleanValue)) { - console.warn(`Invalid number for ${metric} in ${month}. Resetting to 0. Original:`, rawValue); - cleanValue = 0; - } - dataToSave[month][metric] = cleanValue; - }); - }); - - const payload = { - project_id: projectId, - overrides: Object.entries(dataToSave).map(([month, values]) => ({ - month: month, - opening_balance: values['Opening Balance'], - payment: values.Payment, - wip: values.WIP, - expenses: values.Expenses, - cost: values.Cost, - nsr: values.NSR, - margin: values.Margin, - is_confirmed: month === state.overrideMonth ? 1 : 0 - })) - }; - - - - const response = await fetch('save_override.php?t=' + new Date().getTime(), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload) - }); - - if (!response.ok) { - let errorMsg = `HTTP error! status: ${response.status}`; - try { - const errorData = await response.json(); - errorMsg = errorData.message || errorMsg; - } catch (e) { - // Not a JSON response - } - throw new Error(errorMsg); - } - - const result = await response.json(); - if (result.success) { - alert(result.message || 'Override confirmed successfully!'); - window.location.reload(); - } else { - throw new Error(result.message || 'Failed to save override.'); - } - } catch (error) { - console.error('Error confirming override:', error); - alert(`Error: ${error.message}`); - } - } - - function exitOverrideMode() { - metrics.forEach(metric => { - const cell = table.querySelector(`td[data-month="${state.overrideMonth}"][data-metric="${metric}"]`); - if (cell) { - cell.innerHTML = state.originalTableState[metric]; - } - }); - - const buttonCell = table.querySelector(`th .btn-group`).parentElement; - buttonCell.innerHTML = state.originalTableState['button']; - - updateTable(initialFinancialData); - - state.isOverrideActive = false; - state.overrideMonth = null; - state.originalTableState = {}; - } - - table.addEventListener('click', (e) => { - if (e.target.classList.contains('override-btn')) { - enterOverrideMode(e.target.dataset.month); - } else if (e.target.classList.contains('confirm-override')) { - confirmOverride(); - } else if (e.target.classList.contains('cancel-override')) { - exitOverrideMode(); - } - }); -}); \ No newline at end of file diff --git a/db/migrate.php b/db/migrate.php deleted file mode 100644 index b3df702..0000000 --- a/db/migrate.php +++ /dev/null @@ -1,81 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - // Create migrations table if it doesn't exist - $pdo->exec("CREATE TABLE IF NOT EXISTS migrations ( - id INT AUTO_INCREMENT PRIMARY KEY, - migration_name VARCHAR(255) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - )"); - - // Get all migration files - $migrationFiles = glob(__DIR__ . '/migrations/*.sql'); - - // Get already run migrations - $stmt = $pdo->query("SELECT migration_name FROM migrations"); - $runMigrations = $stmt->fetchAll(PDO::FETCH_COLUMN); - - echo 'Database Migrations'; - echo ''; - echo ''; - echo '
'; - echo '

Database Migrations

'; - - $migrationsApplied = false; - - foreach ($migrationFiles as $file) { - $migrationName = basename($file); - if (!in_array($migrationName, $runMigrations)) { - echo '
Applying migration: ' . htmlspecialchars($migrationName) . '...
'; - $sql = file_get_contents($file); - $statements = array_filter(array_map('trim', explode(';', $sql))); - $fileHasError = false; - - foreach ($statements as $statement) { - if (empty($statement)) continue; - try { - $pdo->exec($statement); - } catch (PDOException $e) { - // 1060 is the specific error code for "Duplicate column name" - if (strpos($e->getMessage(), '1060') !== false) { - // It's a duplicate column error, we can ignore it. - echo '
Ignoring existing column in ' . htmlspecialchars($migrationName) . '.
'; - } else { - // It's another error, so we should stop. - echo '
Error applying migration ' . htmlspecialchars($migrationName) . ': ' . htmlspecialchars($e->getMessage()) . '
'; - $fileHasError = true; - break; // break from statements loop - } - } - } - - if (!$fileHasError) { - // Record migration - $stmt = $pdo->prepare("INSERT INTO migrations (migration_name) VALUES (?)"); - $stmt->execute([$migrationName]); - - echo '
Successfully applied ' . htmlspecialchars($migrationName) . '
'; - $migrationsApplied = true; - } else { - // Stop on error - break; // break from files loop - } - } - } - - if (!$migrationsApplied) { - echo '
Database is already up to date.
'; - } - - echo 'Back to Home'; - echo '
'; - -} catch (PDOException $e) { - http_response_code(500); - die("Database connection failed: " . $e->getMessage()); -} \ No newline at end of file diff --git a/db/migrations/005_create_project_finance_monthly_table.sql b/db/migrations/005_create_project_finance_monthly_table.sql index e8d9d00..e0d9ee7 100644 --- a/db/migrations/005_create_project_finance_monthly_table.sql +++ b/db/migrations/005_create_project_finance_monthly_table.sql @@ -1,19 +1,11 @@ -DROP TABLE IF EXISTS `projectFinanceMonthly`; - CREATE TABLE IF NOT EXISTS `projectFinanceMonthly` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `projectId` INT NOT NULL, + `metricName` VARCHAR(255) NOT NULL, `month` DATE NOT NULL, - `opening_balance` DECIMAL(15, 2) DEFAULT 0, - `payment` DECIMAL(15, 2) DEFAULT 0, - `wip` DECIMAL(15, 2) DEFAULT 0, - `expenses` DECIMAL(15, 2) DEFAULT 0, - `cost` DECIMAL(15, 2) DEFAULT 0, - `nsr` DECIMAL(15, 2) DEFAULT 0, - `margin` DECIMAL(15, 5) DEFAULT 0, - `is_confirmed` TINYINT(1) NOT NULL DEFAULT 0, + `amount` DECIMAL(15, 2) NOT NULL, `createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (`projectId`) REFERENCES `projects`(`id`) ON DELETE CASCADE, - UNIQUE KEY `unique_project_month` (`projectId`, `month`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file + UNIQUE KEY `project_metric_month` (`projectId`, `metricName`, `month`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/db/migrations/007_add_revenue_fields_to_roster.sql b/db/migrations/007_add_revenue_fields_to_roster.sql index 0143998..8590309 100644 --- a/db/migrations/007_add_revenue_fields_to_roster.sql +++ b/db/migrations/007_add_revenue_fields_to_roster.sql @@ -1,2 +1,3 @@ -ALTER TABLE `roster` ADD COLUMN `grossRevenue` DECIMAL(10, 2) NOT NULL DEFAULT 0.00; -ALTER TABLE `roster` ADD COLUMN `discountedRevenue` DECIMAL(10, 2) NOT NULL DEFAULT 0.00; +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; diff --git a/db/migrations/009_create_project_finance_monthly_override_table.sql b/db/migrations/009_create_project_finance_monthly_override_table.sql deleted file mode 100644 index fdf98b6..0000000 --- a/db/migrations/009_create_project_finance_monthly_override_table.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS `project_finance_monthly_override` ( - `project_id` INT NOT NULL, - `nsr_override` DECIMAL(15, 2) DEFAULT NULL, - `cost_override` DECIMAL(15, 2) DEFAULT NULL, - `hours_override` DECIMAL(10, 2) DEFAULT NULL, - `createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - `updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`project_id`), - FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/db/migrations/010_alter_override_table.sql b/db/migrations/010_alter_override_table.sql deleted file mode 100644 index 5159dc3..0000000 --- a/db/migrations/010_alter_override_table.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Step 1: Drop the foreign key constraint -ALTER TABLE project_finance_monthly_override DROP FOREIGN KEY project_finance_monthly_override_ibfk_1; - --- Step 2: Drop the old primary key -ALTER TABLE project_finance_monthly_override DROP PRIMARY KEY; - --- Step 3: Add the new month column -ALTER TABLE project_finance_monthly_override ADD COLUMN month VARCHAR(7) NOT NULL; - --- Step 4: Add the new composite primary key -ALTER TABLE project_finance_monthly_override ADD PRIMARY KEY (project_id, month); - --- Step 5: Re-add the foreign key constraint with a more descriptive name -ALTER TABLE project_finance_monthly_override ADD CONSTRAINT fk_override_project_id FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/describe_table.php b/describe_table.php deleted file mode 100644 index 2fd72e0..0000000 --- a/describe_table.php +++ /dev/null @@ -1,12 +0,0 @@ -query("DESCRIBE project_finance_monthly_override"); - $columns = $stmt->fetchAll(PDO::FETCH_ASSOC); - print_r($columns); -} catch (Exception $e) { - echo "Error: " . $e->getMessage(); -} -?> \ No newline at end of file diff --git a/logs/php_errors.log b/logs/php_errors.log deleted file mode 100644 index 6fc7ef1..0000000 --- a/logs/php_errors.log +++ /dev/null @@ -1,20 +0,0 @@ -[26-Nov-2025 13:29:03 UTC] save_override.php: Script start -[26-Nov-2025 13:29:03 UTC] save_override.php: Invalid request method -[26-Nov-2025 13:29:08 UTC] save_override.php: Script start -[26-Nov-2025 13:29:08 UTC] save_override.php: Invalid request method -[26-Nov-2025 13:29:13 UTC] save_override.php: Script start -[26-Nov-2025 13:29:13 UTC] save_override.php: Invalid request method -[26-Nov-2025 13:29:19 UTC] save_override.php: Script start -[26-Nov-2025 13:29:19 UTC] save_override.php: Invalid request method -[26-Nov-2025 13:29:24 UTC] save_override.php: Script start -[26-Nov-2025 13:29:24 UTC] save_override.php: Invalid request method -[26-Nov-2025 13:29:29 UTC] save_override.php: Script start -[26-Nov-2025 13:29:29 UTC] save_override.php: Invalid request method -[26-Nov-2025 13:29:29 UTC] save_override.php: Script start -[26-Nov-2025 13:29:29 UTC] save_override.php: JSON data decoded -[26-Nov-2025 13:29:29 UTC] save_override.php: Database connection successful -[26-Nov-2025 13:29:29 UTC] save_override.php: Starting database transaction -[26-Nov-2025 13:29:29 UTC] save_override.php: SQL statement prepared -[26-Nov-2025 13:29:29 UTC] save_override.php: Processing month: 2025-11-01 -[26-Nov-2025 13:29:29 UTC] save_override.php: An exception occurred: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'month' at row 1 -[26-Nov-2025 13:29:29 UTC] save_override.php: Rolling back transaction diff --git a/logs/raw_input.log b/logs/raw_input.log deleted file mode 100644 index 00786fd..0000000 --- a/logs/raw_input.log +++ /dev/null @@ -1,24 +0,0 @@ -{"project_id":25,"overrides":[{"month":"2025-11-01","nsr":27855,"cost":1350},{"month":"2025-12-01","nsr":35545,"cost":2600},{"month":"2026-01-01","nsr":43355,"cost":3225}]}Executing with params: Array -( - [:project_id] => 25 - [:month] => 2025-11-01 - [:nsr_override] => 27855 - [:cost_override] => 1350 - [:hours_override] => -) -Executing with params: Array -( - [:project_id] => 25 - [:month] => 2025-12-01 - [:nsr_override] => 35545 - [:cost_override] => 2600 - [:hours_override] => -) -Executing with params: Array -( - [:project_id] => 25 - [:month] => 2026-01-01 - [:nsr_override] => 43355 - [:cost_override] => 3225 - [:hours_override] => -) diff --git a/project_details.php b/project_details.php index eb9463f..08fc482 100644 --- a/project_details.php +++ b/project_details.php @@ -10,7 +10,7 @@ function execute_sql_from_file($pdo, $filepath) { return true; } catch (PDOException $e) { // Ignore errors about tables/columns that already exist - if (strpos($e->getMessage(), 'already exists') === false && strpos($e->getMessage(), 'Duplicate column name') === false && strpos($e->getMessage(), 'Duplicate key name') === false) { + if (strpos($e->getMessage(), 'already exists') === false && strpos($e->getMessage(), 'Duplicate column name') === false) { error_log("SQL Execution Error in $filepath: " . $e->getMessage()); } return false; @@ -35,7 +35,7 @@ $project = null; $project_id = $_GET['id'] ?? null; $financial_data = []; $months = []; -$metrics = ["Opening Balance", "Billings", "WIP", "Expenses", "Cost", "NSR", "Margin", "Payment"]; +$metrics = ["Opening Balance", "Billings", "WIP", "Expenses", "Cost", "NSR", "Margin"]; if ($project_id) { $stmt = $pdo->prepare("SELECT * FROM projects WHERE id = :id"); @@ -103,15 +103,6 @@ if ($project_id) { $expenses_stmt->execute([':pid' => $project_id]); $monthly_expenses = $expenses_stmt->fetchAll(PDO::FETCH_KEY_PAIR); - // Fetch confirmed override data - $finance_override_stmt = $pdo->prepare("SELECT * FROM projectFinanceMonthly WHERE projectId = :pid ORDER BY month ASC"); - $finance_override_stmt->execute([':pid' => $project_id]); - $overrides_raw = $finance_override_stmt->fetchAll(PDO::FETCH_ASSOC); - $finance_overrides = []; - foreach ($overrides_raw as $row) { - $finance_overrides[$row['month']] = $row; - } - // 2. Calculate cumulative values month by month $cumulative_billing = 0; $cumulative_cost = 0; @@ -119,54 +110,36 @@ if ($project_id) { $previous_month_wip = 0; foreach ($months as $month) { - if (isset($finance_overrides[$month])) { - // This month has saved data. Use its values. - $financial_data['Opening Balance'][$month] = $finance_overrides[$month]['opening_balance']; - $financial_data['Billings'][$month] = $finance_overrides[$month]['payment']; - $financial_data['Payment'][$month] = $finance_overrides[$month]['payment']; - $financial_data['WIP'][$month] = $finance_overrides[$month]['wip']; - $financial_data['Expenses'][$month] = $finance_overrides[$month]['expenses']; - $financial_data['Cost'][$month] = $finance_overrides[$month]['cost']; - $financial_data['NSR'][$month] = $finance_overrides[$month]['nsr']; - $financial_data['Margin'][$month] = $finance_overrides[$month]['margin']; + // Normalize month keys from fetched data + $cost = $monthly_costs[$month] ?? 0; + $base_monthly_wip = $monthly_wip[$month] ?? 0; + $billing = $monthly_billing[$month] ?? 0; + $expenses = $monthly_expenses[$month] ?? 0; - // Update cumulative trackers for the next potential calculation - $cumulative_billing = $financial_data['Billings'][$month]; - $cumulative_cost = $financial_data['Cost'][$month]; - $cumulative_expenses= $financial_data['Expenses'][$month]; - $previous_month_wip = $financial_data['WIP'][$month]; - } else { - // This month has no saved data. Calculate as usual. - $cost = $monthly_costs[$month] ?? 0; - $base_monthly_wip = $monthly_wip[$month] ?? 0; - $billing = $monthly_billing[$month] ?? 0; - $expenses = $monthly_expenses[$month] ?? 0; + // Cumulative calculations + $cumulative_billing += $billing; + $cumulative_cost += $cost; + $cumulative_expenses += $expenses; - // Cumulative calculations - $cumulative_billing += $billing; - $cumulative_cost += $cost; - $cumulative_expenses += $expenses; + // WIP Calculation (new formula) + // current month WIP = previous month WIP + Month Expenses + base_monthly_wip - month Billing + $current_wip = $previous_month_wip + $expenses + $base_monthly_wip - $billing; - // WIP Calculation - $current_wip = $previous_month_wip + $expenses + $base_monthly_wip - $billing; + $financial_data['WIP'][$month] = $current_wip; + $financial_data['Billings'][$month] = $cumulative_billing; + $financial_data['Cost'][$month] = $cumulative_cost; + $financial_data['Opening Balance'][$month] = 0; // Placeholder + $financial_data['Expenses'][$month] = $cumulative_expenses; - $financial_data['WIP'][$month] = $current_wip; - $financial_data['Billings'][$month] = $cumulative_billing; - $financial_data['Cost'][$month] = $cumulative_cost; - $financial_data['Opening Balance'][$month] = 0; // Placeholder - $financial_data['Expenses'][$month] = $cumulative_expenses; - $financial_data['Payment'][$month] = 0; // Not overridden, so no payment input + // 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; - // 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; - $margin = ($nsr != 0) ? (($nsr - $financial_data['Cost'][$month]) / $nsr) : 0; - $financial_data['Margin'][$month] = $margin; - - // Update previous month's WIP for the next iteration - $previous_month_wip = $current_wip; - } + // Update previous month's WIP for the next iteration + $previous_month_wip = $current_wip; } } } @@ -259,45 +232,21 @@ if (!$project) {
- - +
- + - + -
Metric -
- Confirmed'; - } elseif ($is_eligible) { - echo ''; - } else { - echo ''; - } - ?> -
+ - - \ No newline at end of file diff --git a/save_override.php b/save_override.php deleted file mode 100644 index 678ee7f..0000000 --- a/save_override.php +++ /dev/null @@ -1,93 +0,0 @@ - false, 'message' => 'An error occurred.']; -$pdo = null; - -try { - $pdo = db(); - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - $data = json_decode($raw_input, true); - - if (empty($data['project_id']) || empty($data['overrides'])) { - throw new Exception("Project ID or overrides data not provided."); - } - - $project_id = $data['project_id']; - $overrides = $data['overrides']; - $rows_affected_total = 0; - - $pdo->beginTransaction(); - - $sql = " - INSERT INTO projectFinanceMonthly (projectId, month, opening_balance, payment, wip, expenses, cost, nsr, margin, is_confirmed) - VALUES (:project_id, :month, :opening_balance, :payment, :wip, :expenses, :cost, :nsr, :margin, :is_confirmed) - ON DUPLICATE KEY UPDATE - opening_balance = VALUES(opening_balance), - payment = VALUES(payment), - wip = VALUES(wip), - expenses = VALUES(expenses), - cost = VALUES(cost), - nsr = VALUES(nsr), - margin = VALUES(margin), - is_confirmed = VALUES(is_confirmed) - "; - $stmt = $pdo->prepare($sql); - - foreach ($overrides as $override) { - $date = new DateTime($override['month']); - $month = $date->format('Y-m-d'); - - $params = [ - ':project_id' => $project_id, - ':month' => $month, - ':opening_balance' => $override['opening_balance'] ?? null, - ':payment' => $override['payment'] ?? null, - ':wip' => $override['wip'] ?? null, - ':expenses' => $override['expenses'] ?? null, - ':cost' => $override['cost'] ?? null, - ':nsr' => $override['nsr'] ?? null, - ':margin' => $override['margin'] ?? null, - ':is_confirmed' => $override['is_confirmed'] ?? 0, - ]; - $stmt->execute($params); - $rows_affected_total += $stmt->rowCount(); - } - - if ($rows_affected_total === 0) { - // It's possible that the data is identical, so 0 affected rows is not strictly an error. - // We can consider it a "soft" success. - $pdo->rollBack(); // Rollback to not leave an open transaction - $response['success'] = true; // Report success to the frontend - $response['message'] = 'No changes were detected. Nothing was saved.'; - echo json_encode($response); - exit; - } - - $pdo->commit(); - - $response['success'] = true; - $response['message'] = 'Override data saved successfully. ' . $rows_affected_total . ' records were updated.'; - -} catch (PDOException $e) { - if ($pdo && $pdo->inTransaction()) { - $pdo->rollBack(); - } - error_log("save_override.php: PDOException caught: " . $e->getMessage()); - $response['message'] = "Database Error: " . $e->getMessage(); - http_response_code(500); -} catch (Exception $e) { - if ($pdo && $pdo->inTransaction()) { - $pdo->rollBack(); - } - error_log("save_override.php: Exception caught: " . $e->getMessage()); - $response['message'] = "General Error: " . $e->getMessage(); - http_response_code(500); -} - -echo json_encode($response); -?> \ No newline at end of file diff --git a/test_save.php b/test_save.php deleted file mode 100644 index 92024ea..0000000 --- a/test_save.php +++ /dev/null @@ -1,27 +0,0 @@ - 25, - 'overrides' => [ - ['month' => '2025-11-01', 'nsr' => 1000, 'cost' => 500], - ['month' => '2025-12-01', 'nsr' => 2000, 'cost' => 1000], - ] -]; - -$options = [ - 'http' => [ - 'header' => "Content-type: application/json\r\n", - 'method' => 'POST', - 'content' => json_encode($data), - ], -]; - -$context = stream_context_create($options); -$result = file_get_contents($url, false, $context); - -if ($result === FALSE) { - echo "Error fetching URL"; -} - -var_dump($result); -?> diff --git a/test_sql.php b/test_sql.php deleted file mode 100644 index 53670ad..0000000 --- a/test_sql.php +++ /dev/null @@ -1,12 +0,0 @@ -exec($sql); - echo "Insert successful."; -} catch (Exception $e) { - echo "Error: " . $e->getMessage(); -} -?> \ No newline at end of file