feat(inventory): add area-owned inventory browser
This commit is contained in:
@@ -0,0 +1,176 @@
|
|||||||
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import type { InventoryBrowserQueryDto } from './dto/inventory-browser-query.dto';
|
||||||
|
|
||||||
|
type ParentContext = {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
typeCode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class InventoryBrowserService {
|
||||||
|
constructor(private readonly dataSource: DataSource) {}
|
||||||
|
|
||||||
|
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.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.is_inventory_instance=true
|
||||||
|
AND 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 ')}
|
||||||
|
ORDER BY area.name,area.code
|
||||||
|
`, params);
|
||||||
|
|
||||||
|
return { data, meta: { count: data.length } };
|
||||||
|
}
|
||||||
|
|
||||||
|
async children(parentId: string, query: InventoryBrowserQueryDto) {
|
||||||
|
const parent = await this.parent(parentId);
|
||||||
|
const allowedChildType = this.allowedChildType(parent.typeCode);
|
||||||
|
if (!allowedChildType) return { parent, data: [], meta: { count: 0, hasMore: false } };
|
||||||
|
|
||||||
|
const params: unknown[] = [parentId, allowedChildType];
|
||||||
|
const conditions = [
|
||||||
|
'asset.parent_id=$1::uuid',
|
||||||
|
'lower(type.code)=lower($2)',
|
||||||
|
"asset.information_status<>'INACTIVE'",
|
||||||
|
'type.is_active=true',
|
||||||
|
];
|
||||||
|
if (allowedChildType === 'instalacion' || allowedChildType === 'subinstalacion') {
|
||||||
|
conditions.push('asset.is_inventory_instance=true');
|
||||||
|
}
|
||||||
|
if (query.search?.trim()) {
|
||||||
|
params.push(`%${query.search.trim()}%`);
|
||||||
|
const p = `$${params.length}`;
|
||||||
|
conditions.push(`(asset.code ILIKE ${p} OR asset.name ILIKE ${p} OR COALESCE(asset.common_name,'') ILIKE ${p})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push(201);
|
||||||
|
const limit = `$${params.length}`;
|
||||||
|
const rows = await this.dataSource.query(`
|
||||||
|
SELECT
|
||||||
|
asset.id,asset.code,asset.name,asset.common_name AS "commonName",
|
||||||
|
JSONB_BUILD_OBJECT('id',type.id,'code',type.code,'name',type.name) AS type,
|
||||||
|
CASE WHEN parent_asset.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
||||||
|
'id',parent_asset.id,'code',parent_asset.code,'name',parent_asset.name
|
||||||
|
) END AS parent,
|
||||||
|
asset.information_status AS "informationStatus",
|
||||||
|
asset.operational_status AS "operationalStatus",
|
||||||
|
asset.is_inventory_instance AS "isInventoryInstance",
|
||||||
|
CASE WHEN family.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
||||||
|
'id',family.id,'code',family.code,'name',family.name,'level',family.level
|
||||||
|
) END AS "inventoryFamily",
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)::integer
|
||||||
|
FROM assets child
|
||||||
|
WHERE child.parent_id=asset.id
|
||||||
|
AND child.information_status<>'INACTIVE'
|
||||||
|
AND (
|
||||||
|
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",
|
||||||
|
asset.updated_at AS "updatedAt"
|
||||||
|
FROM assets asset
|
||||||
|
JOIN asset_types type ON type.id=asset.asset_type_id
|
||||||
|
LEFT JOIN assets parent_asset ON parent_asset.id=asset.parent_id
|
||||||
|
LEFT JOIN inventory_families family ON family.id=asset.inventory_family_id
|
||||||
|
WHERE ${conditions.join(' AND ')}
|
||||||
|
ORDER BY asset.name,asset.code
|
||||||
|
LIMIT ${limit}
|
||||||
|
`, params);
|
||||||
|
|
||||||
|
const hasMore = rows.length > 200;
|
||||||
|
const data = hasMore ? rows.slice(0,200) : rows;
|
||||||
|
return { parent, data, meta: { count: data.length, hasMore } };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async parent(parentId: string): Promise<ParentContext> {
|
||||||
|
const [parent] = await this.dataSource.query(`
|
||||||
|
SELECT asset.id,asset.code,asset.name,type.code AS "typeCode"
|
||||||
|
FROM assets asset
|
||||||
|
JOIN asset_types type ON type.id=asset.asset_type_id
|
||||||
|
WHERE asset.id=$1::uuid AND asset.information_status<>'INACTIVE'
|
||||||
|
`,[parentId]) as ParentContext[];
|
||||||
|
if (!parent) {
|
||||||
|
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())) {
|
||||||
|
throw new BadRequestException({
|
||||||
|
code:'INVENTORY_BROWSER_PARENT_TYPE_INVALID',
|
||||||
|
message:'La navegación de Inventarios admite Área → Yacimiento → Instalación → Subinstalación',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private allowedChildType(typeCode: string): string | null {
|
||||||
|
switch (typeCode.toLowerCase()) {
|
||||||
|
case 'area': return 'yacimiento';
|
||||||
|
case 'yacimiento': return 'instalacion';
|
||||||
|
case 'instalacion': return 'subinstalacion';
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user