5a25ed066d
- DELETE /api/projects/🆔 eliminacion logica (deleted_at/deleted_by), mismo permiso que editar el proyecto. Popup de confirmacion antes de aplicarla. - Papelera (GET /api/projects?trash=true, POST /:id/restore, DELETE /:id/permanent) restringida al superadmin; el borrado fisico exige que el proyecto ya este en la papelera y arrastra actividades/comentarios/adjuntos por ON DELETE CASCADE. - Dashboard (KPIs, actividades pendientes/calendario, proyectos por estado) excluye proyectos eliminados logicamente. - Columna "Creado" en el listado de proyectos, ordenado por defecto de mas nuevo a mas antiguo (ListView admite initialSort ademas del orden por clic en cabecera que ya tenia). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
80 lines
3.5 KiB
JavaScript
80 lines
3.5 KiB
JavaScript
import { pool } from '../../config/db.js';
|
|
|
|
export async function summary() {
|
|
const [[activeProjects]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL`
|
|
);
|
|
const [[overdueProjects]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL
|
|
AND p.estimated_end_date IS NOT NULL AND p.estimated_end_date < CURDATE()`
|
|
);
|
|
const [[dueSoonProjects]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL AND p.estimated_end_date IS NOT NULL
|
|
AND p.estimated_end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)`
|
|
);
|
|
const [[pendingActivities]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
|
JOIN projects p ON p.id = a.project_id
|
|
WHERE st.is_closed = 0 AND p.deleted_at IS NULL`
|
|
);
|
|
const [[overdueActivities]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
|
JOIN projects p ON p.id = a.project_id
|
|
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date IS NOT NULL AND a.due_date < CURDATE()`
|
|
);
|
|
const [[todayActivities]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
|
JOIN projects p ON p.id = a.project_id
|
|
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date = CURDATE()`
|
|
);
|
|
const [[avgProgress]] = await pool.query(
|
|
`SELECT AVG(progress_pct) AS avg_pct FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL`
|
|
);
|
|
|
|
return {
|
|
activeProjects: activeProjects.c,
|
|
overdueProjects: overdueProjects.c,
|
|
dueSoonProjects: dueSoonProjects.c,
|
|
pendingActivities: pendingActivities.c,
|
|
overdueActivities: overdueActivities.c,
|
|
todayActivities: todayActivities.c,
|
|
averageProgress: avgProgress.avg_pct === null ? 0 : Math.round(Number(avgProgress.avg_pct) * 100) / 100,
|
|
};
|
|
}
|
|
|
|
export async function myPendingActivities(userId) {
|
|
const [rows] = await pool.query(
|
|
`SELECT a.id, a.code, a.name, a.due_date, a.progress_pct, p.id AS project_id, p.name AS project_name
|
|
FROM activities a JOIN activity_statuses st ON st.id = a.status_id JOIN projects p ON p.id = a.project_id
|
|
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.responsible_user_id = ?
|
|
ORDER BY (a.due_date IS NULL), a.due_date LIMIT 20`,
|
|
[userId]
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
export async function calendarActivities(monthStart, monthEnd) {
|
|
const [rows] = await pool.query(
|
|
`SELECT a.id, a.code, a.name, a.due_date, a.is_milestone, a.progress_pct,
|
|
st.is_closed AS status_is_closed, p.id AS project_id, p.name AS project_name
|
|
FROM activities a JOIN activity_statuses st ON st.id = a.status_id JOIN projects p ON p.id = a.project_id
|
|
WHERE a.due_date BETWEEN ? AND ? AND p.deleted_at IS NULL
|
|
ORDER BY a.due_date`,
|
|
[monthStart, monthEnd]
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
export async function projectsByStatus() {
|
|
const [rows] = await pool.query(
|
|
`SELECT ps.name, ps.color, COUNT(p.id) AS total
|
|
FROM project_statuses ps LEFT JOIN projects p ON p.status_id = ps.id AND p.deleted_at IS NULL
|
|
GROUP BY ps.id ORDER BY ps.sort_order`
|
|
);
|
|
return rows;
|
|
}
|