3884239b7e
Las 7 tarjetas abren su lista en un DetailPopup (proyectos o actividades segun la tarjeta); el bloque "Mis actividades" queda fijo. Nuevas listas de proximos a vencer y para hoy; "Para hoy" deja de contar eliminadas/cancelados. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
193 lines
10 KiB
JavaScript
193 lines
10 KiB
JavaScript
import { pool } from '../../config/db.js';
|
|
|
|
// memberUserId: se pasa solo para roles sin visibilidad total (ver dashboard/controller.js),
|
|
// restringe cada KPI a proyectos donde el usuario es miembro -- mismo criterio que
|
|
// projects/repository.js#findAll, para no mostrar conteos de proyectos que ni siquiera puede ver.
|
|
export async function summary(memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const memberParams = memberUserId ? [memberUserId] : [];
|
|
|
|
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 ${memberClause}`,
|
|
memberParams
|
|
);
|
|
// Base del medidor de "Proyectos activos": todos menos los cancelados (los completados si
|
|
// cuentan). El catalogo no tiene bandera de "cancelado", se identifica por nombre.
|
|
const [[nonCancelledProjects]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%' ${memberClause}`,
|
|
memberParams
|
|
);
|
|
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() ${memberClause}`,
|
|
memberParams
|
|
);
|
|
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) ${memberClause}`,
|
|
memberParams
|
|
);
|
|
// Pendientes y su base (medidor): sin actividades eliminadas ni de proyectos cancelados.
|
|
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 JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL
|
|
AND ps.name NOT LIKE 'cancel%' ${memberClause}`,
|
|
memberParams
|
|
);
|
|
const [[nonCancelledActivities]] = await pool.query(
|
|
`SELECT COUNT(*) AS c FROM activities a
|
|
JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%' ${memberClause}`,
|
|
memberParams
|
|
);
|
|
// Vencidas y su base (medidor): sin eliminadas, sin actividades canceladas ni de proyectos cancelados.
|
|
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 JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%'
|
|
AND a.due_date IS NOT NULL AND a.due_date < CURDATE() ${memberClause}`,
|
|
memberParams
|
|
);
|
|
const [[nonCancelledActivitiesStrict]] = 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 JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%'
|
|
AND st.name NOT LIKE 'cancel%' ${memberClause}`,
|
|
memberParams
|
|
);
|
|
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 JOIN project_statuses ps ON ps.id = p.status_id
|
|
WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%'
|
|
AND a.due_date = CURDATE() ${memberClause}`,
|
|
memberParams
|
|
);
|
|
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 ${memberClause}`,
|
|
memberParams
|
|
);
|
|
|
|
return {
|
|
activeProjects: activeProjects.c,
|
|
nonCancelledProjects: nonCancelledProjects.c,
|
|
overdueProjects: overdueProjects.c,
|
|
dueSoonProjects: dueSoonProjects.c,
|
|
pendingActivities: pendingActivities.c,
|
|
nonCancelledActivities: nonCancelledActivities.c,
|
|
nonCancelledActivitiesStrict: nonCancelledActivitiesStrict.c,
|
|
overdueActivities: overdueActivities.c,
|
|
todayActivities: todayActivities.c,
|
|
averageProgress: avgProgress.avg_pct === null ? 0 : Math.round(Number(avgProgress.avg_pct) * 100) / 100,
|
|
};
|
|
}
|
|
|
|
// Mismo criterio que los KPI pendingActivities ('all') / overdueActivities ('overdue') /
|
|
// todayActivities ('today') -- la lista y el numero de cada tarjeta nunca difieren.
|
|
const ACTIVITY_DUE_CLAUSES = {
|
|
all: '',
|
|
overdue: 'AND a.due_date IS NOT NULL AND a.due_date < CURDATE()',
|
|
today: 'AND a.due_date = CURDATE()',
|
|
};
|
|
export async function pendingActivitiesList(memberUserId, which = 'all') {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const overdueClause = ACTIVITY_DUE_CLAUSES[which];
|
|
const [rows] = await pool.query(
|
|
`SELECT a.id, a.code, a.name, a.due_date, a.progress_pct, p.id AS project_id, p.code AS project_code,
|
|
p.name AS project_name, u.name AS responsible_name
|
|
FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
|
JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id
|
|
LEFT JOIN users u ON u.id = a.responsible_user_id
|
|
WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL
|
|
AND ps.name NOT LIKE 'cancel%' ${overdueClause} ${memberClause}
|
|
ORDER BY (a.due_date IS NULL), a.due_date, p.code, a.code`,
|
|
memberUserId ? [memberUserId] : []
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
// Mismo criterio que el KPI dueSoonProjects de summary().
|
|
export async function dueSoonProjectsList(memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const [rows] = await pool.query(
|
|
`SELECT p.id, p.code, p.name, p.estimated_end_date, p.progress_pct, u.name AS owner_name, ps.name AS status_name
|
|
FROM projects p JOIN project_statuses ps ON ps.id = p.status_id LEFT JOIN users u ON u.id = p.owner_user_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) ${memberClause}
|
|
ORDER BY p.estimated_end_date, p.code`,
|
|
memberUserId ? [memberUserId] : []
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
// Mismo criterio que el KPI activeProjects de summary().
|
|
export async function activeProjectsList(memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const [rows] = await pool.query(
|
|
`SELECT p.id, p.code, p.name, p.estimated_end_date, p.progress_pct, u.name AS owner_name, ps.name AS status_name
|
|
FROM projects p JOIN project_statuses ps ON ps.id = p.status_id LEFT JOIN users u ON u.id = p.owner_user_id
|
|
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}
|
|
ORDER BY (p.estimated_end_date IS NULL), p.estimated_end_date, p.code`,
|
|
memberUserId ? [memberUserId] : []
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
// Mismo criterio que el KPI overdueProjects de summary() -- la lista y el numero nunca difieren.
|
|
export async function overdueProjectsList(memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const [rows] = await pool.query(
|
|
`SELECT p.id, p.code, p.name, p.estimated_end_date, p.progress_pct, u.name AS owner_name, ps.name AS status_name
|
|
FROM projects p JOIN project_statuses ps ON ps.id = p.status_id LEFT JOIN users u ON u.id = p.owner_user_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() ${memberClause}
|
|
ORDER BY p.estimated_end_date`,
|
|
memberUserId ? [memberUserId] : []
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
export async function myPendingActivities(userId, memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const params = memberUserId ? [userId, memberUserId] : [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 = ? ${memberClause}
|
|
ORDER BY (a.due_date IS NULL), a.due_date LIMIT 20`,
|
|
params
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
export async function calendarActivities(monthStart, monthEnd, memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const params = memberUserId ? [monthStart, monthEnd, memberUserId] : [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 ${memberClause}
|
|
ORDER BY a.due_date`,
|
|
params
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
export async function projectsByStatus(memberUserId) {
|
|
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
|
const params = memberUserId ? [memberUserId] : [];
|
|
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 ${memberClause}
|
|
GROUP BY ps.id ORDER BY ps.sort_order`,
|
|
params
|
|
);
|
|
return rows;
|
|
}
|