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; }