F5.1: jerarquía manual Departamento a Subinstalación

This commit is contained in:
2026-09-09 07:48:59 -03:00
parent 0b0484c5fe
commit d7557ef600
@@ -38,8 +38,10 @@ type ParentRow = {
typeCode: string;
inventoryFamilyId: string | null;
};
type IdRow = { id: string };
const TYPE_CODE_BY_KIND: Record<Exclude<InventoryStructureKind, 'EMPRESA'>, string> = {
DEPARTAMENTO: 'departamento',
AREA: 'area',
YACIMIENTO: 'yacimiento',
INSTALACION: 'instalacion',
@@ -47,7 +49,8 @@ const TYPE_CODE_BY_KIND: Record<Exclude<InventoryStructureKind, 'EMPRESA'>, stri
};
const PARENT_TYPE_BY_KIND: Record<InventoryStructureKind, string | null> = {
EMPRESA: null,
AREA: null,
DEPARTAMENTO: null,
AREA: 'departamento',
YACIMIENTO: 'area',
INSTALACION: 'yacimiento',
SUBINSTALACION: 'instalacion',
@@ -70,22 +73,24 @@ export class InventoryStructureService {
SELECT id,code,name
FROM asset_types
WHERE (
lower(code) IN ('area','yacimiento','instalacion','subinstalacion')
lower(code) IN ('departamento','area','yacimiento','instalacion','subinstalacion')
OR operational_role='COMPANY'
) AND is_active=true
ORDER BY CASE
WHEN operational_role='COMPANY' THEN 0
WHEN lower(code)='area' THEN 1
WHEN lower(code)='yacimiento' THEN 2
WHEN lower(code)='instalacion' THEN 3
WHEN lower(code)='subinstalacion' THEN 4 ELSE 9 END
WHEN lower(code)='departamento' THEN 1
WHEN lower(code)='area' THEN 2
WHEN lower(code)='yacimiento' THEN 3
WHEN lower(code)='instalacion' THEN 4
WHEN lower(code)='subinstalacion' THEN 5 ELSE 9 END
`)) as StructureTypeRow[];
const company = types.find((item) => ['empresa','organizacion'].includes(item.code.toLowerCase()));
const departamento = types.find((item) => item.code.toLowerCase()==='departamento');
const area = types.find((item) => item.code.toLowerCase()==='area');
const yacimiento = types.find((item) => item.code.toLowerCase()==='yacimiento');
const instalacion = types.find((item) => item.code.toLowerCase()==='instalacion');
const subinstalacion = types.find((item) => item.code.toLowerCase()==='subinstalacion');
if (!company || !area || !yacimiento || !instalacion || !subinstalacion) {
if (!company || !departamento || !area || !yacimiento || !instalacion || !subinstalacion) {
throw new ConflictException({
code: 'INVENTORY_STRUCTURE_TYPES_INCOMPLETE',
message: 'La configuración maestra de Empresa e Inventario todavía no está completa',
@@ -109,7 +114,8 @@ export class InventoryStructureService {
{ kind: 'EMPRESA', label: 'Empresa', type: company, parentKind: null, requiresFamily: false },
],
levels: [
{ kind: 'AREA', label: 'Área', type: area, parentKind: null, requiresFamily: false },
{ kind: 'DEPARTAMENTO', label: 'Departamento', type: departamento, parentKind: null, requiresFamily: false },
{ kind: 'AREA', label: 'Área', type: area, parentKind: 'DEPARTAMENTO', requiresFamily: false },
{ kind: 'YACIMIENTO', label: 'Yacimiento', type: yacimiento, parentKind: 'AREA', requiresFamily: false },
{ kind: 'INSTALACION', label: 'Instalación', type: instalacion, parentKind: 'YACIMIENTO', requiresFamily: true },
{ kind: 'SUBINSTALACION', label: 'Subinstalación', type: subinstalacion, parentKind: 'INSTALACION', requiresFamily: true },
@@ -121,7 +127,7 @@ export class InventoryStructureService {
async parents(kindValue: string, search?: string) {
const kind = kindValue.toUpperCase() as InventoryStructureKind;
if (!(kind in PARENT_TYPE_BY_KIND) || kind === 'AREA' || kind === 'EMPRESA') {
if (!(kind in PARENT_TYPE_BY_KIND) || kind === 'DEPARTAMENTO' || kind === 'EMPRESA') {
throw new BadRequestException({
code: 'INVENTORY_STRUCTURE_PARENT_KIND_INVALID',
message: 'El nivel indicado no requiere un registro padre',
@@ -153,7 +159,7 @@ export class InventoryStructureService {
AND asset.information_status<>'INACTIVE'
${searchSql}
ORDER BY asset.name,asset.code
LIMIT 80
LIMIT 100
`, parameters);
return { data: rows };
}
@@ -169,7 +175,9 @@ export class InventoryStructureService {
const parent = await this.requireParent(manager, dto.kind, dto.parentId ?? null);
const family = await this.requireFamily(manager, dto.kind, dto.familyId ?? null, parent);
const generatedCode = dto.code?.trim().toUpperCase() || this.generatedCode(dto.kind, dto.name);
const operationalAreaId = parent ? await this.resolveAreaId(manager, parent) : null;
const operationalAreaId = parent && ['YACIMIENTO','INSTALACION','SUBINSTALACION'].includes(dto.kind)
? await this.resolveAreaId(manager, parent)
: null;
const inserted = (await manager.query(`
INSERT INTO assets (
@@ -193,9 +201,9 @@ export class InventoryStructureService {
AssetInformationStatus.DRAFT,
AssetOperationalStatus.UNKNOWN,
AssetDataOrigin.MANUAL,
dto.kind === 'EMPRESA' ? 'Maestro de Empresas F5' : 'Estructura de Inventario F5',
dto.kind === 'EMPRESA' ? 'Maestro manual de Empresas F5.1' : 'Estructura manual de Inventario F5.1',
dto.kind === 'EMPRESA' ? 'inventory-master:empresa' : `inventory-structure:${dto.kind.toLowerCase()}`,
family ? `Familia técnica: ${family.code} · ${family.name}` : null,
family ? `Clasificación técnica: ${family.code} · ${family.name}` : null,
principal.userId,
])) as Array<{ id: string }>;
const id = inserted[0]?.id;
@@ -225,7 +233,7 @@ export class InventoryStructureService {
id,
parent?.id ?? null,
operationalAreaId,
dto.kind === 'EMPRESA' ? 'Alta guiada de Empresa independiente F5' : 'Alta guiada de estructura de Inventario F5',
dto.kind === 'EMPRESA' ? 'Alta manual de Empresa independiente F5.1' : 'Alta manual de estructura de Inventario F5.1',
versionNumber,
request.requestId,
principal.userId,
@@ -285,7 +293,7 @@ export class InventoryStructureService {
code: 'INVENTORY_ROOT_MUST_NOT_HAVE_PARENT',
message: kind === 'EMPRESA'
? 'Una Empresa es un maestro independiente y no puede tener padre'
: 'Un Área es un registro raíz y no puede tener padre',
: 'Un Departamento es un registro raíz y no puede tener padre',
});
}
return null;
@@ -309,7 +317,7 @@ export class InventoryStructureService {
if (parent.typeCode.toLowerCase() !== expectedType) {
throw new BadRequestException({
code: 'INVENTORY_STRUCTURE_PARENT_INVALID',
message: 'La jerarquía requerida es Área → Yacimiento → Instalación → Subinstalación',
message: 'La jerarquía requerida es Departamento → Área → Yacimiento → Instalación → Subinstalación',
});
}
return parent;
@@ -349,13 +357,13 @@ export class InventoryStructureService {
if (!expectedLevel) {
if (familyId) throw new BadRequestException({
code: 'INVENTORY_STRUCTURE_FAMILY_NOT_ALLOWED',
message: 'Empresa, Área y Yacimiento no llevan familia técnica',
message: 'Empresa, Departamento, Área y Yacimiento no llevan clasificación técnica',
});
return null;
}
if (!familyId) throw new BadRequestException({
code: 'INVENTORY_STRUCTURE_FAMILY_REQUIRED',
message: `Elegí la familia técnica de la ${kind.toLowerCase()}`,
message: `Elegí la clasificación técnica de la ${kind.toLowerCase()}`,
});
const rows = (await manager.query(`
SELECT family.id,family.code,family.name,family.level,
@@ -369,22 +377,27 @@ export class InventoryStructureService {
LIMIT 1
`, [familyId])) as FamilyRow[];
const family = rows[0];
if (!family) throw new NotFoundException({ code: 'INVENTORY_FAMILY_NOT_FOUND', message: 'La familia técnica no existe' });
if (!family) throw new NotFoundException({ code: 'INVENTORY_FAMILY_NOT_FOUND', message: 'La clasificación técnica no existe' });
if (family.level !== expectedLevel) throw new BadRequestException({
code: 'INVENTORY_FAMILY_LEVEL_INVALID',
message: 'La familia técnica no corresponde al nivel seleccionado',
message: 'La clasificación técnica no corresponde al nivel seleccionado',
});
if (kind === 'SUBINSTALACION' && family.parentFamilyId !== parent?.inventoryFamilyId) {
throw new BadRequestException({
code: 'INVENTORY_SUBINSTALLATION_FAMILY_PARENT_INVALID',
message: 'La Subinstalación elegida no pertenece a la familia de la Instalación seleccionada',
message: 'La Subinstalación elegida no pertenece a la clasificación de la Instalación seleccionada',
});
}
return family;
}
private generatedCode(kind: InventoryStructureKind, name: string): string {
const prefix = kind === 'EMPRESA' ? 'EMP' : kind === 'YACIMIENTO' ? 'YAC' : kind === 'INSTALACION' ? 'INST' : kind === 'SUBINSTALACION' ? 'SUB' : 'AREA';
const prefix = kind === 'EMPRESA' ? 'EMP'
: kind === 'DEPARTAMENTO' ? 'DEP'
: kind === 'AREA' ? 'AREA'
: kind === 'YACIMIENTO' ? 'YAC'
: kind === 'INSTALACION' ? 'INST'
: 'SUB';
const readable = name
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
@@ -417,5 +430,3 @@ export class InventoryStructureService {
return rows[0];
}
}
type IdRow = { id: string };