diff --git a/worklenz-frontend/src/features/projects/finance/project-finance.slice.ts b/worklenz-frontend/src/features/projects/finance/project-finance.slice.ts index 564379df..c76b5550 100644 --- a/worklenz-frontend/src/features/projects/finance/project-finance.slice.ts +++ b/worklenz-frontend/src/features/projects/finance/project-finance.slice.ts @@ -23,9 +23,9 @@ const secondsToHours = (seconds: number) => seconds / 3600; const calculateTaskCosts = (task: IProjectFinanceTask) => { const hours = secondsToHours(task.estimated_seconds || 0); const timeLoggedHours = secondsToHours(task.total_time_logged_seconds || 0); - const fixedCost = task.fixed_cost || 0; - const totalBudget = (task.estimated_cost || 0) + fixedCost; + const totalBudget = task.estimated_cost || 0; + // task.total_actual already includes actual_cost_from_logs + fixed_cost from backend const totalActual = task.total_actual || 0; const variance = totalActual - totalBudget; @@ -114,8 +114,8 @@ const recalculateTaskHierarchy = (tasks: IProjectFinanceTask[]): IProjectFinance total_actual: totalActual, estimated_seconds: subtaskTotals.estimated_seconds, total_time_logged_seconds: subtaskTotals.total_time_logged_seconds, - total_budget: totalEstimatedCost + totalFixedCost, - variance: totalActual - (totalEstimatedCost + totalFixedCost) + total_budget: totalEstimatedCost, + variance: totalActual - totalEstimatedCost }; return updatedTask; diff --git a/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table-wrapper.tsx b/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table-wrapper.tsx index 0fc13c13..251e6ff5 100644 --- a/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table-wrapper.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table-wrapper.tsx @@ -67,29 +67,31 @@ const FinanceTableWrapper: React.FC = ({ activeTablesL // Recursive function to calculate totals from task hierarchy without double counting const calculateTaskTotalsRecursively = (tasks: IProjectFinanceTask[]): any => { return tasks.reduce((acc, task) => { - // For parent tasks with subtasks, only count the aggregated values (no double counting) - // For leaf tasks, count their individual values + // For parent tasks with subtasks, aggregate values from subtasks only + // For leaf tasks, use their individual values if (task.sub_tasks && task.sub_tasks.length > 0) { - // Parent task - use its aggregated values which already include subtask totals + // Parent task - only use aggregated values from subtasks (no parent's own values) + const subtaskTotals = calculateTaskTotalsRecursively(task.sub_tasks); return { - hours: acc.hours + (task.estimated_seconds || 0), - cost: acc.cost + (task.actual_cost_from_logs || 0), - fixedCost: acc.fixedCost + (task.fixed_cost || 0), - totalBudget: acc.totalBudget + (task.total_budget || 0), - totalActual: acc.totalActual + (task.total_actual || 0), - variance: acc.variance + (task.variance || 0), - total_time_logged: acc.total_time_logged + (task.total_time_logged_seconds || 0), - estimated_cost: acc.estimated_cost + (task.estimated_cost || 0) + hours: acc.hours + subtaskTotals.hours, + cost: acc.cost + subtaskTotals.cost, + fixedCost: acc.fixedCost + subtaskTotals.fixedCost, + totalBudget: acc.totalBudget + subtaskTotals.totalBudget, + totalActual: acc.totalActual + subtaskTotals.totalActual, + variance: acc.variance + subtaskTotals.variance, + total_time_logged: acc.total_time_logged + subtaskTotals.total_time_logged, + estimated_cost: acc.estimated_cost + subtaskTotals.estimated_cost }; } else { - // Leaf task - use its individual values + // Leaf task - calculate values from individual task properties + const leafTotalActual = (task.actual_cost_from_logs || 0) + (task.fixed_cost || 0); return { hours: acc.hours + (task.estimated_seconds || 0), cost: acc.cost + (task.actual_cost_from_logs || 0), fixedCost: acc.fixedCost + (task.fixed_cost || 0), - totalBudget: acc.totalBudget + (task.total_budget || 0), - totalActual: acc.totalActual + (task.total_actual || 0), - variance: acc.variance + (task.variance || 0), + totalBudget: acc.totalBudget + (task.estimated_cost || 0), + totalActual: acc.totalActual + leafTotalActual, + variance: acc.variance + (leafTotalActual - (task.estimated_cost || 0)), total_time_logged: acc.total_time_logged + (task.total_time_logged_seconds || 0), estimated_cost: acc.estimated_cost + (task.estimated_cost || 0) }; diff --git a/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx b/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx index 46ecd9b1..23a064f2 100644 --- a/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx @@ -539,8 +539,7 @@ const FinanceTable = ({ for (const task of taskList) { if (task.sub_tasks && task.sub_tasks.length > 0) { - // Parent task with loaded subtasks - only count the subtasks recursively - // This completely avoids the parent's aggregated values to prevent double counting + // Parent task with loaded subtasks - only use subtasks values (no parent's own values) const subtaskTotals = calculateTaskTotalsRecursive(task.sub_tasks); totals.hours += subtaskTotals.hours; totals.total_time_logged += subtaskTotals.total_time_logged; @@ -552,14 +551,15 @@ const FinanceTable = ({ totals.variance += subtaskTotals.variance; } else { // Leaf task or parent task without loaded subtasks - use its values directly + const leafTotalActual = (task.actual_cost_from_logs || 0) + (task.fixed_cost || 0); totals.hours += task.estimated_seconds || 0; totals.total_time_logged += task.total_time_logged_seconds || 0; totals.estimated_cost += task.estimated_cost || 0; totals.actual_cost_from_logs += task.actual_cost_from_logs || 0; totals.fixed_cost += task.fixed_cost || 0; - totals.total_budget += task.total_budget || 0; - totals.total_actual += task.total_actual || 0; - totals.variance += task.variance || 0; + totals.total_budget += task.estimated_cost || 0; + totals.total_actual += leafTotalActual; + totals.variance += leafTotalActual - (task.estimated_cost || 0); } }