|
|
|
@ -281,4 +281,140 @@ export class TaskService {
|
|
|
|
.get(groupId) as any;
|
|
|
|
.get(groupId) as any;
|
|
|
|
return Number(row?.cnt || 0);
|
|
|
|
return Number(row?.cnt || 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Tomar tarea (claim): idempotente
|
|
|
|
|
|
|
|
static claimTask(taskId: number, userId: string): {
|
|
|
|
|
|
|
|
status: 'claimed' | 'already' | 'not_found' | 'completed';
|
|
|
|
|
|
|
|
task?: { id: number; description: string; due_date: string | null };
|
|
|
|
|
|
|
|
} {
|
|
|
|
|
|
|
|
const ensuredUser = ensureUserExists(userId, this.dbInstance);
|
|
|
|
|
|
|
|
if (!ensuredUser) {
|
|
|
|
|
|
|
|
throw new Error('No se pudo asegurar el usuario');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const existing = this.dbInstance
|
|
|
|
|
|
|
|
.prepare(`
|
|
|
|
|
|
|
|
SELECT id, description, due_date, completed, completed_at
|
|
|
|
|
|
|
|
FROM tasks
|
|
|
|
|
|
|
|
WHERE id = ?
|
|
|
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
.get(taskId) as any;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!existing) {
|
|
|
|
|
|
|
|
return { status: 'not_found' };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (existing.completed || existing.completed_at) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
status: 'completed',
|
|
|
|
|
|
|
|
task: {
|
|
|
|
|
|
|
|
id: Number(existing.id),
|
|
|
|
|
|
|
|
description: String(existing.description || ''),
|
|
|
|
|
|
|
|
due_date: existing.due_date ? String(existing.due_date) : null,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const already = this.dbInstance
|
|
|
|
|
|
|
|
.prepare(`SELECT 1 FROM task_assignments WHERE task_id = ? AND user_id = ?`)
|
|
|
|
|
|
|
|
.get(taskId, ensuredUser);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (already) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
status: 'already',
|
|
|
|
|
|
|
|
task: {
|
|
|
|
|
|
|
|
id: Number(existing.id),
|
|
|
|
|
|
|
|
description: String(existing.description || ''),
|
|
|
|
|
|
|
|
due_date: existing.due_date ? String(existing.due_date) : null,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const insertAssignment = this.dbInstance.prepare(`
|
|
|
|
|
|
|
|
INSERT OR IGNORE INTO task_assignments (task_id, user_id, assigned_by)
|
|
|
|
|
|
|
|
VALUES (?, ?, ?)
|
|
|
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.dbInstance.transaction(() => {
|
|
|
|
|
|
|
|
insertAssignment.run(taskId, ensuredUser, ensuredUser);
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
status: 'claimed',
|
|
|
|
|
|
|
|
task: {
|
|
|
|
|
|
|
|
id: Number(existing.id),
|
|
|
|
|
|
|
|
description: String(existing.description || ''),
|
|
|
|
|
|
|
|
due_date: existing.due_date ? String(existing.due_date) : null,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Soltar tarea (unassign): idempotente
|
|
|
|
|
|
|
|
static unassignTask(taskId: number, userId: string): {
|
|
|
|
|
|
|
|
status: 'unassigned' | 'not_assigned' | 'not_found' | 'completed';
|
|
|
|
|
|
|
|
task?: { id: number; description: string; due_date: string | null };
|
|
|
|
|
|
|
|
now_unassigned?: boolean; // true si tras soltar no quedan asignados
|
|
|
|
|
|
|
|
} {
|
|
|
|
|
|
|
|
const ensuredUser = ensureUserExists(userId, this.dbInstance);
|
|
|
|
|
|
|
|
if (!ensuredUser) {
|
|
|
|
|
|
|
|
throw new Error('No se pudo asegurar el usuario');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const existing = this.dbInstance
|
|
|
|
|
|
|
|
.prepare(`
|
|
|
|
|
|
|
|
SELECT id, description, due_date, completed, completed_at
|
|
|
|
|
|
|
|
FROM tasks
|
|
|
|
|
|
|
|
WHERE id = ?
|
|
|
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
.get(taskId) as any;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!existing) {
|
|
|
|
|
|
|
|
return { status: 'not_found' };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (existing.completed || existing.completed_at) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
status: 'completed',
|
|
|
|
|
|
|
|
task: {
|
|
|
|
|
|
|
|
id: Number(existing.id),
|
|
|
|
|
|
|
|
description: String(existing.description || ''),
|
|
|
|
|
|
|
|
due_date: existing.due_date ? String(existing.due_date) : null,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const deleteStmt = this.dbInstance.prepare(`
|
|
|
|
|
|
|
|
DELETE FROM task_assignments
|
|
|
|
|
|
|
|
WHERE task_id = ? AND user_id = ?
|
|
|
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const result = deleteStmt.run(taskId, ensuredUser) as any;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cntRow = this.dbInstance
|
|
|
|
|
|
|
|
.prepare(`SELECT COUNT(*) as cnt FROM task_assignments WHERE task_id = ?`)
|
|
|
|
|
|
|
|
.get(taskId) as any;
|
|
|
|
|
|
|
|
const remaining = Number(cntRow?.cnt || 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (result.changes && result.changes > 0) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
status: 'unassigned',
|
|
|
|
|
|
|
|
task: {
|
|
|
|
|
|
|
|
id: Number(existing.id),
|
|
|
|
|
|
|
|
description: String(existing.description || ''),
|
|
|
|
|
|
|
|
due_date: existing.due_date ? String(existing.due_date) : null,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
now_unassigned: remaining === 0,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
status: 'not_assigned',
|
|
|
|
|
|
|
|
task: {
|
|
|
|
|
|
|
|
id: Number(existing.id),
|
|
|
|
|
|
|
|
description: String(existing.description || ''),
|
|
|
|
|
|
|
|
due_date: existing.due_date ? String(existing.due_date) : null,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
now_unassigned: remaining === 0,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|