From 05481155165456ef977c01136f4a080a9719d686 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Tue, 8 Sep 2026 20:02:59 -0300 Subject: [PATCH] feat(inventory): add real inventory list endpoint logic --- .../asset-master/inventory-browser.service.ts | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/api-v3/src/asset-master/inventory-browser.service.ts b/api-v3/src/asset-master/inventory-browser.service.ts index 6e5cef6..10e6d30 100644 --- a/api-v3/src/asset-master/inventory-browser.service.ts +++ b/api-v3/src/asset-master/inventory-browser.service.ts @@ -13,6 +13,112 @@ type ParentContext = { export class InventoryBrowserService { constructor(private readonly dataSource: DataSource) {} + async items(query: InventoryBrowserQueryDto) { + const params: unknown[] = []; + const conditions = [ + 'asset.is_inventory_instance=true', + "asset.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(`(asset.code ILIKE ${p} OR asset.name ILIKE ${p} OR COALESCE(asset.common_name,'') ILIKE ${p})`); + } + if (query.typeId) conditions.push(`asset.asset_type_id=${add(query.typeId)}::uuid`); + if (query.status) conditions.push(`asset.information_status=${add(query.status)}::asset_information_status`); + if (query.operationalStatus) conditions.push(`asset.operational_status=${add(query.operationalStatus)}::asset_operational_status`); + if (query.needsValidation === true) conditions.push("asset.information_status NOT IN ('VALIDATED','INACTIVE')"); + 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 === 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.operatorCompanyId) { + const company = add(query.operatorCompanyId); + conditions.push(`EXISTS ( + SELECT 1 FROM area_company_relations relation + WHERE relation.area_id=asset.operational_area_id + AND relation.company_id=${company}::uuid + AND relation.relation_role='OPERATOR' + AND relation.valid_until IS NULL + )`); + } + + const where = conditions.join(' AND '); + const [countRow] = (await this.dataSource.query(` + SELECT COUNT(*)::integer AS total + FROM assets asset + JOIN asset_types type ON type.id=asset.asset_type_id + WHERE ${where} + `,params)) as Array<{total:number}>; + const total=Number(countRow?.total ?? 0); + const offset=(query.page-1)*query.pageSize; + params.push(query.pageSize); + const limit=`$${params.length}`; + params.push(offset); + const offsetParam=`$${params.length}`; + + const data=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.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT( + 'id',parent.id,'code',parent.code,'name',parent.name + ) END AS parent, + CASE WHEN area.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT( + 'id',area.id,'code',area.code,'name',area.name + ) END AS "operationalArea", + ( + 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=asset.operational_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 "operatorCompany", + asset.information_status AS "informationStatus", + asset.operational_status AS "operationalStatus", + 0::integer AS "childrenCount", + 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", + (SELECT COUNT(*)::integer FROM asset_media media WHERE media.asset_id=asset.id AND media.deleted_at IS NULL) AS "mediaCount", + asset.data_origin AS "dataOrigin", + (asset.provenance_verified_at IS NOT NULL) AS "provenanceVerified", + asset.current_version AS "currentVersion", + asset.updated_at AS "updatedAt" + FROM assets asset + JOIN asset_types type ON type.id=asset.asset_type_id + LEFT JOIN assets parent ON parent.id=asset.parent_id + LEFT JOIN assets area ON area.id=asset.operational_area_id + LEFT JOIN LATERAL ( + SELECT ST_GeometryType(geometry.geometry)::text AS type + FROM asset_geometries geometry + WHERE geometry.asset_id=asset.id + ORDER BY geometry.updated_at DESC + LIMIT 1 + ) geometry_type ON true + WHERE ${where} + ORDER BY asset.name,asset.code + LIMIT ${limit} OFFSET ${offsetParam} + `,params); + + return { + data, + meta:{ + page:query.page, + pageSize:query.pageSize, + total, + totalPages:total===0 ? 0 : Math.ceil(total/query.pageSize), + }, + }; + } + async areas(query: InventoryBrowserQueryDto) { const params: unknown[] = []; const conditions = [ @@ -29,6 +135,7 @@ export class InventoryBrowserService { 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 (