@anpdgovbr/shared-types
Version:
Biblioteca central de tipos TypeScript compartilhados para os projetos da ANPD (BETA)
455 lines (454 loc) • 15.9 kB
JavaScript
/**
* Utilitários para manipulação da estrutura organizacional (departamentos).
*
* Contém funções para converter a árvore em lista plana, buscar unidades,
* construir/parsear caminhos no formato AD, validar hierarquias e gerar
* visualizações hierárquicas amigáveis.
*/
import { TipoUnidade } from "../../enums";
// Importar dados estáticos
import departmentData from "./department.json";
/**
* Estrutura organizacional carregada do modelo estático.
*
* Contém a árvore hierárquica da organização, estatísticas e enums relacionados.
*/
const estruturaOrganizacional = departmentData;
/**
* Converter estrutura hierárquica em lista plana.
*
* Recebe uma unidade organizacional (nó da árvore) e produz uma lista plana
* de unidades (UnidadeFlat) contendo referências ao pai, filhos e metadados
* de hierarquia.
*
* @param unidade Unidade raiz a ser convertida.
* @param pai Unidade pai no nível superior (opcional).
* @param nivel Nível atual na árvore (padrão: 1).
* @param caminho Caminho acumulado de siglas até a unidade atual.
* @returns Array de UnidadeFlat representando a subárvore em lista plana.
*/
export function converterParaListaPlana(unidade, pai, nivel = 1, caminho = []) {
const caminhoAtual = [...caminho, unidade.sigla];
const unidadeFlat = {
codigo: unidade.codigo,
sigla: unidade.sigla,
nome: unidade.nome,
tipo: unidade.tipo || inferirTipoUnidade(unidade, nivel),
nivel,
hierarquia_path: caminhoAtual,
hierarquia_display: caminhoAtual.slice().reverse().join("/"),
// não atribuir `pai` quando undefined para manter compatibilidade com
// `exactOptionalPropertyTypes` do TS
filhos: [],
};
if (pai !== undefined) {
// Atribuição tipada de pai para evitar uso de `any` e satisfazer eslint
ligarPaiFilho(pai, unidadeFlat);
}
const resultado = [unidadeFlat];
// Processar filhos (compatibilidade com ambas as estruturas)
const filhos = unidade.unidades || unidade.filhos || [];
for (const filho of filhos) {
const filhosFlat = converterParaListaPlana(filho, unidadeFlat, nivel + 1, caminhoAtual);
unidadeFlat.filhos.push(...filhosFlat.filter((f) => f.pai === unidadeFlat));
resultado.push(...filhosFlat);
}
return resultado;
}
/**
* Inferir tipo de unidade baseado no contexto (sigla, nome e nível).
*
* A função aplica regras heurísticas para mapear uma unidade para um TipoUnidade
* (ex.: 'organizacao', 'coordenacao', 'servico', etc.), considerando siglas
* conhecidas, padrões no nome e fallback por nível.
*
* @param unidade Unidade organizacional a ser inferida.
* @param nivel Nível da unidade na árvore (1 = raiz).
* @returns TipoUnidade inferido.
*/
function inferirTipoUnidade(unidade, nivel) {
const sigla = unidade.sigla.toUpperCase();
const nome = unidade.nome.toLowerCase();
const tipoEspecial = isSiglaEspecial(sigla);
if (tipoEspecial)
return tipoEspecial;
const tipoPorNome = detectarPorNomePattern(nome);
if (tipoPorNome)
return tipoPorNome;
const tipoPorPrefixo = detectarPorPrefixo(sigla);
if (tipoPorPrefixo)
return tipoPorPrefixo;
return fallbackPorNivel(nivel);
}
function isSiglaEspecial(sigla) {
if (sigla === "ANPD")
return TipoUnidade.ORGANIZACAO;
if (sigla === "GABPR")
return TipoUnidade.GABINETE;
if (sigla === "SG")
return TipoUnidade.SECRETARIA_GERAL;
if (sigla === "COR")
return TipoUnidade.CORREGEDORIA;
if (sigla === "OUV")
return TipoUnidade.OUVIDORIA;
if (sigla.startsWith("PFE"))
return TipoUnidade.PROCURADORIA;
if (sigla === "CD" || sigla === "CNPDPP")
return TipoUnidade.CONSELHO;
return null;
}
function detectarPorNomePattern(nome) {
if (nome.includes("coordenação-geral"))
return TipoUnidade.COORDENACAO_GERAL;
if (nome.includes("coordenação"))
return TipoUnidade.COORDENACAO;
if (nome.includes("divisão"))
return TipoUnidade.DIVISAO;
if (nome.includes("serviço"))
return TipoUnidade.SERVICO;
if (nome.includes("setor"))
return TipoUnidade.SETOR;
return null;
}
function detectarPorPrefixo(sigla) {
if (sigla.startsWith("CG"))
return TipoUnidade.COORDENACAO_GERAL;
if (sigla.startsWith("DI"))
return TipoUnidade.DIVISAO;
if (sigla.startsWith("SE"))
return TipoUnidade.SERVICO;
return null;
}
function fallbackPorNivel(nivel) {
switch (nivel) {
case 1:
return TipoUnidade.ORGANIZACAO;
case 2:
return TipoUnidade.COORDENACAO_GERAL;
case 3:
return TipoUnidade.COORDENACAO;
case 4:
return TipoUnidade.SERVICO;
default:
return TipoUnidade.SETOR;
}
}
/**
* Obter todas as unidades como lista plana.
*
* @returns Array com todas as unidades da organização na forma plana (UnidadeFlat).
*/
export function obterTodasUnidadesFlat() {
return converterParaListaPlana(estruturaOrganizacional.organizacao);
}
/**
* Buscar unidade por sigla (case-insensitive).
*
* @param sigla Sigla da unidade a buscar.
* @returns UnidadeFlat correspondente ou undefined se não encontrada.
*/
export function buscarUnidadePorSigla(sigla) {
const unidades = obterTodasUnidadesFlat();
return unidades.find((unidade) => unidade.sigla.toLowerCase() === sigla.toLowerCase());
}
/**
* Buscar unidade por código SIORG.
*
* @param codigo Código SIORG da unidade.
* @returns UnidadeFlat correspondente ou undefined se não encontrada.
*/
export function buscarUnidadePorCodigo(codigo) {
const unidades = obterTodasUnidadesFlat();
return unidades.find((unidade) => unidade.codigo === codigo);
}
/**
* Buscar unidades por termo (sigla ou nome).
*
* Faz busca parcial (case-insensitive) em sigla e nome e retorna opções
* formatadas para uso em selects/autocomplete.
*
* @param termo Termo de busca.
* @returns Array de UnidadeOption com unidades que casam com o termo.
*/
export function buscarUnidades(termo) {
const termoLower = termo.toLowerCase().trim();
if (!termoLower) {
return [];
}
const unidades = obterTodasUnidadesFlat();
return unidades
.filter((unidade) => unidade.sigla.toLowerCase().includes(termoLower) ||
unidade.nome.toLowerCase().includes(termoLower))
.map((unidade) => ({
codigo: unidade.codigo,
sigla: unidade.sigla,
nome: unidade.nome,
tipo: unidade.tipo,
nivel: unidade.nivel,
hierarquia_display: unidade.hierarquia_display,
label: `${unidade.sigla} - ${unidade.nome}`,
}))
.sort((a, b) => a.sigla.localeCompare(b.sigla));
}
/**
* Obter todas as unidades como opções para select.
*
* @returns Array de UnidadeOption com todas as unidades disponíveis.
*/
export function obterOpcoesUnidadesTodas() {
const unidades = obterTodasUnidadesFlat();
return unidades
.map((unidade) => ({
codigo: unidade.codigo,
sigla: unidade.sigla,
nome: unidade.nome,
tipo: unidade.tipo,
nivel: unidade.nivel,
hierarquia_display: unidade.hierarquia_display,
label: `${unidade.sigla} - ${unidade.nome}`,
}))
.sort((a, b) => a.sigla.localeCompare(b.sigla));
}
/**
* Obter unidades por tipo.
*
* @param tipo Tipo de unidade (ex.: 'servico', 'setor', etc.).
* @returns Array de UnidadeOption filtrado pelo tipo informado.
*/
export function obterUnidadesPorTipo(tipo) {
const unidades = obterTodasUnidadesFlat();
return unidades
.filter((unidade) => unidade.tipo === tipo)
.map((unidade) => ({
codigo: unidade.codigo,
sigla: unidade.sigla,
nome: unidade.nome,
tipo: unidade.tipo,
nivel: unidade.nivel,
hierarquia_display: unidade.hierarquia_display,
label: `${unidade.sigla} - ${unidade.nome}`,
}))
.sort((a, b) => a.sigla.localeCompare(b.sigla));
}
/**
* Obter hierarquia completa de uma unidade.
*
* Retorna a unidade atual, o caminho completo da raiz até ela, as subordinadas
* diretas e todas as subordinadas (recursivamente).
*
* @param sigla Sigla da unidade alvo.
* @returns HierarquiaDepartamento com informações da hierarquia ou null se não encontrada.
*/
export function obterHierarquiaUnidade(sigla) {
const unidade = buscarUnidadePorSigla(sigla);
if (!unidade) {
return null;
}
// Construir caminho completo (da raiz até a unidade atual)
const caminhoCompleto = [];
let atual = unidade;
while (atual) {
caminhoCompleto.unshift(atual);
atual = atual.pai;
}
// Obter todas as subordinadas (diretas e indiretas)
const todasSubordinadas = obterTodasSubordinadas(unidade);
return {
unidade_atual: unidade,
caminho_completo: caminhoCompleto,
subordinadas_diretas: unidade.filhos,
todas_subordinadas: todasSubordinadas,
};
}
/**
* Obter todas as unidades subordinadas (recursivamente).
*
* Função helper interna que percorre a árvore de filhos retornando todas as
* unidades subordinadas diretas e indiretas.
*
* @param unidade UnidadeFlat cuja subárvore será listada.
* @returns Array de UnidadeFlat com todas as subordinadas.
*/
function obterTodasSubordinadas(unidade) {
const subordinadas = [...unidade.filhos];
for (const filho of unidade.filhos) {
subordinadas.push(...obterTodasSubordinadas(filho));
}
return subordinadas;
}
/**
* Atribui a referência de pai em `filho` de forma tipada.
* Se `filho` já tiver pai, sobrescreve com o novo pai informado.
*/
function ligarPaiFilho(pai, filho) {
filho.pai = pai;
}
/**
* Construir visualização hierárquica formatada para exibição textual.
*
* Gera uma string com linhas indentadas representando o caminho da raiz até
* a unidade, usando caracteres gráficos e espaços não-quebráveis para preservar
* alinhamento.
*
* @param sigla Sigla da unidade a ser exibida.
* @returns String formatada com a visualização hierárquica (linhas separadas por '\n').
*/
export function construirVisualizacaoHierarquica(sigla) {
const hierarquia = obterHierarquiaUnidade(sigla);
if (!hierarquia) {
return sigla;
}
const linhas = [];
for (let i = 0; i < hierarquia.caminho_completo.length; i++) {
const unidade = hierarquia.caminho_completo[i];
let prefixo = "";
if (i === 1) {
// Primeiro nível abaixo da raiz
prefixo = "└─";
}
else if (i === 2) {
// Segundo nível - usando espaços não-quebráveis
prefixo = "\u00A0\u00A0\u00A0\u00A0\u00A0└─";
}
else if (i === 3) {
// Terceiro nível - usando espaços não-quebráveis
prefixo = "\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0└─";
}
else if (i >= 4) {
// Níveis 4+: indentação crescente com espaços não-quebráveis
const espacos = "\u00A0".repeat(5 * (i - 1));
prefixo = `${espacos}└─`;
}
const linha = `${prefixo}${unidade.sigla} - ${unidade.nome}`;
linhas.push(linha);
}
return linhas.join("\n");
}
/**
* Converter caminho de hierarquia do AD para objeto estruturado.
*
* Ex.: "DDSS/CGTI/ANPD" -> { siglas: ["DDSS","CGTI","ANPD"], path_string: "...", unidade_principal: "DDSS" }
*
* @param adPath Caminho no formato AD.
* @returns ADDepartmentPath contendo siglas, path_string e unidade_principal.
*/
export function parseADDepartmentPath(adPath) {
const siglas = adPath
.split("/")
.map((s) => s.trim())
.filter(Boolean);
return {
siglas,
path_string: adPath,
unidade_principal: siglas[0] || "",
};
}
/**
* Construir caminho do AD a partir de uma sigla de unidade.
*
* Retorna o caminho formatado (hierarquia_display) ou a própria sigla se a unidade não for encontrada.
*
* @param sigla Sigla da unidade.
* @returns Caminho AD (ex.: "DDSS/CGTI/ANPD") ou a sigla original quando não encontrada.
*/
export function construirADDepartmentPath(sigla) {
const unidade = buscarUnidadePorSigla(sigla);
if (!unidade) {
return sigla;
}
return unidade.hierarquia_display;
}
/**
* Validar se um caminho de hierarquia é válido.
*
* Verifica existência das siglas, consistência da hierarquia e casos especiais
* (ex.: caminho de uma única sigla deve ser "ANPD").
*
* @param siglas Array de siglas representando o caminho hierárquico.
* @returns true se válido, false caso contrário.
*/
export function validarHierarquiaPath(siglas) {
if (siglas.length === 0) {
return false;
}
// Verificar se todas as siglas existem
for (const sigla of siglas) {
if (!buscarUnidadePorSigla(sigla)) {
return false;
}
}
// Verificar se a hierarquia está correta
if (siglas.length === 1) {
return siglas[0] === "ANPD";
}
// comparar com a unidade folha (última sigla informada)
const folhaSigla = siglas[siglas.length - 1];
const unidadeFolha = buscarUnidadePorSigla(folhaSigla);
if (!unidadeFolha)
return false;
const caminhoEsperado = unidadeFolha.hierarquia_path;
return JSON.stringify(siglas) === JSON.stringify(caminhoEsperado);
}
/**
* Obter unidades que podem ser pai de um determinado tipo.
*
* A função aplica regras de quais tipos são permitidos como pai para cada tipo
* de unidade e retorna as opções correspondentes.
*
* @param tipo Tipo de unidade cujo pai possível será buscado.
* @returns Array de UnidadeOption com os candidatos a pai.
*/
export function obterUnidadesPaiPossiveis(tipo) {
const regras = {
[TipoUnidade.ORGANIZACAO]: [],
[TipoUnidade.DIRETORIA]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.COORDENACAO_GERAL]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.COORDENACAO]: [TipoUnidade.COORDENACAO_GERAL],
[TipoUnidade.DIVISAO]: [
TipoUnidade.COORDENACAO_GERAL,
TipoUnidade.COORDENACAO,
],
[TipoUnidade.SERVICO]: [TipoUnidade.COORDENACAO, TipoUnidade.DIVISAO],
[TipoUnidade.SETOR]: [TipoUnidade.SERVICO],
[TipoUnidade.GABINETE]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.SECRETARIA_GERAL]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.OUVIDORIA]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.CORREGEDORIA]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.PROCURADORIA]: [TipoUnidade.ORGANIZACAO],
[TipoUnidade.CONSELHO]: [TipoUnidade.ORGANIZACAO],
};
const tiposPaiPermitidos = regras[tipo] || [];
const unidades = obterTodasUnidadesFlat();
return unidades
.filter((unidade) => tiposPaiPermitidos.includes(unidade.tipo))
.map((unidade) => ({
codigo: unidade.codigo,
sigla: unidade.sigla,
nome: unidade.nome,
tipo: unidade.tipo,
nivel: unidade.nivel,
hierarquia_display: unidade.hierarquia_display,
label: `${unidade.sigla} - ${unidade.nome}`,
}))
.sort((a, b) => a.sigla.localeCompare(b.sigla));
}
/**
* Obter estatísticas da estrutura organizacional.
*
* Retorna o objeto de estatísticas carregado do modelo estático.
*
* @returns Objeto de estatísticas da estrutura.
*/
export function obterEstatisticasEstrutura() {
return estruturaOrganizacional.estatisticas;
}
/**
* Obter enums disponíveis da estrutura organizacional.
*
* Retorna enums (tipos e constantes) presentes no modelo estático.
*
* @returns Objeto com enums da estrutura.
*/
export function obterEnumsEstrutura() {
return estruturaOrganizacional.enums;
}