feat: mover una actividad (con sus tareas) a otro proyecto
Menu de tres puntos en el listado de Actividades -> elegir proyecto destino -> confirmar
("Se movera la actividad... (junto con sus tareas)... Aceptar/Cancelar) antes de ejecutar.
Tareas, comentarios y adjuntos se mueven solos (referencian la actividad, no el proyecto). Las
dependencias existentes se eliminan al mover -- solo tienen sentido dentro del mismo proyecto
(addDependency ya lo exigia). Nuevo PUT /api/activities/:id/move, exige permiso de escritura en
ambos proyectos (origen y destino).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,10 @@ export const deleteActivity = asyncHandler(async (req, res) => {
|
||||
ok(res, { deleted: true });
|
||||
});
|
||||
|
||||
export const putMoveActivity = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.moveActivity(Number(req.params.id), Number(req.body.projectId), req.currentUser));
|
||||
});
|
||||
|
||||
export const postDependency = asyncHandler(async (req, res) => {
|
||||
const deps = await service.addDependency(Number(req.params.id), Number(req.body.dependsOnActivityId), req.currentUser);
|
||||
ok(res, deps, 201);
|
||||
|
||||
@@ -64,6 +64,17 @@ export async function remove(id) {
|
||||
await pool.query('DELETE FROM activities WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
// Las tareas (tasks.activity_id) y los comentarios/adjuntos (entity_id de la actividad) se
|
||||
// "mueven" solos, sin tocar nada — no referencian project_id. sort_order vuelve a 0 ("nunca
|
||||
// reordenado a mano") para que aparezca al final del proyecto destino sin pisar su orden manual.
|
||||
// Las dependencias solo tienen sentido dentro del mismo proyecto (addDependency ya lo exige), asi
|
||||
// que las existentes (en cualquiera de los dos sentidos) quedan invalidas al cambiar de proyecto.
|
||||
export async function moveToProject(id, projectId) {
|
||||
await pool.query('UPDATE activities SET project_id = ?, sort_order = 0 WHERE id = ?', [projectId, id]);
|
||||
await pool.query('DELETE FROM activity_dependencies WHERE activity_id = ? OR depends_on_activity_id = ?', [id, id]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
// Posiciones SIEMPRE 1..N (nunca 0 — 0 queda reservado para "nunca reordenado a mano" en la
|
||||
// migracion). Solo reordena las filas de projectId que vengan en ids, ignora el resto.
|
||||
export async function reorder(projectId, ids) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import { requireAuth } from '../../middleware/auth.js';
|
||||
import { getActivity, putActivity, deleteActivity, postDependency, deleteDependency } from './controller.js';
|
||||
import { getActivity, putActivity, deleteActivity, putMoveActivity, postDependency, deleteDependency } from './controller.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -8,6 +8,7 @@ router.use(requireAuth);
|
||||
router.get('/:id', getActivity);
|
||||
router.put('/:id', putActivity);
|
||||
router.delete('/:id', deleteActivity);
|
||||
router.put('/:id/move', putMoveActivity);
|
||||
router.post('/:id/dependencies', postDependency);
|
||||
router.delete('/:id/dependencies/:dependsOnId', deleteDependency);
|
||||
|
||||
|
||||
@@ -118,6 +118,32 @@ export async function removeActivity(id, currentUser) {
|
||||
await recalcProjectProgress(activity.project_id);
|
||||
}
|
||||
|
||||
// Mueve la actividad (y sus tareas/comentarios/adjuntos, que van solos por referenciar activity_id
|
||||
// en vez de project_id) a otro proyecto. Exige permiso de escritura en AMBOS proyectos.
|
||||
export async function moveActivity(id, targetProjectId, currentUser) {
|
||||
const activity = await repo.findById(id);
|
||||
if (!activity) throw new HttpError(404, 'Actividad no encontrada');
|
||||
const sourceProject = await getProjectOr404(activity.project_id);
|
||||
if (!(await canWriteActivity(currentUser, sourceProject, activity))) {
|
||||
throw new HttpError(403, 'No tienes permisos sobre esta actividad');
|
||||
}
|
||||
if (Number(targetProjectId) === activity.project_id) {
|
||||
throw new HttpError(400, 'La actividad ya pertenece a este proyecto');
|
||||
}
|
||||
const targetProject = await getProjectOr404(targetProjectId);
|
||||
if (!(await canWriteProject(currentUser, targetProject))) {
|
||||
throw new HttpError(403, 'No tienes permisos sobre el proyecto de destino');
|
||||
}
|
||||
const moved = await repo.moveToProject(id, targetProjectId);
|
||||
await logAudit({
|
||||
userId: currentUser.id, action: 'move', entityType: 'activity', entityId: id,
|
||||
detail: { fromProjectId: activity.project_id, toProjectId: Number(targetProjectId) },
|
||||
});
|
||||
await recalcProjectProgress(activity.project_id);
|
||||
await recalcProjectProgress(targetProjectId);
|
||||
return moved;
|
||||
}
|
||||
|
||||
// Nuevo orden manual (arrastrar con el mouse): mismo permiso que crear actividades en el proyecto.
|
||||
export async function reorderActivities(projectId, ids, currentUser) {
|
||||
const project = await getProjectOr404(projectId);
|
||||
|
||||
Reference in New Issue
Block a user