"""Regras de classificação de vencimento/prazo/categoria de ação — porta as regras de negócio da skill "analise-ncs" (ver `projects/Controladoria - Analise NCs/analise-ncs_SKILL.md`). IMPORTANTE: `status_prazo()`/`dias_para_vencer()` dependem da data de hoje — NUNCA persistir o resultado no banco, sempre calcular em tempo de leitura (serializer/dashboard). Só `vencimento_efetivo()` é seguro cachear num model (só muda com uma importação nova).""" from __future__ import annotations from datetime import date STATUS_FINALIZADA = "finalizada" STATUS_VENCIDA = "vencida" STATUS_VENCE_7 = "vence_7_dias" STATUS_VENCE_30 = "vence_30_dias" STATUS_NO_PRAZO = "no_prazo" STATUS_SEM_VENCIMENTO = "sem_vencimento" # Não é uma classificação por-ação real (status_prazo() nunca retorna isto) — # é só um nome de bucket agregado usado por `_filtra_status_prazo()` # (portal_api/views.py) e pelo chip "Em aberto" do frontend, equivalente a # "qualquer bucket exceto finalizada". NUNCA adicionar em STATUS_PRAZO_LABELS: # esse dict é iterado pra montar os contadores do dashboard, uma contagem por # classificação mutuamente exclusiva de ação — "não finalizada" não é # mutuamente exclusiva com vencida/vence_7_dias/etc, é a união delas. STATUS_NAO_FINALIZADA = "nao_finalizada" STATUS_PRAZO_LABELS = { STATUS_FINALIZADA: "Finalizada", STATUS_VENCIDA: "Vencida", STATUS_VENCE_7: "Vence em ≤7 dias", STATUS_VENCE_30: "Vence em ≤30 dias", STATUS_NO_PRAZO: "No prazo", STATUS_SEM_VENCIMENTO: "Sem vencimento", } CATEGORIA_CORRETIVA = "Ação Corretiva" CATEGORIA_CORRECAO = "Correção" CATEGORIA_OUTROS = "Outros" # Tipos de ocorrência que exigem Correção + Ação Corretiva (regra #9 da skill). TIPOS_NC = ("Não Conformidade", "Reclamação de Cliente") def vencimento_efetivo(prazo_prorrogado: date | None, data_conclusao: date | None) -> date | None: """Prazo Prorrogado se preenchido, senão Data da Conclusão da Ação (que no export é a data-limite prevista, não a conclusão real).""" return prazo_prorrogado or data_conclusao def categoria_acao(tipo_acao: str) -> str: tl = (tipo_acao or "").lower() if "corretiva" in tl: return CATEGORIA_CORRETIVA if "correç" in tl or "correc" in tl: return CATEGORIA_CORRECAO return CATEGORIA_OUTROS def status_prazo(venc: date | None, hoje: date, finalizada: bool = False) -> str: """`finalizada=True` (ação com "Data de Finalização" preenchida no Sigsistem) sempre vence qualquer outra classificação — uma ação já concluída nunca é "vencida"/"vence em X dias", mesmo que o prazo dela já tenha passado há muito tempo (comum em export com histórico: o `vencimento_efetivo` congelado continua no passado depois de concluída). Bug real corrigido: antes disso, reimportar um export com histórico de ações já finalizadas fazia a maioria aparecer como "vencida".""" if finalizada: return STATUS_FINALIZADA if venc is None: return STATUS_SEM_VENCIMENTO if venc < hoje: return STATUS_VENCIDA dias = (venc - hoje).days if dias <= 7: return STATUS_VENCE_7 if dias <= 30: return STATUS_VENCE_30 return STATUS_NO_PRAZO def dias_para_vencer(venc: date | None, hoje: date) -> int | None: if venc is None: return None return (venc - hoje).days