import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { AuditService } from '../audit/audit.service'; import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context'; import type { CreateInspectionVisitDto } from './dto/create-inspection-visit.dto'; import type { ListInspectionVisitsQueryDto } from './dto/list-inspection-visits-query.dto'; import type { InspectionVisitView } from './inspection-visits.service'; import { InspectionVisitsService } from './inspection-visits.service'; /** * Capa activa F4 sobre el servicio de Inspecciones. * * Conserva las operaciones de planificación/equipo/Inventario ya probadas y aplica * la clasificación técnica F4 del checklist. El contrato público de Inspección ya * no expone título independiente ni fecha prevista de fin. */ @Injectable() export class F4InspectionVisitsService extends InspectionVisitsService { constructor( private readonly f4DataSource: DataSource, audit: AuditService, ) { super(f4DataSource, audit); } override async list(query: ListInspectionVisitsQueryDto) { const response = await super.list(query); return { ...response, data: response.data.map((visit) => this.normalizeChecklist(visit)), }; } override async getById(id: string): Promise { await this.reclassifyCurrentChecklist(id); return this.normalizeChecklist(await super.getById(id)); } override async create( dto: CreateInspectionVisitDto, principal: AuthPrincipal, request: RequestWithContext, ): Promise { const created = await super.create(dto, principal, request); await this.reclassifyCurrentChecklist(created.id); return this.normalizeChecklist(await super.getById(created.id)); } override async generateChecklist( id: string, principal: AuthPrincipal, request: RequestWithContext, ): Promise { await super.generateChecklist(id, principal, request); await this.reclassifyCurrentChecklist(id); return this.normalizeChecklist(await super.getById(id)); } private normalizeChecklist(visit: T): T { if (!visit || typeof visit !== 'object' || !('checklist' in visit)) return visit; const view = visit as T & InspectionVisitView; view.checklist = { ...view.checklist, companyOverdue: 0, items: view.checklist.items.filter((item) => item.itemKind !== 'COMPANY_OVERDUE'), }; return view; } private async reclassifyCurrentChecklist(visitId: string): Promise { await this.f4DataSource.transaction(async (manager) => { const [visit] = await manager.query(` SELECT checklist_generation AS generation, planned_start_at AS "plannedStartAt" FROM inspection_visits WHERE id=$1 FOR UPDATE `, [visitId]) as Array<{ generation: number; plannedStartAt: Date | null }>; if (!visit || Number(visit.generation) < 1 || !visit.plannedStartAt) return; const targetDate = new Date(visit.plannedStartAt).toISOString().slice(0, 10); await manager.query(` UPDATE inspection_visit_checklist_items item SET item_kind = CASE WHEN finding.status='OPEN' AND finding.next_control_on IS NOT NULL AND finding.next_control_on < $3::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' THEN 'VERIFICATION_OVERDUE' WHEN finding.status='OPEN' AND finding.next_control_on IS NOT NULL AND finding.next_control_on BETWEEN $3::date AND ($3::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' THEN 'UPCOMING_CONTROL' ELSE 'ANTECEDENT' END, reference_on = CASE WHEN 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' THEN finding.next_control_on ELSE NULL END FROM inspection_findings finding WHERE item.visit_id=$1 AND item.generation_number=$2 AND finding.id=item.finding_id `, [visitId, visit.generation, targetDate]); }); } }