bugfix: Invoice details
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,6 +19,10 @@ class InvoiceContext:
|
||||
complete_invoices: dict[str, Any] | None = None
|
||||
billing_analysis: dict[str, Any] | None = None
|
||||
invoice_detail: Any = None
|
||||
invoice_amount: str = ""
|
||||
invoice_amount_open: str = ""
|
||||
invoice_period: str = ""
|
||||
invoice_emissao: str = ""
|
||||
customer_id: str = ""
|
||||
error: str | None = None
|
||||
cache_hit: bool = False
|
||||
@@ -33,6 +37,10 @@ class InvoiceContext:
|
||||
"complete_invoices_payload": self.complete_invoices,
|
||||
"billing_analysis": self.billing_analysis,
|
||||
"invoice_detail": self.invoice_detail,
|
||||
"invoice_amount": self.invoice_amount,
|
||||
"invoice_amount_open": self.invoice_amount_open,
|
||||
"invoice_period": self.invoice_period,
|
||||
"invoice_emissao": self.invoice_emissao,
|
||||
"customer_id": self.customer_id,
|
||||
"invoice_context_error": self.error,
|
||||
"invoice_context_cache_hit": self.cache_hit,
|
||||
@@ -42,6 +50,55 @@ class InvoiceContext:
|
||||
}
|
||||
|
||||
|
||||
def extract_invoice_summary_context(detail: Any) -> dict[str, str]:
|
||||
"""Extrai o resumo semântico da fatura detalhada.
|
||||
|
||||
Mantém a paridade com o prefetch do Contas original: ``bill_pdf`` devolve
|
||||
um envelope com ``parsed_content`` e o parser coloca o total em
|
||||
``total_geral``. Workflows e agentes não precisam conhecer a estrutura do
|
||||
PDF para consumir ``invoice_amount``/``invoice_amount_open``.
|
||||
"""
|
||||
parsed = detail
|
||||
if isinstance(detail, dict) and isinstance(detail.get("parsed_content"), dict):
|
||||
parsed = detail["parsed_content"]
|
||||
if not isinstance(parsed, dict):
|
||||
return {}
|
||||
|
||||
resumo = parsed.get("Fatura Resumo")
|
||||
if not isinstance(resumo, list):
|
||||
resumo = []
|
||||
|
||||
invoice_period = ""
|
||||
invoice_emissao = ""
|
||||
invoice_amount = ""
|
||||
parsed_total = parsed.get("total_geral")
|
||||
if parsed_total is not None and str(parsed_total).strip():
|
||||
invoice_amount = str(parsed_total).strip()
|
||||
|
||||
for item in resumo:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
desc = str(item.get("desc", "") or "").strip().casefold()
|
||||
if desc in {"período", "periodo"}:
|
||||
invoice_period = str(item.get("period", "") or "").strip()
|
||||
elif desc in {"emissão", "emissao"}:
|
||||
invoice_emissao = str(item.get("emissao", "") or "").strip()
|
||||
if not invoice_amount and bool(item.get("is_total")) and str(item.get("value", "")).strip():
|
||||
invoice_amount = str(item.get("value", "")).strip()
|
||||
if not invoice_amount and "total" in desc and str(item.get("value", "")).strip():
|
||||
invoice_amount = str(item.get("value", "")).strip()
|
||||
|
||||
context: dict[str, str] = {}
|
||||
if invoice_period:
|
||||
context["invoice_period"] = invoice_period
|
||||
if invoice_emissao:
|
||||
context["invoice_emissao"] = invoice_emissao
|
||||
if invoice_amount:
|
||||
context["invoice_amount"] = invoice_amount
|
||||
context["invoice_amount_open"] = invoice_amount
|
||||
return context
|
||||
|
||||
|
||||
class InvoiceContextService:
|
||||
"""Session-scoped invoice prefetch backed by the framework cache.
|
||||
|
||||
@@ -155,6 +212,7 @@ class InvoiceContextService:
|
||||
|
||||
detail = None
|
||||
detail_ms = 0.0
|
||||
summary: dict[str, str] = {}
|
||||
if include_detail and resolved_invoice_id:
|
||||
detail, detail_error, detail_ms = await self._timed_to_thread(
|
||||
"invoice_detail", self.client.bill_pdf, msisdn, resolved_invoice_id, customer_id,
|
||||
@@ -163,6 +221,8 @@ class InvoiceContextService:
|
||||
if detail_error:
|
||||
errors["invoice_detail"] = detail_error
|
||||
error_parts.append(f"invoice_detail: {detail_error}")
|
||||
else:
|
||||
summary = extract_invoice_summary_context(detail)
|
||||
|
||||
metadata = {
|
||||
"cache_hit": False,
|
||||
@@ -177,7 +237,12 @@ class InvoiceContextService:
|
||||
msisdn=msisdn, invoice_id=resolved_invoice_id,
|
||||
complete_invoices=complete if isinstance(complete, dict) else None,
|
||||
billing_analysis=billing if isinstance(billing, dict) else None,
|
||||
invoice_detail=detail, customer_id=customer_id,
|
||||
invoice_detail=detail,
|
||||
invoice_amount=summary.get("invoice_amount", ""),
|
||||
invoice_amount_open=summary.get("invoice_amount_open", ""),
|
||||
invoice_period=summary.get("invoice_period", ""),
|
||||
invoice_emissao=summary.get("invoice_emissao", ""),
|
||||
customer_id=customer_id,
|
||||
error="; ".join(error_parts) or None, cache_hit=False, business_events=events, errors=errors, metadata=metadata,
|
||||
)
|
||||
|
||||
@@ -219,7 +284,10 @@ class InvoiceContextService:
|
||||
await self.cache.set(key, {
|
||||
"msisdn": ctx.msisdn, "invoice_id": ctx.invoice_id,
|
||||
"complete_invoices": ctx.complete_invoices, "billing_analysis": ctx.billing_analysis,
|
||||
"invoice_detail": ctx.invoice_detail, "customer_id": ctx.customer_id,
|
||||
"invoice_detail": ctx.invoice_detail,
|
||||
"invoice_amount": ctx.invoice_amount, "invoice_amount_open": ctx.invoice_amount_open,
|
||||
"invoice_period": ctx.invoice_period, "invoice_emissao": ctx.invoice_emissao,
|
||||
"customer_id": ctx.customer_id,
|
||||
"error": ctx.error, "cache_hit": False,
|
||||
"errors": dict(ctx.errors or {}), "metadata": dict(ctx.metadata or {}),
|
||||
"_fetched_at": time.time(),
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -36,8 +36,29 @@ class ContasDomainService:
|
||||
partial = [p for p in products if needle in str(p.get("name") or p.get("description") or "").lower() or str(p.get("name") or p.get("description") or "").lower() in needle]
|
||||
return partial[0] if partial else None
|
||||
|
||||
def consultar_faturas(self, *, msisdn: str, **_: Any) -> Any:
|
||||
return self.client.consultar_faturas(msisdn)
|
||||
def consultar_faturas(self, *, msisdn: str, **args: Any) -> Any:
|
||||
"""Consulta faturas e expõe o resumo semântico quando pré-carregado.
|
||||
|
||||
A fonte de ``invoice_amount`` continua sendo a fatura detalhada (Bill PDF),
|
||||
como no Contas original. O MCP apenas apresenta esse dado junto do retorno
|
||||
de Complete Invoices; não altera o contrato do backend TIM nem inventa valor.
|
||||
"""
|
||||
complete = args.get("complete_invoices_payload")
|
||||
if not isinstance(complete, dict):
|
||||
complete = self.client.consultar_faturas(msisdn)
|
||||
if not isinstance(complete, dict):
|
||||
return complete
|
||||
|
||||
result = dict(complete)
|
||||
for key in ("invoice_amount", "invoice_amount_open", "invoice_period", "invoice_emissao"):
|
||||
value = args.get(key)
|
||||
if value not in (None, ""):
|
||||
result[key] = value
|
||||
if args.get("invoice_id") not in (None, ""):
|
||||
result.setdefault("invoice_id", args.get("invoice_id"))
|
||||
if args.get("customer_id") not in (None, ""):
|
||||
result.setdefault("customer_id", args.get("customer_id"))
|
||||
return result
|
||||
|
||||
def consultar_plano(self, *, msisdn: str, **args: Any) -> dict[str, Any]:
|
||||
"""Retorna somente os planos presentes na evidência de billing analysis.
|
||||
|
||||
Reference in New Issue
Block a user