F4: isolate technical inspection checklist and public visit model
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
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 histórico de Inspecciones.
|
||||||
|
*
|
||||||
|
* Mantiene las operaciones de planificación/equipo/Inventario ya probadas, pero
|
||||||
|
* elimina de la salida funcional los conceptos retirados en F4:
|
||||||
|
* - no existe un título de negocio independiente del código institucional;
|
||||||
|
* - no existe fecha prevista de fin;
|
||||||
|
* - el checklist es exclusivamente técnico y nunca depende de respuestas de empresa.
|
||||||
|
*
|
||||||
|
* Las columnas legacy permanecen temporalmente en PostgreSQL para una migración de
|
||||||
|
* limpieza posterior a la validación del piloto, sin intervenir datos históricos a ciegas.
|
||||||
|
*/
|
||||||
|
@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.normalizeVisit(visit)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
override async getById(id: string): Promise<InspectionVisitView> {
|
||||||
|
await this.reclassifyCurrentChecklist(id);
|
||||||
|
return this.normalizeVisit(await super.getById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
override async create(
|
||||||
|
dto: CreateInspectionVisitDto,
|
||||||
|
principal: AuthPrincipal,
|
||||||
|
request: RequestWithContext,
|
||||||
|
): Promise<InspectionVisitView> {
|
||||||
|
const created = await super.create(dto, principal, request);
|
||||||
|
await this.reclassifyCurrentChecklist(created.id);
|
||||||
|
return this.normalizeVisit(await super.getById(created.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
override async generateChecklist(
|
||||||
|
id: string,
|
||||||
|
principal: AuthPrincipal,
|
||||||
|
request: RequestWithContext,
|
||||||
|
): Promise<InspectionVisitView> {
|
||||||
|
await super.generateChecklist(id, principal, request);
|
||||||
|
await this.reclassifyCurrentChecklist(id);
|
||||||
|
return this.normalizeVisit(await super.getById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeVisit<T extends { code: string; title: string; plannedEndAt: Date | null }>(visit: T): T {
|
||||||
|
const normalized = {
|
||||||
|
...visit,
|
||||||
|
// Compatibilidad de contrato: consumidores antiguos reciben el identificador,
|
||||||
|
// no un segundo nombre persistente que pueda divergir.
|
||||||
|
title: visit.code,
|
||||||
|
plannedEndAt: null,
|
||||||
|
} as T;
|
||||||
|
|
||||||
|
if ('checklist' in normalized) {
|
||||||
|
const view = normalized as T & InspectionVisitView;
|
||||||
|
view.checklist = {
|
||||||
|
...view.checklist,
|
||||||
|
companyOverdue: 0,
|
||||||
|
items: view.checklist.items.filter((item) => item.itemKind !== 'COMPANY_OVERDUE'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reclassifyCurrentChecklist(visitId: string): Promise<void> {
|
||||||
|
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]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user