fix(api): validate inspection operator against Yacimiento

This commit is contained in:
2026-09-11 01:06:59 -03:00
parent 99f068b416
commit a98dfb955d
@@ -10,9 +10,9 @@ import { validateInspectionPlanningScope } from './inspection-planning-scope';
import { InspectionVisitsService } from './inspection-visits.service'; import { InspectionVisitsService } from './inspection-visits.service';
/** /**
* Creación WEB F6.1. Separa explícitamente ubicación territorial y alcance: * Creación WEB. La ubicación territorial es Departamento → Área → Yacimiento.
* Departamento → Área → Yacimiento. La Operadora no es un padre físico; se * Empresa y Tipo de concesión pertenecen directamente al Yacimiento; por eso
* valida contra la relación temporal vigente del Área para la fecha planificada. * la Operadora elegida debe coincidir con el Yacimiento seleccionado.
*/ */
@Injectable() @Injectable()
export class InspectionPlanningCreateService { export class InspectionPlanningCreateService {
@@ -52,27 +52,27 @@ export class InspectionPlanningCreateService {
stage = 'operator:validate'; stage = 'operator:validate';
const [context] = await manager.query(` const [context] = await manager.query(`
SELECT relation.id SELECT yacimiento.id
FROM area_company_relations relation FROM assets yacimiento
INNER JOIN assets area ON area.id=relation.area_id INNER JOIN asset_types yacimiento_type ON yacimiento_type.id=yacimiento.asset_type_id
INNER JOIN asset_types area_type ON area_type.id=area.asset_type_id INNER JOIN assets company ON company.id=yacimiento.operator_company_id
INNER JOIN assets company ON company.id=relation.company_id
INNER JOIN asset_types company_type ON company_type.id=company.asset_type_id INNER JOIN asset_types company_type ON company_type.id=company.asset_type_id
WHERE relation.area_id=$1::uuid WHERE yacimiento.id=$1::uuid
AND relation.company_id=$2::uuid AND yacimiento.parent_id=$2::uuid
AND relation.relation_role='OPERATOR' AND yacimiento.operator_company_id=$3::uuid
AND relation.valid_from <= $3::timestamptz AND lower(yacimiento_type.code)='yacimiento'
AND (relation.valid_until IS NULL OR relation.valid_until > $3::timestamptz) AND yacimiento_type.is_active=true
AND area_type.operational_role='AREA' AND yacimiento.information_status<>'INACTIVE'
AND yacimiento.concession_type_id IS NOT NULL
AND company_type.operational_role='COMPANY' AND company_type.operational_role='COMPANY'
AND area.information_status<>'INACTIVE' AND company_type.is_active=true
AND company.information_status<>'INACTIVE' AND company.information_status<>'INACTIVE'
LIMIT 1 LIMIT 1
`, [dto.operationalAreaId, dto.operatorCompanyId, plannedStartAt]) as Array<{ id: string }>; `, [scopeAssetId, dto.operationalAreaId, dto.operatorCompanyId]) as Array<{ id: string }>;
if (!context) { if (!context) {
throw new BadRequestException({ throw new BadRequestException({
code: 'INSPECTION_OPERATOR_NOT_ACTIVE_IN_AREA', code: 'INSPECTION_OPERATOR_NOT_RELATED_TO_YACIMIENTO',
message: 'La Operadora seleccionada no está vigente para el Área en la fecha de la Inspección', message: 'La Empresa seleccionada no es la Empresa relacionada con el Yacimiento',
}); });
} }
@@ -129,8 +129,6 @@ export class InspectionPlanningCreateService {
VALUES ($1::uuid,$2::uuid,true,$3::uuid) VALUES ($1::uuid,$2::uuid,true,$3::uuid)
`, [visit.id, dto.leadInspectorUserId, principal.userId]); `, [visit.id, dto.leadInspectorUserId, principal.userId]);
// El checklist sigue el alcance físico del Yacimiento. No usa
// assets.operator_company_id, que en F6 es sólo snapshot histórico.
stage = 'checklist:insert'; stage = 'checklist:insert';
await manager.query(` await manager.query(`
INSERT INTO inspection_visit_checklist_items ( INSERT INTO inspection_visit_checklist_items (
@@ -153,6 +151,7 @@ export class InspectionPlanningCreateService {
FROM inspection_findings finding FROM inspection_findings finding
INNER JOIN assets inventory ON inventory.id=finding.asset_id INNER JOIN assets inventory ON inventory.id=finding.asset_id
WHERE inventory.operational_area_id=$2::uuid WHERE inventory.operational_area_id=$2::uuid
AND inventory.operator_company_id=$5::uuid
AND finding.status<>'VOIDED' AND finding.status<>'VOIDED'
AND EXISTS ( AND EXISTS (
WITH RECURSIVE ancestors AS ( WITH RECURSIVE ancestors AS (
@@ -165,7 +164,7 @@ export class InspectionPlanningCreateService {
SELECT 1 FROM ancestors WHERE id=$3::uuid LIMIT 1 SELECT 1 FROM ancestors WHERE id=$3::uuid LIMIT 1
) )
ORDER BY finding.created_at,finding.id ORDER BY finding.created_at,finding.id
`, [visit.id, dto.operationalAreaId, scopeAssetId, plannedStartAt.toISOString().slice(0, 10)]); `, [visit.id, dto.operationalAreaId, scopeAssetId, plannedStartAt.toISOString().slice(0, 10), dto.operatorCompanyId]);
stage = 'checklist:assets'; stage = 'checklist:assets';
await manager.query(` await manager.query(`
@@ -197,7 +196,7 @@ export class InspectionPlanningCreateService {
) )
SELECT DISTINCT SELECT DISTINCT
$1::uuid, item.asset_id, 'AUTO_INCLUDED', NULL, $2::uuid, $1::uuid, item.asset_id, 'AUTO_INCLUDED', NULL, $2::uuid,
jsonb_build_object('generation',1,'source','F6.1_CREATE') jsonb_build_object('generation',1,'source','AUTHORITATIVE_CREATE')
FROM inspection_visit_checklist_items item FROM inspection_visit_checklist_items item
WHERE item.visit_id=$1::uuid WHERE item.visit_id=$1::uuid
AND item.generation_number=1 AND item.generation_number=1
@@ -224,6 +223,7 @@ export class InspectionPlanningCreateService {
operationalAreaId: dto.operationalAreaId, operationalAreaId: dto.operationalAreaId,
scopeAssetId, scopeAssetId,
operatorCompanyId: dto.operatorCompanyId, operatorCompanyId: dto.operatorCompanyId,
operatorSource: 'YACIMIENTO',
plannedStartAt: plannedStartAt.toISOString(), plannedStartAt: plannedStartAt.toISOString(),
leadInspectorUserId: dto.leadInspectorUserId, leadInspectorUserId: dto.leadInspectorUserId,
}, },
@@ -243,7 +243,7 @@ export class InspectionPlanningCreateService {
const technical = error instanceof Error const technical = error instanceof Error
? `${error.name}: ${error.message}${error.stack ? `\n${error.stack}` : ''}` ? `${error.name}: ${error.message}${error.stack ? `\n${error.stack}` : ''}`
: String(error); : String(error);
this.logger.error(`F6.1 inspection create failed at ${stage}: ${technical}`); this.logger.error(`inspection create failed at ${stage}: ${technical}`);
throw error; throw error;
} }
} }