chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { RequirePermissions } from '../authorization/decorators/require-permissions.decorator';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
@Controller('dashboard')
|
||||
export class DashboardController {
|
||||
constructor(private readonly dashboard: DashboardService) {}
|
||||
|
||||
@Get('summary')
|
||||
@RequirePermissions('dashboard.read')
|
||||
summary() {
|
||||
return this.dashboard.summary();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DashboardController } from './dashboard.controller';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
@Module({
|
||||
controllers: [DashboardController],
|
||||
providers: [DashboardService],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
export interface DashboardAuditItem {
|
||||
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;
|
||||
awaitingCompanyResponse: number | string;
|
||||
overdueCompanyResponses: number | string;
|
||||
companyResponsesDueNext7Days: number | string;
|
||||
awaitingVerificationSchedule: number | string;
|
||||
overdueControls: number | string;
|
||||
controlsNext30Days: 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 information_status <> 'INACTIVE') AS "totalAssets",
|
||||
(SELECT COUNT(*) FROM assets WHERE information_status NOT IN ('VALIDATED','INACTIVE')) AS "assetsNeedValidation",
|
||||
(SELECT COUNT(*) FROM assets asset WHERE 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_findings
|
||||
WHERE status = 'OPEN' AND company_response_received_on IS NULL
|
||||
) AS "awaitingCompanyResponse",
|
||||
(
|
||||
SELECT COUNT(*) FROM inspection_findings
|
||||
WHERE status = 'OPEN'
|
||||
AND company_response_received_on IS NULL
|
||||
AND correction_due_on IS NOT NULL
|
||||
AND correction_due_on < (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date
|
||||
) AS "overdueCompanyResponses",
|
||||
(
|
||||
SELECT COUNT(*) FROM inspection_findings
|
||||
WHERE status = 'OPEN'
|
||||
AND company_response_received_on IS NULL
|
||||
AND correction_due_on BETWEEN
|
||||
(CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date
|
||||
AND (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date + 7
|
||||
) AS "companyResponsesDueNext7Days",
|
||||
(
|
||||
SELECT COUNT(*) FROM inspection_findings
|
||||
WHERE status = 'OPEN'
|
||||
AND company_response_received_on IS NOT NULL
|
||||
AND next_control_on IS NULL
|
||||
) AS "awaitingVerificationSchedule",
|
||||
(
|
||||
SELECT COUNT(*) FROM inspection_findings
|
||||
WHERE status = 'OPEN'
|
||||
AND company_response_received_on IS NOT NULL
|
||||
AND next_control_on < (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date
|
||||
) AS "overdueControls",
|
||||
(
|
||||
SELECT COUNT(*) FROM inspection_findings
|
||||
WHERE status = 'OPEN'
|
||||
AND next_control_on BETWEEN
|
||||
(CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date
|
||||
AND (CURRENT_TIMESTAMP AT TIME ZONE 'America/Argentina/Mendoza')::date + 30
|
||||
) AS "controlsNext30Days"
|
||||
`)) as DashboardCountsRow[];
|
||||
|
||||
const recentAudit = (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
|
||||
ORDER BY event.occurred_at DESC, event.id DESC
|
||||
LIMIT 6
|
||||
`)) as DashboardAuditItem[];
|
||||
|
||||
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.company_response_received_on IS NOT NULL
|
||||
AND finding.next_control_on IS NOT NULL
|
||||
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),
|
||||
awaitingCompanyResponse: Number(counts?.awaitingCompanyResponse ?? 0),
|
||||
overdueCompanyResponses: Number(counts?.overdueCompanyResponses ?? 0),
|
||||
companyResponsesDueNext7Days: Number(counts?.companyResponsesDueNext7Days ?? 0),
|
||||
awaitingVerificationSchedule: Number(counts?.awaitingVerificationSchedule ?? 0),
|
||||
overdueControls: Number(counts?.overdueControls ?? 0),
|
||||
controlsNext30Days: Number(counts?.controlsNext30Days ?? 0),
|
||||
},
|
||||
recentAudit,
|
||||
upcomingControls,
|
||||
generatedAt: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user