From 5738306a14989d0101b76153a7a50f3a253486be Mon Sep 17 00:00:00 2001 From: enlineawork Date: Wed, 9 Sep 2026 15:09:04 -0300 Subject: [PATCH] fix(f6.1): resolve verification operator from temporal area relation --- .../inspection-verifications.service.ts | 73 ++++++++++++++----- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/api-v3/src/inspection-verifications/inspection-verifications.service.ts b/api-v3/src/inspection-verifications/inspection-verifications.service.ts index a2fcdcc..ab32365 100644 --- a/api-v3/src/inspection-verifications/inspection-verifications.service.ts +++ b/api-v3/src/inspection-verifications/inspection-verifications.service.ts @@ -66,9 +66,6 @@ interface LockedFindingRow { assetId: string; assetCode: string; assetName: string; - companyId: string | null; - companyCode: string | null; - companyName: string | null; areaId: string | null; areaCode: string | null; areaName: string | null; @@ -76,6 +73,12 @@ interface LockedFindingRow { status: string; } +interface PlannedOperatorRow { + companyId: string; + companyCode: string; + companyName: string; +} + @Injectable() export class InspectionVerificationsService { constructor( @@ -142,8 +145,19 @@ export class InspectionVerificationsService { INNER JOIN inspection_visits source_visit ON source_visit.id = act.visit_id INNER JOIN assets asset ON asset.id = finding.asset_id INNER JOIN asset_types asset_type ON asset_type.id = asset.asset_type_id - LEFT JOIN assets company ON company.id = asset.operator_company_id LEFT JOIN assets area ON area.id = asset.operational_area_id + LEFT JOIN LATERAL ( + SELECT operator_company.id,operator_company.code,operator_company.name + FROM area_company_relations relation + INNER JOIN assets operator_company ON operator_company.id=relation.company_id + WHERE relation.area_id=asset.operational_area_id + AND relation.relation_role='OPERATOR' + AND relation.valid_from <= CURRENT_TIMESTAMP + AND (relation.valid_until IS NULL OR relation.valid_until > CURRENT_TIMESTAMP) + AND operator_company.information_status<>'INACTIVE' + ORDER BY relation.valid_from DESC,relation.created_at DESC,relation.id DESC + LIMIT 1 + ) company ON true ${activeVisitJoin} `; @@ -495,15 +509,11 @@ export class InspectionVerificationsService { finding.next_control_on AS "nextControlOn", asset.code AS "assetCode", asset.name AS "assetName", - company.id AS "companyId", - company.code AS "companyCode", - company.name AS "companyName", area.id AS "areaId", area.code AS "areaCode", area.name AS "areaName" FROM inspection_findings finding INNER JOIN assets asset ON asset.id = finding.asset_id - LEFT JOIN assets company ON company.id = asset.operator_company_id LEFT JOIN assets area ON area.id = asset.operational_area_id WHERE finding.id = ANY($1::uuid[]) FOR UPDATE OF finding @@ -519,25 +529,51 @@ export class InspectionVerificationsService { message: `${invalid.code} necesita estar abierto y tener una fecha de control para planificar su verificación`, }); } - const missingContext = rows.find((row) => !row.companyId || !row.areaId); - if (missingContext) { + const missingArea = rows.find((row) => !row.areaId); + if (missingArea) { throw new BadRequestException({ code: 'VERIFICATION_CONTEXT_REQUIRED', - message: `${missingContext.code} necesita empresa y área operativa antes de planificar la visita`, + message: `${missingArea.code} necesita un Área física antes de planificar la visita`, }); } - const companyId = rows[0]?.companyId; const areaId = rows[0]?.areaId; - if (!companyId || !areaId) { - throw new BadRequestException({ code: 'VERIFICATION_CONTEXT_REQUIRED', message: 'Los hallazgos necesitan empresa y área operativa' }); + if (!areaId) { + throw new BadRequestException({ code: 'VERIFICATION_CONTEXT_REQUIRED', message: 'Los hallazgos necesitan un Área física' }); } - if (rows.some((row) => row.companyId !== companyId || row.areaId !== areaId)) { + if (rows.some((row) => row.areaId !== areaId)) { throw new BadRequestException({ code: 'VERIFICATION_CONTEXT_MIXED', - message: 'Seleccioná hallazgos de una misma empresa y área para crear una visita de verificación', + message: 'Seleccioná hallazgos de una misma Área para crear una visita de verificación', }); } + // A verification visit is a new operational act. Its operator is therefore + // the Company valid for the Area at the planned visit date, never the + // historical compatibility snapshot stored on the Inventory. + const [operator] = await manager.query(` + SELECT + company.id AS "companyId", + company.code AS "companyCode", + company.name AS "companyName" + FROM area_company_relations relation + INNER JOIN assets company ON company.id=relation.company_id + WHERE relation.area_id=$1::uuid + AND relation.relation_role='OPERATOR' + AND relation.valid_from <= $2::timestamptz + AND (relation.valid_until IS NULL OR relation.valid_until > $2::timestamptz) + AND company.information_status<>'INACTIVE' + ORDER BY relation.valid_from DESC,relation.created_at DESC,relation.id DESC + LIMIT 1 + FOR SHARE OF relation + `, [areaId, plannedStartAt]) as PlannedOperatorRow[]; + if (!operator) { + throw new ConflictException({ + code: 'VERIFICATION_OPERATOR_NOT_ACTIVE', + message: 'El Área no tiene una Operadora vigente para la fecha prevista de la verificación', + }); + } + const companyId = operator.companyId; + const activeLinks = await manager.query(` SELECT verification_link.finding_id AS "findingId", visit.code FROM inspection_finding_verification_visits verification_link @@ -552,7 +588,7 @@ export class InspectionVerificationsService { }); } - const companyName = rows[0]?.companyName ?? 'Empresa'; + const companyName = operator.companyName; const areaName = rows[0]?.areaName ?? 'Área'; const code = await nextInspectionVisitCode(manager, plannedStartAt); const findingCodes = rows.map((row) => row.code).join(', '); @@ -637,6 +673,7 @@ export class InspectionVerificationsService { areaId, findingIds, assetIds, + operatorSource: 'area_company_relations', }, }, manager); @@ -647,7 +684,7 @@ export class InspectionVerificationsService { status: visit.status, plannedStartAt: visit.plannedStartAt, }, - company: { id: companyId, code: rows[0]?.companyCode ?? '', name: companyName }, + company: { id: companyId, code: operator.companyCode, name: companyName }, area: { id: areaId, code: rows[0]?.areaCode ?? '', name: areaName }, findingCount: findingIds.length, assetCount: assetIds.length,