From f8ddd7263adec0d3fada07f6dd2ee3a4939e127c Mon Sep 17 00:00:00 2001 From: enlineawork Date: Thu, 10 Sep 2026 13:00:39 -0300 Subject: [PATCH] fix(F6.1): validate yacimiento inspection scope --- .../inspection-planning-scope.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 api-v3/src/inspection-visits/inspection-planning-scope.ts diff --git a/api-v3/src/inspection-visits/inspection-planning-scope.ts b/api-v3/src/inspection-visits/inspection-planning-scope.ts new file mode 100644 index 0000000..8d8202d --- /dev/null +++ b/api-v3/src/inspection-visits/inspection-planning-scope.ts @@ -0,0 +1,35 @@ +import { BadRequestException } from '@nestjs/common'; +import type { EntityManager } from 'typeorm'; + +/** + * F6.1 keeps territorial levels explicit. operationalAreaId freezes the Area + * context while scopeAssetId identifies the Yacimiento inspected inside it. + * Legacy/mobile callers may still use the Area itself as scope until Android + * supplies a Yacimiento explicitly. + */ +export async function validateInspectionPlanningScope( + manager: EntityManager, + operationalAreaId: string, + scopeAssetId: string, +): Promise { + if (scopeAssetId === operationalAreaId) return; + + const [scope] = await manager.query(` + SELECT 1 + FROM assets yacimiento + INNER JOIN asset_types type ON type.id=yacimiento.asset_type_id + WHERE yacimiento.id=$1::uuid + AND yacimiento.parent_id=$2::uuid + AND lower(type.code)='yacimiento' + AND type.is_active=true + AND yacimiento.information_status<>'INACTIVE' + LIMIT 1 + `, [scopeAssetId, operationalAreaId]) as Array<{ '?column?': number }>; + + if (!scope) { + throw new BadRequestException({ + code: 'INSPECTION_YACIMIENTO_OUTSIDE_AREA', + message: 'El Yacimiento seleccionado no pertenece al Área indicada', + }); + } +}