fix(android): scope mobile inspections to yacimiento
DH V2 CI / API · typecheck, tests, build (push) Successful in 5m16s
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Failing after 6m13s
DH V2 CI / WEB · typecheck, build (push) Successful in 4m52s
DH V2 CI / Docker / migrations / production images (push) Skipped
DH V2 CI / Promote verified main to deploy (push) Skipped

This commit is contained in:
2026-09-16 07:33:03 -03:00
parent 29208f4e1f
commit c71afab9af
18 changed files with 287 additions and 70 deletions
@@ -1,9 +1,15 @@
import { IsUUID } from 'class-validator';
export class OpenMobileInspectionDto {
@IsUUID('4')
departmentId!: string;
@IsUUID('4')
operationalAreaId!: string;
@IsUUID('4')
scopeAssetId!: string;
@IsUUID('4')
operatorCompanyId!: string;
}
@@ -62,6 +62,67 @@ export class InspectionPlanningHierarchyService {
return { data };
}
async operatorsForYacimiento(yacimientoId: string): Promise<{ data: InspectionPlanningHierarchyItem[] }> {
await this.requireType(yacimientoId, 'yacimiento', 'El Yacimiento seleccionado no es válido');
const data = await this.dataSource.query(`
SELECT company.id, company.code, COALESCE(profile.legal_name,company.name) AS name
FROM assets yacimiento
INNER JOIN assets company ON company.id=yacimiento.operator_company_id
INNER JOIN asset_types company_type ON company_type.id=company.asset_type_id
LEFT JOIN organization_profiles profile ON profile.asset_id=company.id
WHERE yacimiento.id=$1::uuid
AND yacimiento.information_status<>'INACTIVE'
AND company_type.operational_role='COMPANY'
AND company_type.is_active=true
AND company.information_status<>'INACTIVE'
ORDER BY name, company.code
`, [yacimientoId]) as InspectionPlanningHierarchyItem[];
return { data };
}
async validateMobileSelection(
departmentId: string,
areaId: string,
yacimientoId: string,
companyId: string,
): Promise<void> {
const [row] = await this.dataSource.query(`
SELECT 1
FROM assets department
INNER JOIN asset_types department_type ON department_type.id=department.asset_type_id
INNER JOIN assets area ON area.parent_id=department.id
INNER JOIN asset_types area_type ON area_type.id=area.asset_type_id
INNER JOIN assets yacimiento ON yacimiento.parent_id=area.id
INNER JOIN asset_types yacimiento_type ON yacimiento_type.id=yacimiento.asset_type_id
INNER JOIN assets company ON company.id=yacimiento.operator_company_id
INNER JOIN asset_types company_type ON company_type.id=company.asset_type_id
WHERE department.id=$1::uuid
AND area.id=$2::uuid
AND yacimiento.id=$3::uuid
AND company.id=$4::uuid
AND lower(department_type.code)='departamento'
AND lower(area_type.code)='area'
AND lower(yacimiento_type.code)='yacimiento'
AND department_type.is_active=true
AND area_type.is_active=true
AND yacimiento_type.is_active=true
AND company_type.operational_role='COMPANY'
AND company_type.is_active=true
AND department.information_status<>'INACTIVE'
AND area.information_status<>'INACTIVE'
AND yacimiento.information_status<>'INACTIVE'
AND company.information_status<>'INACTIVE'
AND yacimiento.concession_type_id IS NOT NULL
LIMIT 1
`, [departmentId, areaId, yacimientoId, companyId]) as Array<{ '?column?': number }>;
if (!row) {
throw new BadRequestException({
code: 'INSPECTION_MOBILE_CONTEXT_INVALID',
message: 'La selección debe respetar Departamento → Área → Yacimiento → Operadora',
});
}
}
async operatorsForArea(
areaId: string,
at?: string,
@@ -87,21 +87,41 @@ export class InspectionVisitsController {
return this.planningHierarchy.operatorsForArea(areaId, at);
}
@Get('mobile/planning-context/areas')
@Get('mobile/planning-context/departments')
@RequirePermissions('inspections.execute')
mobilePlanningAreas(@CurrentAuth() principal: AuthPrincipal) {
mobilePlanningDepartments(@CurrentAuth() principal: AuthPrincipal) {
assertMobileInspector(principal);
return this.visits.listPlanningAreas();
return this.planningHierarchy.departments();
}
@Get('mobile/planning-context/areas/:areaId/operators')
@Get('mobile/planning-context/departments/:departmentId/areas')
@RequirePermissions('inspections.execute')
mobilePlanningOperators(
mobilePlanningAreasForDepartment(
@Param('departmentId', new ParseUUIDPipe({ version: '4' })) departmentId: string,
@CurrentAuth() principal: AuthPrincipal,
) {
assertMobileInspector(principal);
return this.planningHierarchy.areasForDepartment(departmentId);
}
@Get('mobile/planning-context/areas/:areaId/yacimientos')
@RequirePermissions('inspections.execute')
mobilePlanningYacimientos(
@Param('areaId', new ParseUUIDPipe({ version: '4' })) areaId: string,
@CurrentAuth() principal: AuthPrincipal,
) {
assertMobileInspector(principal);
return this.visits.listPlanningOperators(areaId);
return this.planningHierarchy.yacimientosForArea(areaId);
}
@Get('mobile/planning-context/yacimientos/:yacimientoId/operators')
@RequirePermissions('inspections.execute')
mobilePlanningOperatorsForYacimiento(
@Param('yacimientoId', new ParseUUIDPipe({ version: '4' })) yacimientoId: string,
@CurrentAuth() principal: AuthPrincipal,
) {
assertMobileInspector(principal);
return this.planningHierarchy.operatorsForYacimiento(yacimientoId);
}
@Post('mobile/open')
@@ -112,10 +132,15 @@ export class InspectionVisitsController {
@Req() request: RequestWithContext,
) {
assertMobileInspector(principal);
// Android conserva por compatibilidad el alcance a nivel Área hasta que su
// flujo también solicite Yacimiento. No se mezcla con la creación WEB F6.1.
const created = await this.visits.create({
await this.planningHierarchy.validateMobileSelection(
dto.departmentId,
dto.operationalAreaId,
dto.scopeAssetId,
dto.operatorCompanyId,
);
const created = await this.planningCreate.create({
operationalAreaId: dto.operationalAreaId,
scopeAssetId: dto.scopeAssetId,
operatorCompanyId: dto.operatorCompanyId,
plannedStartAt: new Date().toISOString(),
leadInspectorUserId: principal.userId,
+2 -2
View File
@@ -1,2 +1,2 @@
export const API_VERSION = '0.29.0-13';
export const API_PHASE = 'F6.12';
export const API_VERSION = '0.29.0-14';
export const API_PHASE = 'F6.14';