1d22adc42e
Backend (Express + MariaDB) y frontend (React + Vite, identidad Fluent/Windows) con el nucleo funcional: seguridad, organizacion, proyectos, actividades, subtareas, dependencias, dashboard, calendario, gantt, comentarios, adjuntos, notificaciones in-app y catalogos. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
60 lines
2.9 KiB
JavaScript
60 lines
2.9 KiB
JavaScript
import * as repo from './repository.js';
|
|
import * as activitiesRepo from '../activities/repository.js';
|
|
import * as projectsRepo from '../projects/repository.js';
|
|
import { canWriteActivity, recalcFromSubtasks } 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 createSubtask(activityId, payload, currentUser) {
|
|
await requireActivityAccess(activityId, currentUser);
|
|
if (!payload.name) throw new HttpError(400, 'El nombre es obligatorio');
|
|
const subtask = await repo.create(activityId, payload);
|
|
await logAudit({ userId: currentUser.id, action: 'create', entityType: 'subtask', entityId: subtask.id });
|
|
const counts = await repo.countsByActivity(activityId);
|
|
await recalcFromSubtasks(activityId, counts.done, counts.total);
|
|
return subtask;
|
|
}
|
|
|
|
export async function updateSubtask(id, payload, currentUser) {
|
|
const subtask = await repo.findById(id);
|
|
if (!subtask) throw new HttpError(404, 'Subtarea no encontrada');
|
|
await requireActivityAccess(subtask.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: 'subtask', entityId: id });
|
|
return updated;
|
|
}
|
|
|
|
export async function setSubtaskDone(id, isDone, currentUser) {
|
|
const subtask = await repo.findById(id);
|
|
if (!subtask) throw new HttpError(404, 'Subtarea no encontrada');
|
|
await requireActivityAccess(subtask.activity_id, currentUser);
|
|
const updated = await repo.setDone(id, isDone);
|
|
await logAudit({ userId: currentUser.id, action: isDone ? 'complete' : 'reopen', entityType: 'subtask', entityId: id });
|
|
const counts = await repo.countsByActivity(subtask.activity_id);
|
|
await recalcFromSubtasks(subtask.activity_id, counts.done, counts.total);
|
|
return updated;
|
|
}
|
|
|
|
export async function removeSubtask(id, currentUser) {
|
|
const subtask = await repo.findById(id);
|
|
if (!subtask) throw new HttpError(404, 'Subtarea no encontrada');
|
|
await requireActivityAccess(subtask.activity_id, currentUser);
|
|
await repo.remove(id);
|
|
await logAudit({ userId: currentUser.id, action: 'delete', entityType: 'subtask', entityId: id });
|
|
const counts = await repo.countsByActivity(subtask.activity_id);
|
|
await recalcFromSubtasks(subtask.activity_id, counts.done, counts.total);
|
|
}
|