33 KiB
Indicador de Desempenho (Geradoc)
Movido do
CLAUDE.mdda raiz em 2026-08-26 para reduzir conflito de edição entre aplicações (documentação por app, código continua no mesmo lugar). VerCLAUDE.mdna raiz para arquitetura geral/transversal do Portal (modelo de permissões, API, CSS, etc.) — este arquivo é carregado automaticamente ao trabalhar dentro deportal_api/indicadores/. Ver também a skillindicador-desempenho(.claude/skills/) para contexto de negócio (por quê, limitações conhecidas).
Segunda ferramenta de Geradoc — substitui a apuração manual do indicador de desempenho do Fiscontábil (departamentos Contábil/Fiscal), antes feita numa planilha (FISCO CONTABIL *.ods, em projects/Indicadores/) com fórmulas quebradas por edições manuais acumuladas. Mesmo padrão de permissão de toggle único de Simulação de Custo de Contratação (apps["indicador-desempenho"] em permissoes["geradoc"], checado por PermissaoApp("geradoc", "indicador-desempenho") em todos os ModelViewSet relacionados). Pacote de negócio em portal_api/indicadores/ (sem ORM): tipos.py, leiaute.py, pipeline.py, entregas.py, calculo.py, recibo.py, departamentos.py.
-
Escopo v1: só o Fiscontábil, papéis Balancete/Liberação Fiscal/Conciliação Financeira. Outros departamentos ficam pra rodada futura — exceto pela estrutura de cadastro em si (ver "Departamento organizacional" abaixo), que já suporta múltiplos departamentos com critérios/percentuais próprios, mesmo que só o Fisco/Contábil tenha regras cadastradas até agora.
-
8 models (migrações
0024–0028,0033–0035):IndicadorDepartamento(cadastro de departamentos — nome/ativo — usado pra escopar critérios, percentuais e metas de Departamento; ver "Departamento organizacional" abaixo),IndicadorDepartamentoGerente(relação gerente→departamento, mantida manualmente pela aplicação),IndicadorPercentualTipo(percentuais individual/grupo/departamento por tipo de colaborador e por departamento, histórico viavigente_desde— nunca editado in-place),IndicadorCriterio(cadastro genérico de critério: departamento/nome/grupo/peso/período/papel/calculo_automatico/limiar_percentual),IndicadorApuracao(uma apuração mensal —competencia,statusrevisao/concluida, as 2 planilhas anexadas,avisosde processamento),IndicadorApuracaoColaborador(um colaborador dentro de uma apuração, compct_individual/pct_grupo/pct_departamentoe respectivos flags*_ajustado_manualmente, maisdepartamento— FK praIndicadorDepartamento, resolvida na criação via o gerente do colaborador, ver "Departamento organizacional" abaixo),IndicadorApuracaoEmpresa(uma empresa/honorário do colaborador naquele mês) eIndicadorApuracaoResposta(SIM/NÃO/NÃO FAZ/NÃO SE APLICA de um colaborador para um critério). -
Tipo do colaborador é derivado por empresa, não é cadastro:
TIPO_COLABORADOR_INDICADOR_CHOICES(Contábil+Fiscal/Contador SC/Contador CC/Fiscal/Conciliador) — regra emindicadores/tipos.py, validada contra um recibo-modelo real (~99,99% de precisão no teste com 43 colaboradores). -
3 critérios são calculados automaticamente a partir da planilha "Serviços Tareffa" (
indicadores/entregas.py/pipeline.py) — entrega de balancetes/liberações fiscais/conciliações no prazo, comparadas contraIndicadorCriterio.limiar_percentualpra decidir SIM/NÃO. Todo o resto é sempre marcação manual do RH (SIM/NÃO/NÃO FAZ/NÃO SE APLICA por critério, individual ou em lote). Um critério automático sem nenhum registro do serviço vira NÃO SE APLICA, não NÃO FAZ — permite deixarpapel_aplicavelem branco nesses 3 critérios (um mesmo critério, ex. "balancete", se aplica a 3 tipos ao mesmo tempo, epapel_aplicavelsó aceita um valor); quem não presta aquele serviço fica de fora do cálculo por conta própria (NÃO SE APLICA é excluído do denominador emcalculo.py). -
Fórmula:
honorario_ajustado = honorario_empresa × pct_individual_do_colaborador;valor_individual = honorario_ajustado × percentual_individual(tipo);valor_grupo/valor_departamento = valor_individual × percentual_grupo/departamento(tipo) × pct_grupo/departamento_do_colaborador.pct_individualnão é só a média dos critérios Individual — é a composição ponderada dos 3 níveis (Individual/Grupo/Departamento), cada um pesando conforme o peso médio dos seus próprios critérios aplicáveis na competência (calculo._combina_niveis/_peso_medio_nivel); sópct_grupo/pct_departamentocontinuam sendo a média simples dos próprios critérios, sem composição. -
"Cada gerente representa um grupo", cada departamento representa um departamento (não é redundante, ver abaixo):
pct_grupoé conceitualmente compartilhado por todos os colaboradores com o mesmogerentedentro da apuração, epct_departamentoé compartilhado por todos os colaboradores do mesmoIndicadorDepartamento(ver "Departamento organizacional" abaixo — não mais um valor único pra toda a apuração) — por isso não são ajustados colaborador a colaborador (IndicadorApuracaoColaboradorViewSetsó cobrepct_individual);IndicadorApuracaoViewSet.ajustar_grupo/recalcular_grupo/ajustar_departamento/recalcular_departamentoaplicam a mudança de uma vez a todo o grupo/departamento, mantendo os colaboradores em sincronia (ajustar_departamento/recalcular_departamentorecebemdepartamento— o id doIndicadorDepartamento— no corpo, filtrando com um.filter(departamento_id=...)direto). A tela (indicador-desempenho.js) reflete isso com uma tabela de "Metas de Grupo e Departamento" no topo (uma linha de Departamento porIndicadorDepartamento+ uma linha de Grupo por gerente dentro dele) separada da lista de colaboradores abaixo (que serve só pra revisão individual — percentual Individual, respostas de critério, recibo); botões de filtro por departamento (#ind-filtro-departamento, ver "Departamento organizacional" abaixo) restringem a tabela de Metas e a lista de colaboradores a um departamento de cada vez, sem afetar o cálculo de nenhuma meta. Colaborador cujo gerente não está mapeado a nenhum departamento cai num grupo "Sem departamento definido" (sem<select>de meta — não háIndicadorDepartamentopra aplicar). A meta de Grupo/Departamento é sempre Sim/Não (100%/0%), nunca um percentual livre — decisão explícita do usuário ("será pago ou não") — por isso a coluna "Meta (%)" dessa tabela é um<select class="ind-meta-select">com só essas duas opções (ehSim = valor >= 50decide qual aparece selecionada ao carregar), não um campo de texto livre; o valor enviado aajustar-grupo/ajustar-departamentoé sempre"100"ou"0". O percentual Individual de cada colaborador continua livre (é uma composição ponderada dos 3 níveis, pode legitimamente ser fracionário — ver acima). -
Departamento organizacional (
IndicadorDepartamento/IndicadorDepartamentoGerente/portal_api.indicadores.departamentos) — substituiu, numa rodada posterior, o mecanismo de "setor" (coluna bruta "departamento" da planilha Tareffa + fusão automática Contabilidade/Fiscal→Fisco-Contábil +IndicadorSetorApelido, cadastro-exceção por colaborador). Agora critérios e percentuais também são configurados por departamento (não só as metas de Grupo/Departamento) — decisão explícita do usuário: a regra do Fisco/Contábil pode ser diferente da regra do Condomínio, por exemplo.IndicadorDepartamento(nome/ativo) é um cadastro simples, mantido pela própria aplicação (Configurações → Departamentos); a relação com gerentes (IndicadorDepartamentoGerente,nome_gerenteúnico — um gerente pertence a só um departamento, mas um departamento pode ter vários gerentes, ex.: Fisco/Contábil tem "João Candido Rodrigues" e "Lhais Vergilio Delavy") também é mantida manualmente por ora — alimentar isso automaticamente a partir da planilha fica pra uma rodada futura (decisão explícita do usuário). Pra não obrigar o RH a redigitar nomes (arriscando um typo que faria uma apuração futura não casar com o departamento certo), o popup "Gerenciar Gerentes" (indicador-desempenho.js) mostra uma lista de sugestões clicáveis —carregarGerentesSugeridos()busca a apuração mais recente (GET /api/indicadores-apuracoes/, já ordenada por-competencia/-criado_em) e lista os nomes distintos decolaborador.gerenteque ainda não estão em nenhumIndicadorDepartamentoGerente; clicar numa sugestão já cria a relação pra aquele departamento. É só um atalho de UI (não muda a origem do dado) — o campo de texto livre continua disponível pra gerentes que não apareceram na última apuração.Resolução do departamento de um colaborador:
IndicadorApuracaoViewSet.create()montamapa_gerentes(departamentos.carrega_mapa_gerentes(),{nome_gerente: departamento_id}) uma vez e passa propipeline.processa_apuracao(), que resolvedepartamento_id = mapa_gerentes.get(colaborador.gerente)pra cada colaborador antes de decidir quais critérios automáticos calcular pra ele (críticos automáticos também são agrupados pordepartamento_id—criterios_automaticos_por_departamento, já que departamentos diferentes podem ter critérios/limiares diferentes). O resultado (IndicadorApuracaoColaborador.departamento, FK nullable) é um retrato daquele momento — mesmo espírito degerente/setorantes dele: se a relação gerente→departamento mudar depois, apurações já criadas não mudam sozinhas. Colaborador cujo gerente não está mapeado a nenhum departamento fica comdepartamento=Nonee vira um aviso no processamento ("Gerente X não está associado a nenhum departamento..."), além de não ganhar nenhumaIndicadorApuracaoResposta(sem departamento, não há de onde vir nenhum critério).IndicadorApuracaoColaboradorSerializerexpõedepartamento(id) +departamento_nome(com fallbackNone, mesmo padrão decriado_por_nome).Migração em 3 passos (mesmo padrão de qualquer FK obrigatória adicionada a uma tabela já populada):
0033cria os 2 models novos + adicionadepartamentonullable emIndicadorCriterio/IndicadorPercentualTipo/IndicadorApuracaoColaborador(e removesetor/IndicadorSetorApelido);0034(RunPython) cria o departamento "Fisco/Contábil" e aponta todoIndicadorCriterio/IndicadorPercentualTipojá existente pra ele (é literalmente o que a regra única representava até então);0035tornadepartamentoobrigatório emIndicadorCriterio/IndicadorPercentualTipo(não emIndicadorApuracaoColaborador, que continua nullable). Apurações criadas antes desta migração (e qualquer apuração nova, até o admin mapear os gerentes relevantes em Configurações → Departamentos) ficam comdepartamentoem branco em todos os colaboradores — precisam de um backfill pontual ou de serem reprocessadas depois que a relação gerente→departamento existir.Limitação conhecida, validada com dados reais: como a resolução é por
gerente(não por colaborador), dois subordinados diretos do mesmo gerente sempre caem no mesmo departamento — isso quebra o caso de uma gerente que supervisiona pessoas de departamentos diferentes. Ex. real: "Elizangela de Paula Kuhn" supervisiona diretamente os líderes de Fisco/Contábil (João Candido Rodrigues, Lhais Vergilio Delavy, Paloma Ramão, Elizangela dos Santos — departamento "Gerentes") e Luciane Gonzaga (que deveria cair em "Rocket", já que ela chefia esse outro departamento) — como todos compartilham o mesmogerente, mapear "Elizangela de Paula Kuhn" → "Gerentes" também classifica Luciane Gonzaga como "Gerentes", não "Rocket". Não existe mais um mecanismo de exceção por colaborador individual (o antigoIndicadorSetorApelidocobria exatamente esse tipo de caso) — se isso for um problema real, precisa ser resolvido numa rodada futura (ex.: reintroduzindo uma exceção por nome de colaborador, por cima da relação gerente→departamento). -
Detalhamento da composição no card do colaborador (
portal_api.indicadores.calculo.composicao_individual, exposto como o campocomposicao_individualdeIndicadorApuracaoColaboradorSerializer): reconstrói, só pra exibição, o percentual bruto de Individual (antes da composição) e o peso médio de cada um dos 3 níveis (_peso_medio_nivel) — dados querecalcula_colaboradorcalcula mas não persiste, por não precisar deles depois de gravarpct_individual. No cabeçalho do card (indicador-desempenho.js), essa linha ("Individual: X% (peso Y%) · Grupo: X% (peso Y%) · Departamento: X% (peso Y%)") fica ao lado do nome/gerente, numa coluna própria do grid centralizada — não embaixo — e cada um dos 3 níveis fica verde/vermelho conforme bateu 100% ou não; o "Total Indicador" (renomeado de "Individual", que épct_individual, com o lápis de ajuste manual sempre ao lado do valor numa linha que não quebra) fica neutro, sem cor, pra não repetir a mesma informação 4 vezes.composicao_individual()usacolaborador.respostas.all()(não.select_related("criterio")) de propósito, pra reaproveitar oprefetch_related("colaboradores__respostas__criterio")queIndicadorApuracaoViewSet.get_queryset()aplica só na actionretrieve— evita 1 query extra por colaborador ao abrir a tela de revisão. -
Tabela "Metas de Grupo e Departamento" só tem uma forma de responder Sim/Não por critério — a coluna "Meta (%)" (ajusta
pct_grupo/pct_departamentodireto). Existia um segundo<select>Sim/Não ao lado do texto de cada critério (bulk, viaaplicar-em-lote), removido por ser redundante com o da direita; a lista de critérios ali agora é só informativa (nome + peso). Responder um critério específico continua possível por colaborador, dentro da lista de colaboradores abaixo (renderRespostasGrupoHtml). -
"Corrigir Responsável" (
#ind-corrigir-responsavel-btn, popup próprio): busca uma empresa (por nome ou código, entre todas as empresas da apuração, não só as com problema de honorário —empresasAgrupadasPorCodigo(() => true)) e mostra, pra cada responsável dela (uma linha porIndicadorApuracaoEmpresa, ex.: "Valéria Bonete — Fiscal"), um<select>com os demais colaboradores da apuração pra reatribuir aquele papel. Reatribuir chamaPOST /api/indicadores-apuracoes-empresas/{id}/trocar-responsavel/(IndicadorApuracaoEmpresaViewSet.trocar_responsavel, serializerIndicadorApuracaoEmpresaTrocarResponsavelSerializercom{colaborador_id}), que só troca a FKcolaboradorda linha (codigo_empresa/tipo/honorário continuam os mesmos) e recalcula os dois colaboradores envolvidos (o que perdeu a empresa e o que ganhou) — validado no backend contra: colaborador de outra apuração, colaborador igual ao atual, e colaborador que já é responsável por essa mesma empresa/tipo (evitaria duas linhas duplicadas pra ele). O<select>exclui o colaborador atual das opções e nasce com um placeholder desabilitado ("Selecionar novo responsável…") pra nunca reatribuir sem escolha explícita. -
Checklist de revisão do RH (
IndicadorApuracaoColaborador.validado, migração0031): um checkbox no início de cada card (.ind-colaborador-card__validado, primeira coluna do grid do cabeçalho), sem relação com nenhum cálculo — só ajuda o RH a controlar quem já conferiu numa apuração com muitos colaboradores. Marcado, a borda do card inteiro fica verde (.ind-colaborador-card.is-validado, mesma largura de sempre, só muda a cor, pra não deslocar layout).POST /api/indicadores-apuracoes-colaboradores/{id}/marcar-validado/(marcar_validado, serializerIndicadorApuracaoColaboradorValidadoSerializercom{validado}) só grava o campo, sem chamarrecalcula_colaborador. Diferente dos outros ajustes desta tela, o frontend não recarrega a apuração inteira depois de marcar/desmarcar: atualiza só o card clicado localmente. Os outros ajustes recarregam porrecarregarRevisao(), que desde a revisão de interface de 30/09/2026 (rodada 47 do CHANGELOG) guarda os cards abertos, a rolagem e as respostas marcadas para o lote e os restaura depois do redesenho, então nenhum ajuste fecha mais o card em conferência; erro de rede reverte o checkbox e o estado em memória (mesmo padrão de outros toggles imediatos do app, ex. inativar usuário). O<label>inteiro (não só o<input>) precisa ficar de fora do gate de clique que expande/recolhe o card no cabeçalho, senão um clique na área do label (fora do glifo do checkbox) expande/recolhe o card ao mesmo tempo que marca/desmarca o validado — resultado de como labels HTML disparam dois eventos de clique encadeados. -
Forçar SIM num critério automático não vira 100% na média — a média ponderada usa o percentual real medido (
_fracao_atingidaemcalculo.py), mesmo que o RH marque SIM por cima; só critério manual (sempercentual_calculado) é binário SIM=100%/resto=0%. Pra dar crédito cheio apesar do percentual medido baixo, o RH ajusta o percentual agregado direto (nível 2 acima), não o critério. -
create()é atômico:IndicadorApuracaoViewSet.create()roda o pipeline inteiro (parse das 2 planilhas + persistência de colaboradores/empresas/respostas) dentro detransaction.atomic()— uma falha no meio (planilha fora do leiaute, overflow decimal) desfaz tudo no banco, devolvendo 400 genérico. As 2 planilhas não são gravadas (rodada 48 do CHANGELOG desta pasta, pedido do usuário: nada as lia depois da criação): comoleiaute.pypede caminho, a view as grava em arquivos temporários (_salva_arquivo_temporario()), apagados numfinally; o limite de 15MB fica no serializer (validar_tamanho_arquivo_indicador). -
POST /api/indicadores-apuracoes/{id}/gerar/monta um ZIP com um PDF de recibo por colaborador (indicadores/recibo.py,reportlab) a partir do que já está salvo — não reprocessa as planilhas, reflete qualquer ajuste manual feito na revisão. Recibo é documento interno (só quem tem a permissão do RH acessa/baixa) — sem visão própria do colaborador no Portal nesta v1. Botão "Gerar Recibos" (indicador-desempenho.js) abre um modal antes de chamar o endpoint — mesmo componente de busca por nome + filtro por departamento + checklist (com "marcar todos os resultados da busca") do "Ajuste Indicador em Lote", só que já nasce com todo mundo marcado (reproduz o comportamento antigo de "gerar pra todos" sem precisar marcar um por um); desmarcar alguns permite gerar recibo avulso de um colaborador só, de alguns específicos, ou de um departamento inteiro. O endpoint recebecolaborador_ids(lista, opcional) e só marca a apuração comoconcluidaquando o conjunto pedido bate com todos os colaboradores da apuração (semcolaborador_ids, ou uma seleção que cobre o total) — gerar um recibo avulso pra conferência não fecha a apuração inteira como se o mês estivesse todo revisado. -
Layout do PDF do recibo (
indicadores/recibo.py): o banner "PERCENTUAL DO INDICADOR INDIVIDUAL" sempre mostra o percentual efetivo/medido (calculo.composicao_individual()["total_calculado"]— a composição dos 3 níveis recalculada na hora, ignorando qualquer ajuste manual), nãocolaborador.pct_individualpuro — decisão explícita do usuário: se o RH/Diretoria sobrescreveu o Individual pra 100%, o banner precisa continuar mostrando o que o colaborador de fato atingiu (ex.: 74,29%), não o valor pago. Quandopct_individual_ajustado_manualmente=True, uma linha de detalhe abaixo do banner mostra "Percentual Individual Ajustado Pela Direção: 100,00%." (rótulo renomeado de "ajustado manualmente pelo RH", com o valor ajustado ao lado — antes só dizia que tinha sido ajustado, sem mostrar pra quanto) — os dois números lado a lado deixam claro o que foi medido e o que foi pago. Tabela "Empresas": toda célula (antes só "Empresa" eraParagraph, o resto strings soltas) virouParagraphcom estilo de alinhamento próprio (celula_centro/celula_direita/celula_negrito/celula_direita_negrito,_estilos()) — string solta não quebra linha dentro da coluna, e comALIGNà direita/centro um valor mais largo que a coluna (ex.: "Contador (com conciliador)" em Tipo, ou os totais em negrito, mais largos que a mesma string em peso normal) vazava visualmente por cima da célula vizinha em vez de quebrar linha — bug real visto com dados reais (coluna Tipo cobria "Hon. Ajustado"). Coluna "Tipo" ganhou um dicionário de labels curtos só pro PDF (TIPO_LABEL_CURTO: "Contador SC"/"Contador CC" em vez de "Contador (sem/com conciliador)" — mesma abreviação já usada informalmente neste documento) porque o label completo não cabia nem quebrando linha numa coluna estreita; a linha de total virou "Total do Indicador" (era "Total Resultado"). Larguras de coluna e padding lateral (LEFTPADDING/RIGHTPADDING, reduzidos de 6pt padrão do reportlab pra 3pt) ajustados pra caber os maiores valores reais vistos na apuração (ex.: R$ 28.023,16) numa linha só._moeda()usa (não espaço comum) entre "R$" e o número — com espaço comum, quando o valor não cabia numa linha só, o reportlab quebrava exatamente ali, deixando "R$" sozinho numa linha acima do número; com espaço não separável, o "R$" fica sempre grudado à esquerda do número (mesmo que precise de mais espaço na coluna pra caber tudo numa linha, resolvido junto pelas larguras/padding acima). Rótulo da linha de detalhe é "Percentual individual ajustado pela direção" (minúsculo, só a primeira letra maiúscula — não "Percentual Individual Ajustado Pela Direção"). -
aplicar_em_lote(IndicadorApuracaoRespostaViewSet,POST /api/indicadores-apuracoes-respostas/aplicar-em-lote/) aplica o mesmo valor a várias respostas de critério de uma vez — a "múltipla seleção" pedida pelo usuário na tela de revisão. -
"Empresas sem Honorário" (
#ind-empresas-sem-honorario-btn, cor de atenção —--danger, mesma linguagem visual do input/selo de honorário não encontrado, só enquanto houver alguma empresa pendente — sem nada pra resolver, o botão perde a classe.ind-empresas-sem-honorario-btn(volta a.btn-outlineneutro) e o texto vira "Visualizar Empresas com Honorário Ajustado (N)", apontando direto pra revisão do que já foi ajustado — substituiu o antigo checkbox "Só com honorário não encontrado" que filtrava a lista de colaboradores): abre um modal que agrupa porcodigo_empresatodas asIndicadorApuracaoEmpresacomhonorario_nao_encontrado=Trueda apuração — a mesma empresa pode aparecer sob mais de um colaborador (um responsável pelo balancete, outro pela liberação fiscal, outro pela conciliação financeira), mas o honorário é da empresa, não da pessoa. Preencher um valor ali chamaPOST /api/indicadores-apuracoes/{id}/ajustar-honorario-empresa/(IndicadorApuracaoViewSet.ajustar_honorario_empresa, serializerIndicadorApuracaoAjusteHonorarioEmpresaSerializercom{codigo_empresa, honorario}), que atualiza todas as linhas daquele código nesta apuração de uma vez (recalculando cada colaborador afetado) — diferente dePATCH /api/indicadores-apuracoes-empresas/{id}/(ainda existe, ajusta só uma linha por id, usado direto na tabela "Empresas" de dentro do card do colaborador). Os dois caminhos (linha única e em lote) marcamhonorario_ajustado_manualmente=Truena(s) linha(s) afetada(s) — fica permanente (sem UI de reverter, diferente depct_individual_ajustado_manualmente/etc., já que não existe um "automático" pra voltar quando o código nunca casou com a planilha) e vira uma nota "honorário ajustado manualmente" (cor--accent) ao lado do valor, na tabela "Empresas" de dentro do card do colaborador — visível só depois quehonorario_nao_encontradojá foi resolvido (os dois nunca aparecem juntos). Essa tabela também passou a listar porcodigo_empresa(IndicadorApuracaoEmpresa.Meta.ordering, migração0029), não mais por nome. Comocodigo_empresaéCharField, ordenar só por ele é ordem alfabética, não numérica — "80"/"503" apareciam depois de "2134" (o caractere'8'/'5'é "maior" que'1'/'2', mesmo o número sendo menor). Corrigido (migração0030) ordenando primeiro pelo tamanho da string (Length("codigo_empresa")) e só depois pelo valor — reproduz a ordem numérica certa pra códigos sem zero à esquerda (string mais curta = número menor, sempre) sem converter pra inteiro, o que quebraria com erro de banco se algum código um dia não fosse só dígitos. -
"Empresas ajustadas manualmente" é uma segunda seção dentro do mesmo popup "Empresas sem Honorário" — não um segundo botão/modal (revertido de propósito: nasceu como um botão separado, "Verificar Empresas Ajustadas Manualmente", e o usuário pediu pra unificar num popup só, "facilitando a usabilidade da ferramenta"). Fica escondida por padrão, atrás de um botão de largura cheia no final da lista principal (
#ind-empresas-ajustadas-toggle-btn,.ind-esh-toggle-btn, com contador — "Visualizar"/"Ocultar Empresas Ajustadas Manualmente (N)") — pedido explícito do usuário logo depois de testar a versão anterior (as duas seções sempre visíveis de uma vez): a lista secundária só deve aparecer sob demanda, no final do modal. Lista, também agrupada porcodigo_empresa, as empresas comhonorario_ajustado_manualmente=True— permite corrigir um valor já ajustado (campo já vem preenchido com o honorário atual, ao contrário da lista principal, que começa em branco). Reaproveita o mesmo endpointajustar-honorario-empresa— o filtro do backend cobreQ(honorario_nao_encontrado=True) | Q(honorario_ajustado_manualmente=True), nunca uma empresa cujo honorário só veio certo da planilha e nunca foi mexido. As duas listas compartilham as funções de agrupamento/renderização/ordenação (empresasAgrupadasPorCodigo,renderEmpresaGrupoItemHtml) emindicador-desempenho.js, parametrizadas só pelo filtro;renderEmpresasHonorario()sempre re-renderiza a lista principal e só re-renderiza a de "ajustadas" se a seção já estiver aberta (ao abrir o popup, essa seção sempre volta a fechar) — necessário porque corrigir uma empresa "sem honorário" faz ela migrar pra lista de "ajustadas" na hora, e essa migração só precisa refletir de imediato se o usuário já estiver olhando pra ela. As duas ficam dentro de um único wrapper que rola (.ind-esh-scroll), com título e "Fechar" sempre visíveis fora dele (mesmomax-height:85vhdo popup). Em cada item, o código aparece antes do nome da empresa no cabeçalho (.ind-esh-codigoseguido de.ind-esh-nome), mesma ordem da tabela "Empresas" do colaborador. -
"Ajuste Indicador em Lote" (
#ind-lote-global-btn,indicador-desempenho.js): modal separado do anterior — ajustapct_individual(não critérios) de vários colaboradores selecionados por nome de uma vez, pra dois casos binários só: "Ajustar" (#ind-lote-global-ajustar-btn, aplicapct_individual=100a todos, viaPATCH /api/indicadores-apuracoes-colaboradores/{id}/) ou "Reverter" (chama a actionrecalcularde cada um, voltando ao cálculo automático) — sem campo de percentual livre. Os dois botões sãobtn-solid(mesma cor) — só "Cancelar" ficabtn-outline, já que as duas ações são igualmente "reais", não uma primária e uma secundária. Não existe endpoint de lote dedicado pra isso; o frontend dispara um PATCH/POST por colaborador em paralelo (Promise.all). -
Percentuais/critérios são cadastro editável pela tela, não hardcoded — decisão explícita do usuário pra não fixar no código números incertos vindos da planilha antiga já quebrada. Primeiro histórico populado via
python manage.py seed_indicador_desempenho(idempotente), com os valores exatos da planilha antiga (vigente_desdefixado em 01/01/2024 por falta de data documentada — ajustar se o usuário informar a data real). -
Bugs de robustez corrigidos ao testar com 43 colaboradores reais:
openpyxl.load_workbook(..., read_only=True)precisa de.close()explícito (indicadores/leiaute.py), senão o Windows mantém o upload memory-mapped e bloqueia excluir a apuração depois; campos percentuais precisaram demax_digits=7(não 6) — qualquerDecimalFieldque representa um percentual "de 0 a 100" precisa demax_digits >= decimal_places + 3pra caber o "100" exato semDataError: numeric field overflow.
API
| Endpoint | Método | Uso |
|---|---|---|
/api/indicadores-percentuais-tipo/ |
GET/POST/DELETE | histórico de percentuais individual/grupo/departamento por tipo de colaborador (IndicadorPercentualTipo) — nunca editado in-place, só criado com vigente_desde novo; mesma permissão de toggle único apps["indicador-desempenho"] em permissoes["geradoc"] |
/api/indicadores-criterios/, /api/indicadores-criterios/{id}/ |
GET/POST/PATCH/DELETE | CRUD do cadastro genérico de critérios (IndicadorCriterio) — nome/grupo/peso/período/papel/cálculo automático livres, editável pelo RH |
/api/indicadores-apuracoes/, /api/indicadores-apuracoes/{id}/ |
GET/POST/DELETE | apuração mensal (IndicadorApuracao); POST é multipart (2 planilhas) e roda indicadores.pipeline.processa_apuracao() de forma síncrona dentro de um transaction.atomic(), persistindo colaboradores/empresas/respostas já calculados; DELETE também apaga os 2 arquivos de MEDIA_ROOT |
/api/indicadores-apuracoes/{id}/gerar/ |
POST | gera um ZIP com um PDF de recibo por colaborador (indicadores.recibo.gera_pdf_recibo), a partir do que já está salvo (não reprocessa as planilhas); colaborador_ids opcional no corpo restringe a geração a só esses colaboradores (modal "Gerar Recibos" — um colaborador só, alguns específicos, por departamento ou todos); marca a apuração como concluida só quando a seleção cobre todos os colaboradores |
/api/indicadores-apuracoes/{id}/ajustar-grupo/, /recalcular-grupo/ |
POST | ajusta (ou reverte) o pct_grupo de todos os colaboradores de um mesmo gerente na apuração de uma vez — "cada gerente representa um grupo" |
/api/indicadores-apuracoes/{id}/ajustar-departamento/, /recalcular-departamento/ |
POST | idem, mas aplica a todos os colaboradores do departamento (id de um IndicadorDepartamento) informado no corpo ({departamento, pct_departamento}/{departamento}) — cada departamento tem sua própria meta de Departamento |
/api/indicadores-departamentos/, /api/indicadores-departamentos/{id}/ |
GET/POST/PATCH/DELETE | cadastro de departamentos (IndicadorDepartamento, nome/ativo) — mesma permissão de toggle único do Indicador de Desempenho |
/api/indicadores-departamentos-gerentes/, /api/indicadores-departamentos-gerentes/{id}/ |
GET/POST/PATCH/DELETE | relação gerente→departamento (IndicadorDepartamentoGerente, nome_gerente único) — mesma permissão |
/api/indicadores-apuracoes/{id}/ajustar-honorario-empresa/ |
POST | {codigo_empresa, honorario} — preenche (ou corrige) o honorário de uma empresa com honorario_nao_encontrado=True ou honorario_ajustado_manualmente=True de uma vez pra todos os colaboradores desta apuração que a têm (mesmo código), recalculando cada um |
/api/indicadores-apuracoes-colaboradores/{id}/ |
GET/PATCH | ajuste manual do pct_individual de um colaborador (pct_individual_ajustado_manualmente=True); recalcula valor_total via indicadores.calculo.recalcula_colaborador |
/api/indicadores-apuracoes-colaboradores/{id}/recalcular/ |
POST | reverte pct_individual pro modo automático (limpa o ajuste manual) e recalcula |
/api/indicadores-apuracoes-colaboradores/{id}/marcar-validado/ |
POST | {validado} — checklist de revisão do RH, só grava o campo, sem recalcular nada |
/api/indicadores-apuracoes-empresas/{id}/trocar-responsavel/ |
POST | {colaborador_id} — reatribui essa linha (empresa+tipo) pra outro colaborador da mesma apuração, recalculando os dois |
/api/indicadores-apuracoes-empresas/{id}/ |
GET/PATCH | preenchimento manual do honorario de uma empresa com honorario_nao_encontrado=True (código não casou com a planilha de Honorários Por Cliente); zera essa flag e recalcula o colaborador |
/api/indicadores-apuracoes-respostas/{id}/ |
GET/PATCH | edição de uma resposta de critério (SIM/NÃO/NÃO FAZ/NÃO SE APLICA) já existente; recalcula o colaborador |
/api/indicadores-apuracoes-respostas/aplicar-em-lote/ |
POST | {resposta_ids, valor} — aplica o mesmo valor a várias respostas de uma vez (seleção múltipla da tela de revisão), recalculando todos os colaboradores afetados |
Estes endpoints moravam na tabela de API do
CLAUDE.mdda raiz e foram trazidos para cá: endpoint de aplicação mora na doc da aplicação. A raiz mantém só os transversais (auth,/api/me/, catálogo, perfis, usuários, favoritos, widgets, compromissos, notificações).