import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; export interface DashboardInspectorActivityItem { id: string; occurredAt: Date; actorUsername: string | null; action: string; entityType: string | null; entityId: string | null; } interface DashboardCountsRow { totalAssets: number | string; assetsNeedValidation: number | string; assetsWithoutGeometry: number | string; plannedInspections: number | string; activeUsers: number | string; inactiveUsers: number | string; activeSessions: number | string; openFindings: number | string; actsInFollowUp: number | string; findingsWithoutControlDate: number | string; overdueControls: number | string; controlsNext30Days: number | string; reportsWorking: number | string; reportsOfficialized: number | string; sealedActsWithoutReport: number | string; } export interface DashboardUpcomingControl { id: string; code: string; title: string; nextControlOn: string; actId: string; actCode: string; visitId: string; visitCode: string; assetId: string; assetCode: string; assetName: string; } @Injectable() export class DashboardService { constructor(private readonly dataSource: DataSource) {} async summary() { return this.dataSource.transaction('REPEATABLE READ', async (manager) => { const [counts] = (await manager.query(` SELECT (SELECT COUNT(*) FROM assets WHERE is_inventory_instance=true AND information_status <> 'INACTIVE') AS "totalAssets", (SELECT COUNT(*) FROM assets WHERE is_inventory_instance=true AND information_status NOT IN ('VALIDATED','INACTIVE')) AS "assetsNeedValidation", ( SELECT COUNT(*) FROM assets asset WHERE asset.is_inventory_instance=true AND asset.information_status <> 'INACTIVE' AND NOT EXISTS (SELECT 1 FROM asset_geometries geometry WHERE geometry.asset_id=asset.id) ) AS "assetsWithoutGeometry", (SELECT COUNT(*) FROM inspection_visits WHERE status='PLANNED') AS "plannedInspections", (SELECT COUNT(*) FROM users WHERE status = 'ACTIVE') AS "activeUsers", (SELECT COUNT(*) FROM users WHERE status = 'INACTIVE') AS "inactiveUsers", ( SELECT COUNT(*) FROM auth_sessions WHERE revoked_at IS NULL AND expires_at > CURRENT_TIMESTAMP ) AS "activeSessions", (SELECT COUNT(*) FROM inspection_findings WHERE status = 'OPEN') AS "openFindings", ( SELECT COUNT(*) FROM inspection_acts act WHERE act.status IN ('SEALED','CLOSED','RECTIFIED') AND NOT ( EXISTS ( SELECT 1 FROM inspection_findings finding WHERE finding.act_id=act.id AND finding.status<>'VOIDED' ) AND NOT EXISTS ( SELECT 1 FROM inspection_findings finding WHERE finding.act_id=act.id AND finding.status='OPEN' ) ) ) AS "actsInFollowUp", ( SELECT COUNT(*) FROM inspection_findings finding WHERE finding.status = 'OPEN' AND finding.next_control_on IS NULL AND COALESCE(( SELECT verification.outcome FROM inspection_finding_verification_visits verification WHERE verification.finding_id=finding.id AND verification.outcome IS NOT NULL ORDER BY verification.result_recorded_at DESC NULLS LAST, verification.created_at DESC, verification.id DESC LIMIT 1 ), '') <> 'RESOLVED' ) AS "findingsWithoutControlDate", ( SELECT COUNT(*) FROM inspection_findings finding WHERE finding.status = 'OPEN' AND finding.next_control_on < (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date AND COALESCE(( SELECT verification.outcome FROM inspection_finding_verification_visits verification WHERE verification.finding_id=finding.id AND verification.outcome IS NOT NULL ORDER BY verification.result_recorded_at DESC NULLS LAST, verification.created_at DESC, verification.id DESC LIMIT 1 ), '') <> 'RESOLVED' ) AS "overdueControls", ( SELECT COUNT(*) FROM inspection_findings finding WHERE finding.status = 'OPEN' AND finding.next_control_on BETWEEN (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date AND (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date + 30 AND COALESCE(( SELECT verification.outcome FROM inspection_finding_verification_visits verification WHERE verification.finding_id=finding.id AND verification.outcome IS NOT NULL ORDER BY verification.result_recorded_at DESC NULLS LAST, verification.created_at DESC, verification.id DESC LIMIT 1 ), '') <> 'RESOLVED' ) AS "controlsNext30Days", (SELECT COUNT(*) FROM inspection_reports WHERE status='WORKING') AS "reportsWorking", (SELECT COUNT(*) FROM inspection_reports WHERE status='OFFICIALIZED') AS "reportsOfficialized", ( SELECT COUNT(*) FROM inspection_acts act WHERE act.status='SEALED' AND NOT EXISTS (SELECT 1 FROM inspection_reports report WHERE report.act_id=act.id) ) AS "sealedActsWithoutReport" `)) as DashboardCountsRow[]; const recentInspectorActivity = (await manager.query(` SELECT event.id, event.occurred_at AS "occurredAt", event.actor_username AS "actorUsername", event.action, event.entity_type AS "entityType", event.entity_id AS "entityId" FROM audit_events event WHERE event.actor_user_id IS NOT NULL AND ( EXISTS ( SELECT 1 FROM inspection_visit_members member WHERE member.user_id=event.actor_user_id AND member.included=true ) OR EXISTS ( SELECT 1 FROM inspection_visits visit WHERE visit.lead_inspector_user_id=event.actor_user_id ) ) AND ( event.action LIKE 'INSPECTION_%' OR event.action LIKE 'ASSET_%' OR event.action LIKE 'FINDING_%' ) ORDER BY event.occurred_at DESC, event.id DESC LIMIT 8 `)) as DashboardInspectorActivityItem[]; const upcomingControls = (await manager.query(` SELECT finding.id, finding.code, finding.title, finding.next_control_on AS "nextControlOn", act.id AS "actId", act.code AS "actCode", visit.id AS "visitId", visit.code AS "visitCode", asset.id AS "assetId", asset.code AS "assetCode", asset.name AS "assetName" FROM inspection_findings finding INNER JOIN inspection_acts act ON act.id = finding.act_id INNER JOIN inspection_visits visit ON visit.id = act.visit_id INNER JOIN assets asset ON asset.id = finding.asset_id WHERE finding.status = 'OPEN' AND finding.next_control_on IS NOT NULL AND COALESCE(( SELECT verification.outcome FROM inspection_finding_verification_visits verification WHERE verification.finding_id=finding.id AND verification.outcome IS NOT NULL ORDER BY verification.result_recorded_at DESC NULLS LAST, verification.created_at DESC, verification.id DESC LIMIT 1 ), '') <> 'RESOLVED' ORDER BY finding.next_control_on, finding.code LIMIT 8 `)) as DashboardUpcomingControl[]; return { counts: { totalAssets: Number(counts?.totalAssets ?? 0), assetsNeedValidation: Number(counts?.assetsNeedValidation ?? 0), assetsWithoutGeometry: Number(counts?.assetsWithoutGeometry ?? 0), plannedInspections: Number(counts?.plannedInspections ?? 0), activeUsers: Number(counts?.activeUsers ?? 0), inactiveUsers: Number(counts?.inactiveUsers ?? 0), activeSessions: Number(counts?.activeSessions ?? 0), openFindings: Number(counts?.openFindings ?? 0), actsInFollowUp: Number(counts?.actsInFollowUp ?? 0), findingsWithoutControlDate: Number(counts?.findingsWithoutControlDate ?? 0), overdueControls: Number(counts?.overdueControls ?? 0), controlsNext30Days: Number(counts?.controlsNext30Days ?? 0), reportsWorking: Number(counts?.reportsWorking ?? 0), reportsOfficialized: Number(counts?.reportsOfficialized ?? 0), sealedActsWithoutReport: Number(counts?.sealedActsWithoutReport ?? 0), }, recentInspectorActivity, upcomingControls, generatedAt: new Date().toISOString(), }; }); } }