Merge pull request #109 from chamikaJ/fix/custom-progress-methods
Enhance task progress calculation and update logic
This commit is contained in:
@@ -22,12 +22,19 @@ DECLARE
|
|||||||
_use_manual_progress BOOLEAN = FALSE;
|
_use_manual_progress BOOLEAN = FALSE;
|
||||||
_use_weighted_progress BOOLEAN = FALSE;
|
_use_weighted_progress BOOLEAN = FALSE;
|
||||||
_use_time_progress BOOLEAN = FALSE;
|
_use_time_progress BOOLEAN = FALSE;
|
||||||
|
_task_complete BOOLEAN = FALSE;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Check if manual progress is set for this task
|
-- Check if manual progress is set for this task
|
||||||
SELECT manual_progress, progress_value, project_id
|
SELECT manual_progress, progress_value, project_id,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = tasks.id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
) AS is_complete
|
||||||
FROM tasks
|
FROM tasks
|
||||||
WHERE id = _task_id
|
WHERE id = _task_id
|
||||||
INTO _is_manual, _manual_value, _project_id;
|
INTO _is_manual, _manual_value, _project_id, _task_complete;
|
||||||
|
|
||||||
-- Check if the project uses manual progress
|
-- Check if the project uses manual progress
|
||||||
IF _project_id IS NOT NULL THEN
|
IF _project_id IS NOT NULL THEN
|
||||||
@@ -45,8 +52,21 @@ BEGIN
|
|||||||
WHERE parent_task_id = _task_id AND archived IS FALSE
|
WHERE parent_task_id = _task_id AND archived IS FALSE
|
||||||
INTO _sub_tasks_count;
|
INTO _sub_tasks_count;
|
||||||
|
|
||||||
-- Always respect manual progress value if set
|
-- If task is complete, always return 100%
|
||||||
IF _is_manual IS TRUE AND _manual_value IS NOT NULL THEN
|
IF _task_complete IS TRUE THEN
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'ratio', 100,
|
||||||
|
'total_completed', 1,
|
||||||
|
'total_tasks', 1,
|
||||||
|
'is_manual', FALSE
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Use manual progress value in two cases:
|
||||||
|
-- 1. When task has manual_progress = TRUE and progress_value is set
|
||||||
|
-- 2. When project has use_manual_progress = TRUE and progress_value is set
|
||||||
|
IF (_is_manual IS TRUE AND _manual_value IS NOT NULL) OR
|
||||||
|
(_use_manual_progress IS TRUE AND _manual_value IS NOT NULL) THEN
|
||||||
RETURN JSON_BUILD_OBJECT(
|
RETURN JSON_BUILD_OBJECT(
|
||||||
'ratio', _manual_value,
|
'ratio', _manual_value,
|
||||||
'total_completed', 0,
|
'total_completed', 0,
|
||||||
@@ -64,23 +84,13 @@ BEGIN
|
|||||||
-- Otherwise, use the progress value if set manually, or 0
|
-- Otherwise, use the progress value if set manually, or 0
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
CASE
|
||||||
WHEN EXISTS(
|
WHEN _task_complete IS TRUE THEN 100
|
||||||
SELECT 1
|
|
||||||
FROM tasks_with_status_view
|
|
||||||
WHERE tasks_with_status_view.task_id = _task_id
|
|
||||||
AND is_done IS TRUE
|
|
||||||
) THEN 100
|
|
||||||
ELSE COALESCE(_manual_value, 0)
|
ELSE COALESCE(_manual_value, 0)
|
||||||
END
|
END
|
||||||
INTO _ratio;
|
INTO _ratio;
|
||||||
ELSE
|
ELSE
|
||||||
-- Traditional calculation for non-time-based tasks
|
-- Traditional calculation for non-time-based tasks
|
||||||
SELECT (CASE
|
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
||||||
WHEN EXISTS(SELECT 1
|
|
||||||
FROM tasks_with_status_view
|
|
||||||
WHERE tasks_with_status_view.task_id = _task_id
|
|
||||||
AND is_done IS TRUE) THEN 1
|
|
||||||
ELSE 0 END)
|
|
||||||
INTO _parent_task_done;
|
INTO _parent_task_done;
|
||||||
|
|
||||||
_ratio = _parent_task_done * 100;
|
_ratio = _parent_task_done * 100;
|
||||||
@@ -90,99 +100,111 @@ BEGIN
|
|||||||
IF _use_manual_progress IS TRUE THEN
|
IF _use_manual_progress IS TRUE THEN
|
||||||
WITH subtask_progress AS (
|
WITH subtask_progress AS (
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
t.id,
|
||||||
-- If subtask has manual progress, use that value
|
t.manual_progress,
|
||||||
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
|
t.progress_value,
|
||||||
progress_value
|
EXISTS(
|
||||||
-- Otherwise use completion status (0 or 100)
|
SELECT 1
|
||||||
ELSE
|
FROM tasks_with_status_view
|
||||||
CASE
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
WHEN EXISTS(
|
AND is_done IS TRUE
|
||||||
SELECT 1
|
) AS is_complete
|
||||||
FROM tasks_with_status_view
|
|
||||||
WHERE tasks_with_status_view.task_id = t.id
|
|
||||||
AND is_done IS TRUE
|
|
||||||
) THEN 100
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
END AS progress_value
|
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
WHERE t.parent_task_id = _task_id
|
WHERE t.parent_task_id = _task_id
|
||||||
AND t.archived IS FALSE
|
AND t.archived IS FALSE
|
||||||
|
),
|
||||||
|
subtask_with_values AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- For completed tasks, always use 100%
|
||||||
|
WHEN is_complete IS TRUE THEN 100
|
||||||
|
-- For tasks with progress value set, use it regardless of manual_progress flag
|
||||||
|
WHEN progress_value IS NOT NULL THEN progress_value
|
||||||
|
-- Default to 0 for incomplete tasks with no progress value
|
||||||
|
ELSE 0
|
||||||
|
END AS progress_value
|
||||||
|
FROM subtask_progress
|
||||||
)
|
)
|
||||||
SELECT COALESCE(AVG(progress_value), 0)
|
SELECT COALESCE(AVG(progress_value), 0)
|
||||||
FROM subtask_progress
|
FROM subtask_with_values
|
||||||
INTO _ratio;
|
INTO _ratio;
|
||||||
-- If project uses weighted progress, calculate based on subtask weights
|
-- If project uses weighted progress, calculate based on subtask weights
|
||||||
ELSIF _use_weighted_progress IS TRUE THEN
|
ELSIF _use_weighted_progress IS TRUE THEN
|
||||||
WITH subtask_progress AS (
|
WITH subtask_progress AS (
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
t.id,
|
||||||
-- If subtask has manual progress, use that value
|
t.manual_progress,
|
||||||
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
|
t.progress_value,
|
||||||
progress_value
|
EXISTS(
|
||||||
-- Otherwise use completion status (0 or 100)
|
SELECT 1
|
||||||
ELSE
|
FROM tasks_with_status_view
|
||||||
CASE
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
WHEN EXISTS(
|
AND is_done IS TRUE
|
||||||
SELECT 1
|
) AS is_complete,
|
||||||
FROM tasks_with_status_view
|
COALESCE(t.weight, 100) AS weight
|
||||||
WHERE tasks_with_status_view.task_id = t.id
|
|
||||||
AND is_done IS TRUE
|
|
||||||
) THEN 100
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
END AS progress_value,
|
|
||||||
COALESCE(weight, 100) AS weight
|
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
WHERE t.parent_task_id = _task_id
|
WHERE t.parent_task_id = _task_id
|
||||||
AND t.archived IS FALSE
|
AND t.archived IS FALSE
|
||||||
|
),
|
||||||
|
subtask_with_values AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- For completed tasks, always use 100%
|
||||||
|
WHEN is_complete IS TRUE THEN 100
|
||||||
|
-- For tasks with progress value set, use it regardless of manual_progress flag
|
||||||
|
WHEN progress_value IS NOT NULL THEN progress_value
|
||||||
|
-- Default to 0 for incomplete tasks with no progress value
|
||||||
|
ELSE 0
|
||||||
|
END AS progress_value,
|
||||||
|
weight
|
||||||
|
FROM subtask_progress
|
||||||
)
|
)
|
||||||
SELECT COALESCE(
|
SELECT COALESCE(
|
||||||
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
|
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
FROM subtask_progress
|
FROM subtask_with_values
|
||||||
INTO _ratio;
|
INTO _ratio;
|
||||||
-- If project uses time-based progress, calculate based on estimated time
|
-- If project uses time-based progress, calculate based on estimated time
|
||||||
ELSIF _use_time_progress IS TRUE THEN
|
ELSIF _use_time_progress IS TRUE THEN
|
||||||
WITH subtask_progress AS (
|
WITH subtask_progress AS (
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
t.id,
|
||||||
-- If subtask has manual progress, use that value
|
t.manual_progress,
|
||||||
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
|
t.progress_value,
|
||||||
progress_value
|
EXISTS(
|
||||||
-- Otherwise use completion status (0 or 100)
|
SELECT 1
|
||||||
ELSE
|
FROM tasks_with_status_view
|
||||||
CASE
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
WHEN EXISTS(
|
AND is_done IS TRUE
|
||||||
SELECT 1
|
) AS is_complete,
|
||||||
FROM tasks_with_status_view
|
COALESCE(t.total_minutes, 0) AS estimated_minutes
|
||||||
WHERE tasks_with_status_view.task_id = t.id
|
|
||||||
AND is_done IS TRUE
|
|
||||||
) THEN 100
|
|
||||||
ELSE 0
|
|
||||||
END
|
|
||||||
END AS progress_value,
|
|
||||||
COALESCE(total_minutes, 0) AS estimated_minutes
|
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
WHERE t.parent_task_id = _task_id
|
WHERE t.parent_task_id = _task_id
|
||||||
AND t.archived IS FALSE
|
AND t.archived IS FALSE
|
||||||
|
),
|
||||||
|
subtask_with_values AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- For completed tasks, always use 100%
|
||||||
|
WHEN is_complete IS TRUE THEN 100
|
||||||
|
-- For tasks with progress value set, use it regardless of manual_progress flag
|
||||||
|
WHEN progress_value IS NOT NULL THEN progress_value
|
||||||
|
-- Default to 0 for incomplete tasks with no progress value
|
||||||
|
ELSE 0
|
||||||
|
END AS progress_value,
|
||||||
|
estimated_minutes
|
||||||
|
FROM subtask_progress
|
||||||
)
|
)
|
||||||
SELECT COALESCE(
|
SELECT COALESCE(
|
||||||
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
|
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
FROM subtask_progress
|
FROM subtask_with_values
|
||||||
INTO _ratio;
|
INTO _ratio;
|
||||||
ELSE
|
ELSE
|
||||||
-- Traditional calculation based on completion status
|
-- Traditional calculation based on completion status
|
||||||
SELECT (CASE
|
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
||||||
WHEN EXISTS(SELECT 1
|
|
||||||
FROM tasks_with_status_view
|
|
||||||
WHERE tasks_with_status_view.task_id = _task_id
|
|
||||||
AND is_done IS TRUE) THEN 1
|
|
||||||
ELSE 0 END)
|
|
||||||
INTO _parent_task_done;
|
INTO _parent_task_done;
|
||||||
|
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
-- Migration: Fix multilevel subtask progress calculation for weighted and manual progress
|
||||||
|
-- Date: 2025-05-06
|
||||||
|
-- Version: 1.0.0
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Update the trigger function to recursively recalculate parent task progress up the entire hierarchy
|
||||||
|
CREATE OR REPLACE FUNCTION update_parent_task_progress() RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
_parent_task_id UUID;
|
||||||
|
_project_id UUID;
|
||||||
|
_ratio FLOAT;
|
||||||
|
BEGIN
|
||||||
|
-- Check if this is a subtask
|
||||||
|
IF NEW.parent_task_id IS NOT NULL THEN
|
||||||
|
_parent_task_id := NEW.parent_task_id;
|
||||||
|
|
||||||
|
-- Force any parent task with subtasks to NOT use manual progress
|
||||||
|
UPDATE tasks
|
||||||
|
SET manual_progress = FALSE
|
||||||
|
WHERE id = _parent_task_id;
|
||||||
|
|
||||||
|
-- Calculate and update the parent's progress value
|
||||||
|
SELECT (get_task_complete_ratio(_parent_task_id)->>'ratio')::FLOAT INTO _ratio;
|
||||||
|
|
||||||
|
-- Update the parent's progress value
|
||||||
|
UPDATE tasks
|
||||||
|
SET progress_value = _ratio
|
||||||
|
WHERE id = _parent_task_id;
|
||||||
|
|
||||||
|
-- Recursively propagate changes up the hierarchy by using a recursive CTE
|
||||||
|
WITH RECURSIVE task_hierarchy AS (
|
||||||
|
-- Base case: Start with the parent task
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
parent_task_id
|
||||||
|
FROM tasks
|
||||||
|
WHERE id = _parent_task_id
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive case: Go up to each ancestor
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.parent_task_id
|
||||||
|
FROM tasks t
|
||||||
|
JOIN task_hierarchy th ON t.id = th.parent_task_id
|
||||||
|
WHERE t.id IS NOT NULL
|
||||||
|
)
|
||||||
|
-- For each ancestor, recalculate its progress
|
||||||
|
UPDATE tasks
|
||||||
|
SET
|
||||||
|
manual_progress = FALSE,
|
||||||
|
progress_value = (SELECT (get_task_complete_ratio(task_hierarchy.id)->>'ratio')::FLOAT)
|
||||||
|
FROM task_hierarchy
|
||||||
|
WHERE tasks.id = task_hierarchy.id
|
||||||
|
AND task_hierarchy.parent_task_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Log the recalculation for debugging
|
||||||
|
RAISE NOTICE 'Updated progress for task % to %', _parent_task_id, _ratio;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- If this task has progress value of 100 and doesn't have subtasks, we might want to prompt the user
|
||||||
|
-- to mark it as done. We'll annotate this in a way that the socket handler can detect.
|
||||||
|
IF NEW.progress_value = 100 OR NEW.weight = 100 OR NEW.total_minutes > 0 THEN
|
||||||
|
-- Check if task has status in "done" category
|
||||||
|
SELECT project_id FROM tasks WHERE id = NEW.id INTO _project_id;
|
||||||
|
|
||||||
|
-- Get the progress ratio for this task
|
||||||
|
SELECT (get_task_complete_ratio(NEW.id)->>'ratio')::FLOAT INTO _ratio;
|
||||||
|
|
||||||
|
IF _ratio >= 100 THEN
|
||||||
|
-- Log that this task is at 100% progress
|
||||||
|
RAISE NOTICE 'Task % progress is at 100%%, may need status update', NEW.id;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Update existing trigger or create a new one to handle more changes
|
||||||
|
DROP TRIGGER IF EXISTS update_parent_task_progress_trigger ON tasks;
|
||||||
|
CREATE TRIGGER update_parent_task_progress_trigger
|
||||||
|
AFTER UPDATE OF progress_value, weight, total_minutes, parent_task_id, manual_progress ON tasks
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_parent_task_progress();
|
||||||
|
|
||||||
|
-- Also add a trigger for when a new task is inserted
|
||||||
|
DROP TRIGGER IF EXISTS update_parent_task_progress_on_insert_trigger ON tasks;
|
||||||
|
CREATE TRIGGER update_parent_task_progress_on_insert_trigger
|
||||||
|
AFTER INSERT ON tasks
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.parent_task_id IS NOT NULL)
|
||||||
|
EXECUTE FUNCTION update_parent_task_progress();
|
||||||
|
|
||||||
|
-- Add a comment to explain the fix
|
||||||
|
COMMENT ON FUNCTION update_parent_task_progress() IS
|
||||||
|
'This function recursively updates progress values for all ancestors when a task''s progress changes.
|
||||||
|
The previous version only updated the immediate parent, which led to incorrect progress values for
|
||||||
|
higher-level parent tasks when using weighted or manual progress calculations with multi-level subtasks.';
|
||||||
|
|
||||||
|
-- Add a function to immediately recalculate all task progress values in the correct order
|
||||||
|
-- This will fix existing data where parent tasks don't have proper progress values
|
||||||
|
CREATE OR REPLACE FUNCTION recalculate_all_task_progress() RETURNS void AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
-- First, reset manual_progress flag for all tasks that have subtasks
|
||||||
|
UPDATE tasks AS t
|
||||||
|
SET manual_progress = FALSE
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks
|
||||||
|
WHERE parent_task_id = t.id
|
||||||
|
AND archived IS FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Start recalculation from leaf tasks (no subtasks) and propagate upward
|
||||||
|
-- This ensures calculations are done in the right order
|
||||||
|
WITH RECURSIVE task_hierarchy AS (
|
||||||
|
-- Base case: Start with all leaf tasks (no subtasks)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
parent_task_id,
|
||||||
|
0 AS level
|
||||||
|
FROM tasks
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM tasks AS sub
|
||||||
|
WHERE sub.parent_task_id = tasks.id
|
||||||
|
AND sub.archived IS FALSE
|
||||||
|
)
|
||||||
|
AND archived IS FALSE
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive case: Move up to parent tasks, but only after processing all their children
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.parent_task_id,
|
||||||
|
th.level + 1
|
||||||
|
FROM tasks t
|
||||||
|
JOIN task_hierarchy th ON t.id = th.parent_task_id
|
||||||
|
WHERE t.archived IS FALSE
|
||||||
|
)
|
||||||
|
-- Sort by level to ensure we calculate in the right order (leaves first, then parents)
|
||||||
|
-- This ensures we're using already updated progress values
|
||||||
|
UPDATE tasks
|
||||||
|
SET progress_value = (SELECT (get_task_complete_ratio(tasks.id)->>'ratio')::FLOAT)
|
||||||
|
FROM (
|
||||||
|
SELECT id, level
|
||||||
|
FROM task_hierarchy
|
||||||
|
ORDER BY level
|
||||||
|
) AS ordered_tasks
|
||||||
|
WHERE tasks.id = ordered_tasks.id
|
||||||
|
AND (manual_progress IS FALSE OR manual_progress IS NULL);
|
||||||
|
|
||||||
|
-- Log the completion of the recalculation
|
||||||
|
RAISE NOTICE 'Finished recalculating all task progress values';
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Execute the function to fix existing data
|
||||||
|
SELECT recalculate_all_task_progress();
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
-- CONSOLIDATED MIGRATION FILE
|
||||||
|
-- Contains all progress-related migrations from April-May 2025
|
||||||
|
-- Generated on: (current date)
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Migration: Add manual task progress
|
||||||
|
-- Date: 2025-04-22
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- File: 20250422132400-manual-task-progress.sql
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Add manual progress fields to tasks table
|
||||||
|
ALTER TABLE tasks
|
||||||
|
ADD COLUMN IF NOT EXISTS manual_progress BOOLEAN DEFAULT FALSE,
|
||||||
|
ADD COLUMN IF NOT EXISTS progress_value INTEGER DEFAULT NULL,
|
||||||
|
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
-- Update function to consider manual progress
|
||||||
|
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
_parent_task_done FLOAT = 0;
|
||||||
|
_sub_tasks_done FLOAT = 0;
|
||||||
|
_sub_tasks_count FLOAT = 0;
|
||||||
|
_total_completed FLOAT = 0;
|
||||||
|
_total_tasks FLOAT = 0;
|
||||||
|
_ratio FLOAT = 0;
|
||||||
|
_is_manual BOOLEAN = FALSE;
|
||||||
|
_manual_value INTEGER = NULL;
|
||||||
|
BEGIN
|
||||||
|
-- Check if manual progress is set
|
||||||
|
SELECT manual_progress, progress_value
|
||||||
|
FROM tasks
|
||||||
|
WHERE id = _task_id
|
||||||
|
INTO _is_manual, _manual_value;
|
||||||
|
|
||||||
|
-- If manual progress is enabled and has a value, use it directly
|
||||||
|
IF _is_manual IS TRUE AND _manual_value IS NOT NULL THEN
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'ratio', _manual_value,
|
||||||
|
'total_completed', 0,
|
||||||
|
'total_tasks', 0,
|
||||||
|
'is_manual', TRUE
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Otherwise calculate automatically as before
|
||||||
|
SELECT (CASE
|
||||||
|
WHEN EXISTS(SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = _task_id
|
||||||
|
AND is_done IS TRUE) THEN 1
|
||||||
|
ELSE 0 END)
|
||||||
|
INTO _parent_task_done;
|
||||||
|
SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id AND archived IS FALSE INTO _sub_tasks_count;
|
||||||
|
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE parent_task_id = _task_id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
INTO _sub_tasks_done;
|
||||||
|
|
||||||
|
_total_completed = _parent_task_done + _sub_tasks_done;
|
||||||
|
_total_tasks = _sub_tasks_count; -- +1 for the parent task
|
||||||
|
|
||||||
|
IF _total_tasks > 0 THEN
|
||||||
|
_ratio = (_total_completed / _total_tasks) * 100;
|
||||||
|
ELSE
|
||||||
|
_ratio = _parent_task_done * 100;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'ratio', _ratio,
|
||||||
|
'total_completed', _total_completed,
|
||||||
|
'total_tasks', _total_tasks,
|
||||||
|
'is_manual', FALSE
|
||||||
|
);
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Migration: Subtask manual progress
|
||||||
|
-- Date: 2025-04-23
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- File: 20250423000000-subtask-manual-progress.sql
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Note: Contents extracted from the file description (actual file not available)
|
||||||
|
-- This migration likely extends the manual progress feature to support subtasks
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Migration: Add progress and weight activity types
|
||||||
|
-- Date: 2025-04-24
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- File: 20250424000000-add-progress-and-weight-activity-types.sql
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Note: Contents extracted from the file description (actual file not available)
|
||||||
|
-- This migration likely adds new activity types for tracking progress and weight changes
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Migration: Update time-based progress mode to work for all tasks
|
||||||
|
-- Date: 2025-04-25
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- File: 20250425000000-update-time-based-progress.sql
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Update function to use time-based progress for all tasks
|
||||||
|
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
_parent_task_done FLOAT = 0;
|
||||||
|
_sub_tasks_done FLOAT = 0;
|
||||||
|
_sub_tasks_count FLOAT = 0;
|
||||||
|
_total_completed FLOAT = 0;
|
||||||
|
_total_tasks FLOAT = 0;
|
||||||
|
_ratio FLOAT = 0;
|
||||||
|
_is_manual BOOLEAN = FALSE;
|
||||||
|
_manual_value INTEGER = NULL;
|
||||||
|
_project_id UUID;
|
||||||
|
_use_manual_progress BOOLEAN = FALSE;
|
||||||
|
_use_weighted_progress BOOLEAN = FALSE;
|
||||||
|
_use_time_progress BOOLEAN = FALSE;
|
||||||
|
_task_complete BOOLEAN = FALSE;
|
||||||
|
BEGIN
|
||||||
|
-- Check if manual progress is set for this task
|
||||||
|
SELECT manual_progress, progress_value, project_id,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = tasks.id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
) AS is_complete
|
||||||
|
FROM tasks
|
||||||
|
WHERE id = _task_id
|
||||||
|
INTO _is_manual, _manual_value, _project_id, _task_complete;
|
||||||
|
|
||||||
|
-- Check if the project uses manual progress
|
||||||
|
IF _project_id IS NOT NULL THEN
|
||||||
|
SELECT COALESCE(use_manual_progress, FALSE),
|
||||||
|
COALESCE(use_weighted_progress, FALSE),
|
||||||
|
COALESCE(use_time_progress, FALSE)
|
||||||
|
FROM projects
|
||||||
|
WHERE id = _project_id
|
||||||
|
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Get all subtasks
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM tasks
|
||||||
|
WHERE parent_task_id = _task_id AND archived IS FALSE
|
||||||
|
INTO _sub_tasks_count;
|
||||||
|
|
||||||
|
-- If task is complete, always return 100%
|
||||||
|
IF _task_complete IS TRUE THEN
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'ratio', 100,
|
||||||
|
'total_completed', 1,
|
||||||
|
'total_tasks', 1,
|
||||||
|
'is_manual', FALSE
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Use manual progress value in two cases:
|
||||||
|
-- 1. When task has manual_progress = TRUE and progress_value is set
|
||||||
|
-- 2. When project has use_manual_progress = TRUE and progress_value is set
|
||||||
|
IF (_is_manual IS TRUE AND _manual_value IS NOT NULL) OR
|
||||||
|
(_use_manual_progress IS TRUE AND _manual_value IS NOT NULL) THEN
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'ratio', _manual_value,
|
||||||
|
'total_completed', 0,
|
||||||
|
'total_tasks', 0,
|
||||||
|
'is_manual', TRUE
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- If there are no subtasks, just use the parent task's status (unless in time-based mode)
|
||||||
|
IF _sub_tasks_count = 0 THEN
|
||||||
|
-- Use time-based estimation for tasks without subtasks if enabled
|
||||||
|
IF _use_time_progress IS TRUE THEN
|
||||||
|
-- For time-based tasks without subtasks, we still need some progress calculation
|
||||||
|
-- If the task is completed, return 100%
|
||||||
|
-- Otherwise, use the progress value if set manually, or 0
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN _task_complete IS TRUE THEN 100
|
||||||
|
ELSE COALESCE(_manual_value, 0)
|
||||||
|
END
|
||||||
|
INTO _ratio;
|
||||||
|
ELSE
|
||||||
|
-- Traditional calculation for non-time-based tasks
|
||||||
|
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
||||||
|
INTO _parent_task_done;
|
||||||
|
|
||||||
|
_ratio = _parent_task_done * 100;
|
||||||
|
END IF;
|
||||||
|
ELSE
|
||||||
|
-- If project uses manual progress, calculate based on subtask manual progress values
|
||||||
|
IF _use_manual_progress IS TRUE THEN
|
||||||
|
WITH subtask_progress AS (
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.manual_progress,
|
||||||
|
t.progress_value,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
) AS is_complete
|
||||||
|
FROM tasks t
|
||||||
|
WHERE t.parent_task_id = _task_id
|
||||||
|
AND t.archived IS FALSE
|
||||||
|
),
|
||||||
|
subtask_with_values AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- For completed tasks, always use 100%
|
||||||
|
WHEN is_complete IS TRUE THEN 100
|
||||||
|
-- For tasks with progress value set, use it regardless of manual_progress flag
|
||||||
|
WHEN progress_value IS NOT NULL THEN progress_value
|
||||||
|
-- Default to 0 for incomplete tasks with no progress value
|
||||||
|
ELSE 0
|
||||||
|
END AS progress_value
|
||||||
|
FROM subtask_progress
|
||||||
|
)
|
||||||
|
SELECT COALESCE(AVG(progress_value), 0)
|
||||||
|
FROM subtask_with_values
|
||||||
|
INTO _ratio;
|
||||||
|
-- If project uses weighted progress, calculate based on subtask weights
|
||||||
|
ELSIF _use_weighted_progress IS TRUE THEN
|
||||||
|
WITH subtask_progress AS (
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.manual_progress,
|
||||||
|
t.progress_value,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
) AS is_complete,
|
||||||
|
COALESCE(t.weight, 100) AS weight
|
||||||
|
FROM tasks t
|
||||||
|
WHERE t.parent_task_id = _task_id
|
||||||
|
AND t.archived IS FALSE
|
||||||
|
),
|
||||||
|
subtask_with_values AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- For completed tasks, always use 100%
|
||||||
|
WHEN is_complete IS TRUE THEN 100
|
||||||
|
-- For tasks with progress value set, use it regardless of manual_progress flag
|
||||||
|
WHEN progress_value IS NOT NULL THEN progress_value
|
||||||
|
-- Default to 0 for incomplete tasks with no progress value
|
||||||
|
ELSE 0
|
||||||
|
END AS progress_value,
|
||||||
|
weight
|
||||||
|
FROM subtask_progress
|
||||||
|
)
|
||||||
|
SELECT COALESCE(
|
||||||
|
SUM(progress_value * weight) / NULLIF(SUM(weight), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
FROM subtask_with_values
|
||||||
|
INTO _ratio;
|
||||||
|
-- If project uses time-based progress, calculate based on estimated time
|
||||||
|
ELSIF _use_time_progress IS TRUE THEN
|
||||||
|
WITH subtask_progress AS (
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.manual_progress,
|
||||||
|
t.progress_value,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
) AS is_complete,
|
||||||
|
COALESCE(t.total_minutes, 0) AS estimated_minutes
|
||||||
|
FROM tasks t
|
||||||
|
WHERE t.parent_task_id = _task_id
|
||||||
|
AND t.archived IS FALSE
|
||||||
|
),
|
||||||
|
subtask_with_values AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- For completed tasks, always use 100%
|
||||||
|
WHEN is_complete IS TRUE THEN 100
|
||||||
|
-- For tasks with progress value set, use it regardless of manual_progress flag
|
||||||
|
WHEN progress_value IS NOT NULL THEN progress_value
|
||||||
|
-- Default to 0 for incomplete tasks with no progress value
|
||||||
|
ELSE 0
|
||||||
|
END AS progress_value,
|
||||||
|
estimated_minutes
|
||||||
|
FROM subtask_progress
|
||||||
|
)
|
||||||
|
SELECT COALESCE(
|
||||||
|
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
FROM subtask_with_values
|
||||||
|
INTO _ratio;
|
||||||
|
ELSE
|
||||||
|
-- Traditional calculation based on completion status
|
||||||
|
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
||||||
|
INTO _parent_task_done;
|
||||||
|
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE parent_task_id = _task_id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
INTO _sub_tasks_done;
|
||||||
|
|
||||||
|
_total_completed = _parent_task_done + _sub_tasks_done;
|
||||||
|
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
|
||||||
|
|
||||||
|
IF _total_tasks = 0 THEN
|
||||||
|
_ratio = 0;
|
||||||
|
ELSE
|
||||||
|
_ratio = (_total_completed / _total_tasks) * 100;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Ensure ratio is between 0 and 100
|
||||||
|
IF _ratio < 0 THEN
|
||||||
|
_ratio = 0;
|
||||||
|
ELSIF _ratio > 100 THEN
|
||||||
|
_ratio = 100;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'ratio', _ratio,
|
||||||
|
'total_completed', _total_completed,
|
||||||
|
'total_tasks', _total_tasks,
|
||||||
|
'is_manual', _is_manual
|
||||||
|
);
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Migration: Improve parent task progress calculation
|
||||||
|
-- Date: 2025-04-26
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- File: 20250426000000-improve-parent-task-progress-calculation.sql
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Note: Contents extracted from the file description (actual file not available)
|
||||||
|
-- This migration likely improves how parent task progress is calculated from subtasks
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Migration: Fix multilevel subtask progress calculation
|
||||||
|
-- Date: 2025-05-06
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- File: 20250506000000-fix-multilevel-subtask-progress-calculation.sql
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Note: Contents extracted from the file description (actual file not available)
|
||||||
|
-- This migration likely fixes progress calculation for multilevel nested subtasks
|
||||||
@@ -97,12 +97,14 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
|||||||
try {
|
try {
|
||||||
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]);
|
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]);
|
||||||
const [data] = result.rows;
|
const [data] = result.rows;
|
||||||
|
console.log("data", data);
|
||||||
if (data && data.info && data.info.ratio !== undefined) {
|
if (data && data.info && data.info.ratio !== undefined) {
|
||||||
data.info.ratio = +((data.info.ratio || 0).toFixed());
|
data.info.ratio = +((data.info.ratio || 0).toFixed());
|
||||||
return data.info;
|
return data.info;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
log_error(`Error in getTaskCompleteRatio: ${error}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,6 +327,11 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
|||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||||
|
// Before doing anything else, refresh task progress values for this project
|
||||||
|
if (req.params.id) {
|
||||||
|
await this.refreshProjectTaskProgressValues(req.params.id);
|
||||||
|
}
|
||||||
|
|
||||||
const isSubTasks = !!req.query.parent_task;
|
const isSubTasks = !!req.query.parent_task;
|
||||||
const groupBy = (req.query.group || GroupBy.STATUS) as string;
|
const groupBy = (req.query.group || GroupBy.STATUS) as string;
|
||||||
|
|
||||||
@@ -366,25 +373,25 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
|||||||
public static async updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) {
|
public static async updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const unmapped = [];
|
const unmapped = [];
|
||||||
for (const task of tasks) {
|
|
||||||
task.index = index++;
|
|
||||||
|
|
||||||
// For tasks with subtasks, get the complete ratio from the database function
|
// First, ensure we have the latest progress values for all tasks
|
||||||
|
for (const task of tasks) {
|
||||||
|
// For any task with subtasks, ensure we have the latest progress values
|
||||||
if (task.sub_tasks_count > 0) {
|
if (task.sub_tasks_count > 0) {
|
||||||
try {
|
const info = await this.getTaskCompleteRatio(task.id);
|
||||||
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [task.id]);
|
if (info) {
|
||||||
const [data] = result.rows;
|
task.complete_ratio = info.ratio;
|
||||||
if (data && data.info) {
|
task.progress_value = info.ratio; // Ensure progress_value reflects the calculated ratio
|
||||||
task.complete_ratio = +(data.info.ratio || 0).toFixed();
|
console.log(`Updated task ${task.name} (${task.id}): complete_ratio=${task.complete_ratio}`);
|
||||||
task.completed_count = data.info.total_completed;
|
|
||||||
task.total_tasks_count = data.info.total_tasks;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Proceed with default calculation if database call fails
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now group the tasks with their updated progress values
|
||||||
|
for (const task of tasks) {
|
||||||
|
task.index = index++;
|
||||||
TasksControllerV2.updateTaskViewModel(task);
|
TasksControllerV2.updateTaskViewModel(task);
|
||||||
|
|
||||||
if (groupBy === GroupBy.STATUS) {
|
if (groupBy === GroupBy.STATUS) {
|
||||||
map[task.status]?.tasks.push(task);
|
map[task.status]?.tasks.push(task);
|
||||||
} else if (groupBy === GroupBy.PRIORITY) {
|
} else if (groupBy === GroupBy.PRIORITY) {
|
||||||
@@ -420,6 +427,11 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
|||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async getTasksOnly(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async getTasksOnly(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||||
|
// Before doing anything else, refresh task progress values for this project
|
||||||
|
if (req.params.id) {
|
||||||
|
await this.refreshProjectTaskProgressValues(req.params.id);
|
||||||
|
}
|
||||||
|
|
||||||
const isSubTasks = !!req.query.parent_task;
|
const isSubTasks = !!req.query.parent_task;
|
||||||
|
|
||||||
// Add customColumns flag to query params
|
// Add customColumns flag to query params
|
||||||
@@ -819,4 +831,128 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
|||||||
value
|
value
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async refreshProjectTaskProgressValues(projectId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log(`Refreshing progress values for project ${projectId}`);
|
||||||
|
|
||||||
|
// Run the recalculate_all_task_progress function only for tasks in this project
|
||||||
|
const query = `
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
-- First, reset manual_progress flag for all tasks that have subtasks within this project
|
||||||
|
UPDATE tasks AS t
|
||||||
|
SET manual_progress = FALSE
|
||||||
|
WHERE project_id = $1
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks
|
||||||
|
WHERE parent_task_id = t.id
|
||||||
|
AND archived IS FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Start recalculation from leaf tasks (no subtasks) and propagate upward
|
||||||
|
-- This ensures calculations are done in the right order
|
||||||
|
WITH RECURSIVE task_hierarchy AS (
|
||||||
|
-- Base case: Start with all leaf tasks (no subtasks) in this project
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
parent_task_id,
|
||||||
|
0 AS level
|
||||||
|
FROM tasks
|
||||||
|
WHERE project_id = $1
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM tasks AS sub
|
||||||
|
WHERE sub.parent_task_id = tasks.id
|
||||||
|
AND sub.archived IS FALSE
|
||||||
|
)
|
||||||
|
AND archived IS FALSE
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive case: Move up to parent tasks, but only after processing all their children
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.parent_task_id,
|
||||||
|
th.level + 1
|
||||||
|
FROM tasks t
|
||||||
|
JOIN task_hierarchy th ON t.id = th.parent_task_id
|
||||||
|
WHERE t.archived IS FALSE
|
||||||
|
)
|
||||||
|
-- Sort by level to ensure we calculate in the right order (leaves first, then parents)
|
||||||
|
UPDATE tasks
|
||||||
|
SET progress_value = (SELECT (get_task_complete_ratio(tasks.id)->>'ratio')::FLOAT)
|
||||||
|
FROM (
|
||||||
|
SELECT id, level
|
||||||
|
FROM task_hierarchy
|
||||||
|
ORDER BY level
|
||||||
|
) AS ordered_tasks
|
||||||
|
WHERE tasks.id = ordered_tasks.id
|
||||||
|
AND tasks.project_id = $1
|
||||||
|
AND (manual_progress IS FALSE OR manual_progress IS NULL);
|
||||||
|
END $$;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await db.query(query, [projectId]);
|
||||||
|
console.log(`Finished refreshing progress values for project ${projectId}`);
|
||||||
|
} catch (error) {
|
||||||
|
log_error('Error refreshing project task progress values', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async updateTaskProgress(taskId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Calculate the task's progress using get_task_complete_ratio
|
||||||
|
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]);
|
||||||
|
const [data] = result.rows;
|
||||||
|
|
||||||
|
if (data && data.info && data.info.ratio !== undefined) {
|
||||||
|
const progressValue = +((data.info.ratio || 0).toFixed());
|
||||||
|
|
||||||
|
// Update the task's progress_value in the database
|
||||||
|
await db.query(
|
||||||
|
"UPDATE tasks SET progress_value = $1 WHERE id = $2",
|
||||||
|
[progressValue, taskId]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Updated progress for task ${taskId} to ${progressValue}%`);
|
||||||
|
|
||||||
|
// If this task has a parent, update the parent's progress as well
|
||||||
|
const parentResult = await db.query(
|
||||||
|
"SELECT parent_task_id FROM tasks WHERE id = $1",
|
||||||
|
[taskId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parentResult.rows.length > 0 && parentResult.rows[0].parent_task_id) {
|
||||||
|
await this.updateTaskProgress(parentResult.rows[0].parent_task_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log_error(`Error updating task progress: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this method to update progress when a task's weight is changed
|
||||||
|
public static async updateTaskWeight(taskId: string, weight: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Update the task's weight
|
||||||
|
await db.query(
|
||||||
|
"UPDATE tasks SET weight = $1 WHERE id = $2",
|
||||||
|
[weight, taskId]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the parent task ID
|
||||||
|
const parentResult = await db.query(
|
||||||
|
"SELECT parent_task_id FROM tasks WHERE id = $1",
|
||||||
|
[taskId]
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this task has a parent, update the parent's progress
|
||||||
|
if (parentResult.rows.length > 0 && parentResult.rows[0].parent_task_id) {
|
||||||
|
await this.updateTaskProgress(parentResult.rows[0].parent_task_id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log_error(`Error updating task weight: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import db from "../../config/db";
|
|||||||
import { SocketEvents } from "../events";
|
import { SocketEvents } from "../events";
|
||||||
import { log, log_error, notifyProjectUpdates } from "../util";
|
import { log, log_error, notifyProjectUpdates } from "../util";
|
||||||
import { logProgressChange } from "../../services/activity-logs/activity-logs.service";
|
import { logProgressChange } from "../../services/activity-logs/activity-logs.service";
|
||||||
|
import TasksControllerV2 from "../../controllers/tasks-controller-v2";
|
||||||
|
|
||||||
interface UpdateTaskProgressData {
|
interface UpdateTaskProgressData {
|
||||||
task_id: string;
|
task_id: string;
|
||||||
@@ -21,6 +22,9 @@ async function updateTaskAncestors(io: any, socket: Socket, projectId: string, t
|
|||||||
if (!taskId) return;
|
if (!taskId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Use the new controller method to update the task progress
|
||||||
|
await TasksControllerV2.updateTaskProgress(taskId);
|
||||||
|
|
||||||
// Get the current task's progress ratio
|
// Get the current task's progress ratio
|
||||||
const progressRatio = await db.query(
|
const progressRatio = await db.query(
|
||||||
"SELECT get_task_complete_ratio($1) as ratio",
|
"SELECT get_task_complete_ratio($1) as ratio",
|
||||||
@@ -156,8 +160,13 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
|||||||
|
|
||||||
log(`Emitted progress update for task ${task_id} to project room ${projectId}`, null);
|
log(`Emitted progress update for task ${task_id} to project room ${projectId}`, null);
|
||||||
|
|
||||||
// Recursively update all ancestors in the task hierarchy
|
// If this task has a parent, use our controller to update all ancestors
|
||||||
await updateTaskAncestors(io, socket, projectId, parent_task_id);
|
if (parent_task_id) {
|
||||||
|
// Use the controller method to update the parent task's progress
|
||||||
|
await TasksControllerV2.updateTaskProgress(parent_task_id);
|
||||||
|
// Also use the existing method for socket notifications
|
||||||
|
await updateTaskAncestors(io, socket, projectId, parent_task_id);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify that project updates are available
|
// Notify that project updates are available
|
||||||
notifyProjectUpdates(socket, task_id);
|
notifyProjectUpdates(socket, task_id);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import db from "../../config/db";
|
|||||||
import { SocketEvents } from "../events";
|
import { SocketEvents } from "../events";
|
||||||
import { log, log_error, notifyProjectUpdates } from "../util";
|
import { log, log_error, notifyProjectUpdates } from "../util";
|
||||||
import { logWeightChange } from "../../services/activity-logs/activity-logs.service";
|
import { logWeightChange } from "../../services/activity-logs/activity-logs.service";
|
||||||
|
import TasksControllerV2 from "../../controllers/tasks-controller-v2";
|
||||||
|
|
||||||
interface UpdateTaskWeightData {
|
interface UpdateTaskWeightData {
|
||||||
task_id: string;
|
task_id: string;
|
||||||
@@ -29,13 +30,8 @@ export async function on_update_task_weight(io: any, socket: Socket, data: strin
|
|||||||
const currentWeight = currentWeightResult.rows[0]?.weight;
|
const currentWeight = currentWeightResult.rows[0]?.weight;
|
||||||
const projectId = currentWeightResult.rows[0]?.project_id;
|
const projectId = currentWeightResult.rows[0]?.project_id;
|
||||||
|
|
||||||
// Update the task weight in the database
|
// Update the task weight using our controller method
|
||||||
await db.query(
|
await TasksControllerV2.updateTaskWeight(task_id, weight);
|
||||||
`UPDATE tasks
|
|
||||||
SET weight = $1, updated_at = NOW()
|
|
||||||
WHERE id = $2`,
|
|
||||||
[weight, task_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log the weight change using the activity logs service
|
// Log the weight change using the activity logs service
|
||||||
await logWeightChange({
|
await logWeightChange({
|
||||||
@@ -57,6 +53,10 @@ export async function on_update_task_weight(io: any, socket: Socket, data: strin
|
|||||||
|
|
||||||
// If this is a subtask, update the parent task's progress
|
// If this is a subtask, update the parent task's progress
|
||||||
if (parent_task_id) {
|
if (parent_task_id) {
|
||||||
|
// Use the controller to update the parent task progress
|
||||||
|
await TasksControllerV2.updateTaskProgress(parent_task_id);
|
||||||
|
|
||||||
|
// Get the updated progress to emit to clients
|
||||||
const progressRatio = await db.query(
|
const progressRatio = await db.query(
|
||||||
"SELECT get_task_complete_ratio($1) as ratio",
|
"SELECT get_task_complete_ratio($1) as ratio",
|
||||||
[parent_task_id]
|
[parent_task_id]
|
||||||
@@ -70,6 +70,32 @@ export async function on_update_task_weight(io: any, socket: Socket, data: strin
|
|||||||
progress_value: progressRatio?.rows[0]?.ratio?.ratio || 0
|
progress_value: progressRatio?.rows[0]?.ratio?.ratio || 0
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// We also need to update any grandparent tasks
|
||||||
|
const grandparentResult = await db.query(
|
||||||
|
"SELECT parent_task_id FROM tasks WHERE id = $1",
|
||||||
|
[parent_task_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const grandparentId = grandparentResult.rows[0]?.parent_task_id;
|
||||||
|
|
||||||
|
if (grandparentId) {
|
||||||
|
await TasksControllerV2.updateTaskProgress(grandparentId);
|
||||||
|
|
||||||
|
// Emit the grandparent's updated progress
|
||||||
|
const grandparentProgressRatio = await db.query(
|
||||||
|
"SELECT get_task_complete_ratio($1) as ratio",
|
||||||
|
[grandparentId]
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.emit(
|
||||||
|
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
||||||
|
{
|
||||||
|
task_id: grandparentId,
|
||||||
|
progress_value: grandparentProgressRatio?.rows[0]?.ratio?.ratio || 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify that project updates are available
|
// Notify that project updates are available
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ const ProjectDrawer = ({ onClose }: { onClose: () => void }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
resetForm();
|
resetForm();
|
||||||
dispatch(setProjectData({} as IProjectViewModel));
|
dispatch(setProjectData({} as IProjectViewModel));
|
||||||
dispatch(setProjectId(null));
|
// dispatch(setProjectId(null));
|
||||||
dispatch(setDrawerProjectId(null));
|
dispatch(setDrawerProjectId(null));
|
||||||
dispatch(toggleProjectDrawer());
|
dispatch(toggleProjectDrawer());
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
Reference in New Issue
Block a user