Skip to content

Commit 2328955

Browse files
committed
fix(project-finance): improve task financial calculations with accurate aggregation
- Enhanced SQL logic in ProjectFinanceController to sum only leaf tasks for parent tasks, ensuring accurate estimated_seconds and total_minutes calculations. - Updated FinanceTable component to utilize backend-provided aggregated values, simplifying man days calculations and improving overall financial accuracy in the UI.
1 parent 6ee2b34 commit 2328955

2 files changed

Lines changed: 36 additions & 13 deletions

File tree

worklenz-backend/src/controllers/project-finance-controller.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,35 @@ export default class ProjectfinanceController extends WorklenzControllerBase {
254254
-- For parent tasks, sum values from descendants only (exclude parent task itself)
255255
CASE
256256
WHEN tc.level = 0 AND tc.sub_tasks_count > 0 THEN (
257-
SELECT SUM(sub_tc.estimated_seconds)
257+
-- For parent tasks with children, sum only LEAF tasks (tasks without children)
258+
SELECT COALESCE(SUM(sub_tc.estimated_seconds), 0)
258259
FROM task_costs sub_tc
259-
WHERE sub_tc.root_id = tc.id AND sub_tc.id != tc.id
260+
WHERE sub_tc.root_id = tc.id
261+
AND sub_tc.id != tc.id
262+
AND NOT EXISTS (
263+
SELECT 1 FROM task_costs child_tc
264+
WHERE child_tc.parent_task_id = sub_tc.id
265+
AND child_tc.root_id = tc.id
266+
)
260267
)
261268
ELSE tc.estimated_seconds
262269
END as estimated_seconds,
270+
-- Add total_minutes aggregation for recursive man days calculation
271+
CASE
272+
WHEN tc.level = 0 AND tc.sub_tasks_count > 0 THEN (
273+
-- For parent tasks with children, sum only LEAF tasks (tasks without children)
274+
SELECT COALESCE(SUM(sub_tc.total_minutes), 0)
275+
FROM task_costs sub_tc
276+
WHERE sub_tc.root_id = tc.id
277+
AND sub_tc.id != tc.id
278+
AND NOT EXISTS (
279+
SELECT 1 FROM task_costs child_tc
280+
WHERE child_tc.parent_task_id = sub_tc.id
281+
AND child_tc.root_id = tc.id
282+
)
283+
)
284+
ELSE tc.total_minutes
285+
END as total_minutes,
263286
CASE
264287
WHEN tc.level = 0 AND tc.sub_tasks_count > 0 THEN (
265288
SELECT SUM(sub_tc.total_time_logged_seconds)

worklenz-frontend/src/pages/projects/projectView/finance/finance-tab/finance-table/finance-table.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -490,14 +490,15 @@ const FinanceTable = ({ table, loading, onTaskClick, columns }: FinanceTableProp
490490
</Typography.Text>
491491
);
492492
case FinanceTableColumnKeys.MAN_DAYS:
493-
// Calculate man days from total_minutes, fallback to estimated_seconds if total_minutes is 0
494-
const displayManDays =
493+
// Backend now provides correct recursive aggregation for parent tasks
494+
const taskManDays =
495495
task.total_minutes > 0
496496
? task.total_minutes / 60 / (hoursPerDay || 8)
497497
: task.estimated_seconds / 3600 / (hoursPerDay || 8);
498+
498499
return (
499500
<Typography.Text style={{ fontSize: Math.max(12, 14 - level * 0.5) }}>
500-
{formatManDays(displayManDays, 1, hoursPerDay)}
501+
{formatManDays(taskManDays, 1, hoursPerDay)}
501502
</Typography.Text>
502503
);
503504
case FinanceTableColumnKeys.TOTAL_TIME_LOGGED:
@@ -641,10 +642,9 @@ const FinanceTable = ({ table, loading, onTaskClick, columns }: FinanceTableProp
641642
return flattened;
642643
}, [tasks, selectedTask, editingFixedCostValue, hasEditPermission, themeMode, hoveredTaskId]);
643644

644-
// Calculate totals for the current table
645-
// Optimized calculation that avoids double counting in nested hierarchies
645+
// Calculate totals for the current table - backend provides correct aggregated values
646646
const totals = useMemo(() => {
647-
const calculateTaskTotalsRecursive = (taskList: IProjectFinanceTask[]): any => {
647+
const calculateTaskTotals = (taskList: IProjectFinanceTask[]): any => {
648648
let totals = {
649649
hours: 0,
650650
man_days: 0,
@@ -659,8 +659,8 @@ const FinanceTable = ({ table, loading, onTaskClick, columns }: FinanceTableProp
659659

660660
for (const task of taskList) {
661661
if (task.sub_tasks && task.sub_tasks.length > 0) {
662-
// Parent task with loaded subtasks - only use subtasks values (no parent's own values)
663-
const subtaskTotals = calculateTaskTotalsRecursive(task.sub_tasks);
662+
// Parent task with loaded subtasks - only count subtasks recursively
663+
const subtaskTotals = calculateTaskTotals(task.sub_tasks);
664664
totals.hours += subtaskTotals.hours;
665665
totals.man_days += subtaskTotals.man_days;
666666
totals.total_time_logged += subtaskTotals.total_time_logged;
@@ -671,11 +671,11 @@ const FinanceTable = ({ table, loading, onTaskClick, columns }: FinanceTableProp
671671
totals.total_actual += subtaskTotals.total_actual;
672672
totals.variance += subtaskTotals.variance;
673673
} else {
674-
// Leaf task or parent task without loaded subtasks - use its values directly
674+
// Leaf task or parent task without loaded subtasks - use backend aggregated values
675675
const leafTotalActual = task.actual_cost_from_logs || 0;
676676
const leafTotalBudget = (task.estimated_cost || 0) + (task.fixed_cost || 0);
677677
totals.hours += task.estimated_seconds || 0;
678-
// Calculate man days from total_minutes, fallback to estimated_seconds if total_minutes is 0
678+
// Use same calculation as individual task display - backend provides correct values
679679
const taskManDays =
680680
task.total_minutes > 0
681681
? task.total_minutes / 60 / (hoursPerDay || 8)
@@ -694,7 +694,7 @@ const FinanceTable = ({ table, loading, onTaskClick, columns }: FinanceTableProp
694694
return totals;
695695
};
696696

697-
return calculateTaskTotalsRecursive(tasks);
697+
return calculateTaskTotals(tasks);
698698
}, [tasks, hoursPerDay]);
699699

700700
// Format the totals for display

0 commit comments

Comments
 (0)