refactor(project-finance): streamline task cost calculations and prevent double counting
- Removed fixed cost from budget calculations, as actual costs are now aggregated from logs and backend data. - Updated recursive functions in the FinanceTable and project finance slice to ensure accurate totals without double counting. - Enhanced comments for clarity on the calculation logic for parent and leaf tasks, improving maintainability.
This commit is contained in:
@@ -23,9 +23,9 @@ const secondsToHours = (seconds: number) => seconds / 3600;
|
|||||||
const calculateTaskCosts = (task: IProjectFinanceTask) => {
|
const calculateTaskCosts = (task: IProjectFinanceTask) => {
|
||||||
const hours = secondsToHours(task.estimated_seconds || 0);
|
const hours = secondsToHours(task.estimated_seconds || 0);
|
||||||
const timeLoggedHours = secondsToHours(task.total_time_logged_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 totalActual = task.total_actual || 0;
|
||||||
const variance = totalActual - totalBudget;
|
const variance = totalActual - totalBudget;
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ const recalculateTaskHierarchy = (tasks: IProjectFinanceTask[]): IProjectFinance
|
|||||||
total_actual: totalActual,
|
total_actual: totalActual,
|
||||||
estimated_seconds: subtaskTotals.estimated_seconds,
|
estimated_seconds: subtaskTotals.estimated_seconds,
|
||||||
total_time_logged_seconds: subtaskTotals.total_time_logged_seconds,
|
total_time_logged_seconds: subtaskTotals.total_time_logged_seconds,
|
||||||
total_budget: totalEstimatedCost + totalFixedCost,
|
total_budget: totalEstimatedCost,
|
||||||
variance: totalActual - (totalEstimatedCost + totalFixedCost)
|
variance: totalActual - totalEstimatedCost
|
||||||
};
|
};
|
||||||
|
|
||||||
return updatedTask;
|
return updatedTask;
|
||||||
|
|||||||
@@ -67,29 +67,31 @@ const FinanceTableWrapper: React.FC<FinanceTableWrapperProps> = ({ activeTablesL
|
|||||||
// Recursive function to calculate totals from task hierarchy without double counting
|
// Recursive function to calculate totals from task hierarchy without double counting
|
||||||
const calculateTaskTotalsRecursively = (tasks: IProjectFinanceTask[]): any => {
|
const calculateTaskTotalsRecursively = (tasks: IProjectFinanceTask[]): any => {
|
||||||
return tasks.reduce((acc, task) => {
|
return tasks.reduce((acc, task) => {
|
||||||
// For parent tasks with subtasks, only count the aggregated values (no double counting)
|
// For parent tasks with subtasks, aggregate values from subtasks only
|
||||||
// For leaf tasks, count their individual values
|
// For leaf tasks, use their individual values
|
||||||
if (task.sub_tasks && task.sub_tasks.length > 0) {
|
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 {
|
return {
|
||||||
hours: acc.hours + (task.estimated_seconds || 0),
|
hours: acc.hours + subtaskTotals.hours,
|
||||||
cost: acc.cost + (task.actual_cost_from_logs || 0),
|
cost: acc.cost + subtaskTotals.cost,
|
||||||
fixedCost: acc.fixedCost + (task.fixed_cost || 0),
|
fixedCost: acc.fixedCost + subtaskTotals.fixedCost,
|
||||||
totalBudget: acc.totalBudget + (task.total_budget || 0),
|
totalBudget: acc.totalBudget + subtaskTotals.totalBudget,
|
||||||
totalActual: acc.totalActual + (task.total_actual || 0),
|
totalActual: acc.totalActual + subtaskTotals.totalActual,
|
||||||
variance: acc.variance + (task.variance || 0),
|
variance: acc.variance + subtaskTotals.variance,
|
||||||
total_time_logged: acc.total_time_logged + (task.total_time_logged_seconds || 0),
|
total_time_logged: acc.total_time_logged + subtaskTotals.total_time_logged,
|
||||||
estimated_cost: acc.estimated_cost + (task.estimated_cost || 0)
|
estimated_cost: acc.estimated_cost + subtaskTotals.estimated_cost
|
||||||
};
|
};
|
||||||
} else {
|
} 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 {
|
return {
|
||||||
hours: acc.hours + (task.estimated_seconds || 0),
|
hours: acc.hours + (task.estimated_seconds || 0),
|
||||||
cost: acc.cost + (task.actual_cost_from_logs || 0),
|
cost: acc.cost + (task.actual_cost_from_logs || 0),
|
||||||
fixedCost: acc.fixedCost + (task.fixed_cost || 0),
|
fixedCost: acc.fixedCost + (task.fixed_cost || 0),
|
||||||
totalBudget: acc.totalBudget + (task.total_budget || 0),
|
totalBudget: acc.totalBudget + (task.estimated_cost || 0),
|
||||||
totalActual: acc.totalActual + (task.total_actual || 0),
|
totalActual: acc.totalActual + leafTotalActual,
|
||||||
variance: acc.variance + (task.variance || 0),
|
variance: acc.variance + (leafTotalActual - (task.estimated_cost || 0)),
|
||||||
total_time_logged: acc.total_time_logged + (task.total_time_logged_seconds || 0),
|
total_time_logged: acc.total_time_logged + (task.total_time_logged_seconds || 0),
|
||||||
estimated_cost: acc.estimated_cost + (task.estimated_cost || 0)
|
estimated_cost: acc.estimated_cost + (task.estimated_cost || 0)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -539,8 +539,7 @@ const FinanceTable = ({
|
|||||||
|
|
||||||
for (const task of taskList) {
|
for (const task of taskList) {
|
||||||
if (task.sub_tasks && task.sub_tasks.length > 0) {
|
if (task.sub_tasks && task.sub_tasks.length > 0) {
|
||||||
// Parent task with loaded subtasks - only count the subtasks recursively
|
// Parent task with loaded subtasks - only use subtasks values (no parent's own values)
|
||||||
// This completely avoids the parent's aggregated values to prevent double counting
|
|
||||||
const subtaskTotals = calculateTaskTotalsRecursive(task.sub_tasks);
|
const subtaskTotals = calculateTaskTotalsRecursive(task.sub_tasks);
|
||||||
totals.hours += subtaskTotals.hours;
|
totals.hours += subtaskTotals.hours;
|
||||||
totals.total_time_logged += subtaskTotals.total_time_logged;
|
totals.total_time_logged += subtaskTotals.total_time_logged;
|
||||||
@@ -552,14 +551,15 @@ const FinanceTable = ({
|
|||||||
totals.variance += subtaskTotals.variance;
|
totals.variance += subtaskTotals.variance;
|
||||||
} else {
|
} else {
|
||||||
// Leaf task or parent task without loaded subtasks - use its values directly
|
// 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.hours += task.estimated_seconds || 0;
|
||||||
totals.total_time_logged += task.total_time_logged_seconds || 0;
|
totals.total_time_logged += task.total_time_logged_seconds || 0;
|
||||||
totals.estimated_cost += task.estimated_cost || 0;
|
totals.estimated_cost += task.estimated_cost || 0;
|
||||||
totals.actual_cost_from_logs += task.actual_cost_from_logs || 0;
|
totals.actual_cost_from_logs += task.actual_cost_from_logs || 0;
|
||||||
totals.fixed_cost += task.fixed_cost || 0;
|
totals.fixed_cost += task.fixed_cost || 0;
|
||||||
totals.total_budget += task.total_budget || 0;
|
totals.total_budget += task.estimated_cost || 0;
|
||||||
totals.total_actual += task.total_actual || 0;
|
totals.total_actual += leafTotalActual;
|
||||||
totals.variance += task.variance || 0;
|
totals.variance += leafTotalActual - (task.estimated_cost || 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user