Merge branch 'feature/recurring-tasks' of https://github.com/Worklenz/worklenz into feature/project-finance
This commit is contained in:
@@ -408,6 +408,65 @@ export default class ReportingAllocationController extends ReportingControllerBa
|
||||
|
||||
const { duration, date_range } = req.body;
|
||||
|
||||
// Calculate the date range (start and end)
|
||||
let startDate: moment.Moment;
|
||||
let endDate: moment.Moment;
|
||||
if (date_range && date_range.length === 2) {
|
||||
startDate = moment(date_range[0]);
|
||||
endDate = moment(date_range[1]);
|
||||
} else if (duration === DATE_RANGES.ALL_TIME) {
|
||||
// Fetch the earliest start_date (or created_at if null) from selected projects
|
||||
const minDateQuery = `SELECT MIN(COALESCE(start_date, created_at)) as min_date FROM projects WHERE id IN (${projectIds})`;
|
||||
const minDateResult = await db.query(minDateQuery, []);
|
||||
const minDate = minDateResult.rows[0]?.min_date;
|
||||
startDate = minDate ? moment(minDate) : moment('2000-01-01');
|
||||
endDate = moment();
|
||||
} else {
|
||||
switch (duration) {
|
||||
case DATE_RANGES.YESTERDAY:
|
||||
startDate = moment().subtract(1, "day");
|
||||
endDate = moment().subtract(1, "day");
|
||||
break;
|
||||
case DATE_RANGES.LAST_WEEK:
|
||||
startDate = moment().subtract(1, "week").startOf("isoWeek");
|
||||
endDate = moment().subtract(1, "week").endOf("isoWeek");
|
||||
break;
|
||||
case DATE_RANGES.LAST_MONTH:
|
||||
startDate = moment().subtract(1, "month").startOf("month");
|
||||
endDate = moment().subtract(1, "month").endOf("month");
|
||||
break;
|
||||
case DATE_RANGES.LAST_QUARTER:
|
||||
startDate = moment().subtract(3, "months").startOf("quarter");
|
||||
endDate = moment().subtract(1, "quarter").endOf("quarter");
|
||||
break;
|
||||
default:
|
||||
startDate = moment().startOf("day");
|
||||
endDate = moment().endOf("day");
|
||||
}
|
||||
}
|
||||
|
||||
// Count only weekdays (Mon-Fri) in the period
|
||||
let workingDays = 0;
|
||||
let current = startDate.clone();
|
||||
while (current.isSameOrBefore(endDate, 'day')) {
|
||||
const day = current.isoWeekday();
|
||||
if (day >= 1 && day <= 5) workingDays++;
|
||||
current.add(1, 'day');
|
||||
}
|
||||
|
||||
// Get hours_per_day for all selected projects
|
||||
const projectHoursQuery = `SELECT id, hours_per_day FROM projects WHERE id IN (${projectIds})`;
|
||||
const projectHoursResult = await db.query(projectHoursQuery, []);
|
||||
const projectHoursMap: Record<string, number> = {};
|
||||
for (const row of projectHoursResult.rows) {
|
||||
projectHoursMap[row.id] = row.hours_per_day || 8;
|
||||
}
|
||||
// Sum total working hours for all selected projects
|
||||
let totalWorkingHours = 0;
|
||||
for (const pid of Object.keys(projectHoursMap)) {
|
||||
totalWorkingHours += workingDays * projectHoursMap[pid];
|
||||
}
|
||||
|
||||
const durationClause = this.getDateRangeClause(duration || DATE_RANGES.LAST_WEEK, date_range);
|
||||
const archivedClause = archived
|
||||
? ""
|
||||
@@ -430,6 +489,12 @@ export default class ReportingAllocationController extends ReportingControllerBa
|
||||
for (const member of result.rows) {
|
||||
member.value = member.logged_time ? parseFloat(moment.duration(member.logged_time, "seconds").asHours().toFixed(2)) : 0;
|
||||
member.color_code = getColor(member.name);
|
||||
member.total_working_hours = totalWorkingHours;
|
||||
member.utilization_percent = (totalWorkingHours > 0 && member.logged_time) ? ((parseFloat(member.logged_time) / (totalWorkingHours * 3600)) * 100).toFixed(2) : '0.00';
|
||||
member.utilized_hours = member.logged_time ? (parseFloat(member.logged_time) / 3600).toFixed(2) : '0.00';
|
||||
// Over/under utilized hours: utilized_hours - total_working_hours
|
||||
const overUnder = member.utilized_hours && member.total_working_hours ? (parseFloat(member.utilized_hours) - member.total_working_hours) : 0;
|
||||
member.over_under_utilized_hours = overUnder.toFixed(2);
|
||||
}
|
||||
|
||||
return res.status(200).send(new ServerResponse(true, result.rows));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import WorklenzControllerBase from "./worklenz-controller-base";
|
||||
import {getColor} from "../shared/utils";
|
||||
import {PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA} from "../shared/constants";
|
||||
import { getColor } from "../shared/utils";
|
||||
import { PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA } from "../shared/constants";
|
||||
import moment from "moment/moment";
|
||||
|
||||
export const GroupBy = {
|
||||
@@ -32,23 +32,14 @@ export default class TasksControllerBase extends WorklenzControllerBase {
|
||||
}
|
||||
|
||||
public static updateTaskViewModel(task: any) {
|
||||
console.log(`Processing task ${task.id} (${task.name})`);
|
||||
console.log(` manual_progress: ${task.manual_progress}, progress_value: ${task.progress_value}`);
|
||||
console.log(` project_use_manual_progress: ${task.project_use_manual_progress}, project_use_weighted_progress: ${task.project_use_weighted_progress}`);
|
||||
console.log(` has subtasks: ${task.sub_tasks_count > 0}`);
|
||||
|
||||
// For parent tasks (with subtasks), always use calculated progress from subtasks
|
||||
if (task.sub_tasks_count > 0) {
|
||||
// For parent tasks without manual progress, calculate from subtasks (already done via db function)
|
||||
console.log(` Parent task with subtasks: complete_ratio=${task.complete_ratio}`);
|
||||
|
||||
// Ensure progress matches complete_ratio for consistency
|
||||
task.progress = task.complete_ratio || 0;
|
||||
|
||||
|
||||
// Important: Parent tasks should not have manual progress
|
||||
// If they somehow do, reset it
|
||||
if (task.manual_progress) {
|
||||
console.log(` WARNING: Parent task ${task.id} had manual_progress set to true, resetting`);
|
||||
task.manual_progress = false;
|
||||
task.progress_value = null;
|
||||
}
|
||||
@@ -58,28 +49,24 @@ export default class TasksControllerBase extends WorklenzControllerBase {
|
||||
// For manually set progress, use that value directly
|
||||
task.progress = parseInt(task.progress_value);
|
||||
task.complete_ratio = parseInt(task.progress_value);
|
||||
|
||||
console.log(` Using manual progress: progress=${task.progress}, complete_ratio=${task.complete_ratio}`);
|
||||
}
|
||||
}
|
||||
// For tasks with no subtasks and no manual progress, calculate based on time
|
||||
else {
|
||||
task.progress = task.total_minutes_spent && task.total_minutes
|
||||
? ~~(task.total_minutes_spent / task.total_minutes * 100)
|
||||
task.progress = task.total_minutes_spent && task.total_minutes
|
||||
? ~~(task.total_minutes_spent / task.total_minutes * 100)
|
||||
: 0;
|
||||
|
||||
|
||||
// Set complete_ratio to match progress
|
||||
task.complete_ratio = task.progress;
|
||||
|
||||
console.log(` Calculated time-based progress: progress=${task.progress}, complete_ratio=${task.complete_ratio}`);
|
||||
}
|
||||
|
||||
|
||||
// Ensure numeric values
|
||||
task.progress = parseInt(task.progress) || 0;
|
||||
task.complete_ratio = parseInt(task.complete_ratio) || 0;
|
||||
|
||||
|
||||
task.overdue = task.total_minutes < task.total_minutes_spent;
|
||||
|
||||
task.time_spent = {hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60};
|
||||
task.time_spent = { hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60 };
|
||||
|
||||
task.comments_count = Number(task.comments_count) ? +task.comments_count : 0;
|
||||
task.attachments_count = Number(task.attachments_count) ? +task.attachments_count : 0;
|
||||
|
||||
@@ -97,7 +97,6 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
try {
|
||||
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]);
|
||||
const [data] = result.rows;
|
||||
console.log("data", data);
|
||||
if (data && data.info && data.info.ratio !== undefined) {
|
||||
data.info.ratio = +((data.info.ratio || 0).toFixed());
|
||||
return data.info;
|
||||
@@ -833,9 +832,7 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
}
|
||||
|
||||
public static async refreshProjectTaskProgressValues(projectId: string): Promise<void> {
|
||||
try {
|
||||
console.log(`Refreshing progress values for project ${projectId}`);
|
||||
|
||||
try {
|
||||
// Run the recalculate_all_task_progress function only for tasks in this project
|
||||
const query = `
|
||||
DO $$
|
||||
@@ -893,10 +890,10 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
END $$;
|
||||
`;
|
||||
|
||||
const result = await db.query(query);
|
||||
await db.query(query);
|
||||
console.log(`Finished refreshing progress values for project ${projectId}`);
|
||||
} catch (error) {
|
||||
log_error('Error refreshing project task progress values', error);
|
||||
log_error("Error refreshing project task progress values", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user