F5.1: mostrar y navegar todo el Inventario

This commit is contained in:
2026-09-09 07:49:47 -03:00
parent d7557ef600
commit 4db6abb3c5
@@ -16,9 +16,9 @@ export class InventoryBrowserService {
async items(query: InventoryBrowserQueryDto) { async items(query: InventoryBrowserQueryDto) {
const params: unknown[] = []; const params: unknown[] = [];
const conditions = [ const conditions = [
'asset.is_inventory_instance=true',
"asset.information_status<>'INACTIVE'", "asset.information_status<>'INACTIVE'",
'type.is_active=true', 'type.is_active=true',
"(type.operational_role='COMPANY' OR lower(type.code) IN ('departamento','area','yacimiento','instalacion','subinstalacion'))",
]; ];
const add = (value: unknown): string => { const add = (value: unknown): string => {
params.push(value); params.push(value);
@@ -36,15 +36,21 @@ export class InventoryBrowserService {
if (query.needsValidation === false) conditions.push("asset.information_status='VALIDATED'"); if (query.needsValidation === false) conditions.push("asset.information_status='VALIDATED'");
if (query.hasGeometry === true) conditions.push('EXISTS (SELECT 1 FROM asset_geometries geometry_filter WHERE geometry_filter.asset_id=asset.id)'); if (query.hasGeometry === true) conditions.push('EXISTS (SELECT 1 FROM asset_geometries geometry_filter WHERE geometry_filter.asset_id=asset.id)');
if (query.hasGeometry === false) conditions.push('NOT EXISTS (SELECT 1 FROM asset_geometries geometry_filter WHERE geometry_filter.asset_id=asset.id)'); if (query.hasGeometry === false) conditions.push('NOT EXISTS (SELECT 1 FROM asset_geometries geometry_filter WHERE geometry_filter.asset_id=asset.id)');
if (query.operationalAreaId) conditions.push(`asset.operational_area_id=${add(query.operationalAreaId)}::uuid`); if (query.operationalAreaId) {
const area = add(query.operationalAreaId);
conditions.push(`(asset.id=${area}::uuid OR asset.operational_area_id=${area}::uuid)`);
}
if (query.operatorCompanyId) { if (query.operatorCompanyId) {
const company = add(query.operatorCompanyId); const company = add(query.operatorCompanyId);
conditions.push(`EXISTS ( conditions.push(`(
asset.id=${company}::uuid
OR EXISTS (
SELECT 1 FROM area_company_relations relation SELECT 1 FROM area_company_relations relation
WHERE relation.area_id=asset.operational_area_id WHERE (relation.area_id=asset.operational_area_id OR relation.area_id=asset.id)
AND relation.company_id=${company}::uuid AND relation.company_id=${company}::uuid
AND relation.relation_role='OPERATOR' AND relation.relation_role='OPERATOR'
AND relation.valid_until IS NULL AND relation.valid_until IS NULL
)
)`); )`);
} }
@@ -76,7 +82,8 @@ export class InventoryBrowserService {
SELECT JSONB_BUILD_OBJECT('id',company.id,'code',company.code,'name',company.name) SELECT JSONB_BUILD_OBJECT('id',company.id,'code',company.code,'name',company.name)
FROM area_company_relations relation FROM area_company_relations relation
JOIN assets company ON company.id=relation.company_id JOIN assets company ON company.id=relation.company_id
WHERE relation.area_id=asset.operational_area_id WHERE relation.area_id=COALESCE(asset.operational_area_id,
CASE WHEN type.operational_role='AREA' THEN asset.id ELSE NULL END)
AND relation.relation_role='OPERATOR' AND relation.relation_role='OPERATOR'
AND relation.valid_until IS NULL AND relation.valid_until IS NULL
ORDER BY relation.valid_from DESC,relation.created_at DESC ORDER BY relation.valid_from DESC,relation.created_at DESC
@@ -84,7 +91,8 @@ export class InventoryBrowserService {
) AS "operatorCompany", ) AS "operatorCompany",
asset.information_status AS "informationStatus", asset.information_status AS "informationStatus",
asset.operational_status AS "operationalStatus", asset.operational_status AS "operationalStatus",
0::integer AS "childrenCount", (SELECT COUNT(*)::integer FROM assets child
WHERE child.parent_id=asset.id AND child.information_status<>'INACTIVE') AS "childrenCount",
EXISTS (SELECT 1 FROM asset_geometries geometry WHERE geometry.asset_id=asset.id) AS "hasGeometry", EXISTS (SELECT 1 FROM asset_geometries geometry WHERE geometry.asset_id=asset.id) AS "hasGeometry",
CASE WHEN geometry_type.type IS NULL THEN NULL ELSE geometry_type.type END AS "geometryType", CASE WHEN geometry_type.type IS NULL THEN NULL ELSE geometry_type.type END AS "geometryType",
(SELECT COUNT(*)::integer FROM asset_media media WHERE media.asset_id=asset.id AND media.deleted_at IS NULL) AS "mediaCount", (SELECT COUNT(*)::integer FROM asset_media media WHERE media.asset_id=asset.id AND media.deleted_at IS NULL) AS "mediaCount",
@@ -104,7 +112,14 @@ export class InventoryBrowserService {
LIMIT 1 LIMIT 1
) geometry_type ON true ) geometry_type ON true
WHERE ${where} WHERE ${where}
ORDER BY asset.name,asset.code ORDER BY CASE
WHEN lower(type.code)='departamento' THEN 0
WHEN type.operational_role='COMPANY' THEN 1
WHEN lower(type.code)='area' THEN 2
WHEN lower(type.code)='yacimiento' THEN 3
WHEN lower(type.code)='instalacion' THEN 4
WHEN lower(type.code)='subinstalacion' THEN 5 ELSE 9 END,
asset.name,asset.code
LIMIT ${limit} OFFSET ${offsetParam} LIMIT ${limit} OFFSET ${offsetParam}
`,params); `,params);
@@ -119,28 +134,30 @@ export class InventoryBrowserService {
}; };
} }
async areas(query: InventoryBrowserQueryDto) { async departments(query: InventoryBrowserQueryDto) {
const params: unknown[] = []; const params: unknown[] = [];
const conditions = [ const conditions = [
"type.operational_role='AREA'", "lower(type.code)='departamento'",
"area.information_status<>'INACTIVE'", "department.information_status<>'INACTIVE'",
'type.is_active=true', 'type.is_active=true',
]; ];
const add = (value: unknown): string => { const add = (value: unknown): string => {
params.push(value); params.push(value);
return `$${params.length}`; return `$${params.length}`;
}; };
if (query.search?.trim()) { if (query.search?.trim()) {
const p = add(`%${query.search.trim()}%`); const p = add(`%${query.search.trim()}%`);
conditions.push(`(area.code ILIKE ${p} OR area.name ILIKE ${p} OR COALESCE(area.common_name,'') ILIKE ${p})`); conditions.push(`(department.code ILIKE ${p} OR department.name ILIKE ${p} OR COALESCE(department.common_name,'') ILIKE ${p})`);
} }
if (query.operationalAreaId) conditions.push(`area.id=${add(query.operationalAreaId)}::uuid`);
if (query.operatorCompanyId) { if (query.operatorCompanyId) {
const p = add(query.operatorCompanyId); const p = add(query.operatorCompanyId);
conditions.push(`EXISTS ( conditions.push(`EXISTS (
SELECT 1 FROM area_company_relations relation SELECT 1
WHERE relation.area_id=area.id FROM assets area
JOIN asset_types atype ON atype.id=area.asset_type_id
JOIN area_company_relations relation ON relation.area_id=area.id
WHERE area.parent_id=department.id
AND lower(atype.code)='area'
AND relation.company_id=${p}::uuid AND relation.company_id=${p}::uuid
AND relation.relation_role='OPERATOR' AND relation.relation_role='OPERATOR'
AND relation.valid_until IS NULL AND relation.valid_until IS NULL
@@ -149,41 +166,86 @@ export class InventoryBrowserService {
const data = await this.dataSource.query(` const data = await this.dataSource.query(`
SELECT SELECT
area.id,area.code,area.name,area.common_name AS "commonName", department.id,department.code,department.name,department.common_name AS "commonName",
JSONB_BUILD_OBJECT('id',type.id,'code',type.code,'name',type.name) AS type, JSONB_BUILD_OBJECT('id',type.id,'code',type.code,'name',type.name) AS type,
area.information_status AS "informationStatus", department.information_status AS "informationStatus",
area.operational_status AS "operationalStatus", department.operational_status AS "operationalStatus",
( (SELECT COUNT(*)::integer FROM assets area
SELECT JSONB_BUILD_OBJECT('id',company.id,'code',company.code,'name',company.name) JOIN asset_types atype ON atype.id=area.asset_type_id
FROM area_company_relations relation WHERE area.parent_id=department.id AND lower(atype.code)='area'
JOIN assets company ON company.id=relation.company_id AND area.information_status<>'INACTIVE') AS "areaCount",
WHERE relation.area_id=area.id (SELECT COUNT(*)::integer FROM assets child
AND relation.relation_role='OPERATOR' WHERE child.parent_id=department.id AND child.information_status<>'INACTIVE') AS "childrenCount"
AND relation.valid_until IS NULL FROM assets department
ORDER BY relation.valid_from DESC,relation.created_at DESC JOIN asset_types type ON type.id=department.asset_type_id
LIMIT 1 WHERE ${conditions.join(' AND ')}
) AS "currentOperator", ORDER BY department.name,department.code
( `, params);
SELECT COUNT(*)::integer return { data, meta: { count: data.length } };
FROM assets yacimiento }
JOIN asset_types ytype ON ytype.id=yacimiento.asset_type_id
WHERE yacimiento.parent_id=area.id async companies(query: InventoryBrowserQueryDto) {
AND lower(ytype.code)='yacimiento' const params: unknown[] = [];
AND yacimiento.information_status<>'INACTIVE' const conditions = [
) AS "yacimientoCount", "type.operational_role='COMPANY'",
( "company.information_status<>'INACTIVE'",
SELECT COUNT(*)::integer 'type.is_active=true',
FROM assets inventory ];
WHERE inventory.is_inventory_instance=true if (query.search?.trim()) {
AND inventory.information_status<>'INACTIVE' params.push(`%${query.search.trim()}%`);
AND inventory.operational_area_id=area.id conditions.push(`(company.code ILIKE $1 OR company.name ILIKE $1 OR COALESCE(company.common_name,'') ILIKE $1)`);
) AS "inventoryCount" }
FROM assets area const data = await this.dataSource.query(`
JOIN asset_types type ON type.id=area.asset_type_id SELECT company.id,company.code,company.name,company.common_name AS "commonName",
JSONB_BUILD_OBJECT('id',type.id,'code',type.code,'name',type.name) AS type,
company.information_status AS "informationStatus",
(SELECT COUNT(*)::integer FROM area_company_relations relation
WHERE relation.company_id=company.id AND relation.relation_role='OPERATOR'
AND relation.valid_until IS NULL) AS "areaCount"
FROM assets company
JOIN asset_types type ON type.id=company.asset_type_id
WHERE ${conditions.join(' AND ')}
ORDER BY company.name,company.code
`,params);
return { data,meta:{count:data.length} };
}
async areas(query: InventoryBrowserQueryDto) {
const params: unknown[] = [];
const conditions = [
"type.operational_role='AREA'",
"area.information_status<>'INACTIVE'",
'type.is_active=true',
];
const add = (value: unknown): string => { params.push(value); return `$${params.length}`; };
if (query.search?.trim()) {
const p = add(`%${query.search.trim()}%`);
conditions.push(`(area.code ILIKE ${p} OR area.name ILIKE ${p} OR COALESCE(area.common_name,'') ILIKE ${p})`);
}
if (query.operationalAreaId) conditions.push(`area.id=${add(query.operationalAreaId)}::uuid`);
if (query.operatorCompanyId) {
const p = add(query.operatorCompanyId);
conditions.push(`EXISTS (SELECT 1 FROM area_company_relations relation
WHERE relation.area_id=area.id AND relation.company_id=${p}::uuid
AND relation.relation_role='OPERATOR' AND relation.valid_until IS NULL)`);
}
const data = await this.dataSource.query(`
SELECT area.id,area.code,area.name,area.common_name AS "commonName",
JSONB_BUILD_OBJECT('id',type.id,'code',type.code,'name',type.name) AS type,
area.information_status AS "informationStatus",area.operational_status AS "operationalStatus",
(SELECT JSONB_BUILD_OBJECT('id',company.id,'code',company.code,'name',company.name)
FROM area_company_relations relation JOIN assets company ON company.id=relation.company_id
WHERE relation.area_id=area.id AND relation.relation_role='OPERATOR' AND relation.valid_until IS NULL
ORDER BY relation.valid_from DESC,relation.created_at DESC LIMIT 1) AS "currentOperator",
(SELECT COUNT(*)::integer FROM assets yacimiento JOIN asset_types ytype ON ytype.id=yacimiento.asset_type_id
WHERE yacimiento.parent_id=area.id AND lower(ytype.code)='yacimiento'
AND yacimiento.information_status<>'INACTIVE') AS "yacimientoCount",
(SELECT COUNT(*)::integer FROM assets inventory
WHERE inventory.information_status<>'INACTIVE' AND inventory.operational_area_id=area.id) AS "inventoryCount"
FROM assets area JOIN asset_types type ON type.id=area.asset_type_id
WHERE ${conditions.join(' AND ')} WHERE ${conditions.join(' AND ')}
ORDER BY area.name,area.code ORDER BY area.name,area.code
`, params); `, params);
return { data, meta: { count: data.length } }; return { data, meta: { count: data.length } };
} }
@@ -199,9 +261,6 @@ export class InventoryBrowserService {
"asset.information_status<>'INACTIVE'", "asset.information_status<>'INACTIVE'",
'type.is_active=true', 'type.is_active=true',
]; ];
if (allowedChildType === 'instalacion' || allowedChildType === 'subinstalacion') {
conditions.push('asset.is_inventory_instance=true');
}
if (query.search?.trim()) { if (query.search?.trim()) {
params.push(`%${query.search.trim()}%`); params.push(`%${query.search.trim()}%`);
const p = `$${params.length}`; const p = `$${params.length}`;
@@ -223,20 +282,12 @@ export class InventoryBrowserService {
CASE WHEN family.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT( CASE WHEN family.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
'id',family.id,'code',family.code,'name',family.name,'level',family.level 'id',family.id,'code',family.code,'name',family.name,'level',family.level
) END AS "inventoryFamily", ) END AS "inventoryFamily",
( (SELECT COUNT(*)::integer FROM assets child
SELECT COUNT(*)::integer WHERE child.parent_id=asset.id AND child.information_status<>'INACTIVE') AS "childrenCount",
FROM assets child CASE WHEN family.id IS NULL THEN 0 ELSE (
WHERE child.parent_id=asset.id SELECT COUNT(*)::integer FROM finding_catalog_item_inventory_families mapping
AND child.information_status<>'INACTIVE' WHERE mapping.inventory_family_id=family.id
AND ( ) END AS "findingCount",
lower(type.code)='area'
OR child.is_inventory_instance=true
OR EXISTS (
SELECT 1 FROM asset_types child_type
WHERE child_type.id=child.asset_type_id AND lower(child_type.code)='yacimiento'
)
)
) AS "childrenCount",
EXISTS (SELECT 1 FROM asset_geometries geometry WHERE geometry.asset_id=asset.id) AS "hasGeometry", EXISTS (SELECT 1 FROM asset_geometries geometry WHERE geometry.asset_id=asset.id) AS "hasGeometry",
asset.updated_at AS "updatedAt" asset.updated_at AS "updatedAt"
FROM assets asset FROM assets asset
@@ -263,10 +314,10 @@ export class InventoryBrowserService {
if (!parent) { if (!parent) {
throw new NotFoundException({ code:'INVENTORY_BROWSER_PARENT_NOT_FOUND',message:'El nivel de Inventario no existe' }); throw new NotFoundException({ code:'INVENTORY_BROWSER_PARENT_NOT_FOUND',message:'El nivel de Inventario no existe' });
} }
if (!['area','yacimiento','instalacion','subinstalacion'].includes(parent.typeCode.toLowerCase())) { if (!['departamento','area','yacimiento','instalacion','subinstalacion'].includes(parent.typeCode.toLowerCase())) {
throw new BadRequestException({ throw new BadRequestException({
code:'INVENTORY_BROWSER_PARENT_TYPE_INVALID', code:'INVENTORY_BROWSER_PARENT_TYPE_INVALID',
message:'La navegación de Inventarios admite Área → Yacimiento → Instalación → Subinstalación', message:'La navegación de Inventarios admite Departamento → Área → Yacimiento → Instalación → Subinstalación',
}); });
} }
return parent; return parent;
@@ -274,6 +325,7 @@ export class InventoryBrowserService {
private allowedChildType(typeCode: string): string | null { private allowedChildType(typeCode: string): string | null {
switch (typeCode.toLowerCase()) { switch (typeCode.toLowerCase()) {
case 'departamento': return 'area';
case 'area': return 'yacimiento'; case 'area': return 'yacimiento';
case 'yacimiento': return 'instalacion'; case 'yacimiento': return 'instalacion';
case 'instalacion': return 'subinstalacion'; case 'instalacion': return 'subinstalacion';