mirror of
https://github.com/hoshikawa2/first_contas.git
synced 2026-07-09 10:14:20 +00:00
727 lines
24 KiB
Python
727 lines
24 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
from agente_contas_tim.protocol_triplets import resolve_protocol_triplet
|
|
from agente_contas_tim.text_utils import vocalize_digits
|
|
from agente_contas_tim.workflows.actions.registry import (
|
|
WorkflowRuntimeContext,
|
|
workflow_action,
|
|
)
|
|
from agente_contas_tim.workflows.runtime_types import ActionResult
|
|
from agente_contas_tim.workflows.actions.common.helpers import (
|
|
_final_msisdn,
|
|
_first_text_from_params_or_state,
|
|
_first_value_from_params_or_state,
|
|
_format_amount,
|
|
_normalize_number_text,
|
|
_result_failed_or_missing_data,
|
|
_runtime_llm_callbacks,
|
|
_runtime_llm_metadata,
|
|
_to_dict,
|
|
_utc_now_iso,
|
|
)
|
|
from agente_contas_tim.workflows.actions.invoice_explanation.helpers import _normalizar_mensagem_voz
|
|
from agente_contas_tim.workflows.actions.pro_rata.helpers import (
|
|
_build_pro_rata_ajuste_recusado_msg,
|
|
_build_pro_rata_contestacao_tool_payload,
|
|
_build_pro_rata_contestation_items,
|
|
_build_pro_rata_controle_msg,
|
|
_build_pro_rata_human_validation_text,
|
|
_build_pro_rata_oferta_ajuste_msg,
|
|
_build_pro_rata_sem_controle_msg,
|
|
_decimal_from_any,
|
|
_extract_pro_rata_contestation_context,
|
|
_fetch_invoice_payload_pro_rata,
|
|
_money,
|
|
_resolve_plano_controle,
|
|
_resolve_pro_rata_liquid_value,
|
|
_resolve_pro_rata_period_days,
|
|
)
|
|
|
|
logger = logging.getLogger("agente_contas_tim.workflows.actions.tim_actions")
|
|
|
|
|
|
@workflow_action("formatar_pro_rata")
|
|
def formatar_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Reescreve a mensagem crua do pro-rata aplicando vocalizacao para TTS.
|
|
|
|
Recebe `mensagem_base` (obrigatorio) e, opcionalmente, `trailer_override`
|
|
para forcar uma frase final (ex.: caminho de reperguntar). Em caso de
|
|
falha do LLM, faz fallback para a mensagem crua para nao bloquear o
|
|
fluxo.
|
|
"""
|
|
mensagem_base = str(params.get("mensagem_base", "") or "").strip()
|
|
if not mensagem_base:
|
|
return ActionResult.fail(
|
|
"mensagem_base obrigatoria para formatar_pro_rata"
|
|
)
|
|
|
|
trailer_override = str(params.get("trailer_override", "") or "").strip()
|
|
|
|
mensagem = ""
|
|
if runtime.llm_gateway is not None:
|
|
try:
|
|
llm_result = runtime.llm_gateway.execute(
|
|
capability_id="fluxo_pro_rata_reescrita",
|
|
variables={
|
|
"mensagem_base": mensagem_base,
|
|
"trailer_override": trailer_override,
|
|
},
|
|
user_text=mensagem_base,
|
|
callbacks=_runtime_llm_callbacks(runtime),
|
|
tags=["workflow_action"],
|
|
metadata=_runtime_llm_metadata(runtime),
|
|
)
|
|
mensagem = str(getattr(llm_result, "content", "") or "").strip()
|
|
mensagem = _normalizar_mensagem_voz(
|
|
mensagem,
|
|
trailer_override=trailer_override,
|
|
)
|
|
except Exception:
|
|
logger.exception(
|
|
"formatar_pro_rata: falha ao invocar capability "
|
|
"fluxo_pro_rata_reescrita"
|
|
)
|
|
|
|
if not mensagem:
|
|
mensagem = (
|
|
f"{mensagem_base} {trailer_override}".strip()
|
|
if trailer_override
|
|
else mensagem_base
|
|
)
|
|
|
|
return ActionResult.ok({"mensagem": mensagem})
|
|
|
|
|
|
@workflow_action("preparar_pro_rata")
|
|
def preparar_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Monta mensagem inicial do pro-rata e decide se pausa por Plano Controle."""
|
|
raw_planos = params.get("planos")
|
|
planos = [p for p in (raw_planos or []) if isinstance(p, dict)]
|
|
has_controle = bool(params.get("has_plano_controle", False))
|
|
|
|
if len(planos) != 2 or not all(
|
|
str(p.get("desc", "")).strip() for p in planos
|
|
):
|
|
return ActionResult.fail(
|
|
"pro_rata exige exatamente dois planos com 'desc' preenchido."
|
|
)
|
|
|
|
if not has_controle:
|
|
return ActionResult.ok(
|
|
{
|
|
"await_user_input": False,
|
|
"mensagem": _build_pro_rata_sem_controle_msg(planos),
|
|
"mensagem_pos_aceite": "",
|
|
"mensagem_reperguntar_esclarecimento": "",
|
|
"mensagem_oferta_ajuste": "",
|
|
"mensagem_reperguntar_ajuste": "",
|
|
"mensagem_ajuste_recusado": "",
|
|
"has_plano_controle": False,
|
|
}
|
|
)
|
|
|
|
mensagem = _build_pro_rata_controle_msg(planos)
|
|
mensagem_oferta_ajuste = _build_pro_rata_oferta_ajuste_msg()
|
|
return ActionResult.ok(
|
|
{
|
|
"await_user_input": True,
|
|
"mensagem": mensagem,
|
|
"mensagem_pos_aceite": "",
|
|
"mensagem_reperguntar_esclarecimento": (
|
|
"Para seguirmos, preciso de uma confirmacao. " + mensagem
|
|
),
|
|
"mensagem_oferta_ajuste": mensagem_oferta_ajuste,
|
|
"mensagem_reperguntar_ajuste": (
|
|
"Para seguirmos, preciso de uma confirmacao. "
|
|
+ mensagem_oferta_ajuste
|
|
),
|
|
"mensagem_ajuste_recusado": _build_pro_rata_ajuste_recusado_msg(),
|
|
"has_plano_controle": True,
|
|
}
|
|
)
|
|
|
|
|
|
@workflow_action("calcular_itens_contestacao_pro_rata")
|
|
def calcular_itens_contestacao_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Calcula itens/valores a contestar no fluxo de pro rata."""
|
|
raw_planos = _first_value_from_params_or_state(state, params, "planos")
|
|
planos = [p for p in (raw_planos or []) if isinstance(p, dict)]
|
|
if not planos:
|
|
return ActionResult.fail(
|
|
"planos obrigatorio para calcular itens de contestacao do pro_rata"
|
|
)
|
|
|
|
items: list[dict[str, Any]] = []
|
|
total = Decimal("0")
|
|
for plano in planos:
|
|
item_name = str(
|
|
plano.get("desc")
|
|
or plano.get("name")
|
|
or plano.get("item_name")
|
|
or ""
|
|
).strip()
|
|
if not item_name:
|
|
continue
|
|
valor = _normalize_number_text(
|
|
plano.get("value")
|
|
or plano.get("valor")
|
|
or plano.get("claimed_amount")
|
|
or "0"
|
|
)
|
|
valor_decimal = Decimal(valor)
|
|
total += valor_decimal
|
|
items.append(
|
|
{
|
|
"item_name": item_name,
|
|
"item_type": "PRO_RATA",
|
|
"claimed_amount": valor,
|
|
"validated_amount": valor,
|
|
}
|
|
)
|
|
|
|
if not items:
|
|
return ActionResult.fail(
|
|
"Nao foi possivel calcular itens de contestacao a partir dos planos."
|
|
)
|
|
|
|
total_txt = _normalize_number_text(format(total, "f"))
|
|
return ActionResult.ok(
|
|
{
|
|
"items": items,
|
|
"invoice_amount_open": total_txt,
|
|
"invoice_amount": total_txt,
|
|
}
|
|
)
|
|
|
|
|
|
@workflow_action("executar_contestacao_plano_controle")
|
|
def executar_contestacao_plano_controle(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Delega para o workflow/tool contestacao_tool apos calcular os itens."""
|
|
context, context_error = _extract_pro_rata_contestation_context(state, params)
|
|
if context is None:
|
|
return ActionResult.fail(
|
|
context_error or "Dados invalidos para contestacao de pro_rata"
|
|
)
|
|
|
|
if runtime.workflow_runner is None:
|
|
return ActionResult.fail(
|
|
"workflow_runner nao configurado para delegar contestacao_tool"
|
|
)
|
|
contestacao_payload = _build_pro_rata_contestacao_tool_payload(
|
|
state,
|
|
params,
|
|
context=context,
|
|
)
|
|
|
|
contestacao_result = runtime.workflow_runner(
|
|
"contestacao_tool",
|
|
contestacao_payload,
|
|
None,
|
|
None,
|
|
)
|
|
if contestacao_result.status != "COMPLETED" or not isinstance(
|
|
contestacao_result.data, dict
|
|
):
|
|
return ActionResult.fail(
|
|
str(contestacao_result.error or "Falha ao abrir contestacao de pro_rata"),
|
|
**(
|
|
contestacao_result.metadata
|
|
if isinstance(contestacao_result.metadata, dict)
|
|
else {}
|
|
),
|
|
)
|
|
|
|
contest_payload = contestacao_result.data
|
|
contestacao_lines = (
|
|
contest_payload.get("linhas", [])
|
|
if isinstance(contest_payload.get("linhas"), list)
|
|
else []
|
|
)
|
|
protocolo_id = ""
|
|
if contestacao_lines and isinstance(contestacao_lines[0], dict):
|
|
protocolo_id = str(contestacao_lines[0].get("protocolo_id", "")).strip()
|
|
if not protocolo_id:
|
|
protocolo_id = str(
|
|
contest_payload.get("protocol_number")
|
|
or contest_payload.get("protocolo_id")
|
|
or ""
|
|
).strip()
|
|
mensagem = str(contest_payload.get("mensagem", "")).strip()
|
|
if not mensagem:
|
|
mensagem = (
|
|
"Contestacao de pro rata registrada com sucesso. "
|
|
f"Protocolo: {protocolo_id or 'n/a'}."
|
|
)
|
|
plano_names = [
|
|
str(item.get("item_name", "")).strip()
|
|
for item in context["items"]
|
|
if isinstance(item, dict) and str(item.get("item_name", "")).strip()
|
|
]
|
|
transaction_id = str(
|
|
contest_payload.get("contestation_id")
|
|
or contest_payload.get("sr")
|
|
or protocolo_id
|
|
or ""
|
|
).strip()
|
|
audit = {
|
|
"plano_cliente": ", ".join(plano_names),
|
|
"valor_calculado_credito": str(context["invoice_amount"]),
|
|
"data_hora_contestacao": _utc_now_iso(),
|
|
"transaction_id_ajuste": transaction_id,
|
|
"mensagem_confirmacao": mensagem,
|
|
}
|
|
raw_devolucao = params.get("devolucao")
|
|
devolucao = dict(raw_devolucao) if isinstance(raw_devolucao, dict) else {}
|
|
devolucao.setdefault("items", context["items"])
|
|
devolucao["invoice_amount_open"] = context["invoice_amount_open"]
|
|
devolucao["invoice_amount"] = context["invoice_amount"]
|
|
format_text = str(
|
|
contest_payload.get("format_text")
|
|
or (
|
|
"sms"
|
|
if bool(
|
|
contest_payload.get("sms_enviado")
|
|
or contest_payload.get("sms_sent")
|
|
)
|
|
else "conta_futura"
|
|
)
|
|
).strip()
|
|
devolucao["format_text"] = format_text
|
|
devolucao["resolution_type"] = str(
|
|
contest_payload.get("resolution_type")
|
|
or ("new_boleto" if format_text == "sms" else "credit_bill")
|
|
).strip()
|
|
devolucao["sms_enviado"] = bool(
|
|
contest_payload.get("sms_enviado")
|
|
or contest_payload.get("sms_sent")
|
|
or format_text == "sms"
|
|
)
|
|
devolucao["sms_sent"] = devolucao["sms_enviado"]
|
|
for key in (
|
|
"decision_reason",
|
|
"data_credito_proxima_fatura",
|
|
"barcode",
|
|
"items_response",
|
|
"sr_conta_certa_id",
|
|
):
|
|
if key in contest_payload:
|
|
devolucao[key] = contest_payload[key]
|
|
if protocolo_id:
|
|
devolucao["protocolo_id"] = protocolo_id
|
|
if transaction_id:
|
|
devolucao["transaction_id_ajuste"] = transaction_id
|
|
logger.info(
|
|
"pro_rata.contestacao.audit msisdn_final=%s plano_cliente=%s valor_credito=%s transaction_id=%s data_hora=%s mensagem_confirmacao=%s",
|
|
_final_msisdn(str(context["msisdn"])),
|
|
audit["plano_cliente"],
|
|
audit["valor_calculado_credito"],
|
|
audit["transaction_id_ajuste"],
|
|
audit["data_hora_contestacao"],
|
|
audit["mensagem_confirmacao"],
|
|
)
|
|
return ActionResult.ok(
|
|
{
|
|
"success": True,
|
|
"mensagem": mensagem,
|
|
"protocolo_id": protocolo_id,
|
|
"contestation_id": str(
|
|
contest_payload.get("contestation_id")
|
|
or ""
|
|
).strip(),
|
|
"sr": str(
|
|
contest_payload.get("sr")
|
|
or protocolo_id
|
|
).strip(),
|
|
"items": context["items"],
|
|
"invoice_amount_open": context["invoice_amount_open"],
|
|
"invoice_amount": context["invoice_amount"],
|
|
"devolucao": devolucao,
|
|
"audit": audit,
|
|
"response": contest_payload,
|
|
},
|
|
**(
|
|
contestacao_result.metadata
|
|
if isinstance(contestacao_result.metadata, dict)
|
|
else {}
|
|
),
|
|
)
|
|
|
|
|
|
@workflow_action("definir_devolucao_ajuste_pro_rata")
|
|
def definir_devolucao_ajuste_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Calcula devolucao do ajuste pro-rata para Plano Controle.
|
|
|
|
A base do pro-rata e o valor liquido do Plano Controle. A devolucao e
|
|
alocada nos itens do DANFE vinculados ao mesmo plano.
|
|
"""
|
|
raw_planos = _first_value_from_params_or_state(state, params, "planos")
|
|
planos = [p for p in (raw_planos or []) if isinstance(p, dict)]
|
|
if len(planos) != 2:
|
|
return ActionResult.fail(
|
|
"pro_rata exige exatamente dois planos para calcular devolucao."
|
|
)
|
|
|
|
resolved = _resolve_plano_controle(planos)
|
|
if resolved is None:
|
|
return ActionResult.fail(
|
|
"Nao foi possivel identificar exatamente um Plano Controle."
|
|
)
|
|
plano_controle, outro_plano = resolved
|
|
|
|
valor_liquido = _resolve_pro_rata_liquid_value(plano_controle)
|
|
if valor_liquido is None or valor_liquido <= 0:
|
|
return ActionResult.fail(
|
|
"Valor liquido do Plano Controle ausente ou invalido."
|
|
)
|
|
|
|
invoice_payload, error = _fetch_invoice_payload_pro_rata(state, params, runtime)
|
|
if error:
|
|
return ActionResult.fail(error)
|
|
|
|
period_days = _resolve_pro_rata_period_days(
|
|
outro_plano=outro_plano,
|
|
invoice_payload=invoice_payload,
|
|
invoice_period=_first_text_from_params_or_state(
|
|
state,
|
|
params,
|
|
"invoice_period",
|
|
"invoicePeriod",
|
|
"periodo_fatura",
|
|
"periodoFatura",
|
|
),
|
|
invoice_emissao=_first_text_from_params_or_state(
|
|
state,
|
|
params,
|
|
"invoice_emissao",
|
|
"invoiceEmissao",
|
|
"emissao_fatura",
|
|
"emissaoFatura",
|
|
),
|
|
)
|
|
if period_days is None:
|
|
return ActionResult.fail(
|
|
"Periodo da fatura ou dos planos ausente ou invalido para calcular pro-rata."
|
|
)
|
|
dias_ciclo, dias_controle = period_days
|
|
|
|
valor_liquido = _money(valor_liquido)
|
|
valor_usado = (valor_liquido / Decimal(dias_ciclo)) * Decimal(dias_controle)
|
|
valor_devolver = _money(valor_liquido - valor_usado)
|
|
|
|
danfe = invoice_payload.get("DANFE-COM")
|
|
if not isinstance(danfe, dict) or not danfe:
|
|
return ActionResult.fail("DANFE-COM nao encontrado na fatura recuperada.")
|
|
|
|
items, restante = _build_pro_rata_contestation_items(
|
|
danfe=danfe,
|
|
plano_controle=plano_controle,
|
|
valor_devolver=valor_devolver,
|
|
)
|
|
if restante > 0:
|
|
return ActionResult.fail(
|
|
"Itens do DANFE insuficientes para cobrir devolucao de "
|
|
f"R$ {_format_amount(restante)}."
|
|
)
|
|
|
|
total_validado = sum(
|
|
(
|
|
_decimal_from_any(item.get("validatedAmount")) or Decimal("0")
|
|
)
|
|
for item in items
|
|
if isinstance(item, dict)
|
|
)
|
|
logger.info(
|
|
"definir_devolucao_ajuste_pro_rata: items=%s total_validado=%s",
|
|
items,
|
|
_format_amount(_money(total_validado)),
|
|
)
|
|
|
|
total_validado_txt = _normalize_number_text(format(_money(total_validado), "f"))
|
|
texto_validacao_humana = _build_pro_rata_human_validation_text(
|
|
plano_controle=plano_controle,
|
|
valor_liquido=valor_liquido,
|
|
dias_ciclo=dias_ciclo,
|
|
dias_controle=dias_controle,
|
|
valor_usado=valor_usado,
|
|
valor_devolver=valor_devolver,
|
|
items=items,
|
|
)
|
|
return ActionResult.ok(
|
|
{
|
|
"items": items,
|
|
"invoice_amount_open": total_validado_txt,
|
|
"invoice_amount": total_validado_txt,
|
|
"texto_validacao_humana": texto_validacao_humana,
|
|
}
|
|
)
|
|
|
|
|
|
@workflow_action("executar_devolucao_pro_rata")
|
|
def executar_devolucao_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""A implementar: dispara devolucao do ajuste pro-rata.
|
|
|
|
Por enquanto repassa o payload de definir_devolucao para o proximo no.
|
|
"""
|
|
devolucao = params.get("devolucao")
|
|
if not isinstance(devolucao, dict):
|
|
devolucao = {}
|
|
items = devolucao.get("items")
|
|
if not isinstance(items, list):
|
|
items = []
|
|
logger.warning(
|
|
"executar_devolucao_pro_rata: a implementar (mock ativo)"
|
|
)
|
|
output = dict(devolucao)
|
|
output["items"] = items
|
|
output["mocked"] = True
|
|
return ActionResult.ok(output)
|
|
|
|
|
|
@workflow_action("orientar_pagamento_pro_rata")
|
|
def orientar_pagamento_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Orienta o cliente sobre pagamento apos definir o credito."""
|
|
devolucao = params.get("devolucao")
|
|
if not isinstance(devolucao, dict):
|
|
devolucao = {}
|
|
|
|
raw_items = devolucao.get("items")
|
|
items = raw_items if isinstance(raw_items, list) else []
|
|
valor = Decimal("0")
|
|
plano_nome = ""
|
|
for item in items:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
if not plano_nome:
|
|
plano_nome = str(
|
|
item.get("itemName")
|
|
or item.get("item_name")
|
|
or ""
|
|
).strip()
|
|
item_value = _decimal_from_any(
|
|
item.get("validatedAmount")
|
|
if item.get("validatedAmount") is not None
|
|
else item.get("validated_amount")
|
|
)
|
|
if item_value is not None:
|
|
valor += item_value
|
|
|
|
valor_txt = _format_amount(_money(valor))
|
|
plano_txt = f" {plano_nome}" if plano_nome else ""
|
|
protocolo_id = str(devolucao.get("protocolo_id", "")).strip()
|
|
protocolo_txt = (
|
|
f" Seu numero de protocolo e {vocalize_digits(protocolo_id)}."
|
|
if protocolo_id
|
|
else ""
|
|
)
|
|
data_credito = str(
|
|
devolucao.get("data_credito_proxima_fatura", "")
|
|
).strip()
|
|
data_credito_txt = (
|
|
f" na fatura com vencimento em {data_credito}, considerando o seu ciclo de faturamento"
|
|
if data_credito
|
|
else " em uma proxima fatura"
|
|
)
|
|
sms_codigo_txt = (
|
|
"com o codigo de barras atualizado"
|
|
if str(devolucao.get("barcode", "")).strip()
|
|
else "com as orientacoes para pagamento"
|
|
)
|
|
|
|
mensagem = (
|
|
(
|
|
"Realizei a contestacao da fatura considerando o valor "
|
|
f"proporcional do Plano Controle{plano_txt}. O valor "
|
|
f"contestado, de R$ {valor_txt}, foi retirado da sua fatura. "
|
|
f"Enviamos uma mensagem {sms_codigo_txt}, com prazo de 10 dias "
|
|
f"para pagamento.{protocolo_txt}"
|
|
)
|
|
if str(devolucao.get("format_text", "")).strip() == "sms"
|
|
else (
|
|
"Realizei a contestacao considerando o valor proporcional do "
|
|
f"Plano Controle{plano_txt}. O valor contestado, de "
|
|
f"R$ {valor_txt}, ficou registrado como credito{data_credito_txt}."
|
|
f"{protocolo_txt}"
|
|
)
|
|
).strip()
|
|
|
|
return ActionResult.ok({"mensagem": mensagem, "devolucao": devolucao})
|
|
|
|
|
|
@workflow_action("registrar_atendimento_pro_rata")
|
|
def registrar_atendimento_pro_rata(
|
|
state: dict[str, Any],
|
|
params: dict[str, Any],
|
|
runtime: WorkflowRuntimeContext,
|
|
) -> ActionResult:
|
|
"""Registra protocolo de fechamento do atendimento pro rata."""
|
|
mensagem_base = str(params.get("mensagem_base", "")).strip()
|
|
caminho = str(params.get("caminho", "")).strip()
|
|
devolucao = params.get("devolucao")
|
|
if not isinstance(devolucao, dict):
|
|
devolucao = {}
|
|
msisdn = _first_text_from_params_or_state(
|
|
state,
|
|
params,
|
|
"msisdn",
|
|
"Msisdn",
|
|
"MSISDN",
|
|
)
|
|
if not msisdn:
|
|
return ActionResult.fail("msisdn obrigatorio para registrar_atendimento_pro_rata")
|
|
|
|
social_sec_no = re.sub(
|
|
r"\D",
|
|
"",
|
|
_first_text_from_params_or_state(
|
|
state,
|
|
params,
|
|
"social_sec_no",
|
|
"socialSecNo",
|
|
"cpf",
|
|
"customer_document",
|
|
"customerDocument",
|
|
"document",
|
|
),
|
|
)
|
|
caminho_norm = caminho.strip().lower()
|
|
scenario = (
|
|
"pro_rata_reclamacao"
|
|
if caminho_norm == "nao_aceitou"
|
|
else "pro_rata_mensalidade"
|
|
)
|
|
close_triplet = resolve_protocol_triplet(scenario, stage="close")
|
|
reason1 = close_triplet.reason1 or "Informação"
|
|
reason2 = close_triplet.reason2 or "Conta"
|
|
reason3 = close_triplet.reason3 or "Mensalidade"
|
|
|
|
request_status = str(params.get("request_status", "Fechado")).strip() or "Fechado"
|
|
status = str(params.get("status", "CLOSED")).strip() or "CLOSED"
|
|
ic = str(params.get("ic", "")).strip()
|
|
message_id = _first_text_from_params_or_state(
|
|
state,
|
|
params,
|
|
"message_id",
|
|
"messageId",
|
|
)
|
|
|
|
register_result = runtime.factory.create_protocol_v2(
|
|
msisdn=msisdn,
|
|
interaction_protocol="",
|
|
flag_sms=True,
|
|
social_sec_no=social_sec_no,
|
|
source="CHAT",
|
|
reason1=reason1,
|
|
reason2=reason2,
|
|
reason3=reason3,
|
|
direction_contact="FROM-CLIENT",
|
|
type="CLIENTE",
|
|
request_flag=True,
|
|
request_status=request_status,
|
|
status=status,
|
|
service_request_notes="Solicitação via chat",
|
|
client_id="AIAGENTCR",
|
|
message_id=message_id,
|
|
).execute()
|
|
if _result_failed_or_missing_data(register_result, state=state):
|
|
return ActionResult.fail(
|
|
register_result.error or "Falha ao registrar protocolo do pro_rata",
|
|
**register_result.metadata,
|
|
)
|
|
|
|
payload = _to_dict(register_result.data)
|
|
response = payload.get("response", {}) if isinstance(payload, dict) else {}
|
|
protocolo_id = ""
|
|
if isinstance(response, dict):
|
|
protocolo_id = str(
|
|
response.get("interactionProtocol")
|
|
or response.get("protocolId")
|
|
or response.get("protocolo_id")
|
|
or ""
|
|
).strip()
|
|
if not protocolo_id:
|
|
return ActionResult.fail("Protocolo nao retornado para pro_rata")
|
|
|
|
if not mensagem_base and caminho_norm == "aceitou":
|
|
mensagem_base = (
|
|
f"Sua solicitação foi registrada. "
|
|
f"Seu número de protocolo é {vocalize_digits(protocolo_id)}."
|
|
)
|
|
|
|
logger.info(
|
|
"registrar_atendimento_pro_rata caminho=%s msisdn=%s protocolo=%s",
|
|
caminho_norm or "n/a",
|
|
_final_msisdn(msisdn),
|
|
protocolo_id,
|
|
)
|
|
result: dict[str, Any] = {
|
|
"success": True,
|
|
"mensagem": mensagem_base,
|
|
"protocolo": protocolo_id,
|
|
"protocolo_id": protocolo_id,
|
|
"pro_rata_protocol": protocolo_id,
|
|
"protocolos_por_linha": [
|
|
{
|
|
"msisdn": msisdn,
|
|
"nome": "pro_rata",
|
|
"protocolo_id": protocolo_id,
|
|
}
|
|
],
|
|
"tripleta": {
|
|
"reason1": reason1,
|
|
"reason2": reason2,
|
|
"reason3": reason3,
|
|
},
|
|
"protocol_closed": True,
|
|
"devolucao": devolucao,
|
|
"requires_protocol_in_response": True,
|
|
"protocols_for_response": [protocolo_id],
|
|
}
|
|
if ic:
|
|
result["ic"] = ic
|
|
return ActionResult.ok(result)
|
|
|
|
|
|
__all__ = [
|
|
'formatar_pro_rata',
|
|
'preparar_pro_rata',
|
|
'calcular_itens_contestacao_pro_rata',
|
|
'executar_contestacao_plano_controle',
|
|
'definir_devolucao_ajuste_pro_rata',
|
|
'executar_devolucao_pro_rata',
|
|
'orientar_pagamento_pro_rata',
|
|
'registrar_atendimento_pro_rata',
|
|
]
|