ajuste no contas

This commit is contained in:
T3782834
2026-09-01 11:24:03 -03:00
parent 9ed4782f9d
commit 397b831fd3
428 changed files with 2294 additions and 4648 deletions

View File

@@ -261,6 +261,15 @@ def _preflight_subject(name: str, args: dict[str, Any]) -> dict[str, Any] | None
subject = str(args.get("subject") or "").strip()
if not msisdn or not subject:
return None
# ``validar_vas_subject`` is the authoritative, side-effect-free resolver
# used at the transaction boundary. When it has already canonicalized the
# subject and redirected the action to the strategic/bundle workflow, do not
# resolve the same subject a second time against invoice evidence. Repeating
# resolution with a different catalog can incorrectly turn a previously
# ELIGIBLE subject into NEEDS_PARAMETER (the Aya Audiobooks case).
if name == "tratar_vas_estrategico" and bool(args.get("_vas_subject_prevalidated")):
return None
# Contestação pode referenciar qualquer categoria da fatura. Antes do
# resolver especializado em serviços/VAS, preserve um item explicitamente
# citado no texto que abriu a transação. Isso impede que um plano explícito
@@ -507,10 +516,19 @@ def _result_payload(result: Any, *, workflow_name: str) -> dict[str, Any]:
async def _run_workflow(workflow_name: str, args: dict[str, Any]) -> dict[str, Any]:
# A workflow execution belongs to one transaction, never to the whole
# conversation session. Starting a workflow is therefore always a NEW
# execution. The only path allowed to reuse an execution id is
# ``retomar_workflow`` below, which calls ``aresume`` explicitly.
#
# Do not trust/forward a residual ``workflow_execution_id`` from a previous
# transaction: WorkflowRuntime.arun() will allocate a fresh UUID.
payload_args = dict(args)
payload_args.pop("workflow_execution_id", None)
result = await get_workflow_runtime().arun(
workflow_name,
_workflow_payload(workflow_name, args),
execution_id=args.get("workflow_execution_id"),
_workflow_payload(workflow_name, payload_args),
execution_id=None,
)
payload = _result_payload(result, workflow_name=workflow_name)
if workflow_name == "contestacao_tool" and payload.get("status") == "FAILED":
@@ -665,10 +683,15 @@ async def _run_cancelamento_com_contestacao(args: dict[str, Any]) -> dict[str, A
if social_sec_no:
normalized_args["social_sec_no"] = social_sec_no
normalized_args["items"] = _normalize_cancel_items(args, holder_msisdn)
# Nova transação de cancelamento = nova execução de workflow. Mesmo que
# algum envelope antigo carregue ``workflow_execution_id``, ele não pode ser
# reutilizado depois que a transação anterior terminou. Resume é tratado
# exclusivamente por ``retomar_workflow``/``aresume``.
normalized_args.pop("workflow_execution_id", None)
cancel_result = await get_workflow_runtime().arun(
"cancelamento_vas_avulso",
_workflow_payload("cancelamento_vas_avulso", normalized_args),
execution_id=args.get("workflow_execution_id"),
execution_id=None,
)
cancel_data = _workflow_result_dict(cancel_result)
if cancel_data.get("status") != "COMPLETED":
@@ -736,20 +759,48 @@ async def _run_cancelamento_com_contestacao(args: dict[str, Any]) -> dict[str, A
total = 0.0
for candidate in candidates:
subject = str(candidate.get("subject") or (candidate.get("service") or {}).get("name") or "").strip()
value = _value_for_subject(normalized_args, subject)
requested_value = _value_for_subject(normalized_args, subject)
# Keep the amount claimed by the user separate from the amount validated
# against VAS evidence. The cancellation result already carries the
# canonical service object, so a missing user amount must never silently
# become R$ 0,00. This also preserves mismatch evidence (e.g. user says
# 29,98 while the active VAS is 14,99) for CVAL/observation downstream.
service_evidence = candidate.get("service") if isinstance(candidate.get("service"), dict) else {}
details = service_evidence.get("details") if isinstance(service_evidence.get("details"), dict) else {}
validated_value = (
candidate.get("validated_amount")
if candidate.get("validated_amount") is not None
else candidate.get("validatedAmount")
if candidate.get("validatedAmount") is not None
else details.get("valor")
if details.get("valor") is not None
else service_evidence.get("valor")
if service_evidence.get("valor") is not None
else service_evidence.get("value")
if service_evidence.get("value") is not None
else service_evidence.get("price")
)
if validated_value is None:
validated_value = requested_value
claimed_value = requested_value if requested_value is not None else validated_value
try:
numeric = float(str(value).replace(".", "").replace(",", ".")) if isinstance(value, str) and "," in value else float(value or 0)
text = str(validated_value if validated_value is not None else "0")
numeric = float(text.replace(".", "").replace(",", ".")) if "," in text else float(text or 0)
except Exception:
numeric = 0.0
total += numeric
money = _money_ptbr(value if value is not None else numeric)
claimed_money = _money_ptbr(claimed_value if claimed_value is not None else 0)
validated_money = _money_ptbr(validated_value if validated_value is not None else claimed_value or 0)
items.append({
"itemName": subject,
"item_name": subject,
"claimedAmount": money,
"validatedAmount": money,
"claimed_amount": money,
"validated_amount": money,
"claimedAmount": claimed_money,
"validatedAmount": validated_money,
"claimed_amount": claimed_money,
"validated_amount": validated_money,
})
contest_payload = {
@@ -1334,7 +1385,11 @@ async def _validate_vas_subject(args: dict[str, Any]) -> dict[str, Any]:
"resolved_subject": canonical,
"entity_resolution": "vas_evidence",
"transaction_decision": {
"resolved_arguments": {"subject": canonical},
"resolved_arguments": {
"subject": canonical,
"_vas_subject_prevalidated": True,
**({"type": resolved_type} if resolved_type else {}),
},
"target_tool": effective_tool or requested_tool,
"action_changed": action_changed,
"requires_reconfirmation": action_changed,