UNPKG

@anpdgovbr/shared-types

Version:

Biblioteca central de tipos TypeScript compartilhados para os projetos da ANPD (BETA)

455 lines (454 loc) 15.9 kB
/** * 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; }