605 lines
17 KiB
JavaScript
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;
|
|
}
|