feat(inventory): preload authoritative technical catalog
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import {
|
||||
loadF5InventoryAuthoritativeSource,
|
||||
type F5InstallationCatalogRow,
|
||||
type F5SubinstallationCatalogRow,
|
||||
} from '../../reference-data/f5-authoritative-inventory-source';
|
||||
|
||||
type IdRow = { id: string };
|
||||
|
||||
const CATALOG_DOCUMENT_NUMBER = 'DH-F5-INVENTORY-CATALOG';
|
||||
const CATALOG_CATEGORY_CODE = 'F5MODEL';
|
||||
const CATALOG_SOURCE_NAME = 'final_modelov2.xlsx';
|
||||
|
||||
function findingKey(value: string): string {
|
||||
return value.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
function hashCode(prefix: string, value: string, length = 12): string {
|
||||
return `${prefix}-${createHash('sha1').update(value).digest('hex').slice(0, length).toUpperCase()}`;
|
||||
}
|
||||
|
||||
function installationCode(name: string): string {
|
||||
return hashCode('F5-I', findingKey(name));
|
||||
}
|
||||
|
||||
function subinstallationCode(installation: string, name: string): string {
|
||||
return hashCode('F5-S', `${findingKey(installation)}|${findingKey(name)}`);
|
||||
}
|
||||
|
||||
function subOtherCode(parentCode: string): string {
|
||||
return hashCode('F5-S-OTRO', parentCode);
|
||||
}
|
||||
|
||||
export class F5AuthoritativeInventoryCatalog1790087300000 implements MigrationInterface {
|
||||
name = 'F5AuthoritativeInventoryCatalog1790087300000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const source = loadF5InventoryAuthoritativeSource();
|
||||
if (
|
||||
source.catalogSource.file !== CATALOG_SOURCE_NAME
|
||||
|| source.catalogSource.sheet !== 'Hoja1'
|
||||
|| source.catalogSource.sha256 !== 'c9a2d1db59fff2157162c41009b8c9042a3c7a3001649239a07732a3b8fca155'
|
||||
|| source.catalogSource.installations.length !== 14
|
||||
|| source.catalogSource.subinstallations.length !== 109
|
||||
) {
|
||||
throw new Error('F5 inventory catalog source contract mismatch');
|
||||
}
|
||||
|
||||
await queryRunner.query(`
|
||||
INSERT INTO source_documents (
|
||||
document_type,document_number,title,issuer,external_reference,notes
|
||||
)
|
||||
VALUES ('SPREADSHEET',$1,$2,'Dirección de Hidrocarburos',$3,$4)
|
||||
ON CONFLICT (document_number,issuer) WHERE document_number IS NOT NULL AND issuer IS NOT NULL
|
||||
DO UPDATE SET
|
||||
title=EXCLUDED.title,
|
||||
external_reference=EXCLUDED.external_reference,
|
||||
notes=EXCLUDED.notes,
|
||||
updated_at=CURRENT_TIMESTAMP
|
||||
`, [
|
||||
CATALOG_DOCUMENT_NUMBER,
|
||||
CATALOG_SOURCE_NAME,
|
||||
`sha256:${source.catalogSource.sha256}`,
|
||||
`F5 · catálogo técnico autorizado · hoja ${source.catalogSource.sheet} · 14 Instalaciones · 109 Subinstalaciones`,
|
||||
]);
|
||||
|
||||
const sourceDocumentId = await this.id(
|
||||
queryRunner,
|
||||
`SELECT id FROM source_documents WHERE document_number=$1 AND issuer='Dirección de Hidrocarburos' LIMIT 1`,
|
||||
[CATALOG_DOCUMENT_NUMBER],
|
||||
'catalog source document',
|
||||
);
|
||||
|
||||
await queryRunner.query(`
|
||||
UPDATE inventory_families
|
||||
SET is_active=false,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE source_reference IS NULL OR source_reference NOT LIKE 'F5:%'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE finding_categories
|
||||
SET is_active=false,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE lower(code) IN ('app26','app26r2')
|
||||
`);
|
||||
|
||||
const installationIds = new Map<string,string>();
|
||||
for (const installation of source.catalogSource.installations) {
|
||||
const familyId = await this.upsertFamily(
|
||||
queryRunner,
|
||||
installationCode(installation.name),
|
||||
installation.name,
|
||||
'INSTALLATION',
|
||||
`F5:${CATALOG_SOURCE_NAME}|${source.catalogSource.sheet}|rows:${installation.sourceStartRow}-${installation.sourceEndRow}`,
|
||||
);
|
||||
installationIds.set(findingKey(installation.name),familyId);
|
||||
}
|
||||
|
||||
for (const subinstallation of source.catalogSource.subinstallations) {
|
||||
const parentId = installationIds.get(findingKey(subinstallation.installation));
|
||||
if (!parentId) throw new Error(`F5 missing installation family ${subinstallation.installation}`);
|
||||
const childId = await this.upsertFamily(
|
||||
queryRunner,
|
||||
subinstallationCode(subinstallation.installation,subinstallation.name),
|
||||
subinstallation.name,
|
||||
'SUBINSTALLATION',
|
||||
`F5:${CATALOG_SOURCE_NAME}|${source.catalogSource.sheet}|rows:${subinstallation.sourceStartRow}-${subinstallation.sourceEndRow}${subinstallation.reference ? `|reference:${subinstallation.reference}` : ''}`,
|
||||
);
|
||||
await this.parentRule(queryRunner,childId,parentId);
|
||||
}
|
||||
|
||||
const installationOtherId = await this.upsertFamily(
|
||||
queryRunner,
|
||||
'F5-I-OTRO',
|
||||
'Otro / no catalogado',
|
||||
'INSTALLATION',
|
||||
'F5:SYSTEM:OTHER:INSTALLATION',
|
||||
);
|
||||
for (const [installationKey,parentId] of installationIds) {
|
||||
const parent = source.catalogSource.installations.find((item) => findingKey(item.name)===installationKey);
|
||||
if (!parent) continue;
|
||||
const childId = await this.upsertFamily(
|
||||
queryRunner,
|
||||
subOtherCode(installationCode(parent.name)),
|
||||
'Otro / no catalogado',
|
||||
'SUBINSTALLATION',
|
||||
`F5:SYSTEM:OTHER:SUBINSTALLATION:${installationCode(parent.name)}`,
|
||||
);
|
||||
await this.parentRule(queryRunner,childId,parentId);
|
||||
}
|
||||
const rootOtherChild = await this.upsertFamily(
|
||||
queryRunner,
|
||||
subOtherCode('F5-I-OTRO'),
|
||||
'Otro / no catalogado',
|
||||
'SUBINSTALLATION',
|
||||
'F5:SYSTEM:OTHER:SUBINSTALLATION:F5-I-OTRO',
|
||||
);
|
||||
await this.parentRule(queryRunner,rootOtherChild,installationOtherId);
|
||||
|
||||
await queryRunner.query(`
|
||||
INSERT INTO finding_categories(code,name,sort_order,is_active)
|
||||
SELECT $1,'DH · Modelo de Inventarios F5',270,true
|
||||
WHERE NOT EXISTS (SELECT 1 FROM finding_categories WHERE lower(code)=lower($1))
|
||||
`, [CATALOG_CATEGORY_CODE]);
|
||||
await queryRunner.query(`
|
||||
UPDATE finding_categories
|
||||
SET name='DH · Modelo de Inventarios F5',sort_order=270,is_active=true,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE lower(code)=lower($1)
|
||||
`,[CATALOG_CATEGORY_CODE]);
|
||||
const categoryId = await this.id(
|
||||
queryRunner,
|
||||
`SELECT id FROM finding_categories WHERE lower(code)=lower($1) LIMIT 1`,
|
||||
[CATALOG_CATEGORY_CODE],
|
||||
'F5 finding category',
|
||||
);
|
||||
|
||||
const titleByKey = new Map<string,string>();
|
||||
const register = (title: string): void => {
|
||||
const clean = title.trim();
|
||||
if (!clean || /^idem\b/i.test(clean) || findingKey(clean)==='hallazgos') return;
|
||||
const itemKey = findingKey(clean);
|
||||
if (!titleByKey.has(itemKey)) titleByKey.set(itemKey,clean);
|
||||
};
|
||||
for (const family of source.catalogSource.installations) for (const title of family.findings) register(title);
|
||||
for (const family of source.catalogSource.subinstallations) for (const title of family.findings) register(title);
|
||||
if (titleByKey.size !== 177) {
|
||||
throw new Error(`F5 finding normalization contract mismatch: ${titleByKey.size}`);
|
||||
}
|
||||
|
||||
const itemIdByKey = new Map<string,string>();
|
||||
const orderedTitles = [...titleByKey.entries()].sort((a,b)=>a[1].localeCompare(b[1],'es'));
|
||||
let sourceNumber=1;
|
||||
for (const [itemKey,title] of orderedTitles) {
|
||||
const itemCode = hashCode('F5-H',itemKey);
|
||||
let itemId = await this.optionalId(
|
||||
queryRunner,
|
||||
`SELECT id FROM finding_catalog_items WHERE lower(code)=lower($1) LIMIT 1`,
|
||||
[itemCode],
|
||||
);
|
||||
if (!itemId) {
|
||||
const rows = (await queryRunner.query(`
|
||||
INSERT INTO finding_catalog_items (
|
||||
category_id,code,source_number,title,import_note,revision,is_active
|
||||
)
|
||||
VALUES ($1::uuid,$2,$3,$4,$5,1,true)
|
||||
RETURNING id
|
||||
`,[
|
||||
categoryId,itemCode,sourceNumber,title,
|
||||
`${CATALOG_SOURCE_NAME} · ${source.catalogSource.sheet} · F5 authoritative catalog`,
|
||||
])) as IdRow[];
|
||||
itemId=rows[0]?.id ?? null;
|
||||
} else {
|
||||
await queryRunner.query(`
|
||||
UPDATE finding_catalog_items
|
||||
SET category_id=$2::uuid,source_number=$3,title=$4,import_note=$5,
|
||||
is_active=true,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=$1::uuid
|
||||
`,[
|
||||
itemId,categoryId,sourceNumber,title,
|
||||
`${CATALOG_SOURCE_NAME} · ${source.catalogSource.sheet} · F5 authoritative catalog`,
|
||||
]);
|
||||
}
|
||||
if (!itemId) throw new Error(`F5 could not create finding ${title}`);
|
||||
itemIdByKey.set(itemKey,itemId);
|
||||
await queryRunner.query(`
|
||||
INSERT INTO finding_catalog_item_versions(item_id,revision,snapshot,actor_username)
|
||||
SELECT item.id,item.revision,
|
||||
jsonb_build_object(
|
||||
'id',item.id,'categoryId',category.id,'categoryCode',category.code,
|
||||
'categoryName',category.name,'code',item.code,'sourceNumber',item.source_number,
|
||||
'title',item.title,'legalBasis',item.legal_basis,'glossary',item.glossary,
|
||||
'importNote',item.import_note,'revision',item.revision,'isActive',item.is_active
|
||||
),'migration:F5'
|
||||
FROM finding_catalog_items item
|
||||
JOIN finding_categories category ON category.id=item.category_id
|
||||
WHERE item.id=$1::uuid
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM finding_catalog_item_versions version
|
||||
WHERE version.item_id=item.id AND version.revision=item.revision
|
||||
)
|
||||
`,[itemId]);
|
||||
sourceNumber+=1;
|
||||
}
|
||||
|
||||
await queryRunner.query(`DELETE FROM finding_catalog_item_inventory_families`);
|
||||
|
||||
for (const family of source.catalogSource.installations) {
|
||||
await this.mapFindings(queryRunner,installationCode(family.name),family,itemIdByKey);
|
||||
}
|
||||
for (const family of source.catalogSource.subinstallations) {
|
||||
await this.mapFindings(
|
||||
queryRunner,
|
||||
subinstallationCode(family.installation,family.name),
|
||||
family,
|
||||
itemIdByKey,
|
||||
);
|
||||
}
|
||||
|
||||
await this.installFamilySyncFunctions(queryRunner);
|
||||
|
||||
await queryRunner.query(`
|
||||
INSERT INTO finding_catalog_asset_type_profiles(asset_type_id,reason)
|
||||
SELECT id,'F5: catálogo contextual por familia técnica. Yacimiento sin familia usa OTROS.'
|
||||
FROM asset_types WHERE lower(code) IN ('yacimiento','instalacion','subinstalacion')
|
||||
ON CONFLICT (asset_type_id) DO UPDATE SET
|
||||
reason=EXCLUDED.reason,updated_at=CURRENT_TIMESTAMP
|
||||
`);
|
||||
|
||||
const [counts] = (await queryRunner.query(`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE level='INSTALLATION' AND source_reference LIKE 'F5:${CATALOG_SOURCE_NAME}%')::integer AS installations,
|
||||
COUNT(*) FILTER (WHERE level='SUBINSTALLATION' AND source_reference LIKE 'F5:${CATALOG_SOURCE_NAME}%')::integer AS subinstallations
|
||||
FROM inventory_families
|
||||
WHERE is_active=true
|
||||
`)) as Array<{ installations:number; subinstallations:number }>;
|
||||
if (Number(counts?.installations ?? 0)!==14 || Number(counts?.subinstallations ?? 0)!==109) {
|
||||
throw new Error(`F5 family preload verification failed: ${JSON.stringify(counts ?? {})}`);
|
||||
}
|
||||
|
||||
const [itemCount] = (await queryRunner.query(`
|
||||
SELECT COUNT(*)::integer AS total
|
||||
FROM finding_catalog_items
|
||||
WHERE category_id=$1::uuid AND is_active=true
|
||||
`,[categoryId])) as Array<{total:number}>;
|
||||
if (Number(itemCount?.total ?? 0)!==177) {
|
||||
throw new Error(`F5 finding preload verification failed: ${itemCount?.total ?? 0}`);
|
||||
}
|
||||
|
||||
void sourceDocumentId;
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const categoryId = await this.optionalId(
|
||||
queryRunner,
|
||||
`SELECT id FROM finding_categories WHERE lower(code)=lower($1) LIMIT 1`,
|
||||
[CATALOG_CATEGORY_CODE],
|
||||
);
|
||||
if (categoryId) {
|
||||
await queryRunner.query(`
|
||||
DELETE FROM finding_catalog_item_inventory_families mapping
|
||||
USING finding_catalog_items item
|
||||
WHERE item.id=mapping.catalog_item_id AND item.category_id=$1::uuid
|
||||
`,[categoryId]);
|
||||
await queryRunner.query(`
|
||||
DELETE FROM finding_catalog_item_versions version
|
||||
USING finding_catalog_items item
|
||||
WHERE item.id=version.item_id
|
||||
AND item.category_id=$1::uuid
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM inspection_findings finding WHERE finding.catalog_item_id=item.id
|
||||
)
|
||||
`,[categoryId]);
|
||||
await queryRunner.query(`
|
||||
DELETE FROM finding_catalog_items item
|
||||
WHERE item.category_id=$1::uuid
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM inspection_findings finding WHERE finding.catalog_item_id=item.id
|
||||
)
|
||||
`,[categoryId]);
|
||||
await queryRunner.query(`
|
||||
DELETE FROM finding_categories category
|
||||
WHERE category.id=$1::uuid
|
||||
AND NOT EXISTS (SELECT 1 FROM finding_catalog_items item WHERE item.category_id=category.id)
|
||||
`,[categoryId]);
|
||||
}
|
||||
|
||||
await queryRunner.query(`
|
||||
DELETE FROM inventory_families family
|
||||
WHERE family.source_reference LIKE 'F5:%'
|
||||
AND NOT EXISTS (SELECT 1 FROM assets asset WHERE asset.inventory_family_id=family.id)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE inventory_families
|
||||
SET is_active=true,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE source_reference LIKE 'APLICACION APP%' OR source_reference LIKE 'SYSTEM:F3.1:%'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE finding_categories SET is_active=true,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE lower(code)='app26r2'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
DELETE FROM source_documents
|
||||
WHERE document_number=$1 AND issuer='Dirección de Hidrocarburos'
|
||||
`,[CATALOG_DOCUMENT_NUMBER]);
|
||||
await this.restoreF31FamilySyncFunctions(queryRunner);
|
||||
}
|
||||
|
||||
private async upsertFamily(
|
||||
queryRunner: QueryRunner,
|
||||
familyCode: string,
|
||||
name: string,
|
||||
level: 'INSTALLATION'|'SUBINSTALLATION',
|
||||
sourceReference: string,
|
||||
): Promise<string> {
|
||||
await queryRunner.query(`
|
||||
INSERT INTO inventory_families(
|
||||
code,name,level,legacy_type_code,information_labels,source_reference,is_active
|
||||
)
|
||||
VALUES ($1,$2,$3,NULL,'[]'::jsonb,$4,true)
|
||||
ON CONFLICT (code) DO UPDATE SET
|
||||
name=EXCLUDED.name,level=EXCLUDED.level,legacy_type_code=NULL,
|
||||
information_labels='[]'::jsonb,source_reference=EXCLUDED.source_reference,
|
||||
is_active=true,updated_at=CURRENT_TIMESTAMP
|
||||
`,[familyCode,name,level,sourceReference]);
|
||||
return this.id(
|
||||
queryRunner,
|
||||
`SELECT id FROM inventory_families WHERE code=$1 LIMIT 1`,
|
||||
[familyCode],
|
||||
`inventory family ${familyCode}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async parentRule(queryRunner: QueryRunner,childId:string,parentId:string):Promise<void> {
|
||||
await queryRunner.query(`
|
||||
INSERT INTO inventory_family_parent_rules(child_family_id,parent_family_id)
|
||||
VALUES ($1::uuid,$2::uuid)
|
||||
ON CONFLICT (child_family_id) DO UPDATE SET parent_family_id=EXCLUDED.parent_family_id
|
||||
`,[childId,parentId]);
|
||||
}
|
||||
|
||||
private async mapFindings(
|
||||
queryRunner: QueryRunner,
|
||||
familyCode: string,
|
||||
family: F5InstallationCatalogRow|F5SubinstallationCatalogRow,
|
||||
itemIdByKey: Map<string,string>,
|
||||
): Promise<void> {
|
||||
const familyId = await this.id(
|
||||
queryRunner,
|
||||
`SELECT id FROM inventory_families WHERE code=$1 LIMIT 1`,
|
||||
[familyCode],
|
||||
`family ${familyCode}`,
|
||||
);
|
||||
const mapped = new Set<string>();
|
||||
for (const rawTitle of family.findings) {
|
||||
const itemKey=findingKey(rawTitle);
|
||||
if (!itemKey || itemKey==='hallazgos' || mapped.has(itemKey)) continue;
|
||||
mapped.add(itemKey);
|
||||
const itemId=itemIdByKey.get(itemKey);
|
||||
if (!itemId) throw new Error(`F5 missing finding item ${rawTitle}`);
|
||||
await queryRunner.query(`
|
||||
INSERT INTO finding_catalog_item_inventory_families(catalog_item_id,inventory_family_id)
|
||||
VALUES ($1::uuid,$2::uuid)
|
||||
ON CONFLICT (catalog_item_id,inventory_family_id) DO NOTHING
|
||||
`,[itemId,familyId]);
|
||||
}
|
||||
}
|
||||
|
||||
private async installFamilySyncFunctions(queryRunner: QueryRunner):Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE OR REPLACE FUNCTION sync_asset_inventory_family_catalog()
|
||||
RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
DELETE FROM finding_catalog_asset_overrides
|
||||
WHERE asset_id=NEW.id
|
||||
AND reason LIKE 'F% familia técnica:%';
|
||||
|
||||
IF NEW.inventory_family_id IS NOT NULL THEN
|
||||
INSERT INTO finding_catalog_asset_overrides(
|
||||
asset_id,catalog_item_id,is_enabled,reason,created_by,updated_by
|
||||
)
|
||||
SELECT NEW.id,mapping.catalog_item_id,true,
|
||||
'F5 familia técnica: catálogo contextual automático',
|
||||
NEW.created_by,NEW.updated_by
|
||||
FROM finding_catalog_item_inventory_families mapping
|
||||
WHERE mapping.inventory_family_id=NEW.inventory_family_id
|
||||
ON CONFLICT (asset_id,catalog_item_id) DO UPDATE SET
|
||||
is_enabled=true,
|
||||
reason='F5 familia técnica: catálogo contextual automático',
|
||||
updated_by=NEW.updated_by,
|
||||
updated_at=CURRENT_TIMESTAMP;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE OR REPLACE FUNCTION sync_inventory_family_mapping_assets()
|
||||
RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF TG_OP='DELETE' THEN
|
||||
DELETE FROM finding_catalog_asset_overrides override_record
|
||||
USING assets asset
|
||||
WHERE override_record.asset_id=asset.id
|
||||
AND asset.inventory_family_id=OLD.inventory_family_id
|
||||
AND override_record.catalog_item_id=OLD.catalog_item_id
|
||||
AND override_record.reason LIKE 'F% familia técnica:%';
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
INSERT INTO finding_catalog_asset_overrides(
|
||||
asset_id,catalog_item_id,is_enabled,reason,created_by,updated_by
|
||||
)
|
||||
SELECT asset.id,NEW.catalog_item_id,true,
|
||||
'F5 familia técnica: catálogo contextual automático',
|
||||
asset.created_by,asset.updated_by
|
||||
FROM assets asset
|
||||
WHERE asset.inventory_family_id=NEW.inventory_family_id
|
||||
ON CONFLICT (asset_id,catalog_item_id) DO UPDATE SET
|
||||
is_enabled=true,
|
||||
reason='F5 familia técnica: catálogo contextual automático',
|
||||
updated_at=CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
DELETE FROM finding_catalog_asset_overrides
|
||||
WHERE reason LIKE 'F% familia técnica:%'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
INSERT INTO finding_catalog_asset_overrides(
|
||||
asset_id,catalog_item_id,is_enabled,reason,created_by,updated_by
|
||||
)
|
||||
SELECT asset.id,mapping.catalog_item_id,true,
|
||||
'F5 familia técnica: catálogo contextual automático',
|
||||
asset.created_by,asset.updated_by
|
||||
FROM assets asset
|
||||
JOIN finding_catalog_item_inventory_families mapping
|
||||
ON mapping.inventory_family_id=asset.inventory_family_id
|
||||
WHERE asset.inventory_family_id IS NOT NULL
|
||||
ON CONFLICT (asset_id,catalog_item_id) DO UPDATE SET
|
||||
is_enabled=true,
|
||||
reason='F5 familia técnica: catálogo contextual automático',
|
||||
updated_at=CURRENT_TIMESTAMP
|
||||
`);
|
||||
}
|
||||
|
||||
private async restoreF31FamilySyncFunctions(queryRunner: QueryRunner):Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE OR REPLACE FUNCTION sync_asset_inventory_family_catalog()
|
||||
RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
DELETE FROM finding_catalog_asset_overrides
|
||||
WHERE asset_id=NEW.id AND reason LIKE 'F3.1 familia técnica:%';
|
||||
IF NEW.inventory_family_id IS NOT NULL THEN
|
||||
INSERT INTO finding_catalog_asset_overrides(
|
||||
asset_id,catalog_item_id,is_enabled,reason,created_by,updated_by
|
||||
)
|
||||
SELECT NEW.id,mapping.catalog_item_id,true,
|
||||
'F3.1 familia técnica: catálogo contextual automático',
|
||||
NEW.created_by,NEW.updated_by
|
||||
FROM finding_catalog_item_inventory_families mapping
|
||||
WHERE mapping.inventory_family_id=NEW.inventory_family_id
|
||||
ON CONFLICT (asset_id,catalog_item_id) DO UPDATE SET
|
||||
is_enabled=true,reason='F3.1 familia técnica: catálogo contextual automático',
|
||||
updated_by=NEW.updated_by,updated_at=CURRENT_TIMESTAMP;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE OR REPLACE FUNCTION sync_inventory_family_mapping_assets()
|
||||
RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF TG_OP='DELETE' THEN
|
||||
DELETE FROM finding_catalog_asset_overrides override_record
|
||||
USING assets asset
|
||||
WHERE override_record.asset_id=asset.id
|
||||
AND asset.inventory_family_id=OLD.inventory_family_id
|
||||
AND override_record.catalog_item_id=OLD.catalog_item_id
|
||||
AND override_record.reason LIKE 'F3.1 familia técnica:%';
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
INSERT INTO finding_catalog_asset_overrides(
|
||||
asset_id,catalog_item_id,is_enabled,reason,created_by,updated_by
|
||||
)
|
||||
SELECT asset.id,NEW.catalog_item_id,true,
|
||||
'F3.1 familia técnica: catálogo contextual automático',
|
||||
asset.created_by,asset.updated_by
|
||||
FROM assets asset
|
||||
WHERE asset.inventory_family_id=NEW.inventory_family_id
|
||||
ON CONFLICT (asset_id,catalog_item_id) DO UPDATE SET
|
||||
is_enabled=true,reason='F3.1 familia técnica: catálogo contextual automático',
|
||||
updated_at=CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
`);
|
||||
}
|
||||
|
||||
private async id(queryRunner: QueryRunner,sql:string,params:unknown[],label:string):Promise<string> {
|
||||
const value=await this.optionalId(queryRunner,sql,params);
|
||||
if (!value) throw new Error(`F5 could not resolve ${label}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
private async optionalId(queryRunner: QueryRunner,sql:string,params:unknown[]):Promise<string|null> {
|
||||
const rows=(await queryRunner.query(sql,params)) as IdRow[];
|
||||
return rows[0]?.id ?? null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user