mirror of
https://github.com/hoshikawa2/first_contas.git
synced 2026-07-09 18:24:20 +00:00
first commit
This commit is contained in:
851
legacy_reference/workflows/actions/vas_avulso/actions.py
Normal file
851
legacy_reference/workflows/actions/vas_avulso/actions.py
Normal file
@@ -0,0 +1,851 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
from agente_contas_tim.constants.ic_tags_enum import VAATag
|
||||
from agente_contas_tim.integrations.rct_policy import RCTOperation
|
||||
from agente_contas_tim.observability import get_session_id
|
||||
from agente_contas_tim.protocol_triplets import resolve_protocol_triplet
|
||||
from agente_contas_tim.workflows.actions.inputs import parse_cancel_vas_items
|
||||
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 (
|
||||
_build_ic_context,
|
||||
_emit_vaa,
|
||||
_find_matching_service,
|
||||
_first_text_from_params_or_state,
|
||||
_format_amount,
|
||||
_idempotency_get,
|
||||
_idempotency_set,
|
||||
_parse_amount,
|
||||
_resolve_service_csp_id,
|
||||
_result_failed_or_missing_data,
|
||||
_service_from_dict,
|
||||
_to_dict,
|
||||
)
|
||||
from agente_contas_tim.workflows.actions.vas_avulso.helpers import (
|
||||
_build_active_services,
|
||||
_empty_cancel_result,
|
||||
_service_can_be_canceled,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("agente_contas_tim.workflows.actions.tim_actions")
|
||||
|
||||
|
||||
@workflow_action("consulta_vas")
|
||||
def query_vas(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
input_state = state.get("input", {}) if isinstance(state, dict) else {}
|
||||
app_id = str(input_state.get("app_id", ""))
|
||||
service_context = input_state.get("service_context")
|
||||
if isinstance(service_context, dict):
|
||||
context_app_id = str(service_context.get("app_id", "")).strip()
|
||||
if not app_id or context_app_id == app_id:
|
||||
return ActionResult.ok(
|
||||
{
|
||||
"services": [service_context],
|
||||
"service_found": True,
|
||||
"service_status": str(service_context.get("status", "")),
|
||||
"service": service_context,
|
||||
"consulta_metadata": {"source": "service_context"},
|
||||
}
|
||||
)
|
||||
|
||||
result = runtime.factory.create_query_vas(
|
||||
msisdn=str(params["msisdn"])
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(result, state=state):
|
||||
return ActionResult.fail(
|
||||
result.error or "Falha na consulta",
|
||||
**result.metadata,
|
||||
)
|
||||
|
||||
payload = _to_dict(result.data)
|
||||
services = payload.get("services", [])
|
||||
target = next(
|
||||
(service for service in services if str(service.get("app_id", "")) == app_id),
|
||||
None,
|
||||
)
|
||||
return ActionResult.ok(
|
||||
{
|
||||
"services": services,
|
||||
"service_found": target is not None,
|
||||
"service_status": str((target or {}).get("status", "")),
|
||||
"service": target,
|
||||
"consulta_metadata": dict(result.metadata),
|
||||
},
|
||||
**result.metadata,
|
||||
)
|
||||
|
||||
|
||||
@workflow_action("cancelamento_vas_avulso_batch")
|
||||
def cancelamento_vas_avulso_batch(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
csp_id = str(params.get("csp_id", "740")).strip() or "740"
|
||||
channel = "AIAGENTCR"
|
||||
request_status = str(params.get("request_status", "Fechado")).strip() or "Fechado"
|
||||
status = str(params.get("status", "CLOSED")).strip() or "CLOSED"
|
||||
message_id = _first_text_from_params_or_state(
|
||||
state,
|
||||
params,
|
||||
"message_id",
|
||||
"messageId",
|
||||
)
|
||||
idempotency_key = str(params.get("idempotency_key", "")).strip()
|
||||
social_sec_no = re.sub(
|
||||
r"\D",
|
||||
"",
|
||||
_first_text_from_params_or_state(
|
||||
state,
|
||||
params,
|
||||
"social_sec_no",
|
||||
"socialSecNo",
|
||||
"customer_document",
|
||||
"customerDocument",
|
||||
),
|
||||
)
|
||||
|
||||
input_state = state.get("input", {}) if isinstance(state, dict) else {}
|
||||
_ic_base: dict[str, Any] = {
|
||||
"sessionId": str(get_session_id() or ""),
|
||||
"gsm": "",
|
||||
"ani": str((input_state.get("ani") if isinstance(input_state, dict) else "") or "").strip(),
|
||||
"uraCallId": str((input_state.get("ura_call_id") if isinstance(input_state, dict) else "") or "").strip(),
|
||||
"agentId": "contas",
|
||||
"channelId": str((input_state.get("channel_id") if isinstance(input_state, dict) else "URA") or "URA").strip(),
|
||||
}
|
||||
|
||||
|
||||
parsed_items = parse_cancel_vas_items(params.get("items"))
|
||||
|
||||
if not parsed_items:
|
||||
return ActionResult.ok(
|
||||
_empty_cancel_result(
|
||||
"", mensagem="Nenhum serviço informado para contestação."
|
||||
)
|
||||
)
|
||||
|
||||
normalized_items: list[tuple[str, str, float]] = [
|
||||
(item.msisdn, item.service, item.value) for item in parsed_items
|
||||
]
|
||||
_emit_vaa(VAATag.INICIO_FLUXO, _ic_base, gsm=normalized_items[0][0])
|
||||
|
||||
msisdns_in_order: list[str] = []
|
||||
seen_msisdns: set[str] = set()
|
||||
for item_msisdn, _, _ in normalized_items:
|
||||
if item_msisdn not in seen_msisdns:
|
||||
seen_msisdns.add(item_msisdn)
|
||||
msisdns_in_order.append(item_msisdn)
|
||||
|
||||
protocol_entries: list[dict[str, str]] = []
|
||||
|
||||
active_by_msisdn: dict[str, dict[str, dict[str, Any]]] = {}
|
||||
query_failures: set[str] = set()
|
||||
for line_msisdn in msisdns_in_order:
|
||||
query_result = runtime.factory.create_query_vas(
|
||||
msisdn=line_msisdn
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(query_result, state=state):
|
||||
query_failures.add(line_msisdn)
|
||||
active_by_msisdn[line_msisdn] = {}
|
||||
continue
|
||||
active_by_msisdn[line_msisdn] = _build_active_services(
|
||||
getattr(query_result.data, "services", [])
|
||||
)
|
||||
|
||||
canceled_items: list[dict[str, Any]] = []
|
||||
errors: list[dict[str, str]] = []
|
||||
not_found_services: list[dict[str, str]] = []
|
||||
contestation_candidates: list[dict[str, Any]] = []
|
||||
technical_error_in_iu = False
|
||||
|
||||
def _to_display_amount(value: Any) -> str:
|
||||
candidates: list[Any] = [value]
|
||||
|
||||
def _collect_from_any(raw: Any) -> list[Any]:
|
||||
collected: list[Any] = []
|
||||
if raw is None:
|
||||
return collected
|
||||
|
||||
if isinstance(raw, (str, int, float, Decimal)):
|
||||
collected.append(raw)
|
||||
return collected
|
||||
|
||||
if isinstance(raw, dict):
|
||||
for key in (
|
||||
"valor",
|
||||
"price",
|
||||
"amount",
|
||||
"cost",
|
||||
"value",
|
||||
"valor_numeric",
|
||||
"valor_float",
|
||||
"price_numeric",
|
||||
"amount_float",
|
||||
):
|
||||
if key in raw:
|
||||
candidate = raw.get(key)
|
||||
if candidate not in (None, ""):
|
||||
collected.append(candidate)
|
||||
details = raw.get("details")
|
||||
if isinstance(details, dict):
|
||||
collected.extend(_collect_from_any(details))
|
||||
extra = raw.get("extra")
|
||||
if isinstance(extra, dict):
|
||||
collected.extend(_collect_from_any(extra))
|
||||
|
||||
if isinstance(raw, (list, tuple, set)):
|
||||
for item in raw:
|
||||
if item not in (None, ""):
|
||||
collected.extend(_collect_from_any(item))
|
||||
|
||||
return collected
|
||||
|
||||
for candidate in _collect_from_any(value):
|
||||
parsed = _parse_amount(candidate)
|
||||
if parsed is None:
|
||||
continue
|
||||
try:
|
||||
return _format_amount(parsed)
|
||||
except Exception:
|
||||
continue
|
||||
return ""
|
||||
|
||||
def _resolve_item_amount(
|
||||
requested: Any,
|
||||
matched_service: dict[str, Any],
|
||||
) -> str:
|
||||
service_context = matched_service.get("service_context")
|
||||
context = service_context if isinstance(service_context, dict) else {}
|
||||
return (
|
||||
_to_display_amount(requested)
|
||||
or _to_display_amount(
|
||||
{
|
||||
"valor": matched_service.get("valor"),
|
||||
"price": matched_service.get("price"),
|
||||
"amount": matched_service.get("amount"),
|
||||
"cost": matched_service.get("cost"),
|
||||
"value": matched_service.get("value"),
|
||||
"amount_numeric": matched_service.get("amount_numeric"),
|
||||
"valor_numeric": matched_service.get("valor_numeric"),
|
||||
"valor_float": matched_service.get("valor_float"),
|
||||
"price_numeric": matched_service.get("price_numeric"),
|
||||
"service_context": context,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def _parse_amount_from_item(item: dict[str, Any]) -> Decimal | None:
|
||||
for key in ("valor", "value", "amount", "price", "servico_valor"):
|
||||
raw_value = item.get(key)
|
||||
if raw_value is None:
|
||||
continue
|
||||
parsed = _parse_amount(raw_value)
|
||||
if parsed is not None:
|
||||
return parsed
|
||||
return None
|
||||
|
||||
def _build_contestation_candidate(
|
||||
*,
|
||||
msisdn: str,
|
||||
service_name: str,
|
||||
amount: Any,
|
||||
app_id: str = "",
|
||||
codigo_boleto: str = "",
|
||||
iu_reason: str = "",
|
||||
) -> dict[str, Any]:
|
||||
resolved_amount = _to_display_amount(amount) or "0"
|
||||
return {
|
||||
"msisdn": msisdn,
|
||||
"servico": str(service_name or "").strip(),
|
||||
"service": str(service_name or "").strip(),
|
||||
"app_id": str(app_id or "").strip(),
|
||||
"valor": resolved_amount,
|
||||
"value": resolved_amount,
|
||||
"amount": resolved_amount,
|
||||
"price": resolved_amount,
|
||||
"codigo_boleto": str(codigo_boleto or "").strip(),
|
||||
"iu_reason": str(iu_reason or "").strip(),
|
||||
}
|
||||
|
||||
for item_msisdn, requested_service, requested_value in normalized_items:
|
||||
if item_msisdn in query_failures:
|
||||
technical_error_in_iu = True
|
||||
errors.append(
|
||||
{
|
||||
"msisdn": item_msisdn,
|
||||
"servico": requested_service,
|
||||
"erro": (
|
||||
"Falha ao consultar a linha de final "
|
||||
f"{item_msisdn[-2:]}."
|
||||
),
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
active_services = active_by_msisdn.get(item_msisdn, {})
|
||||
matched_service = _find_matching_service(
|
||||
requested_service, active_services,
|
||||
)
|
||||
if not matched_service:
|
||||
logger.info(
|
||||
"cancelamento_vas_avulso_batch.skip reason=service_not_found msisdn=%s requested_service=%s available_services=%s",
|
||||
item_msisdn,
|
||||
requested_service,
|
||||
list(active_services.keys()),
|
||||
)
|
||||
not_found_services.append(
|
||||
{"msisdn": item_msisdn, "servico": requested_service}
|
||||
)
|
||||
contestation_candidates.append(
|
||||
_build_contestation_candidate(
|
||||
msisdn=item_msisdn,
|
||||
service_name=requested_service,
|
||||
amount=requested_value,
|
||||
iu_reason="service_not_found",
|
||||
)
|
||||
)
|
||||
continue
|
||||
if not _service_can_be_canceled(matched_service):
|
||||
logger.info(
|
||||
"cancelamento_vas_avulso_batch.skip reason=can_cancel_false msisdn=%s requested_service=%s matched_service=%s",
|
||||
item_msisdn,
|
||||
requested_service,
|
||||
str(matched_service.get("service_name", "")).strip(),
|
||||
)
|
||||
errors.append(
|
||||
{
|
||||
"msisdn": item_msisdn,
|
||||
"servico": requested_service,
|
||||
"erro": (
|
||||
"Serviço já se encontra cancelado, não sendo possível "
|
||||
"realizar recancelamento."
|
||||
),
|
||||
}
|
||||
)
|
||||
contestation_candidates.append(
|
||||
_build_contestation_candidate(
|
||||
msisdn=item_msisdn,
|
||||
service_name=str(
|
||||
matched_service.get("service_name", "")
|
||||
).strip()
|
||||
or requested_service,
|
||||
amount=requested_value,
|
||||
app_id=str(matched_service.get("app_id", "")).strip(),
|
||||
codigo_boleto=str(
|
||||
matched_service.get("codigo_boleto", "")
|
||||
).strip(),
|
||||
iu_reason="already_inactive_or_blocked",
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
service_name = str(matched_service.get("service_name", ""))
|
||||
app_id = str(matched_service.get("app_id", ""))
|
||||
valor = _resolve_item_amount(requested_value, matched_service)
|
||||
codigo_boleto = str(matched_service.get("codigo_boleto", "")).strip()
|
||||
|
||||
cache_key = ""
|
||||
if idempotency_key and service_name:
|
||||
cache_key = (
|
||||
f"{idempotency_key}:{item_msisdn}:"
|
||||
f"{service_name.lower()}:{app_id}"
|
||||
)
|
||||
cached_item = _idempotency_get(cache_key)
|
||||
if cached_item is not None:
|
||||
merged_item = dict(cached_item)
|
||||
if not str(
|
||||
merged_item.get("valor", "")
|
||||
or merged_item.get("value", "")
|
||||
or merged_item.get("amount", "")
|
||||
).strip():
|
||||
merged_item.update(
|
||||
{
|
||||
"valor": valor,
|
||||
"value": valor,
|
||||
"amount": valor,
|
||||
"price": valor,
|
||||
}
|
||||
)
|
||||
if cache_key:
|
||||
_idempotency_set(cache_key, merged_item)
|
||||
canceled_items.append(merged_item)
|
||||
continue
|
||||
|
||||
resolved_csp_id = _resolve_service_csp_id(
|
||||
matched_service,
|
||||
fallback_csp_id=csp_id,
|
||||
)
|
||||
|
||||
_emit_vaa(VAATag.BLOQUEIO_INICIO, _ic_base, gsm=item_msisdn)
|
||||
block_result = runtime.factory.create_block_vas(
|
||||
msisdn=item_msisdn,
|
||||
app_id=app_id,
|
||||
csp_id=resolved_csp_id,
|
||||
service=_service_from_dict(
|
||||
matched_service.get("service_context")
|
||||
),
|
||||
ic_context={**_ic_base, "gsm": item_msisdn},
|
||||
).execute()
|
||||
if not block_result.success:
|
||||
technical_error_in_iu = True
|
||||
_emit_vaa(VAATag.ITEM_CANCELADO_FAIL, _ic_base, gsm=item_msisdn)
|
||||
errors.append(
|
||||
{
|
||||
"msisdn": item_msisdn,
|
||||
"servico": service_name,
|
||||
"erro": block_result.error or "Falha no bloqueio",
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
cancel_result = runtime.factory.create_cancellation_vas(
|
||||
msisdn=item_msisdn,
|
||||
app_id=app_id,
|
||||
csp_id=resolved_csp_id,
|
||||
channel=channel,
|
||||
interaction_protocol="",
|
||||
service=_service_from_dict(
|
||||
matched_service.get("service_context")
|
||||
),
|
||||
).execute()
|
||||
if not cancel_result.success:
|
||||
technical_error_in_iu = True
|
||||
_emit_vaa(VAATag.ITEM_CANCELADO_FAIL, _ic_base, gsm=item_msisdn)
|
||||
errors.append(
|
||||
{
|
||||
"msisdn": item_msisdn,
|
||||
"servico": service_name,
|
||||
"erro": cancel_result.error or "Falha no cancelamento",
|
||||
}
|
||||
)
|
||||
continue
|
||||
_emit_vaa(VAATag.ITEM_CANCELADO_OK, _ic_base, gsm=item_msisdn)
|
||||
cancel_payload = _to_dict(cancel_result.data)
|
||||
cancelamento = (
|
||||
cancel_payload.get("cancelamento", {})
|
||||
if isinstance(cancel_payload, dict)
|
||||
else {}
|
||||
)
|
||||
cancellation_protocol = ""
|
||||
if isinstance(cancelamento, dict):
|
||||
cancellation_protocol = str(
|
||||
cancelamento.get("interactionProtocol")
|
||||
or cancelamento.get("protocolId")
|
||||
or cancelamento.get("protocolo_id")
|
||||
or ""
|
||||
).strip()
|
||||
if not cancellation_protocol:
|
||||
body_payload = cancelamento.get("body", {})
|
||||
if isinstance(body_payload, dict):
|
||||
cancellation_protocol = str(
|
||||
body_payload.get("interactionProtocol")
|
||||
or body_payload.get("protocolId")
|
||||
or body_payload.get("protocolo_id")
|
||||
or ""
|
||||
).strip()
|
||||
|
||||
canceled_item: dict[str, Any] = {
|
||||
"msisdn": item_msisdn,
|
||||
"servico": service_name,
|
||||
"app_id": app_id,
|
||||
"valor": valor,
|
||||
"value": valor,
|
||||
"amount": valor,
|
||||
"price": valor,
|
||||
"codigo_boleto": codigo_boleto,
|
||||
}
|
||||
canceled_items.append(canceled_item)
|
||||
contestation_candidates.append(dict(canceled_item))
|
||||
if cache_key:
|
||||
_idempotency_set(cache_key, canceled_item)
|
||||
|
||||
_emit_vaa(VAATag.PROTOCOLO_INICIO, _ic_base, gsm=item_msisdn)
|
||||
close_triplet = resolve_protocol_triplet(
|
||||
"cancelamento_vas_avulso",
|
||||
stage="close",
|
||||
context={"servico": service_name},
|
||||
)
|
||||
protocol_result = runtime.factory.create_protocol_v2(
|
||||
msisdn=item_msisdn,
|
||||
interaction_protocol=cancellation_protocol,
|
||||
flag_sms=True,
|
||||
social_sec_no=social_sec_no,
|
||||
source="CHAT",
|
||||
reason1=close_triplet.reason1 or "Informação",
|
||||
reason2=close_triplet.reason2 or "Conta",
|
||||
reason3=close_triplet.reason3 or "Valor",
|
||||
direction_contact="FROM-CLIENT",
|
||||
type="CLIENTE",
|
||||
request_flag=True,
|
||||
request_status=request_status,
|
||||
status=status,
|
||||
service_request_notes=f"Serviço cancelado: {service_name}",
|
||||
client_id="AIAGENTCR",
|
||||
ic_context={**_ic_base, "gsm": str(item_msisdn)},
|
||||
rct_operation=RCTOperation.REG_ATEND_VAS_AVULSO,
|
||||
message_id=message_id,
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(protocol_result, state=state):
|
||||
errors.append(
|
||||
{
|
||||
"msisdn": item_msisdn,
|
||||
"servico": service_name,
|
||||
"erro": (
|
||||
protocol_result.error
|
||||
or "Falha ao registrar protocolo após cancelamento"
|
||||
),
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
_emit_vaa(VAATag.PROTOCOLO_OK, _ic_base, gsm=item_msisdn)
|
||||
protocol_payload = _to_dict(protocol_result.data)
|
||||
protocol_response = (
|
||||
protocol_payload.get("response", {})
|
||||
if isinstance(protocol_payload, dict)
|
||||
else {}
|
||||
)
|
||||
if not isinstance(protocol_response, dict):
|
||||
protocol_response = {}
|
||||
protocolo_id = str(
|
||||
cancellation_protocol
|
||||
or protocol_response.get("interactionProtocol")
|
||||
or protocol_response.get("protocolId")
|
||||
or protocol_response.get("protocolo_id")
|
||||
or ""
|
||||
).strip()
|
||||
if not protocolo_id:
|
||||
errors.append(
|
||||
{
|
||||
"msisdn": item_msisdn,
|
||||
"servico": service_name,
|
||||
"erro": "Protocolo não retornado no registro de atendimento",
|
||||
}
|
||||
)
|
||||
continue
|
||||
protocol_entries.append(
|
||||
{"msisdn": item_msisdn, "protocolo_id": protocolo_id}
|
||||
)
|
||||
|
||||
summary_parts: list[str] = []
|
||||
if canceled_items:
|
||||
by_line: dict[str, list[str]] = {}
|
||||
for item in canceled_items:
|
||||
by_line.setdefault(
|
||||
str(item.get("msisdn", "")), []
|
||||
).append(str(item.get("servico", "")))
|
||||
for line, names in by_line.items():
|
||||
summary_parts.append(
|
||||
"Cancelados com sucesso na linha de final "
|
||||
f"{line[-2:]}: {', '.join(names)}."
|
||||
)
|
||||
|
||||
for error in errors:
|
||||
summary_parts.append(
|
||||
f"Erro ao cancelar {error['servico']} "
|
||||
f"(linha {error['msisdn'][-2:]}): {error['erro']}."
|
||||
)
|
||||
|
||||
if not_found_services:
|
||||
joined = ", ".join(
|
||||
f"{nf['servico']} (linha {nf['msisdn'][-2:]})"
|
||||
for nf in not_found_services
|
||||
)
|
||||
summary_parts.append(f"Não encontrados: {joined}.")
|
||||
|
||||
total_valor = Decimal("0")
|
||||
total_tem_valor = False
|
||||
cancelados_descritivo: list[str] = []
|
||||
for item in canceled_items:
|
||||
servico = str(item.get("servico", "")).strip()
|
||||
valor = str(item.get("valor", "")).strip()
|
||||
parsed = _parse_amount_from_item(item)
|
||||
if parsed is not None:
|
||||
total_valor += parsed
|
||||
total_tem_valor = True
|
||||
if servico:
|
||||
valor_txt = f" (valor: R$ {valor})" if valor else ""
|
||||
cancelados_descritivo.append(f"{servico}{valor_txt}")
|
||||
|
||||
total_valor_txt = (
|
||||
f"R$ {_format_amount(total_valor)}"
|
||||
if total_tem_valor
|
||||
else "R$ 0,00"
|
||||
)
|
||||
|
||||
if technical_error_in_iu:
|
||||
contestation_candidates = []
|
||||
|
||||
primary_msisdn = msisdns_in_order[0] if msisdns_in_order else ""
|
||||
resumo_agente = (
|
||||
"Cancelamento VAS avulso concluido. "
|
||||
f"Telefone: {primary_msisdn}. "
|
||||
"Servicos cancelados: "
|
||||
f"{', '.join(cancelados_descritivo) if cancelados_descritivo else 'nenhum'}. "
|
||||
f"Total dos servicos cancelados: {total_valor_txt}."
|
||||
)
|
||||
|
||||
return ActionResult.ok(
|
||||
{
|
||||
"success": len(canceled_items) > 0,
|
||||
"mensagem": " ".join(summary_parts),
|
||||
"resumo_agente": resumo_agente,
|
||||
"msisdn": primary_msisdn,
|
||||
"cancelados": canceled_items,
|
||||
"erros": errors,
|
||||
"nao_encontrados": not_found_services,
|
||||
"itens_para_contestacao": contestation_candidates,
|
||||
"protocolos_por_linha": protocol_entries,
|
||||
"protocol_closed": bool(protocol_entries),
|
||||
"prompt_signals": {
|
||||
"servicos_cancelados": [
|
||||
item.get("servico") for item in canceled_items
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@workflow_action("avaliar_proxima_acao")
|
||||
def evaluate_next_action(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
"""Action exemplo: decide se deve aguardar confirmação do usuário."""
|
||||
services = []
|
||||
previous_query = state.get("vars", {}).get("node1")
|
||||
if isinstance(previous_query, dict):
|
||||
previous_services = previous_query.get("services", [])
|
||||
if isinstance(previous_services, list):
|
||||
services = previous_services
|
||||
|
||||
metadata: dict[str, Any] = {}
|
||||
if not services:
|
||||
result = runtime.factory.create_query_vas(
|
||||
msisdn=str(params["msisdn"])
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(result, state=state):
|
||||
return ActionResult.fail(
|
||||
result.error or "Falha na consulta para avaliação",
|
||||
**result.metadata,
|
||||
)
|
||||
services = _to_dict(result.data).get("services", [])
|
||||
metadata = dict(result.metadata)
|
||||
|
||||
tim_music = next(
|
||||
(
|
||||
s
|
||||
for s in services
|
||||
if str(s.get("service_name", "")).strip().lower() == "tim music"
|
||||
and str(s.get("status", "")).upper() == "ACTIVE"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if tim_music:
|
||||
client_payload = {
|
||||
"type": "question",
|
||||
"text": (
|
||||
"O motivo do aumento da sua fatura é o serviço TIM Music, que está ativo em sua linha. "
|
||||
"Deseja cancelar o TIM Music? Se sim, responda SIM para que eu possa prosseguir com o bloqueio e cancelamento."
|
||||
),
|
||||
"options": ["SIM", "NAO"],
|
||||
"service": {
|
||||
"app_id": tim_music.get("app_id", ""),
|
||||
"service_name": tim_music.get("service_name", ""),
|
||||
"status": tim_music.get("status", ""),
|
||||
},
|
||||
}
|
||||
return ActionResult.ok(
|
||||
{
|
||||
"await_user_input": True,
|
||||
"client_payload": client_payload,
|
||||
"service_context": tim_music,
|
||||
},
|
||||
**metadata,
|
||||
)
|
||||
|
||||
# Caso não haja TIM Music ativo, segue fluxo normal
|
||||
app_id = str(params.get("app_id", state.get("input", {}).get("app_id", "")))
|
||||
target = next(
|
||||
(service for service in services if str(service.get("app_id", "")) == app_id),
|
||||
None,
|
||||
)
|
||||
if target is None:
|
||||
return ActionResult.ok(
|
||||
{
|
||||
"await_user_input": False,
|
||||
"client_payload": {
|
||||
"type": "info",
|
||||
"text": f"Serviço appId={app_id} não localizado para confirmação.",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
status = str(target.get("status", "")).upper()
|
||||
await_user_input = status == "ACTIVE"
|
||||
|
||||
if await_user_input:
|
||||
client_payload = {
|
||||
"type": "question",
|
||||
"text": "Deseja continuar com a operação deste serviço?",
|
||||
"options": ["SIM", "NAO"],
|
||||
"service": {
|
||||
"app_id": target.get("app_id", ""),
|
||||
"service_name": target.get("service_name", ""),
|
||||
"status": target.get("status", ""),
|
||||
},
|
||||
}
|
||||
else:
|
||||
client_payload = {
|
||||
"type": "info",
|
||||
"text": "Serviço não requer confirmação adicional.",
|
||||
"service": {
|
||||
"app_id": target.get("app_id", ""),
|
||||
"service_name": target.get("service_name", ""),
|
||||
"status": target.get("status", ""),
|
||||
},
|
||||
}
|
||||
|
||||
return ActionResult.ok(
|
||||
{
|
||||
"await_user_input": await_user_input,
|
||||
"client_payload": client_payload,
|
||||
"service_context": target,
|
||||
},
|
||||
**metadata,
|
||||
)
|
||||
|
||||
|
||||
@workflow_action("bloquear_vas")
|
||||
def bloquear_vas(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
service_context = state.get("vars", {}).get("node2", {}).get("service_context")
|
||||
msisdn = str(params["msisdn"])
|
||||
input_state = state.get("input", {}) if isinstance(state, dict) else {}
|
||||
result = runtime.factory.create_block_vas(
|
||||
msisdn=msisdn,
|
||||
app_id=str(params["app_id"]),
|
||||
csp_id=str(params.get("csp_id", "740")),
|
||||
service=_service_from_dict(service_context),
|
||||
ic_context=_build_ic_context(input_state, gsm=msisdn),
|
||||
query_metadata=state.get("vars", {})
|
||||
.get("node1", {})
|
||||
.get("consulta_metadata", {}),
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(result, state=state):
|
||||
return ActionResult.fail(
|
||||
result.error or "Falha no bloqueio",
|
||||
**result.metadata,
|
||||
)
|
||||
return ActionResult.ok(_to_dict(result.data), **result.metadata)
|
||||
|
||||
|
||||
@workflow_action("cancelar_vas")
|
||||
def cancelar_vas(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
service_context = state.get("vars", {}).get("node2", {}).get("service_context")
|
||||
result = runtime.factory.create_cancellation_vas(
|
||||
msisdn=str(params["msisdn"]),
|
||||
app_id=str(params["app_id"]),
|
||||
csp_id=str(params.get("csp_id", "740")),
|
||||
interaction_protocol=str(params.get("interaction_protocol", "")),
|
||||
channel="AIAGENTCR",
|
||||
service=_service_from_dict(service_context),
|
||||
query_metadata=state.get("vars", {})
|
||||
.get("node1", {})
|
||||
.get("consulta_metadata", {}),
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(result, state=state):
|
||||
return ActionResult.fail(
|
||||
result.error or "Falha no cancelamento",
|
||||
**result.metadata,
|
||||
)
|
||||
return ActionResult.ok(_to_dict(result.data), **result.metadata)
|
||||
|
||||
|
||||
@workflow_action("bloquear_vas_single")
|
||||
def bloquear_vas_single(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
"""Bloqueia um serviço VAS usando msisdn + app_id dos params."""
|
||||
service_context = (
|
||||
state.get("vars", {})
|
||||
.get("consulta_vas", {})
|
||||
.get("service")
|
||||
)
|
||||
msisdn = str(params["msisdn"])
|
||||
input_state = state.get("input", {}) if isinstance(state, dict) else {}
|
||||
result = runtime.factory.create_block_vas(
|
||||
msisdn=msisdn,
|
||||
app_id=str(params["app_id"]),
|
||||
csp_id=str(params.get("csp_id", "740")),
|
||||
service=_service_from_dict(service_context),
|
||||
ic_context=_build_ic_context(input_state, gsm=msisdn),
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(result, state=state):
|
||||
return ActionResult.fail(
|
||||
result.error or "Falha no bloqueio",
|
||||
**result.metadata,
|
||||
)
|
||||
return ActionResult.ok(_to_dict(result.data), **result.metadata)
|
||||
|
||||
|
||||
@workflow_action("cancelar_vas_single")
|
||||
def cancelar_vas_single(
|
||||
state: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
runtime: WorkflowRuntimeContext,
|
||||
) -> ActionResult:
|
||||
"""Cancela um serviço VAS usando msisdn + app_id dos params."""
|
||||
service_context = (
|
||||
state.get("vars", {})
|
||||
.get("consulta_vas", {})
|
||||
.get("service")
|
||||
)
|
||||
result = runtime.factory.create_cancellation_vas(
|
||||
msisdn=str(params["msisdn"]),
|
||||
app_id=str(params["app_id"]),
|
||||
csp_id=str(params.get("csp_id", "740")),
|
||||
channel="AIAGENTCR",
|
||||
interaction_protocol=str(params.get("interaction_protocol", "")),
|
||||
service=_service_from_dict(service_context),
|
||||
).execute()
|
||||
if _result_failed_or_missing_data(result, state=state):
|
||||
return ActionResult.fail(
|
||||
result.error or "Falha no cancelamento",
|
||||
**result.metadata,
|
||||
)
|
||||
return ActionResult.ok(_to_dict(result.data), **result.metadata)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'query_vas',
|
||||
'cancelamento_vas_avulso_batch',
|
||||
'evaluate_next_action',
|
||||
'bloquear_vas',
|
||||
'cancelar_vas',
|
||||
'bloquear_vas_single',
|
||||
'cancelar_vas_single',
|
||||
]
|
||||
Reference in New Issue
Block a user