87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""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
|