Files
panel/src/lib/queries/tasks.js

605 lines
17 KiB
JavaScript

import db from "../db.js";
import { addNoteToTask } from "./notes.js";
import { getUserLanguage, serverT, translateStatus, translatePriority } from "../serverTranslations.js";
// Get all task templates (for dropdown selection)
export function getAllTaskTemplates() {
return db
.prepare("SELECT * FROM tasks WHERE is_standard = 1 ORDER BY name ASC")
.all();
}
// Get all project tasks across all projects
export function getAllProjectTasks() {
return db
.prepare(
`
SELECT
pt.*,
COALESCE(pt.custom_task_name, t.name) as task_name,
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
COALESCE(pt.custom_description, t.description) as description,
CASE
WHEN pt.task_template_id IS NOT NULL THEN 'template'
ELSE 'custom'
END as task_type,
p.project_name,
p.wp,
p.plot,
p.city,
p.address,
p.finish_date,
creator.name as created_by_name,
creator.username as created_by_username,
assignee.name as assigned_to_name,
assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN projects p ON pt.project_id = p.project_id
LEFT JOIN users creator ON pt.created_by = creator.id
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
ORDER BY pt.date_added DESC
`
)
.all();
}
// Get project tasks for a specific project
export function getProjectTasks(projectId) {
return db
.prepare(
`
SELECT
pt.*,
COALESCE(pt.custom_task_name, t.name) as task_name,
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
COALESCE(pt.custom_description, t.description) as description,
CASE
WHEN pt.task_template_id IS NOT NULL THEN 'template'
ELSE 'custom'
END as task_type,
creator.name as created_by_name,
creator.username as created_by_username,
assignee.name as assigned_to_name,
assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN users creator ON pt.created_by = creator.id
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
WHERE pt.project_id = ?
ORDER BY pt.date_added DESC
`
)
.all(projectId);
}
// Create a new project task
export function createProjectTask(data) {
let result;
let taskName;
if (data.task_template_id) {
// Creating from template - explicitly set custom_max_wait_days to NULL so COALESCE uses template value
const stmt = db.prepare(`
INSERT INTO project_tasks (
project_id, task_template_id, custom_max_wait_days, status, priority,
created_by, assigned_to, created_at, updated_at
)
VALUES (?, ?, NULL, ?, ?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))
`);
result = stmt.run(
data.project_id,
data.task_template_id,
data.status || "not_started",
data.priority || "normal",
data.created_by || null,
data.assigned_to || null
);
// Get the template name for the log
const templateStmt = db.prepare("SELECT name FROM tasks WHERE task_id = ?");
const template = templateStmt.get(data.task_template_id);
taskName = template?.name || "Unknown template";
} else {
// Creating custom task
const stmt = db.prepare(`
INSERT INTO project_tasks (
project_id, custom_task_name, custom_max_wait_days, custom_description,
status, priority, created_by, assigned_to, created_at, updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))
`);
result = stmt.run(
data.project_id,
data.custom_task_name,
data.custom_max_wait_days || 0,
data.custom_description || "",
data.status || "not_started",
data.priority || "normal",
data.created_by || null,
data.assigned_to || null
);
taskName = data.custom_task_name;
}
// Add system note for task creation
if (result.lastInsertRowid) {
const language = getUserLanguage();
const priority = data.priority || "normal";
const status = data.status || "not_started";
const translatedPriority = translatePriority(priority, language);
const translatedStatus = translateStatus(status, language);
const logMessage = `${serverT("Task created", language)} "${taskName}" ${serverT("with priority", language)}: ${translatedPriority}, ${serverT("status", language)}: ${translatedStatus}`;
addNoteToTask(result.lastInsertRowid, logMessage, true, data.created_by);
}
return result;
}
// Update project task status
export function updateProjectTaskStatus(taskId, status, userId = null) {
// First get the current task details for logging
const getCurrentTask = db.prepare(`
SELECT
pt.status,
COALESCE(pt.custom_task_name, t.name) as task_name
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
WHERE pt.id = ?
`);
const currentTask = getCurrentTask.get(taskId);
if (!currentTask) {
throw new Error(`Task with ID ${taskId} not found`);
}
const oldStatus = currentTask.status;
let stmt;
let result;
if ((oldStatus === "not_started" || oldStatus === "pending") && status === "in_progress") {
// Starting a task - set date_started
stmt = db.prepare(`
UPDATE project_tasks
SET status = ?, date_started = datetime('now', 'localtime'), updated_at = datetime('now', 'localtime')
WHERE id = ?
`);
result = stmt.run(status, taskId);
} else if (status === "completed") {
// Completing a task - set date_completed
stmt = db.prepare(`
UPDATE project_tasks
SET status = ?, date_completed = datetime('now', 'localtime'), updated_at = datetime('now', 'localtime')
WHERE id = ?
`);
result = stmt.run(status, taskId);
} else {
// Just updating status without changing timestamps
stmt = db.prepare(`
UPDATE project_tasks
SET status = ?, updated_at = datetime('now', 'localtime')
WHERE id = ?
`);
result = stmt.run(status, taskId);
}
// Add system note for status change (only if status actually changed)
if (result.changes > 0 && oldStatus !== status) {
const language = getUserLanguage(); // Default to Polish for now
const fromStatus = translateStatus(oldStatus, language);
const toStatus = translateStatus(status, language);
const logMessage = `${serverT("Status changed from", language)} "${fromStatus}" ${serverT("to", language)} "${toStatus}"`;
addNoteToTask(taskId, logMessage, true, userId);
}
return result;
}
// Delete a project task
export function deleteProjectTask(taskId) {
// First delete all related task notes
const deleteNotesStmt = db.prepare("DELETE FROM notes WHERE task_id = ?");
deleteNotesStmt.run(taskId);
// Then delete the task itself
const deleteTaskStmt = db.prepare("DELETE FROM project_tasks WHERE id = ?");
return deleteTaskStmt.run(taskId);
}
// Get project tasks assigned to a specific user
export function getProjectTasksByAssignedUser(userId) {
return db
.prepare(
`
SELECT
pt.*,
COALESCE(pt.custom_task_name, t.name) as task_name,
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
COALESCE(pt.custom_description, t.description) as description,
CASE
WHEN pt.task_template_id IS NOT NULL THEN 'template'
ELSE 'custom'
END as task_type,
p.project_name,
p.wp,
p.plot,
p.city,
p.address,
p.finish_date,
creator.name as created_by_name,
creator.username as created_by_username,
assignee.name as assigned_to_name,
assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN projects p ON pt.project_id = p.project_id
LEFT JOIN users creator ON pt.created_by = creator.id
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
WHERE pt.assigned_to = ?
ORDER BY pt.date_added DESC
`
)
.all(userId);
}
// Get project tasks created by a specific user
export function getProjectTasksByCreator(userId) {
return db
.prepare(
`
SELECT
pt.*,
COALESCE(pt.custom_task_name, t.name) as task_name,
COALESCE(pt.custom_max_wait_days, t.max_wait_days) as max_wait_days,
COALESCE(pt.custom_description, t.description) as description,
CASE
WHEN pt.task_template_id IS NOT NULL THEN 'template'
ELSE 'custom'
END as task_type,
p.project_name,
p.wp,
p.plot,
p.city,
p.address,
p.finish_date,
creator.name as created_by_name,
creator.username as created_by_username,
assignee.name as assigned_to_name,
assignee.username as assigned_to_username
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
LEFT JOIN projects p ON pt.project_id = p.project_id
LEFT JOIN users creator ON pt.created_by = creator.id
LEFT JOIN users assignee ON pt.assigned_to = assignee.id
WHERE pt.created_by = ?
ORDER BY pt.date_added DESC
`
)
.all(userId);
}
// Update project task assignment
export function updateProjectTaskAssignment(taskId, assignedToUserId) {
const stmt = db.prepare(`
UPDATE project_tasks
SET assigned_to = ?, updated_at = datetime('now', 'localtime')
WHERE id = ?
`);
return stmt.run(assignedToUserId, taskId);
}
// Get active users for task assignment (same as projects)
export function getAllUsersForTaskAssignment() {
return db
.prepare(
`
SELECT id, name, username, role
FROM users
WHERE is_active = 1 AND can_be_assigned = 1
ORDER BY name ASC
`
)
.all();
}
// Update project task (general update for edit modal)
export function updateProjectTask(taskId, updates, userId = null) {
// Get current task for logging
const getCurrentTask = db.prepare(`
SELECT
pt.*,
COALESCE(pt.custom_task_name, t.name) as task_name
FROM project_tasks pt
LEFT JOIN tasks t ON pt.task_template_id = t.task_id
WHERE pt.id = ?
`);
const currentTask = getCurrentTask.get(taskId);
if (!currentTask) {
throw new Error(`Task with ID ${taskId} not found`);
}
// Build dynamic update query
const fields = [];
const values = [];
if (updates.priority !== undefined) {
fields.push("priority = ?");
values.push(updates.priority);
}
if (updates.status !== undefined) {
fields.push("status = ?");
values.push(updates.status);
// Handle status-specific timestamp updates
if ((currentTask.status === "not_started" || currentTask.status === "pending") && updates.status === "in_progress") {
fields.push("date_started = datetime('now', 'localtime')");
} else if (updates.status === "completed") {
fields.push("date_completed = datetime('now', 'localtime')");
}
}
if (updates.assigned_to !== undefined) {
fields.push("assigned_to = ?");
values.push(updates.assigned_to || null);
}
if (updates.date_started !== undefined) {
fields.push("date_started = ?");
values.push(updates.date_started || null);
}
// Always update the updated_at timestamp
fields.push("updated_at = datetime('now', 'localtime')");
values.push(taskId);
const stmt = db.prepare(`
UPDATE project_tasks
SET ${fields.join(", ")}
WHERE id = ?
`);
const result = stmt.run(...values);
// Log the update
if (userId) {
const language = getUserLanguage(); // Default to Polish for now
const changes = [];
if (
updates.priority !== undefined &&
updates.priority !== currentTask.priority
) {
const oldPriority = translatePriority(currentTask.priority, language) || serverT("None", language);
const newPriority = translatePriority(updates.priority, language) || serverT("None", language);
changes.push(
`${serverT("Priority", language)}: ${oldPriority}${newPriority}`
);
}
if (updates.status !== undefined && updates.status !== currentTask.status) {
const oldStatus = translateStatus(currentTask.status, language) || serverT("None", language);
const newStatus = translateStatus(updates.status, language) || serverT("None", language);
changes.push(
`${serverT("Status", language)}: ${oldStatus}${newStatus}`
);
}
if (
updates.assigned_to !== undefined &&
updates.assigned_to !== currentTask.assigned_to
) {
changes.push(serverT("Assignment updated", language));
}
if (
updates.date_started !== undefined &&
updates.date_started !== currentTask.date_started
) {
const oldDate = currentTask.date_started || serverT("None", language);
const newDate = updates.date_started || serverT("None", language);
changes.push(
`${serverT("Date started", language)}: ${oldDate}${newDate}`
);
}
if (changes.length > 0) {
const logMessage = `${serverT("Task updated", language)}: ${changes.join(", ")}`;
addNoteToTask(taskId, logMessage, true, userId);
}
}
return result;
}
// ===== TASK SETS =====
// Get all task sets
export function getAllTaskSets() {
const taskSets = db
.prepare("SELECT * FROM task_sets ORDER BY name ASC")
.all();
// Add templates to each task set
return taskSets.map(taskSet => {
const templates = db
.prepare(`
SELECT
tst.sort_order,
t.task_id,
t.name,
t.max_wait_days,
t.description
FROM task_set_templates tst
JOIN tasks t ON tst.task_template_id = t.task_id
WHERE tst.set_id = ?
ORDER BY tst.sort_order ASC
`)
.all(taskSet.set_id);
return { ...taskSet, templates };
});
}
// Get task sets by task category
export function getTaskSetsByTaskCategory(taskCategory) {
const taskSets = db
.prepare("SELECT * FROM task_sets WHERE task_category = ? ORDER BY name ASC")
.all(taskCategory);
// Add templates to each task set
return taskSets.map(taskSet => {
const templates = db
.prepare(`
SELECT
tst.sort_order,
t.task_id,
t.name,
t.max_wait_days,
t.description
FROM task_set_templates tst
JOIN tasks t ON tst.task_template_id = t.task_id
WHERE tst.set_id = ?
ORDER BY tst.sort_order ASC
`)
.all(taskSet.set_id);
return { ...taskSet, templates };
});
}
// Get task set by ID with templates
export function getTaskSetById(setId) {
const taskSet = db
.prepare("SELECT * FROM task_sets WHERE set_id = ?")
.get(setId);
if (taskSet) {
const templates = db
.prepare(`
SELECT
tst.sort_order,
t.task_id,
t.name,
t.max_wait_days,
t.description
FROM task_set_templates tst
JOIN tasks t ON tst.task_template_id = t.task_id
WHERE tst.set_id = ?
ORDER BY tst.sort_order ASC
`)
.all(setId);
return { ...taskSet, templates };
}
return null;
}
// Create a new task set
export function createTaskSet(data) {
const result = db
.prepare(`
INSERT INTO task_sets (name, description, task_category, created_at, updated_at)
VALUES (?, ?, ?, datetime('now', 'localtime'), datetime('now', 'localtime'))
`)
.run(data.name, data.description || null, data.task_category);
return result.lastInsertRowid;
}
// Update a task set
export function updateTaskSet(setId, data) {
const fields = [];
const values = [];
if (data.name !== undefined) {
fields.push("name = ?");
values.push(data.name);
}
if (data.description !== undefined) {
fields.push("description = ?");
values.push(data.description || null);
}
if (data.task_category !== undefined) {
fields.push("task_category = ?");
values.push(data.task_category);
}
fields.push("updated_at = datetime('now', 'localtime')");
values.push(setId);
const stmt = db.prepare(`
UPDATE task_sets
SET ${fields.join(", ")}
WHERE set_id = ?
`);
return stmt.run(...values);
}
// Delete a task set
export function deleteTaskSet(setId) {
// Delete task set templates first (cascade should handle this, but being explicit)
db.prepare("DELETE FROM task_set_templates WHERE set_id = ?").run(setId);
// Delete the task set
return db.prepare("DELETE FROM task_sets WHERE set_id = ?").run(setId);
}
// Add task template to set
export function addTaskTemplateToSet(setId, taskTemplateId, sortOrder = 0) {
return db
.prepare(`
INSERT OR REPLACE INTO task_set_templates (set_id, task_template_id, sort_order)
VALUES (?, ?, ?)
`)
.run(setId, taskTemplateId, sortOrder);
}
// Remove task template from set
export function removeTaskTemplateFromSet(setId, taskTemplateId) {
return db
.prepare(`
DELETE FROM task_set_templates
WHERE set_id = ? AND task_template_id = ?
`)
.run(setId, taskTemplateId);
}
// Apply task set to project (bulk create project tasks)
export function applyTaskSetToProject(setId, projectId, userId = null) {
// Get the task set with templates
const taskSet = getTaskSetById(setId);
if (!taskSet) {
throw new Error(`Task set with ID ${setId} not found`);
}
const createdTasks = [];
const language = getUserLanguage();
// Create project tasks for each template in the set
for (const template of taskSet.templates) {
const result = createProjectTask({
project_id: projectId,
task_template_id: template.task_id,
status: "not_started",
priority: "normal",
created_by: userId,
assigned_to: null, // Will be assigned based on user role logic in createProjectTask
});
createdTasks.push(result.lastInsertRowid);
// Add system note for task set application
const logMessage = `${serverT("Task added from set", language)} "${taskSet.name}"`;
addNoteToTask(result.lastInsertRowid, logMessage, true, userId);
}
return createdTasks;
}