Add 3-level role permissions, organismos operadores, and Histórico de Tomas page

Implements the full ADMIN → ORGANISMO_OPERADOR → OPERATOR permission hierarchy
with scope-filtered data access across all backend services. Adds organismos
operadores management (ADMIN only) and a new Histórico page for viewing
per-meter reading history with chart, consumption stats, and CSV export.

Key changes:
- Backend: 3-level scope filtering on all services (meters, readings, projects, users)
- Backend: Protect GET /meters routes with authenticateToken for role-based filtering
- Backend: Pass requestingUser to reading service for scoped meter readings
- Frontend: New HistoricoPage with meter selector, AreaChart, paginated table
- Frontend: Consumption cards (Actual, Pasado, Diferencial) above date filters
- Frontend: Meter search by name, serial, location, CESPT account, cadastral key
- Frontend: OrganismosPage, updated Sidebar with 3-level visibility
- SQL migrations for organismos_operadores table and FK columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Exteban08
2026-02-09 10:21:33 +00:00
parent 61dafa83ac
commit 613fb2d787
43 changed files with 3049 additions and 324 deletions

View File

@@ -76,7 +76,7 @@ export interface PaginatedResult<T> {
export async function getAll(
filters?: ConcentratorFilters,
pagination?: PaginationOptions,
requestingUser?: { roleName: string; projectId?: string | null }
requestingUser?: { roleName: string; projectId?: string | null; organismoOperadorId?: string | null }
): Promise<PaginatedResult<Concentrator>> {
const page = pagination?.page || 1;
const limit = pagination?.limit || 10;
@@ -89,15 +89,19 @@ export async function getAll(
const params: unknown[] = [];
let paramIndex = 1;
// Role-based filtering: OPERATOR users can only see their assigned project
if (requestingUser && requestingUser.roleName !== 'ADMIN' && requestingUser.projectId) {
// Role-based filtering: 3-level hierarchy
if (requestingUser && requestingUser.roleName === 'ORGANISMO_OPERADOR' && requestingUser.organismoOperadorId) {
conditions.push(`project_id IN (SELECT id FROM projects WHERE organismo_operador_id = $${paramIndex})`);
params.push(requestingUser.organismoOperadorId);
paramIndex++;
} else if (requestingUser && requestingUser.roleName !== 'ADMIN' && requestingUser.projectId) {
conditions.push(`project_id = $${paramIndex}`);
params.push(requestingUser.projectId);
paramIndex++;
}
// Additional filter by project_id (only applies if user is ADMIN or no user context)
if (filters?.project_id && (!requestingUser || requestingUser.roleName === 'ADMIN')) {
// Additional filter by project_id (applies if user is ADMIN, ORGANISMO_OPERADOR, or no user context)
if (filters?.project_id && (!requestingUser || requestingUser.roleName === 'ADMIN' || requestingUser.roleName === 'ORGANISMO_OPERADOR')) {
conditions.push(`project_id = $${paramIndex}`);
params.push(filters.project_id);
paramIndex++;