Inclusão de regra de desconto máximo do empregado.
This commit is contained in:
parent
8942907947
commit
eb61b4f431
@ -305,3 +305,10 @@ Usuário reportou (empresa Questor 1970, "Rede Brasil de Mídia OOH LTDA", compe
|
|||||||
Causa: `_ITEM_COPARTICIPACAO_RE` exigia fronteira de palavra (`\b`) dos dois lados do código de "Tipo Serviço" (CON/EXA/HOS/CLI/ODO/MED). Neste arquivo, esse código vem colado sem espaço nenhum ao final do nome do Prestador (ex.: "...MARCELO FABRICCON 10101012...", "...LUCIANO GUSTAVEXA 40316572..." — mesmo estilo de coluna colada já visto no "Beneficiario" desde a rodada 83, só que numa coluna diferente), então a fronteira à esquerda nunca era satisfeita. O mesmo arquivo revelou, de quebra, mais dois valores não previstos: o grau de dependência "OUTROS DEP" (ausente de `_GRAUS_DEPENDENCIA`) e o código de tipo de serviço "CIR" (cirurgia — "Implante de dispositivo").
|
Causa: `_ITEM_COPARTICIPACAO_RE` exigia fronteira de palavra (`\b`) dos dois lados do código de "Tipo Serviço" (CON/EXA/HOS/CLI/ODO/MED). Neste arquivo, esse código vem colado sem espaço nenhum ao final do nome do Prestador (ex.: "...MARCELO FABRICCON 10101012...", "...LUCIANO GUSTAVEXA 40316572..." — mesmo estilo de coluna colada já visto no "Beneficiario" desde a rodada 83, só que numa coluna diferente), então a fronteira à esquerda nunca era satisfeita. O mesmo arquivo revelou, de quebra, mais dois valores não previstos: o grau de dependência "OUTROS DEP" (ausente de `_GRAUS_DEPENDENCIA`) e o código de tipo de serviço "CIR" (cirurgia — "Implante de dispositivo").
|
||||||
|
|
||||||
Corrigido em `operadoras/unimed/saude.py`: `_ITEM_COPARTICIPACAO_RE` perdeu a fronteira de palavra à esquerda (mantida só à direita, pra não casar um código no meio de outra palavra) e ganhou "CIR"; "OUTROS DEP" foi acrescentado a `_GRAUS_DEPENDENCIA` — sem esse segundo ajuste, mesmo com o regex do item corrigido, a coparticipação dessa dependente cairia por engano na pessoa anterior do bloco (mesmo bug do "COMPANHEIRO" truncado, ver acima). Validado rodando `extrai()` de ponta a ponta contra o arquivo real: 2 beneficiários (KARLA VANESSA R$247,74 + RAPHAELA SOUZ R$183,28), somando R$431,02 — bate exatamente com "Total da Familia: 431,02" impresso no relatório; reconfirmado, sem regressão, que a mensalidade da mesma competência continua extraindo os mesmos 3 beneficiários de antes.
|
Corrigido em `operadoras/unimed/saude.py`: `_ITEM_COPARTICIPACAO_RE` perdeu a fronteira de palavra à esquerda (mantida só à direita, pra não casar um código no meio de outra palavra) e ganhou "CIR"; "OUTROS DEP" foi acrescentado a `_GRAUS_DEPENDENCIA` — sem esse segundo ajuste, mesmo com o regex do item corrigido, a coparticipação dessa dependente cairia por engano na pessoa anterior do bloco (mesmo bug do "COMPANHEIRO" truncado, ver acima). Validado rodando `extrai()` de ponta a ponta contra o arquivo real: 2 beneficiários (KARLA VANESSA R$247,74 + RAPHAELA SOUZ R$183,28), somando R$431,02 — bate exatamente com "Total da Familia: 431,02" impresso no relatório; reconfirmado, sem regressão, que a mensalidade da mesma competência continua extraindo os mesmos 3 beneficiários de antes.
|
||||||
|
|
||||||
|
### "Regra específica": novo critério "Limite de desconto do empregado"
|
||||||
|
|
||||||
|
Usuário pediu uma terceira modalidade dentro de "Regra específica" (Cadastro de Regras): até então só existiam critérios que protegem o gasto da EMPRESA (`limite_valor`: teto de quanto ela cobre; `percentual`: fração do valor custeada por ela) — faltava a direção oposta, um teto de quanto é **descontado do empregado**, com a empresa absorvendo o restante sem limite algum (exemplo dado: mensalidade de R$150/R$200, desconto sempre limitado a R$10, empresa cobre R$140/R$190).
|
||||||
|
|
||||||
|
- **Backend**: `_monta_regra_custeio()` (`serializers.py`) ganhou um quarto parâmetro (`limite_desconto_empregado_bruto`) e passou a montar `regra["limite_desconto_empregado"]`; `matcher._calcula_valores()` ganhou um branch novo que, quando esse campo vem preenchido, calcula `valor_empregado = min(valor_total, limite_desconto_empregado)` e deriva `valor_empresa` como o complemento — **mutuamente exclusivo** com `limite_valor`/`percentual` (validado explicitamente em `_monta_regra_custeio`, erro claro se os dois grupos vierem preenchidos juntos), porque os dois protegem lados opostos do valor (teto da empresa vs. teto do empregado) e misturá-los não tem uma resolução determinística única quando entram em conflito. `ImportacaoPlanoSaudeCreateSerializer` ganhou os 4 campos `limite_desconto_empregado_<tipo>_<pessoa>` (mesmo padrão de `limite_valor_.../percentual_...` já existentes); `RegraCusteioPlanoSaudeSerializer.validate()` passou o novo campo adiante também. Nenhuma migração — continua dentro do mesmo `JSONField` (`custeio_por_tipo`), só um campo novo dentro do dict de cada combinação tipo×pessoa quando `modo="especifica"`.
|
||||||
|
- **Frontend** (`importacao-plano-saude.html`/`.js`): terceiro campo "Limite de desconto do empregado" acrescentado às 4 caixas de "Regra específica" (mensalidade/coparticipação × titular/dependente), num agrupamento visual separado (`.ips-regra-especifica__alt`, linha divisória) dos dois campos existentes, com hint próprio explicando a exclusividade. `atualizarExclusividadeRegraEspecifica()` (nova) desabilita ao vivo um grupo de campos assim que o outro é preenchido (não deixa o usuário sequer tentar preencher os dois) — chamada a cada tecla digitada nos três campos e sempre que o formulário é limpo (`limparCusteioForm()`) ou repopulado a partir de uma regra salva (`aplicarCusteio()`). `coletarCusteioAtual()`, `mensagemErroCusteio()`, `resumoModoPessoa()` (resumo só-leitura de "Nova Importação") e `montarFormDataDeRegra()` atualizados pra ler/validar/exibir/enviar o campo novo.
|
||||||
|
|||||||
@ -53,7 +53,14 @@ Pra adicionar uma operadora nova: criar `operadoras/<nome>/<arquivo>.py` impleme
|
|||||||
- **Toda resolução de família (titular/dependente) é feita por matrícula, nunca por nome** — a coluna "Usuário" do relatório de mensalidade é estreita e trunca nomes longos sem reticências (mesmo padrão de Amil/Bradesco/Humana, confirmado inspecionando os limites reais de x0/x1 do PDF: o nome pára exatamente na borda da coluna seguinte), então comparar o nome truncado da mensalidade com o nome completo do extrato de coparticipação para resolver `numero_titular`/`tipo` não seria confiável. Em vez disso, `_pessoa_por_matricula` (matrícula -> nome/tipo/numero_titular) é populado só ao processar a tabela de **mensalidade** (onde a família já vem corretamente resolvida por ordem de bloco: titular sempre antes dos próprios dependentes) e reaproveitado em `finaliza()` pra resolver os dois candidatos de coparticipação — que só carregam matrícula + valor, nada de nome. Um beneficiário com coparticipação mas ausente de toda tabela de mensalidade desta importação (arquivo daquele contrato não anexado) vira um `ItemAuditoria` explícito (`NAO_CADASTRADO`), nunca é descartado silenciosamente. Nomes truncados na mensalidade em si seguem o fluxo normal (`NOME_DIVERGENTE` em auditoria, resolvido manualmente uma vez via "Vincular pessoa" — nunca por aproximação).
|
- **Toda resolução de família (titular/dependente) é feita por matrícula, nunca por nome** — a coluna "Usuário" do relatório de mensalidade é estreita e trunca nomes longos sem reticências (mesmo padrão de Amil/Bradesco/Humana, confirmado inspecionando os limites reais de x0/x1 do PDF: o nome pára exatamente na borda da coluna seguinte), então comparar o nome truncado da mensalidade com o nome completo do extrato de coparticipação para resolver `numero_titular`/`tipo` não seria confiável. Em vez disso, `_pessoa_por_matricula` (matrícula -> nome/tipo/numero_titular) é populado só ao processar a tabela de **mensalidade** (onde a família já vem corretamente resolvida por ordem de bloco: titular sempre antes dos próprios dependentes) e reaproveitado em `finaliza()` pra resolver os dois candidatos de coparticipação — que só carregam matrícula + valor, nada de nome. Um beneficiário com coparticipação mas ausente de toda tabela de mensalidade desta importação (arquivo daquele contrato não anexado) vira um `ItemAuditoria` explícito (`NAO_CADASTRADO`), nunca é descartado silenciosamente. Nomes truncados na mensalidade em si seguem o fluxo normal (`NOME_DIVERGENTE` em auditoria, resolvido manualmente uma vez via "Vincular pessoa" — nunca por aproximação).
|
||||||
- Validado rodando `extrai()`/`finaliza()` de ponta a ponta contra os 3 arquivos reais da empresa 1972 (Fronteira Outdoor Ltda, competência 08/2026, 2 contratos — 183237 e 183210/"Estadual"): mensalidade batendo exatamente com os totais impressos (R$ 4.076,41 + R$ 808,98 = R$ 4.885,39, 12 beneficiários) e coparticipação batendo com R$ 1.067,17 (3 beneficiários), confirmando que a tabela embutida (também extraída, mesmos valores) foi corretamente descartada em favor do extrato separado, sem duplicar nada.
|
- Validado rodando `extrai()`/`finaliza()` de ponta a ponta contra os 3 arquivos reais da empresa 1972 (Fronteira Outdoor Ltda, competência 08/2026, 2 contratos — 183237 e 183210/"Estadual"): mensalidade batendo exatamente com os totais impressos (R$ 4.076,41 + R$ 808,98 = R$ 4.885,39, 12 beneficiários) e coparticipação batendo com R$ 1.067,17 (3 beneficiários), confirmando que a tabela embutida (também extraída, mesmos valores) foi corretamente descartada em favor do extrato separado, sem duplicar nada.
|
||||||
|
|
||||||
**Diferença deliberada em relação ao pipeline original**: lá, o valor do mês sempre gravava na coluna `VALOR` (desconto do empregado), nunca em `VALOREMPRESA` — regra fixa. Aqui, o usuário escolhe na tela de nova importação, **por tipo de lançamento (mensalidade/coparticipação) e por tipo de beneficiário (titular/dependente)** — quatro combinações independentes, ex.: mensalidade do titular custeada pela empresa e mensalidade do dependente descontada do empregado —, uma de três regras de custeio: "Custeado pela empresa" (`{"modo": "empresa"}`), "Descontado do empregado" (`{"modo": "empregado"}`, o comportamento antigo — nomenclatura "empregado", não "funcionário", pra não confundir com `NOMEFUNC`/`CPFFUNC` do leiaute do Questor, que é outra coisa) ou "Regra específica" (`{"modo": "especifica", "limite_valor": float|None, "percentual": float|None}`). Na regra específica, `limite_valor` é um teto de quanto a empresa cobre (o excedente vira desconto do empregado) e `percentual` é a fração do valor do mês custeada pela empresa (o resto vira desconto) — o usuário pode preencher só um dos dois ou os dois juntos; quando os dois vêm preenchidos, prevalece o que resultar no **menor** valor custeado pela empresa (mais restritivo), decisão explícita do usuário. Essa divisão é calculada por `matcher._calcula_valores(valor_total, regra)` (chamada por `_aplica_regra_custeio`, que grava `valor_empresa`/`valor` **os dois juntos** a partir do mesmo `valor_total`) — note que `valor_empresa` é arredondado primeiro e `valor` é derivado como o complemento exato (`valor_total - valor_empresa`, também arredondado), nunca os dois arredondados de forma independente, senão a soma dos dois podia ficar 1 centavo a mais/menos que o valor original (ex.: 50% de 51,69 tem que fechar em 25,84 + 25,85 = 51,69, não 25,85 + 25,85). Qual das duas regras (titular ou dependente) usar em cada `Individuo`/`LinhaSistema` é resolvido por `matcher._regra_para_pessoa(regra_por_pessoa, tipo_pessoa)` — `tipo_pessoa` 'T' cai em "titular", 'D'/'A' caem em "dependente" (mesmo critério de "D e A tratados igual" já usado no resto do leiaute) — chamada nos dois pontos de aplicação de `_casa_por_cpf`/`_casa_por_nome` antes de `_aplica_regra_custeio`.
|
**Diferença deliberada em relação ao pipeline original**: lá, o valor do mês sempre gravava na coluna `VALOR` (desconto do empregado), nunca em `VALOREMPRESA` — regra fixa. Aqui, o usuário escolhe na tela de nova importação, **por tipo de lançamento (mensalidade/coparticipação) e por tipo de beneficiário (titular/dependente)** — quatro combinações independentes, ex.: mensalidade do titular custeada pela empresa e mensalidade do dependente descontada do empregado —, uma de três regras de custeio: "Custeado pela empresa" (`{"modo": "empresa"}`), "Descontado do empregado" (`{"modo": "empregado"}`, o comportamento antigo — nomenclatura "empregado", não "funcionário", pra não confundir com `NOMEFUNC`/`CPFFUNC` do leiaute do Questor, que é outra coisa) ou "Regra específica" (`{"modo": "especifica", "limite_valor": float|None, "percentual": float|None, "limite_desconto_empregado": float|None}`).
|
||||||
|
|
||||||
|
Dentro da regra específica, dois grupos de critério, **mutuamente exclusivos** entre si (validado em `_monta_regra_custeio`, `serializers.py`, e refletido no formulário desabilitando um grupo assim que o outro é preenchido — `atualizarExclusividadeRegraEspecifica()`, `importacao-plano-saude.js`):
|
||||||
|
|
||||||
|
- **`limite_valor`/`percentual`** protegem o gasto da **empresa**: `limite_valor` é um teto de quanto ela cobre (o excedente vira desconto do empregado) e `percentual` é a fração do valor do mês custeada por ela (o resto vira desconto). Podem vir só um dos dois ou os dois juntos; quando os dois vêm preenchidos, prevalece o que resultar no **menor** valor custeado pela empresa (mais restritivo), decisão explícita do usuário.
|
||||||
|
- **`limite_desconto_empregado`** protege o gasto do **empregado** (adicionado a pedido do usuário, ex.: mensalidade de R$150/R$200 com desconto sempre limitado a R$10, a empresa absorve o restante — R$140/R$190): teto de quanto é descontado dele, sem limite algum pro que sobra pra empresa. Direção oposta da anterior — combinar os dois grupos não teria uma resolução determinística única quando entrassem em conflito (ex.: um teto de empresa que por si só implicaria um desconto maior que o teto de empregado permitido), por isso o formulário nunca deixa preencher os dois grupos ao mesmo tempo pra uma mesma combinação tipo×pessoa.
|
||||||
|
|
||||||
|
Essa divisão é calculada por `matcher._calcula_valores(valor_total, regra)` (chamada por `_aplica_regra_custeio`, que grava `valor_empresa`/`valor` **os dois juntos** a partir do mesmo `valor_total`) — note que o valor "protegido" (empresa ou empregado, conforme o grupo de critério usado) é arredondado primeiro e o outro é derivado como o complemento exato (`valor_total` menos o protegido, também arredondado), nunca os dois arredondados de forma independente, senão a soma dos dois podia ficar 1 centavo a mais/menos que o valor original (ex.: 50% de 51,69 tem que fechar em 25,84 + 25,85 = 51,69, não 25,85 + 25,85). Qual das duas regras (titular ou dependente) usar em cada `Individuo`/`LinhaSistema` é resolvido por `matcher._regra_para_pessoa(regra_por_pessoa, tipo_pessoa)` — `tipo_pessoa` 'T' cai em "titular", 'D'/'A' caem em "dependente" (mesmo critério de "D e A tratados igual" já usado no resto do leiaute) — chamada nos dois pontos de aplicação de `_casa_por_cpf`/`_casa_por_nome` antes de `_aplica_regra_custeio`.
|
||||||
|
|
||||||
## Modelos (`portal_api/models.py`)
|
## Modelos (`portal_api/models.py`)
|
||||||
|
|
||||||
|
|||||||
@ -76,17 +76,26 @@ def _calcula_valores(valor_total: float, regra: Optional[dict]) -> Tuple[float,
|
|||||||
"""
|
"""
|
||||||
Divide `valor_total` entre (valor_empresa, valor_empregado) conforme
|
Divide `valor_total` entre (valor_empresa, valor_empregado) conforme
|
||||||
`regra` (`{"modo": "empresa"|"empregado"|"especifica", "limite_valor":
|
`regra` (`{"modo": "empresa"|"empregado"|"especifica", "limite_valor":
|
||||||
float|None, "percentual": float|None}`):
|
float|None, "percentual": float|None, "limite_desconto_empregado":
|
||||||
|
float|None}`):
|
||||||
|
|
||||||
- "empresa" -> tudo em valor_empresa.
|
- "empresa" -> tudo em valor_empresa.
|
||||||
- "empregado" -> tudo em valor (desconto do empregado) — comportamento
|
- "empregado" -> tudo em valor (desconto do empregado) — comportamento
|
||||||
padrão de antes desta funcionalidade existir.
|
padrão de antes desta funcionalidade existir.
|
||||||
- "especifica" -> `limite_valor` é o teto de quanto a empresa cobre
|
- "especifica" -> duas famílias de critério, mutuamente exclusivas
|
||||||
(excedente vira desconto do empregado); `percentual` é a fração do
|
(garantido por `_monta_regra_custeio`/`serializers.py`, nunca as duas
|
||||||
valor do mês que a empresa cobre (o resto vira desconto). Pode vir só
|
preenchidas ao mesmo tempo):
|
||||||
um dos dois ou os dois — quando os dois vêm juntos, prevalece o mais
|
- `limite_valor`/`percentual` protegem o gasto da EMPRESA: `limite_valor`
|
||||||
restritivo (o menor valor entre os dois critérios), decisão explícita
|
é o teto de quanto ela cobre (excedente vira desconto do empregado);
|
||||||
do usuário ("pode ser aplicado apenas uma destas regras ou as duas").
|
`percentual` é a fração do valor do mês que ela cobre (o resto vira
|
||||||
|
desconto). Pode vir só um dos dois ou os dois — quando os dois vêm
|
||||||
|
juntos, prevalece o mais restritivo (o menor valor entre os dois
|
||||||
|
critérios), decisão explícita do usuário ("pode ser aplicado apenas
|
||||||
|
uma destas regras ou as duas").
|
||||||
|
- `limite_desconto_empregado` protege o gasto do EMPREGADO: teto de
|
||||||
|
quanto é descontado dele (o restante, sem limite, fica com a
|
||||||
|
empresa) — direção oposta da anterior, por isso não se combina com
|
||||||
|
ela.
|
||||||
"""
|
"""
|
||||||
regra = regra or REGRA_CUSTEIO_PADRAO
|
regra = regra or REGRA_CUSTEIO_PADRAO
|
||||||
modo = regra.get("modo", "empregado")
|
modo = regra.get("modo", "empregado")
|
||||||
@ -96,6 +105,12 @@ def _calcula_valores(valor_total: float, regra: Optional[dict]) -> Tuple[float,
|
|||||||
if modo != "especifica":
|
if modo != "especifica":
|
||||||
return 0.0, valor_total
|
return 0.0, valor_total
|
||||||
|
|
||||||
|
limite_desconto_empregado = regra.get("limite_desconto_empregado")
|
||||||
|
if limite_desconto_empregado is not None:
|
||||||
|
valor_empregado = round(max(0.0, min(valor_total, limite_desconto_empregado)), 2)
|
||||||
|
valor_empresa = round(valor_total - valor_empregado, 2)
|
||||||
|
return valor_empresa, valor_empregado
|
||||||
|
|
||||||
candidatos = [valor_total]
|
candidatos = [valor_total]
|
||||||
percentual = regra.get("percentual")
|
percentual = regra.get("percentual")
|
||||||
if percentual is not None:
|
if percentual is not None:
|
||||||
|
|||||||
@ -469,13 +469,28 @@ class FuncaoTelefoniaSerializer(serializers.ModelSerializer):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _monta_regra_custeio(chave: str, modo: str | None, limite_bruto: str, percentual_bruto: str) -> dict[str, Any]:
|
def _monta_regra_custeio(
|
||||||
|
chave: str,
|
||||||
|
modo: str | None,
|
||||||
|
limite_bruto: str,
|
||||||
|
percentual_bruto: str,
|
||||||
|
limite_desconto_empregado_bruto: str = "",
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Valida e monta uma única regra de custeio (uma combinação tipo de
|
"""Valida e monta uma única regra de custeio (uma combinação tipo de
|
||||||
lançamento × tipo de beneficiário) — reaproveitado por
|
lançamento × tipo de beneficiário) — reaproveitado por
|
||||||
ImportacaoPlanoSaudeCreateSerializer (form da nova importação) e
|
ImportacaoPlanoSaudeCreateSerializer (form da nova importação) e
|
||||||
RegraCusteioPlanoSaudeSerializer (banco de regras salvas), já que as duas
|
RegraCusteioPlanoSaudeSerializer (banco de regras salvas), já que as duas
|
||||||
telas usam exatamente a mesma regra de negócio de custeio (ver "Regra de
|
telas usam exatamente a mesma regra de negócio de custeio (ver "Regra de
|
||||||
custeio" no formulário de nova importação)."""
|
custeio" no formulário de nova importação).
|
||||||
|
|
||||||
|
`limite_valor`/`percentual` protegem o gasto da EMPRESA (tetos de quanto
|
||||||
|
ela cobre, o excedente vira desconto do empregado) e são combináveis
|
||||||
|
entre si (vale o mais restritivo). `limite_desconto_empregado` protege o
|
||||||
|
gasto do EMPREGADO (teto de quanto é descontado dele, o restante fica
|
||||||
|
com a empresa) — direção oposta, por isso é mutuamente exclusivo com os
|
||||||
|
outros dois: misturar um teto do lado da empresa com um teto do lado do
|
||||||
|
empregado não tem uma resolução determinística única quando os dois
|
||||||
|
conflitam (ver `_calcula_valores` em matcher.py)."""
|
||||||
if not modo:
|
if not modo:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{f"custeio_{chave}": "Informe como esse tipo é custeado para titular e dependente."}
|
{f"custeio_{chave}": "Informe como esse tipo é custeado para titular e dependente."}
|
||||||
@ -487,12 +502,33 @@ def _monta_regra_custeio(chave: str, modo: str | None, limite_bruto: str, percen
|
|||||||
|
|
||||||
limite_bruto = (limite_bruto or "").strip()
|
limite_bruto = (limite_bruto or "").strip()
|
||||||
percentual_bruto = (percentual_bruto or "").strip()
|
percentual_bruto = (percentual_bruto or "").strip()
|
||||||
if not limite_bruto and not percentual_bruto:
|
limite_desconto_empregado_bruto = (limite_desconto_empregado_bruto or "").strip()
|
||||||
|
|
||||||
|
if limite_desconto_empregado_bruto and (limite_bruto or percentual_bruto):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{f"custeio_{chave}": "Informe o limite de valor e/ou o percentual de custeio da empresa."}
|
{
|
||||||
|
f"custeio_{chave}": (
|
||||||
|
"O limite de desconto do empregado não pode ser combinado com o limite de "
|
||||||
|
"valor/percentual custeado pela empresa."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if not limite_bruto and not percentual_bruto and not limite_desconto_empregado_bruto:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
{
|
||||||
|
f"custeio_{chave}": (
|
||||||
|
"Informe o limite de valor e/ou o percentual de custeio da empresa, ou o limite "
|
||||||
|
"de desconto do empregado."
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
regra: dict[str, Any] = {"modo": "especifica", "limite_valor": None, "percentual": None}
|
regra: dict[str, Any] = {
|
||||||
|
"modo": "especifica",
|
||||||
|
"limite_valor": None,
|
||||||
|
"percentual": None,
|
||||||
|
"limite_desconto_empregado": None,
|
||||||
|
}
|
||||||
if limite_bruto:
|
if limite_bruto:
|
||||||
limite = parse_valor_br(limite_bruto)
|
limite = parse_valor_br(limite_bruto)
|
||||||
if limite < 0:
|
if limite < 0:
|
||||||
@ -503,6 +539,11 @@ def _monta_regra_custeio(chave: str, modo: str | None, limite_bruto: str, percen
|
|||||||
if not (0 <= percentual <= 100):
|
if not (0 <= percentual <= 100):
|
||||||
raise serializers.ValidationError({f"percentual_{chave}": "Informe um percentual entre 0 e 100."})
|
raise serializers.ValidationError({f"percentual_{chave}": "Informe um percentual entre 0 e 100."})
|
||||||
regra["percentual"] = percentual
|
regra["percentual"] = percentual
|
||||||
|
if limite_desconto_empregado_bruto:
|
||||||
|
limite_desconto = parse_valor_br(limite_desconto_empregado_bruto)
|
||||||
|
if limite_desconto < 0:
|
||||||
|
raise serializers.ValidationError({f"limite_desconto_empregado_{chave}": "Informe um valor válido."})
|
||||||
|
regra["limite_desconto_empregado"] = limite_desconto
|
||||||
return regra
|
return regra
|
||||||
|
|
||||||
|
|
||||||
@ -567,12 +608,16 @@ class ImportacaoPlanoSaudeCreateSerializer(serializers.Serializer):
|
|||||||
# do formulário, igual aos outros valores monetários do pipeline.
|
# do formulário, igual aos outros valores monetários do pipeline.
|
||||||
limite_valor_mensalidade_titular = serializers.CharField(required=False, allow_blank=True)
|
limite_valor_mensalidade_titular = serializers.CharField(required=False, allow_blank=True)
|
||||||
percentual_mensalidade_titular = serializers.CharField(required=False, allow_blank=True)
|
percentual_mensalidade_titular = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
limite_desconto_empregado_mensalidade_titular = serializers.CharField(required=False, allow_blank=True)
|
||||||
limite_valor_mensalidade_dependente = serializers.CharField(required=False, allow_blank=True)
|
limite_valor_mensalidade_dependente = serializers.CharField(required=False, allow_blank=True)
|
||||||
percentual_mensalidade_dependente = serializers.CharField(required=False, allow_blank=True)
|
percentual_mensalidade_dependente = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
limite_desconto_empregado_mensalidade_dependente = serializers.CharField(required=False, allow_blank=True)
|
||||||
limite_valor_coparticipacao_titular = serializers.CharField(required=False, allow_blank=True)
|
limite_valor_coparticipacao_titular = serializers.CharField(required=False, allow_blank=True)
|
||||||
percentual_coparticipacao_titular = serializers.CharField(required=False, allow_blank=True)
|
percentual_coparticipacao_titular = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
limite_desconto_empregado_coparticipacao_titular = serializers.CharField(required=False, allow_blank=True)
|
||||||
limite_valor_coparticipacao_dependente = serializers.CharField(required=False, allow_blank=True)
|
limite_valor_coparticipacao_dependente = serializers.CharField(required=False, allow_blank=True)
|
||||||
percentual_coparticipacao_dependente = serializers.CharField(required=False, allow_blank=True)
|
percentual_coparticipacao_dependente = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
limite_desconto_empregado_coparticipacao_dependente = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
|
||||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||||
tipos = [t.strip() for t in attrs["tipos_lancamento"].split(",") if t.strip()]
|
tipos = [t.strip() for t in attrs["tipos_lancamento"].split(",") if t.strip()]
|
||||||
@ -628,6 +673,7 @@ class ImportacaoPlanoSaudeCreateSerializer(serializers.Serializer):
|
|||||||
attrs.get(f"custeio_{chave}"),
|
attrs.get(f"custeio_{chave}"),
|
||||||
attrs.get(f"limite_valor_{chave}", ""),
|
attrs.get(f"limite_valor_{chave}", ""),
|
||||||
attrs.get(f"percentual_{chave}", ""),
|
attrs.get(f"percentual_{chave}", ""),
|
||||||
|
attrs.get(f"limite_desconto_empregado_{chave}", ""),
|
||||||
)
|
)
|
||||||
custeio_por_tipo[tipo] = custeio_por_pessoa
|
custeio_por_tipo[tipo] = custeio_por_pessoa
|
||||||
|
|
||||||
@ -1091,6 +1137,7 @@ class RegraCusteioPlanoSaudeSerializer(serializers.ModelSerializer):
|
|||||||
entrada.get("modo"),
|
entrada.get("modo"),
|
||||||
_valor_custeio_para_texto_br(entrada.get("limite_valor")),
|
_valor_custeio_para_texto_br(entrada.get("limite_valor")),
|
||||||
_valor_custeio_para_texto_br(entrada.get("percentual")),
|
_valor_custeio_para_texto_br(entrada.get("percentual")),
|
||||||
|
_valor_custeio_para_texto_br(entrada.get("limite_desconto_empregado")),
|
||||||
)
|
)
|
||||||
custeio_validado[tipo] = custeio_por_pessoa
|
custeio_validado[tipo] = custeio_por_pessoa
|
||||||
|
|
||||||
|
|||||||
@ -655,6 +655,21 @@
|
|||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Agrupa "Limite de desconto do empregado" visualmente separado dos dois
|
||||||
|
campos do lado da empresa acima (linha divisória) — são mutuamente
|
||||||
|
exclusivos (ver atualizarExclusividadeRegraEspecifica(), JS), então o
|
||||||
|
divisor reforça que são dois "modos" alternativos, não um conjunto só. */
|
||||||
|
.ips-regra-especifica__alt {
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
padding-top: var(--space-3);
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ips-regra-especifica input:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* Resumo por tipo, acima de cada tabela da revisão */
|
/* Resumo por tipo, acima de cada tabela da revisão */
|
||||||
.ips-resumo {
|
.ips-resumo {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -882,13 +882,38 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const regraBox = document.getElementById(`ips-regra-especifica-${chave}`);
|
const regraBox = document.getElementById(`ips-regra-especifica-${chave}`);
|
||||||
const limiteInput = document.getElementById(`ips-limite-${chave}`);
|
const limiteInput = document.getElementById(`ips-limite-${chave}`);
|
||||||
const percentualInput = document.getElementById(`ips-percentual-${chave}`);
|
const percentualInput = document.getElementById(`ips-percentual-${chave}`);
|
||||||
|
const limiteDescontoInput = document.getElementById(`ips-limite-desconto-${chave}`);
|
||||||
if (regraBox) regraBox.hidden = true;
|
if (regraBox) regraBox.hidden = true;
|
||||||
if (limiteInput) limiteInput.value = "";
|
if (limiteInput) limiteInput.value = "";
|
||||||
if (percentualInput) percentualInput.value = "";
|
if (percentualInput) percentualInput.value = "";
|
||||||
|
if (limiteDescontoInput) limiteDescontoInput.value = "";
|
||||||
|
atualizarExclusividadeRegraEspecifica(chave);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "Limite de desconto do empregado" é mutuamente exclusivo com "Limite de
|
||||||
|
// valor custeado pela empresa"/"% de custeio da empresa" dentro da mesma
|
||||||
|
// combinação tipo×pessoa (pedido explícito do usuário) — os dois lados
|
||||||
|
// protegem partes opostas do valor (teto de quanto a empresa paga vs. teto
|
||||||
|
// de quanto o empregado paga), então misturar os dois não tem um resultado
|
||||||
|
// determinístico único (ver `_calcula_valores`/`_monta_regra_custeio` no
|
||||||
|
// backend, que valida a mesma exclusividade). Preencher um lado desabilita
|
||||||
|
// visualmente o outro, refletindo no formulário a mesma regra que o
|
||||||
|
// backend aplica — não limpa o valor do lado desabilitado, só impede
|
||||||
|
// digitar mais nele enquanto o outro lado estiver preenchido.
|
||||||
|
function atualizarExclusividadeRegraEspecifica(chave) {
|
||||||
|
const limiteInput = document.getElementById(`ips-limite-${chave}`);
|
||||||
|
const percentualInput = document.getElementById(`ips-percentual-${chave}`);
|
||||||
|
const limiteDescontoInput = document.getElementById(`ips-limite-desconto-${chave}`);
|
||||||
|
if (!limiteInput || !percentualInput || !limiteDescontoInput) return;
|
||||||
|
const descontoPreenchido = limiteDescontoInput.value.trim() !== "";
|
||||||
|
const empresaPreenchido = limiteInput.value.trim() !== "" || percentualInput.value.trim() !== "";
|
||||||
|
limiteInput.disabled = descontoPreenchido;
|
||||||
|
percentualInput.disabled = descontoPreenchido;
|
||||||
|
limiteDescontoInput.disabled = empresaPreenchido;
|
||||||
|
}
|
||||||
|
|
||||||
// "150,00" / 150 / 150.5 -> "150,00"/"150,50" — usado pra repopular os
|
// "150,00" / 150 / 150.5 -> "150,00"/"150,50" — usado pra repopular os
|
||||||
// inputs de limite/percentual (texto BR) a partir de uma regra salva, cujo
|
// inputs de limite/percentual (texto BR) a partir de uma regra salva, cujo
|
||||||
// custeio_por_tipo já vem validado/parseado como float pelo backend.
|
// custeio_por_tipo já vem validado/parseado como float pelo backend.
|
||||||
@ -926,6 +951,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (escolhido.value === "especifica") {
|
if (escolhido.value === "especifica") {
|
||||||
entrada.limite_valor = document.getElementById(`ips-limite-${chave}`).value.trim();
|
entrada.limite_valor = document.getElementById(`ips-limite-${chave}`).value.trim();
|
||||||
entrada.percentual = document.getElementById(`ips-percentual-${chave}`).value.trim();
|
entrada.percentual = document.getElementById(`ips-percentual-${chave}`).value.trim();
|
||||||
|
entrada.limite_desconto_empregado = document.getElementById(`ips-limite-desconto-${chave}`).value.trim();
|
||||||
}
|
}
|
||||||
custeio_por_tipo[tipo][pessoa] = entrada;
|
custeio_por_tipo[tipo][pessoa] = entrada;
|
||||||
});
|
});
|
||||||
@ -954,7 +980,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (escolhido.value === "especifica") {
|
if (escolhido.value === "especifica") {
|
||||||
const limite = document.getElementById(`ips-limite-${chave}`).value.trim();
|
const limite = document.getElementById(`ips-limite-${chave}`).value.trim();
|
||||||
const percentual = document.getElementById(`ips-percentual-${chave}`).value.trim();
|
const percentual = document.getElementById(`ips-percentual-${chave}`).value.trim();
|
||||||
if (!limite && !percentual) return `Informe o limite de valor e/ou o percentual de custeio para ${rotulo}.`;
|
const limiteDesconto = document.getElementById(`ips-limite-desconto-${chave}`).value.trim();
|
||||||
|
if (!limite && !percentual && !limiteDesconto) {
|
||||||
|
return `Informe o limite de valor, o percentual de custeio ou o limite de desconto do empregado para ${rotulo}.`;
|
||||||
|
}
|
||||||
|
if (limiteDesconto && (limite || percentual)) {
|
||||||
|
return `${rotulo}: o limite de desconto do empregado não pode ser combinado com o limite de valor/percentual da empresa.`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1000,21 +1032,28 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const regraBox = document.getElementById(`ips-regra-especifica-${chave}`);
|
const regraBox = document.getElementById(`ips-regra-especifica-${chave}`);
|
||||||
const limiteInput = document.getElementById(`ips-limite-${chave}`);
|
const limiteInput = document.getElementById(`ips-limite-${chave}`);
|
||||||
const percentualInput = document.getElementById(`ips-percentual-${chave}`);
|
const percentualInput = document.getElementById(`ips-percentual-${chave}`);
|
||||||
|
const limiteDescontoInput = document.getElementById(`ips-limite-desconto-${chave}`);
|
||||||
|
|
||||||
document.querySelectorAll(`input[name="ips-custeio-${chave}"]`).forEach((r) => (r.checked = false));
|
document.querySelectorAll(`input[name="ips-custeio-${chave}"]`).forEach((r) => (r.checked = false));
|
||||||
if (limiteInput) limiteInput.value = "";
|
if (limiteInput) limiteInput.value = "";
|
||||||
if (percentualInput) percentualInput.value = "";
|
if (percentualInput) percentualInput.value = "";
|
||||||
|
if (limiteDescontoInput) limiteDescontoInput.value = "";
|
||||||
if (regraBox) regraBox.hidden = true;
|
if (regraBox) regraBox.hidden = true;
|
||||||
|
|
||||||
const entrada = custeioPorTipo[tipo] && custeioPorTipo[tipo][pessoa];
|
const entrada = custeioPorTipo[tipo] && custeioPorTipo[tipo][pessoa];
|
||||||
if (!entrada || !entrada.modo) return;
|
if (!entrada || !entrada.modo) {
|
||||||
|
atualizarExclusividadeRegraEspecifica(chave);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const radio = document.querySelector(`input[name="ips-custeio-${chave}"][value="${entrada.modo}"]`);
|
const radio = document.querySelector(`input[name="ips-custeio-${chave}"][value="${entrada.modo}"]`);
|
||||||
if (radio) radio.checked = true;
|
if (radio) radio.checked = true;
|
||||||
if (entrada.modo === "especifica") {
|
if (entrada.modo === "especifica") {
|
||||||
if (limiteInput) limiteInput.value = formatarNumeroBr(entrada.limite_valor);
|
if (limiteInput) limiteInput.value = formatarNumeroBr(entrada.limite_valor);
|
||||||
if (percentualInput) percentualInput.value = formatarNumeroBr(entrada.percentual);
|
if (percentualInput) percentualInput.value = formatarNumeroBr(entrada.percentual);
|
||||||
|
if (limiteDescontoInput) limiteDescontoInput.value = formatarNumeroBr(entrada.limite_desconto_empregado);
|
||||||
if (regraBox) regraBox.hidden = false;
|
if (regraBox) regraBox.hidden = false;
|
||||||
}
|
}
|
||||||
|
atualizarExclusividadeRegraEspecifica(chave);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1210,6 +1249,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (entrada.percentual !== null && entrada.percentual !== undefined) {
|
if (entrada.percentual !== null && entrada.percentual !== undefined) {
|
||||||
partes.push(`${formatarNumeroBr(entrada.percentual)}% custeado pela empresa`);
|
partes.push(`${formatarNumeroBr(entrada.percentual)}% custeado pela empresa`);
|
||||||
}
|
}
|
||||||
|
if (entrada.limite_desconto_empregado !== null && entrada.limite_desconto_empregado !== undefined) {
|
||||||
|
partes.push(`desconto do empregado limitado a R$ ${formatarNumeroBr(entrada.limite_desconto_empregado)}`);
|
||||||
|
}
|
||||||
return `regra específica (${partes.join(", ") || "sem detalhe"})`;
|
return `regra específica (${partes.join(", ") || "sem detalhe"})`;
|
||||||
}
|
}
|
||||||
return entrada.modo;
|
return entrada.modo;
|
||||||
@ -1299,6 +1341,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (entrada.modo === "especifica") {
|
if (entrada.modo === "especifica") {
|
||||||
formData.append(`limite_valor_${tipo}_${pessoa}`, formatarNumeroBr(entrada.limite_valor));
|
formData.append(`limite_valor_${tipo}_${pessoa}`, formatarNumeroBr(entrada.limite_valor));
|
||||||
formData.append(`percentual_${tipo}_${pessoa}`, formatarNumeroBr(entrada.percentual));
|
formData.append(`percentual_${tipo}_${pessoa}`, formatarNumeroBr(entrada.percentual));
|
||||||
|
formData.append(`limite_desconto_empregado_${tipo}_${pessoa}`, formatarNumeroBr(entrada.limite_desconto_empregado));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -2428,6 +2471,20 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Duplo custeio da "Regra específica" (limite/percentual do lado da
|
||||||
|
// empresa x limite de desconto do lado do empregado) é mutuamente
|
||||||
|
// exclusivo — ver atualizarExclusividadeRegraEspecifica(). Reavalia a cada
|
||||||
|
// tecla digitada nos três campos de cada combinação tipo×pessoa.
|
||||||
|
PID_IPS_TIPOS.forEach((tipo) => {
|
||||||
|
PID_IPS_PESSOAS.forEach((pessoa) => {
|
||||||
|
const chave = `${tipo}-${pessoa}`;
|
||||||
|
[`ips-limite-${chave}`, `ips-percentual-${chave}`, `ips-limite-desconto-${chave}`].forEach((id) => {
|
||||||
|
const input = document.getElementById(id);
|
||||||
|
if (input) input.addEventListener("input", () => atualizarExclusividadeRegraEspecifica(chave));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (tabsEl) {
|
if (tabsEl) {
|
||||||
tabsEl.addEventListener("click", (event) => {
|
tabsEl.addEventListener("click", (event) => {
|
||||||
const btn = event.target.closest("[data-ips-tab]");
|
const btn = event.target.closest("[data-ips-tab]");
|
||||||
|
|||||||
@ -600,6 +600,13 @@
|
|||||||
<input type="text" id="ips-percentual-mensalidade-titular" placeholder="Ex.: 50" autocomplete="off" />
|
<input type="text" id="ips-percentual-mensalidade-titular" placeholder="Ex.: 50" autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
||||||
|
<div class="ips-regra-especifica__alt">
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="ips-limite-desconto-mensalidade-titular">Limite de desconto do empregado</label>
|
||||||
|
<input type="text" id="ips-limite-desconto-mensalidade-titular" placeholder="Ex.: 10,00" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<p class="ips-regra-especifica__hint">Alternativa aos campos acima: define o valor máximo descontado do empregado — a empresa custeia o restante. Não pode ser combinado com o limite de valor/percentual (preencher este campo desabilita os outros dois, e vice-versa).</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ips-custeio-pessoa">
|
<div class="ips-custeio-pessoa">
|
||||||
@ -617,6 +624,13 @@
|
|||||||
<input type="text" id="ips-percentual-mensalidade-dependente" placeholder="Ex.: 50" autocomplete="off" />
|
<input type="text" id="ips-percentual-mensalidade-dependente" placeholder="Ex.: 50" autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
||||||
|
<div class="ips-regra-especifica__alt">
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="ips-limite-desconto-mensalidade-dependente">Limite de desconto do empregado</label>
|
||||||
|
<input type="text" id="ips-limite-desconto-mensalidade-dependente" placeholder="Ex.: 10,00" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<p class="ips-regra-especifica__hint">Alternativa aos campos acima: define o valor máximo descontado do empregado — a empresa custeia o restante. Não pode ser combinado com o limite de valor/percentual (preencher este campo desabilita os outros dois, e vice-versa).</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -642,6 +656,13 @@
|
|||||||
<input type="text" id="ips-percentual-coparticipacao-titular" placeholder="Ex.: 50" autocomplete="off" />
|
<input type="text" id="ips-percentual-coparticipacao-titular" placeholder="Ex.: 50" autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
||||||
|
<div class="ips-regra-especifica__alt">
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="ips-limite-desconto-coparticipacao-titular">Limite de desconto do empregado</label>
|
||||||
|
<input type="text" id="ips-limite-desconto-coparticipacao-titular" placeholder="Ex.: 10,00" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<p class="ips-regra-especifica__hint">Alternativa aos campos acima: define o valor máximo descontado do empregado — a empresa custeia o restante. Não pode ser combinado com o limite de valor/percentual (preencher este campo desabilita os outros dois, e vice-versa).</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ips-custeio-pessoa">
|
<div class="ips-custeio-pessoa">
|
||||||
@ -659,6 +680,13 @@
|
|||||||
<input type="text" id="ips-percentual-coparticipacao-dependente" placeholder="Ex.: 50" autocomplete="off" />
|
<input type="text" id="ips-percentual-coparticipacao-dependente" placeholder="Ex.: 50" autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
<p class="ips-regra-especifica__hint">Preencha um dos dois campos ou os dois — se os dois forem preenchidos, vale o que resultar no menor valor custeado pela empresa. O excedente/restante vira desconto do empregado.</p>
|
||||||
|
<div class="ips-regra-especifica__alt">
|
||||||
|
<div class="modal-field">
|
||||||
|
<label for="ips-limite-desconto-coparticipacao-dependente">Limite de desconto do empregado</label>
|
||||||
|
<input type="text" id="ips-limite-desconto-coparticipacao-dependente" placeholder="Ex.: 10,00" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<p class="ips-regra-especifica__hint">Alternativa aos campos acima: define o valor máximo descontado do empregado — a empresa custeia o restante. Não pode ser combinado com o limite de valor/percentual (preencher este campo desabilita os outros dois, e vice-versa).</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user