refactor: renombra Subtareas a Tareas en todo el sistema
Actividad -> Tarea, sin nivel "sub" intermedio (no hay una entidad "Tarea" de la que estas fueran sub-, asi que el prefijo sobraba). Tabla subtasks -> tasks (RENAME TABLE idempotente en la migracion 005; 001_initial.sql tambien actualizado para que una instalacion nueva cree la tabla ya con el nombre final, sin pasar por el nombre viejo). Modulo backend, rutas, servicio frontend, pestaña del detalle de actividad, i18n y documentacion actualizados. Sin cambios de comportamiento, solo nombres. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { ok, asyncHandler } from '../../utils/http.js';
|
||||
import * as service from './service.js';
|
||||
|
||||
export const getTasks = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.listByActivity(Number(req.params.activityId)));
|
||||
});
|
||||
|
||||
export const postTask = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.createTask(Number(req.params.activityId), req.body, req.currentUser), 201);
|
||||
});
|
||||
|
||||
export const putTask = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.updateTask(Number(req.params.id), req.body, req.currentUser));
|
||||
});
|
||||
|
||||
export const patchTaskDone = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.setTaskDone(Number(req.params.id), !!req.body.isDone, req.currentUser));
|
||||
});
|
||||
|
||||
export const deleteTask = asyncHandler(async (req, res) => {
|
||||
await service.removeTask(Number(req.params.id), req.currentUser);
|
||||
ok(res, { deleted: true });
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { pool } from '../../config/db.js';
|
||||
|
||||
export async function findByActivity(activityId) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT t.id, t.activity_id, t.name, t.responsible_user_id, u.name AS responsible_name,
|
||||
t.is_done, t.due_date, t.created_at
|
||||
FROM tasks t LEFT JOIN users u ON u.id = t.responsible_user_id
|
||||
WHERE t.activity_id = ? ORDER BY t.created_at`,
|
||||
[activityId]
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function findById(id) {
|
||||
const [rows] = await pool.query('SELECT * FROM tasks WHERE id = ?', [id]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function create(activityId, { name, responsibleUserId, dueDate }) {
|
||||
const [result] = await pool.query(
|
||||
'INSERT INTO tasks (activity_id, name, responsible_user_id, due_date) VALUES (?, ?, ?, ?)',
|
||||
[activityId, name, responsibleUserId || null, dueDate || null]
|
||||
);
|
||||
return findById(result.insertId);
|
||||
}
|
||||
|
||||
export async function update(id, { name, responsibleUserId, dueDate }) {
|
||||
await pool.query('UPDATE tasks SET name = ?, responsible_user_id = ?, due_date = ? WHERE id = ?', [
|
||||
name, responsibleUserId || null, dueDate || null, id,
|
||||
]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
export async function setDone(id, isDone) {
|
||||
await pool.query('UPDATE tasks SET is_done = ? WHERE id = ?', [isDone ? 1 : 0, id]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
export async function remove(id) {
|
||||
await pool.query('DELETE FROM tasks WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
export async function countsByActivity(activityId) {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT COUNT(*) AS total, SUM(is_done) AS done FROM tasks WHERE activity_id = ?',
|
||||
[activityId]
|
||||
);
|
||||
return { total: rows[0].total, done: Number(rows[0].done) || 0 };
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Router } from 'express';
|
||||
import { requireAuth } from '../../middleware/auth.js';
|
||||
import { getTasks, postTask, putTask, patchTaskDone, deleteTask } from './controller.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use(requireAuth);
|
||||
router.get('/activities/:activityId/tasks', getTasks);
|
||||
router.post('/activities/:activityId/tasks', postTask);
|
||||
router.put('/tasks/:id', putTask);
|
||||
router.patch('/tasks/:id/done', patchTaskDone);
|
||||
router.delete('/tasks/:id', deleteTask);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,59 @@
|
||||
import * as repo from './repository.js';
|
||||
import * as activitiesRepo from '../activities/repository.js';
|
||||
import * as projectsRepo from '../projects/repository.js';
|
||||
import { canWriteActivity, recalcFromTasks } from '../activities/service.js';
|
||||
import { HttpError } from '../../utils/http.js';
|
||||
import { logAudit } from '../../utils/audit.js';
|
||||
|
||||
async function requireActivityAccess(activityId, currentUser) {
|
||||
const activity = await activitiesRepo.findById(activityId);
|
||||
if (!activity) throw new HttpError(404, 'Actividad no encontrada');
|
||||
const project = await projectsRepo.findById(activity.project_id);
|
||||
if (!(await canWriteActivity(currentUser, project, activity))) {
|
||||
throw new HttpError(403, 'No tienes permisos sobre esta actividad');
|
||||
}
|
||||
return activity;
|
||||
}
|
||||
|
||||
export const listByActivity = (activityId) => repo.findByActivity(activityId);
|
||||
|
||||
export async function createTask(activityId, payload, currentUser) {
|
||||
await requireActivityAccess(activityId, currentUser);
|
||||
if (!payload.name) throw new HttpError(400, 'El nombre es obligatorio');
|
||||
const task = await repo.create(activityId, payload);
|
||||
await logAudit({ userId: currentUser.id, action: 'create', entityType: 'task', entityId: task.id });
|
||||
const counts = await repo.countsByActivity(activityId);
|
||||
await recalcFromTasks(activityId, counts.done, counts.total);
|
||||
return task;
|
||||
}
|
||||
|
||||
export async function updateTask(id, payload, currentUser) {
|
||||
const task = await repo.findById(id);
|
||||
if (!task) throw new HttpError(404, 'Tarea no encontrada');
|
||||
await requireActivityAccess(task.activity_id, currentUser);
|
||||
if (!payload.name) throw new HttpError(400, 'El nombre es obligatorio');
|
||||
const updated = await repo.update(id, payload);
|
||||
await logAudit({ userId: currentUser.id, action: 'update', entityType: 'task', entityId: id });
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function setTaskDone(id, isDone, currentUser) {
|
||||
const task = await repo.findById(id);
|
||||
if (!task) throw new HttpError(404, 'Tarea no encontrada');
|
||||
await requireActivityAccess(task.activity_id, currentUser);
|
||||
const updated = await repo.setDone(id, isDone);
|
||||
await logAudit({ userId: currentUser.id, action: isDone ? 'complete' : 'reopen', entityType: 'task', entityId: id });
|
||||
const counts = await repo.countsByActivity(task.activity_id);
|
||||
await recalcFromTasks(task.activity_id, counts.done, counts.total);
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function removeTask(id, currentUser) {
|
||||
const task = await repo.findById(id);
|
||||
if (!task) throw new HttpError(404, 'Tarea no encontrada');
|
||||
await requireActivityAccess(task.activity_id, currentUser);
|
||||
await repo.remove(id);
|
||||
await logAudit({ userId: currentUser.id, action: 'delete', entityType: 'task', entityId: id });
|
||||
const counts = await repo.countsByActivity(task.activity_id);
|
||||
await recalcFromTasks(task.activity_id, counts.done, counts.total);
|
||||
}
|
||||
Reference in New Issue
Block a user