feat(horas): desglose por trámite en cada abogado + exportar a Excel
- API: by_user_tramite agrupa horas por (abogado, trámite) para socios y para el propio abogado (no-socio) - API: nueva acción ?action=export que devuelve CSV con BOM UTF-8 (Fecha · Abogado · Departamento · Código · Trámite · Cliente · Estatus · Horas · Descripción), respetando los mismos filtros - UI: tarjetas de "Por persona" ahora son expandibles (clic en la tarjeta) y muestran un mini-listado de trámites con total de horas y cantidad de entradas por trámite - UI: cada trámite del desglose es clickable y abre la ficha - UI: botón verde "Exportar a Excel" en la barra de herramientas
This commit is contained in:
+88
-8
@@ -19,7 +19,7 @@ $method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if ($method !== 'GET' || $action !== 'list') err('Acción no reconocida', 404);
|
||||
if ($method !== 'GET' || !in_array($action, ['list','export'], true)) err('Acción no reconocida', 404);
|
||||
|
||||
try {
|
||||
$from = clean_date($_GET['from'] ?? null);
|
||||
@@ -94,14 +94,17 @@ try {
|
||||
// Breakdown por usuario (solo socio/admin): ignora filtro de abogado pero
|
||||
// respeta rango de fechas → permite ver "todos los usuarios" del período
|
||||
$byUser = [];
|
||||
$byUserTramite = [];
|
||||
if (Auth::isSocio()) {
|
||||
$byWhere = ['1=1'];
|
||||
$byParams = [];
|
||||
if ($from) { $byWhere[] = 'h.fecha >= :from'; $byParams[':from'] = $from . ' 00:00:00'; }
|
||||
if ($to) { $byWhere[] = 'h.fecha <= :to'; $byParams[':to'] = $to . ' 23:59:59'; }
|
||||
$whereByUser = implode(' AND ', $byWhere);
|
||||
|
||||
$bySql = "SELECT h.abogado AS name, SUM(h.hrs) AS total, COUNT(*) AS entries
|
||||
FROM tramites_hours h
|
||||
WHERE " . implode(' AND ', $byWhere) . "
|
||||
WHERE $whereByUser
|
||||
GROUP BY h.abogado
|
||||
ORDER BY total DESC";
|
||||
$byStmt = $pdo->prepare($bySql);
|
||||
@@ -112,6 +115,52 @@ try {
|
||||
$bu['entries'] = (int)$bu['entries'];
|
||||
}
|
||||
unset($bu);
|
||||
|
||||
// Breakdown por usuario × trámite
|
||||
$butSql = "SELECT h.abogado AS name, t.id AS tramite_id, t.codigo AS tramite_codigo,
|
||||
t.departamento AS departamento,
|
||||
COALESCE(NULLIF(t.nombre,''), NULLIF(t.tipo_tramite,''), NULLIF(t.tipo_proceso,''), t.codigo) AS tramite_title,
|
||||
t.cliente AS tramite_cliente,
|
||||
SUM(h.hrs) AS total, COUNT(*) AS entries
|
||||
FROM tramites_hours h
|
||||
JOIN tramites_legales t ON t.id = h.tramite_id
|
||||
WHERE $whereByUser
|
||||
GROUP BY h.abogado, t.id
|
||||
ORDER BY h.abogado ASC, total DESC";
|
||||
$butStmt = $pdo->prepare($butSql);
|
||||
$butStmt->execute($byParams);
|
||||
$byUserTramite = $butStmt->fetchAll();
|
||||
foreach ($byUserTramite as &$r) {
|
||||
$r['tramite_id'] = (int)$r['tramite_id'];
|
||||
$r['total'] = (float)$r['total'];
|
||||
$r['entries'] = (int)$r['entries'];
|
||||
}
|
||||
unset($r);
|
||||
} elseif ($filterName !== null) {
|
||||
// Abogado/usuario regular: su propio desglose por trámite
|
||||
$butParams = [':name' => $filterName];
|
||||
$butWhere = ['h.abogado = :name'];
|
||||
if ($from) { $butWhere[] = 'h.fecha >= :from'; $butParams[':from'] = $from . ' 00:00:00'; }
|
||||
if ($to) { $butWhere[] = 'h.fecha <= :to'; $butParams[':to'] = $to . ' 23:59:59'; }
|
||||
$butSql = "SELECT h.abogado AS name, t.id AS tramite_id, t.codigo AS tramite_codigo,
|
||||
t.departamento AS departamento,
|
||||
COALESCE(NULLIF(t.nombre,''), NULLIF(t.tipo_tramite,''), NULLIF(t.tipo_proceso,''), t.codigo) AS tramite_title,
|
||||
t.cliente AS tramite_cliente,
|
||||
SUM(h.hrs) AS total, COUNT(*) AS entries
|
||||
FROM tramites_hours h
|
||||
JOIN tramites_legales t ON t.id = h.tramite_id
|
||||
WHERE " . implode(' AND ', $butWhere) . "
|
||||
GROUP BY h.abogado, t.id
|
||||
ORDER BY total DESC";
|
||||
$butStmt = $pdo->prepare($butSql);
|
||||
$butStmt->execute($butParams);
|
||||
$byUserTramite = $butStmt->fetchAll();
|
||||
foreach ($byUserTramite as &$r) {
|
||||
$r['tramite_id'] = (int)$r['tramite_id'];
|
||||
$r['total'] = (float)$r['total'];
|
||||
$r['entries'] = (int)$r['entries'];
|
||||
}
|
||||
unset($r);
|
||||
}
|
||||
|
||||
// Lista de usuarios para el dropdown de filtro (solo socios/admins)
|
||||
@@ -126,13 +175,44 @@ try {
|
||||
unset($u);
|
||||
}
|
||||
|
||||
if ($action === 'export') {
|
||||
// CSV con BOM UTF-8 para que Excel lea acentos correctamente
|
||||
$filename = 'horas_' . date('Y-m-d_His') . '.csv';
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
$deptLabels = ['adm'=>'Administrativo', 'corp'=>'Corp · Com · Civil', 'mig'=>'Migración', 'lit'=>'Litigios'];
|
||||
$out = fopen('php://output', 'w');
|
||||
fwrite($out, "\xEF\xBB\xBF");
|
||||
fputcsv($out, ['Fecha', 'Abogado', 'Departamento', 'Código', 'Trámite', 'Cliente', 'Estatus', 'Horas', 'Descripción']);
|
||||
foreach ($entries as $e) {
|
||||
$hrs = (float)$e['hrs'];
|
||||
$full = floor($hrs);
|
||||
$mins = (int)round(($hrs - $full) * 60);
|
||||
$hrsFmt = $mins === 0 ? (string)$full : sprintf('%d:%02d', $full, $mins);
|
||||
fputcsv($out, [
|
||||
date('Y-m-d H:i', strtotime($e['fecha'])),
|
||||
$e['abogado'],
|
||||
$deptLabels[$e['tramite_departamento']] ?? $e['tramite_departamento'],
|
||||
$e['tramite_codigo'],
|
||||
$e['tramite_title'],
|
||||
$e['tramite_cliente'],
|
||||
$e['tramite_estatus'],
|
||||
$hrsFmt,
|
||||
$e['descripcion'],
|
||||
]);
|
||||
}
|
||||
fclose($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
ok([
|
||||
'entries' => $entries,
|
||||
'by_user' => $byUser,
|
||||
'avail_users' => $availUsers,
|
||||
'total_hrs' => $totalHrs,
|
||||
'is_socio' => Auth::isSocio(),
|
||||
'my_name' => Auth::userName(),
|
||||
'entries' => $entries,
|
||||
'by_user' => $byUser,
|
||||
'by_user_tramite' => $byUserTramite,
|
||||
'avail_users' => $availUsers,
|
||||
'total_hrs' => $totalHrs,
|
||||
'is_socio' => Auth::isSocio(),
|
||||
'my_name' => Auth::userName(),
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
|
||||
Reference in New Issue
Block a user