Merge pull request #119 from chamikaJ/imp/task-list-loading-improvement
Imp/task list loading improvement
This commit is contained in:
@@ -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)
|
||||||
@@ -212,4 +220,4 @@ A: Open the task drawer, go to the Info tab, and use the progress slider for tas
|
|||||||
A: Yes, each project can have its own progress method.
|
A: Yes, each project can have its own progress method.
|
||||||
|
|
||||||
**Q: What if I don't see progress fields in my task drawer?**
|
**Q: What if I don't see progress fields in my task drawer?**
|
||||||
A: Progress input is only visible for tasks without subtasks. Parent tasks' progress is automatically calculated.
|
A: Progress input is only visible for tasks without subtasks. Parent tasks' progress is automatically calculated.
|
||||||
@@ -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;
|
||||||
@@ -1,44 +1,55 @@
|
|||||||
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,
|
||||||
ADD COLUMN IF NOT EXISTS progress_value INTEGER DEFAULT NULL,
|
ADD COLUMN IF NOT EXISTS progress_value INTEGER DEFAULT NULL,
|
||||||
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
|
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
-- Add progress-related fields to projects table
|
-- Add progress-related fields to projects table
|
||||||
ALTER TABLE projects
|
ALTER TABLE projects
|
||||||
ADD COLUMN IF NOT EXISTS use_manual_progress BOOLEAN DEFAULT FALSE,
|
ADD COLUMN IF NOT EXISTS use_manual_progress BOOLEAN DEFAULT FALSE,
|
||||||
ADD COLUMN IF NOT EXISTS use_weighted_progress BOOLEAN DEFAULT FALSE,
|
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
|
||||||
$$
|
$$
|
||||||
DECLARE
|
DECLARE
|
||||||
_parent_task_done FLOAT = 0;
|
_parent_task_done FLOAT = 0;
|
||||||
_sub_tasks_done FLOAT = 0;
|
_sub_tasks_done FLOAT = 0;
|
||||||
_sub_tasks_count FLOAT = 0;
|
_sub_tasks_count FLOAT = 0;
|
||||||
_total_completed FLOAT = 0;
|
_total_completed FLOAT = 0;
|
||||||
_total_tasks FLOAT = 0;
|
_total_tasks FLOAT = 0;
|
||||||
_ratio FLOAT = 0;
|
_ratio FLOAT = 0;
|
||||||
_is_manual BOOLEAN = FALSE;
|
_is_manual BOOLEAN = FALSE;
|
||||||
_manual_value INTEGER = NULL;
|
_manual_value INTEGER = NULL;
|
||||||
_project_id UUID;
|
_project_id UUID;
|
||||||
_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;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Check if manual progress is set
|
-- Check if manual progress is set
|
||||||
SELECT manual_progress, progress_value, project_id
|
SELECT manual_progress, progress_value, project_id
|
||||||
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;
|
||||||
|
|
||||||
-- 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)
|
||||||
@@ -46,17 +57,18 @@ BEGIN
|
|||||||
WHERE id = _project_id
|
WHERE id = _project_id
|
||||||
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
|
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
|
||||||
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,
|
||||||
'total_tasks', 0,
|
'total_tasks', 0,
|
||||||
'is_manual', TRUE
|
'is_manual', TRUE
|
||||||
);
|
);
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
-- Otherwise calculate automatically as before
|
-- Otherwise calculate automatically as before
|
||||||
SELECT (CASE
|
SELECT (CASE
|
||||||
WHEN EXISTS(SELECT 1
|
WHEN EXISTS(SELECT 1
|
||||||
@@ -75,24 +87,25 @@ 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;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
RETURN JSON_BUILD_OBJECT(
|
RETURN JSON_BUILD_OBJECT(
|
||||||
'ratio', _ratio,
|
'ratio', _ratio,
|
||||||
'total_completed', _total_completed,
|
'total_completed', _total_completed,
|
||||||
'total_tasks', _total_tasks,
|
'total_tasks', _total_tasks,
|
||||||
'is_manual', FALSE
|
'is_manual', FALSE
|
||||||
);
|
);
|
||||||
END
|
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
|
||||||
@@ -167,11 +183,11 @@ BEGIN
|
|||||||
'id', _project_id,
|
'id', _project_id,
|
||||||
'name', (_body ->> 'name')::TEXT,
|
'name', (_body ->> 'name')::TEXT,
|
||||||
'project_manager_id', _project_manager_team_member_id::UUID
|
'project_manager_id', _project_manager_team_member_id::UUID
|
||||||
);
|
);
|
||||||
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,8 +233,9 @@ 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,
|
||||||
category_id, estimated_working_days, estimated_man_days, hours_per_day,
|
folder_id,
|
||||||
|
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,
|
||||||
UPPER(_project_key),
|
UPPER(_project_key),
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -272,7 +290,7 @@ BEGIN
|
|||||||
'id', _project_id,
|
'id', _project_id,
|
||||||
'name', _project_name,
|
'name', _project_name,
|
||||||
'project_created_log_id', _project_created_log_id
|
'project_created_log_id', _project_created_log_id
|
||||||
);
|
);
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
@@ -280,40 +298,187 @@ 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
|
LANGUAGE plpgsql
|
||||||
AS
|
AS
|
||||||
$$
|
$$
|
||||||
DECLARE
|
DECLARE
|
||||||
_parent_task_done FLOAT = 0;
|
_task_id UUID;
|
||||||
_sub_tasks_done FLOAT = 0;
|
_progress_value INTEGER;
|
||||||
_sub_tasks_count FLOAT = 0;
|
_parent_task_id UUID;
|
||||||
_total_completed FLOAT = 0;
|
_project_id UUID;
|
||||||
_total_tasks FLOAT = 0;
|
_current_mode VARCHAR(20);
|
||||||
_ratio FLOAT = 0;
|
BEGIN
|
||||||
_is_manual BOOLEAN = FALSE;
|
_task_id = (_body ->> 'task_id')::UUID;
|
||||||
_manual_value INTEGER = NULL;
|
_progress_value = (_body ->> 'progress_value')::INTEGER;
|
||||||
_project_id UUID;
|
_parent_task_id = (_body ->> 'parent_task_id')::UUID;
|
||||||
_use_manual_progress BOOLEAN = FALSE;
|
|
||||||
_use_weighted_progress BOOLEAN = FALSE;
|
-- Get the project ID and determine the current progress mode
|
||||||
_use_time_progress BOOLEAN = FALSE;
|
SELECT project_id INTO _project_id FROM tasks WHERE id = _task_id;
|
||||||
_task_complete BOOLEAN = FALSE;
|
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
_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,
|
||||||
FROM tasks_with_status_view
|
progress_mode,
|
||||||
WHERE tasks_with_status_view.task_id = tasks.id
|
EXISTS(SELECT 1
|
||||||
AND is_done IS TRUE
|
FROM tasks_with_status_view
|
||||||
) AS is_complete
|
WHERE tasks_with_status_view.task_id = tasks.id
|
||||||
FROM tasks
|
AND is_done IS TRUE) AS is_complete
|
||||||
|
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)
|
||||||
@@ -321,198 +486,206 @@ BEGIN
|
|||||||
WHERE id = _project_id
|
WHERE id = _project_id
|
||||||
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
|
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
-- 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,
|
||||||
'total_tasks', 1,
|
'total_tasks', 1,
|
||||||
'is_manual', FALSE
|
'is_manual', FALSE
|
||||||
);
|
);
|
||||||
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'
|
||||||
RETURN JSON_BUILD_OBJECT(
|
WHEN _use_time_progress IS TRUE THEN 'time'
|
||||||
'ratio', _manual_value,
|
ELSE 'default'
|
||||||
'total_completed', 0,
|
END;
|
||||||
'total_tasks', 0,
|
BEGIN
|
||||||
'is_manual', TRUE
|
-- Only use manual progress value if it was set in the current active mode
|
||||||
);
|
-- or if the task is explicitly marked for manual progress
|
||||||
END IF;
|
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(
|
||||||
|
'ratio', _manual_value,
|
||||||
|
'total_completed', 0,
|
||||||
|
'total_tasks', 0,
|
||||||
|
'is_manual', TRUE
|
||||||
|
);
|
||||||
|
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
|
WHEN _manual_value IS NOT NULL AND (_progress_mode = 'time' OR _progress_mode IS NULL)
|
||||||
ELSE COALESCE(_manual_value, 0)
|
THEN _manual_value
|
||||||
END
|
ELSE 0
|
||||||
|
END
|
||||||
INTO _ratio;
|
INTO _ratio;
|
||||||
ELSE
|
ELSE
|
||||||
-- Traditional calculation for non-time-based tasks
|
-- Traditional calculation for non-time-based tasks
|
||||||
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
SELECT (CASE WHEN _task_complete 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;
|
||||||
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,
|
t.progress_mode,
|
||||||
EXISTS(
|
EXISTS(SELECT 1
|
||||||
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) AS is_complete
|
||||||
AND is_done IS TRUE
|
FROM tasks t
|
||||||
) AS is_complete
|
WHERE t.parent_task_id = _task_id
|
||||||
FROM tasks t
|
AND t.archived IS FALSE),
|
||||||
WHERE t.parent_task_id = _task_id
|
subtask_with_values AS (SELECT CASE
|
||||||
AND t.archived IS FALSE
|
-- For completed tasks, always use 100%
|
||||||
),
|
WHEN is_complete IS TRUE THEN 100
|
||||||
subtask_with_values AS (
|
-- For tasks with progress value set in the correct mode, use it
|
||||||
SELECT
|
WHEN progress_value IS NOT NULL AND
|
||||||
CASE
|
(progress_mode = 'manual' OR progress_mode IS NULL)
|
||||||
-- For completed tasks, always use 100%
|
THEN progress_value
|
||||||
WHEN is_complete IS TRUE THEN 100
|
-- Default to 0 for incomplete tasks with no progress value or wrong mode
|
||||||
-- For tasks with progress value set, use it regardless of manual_progress flag
|
ELSE 0
|
||||||
WHEN progress_value IS NOT NULL THEN progress_value
|
END AS progress_value
|
||||||
-- Default to 0 for incomplete tasks with no progress value
|
FROM subtask_progress)
|
||||||
ELSE 0
|
|
||||||
END AS progress_value
|
|
||||||
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,
|
t.progress_mode,
|
||||||
EXISTS(
|
EXISTS(SELECT 1
|
||||||
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) AS is_complete,
|
||||||
AND is_done IS TRUE
|
COALESCE(t.weight, 100) AS weight
|
||||||
) AS is_complete,
|
FROM tasks t
|
||||||
COALESCE(t.weight, 100) AS weight
|
WHERE t.parent_task_id = _task_id
|
||||||
FROM tasks t
|
AND t.archived IS FALSE),
|
||||||
WHERE t.parent_task_id = _task_id
|
subtask_with_values AS (SELECT CASE
|
||||||
AND t.archived IS FALSE
|
-- For completed tasks, always use 100%
|
||||||
),
|
WHEN is_complete IS TRUE THEN 100
|
||||||
subtask_with_values AS (
|
-- For tasks with progress value set in the correct mode, use it
|
||||||
SELECT
|
WHEN progress_value IS NOT NULL AND
|
||||||
CASE
|
(progress_mode = 'weighted' OR progress_mode IS NULL)
|
||||||
-- For completed tasks, always use 100%
|
THEN progress_value
|
||||||
WHEN is_complete IS TRUE THEN 100
|
-- Default to 0 for incomplete tasks with no progress value or wrong mode
|
||||||
-- For tasks with progress value set, use it regardless of manual_progress flag
|
ELSE 0
|
||||||
WHEN progress_value IS NOT NULL THEN progress_value
|
END AS progress_value,
|
||||||
-- Default to 0 for incomplete tasks with no progress value
|
weight
|
||||||
ELSE 0
|
FROM subtask_progress)
|
||||||
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_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,
|
t.progress_mode,
|
||||||
EXISTS(
|
EXISTS(SELECT 1
|
||||||
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) AS is_complete,
|
||||||
AND is_done IS TRUE
|
COALESCE(t.total_minutes, 0) AS estimated_minutes
|
||||||
) AS is_complete,
|
FROM tasks t
|
||||||
COALESCE(t.total_minutes, 0) AS estimated_minutes
|
WHERE t.parent_task_id = _task_id
|
||||||
FROM tasks t
|
AND t.archived IS FALSE),
|
||||||
WHERE t.parent_task_id = _task_id
|
subtask_with_values AS (SELECT CASE
|
||||||
AND t.archived IS FALSE
|
-- For completed tasks, always use 100%
|
||||||
),
|
WHEN is_complete IS TRUE THEN 100
|
||||||
subtask_with_values AS (
|
-- For tasks with progress value set in the correct mode, use it
|
||||||
SELECT
|
WHEN progress_value IS NOT NULL AND
|
||||||
CASE
|
(progress_mode = 'time' OR progress_mode IS NULL)
|
||||||
-- For completed tasks, always use 100%
|
THEN progress_value
|
||||||
WHEN is_complete IS TRUE THEN 100
|
-- Default to 0 for incomplete tasks with no progress value or wrong mode
|
||||||
-- For tasks with progress value set, use it regardless of manual_progress flag
|
ELSE 0
|
||||||
WHEN progress_value IS NOT NULL THEN progress_value
|
END AS progress_value,
|
||||||
-- Default to 0 for incomplete tasks with no progress value
|
estimated_minutes
|
||||||
ELSE 0
|
FROM subtask_progress)
|
||||||
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_with_values
|
FROM subtask_with_values
|
||||||
INTO _ratio;
|
INTO _ratio;
|
||||||
ELSE
|
ELSE
|
||||||
-- Traditional calculation based on completion status
|
-- Traditional calculation based on completion status
|
||||||
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
SELECT (CASE WHEN _task_complete IS TRUE THEN 1 ELSE 0 END)
|
||||||
INTO _parent_task_done;
|
INTO _parent_task_done;
|
||||||
|
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM tasks_with_status_view
|
FROM tasks_with_status_view
|
||||||
WHERE parent_task_id = _task_id
|
WHERE parent_task_id = _task_id
|
||||||
AND is_done IS TRUE
|
AND is_done IS TRUE
|
||||||
INTO _sub_tasks_done;
|
INTO _sub_tasks_done;
|
||||||
|
|
||||||
_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;
|
||||||
END IF;
|
END IF;
|
||||||
END IF;
|
END IF;
|
||||||
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;
|
||||||
|
|
||||||
RETURN JSON_BUILD_OBJECT(
|
RETURN JSON_BUILD_OBJECT(
|
||||||
'ratio', _ratio,
|
'ratio', _ratio,
|
||||||
'total_completed', _total_completed,
|
'total_completed', _total_completed,
|
||||||
'total_tasks', _total_tasks,
|
'total_tasks', _total_tasks,
|
||||||
'is_manual', _is_manual
|
'is_manual', _is_manual
|
||||||
);
|
);
|
||||||
END
|
END
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
@@ -564,42 +737,42 @@ BEGIN
|
|||||||
status_id,
|
status_id,
|
||||||
parent_task_id,
|
parent_task_id,
|
||||||
sort_order,
|
sort_order,
|
||||||
(SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id,
|
(SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id,
|
||||||
CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key,
|
CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key,
|
||||||
(SELECT start_time
|
(SELECT start_time
|
||||||
FROM task_timers
|
FROM task_timers
|
||||||
WHERE task_id = tasks.id
|
WHERE task_id = tasks.id
|
||||||
AND user_id = _user_id) AS timer_start_time,
|
AND user_id = _user_id) AS timer_start_time,
|
||||||
parent_task_id IS NOT NULL AS is_sub_task,
|
parent_task_id IS NOT NULL AS is_sub_task,
|
||||||
(SELECT COUNT('*')
|
(SELECT COUNT('*')
|
||||||
FROM tasks
|
FROM tasks
|
||||||
WHERE parent_task_id = tasks.id
|
WHERE parent_task_id = tasks.id
|
||||||
AND archived IS FALSE) AS sub_tasks_count,
|
AND archived IS FALSE) AS sub_tasks_count,
|
||||||
(SELECT COUNT(*)
|
(SELECT COUNT(*)
|
||||||
FROM tasks_with_status_view tt
|
FROM tasks_with_status_view tt
|
||||||
WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id)
|
WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id)
|
||||||
AND tt.is_done IS TRUE)
|
AND tt.is_done IS TRUE)
|
||||||
AS completed_count,
|
AS completed_count,
|
||||||
(SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count,
|
(SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count,
|
||||||
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON)
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON)
|
||||||
FROM (SELECT task_labels.label_id AS id,
|
FROM (SELECT task_labels.label_id AS id,
|
||||||
(SELECT name FROM team_labels WHERE id = task_labels.label_id),
|
(SELECT name FROM team_labels WHERE id = task_labels.label_id),
|
||||||
(SELECT color_code FROM team_labels WHERE id = task_labels.label_id)
|
(SELECT color_code FROM team_labels WHERE id = task_labels.label_id)
|
||||||
FROM task_labels
|
FROM task_labels
|
||||||
WHERE task_id = tasks.id
|
WHERE task_id = tasks.id
|
||||||
ORDER BY name) r) AS labels,
|
ORDER BY name) r) AS labels,
|
||||||
(SELECT color_code
|
(SELECT color_code
|
||||||
FROM sys_task_status_categories
|
FROM sys_task_status_categories
|
||||||
WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color,
|
WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color,
|
||||||
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count,
|
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count,
|
||||||
(SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter,
|
(SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter,
|
||||||
(SELECT get_task_assignees(tasks.id)) AS assignees,
|
(SELECT get_task_assignees(tasks.id)) AS assignees,
|
||||||
(SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id,
|
(SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id,
|
||||||
billable,
|
billable,
|
||||||
schedule_id,
|
schedule_id,
|
||||||
progress_value,
|
progress_value,
|
||||||
weight,
|
weight,
|
||||||
(SELECT MAX(level) FROM task_hierarchy) AS task_level
|
(SELECT MAX(level) FROM task_hierarchy) AS task_level
|
||||||
FROM tasks
|
FROM tasks
|
||||||
WHERE id = _task_id) rec;
|
WHERE id = _task_id) rec;
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
<title>Worklenz</title>
|
<title>Worklenz</title>
|
||||||
<!-- Environment configuration -->
|
<!-- Environment configuration -->
|
||||||
<script src="/env-config.js"></script>
|
<script src="/env-config.js"></script>
|
||||||
|
<!-- Unregister service worker -->
|
||||||
|
<script src="/unregister-sw.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -109,5 +109,13 @@
|
|||||||
"expiredDaysAgo": "{{days}} days ago",
|
"expiredDaysAgo": "{{days}} days ago",
|
||||||
|
|
||||||
"continueWith": "Continue with {{plan}}",
|
"continueWith": "Continue with {{plan}}",
|
||||||
"changeToPlan": "Change to {{plan}}"
|
"changeToPlan": "Change to {{plan}}",
|
||||||
|
"creditPlan": "Credit Plan",
|
||||||
|
"customPlan": "Custom Plan",
|
||||||
|
"planValidTill": "Your plan is valid till {{date}}",
|
||||||
|
"purchaseSeatsText": "To continue, you'll need to purchase additional seats.",
|
||||||
|
"currentSeatsText": "You currently have {{seats}} seats available.",
|
||||||
|
"selectSeatsText": "Please select the number of additional seats to purchase.",
|
||||||
|
"purchase": "Purchase",
|
||||||
|
"contactSales": "Contact sales"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,5 +101,13 @@
|
|||||||
|
|
||||||
"expirestoday": "hoy",
|
"expirestoday": "hoy",
|
||||||
"expirestomorrow": "mañana",
|
"expirestomorrow": "mañana",
|
||||||
"expiredDaysAgo": "hace {{days}} días"
|
"expiredDaysAgo": "hace {{days}} días",
|
||||||
|
"creditPlan": "Plan de Crédito",
|
||||||
|
"customPlan": "Plan Personalizado",
|
||||||
|
"planValidTill": "Su plan es válido hasta {{date}}",
|
||||||
|
"purchaseSeatsText": "Para continuar, deberá comprar asientos adicionales.",
|
||||||
|
"currentSeatsText": "Actualmente tiene {{seats}} asientos disponibles.",
|
||||||
|
"selectSeatsText": "Seleccione el número de asientos adicionales a comprar.",
|
||||||
|
"purchase": "Comprar",
|
||||||
|
"contactSales": "Contactar ventas"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,5 +101,13 @@
|
|||||||
|
|
||||||
"expirestoday": "hoje",
|
"expirestoday": "hoje",
|
||||||
"expirestomorrow": "amanhã",
|
"expirestomorrow": "amanhã",
|
||||||
"expiredDaysAgo": "há {{days}} dias"
|
"expiredDaysAgo": "há {{days}} dias",
|
||||||
|
"creditPlan": "Plano de Crédito",
|
||||||
|
"customPlan": "Plano Personalizado",
|
||||||
|
"planValidTill": "Seu plano é válido até {{date}}",
|
||||||
|
"purchaseSeatsText": "Para continuar, você precisará comprar assentos adicionais.",
|
||||||
|
"currentSeatsText": "Atualmente você tem {{seats}} assentos disponíveis.",
|
||||||
|
"selectSeatsText": "Selecione o número de assentos adicionais para comprar.",
|
||||||
|
"purchase": "Comprar",
|
||||||
|
"contactSales": "Fale com vendas"
|
||||||
}
|
}
|
||||||
|
|||||||
7
worklenz-frontend/public/unregister-sw.js
Normal file
7
worklenz-frontend/public/unregister-sw.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.getRegistrations().then(function(registrations) {
|
||||||
|
for(let registration of registrations) {
|
||||||
|
registration.unregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -246,7 +246,7 @@ const CurrentPlanDetails = () => {
|
|||||||
|
|
||||||
const renderFreePlan = () => (
|
const renderFreePlan = () => (
|
||||||
<Flex vertical>
|
<Flex vertical>
|
||||||
<Typography.Text strong>Free Plan</Typography.Text>
|
<Typography.Text strong>{t('freePlan')}</Typography.Text>
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
<br />-{' '}
|
<br />-{' '}
|
||||||
{freePlanSettings?.team_member_limit === 0
|
{freePlanSettings?.team_member_limit === 0
|
||||||
@@ -309,16 +309,16 @@ const CurrentPlanDetails = () => {
|
|||||||
|
|
||||||
const renderCreditSubscriptionInfo = () => {
|
const renderCreditSubscriptionInfo = () => {
|
||||||
return <Flex vertical>
|
return <Flex vertical>
|
||||||
<Typography.Text strong>Credit Plan</Typography.Text>
|
<Typography.Text strong>{t('creditPlan','Credit Plan')}</Typography.Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCustomSubscriptionInfo = () => {
|
const renderCustomSubscriptionInfo = () => {
|
||||||
return <Flex vertical>
|
return <Flex vertical>
|
||||||
<Typography.Text strong>Custom Plan</Typography.Text>
|
<Typography.Text strong>{t('customPlan','Custom Plan')}</Typography.Text>
|
||||||
<Typography.Text>Your plan is valid till {billingInfo?.valid_till_date}</Typography.Text>
|
<Typography.Text>{t('planValidTill','Your plan is valid till {{date}}',{date: billingInfo?.valid_till_date})}</Typography.Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -381,15 +381,15 @@ const CurrentPlanDetails = () => {
|
|||||||
>
|
>
|
||||||
<Flex vertical gap="middle" style={{ marginTop: '8px' }}>
|
<Flex vertical gap="middle" style={{ marginTop: '8px' }}>
|
||||||
<Typography.Paragraph style={{ fontSize: '16px', margin: '0 0 16px 0', fontWeight: 500 }}>
|
<Typography.Paragraph style={{ fontSize: '16px', margin: '0 0 16px 0', fontWeight: 500 }}>
|
||||||
To continue, you'll need to purchase additional seats.
|
{t('purchaseSeatsText','To continue, you\'ll need to purchase additional seats.')}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|
||||||
<Typography.Paragraph style={{ margin: '0 0 16px 0' }}>
|
<Typography.Paragraph style={{ margin: '0 0 16px 0' }}>
|
||||||
You currently have {billingInfo?.total_seats} seats available.
|
{t('currentSeatsText','You currently have {{seats}} seats available.',{seats: billingInfo?.total_seats})}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|
||||||
<Typography.Paragraph style={{ margin: '0 0 24px 0' }}>
|
<Typography.Paragraph style={{ margin: '0 0 24px 0' }}>
|
||||||
Please select the number of additional seats to purchase.
|
{t('selectSeatsText','Please select the number of additional seats to purchase.')}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|
||||||
<div style={{ marginBottom: '24px' }}>
|
<div style={{ marginBottom: '24px' }}>
|
||||||
@@ -416,14 +416,14 @@ const CurrentPlanDetails = () => {
|
|||||||
borderRadius: '2px'
|
borderRadius: '2px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Purchase
|
{t('purchase','Purchase')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="middle"
|
size="middle"
|
||||||
>
|
>
|
||||||
Contact sales
|
{t('contactSales','Contact sales')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user