portal_publico/portal_api/planos_saude/regras_empresa.py

117 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Regras de custeio especiais por empresa ("Regra empresa" na tela de nova
importação, terceiro item de "Tipo de importação" ao lado de Mensalidade/
Coparticipação) — ao contrário de RegraCusteioPlanoSaude (banco de regras
salvas, editável pelo usuário, sempre no formato "por tipo de lançamento ×
titular/dependente"), as regras aqui são cadastradas diretamente no código
pelo desenvolvedor quando o cliente repassa uma regra negociada com uma
empresa específica que não se encaixa nesse formato — tipicamente porque
são calculadas por FAMÍLIA (titular + todos os dependentes juntos), não
por pessoa. Nunca expostas para o usuário cadastrar pela tela; só
"Selecionar regra" na tela de nova importação, que lista as chaves já
registradas aqui (GET /api/importacoes-plano-saude/regras-empresa/).
Só se aplica ao tipo de lançamento "mensalidade" — coparticipação sempre
segue o custeio normal configurado na tela (por isso REGRAS_EMPRESA não
tem nada de coparticipação), e é por isso que "Regra empresa" e
"Mensalidade" são mutuamente exclusivos no formulário: as duas são formas
alternativas de configurar o MESMO tipo de lançamento "mensalidade".
"""
from typing import Any, Callable, Dict, List, Tuple
from portal_api.planos_saude.leiaute_sistema import formata_valor_br
from portal_api.planos_saude.modelos import LinhaSistema
class RegraEmpresaIncompativelError(Exception):
"""Regra empresa selecionada não é compatível com esta importação
(operadora sem casamento por nome, ou planilha padrão de uma empresa
diferente daquela pra qual a regra foi cadastrada) — views.py devolve
esta mensagem direto pro usuário, em vez do erro genérico de "formato
de arquivo não conforme"."""
def _eh_linha_titular(linha: Any) -> bool:
"""Mesmo critério de `LinhaSistema.eh_linha_titular()`, mas duck-typed —
`_aplica_teto_familia` roda tanto contra `LinhaSistema` (pipeline, na
criação da importação) quanto contra `ImportacaoPlanoSaudeLinha` (model
Django, no recálculo pós "Vincular pessoa" — ver
`views._recalcula_familia_regra_empresa`), que não tem esse método."""
return not (linha.nome_dependente or "").strip() and not (linha.cpf_dependente or "").strip()
def _aplica_teto_familia(linhas_e_valores: List[Tuple[Any, float]], teto: float) -> None:
"""Divide o teto de custeio da empresa (`teto`) entre as linhas de UMA
família (titular + dependentes) — decisão explícita do cliente:
DEPENDENTES TÊM PRIORIDADE no uso do teto (são cobertos primeiro, na
ordem em que aparecem — cada um recebe `min(seu valor, o que sobrou do
teto)`), e o TITULAR absorve por último o que sobrar do teto (o
"residual"). Se os dependentes sozinhos já consumirem o teto inteiro, o
titular fica com desconto integral (`valor_empresa=0`) e, se ainda
sobrar dependente sem cobrir depois disso, esse dependente também é
parcialmente descontado — ao contrário de uma divisão proporcional
(tentada numa primeira versão e revertida): o pedido do cliente é
"abater primeiro o valor dos dependentes", não repartir o teto
igualmente entre todos."""
dependentes = [(linha, valor) for linha, valor in linhas_e_valores if not _eh_linha_titular(linha)]
titulares = [(linha, valor) for linha, valor in linhas_e_valores if _eh_linha_titular(linha)]
teto_restante = teto
for linha, valor in dependentes + titulares:
valor = max(0.0, valor)
valor_empresa = round(min(valor, max(0.0, teto_restante)), 2)
valor_empregado = round(valor - valor_empresa, 2)
teto_restante = round(teto_restante - valor_empresa, 2)
linha.valor_empresa = formata_valor_br(valor_empresa)
linha.valor = formata_valor_br(valor_empregado)
def _regra_unimed_1778_tecnomyl(linhas_e_valores: List[Tuple[Any, float]]) -> None:
"""Tecnomyl (código 1778 na Unimed) — ajuda de custo de até R$ 661,61
por família (titular + dependentes juntos, não por pessoa), repassada
pelo cliente em 08/2026: família com mensalidade total acima do teto
tem o excedente descontado do empregado; igual ou abaixo do teto, a
empresa cobre 100% e o empregado não paga nada."""
_aplica_teto_familia(linhas_e_valores, teto=661.61)
# Pra cadastrar uma regra nova: escrever a função `_regra_...(linhas_e_valores)`
# acima (ou reaproveitar `_aplica_teto_familia` se for só um teto por família)
# e registrar aqui. `codigo_empresa` é o código da empresa na planilha padrão
# pra qual a regra foi negociada — usado só pra travar contra aplicar a regra
# errada numa planilha de outra empresa (ver `valida_regra_empresa` abaixo).
REGRAS_EMPRESA: Dict[str, dict] = {
"unimed_1778_tecnomyl": {
"label": "1778 - Unimed (Tecnomyl)",
"codigo_empresa": "1778",
"operadora": "unimed_saude",
"aplica": _regra_unimed_1778_tecnomyl,
},
}
def lista_regras_empresa() -> List[Dict[str, str]]:
return [{"key": chave, "label": dados["label"]} for chave, dados in REGRAS_EMPRESA.items()]
def valida_regra_empresa(
regra_empresa_key: str, chave_casamento: str, linhas_sistema: List[LinhaSistema]
) -> Callable[[List[Tuple[Any, float]]], None]:
"""Valida que a regra empresa escolhida pode ser aplicada nesta
importação e devolve a função de aplicação já resolvida — chamado por
pipeline.processa_importacao antes de rodar o casamento de mensalidade."""
regra = REGRAS_EMPRESA.get(regra_empresa_key)
if regra is None:
raise RegraEmpresaIncompativelError(f"Regra empresa desconhecida: {regra_empresa_key!r}.")
if chave_casamento != "nome":
raise RegraEmpresaIncompativelError(
"Esta operadora não é compatível com 'Regra empresa' (requer casamento por nome)."
)
codigo_esperado = regra.get("codigo_empresa")
if codigo_esperado and not any(l.codigo_empresa == codigo_esperado for l in linhas_sistema):
raise RegraEmpresaIncompativelError(
f"A regra \"{regra['label']}\" foi cadastrada para a empresa código {codigo_esperado}, "
"mas a planilha padrão anexada não tem nenhuma linha com esse código."
)
return regra["aplica"]