feat(task): add progress mode handling and update related functions

Introduce a new `progress_mode` field to tasks and projects to support different progress calculation methods (manual, weighted, time, default). Update database migrations, task progress calculation functions, and related handlers to accommodate these changes. This ensures consistent progress tracking across different project management needs.

The changes include:
- Adding `progress_mode` to the `tasks` table.
- Updating progress calculation functions to respect the selected mode.
- Adding triggers to reset progress values when the project's progress mode changes.
- Enhancing documentation to explain the default progress method.
This commit is contained in:
chamikaJ
2025-05-09 15:59:25 +05:30
parent 4a2393881b
commit ba90fa1274
4 changed files with 712 additions and 238 deletions

View File

@@ -1,9 +1,21 @@
# WorkLenz Task Progress Guide for Users # WorkLenz Task Progress Guide for Users
## Introduction ## Introduction
WorkLenz offers three different ways to track and calculate task progress, each designed for different project management needs. This guide explains how each method works and when to use them. WorkLenz offers three different ways to track and calculate task progress, each designed for different project management needs. This guide explains how each method works and when to use them.
## Default Progress Method
WorkLenz uses a simple completion-based approach as the default progress calculation method. This method is applied when no special progress methods are enabled.
### Example
If you have a parent task with four subtasks and two of the subtasks are marked complete:
- Parent task: Not done
- 2 subtasks: Done
- 2 subtasks: Not done
The parent task will show as 40% complete (2 completed out of 5 total tasks).
## Available Progress Tracking Methods ## Available Progress Tracking Methods
WorkLenz provides these progress tracking methods: WorkLenz provides these progress tracking methods:
@@ -150,10 +162,6 @@ The parent task would be approximately 29% complete.
This demonstrates how tasks with longer time estimates (like Subtask C) have more influence on the overall progress calculation. Even though Subtask B is 80% complete, its shorter time estimate means it contributes less to the overall progress than the partially-completed but longer Subtask A. This demonstrates how tasks with longer time estimates (like Subtask C) have more influence on the overall progress calculation. Even though Subtask B is 80% complete, its shorter time estimate means it contributes less to the overall progress than the partially-completed but longer Subtask A.
## Default Progress Method
If none of the special progress methods are enabled, WorkLenz uses a simple completion-based approach:
### How It Works ### How It Works
- Tasks are either 0% (not done) or 100% (done) - Tasks are either 0% (not done) or 100% (done)

View File

@@ -0,0 +1,150 @@
-- Migration: Update socket event handlers to set progress-mode handlers
-- Date: 2025-04-26
-- Version: 1.0.0
BEGIN;
-- Create ENUM type for progress modes
CREATE TYPE progress_mode_type AS ENUM ('manual', 'weighted', 'time', 'default');
-- Alter tasks table to use ENUM type
ALTER TABLE tasks
ALTER COLUMN progress_mode TYPE progress_mode_type
USING progress_mode::text::progress_mode_type;
-- Update the on_update_task_progress function to set progress_mode
CREATE OR REPLACE FUNCTION on_update_task_progress(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_progress_value INTEGER;
_parent_task_id UUID;
_project_id UUID;
_current_mode progress_mode_type;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_progress_value = (_body ->> 'progress_value')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID and determine the current progress mode
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
IF _project_id IS NOT NULL THEN
SELECT
CASE
WHEN use_manual_progress IS TRUE THEN 'manual'
WHEN use_weighted_progress IS TRUE THEN 'weighted'
WHEN use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END
INTO _current_mode
FROM projects
WHERE id = _project_id;
ELSE
_current_mode := 'default';
END IF;
-- Update the task with progress value and set the progress mode
UPDATE tasks
SET progress_value = _progress_value,
manual_progress = TRUE,
progress_mode = _current_mode,
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'progress_value', _progress_value,
'progress_mode', _current_mode
);
END;
$$;
-- Update the on_update_task_weight function to set progress_mode when weight is updated
CREATE OR REPLACE FUNCTION on_update_task_weight(_body json) RETURNS json
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_weight INTEGER;
_parent_task_id UUID;
_project_id UUID;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_weight = (_body ->> 'weight')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
-- Update the task with weight value and set progress_mode to 'weighted'
UPDATE tasks
SET weight = _weight,
progress_mode = 'weighted',
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'weight', _weight
);
END;
$$;
-- Create a function to reset progress values when switching project progress modes
CREATE OR REPLACE FUNCTION reset_project_progress_values() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
_old_mode progress_mode_type;
_new_mode progress_mode_type;
_project_id UUID;
BEGIN
_project_id := NEW.id;
-- Determine old and new modes
_old_mode :=
CASE
WHEN OLD.use_manual_progress IS TRUE THEN 'manual'
WHEN OLD.use_weighted_progress IS TRUE THEN 'weighted'
WHEN OLD.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
_new_mode :=
CASE
WHEN NEW.use_manual_progress IS TRUE THEN 'manual'
WHEN NEW.use_weighted_progress IS TRUE THEN 'weighted'
WHEN NEW.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
-- If mode has changed, reset progress values for tasks with the old mode
IF _old_mode <> _new_mode THEN
-- Reset progress values for tasks that were set in the old mode
UPDATE tasks
SET progress_value = NULL,
progress_mode = NULL
WHERE project_id = _project_id
AND progress_mode = _old_mode;
END IF;
RETURN NEW;
END;
$$;
-- Create trigger to reset progress values when project progress mode changes
DROP TRIGGER IF EXISTS reset_progress_on_mode_change ON projects;
CREATE TRIGGER reset_progress_on_mode_change
AFTER UPDATE OF use_manual_progress, use_weighted_progress, use_time_progress
ON projects
FOR EACH ROW
EXECUTE FUNCTION reset_project_progress_values();
COMMIT;

View File

@@ -1,5 +1,15 @@
BEGIN; BEGIN;
-- Create ENUM type for progress modes
CREATE TYPE PROGRESS_MODE_TYPE AS ENUM ('manual', 'weighted', 'time', 'default');
-- Alter tasks table to use ENUM type
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 progress_mode PROGRESS_MODE_TYPE DEFAULT 'default',
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
-- Add manual progress fields to tasks table -- Add manual progress fields to tasks table
ALTER TABLE tasks ALTER TABLE tasks
ADD COLUMN IF NOT EXISTS manual_progress BOOLEAN DEFAULT FALSE, ADD COLUMN IF NOT EXISTS manual_progress BOOLEAN DEFAULT FALSE,
@@ -13,7 +23,7 @@ ADD COLUMN IF NOT EXISTS use_weighted_progress BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS use_time_progress BOOLEAN DEFAULT FALSE; ADD COLUMN IF NOT EXISTS use_time_progress BOOLEAN DEFAULT FALSE;
-- Update function to consider manual progress -- Update function to consider manual progress
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id UUID) RETURNS JSON
LANGUAGE plpgsql LANGUAGE plpgsql
AS AS
$$ $$
@@ -38,7 +48,8 @@ BEGIN
INTO _is_manual, _manual_value, _project_id; INTO _is_manual, _manual_value, _project_id;
-- 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
SELECT COALESCE(use_manual_progress, FALSE), SELECT COALESCE(use_manual_progress, FALSE),
COALESCE(use_weighted_progress, FALSE), COALESCE(use_weighted_progress, FALSE),
COALESCE(use_time_progress, FALSE) COALESCE(use_time_progress, FALSE)
@@ -48,7 +59,8 @@ BEGIN
END IF; END IF;
-- If manual progress is enabled and has a value, use it directly -- If manual progress is enabled and has a value, use it directly
IF _is_manual IS TRUE AND _manual_value IS NOT NULL THEN IF _is_manual 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,
@@ -76,7 +88,8 @@ BEGIN
_total_completed = _parent_task_done + _sub_tasks_done; _total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count; -- +1 for the parent task _total_tasks = _sub_tasks_count; -- +1 for the parent task
IF _total_tasks > 0 THEN IF _total_tasks > 0
THEN
_ratio = (_total_completed / _total_tasks) * 100; _ratio = (_total_completed / _total_tasks) * 100;
ELSE ELSE
_ratio = _parent_task_done * 100; _ratio = _parent_task_done * 100;
@@ -92,7 +105,7 @@ END
$$; $$;
-- Update project functions to handle progress-related fields -- Update project functions to handle progress-related fields
CREATE OR REPLACE FUNCTION update_project(_body json) RETURNS json CREATE OR REPLACE FUNCTION update_project(_body JSON) RETURNS JSON
LANGUAGE plpgsql LANGUAGE plpgsql
AS AS
$$ $$
@@ -124,10 +137,11 @@ BEGIN
END IF; END IF;
-- check whether the project name is already in -- check whether the project name is already in
IF EXISTS( IF EXISTS(SELECT name
SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name) FROM projects
AND team_id = _team_id AND id != (_body ->> 'id')::UUID WHERE LOWER(name) = LOWER(_project_name)
) AND team_id = _team_id
AND id != (_body ->> 'id')::UUID)
THEN THEN
RAISE 'PROJECT_EXISTS_ERROR:%', _project_name; RAISE 'PROJECT_EXISTS_ERROR:%', _project_name;
END IF; END IF;
@@ -156,7 +170,9 @@ BEGIN
AND team_id = _team_id AND team_id = _team_id
RETURNING id INTO _project_id; RETURNING id INTO _project_id;
UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'MEMBER') WHERE project_id = _project_id; UPDATE project_members
SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'MEMBER')
WHERE project_id = _project_id;
IF NOT (_project_manager_team_member_id IS NULL) IF NOT (_project_manager_team_member_id IS NULL)
THEN THEN
@@ -171,7 +187,7 @@ BEGIN
END; END;
$$; $$;
CREATE OR REPLACE FUNCTION create_project(_body json) RETURNS json CREATE OR REPLACE FUNCTION create_project(_body JSON) RETURNS JSON
LANGUAGE plpgsql LANGUAGE plpgsql
AS AS
$$ $$
@@ -217,7 +233,8 @@ BEGIN
-- create the project -- create the project
INSERT INSERT
INTO projects (name, key, color_code, start_date, end_date, team_id, notes, owner_id, status_id, health_id, folder_id, INTO projects (name, key, color_code, start_date, end_date, team_id, notes, owner_id, status_id, health_id,
folder_id,
category_id, estimated_working_days, estimated_man_days, hours_per_day, category_id, estimated_working_days, estimated_man_days, hours_per_day,
use_manual_progress, use_weighted_progress, use_time_progress, client_id) use_manual_progress, use_weighted_progress, use_time_progress, client_id)
VALUES (_project_name, VALUES (_project_name,
@@ -264,7 +281,8 @@ BEGIN
PERFORM insert_task_list_columns(_project_id); PERFORM insert_task_list_columns(_project_id);
-- add project manager role if exists -- add project manager role if exists
IF NOT is_null_or_empty(_project_manager_team_member_id) THEN IF NOT is_null_or_empty(_project_manager_team_member_id)
THEN
PERFORM update_project_manager(_project_manager_team_member_id, _project_id); PERFORM update_project_manager(_project_manager_team_member_id, _project_id);
END IF; END IF;
@@ -280,8 +298,152 @@ COMMIT;
BEGIN; BEGIN;
-- Update function to use time-based progress for all tasks -- Update the on_update_task_progress function to set progress_mode
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json CREATE OR REPLACE FUNCTION on_update_task_progress(_body JSON) RETURNS JSON
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_progress_value INTEGER;
_parent_task_id UUID;
_project_id UUID;
_current_mode VARCHAR(20);
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_progress_value = (_body ->> 'progress_value')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID and determine the current progress mode
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
IF _project_id IS NOT NULL
THEN
SELECT CASE
WHEN use_manual_progress IS TRUE THEN 'manual'
WHEN use_weighted_progress IS TRUE THEN 'weighted'
WHEN use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END
INTO _current_mode
FROM projects
WHERE id = _project_id;
ELSE
_current_mode := 'default';
END IF;
-- Update the task with progress value and set the progress mode
UPDATE tasks
SET progress_value = _progress_value,
manual_progress = TRUE,
progress_mode = _current_mode,
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'progress_value', _progress_value,
'progress_mode', _current_mode
);
END;
$$;
-- Update the on_update_task_weight function to set progress_mode when weight is updated
CREATE OR REPLACE FUNCTION on_update_task_weight(_body JSON) RETURNS JSON
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_weight INTEGER;
_parent_task_id UUID;
_project_id UUID;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_weight = (_body ->> 'weight')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
-- Update the task with weight value and set progress_mode to 'weighted'
UPDATE tasks
SET weight = _weight,
progress_mode = 'weighted',
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'weight', _weight
);
END;
$$;
-- Create a function to reset progress values when switching project progress modes
CREATE OR REPLACE FUNCTION reset_project_progress_values() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
_old_mode VARCHAR(20);
_new_mode VARCHAR(20);
_project_id UUID;
BEGIN
_project_id := NEW.id;
-- Determine old and new modes
_old_mode :=
CASE
WHEN OLD.use_manual_progress IS TRUE THEN 'manual'
WHEN OLD.use_weighted_progress IS TRUE THEN 'weighted'
WHEN OLD.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
_new_mode :=
CASE
WHEN NEW.use_manual_progress IS TRUE THEN 'manual'
WHEN NEW.use_weighted_progress IS TRUE THEN 'weighted'
WHEN NEW.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
-- If mode has changed, reset progress values for tasks with the old mode
IF _old_mode <> _new_mode
THEN
-- Reset progress values for tasks that were set in the old mode
UPDATE tasks
SET progress_value = NULL,
progress_mode = NULL
WHERE project_id = _project_id
AND progress_mode = _old_mode;
END IF;
RETURN NEW;
END;
$$;
-- Create trigger to reset progress values when project progress mode changes
DROP TRIGGER IF EXISTS reset_progress_on_mode_change ON projects;
CREATE TRIGGER reset_progress_on_mode_change
AFTER UPDATE OF use_manual_progress, use_weighted_progress, use_time_progress
ON projects
FOR EACH ROW
EXECUTE FUNCTION reset_project_progress_values();
COMMIT;
BEGIN;
-- Add progress_mode column to tasks table to track which mode the progress was set in
ALTER TABLE tasks
ADD COLUMN IF NOT EXISTS progress_mode VARCHAR(20) DEFAULT NULL;
-- Update function to use time-based progress for all tasks and respect mode changes
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id UUID) RETURNS JSON
LANGUAGE plpgsql LANGUAGE plpgsql
AS AS
$$ $$
@@ -299,21 +461,24 @@ DECLARE
_use_weighted_progress BOOLEAN = FALSE; _use_weighted_progress BOOLEAN = FALSE;
_use_time_progress BOOLEAN = FALSE; _use_time_progress BOOLEAN = FALSE;
_task_complete BOOLEAN = FALSE; _task_complete BOOLEAN = FALSE;
_progress_mode VARCHAR(20) = NULL;
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,
EXISTS( progress_value,
SELECT 1 project_id,
progress_mode,
EXISTS(SELECT 1
FROM tasks_with_status_view FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = tasks.id WHERE tasks_with_status_view.task_id = tasks.id
AND is_done IS TRUE AND is_done IS TRUE) AS is_complete
) AS is_complete
FROM tasks FROM tasks
WHERE id = _task_id WHERE id = _task_id
INTO _is_manual, _manual_value, _project_id, _task_complete; INTO _is_manual, _manual_value, _project_id, _progress_mode, _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
SELECT COALESCE(use_manual_progress, FALSE), SELECT COALESCE(use_manual_progress, FALSE),
COALESCE(use_weighted_progress, FALSE), COALESCE(use_weighted_progress, FALSE),
COALESCE(use_time_progress, FALSE) COALESCE(use_time_progress, FALSE)
@@ -325,11 +490,13 @@ BEGIN
-- Get all subtasks -- Get all subtasks
SELECT COUNT(*) SELECT COUNT(*)
FROM tasks FROM tasks
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;
-- If task is complete, always return 100% -- If task is complete, always return 100%
IF _task_complete IS TRUE THEN IF _task_complete IS TRUE
THEN
RETURN JSON_BUILD_OBJECT( RETURN JSON_BUILD_OBJECT(
'ratio', 100, 'ratio', 100,
'total_completed', 1, 'total_completed', 1,
@@ -338,11 +505,22 @@ BEGIN
); );
END IF; END IF;
-- Use manual progress value in two cases: -- Determine current active mode
-- 1. When task has manual_progress = TRUE and progress_value is set DECLARE
-- 2. When project has use_manual_progress = TRUE and progress_value is set _current_mode VARCHAR(20) = CASE
IF (_is_manual IS TRUE AND _manual_value IS NOT NULL) OR WHEN _use_manual_progress IS TRUE THEN 'manual'
(_use_manual_progress IS TRUE AND _manual_value IS NOT NULL) THEN WHEN _use_weighted_progress IS TRUE THEN 'weighted'
WHEN _use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
BEGIN
-- Only use manual progress value if it was set in the current active mode
-- or if the task is explicitly marked for manual progress
IF (_is_manual IS TRUE AND _manual_value IS NOT NULL AND
(_progress_mode IS NULL OR _progress_mode = _current_mode)) OR
(_use_manual_progress IS TRUE AND _manual_value IS NOT NULL AND
(_progress_mode IS NULL OR _progress_mode = 'manual'))
THEN
RETURN JSON_BUILD_OBJECT( RETURN JSON_BUILD_OBJECT(
'ratio', _manual_value, 'ratio', _manual_value,
'total_completed', 0, 'total_completed', 0,
@@ -350,18 +528,22 @@ BEGIN
'is_manual', TRUE 'is_manual', TRUE
); );
END IF; END IF;
END;
-- If there are no subtasks, just use the parent task's status (unless in time-based mode) -- If there are no subtasks, just use the parent task's status (unless in time-based mode)
IF _sub_tasks_count = 0 THEN IF _sub_tasks_count = 0
THEN
-- Use time-based estimation for tasks without subtasks if enabled -- Use time-based estimation for tasks without subtasks if enabled
IF _use_time_progress IS TRUE THEN IF _use_time_progress IS TRUE
THEN
-- For time-based tasks without subtasks, we still need some progress calculation -- For time-based tasks without subtasks, we still need some progress calculation
-- If the task is completed, return 100% -- If the task is completed, return 100%
-- Otherwise, use the progress value if set manually, or 0 -- Otherwise, use the progress value if set manually in the correct mode, or 0
SELECT SELECT CASE
CASE
WHEN _task_complete IS TRUE THEN 100 WHEN _task_complete IS TRUE THEN 100
ELSE COALESCE(_manual_value, 0) WHEN _manual_value IS NOT NULL AND (_progress_mode = 'time' OR _progress_mode IS NULL)
THEN _manual_value
ELSE 0
END END
INTO _ratio; INTO _ratio;
ELSE ELSE
@@ -373,68 +555,60 @@ BEGIN
END IF; END IF;
ELSE ELSE
-- If project uses manual progress, calculate based on subtask manual progress values -- If project uses manual progress, calculate based on subtask manual progress values
IF _use_manual_progress IS TRUE THEN IF _use_manual_progress IS TRUE
WITH subtask_progress AS ( THEN
SELECT WITH subtask_progress AS (SELECT t.id,
t.id,
t.manual_progress, t.manual_progress,
t.progress_value, t.progress_value,
EXISTS( t.progress_mode,
SELECT 1 EXISTS(SELECT 1
FROM tasks_with_status_view FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE AND is_done IS TRUE) AS is_complete
) AS is_complete
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
subtask_with_values AS (
SELECT
CASE
-- For completed tasks, always use 100% -- For completed tasks, always use 100%
WHEN is_complete IS TRUE THEN 100 WHEN is_complete IS TRUE THEN 100
-- For tasks with progress value set, use it regardless of manual_progress flag -- For tasks with progress value set in the correct mode, use it
WHEN progress_value IS NOT NULL THEN progress_value WHEN progress_value IS NOT NULL AND
-- Default to 0 for incomplete tasks with no progress value (progress_mode = 'manual' OR progress_mode IS NULL)
THEN progress_value
-- Default to 0 for incomplete tasks with no progress value or wrong mode
ELSE 0 ELSE 0
END AS progress_value END AS progress_value
FROM subtask_progress FROM subtask_progress)
)
SELECT COALESCE(AVG(progress_value), 0) SELECT COALESCE(AVG(progress_value), 0)
FROM subtask_with_values 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
WITH subtask_progress AS ( THEN
SELECT WITH subtask_progress AS (SELECT t.id,
t.id,
t.manual_progress, t.manual_progress,
t.progress_value, t.progress_value,
EXISTS( t.progress_mode,
SELECT 1 EXISTS(SELECT 1
FROM tasks_with_status_view FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE AND is_done IS TRUE) AS is_complete,
) AS is_complete,
COALESCE(t.weight, 100) AS weight COALESCE(t.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
subtask_with_values AS (
SELECT
CASE
-- For completed tasks, always use 100% -- For completed tasks, always use 100%
WHEN is_complete IS TRUE THEN 100 WHEN is_complete IS TRUE THEN 100
-- For tasks with progress value set, use it regardless of manual_progress flag -- For tasks with progress value set in the correct mode, use it
WHEN progress_value IS NOT NULL THEN progress_value WHEN progress_value IS NOT NULL AND
-- Default to 0 for incomplete tasks with no progress value (progress_mode = 'weighted' OR progress_mode IS NULL)
THEN progress_value
-- Default to 0 for incomplete tasks with no progress value or wrong mode
ELSE 0 ELSE 0
END AS progress_value, END AS progress_value,
weight weight
FROM subtask_progress 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
@@ -442,36 +616,32 @@ BEGIN
FROM subtask_with_values 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
WITH subtask_progress AS ( THEN
SELECT WITH subtask_progress AS (SELECT t.id,
t.id,
t.manual_progress, t.manual_progress,
t.progress_value, t.progress_value,
EXISTS( t.progress_mode,
SELECT 1 EXISTS(SELECT 1
FROM tasks_with_status_view FROM tasks_with_status_view
WHERE tasks_with_status_view.task_id = t.id WHERE tasks_with_status_view.task_id = t.id
AND is_done IS TRUE AND is_done IS TRUE) AS is_complete,
) AS is_complete,
COALESCE(t.total_minutes, 0) AS estimated_minutes COALESCE(t.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
subtask_with_values AS (
SELECT
CASE
-- For completed tasks, always use 100% -- For completed tasks, always use 100%
WHEN is_complete IS TRUE THEN 100 WHEN is_complete IS TRUE THEN 100
-- For tasks with progress value set, use it regardless of manual_progress flag -- For tasks with progress value set in the correct mode, use it
WHEN progress_value IS NOT NULL THEN progress_value WHEN progress_value IS NOT NULL AND
-- Default to 0 for incomplete tasks with no progress value (progress_mode = 'time' OR progress_mode IS NULL)
THEN progress_value
-- Default to 0 for incomplete tasks with no progress value or wrong mode
ELSE 0 ELSE 0
END AS progress_value, END AS progress_value,
estimated_minutes estimated_minutes
FROM subtask_progress 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
@@ -492,7 +662,8 @@ BEGIN
_total_completed = _parent_task_done + _sub_tasks_done; _total_completed = _parent_task_done + _sub_tasks_done;
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task _total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
IF _total_tasks = 0 THEN IF _total_tasks = 0
THEN
_ratio = 0; _ratio = 0;
ELSE ELSE
_ratio = (_total_completed / _total_tasks) * 100; _ratio = (_total_completed / _total_tasks) * 100;
@@ -501,9 +672,11 @@ BEGIN
END IF; END IF;
-- Ensure ratio is between 0 and 100 -- Ensure ratio is between 0 and 100
IF _ratio < 0 THEN IF _ratio < 0
THEN
_ratio = 0; _ratio = 0;
ELSIF _ratio > 100 THEN ELSIF _ratio > 100
THEN
_ratio = 100; _ratio = 100;
END IF; END IF;
@@ -654,4 +827,144 @@ $$;
COMMIT; COMMIT;
BEGIN;
-- Update the on_update_task_progress function to set progress_mode
CREATE OR REPLACE FUNCTION on_update_task_progress(_body JSON) RETURNS JSON
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_progress_value INTEGER;
_parent_task_id UUID;
_project_id UUID;
_current_mode VARCHAR(20);
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_progress_value = (_body ->> 'progress_value')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID and determine the current progress mode
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
IF _project_id IS NOT NULL
THEN
SELECT CASE
WHEN use_manual_progress IS TRUE THEN 'manual'
WHEN use_weighted_progress IS TRUE THEN 'weighted'
WHEN use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END
INTO _current_mode
FROM projects
WHERE id = _project_id;
ELSE
_current_mode := 'default';
END IF;
-- Update the task with progress value and set the progress mode
UPDATE tasks
SET progress_value = _progress_value,
manual_progress = TRUE,
progress_mode = _current_mode,
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'progress_value', _progress_value,
'progress_mode', _current_mode
);
END;
$$;
-- Update the on_update_task_weight function to set progress_mode when weight is updated
CREATE OR REPLACE FUNCTION on_update_task_weight(_body JSON) RETURNS JSON
LANGUAGE plpgsql
AS
$$
DECLARE
_task_id UUID;
_weight INTEGER;
_parent_task_id UUID;
_project_id UUID;
BEGIN
_task_id = (_body ->> 'task_id')::UUID;
_weight = (_body ->> 'weight')::INTEGER;
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
-- Get the project ID
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
-- Update the task with weight value and set progress_mode to 'weighted'
UPDATE tasks
SET weight = _weight,
progress_mode = 'weighted',
updated_at = CURRENT_TIMESTAMP
WHERE id = _task_id;
-- Return the updated task info
RETURN JSON_BUILD_OBJECT(
'task_id', _task_id,
'weight', _weight
);
END;
$$;
-- Create a function to reset progress values when switching project progress modes
CREATE OR REPLACE FUNCTION reset_project_progress_values() RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
_old_mode VARCHAR(20);
_new_mode VARCHAR(20);
_project_id UUID;
BEGIN
_project_id := NEW.id;
-- Determine old and new modes
_old_mode :=
CASE
WHEN OLD.use_manual_progress IS TRUE THEN 'manual'
WHEN OLD.use_weighted_progress IS TRUE THEN 'weighted'
WHEN OLD.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
_new_mode :=
CASE
WHEN NEW.use_manual_progress IS TRUE THEN 'manual'
WHEN NEW.use_weighted_progress IS TRUE THEN 'weighted'
WHEN NEW.use_time_progress IS TRUE THEN 'time'
ELSE 'default'
END;
-- If mode has changed, reset progress values for tasks with the old mode
IF _old_mode <> _new_mode
THEN
-- Reset progress values for tasks that were set in the old mode
UPDATE tasks
SET progress_value = NULL,
progress_mode = NULL
WHERE project_id = _project_id
AND progress_mode = _old_mode;
END IF;
RETURN NEW;
END;
$$;
-- Create trigger to reset progress values when project progress mode changes
DROP TRIGGER IF EXISTS reset_progress_on_mode_change ON projects;
CREATE TRIGGER reset_progress_on_mode_change
AFTER UPDATE OF use_manual_progress, use_weighted_progress, use_time_progress
ON projects
FOR EACH ROW
EXECUTE FUNCTION reset_project_progress_values();
COMMIT;

View File

@@ -1,6 +1,6 @@
import { LabelType } from './label.type';
import { MemberType } from './member.types'; import { MemberType } from './member.types';
import { ProjectType } from './project.types'; import { ProjectType } from './project.types';
import { ITaskLabel } from './tasks/taskLabel.types';
export type TaskStatusType = 'doing' | 'todo' | 'done'; export type TaskStatusType = 'doing' | 'todo' | 'done';
export type TaskPriorityType = 'low' | 'medium' | 'high'; export type TaskPriorityType = 'low' | 'medium' | 'high';
@@ -13,13 +13,16 @@ export type SubTaskType = {
subTaskDueDate?: Date; subTaskDueDate?: Date;
}; };
export type ProgressModeType = 'manual' | 'weighted' | 'time' | 'default';
export type TaskType = { export type TaskType = {
taskId: string; taskId: string;
progress_mode?: ProgressModeType;
task: string; task: string;
description?: string | null; description?: string | null;
progress?: number; progress?: number;
members?: MemberType[]; members?: MemberType[];
labels?: LabelType[]; labels?: ITaskLabel[];
status: TaskStatusType | string; status: TaskStatusType | string;
priority: TaskPriorityType | string; priority: TaskPriorityType | string;
timeTracking?: number; timeTracking?: number;