From f6564194b656e63bf4af4b14830289efb9f01796 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Tue, 8 Sep 2026 21:30:21 -0300 Subject: [PATCH] fix(inventory): treat company on asset as non-owning creation snapshot --- ...00-f5-operational-context-compatibility.ts | 103 +++++++++++------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/api-v3/src/database/migrations/1790087250000-f5-operational-context-compatibility.ts b/api-v3/src/database/migrations/1790087250000-f5-operational-context-compatibility.ts index db0032a..aa61a4e 100644 --- a/api-v3/src/database/migrations/1790087250000-f5-operational-context-compatibility.ts +++ b/api-v3/src/database/migrations/1790087250000-f5-operational-context-compatibility.ts @@ -6,8 +6,8 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; * historical operator/concession truth lives in area_company_relations and is * frozen separately by each Inspección/Acta. * - * operator_company_id is tolerated only on pre-F5 rows for backwards-readable - * history. New assignments or changes are rejected by the DB guard. + * operator_company_id is retained only as a backwards-compatible creation/ + * historical snapshot. Runtime ownership and search MUST NOT depend on it. */ export class F5OperationalContextCompatibility1790087250000 implements MigrationInterface { name = 'F5OperationalContextCompatibility1790087250000'; @@ -27,6 +27,8 @@ export class F5OperationalContextCompatibility1790087250000 implements Migration DECLARE asset_role asset_type_operational_role; area_role asset_type_operational_role; + company_role asset_type_operational_role; + active_relation_id uuid; BEGIN SELECT operational_role INTO asset_role FROM asset_types WHERE id=NEW.asset_type_id; @@ -39,28 +41,30 @@ export class F5OperationalContextCompatibility1790087250000 implements Migration RETURN NEW; END IF; - -- Historical rows may still contain the old operator snapshot. It remains - -- readable, but F5 never creates or changes that ownership-like value. - IF NEW.operator_company_id IS NOT NULL THEN - IF TG_OP='INSERT' - OR OLD.operator_company_id IS NULL - OR NEW.operator_company_id IS DISTINCT FROM OLD.operator_company_id THEN - RAISE EXCEPTION USING ERRCODE='23514', - MESSAGE='La Empresa pertenece al contexto temporal del Área/Inspección y no al Inventario'; - END IF; + IF NEW.operator_company_id IS NOT NULL AND NEW.operational_area_id IS NULL THEN + RAISE EXCEPTION USING ERRCODE='23514', + MESSAGE='Un snapshot de Empresa requiere un Área física'; + END IF; + + -- Once written, an old/current company snapshot cannot be repointed to + -- simulate physical ownership. Company changes happen in the temporal + -- Area↔Empresa relation instead. + IF TG_OP='UPDATE' + AND NEW.operator_company_id IS DISTINCT FROM OLD.operator_company_id THEN + RAISE EXCEPTION USING ERRCODE='23514', + MESSAGE='La Empresa se cambia en la relación temporal del Área, no en el Inventario'; END IF; IF NEW.operational_area_id IS NULL THEN RETURN NEW; END IF; - SELECT t.operational_role INTO area_role + SELECT type.operational_role INTO area_role FROM assets area - JOIN asset_types t ON t.id=area.asset_type_id + JOIN asset_types type ON type.id=area.asset_type_id WHERE area.id=NEW.operational_area_id AND area.information_status<>'INACTIVE' - AND t.is_active=true; - + AND type.is_active=true; IF area_role IS DISTINCT FROM 'AREA'::asset_type_operational_role THEN RAISE EXCEPTION USING ERRCODE='23514', MESSAGE='operational area must be an active AREA asset'; @@ -80,6 +84,35 @@ export class F5OperationalContextCompatibility1790087250000 implements Migration MESSAGE='operational area must be an ancestor in the physical hierarchy'; END IF; + -- A company value is allowed only as the context snapshot that was valid + -- at creation time. It is never used to decide future membership. + IF NEW.operator_company_id IS NOT NULL THEN + SELECT type.operational_role INTO company_role + FROM assets company + JOIN asset_types type ON type.id=company.asset_type_id + WHERE company.id=NEW.operator_company_id + AND company.information_status<>'INACTIVE' + AND type.is_active=true; + IF company_role IS DISTINCT FROM 'COMPANY'::asset_type_operational_role THEN + RAISE EXCEPTION USING ERRCODE='23514', + MESSAGE='operator snapshot must reference an active COMPANY-role asset'; + END IF; + + IF TG_OP='INSERT' THEN + SELECT relation.id INTO active_relation_id + FROM area_company_relations relation + WHERE relation.area_id=NEW.operational_area_id + AND relation.company_id=NEW.operator_company_id + AND relation.relation_role='OPERATOR'::area_organization_role + AND relation.valid_until IS NULL + FOR KEY SHARE; + IF active_relation_id IS NULL THEN + RAISE EXCEPTION USING ERRCODE='23514', + MESSAGE='creation operator snapshot must be active for the selected Area'; + END IF; + END IF; + END IF; + RETURN NEW; END $$; `); @@ -104,29 +137,22 @@ export class F5OperationalContextCompatibility1790087250000 implements Migration MESSAGE='operational area and organization must be assigned together'; END IF; - SELECT operational_role INTO asset_role - FROM asset_types WHERE id=NEW.asset_type_id; + SELECT operational_role INTO asset_role FROM asset_types WHERE id=NEW.asset_type_id; IF asset_role <> 'GENERIC'::asset_type_operational_role THEN - RAISE EXCEPTION USING ERRCODE='23514', - MESSAGE='area and organization assets cannot receive an operational assignment'; + RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='area and organization assets cannot receive an operational assignment'; END IF; - SELECT t.operational_role INTO area_role - FROM assets a JOIN asset_types t ON t.id=a.asset_type_id - WHERE a.id=NEW.operational_area_id - AND a.information_status<>'INACTIVE' AND t.is_active=true; - SELECT t.operational_role INTO company_role - FROM assets a JOIN asset_types t ON t.id=a.asset_type_id - WHERE a.id=NEW.operator_company_id - AND a.information_status<>'INACTIVE' AND t.is_active=true; - + SELECT type.operational_role INTO area_role + FROM assets area JOIN asset_types type ON type.id=area.asset_type_id + WHERE area.id=NEW.operational_area_id AND area.information_status<>'INACTIVE' AND type.is_active=true; + SELECT type.operational_role INTO company_role + FROM assets company JOIN asset_types type ON type.id=company.asset_type_id + WHERE company.id=NEW.operator_company_id AND company.information_status<>'INACTIVE' AND type.is_active=true; IF area_role IS DISTINCT FROM 'AREA'::asset_type_operational_role THEN - RAISE EXCEPTION USING ERRCODE='23514', - MESSAGE='operational area must be an active AREA asset'; + RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='operational area must be an active AREA asset'; END IF; IF company_role IS DISTINCT FROM 'COMPANY'::asset_type_operational_role THEN - RAISE EXCEPTION USING ERRCODE='23514', - MESSAGE='operator organization must be an active COMPANY-role asset'; + RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='operator organization must be an active COMPANY-role asset'; END IF; SELECT relation.id INTO active_relation_id @@ -137,22 +163,17 @@ export class F5OperationalContextCompatibility1790087250000 implements Migration AND relation.valid_until IS NULL FOR KEY SHARE; IF active_relation_id IS NULL THEN - RAISE EXCEPTION USING ERRCODE='23514', - MESSAGE='operational area and organization do not have an active OPERATOR relation'; + RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='operational area and organization do not have an active OPERATOR relation'; END IF; IF NEW.parent_id IS NULL OR NOT EXISTS ( WITH RECURSIVE ancestors AS ( SELECT id,parent_id FROM assets WHERE id=NEW.parent_id UNION ALL - SELECT parent.id,parent.parent_id - FROM assets parent - JOIN ancestors child ON parent.id=child.parent_id - ) - SELECT 1 FROM ancestors WHERE id=NEW.operational_area_id LIMIT 1 + SELECT parent.id,parent.parent_id FROM assets parent JOIN ancestors child ON parent.id=child.parent_id + ) SELECT 1 FROM ancestors WHERE id=NEW.operational_area_id LIMIT 1 ) THEN - RAISE EXCEPTION USING ERRCODE='23514', - MESSAGE='operational area must be an ancestor in the physical hierarchy'; + RAISE EXCEPTION USING ERRCODE='23514',MESSAGE='operational area must be an ancestor in the physical hierarchy'; END IF; RETURN NEW; END $$;