Métricas mensais nas telas de pagamento (parte 4 de 4)
TLDR: Adiciona cards de métricas com comparação mês atual vs. mês anterior nas três telas de pagamento do Avo, dando visibilidade de tendência, valor pendente e saúde do sistema de retry.
Sequência: parte 4 de uma iniciativa de 4 specs escritas entre 27/01 e 03/02/2026 — depende da parte 3: interface Avo.
Contexto
As três telas de pagamento não tinham nenhum contexto temporal ou totalizador:
| Tela | Lacuna | Impacto |
|---|---|---|
| Ordens de pagamento | lista de invoices sem contexto temporal, sem comparação entre períodos | admins não sabiam quanto foi pago no mês, nem se a tendência era de alta ou baixa; difícil identificar invoices pendentes antigas |
| Erros de pagamento | lista de erros sem agrupamento temporal nem valor financeiro afetado | impossível saber se os erros estavam aumentando, ou priorizar por impacto financeiro |
| Terapeutas à pagar | lista de profissionais sem totalizadores | planejamento financeiro prejudicado, sem visão do passivo total nem de quantos profissionais estão bloqueados |
Objetivos
- Expor total pago, quantidade paga, pendências e valor pendente no mês, com variação percentual contra o mês anterior.
- Expor volume de erros, valor afetado, erros críticos e taxa de sucesso de retry.
- Expor total disponível, profissionais elegíveis, bloqueados por conta não aprovada e total de sessões acumuladas.
- Adicionar scopes temporais para filtro rápido por período.
Fora de escopo
- Exportação das métricas para CSV/Excel.
- Gráficos de série temporal — apenas cards de métrica com comparação pontual.
Mudanças
1. Ordens de pagamento — cards mensais
| Card | Exibe |
|---|---|
MonthlyPaidTotalCard |
valor total pago no mês (R$) + variação % vs. mês anterior |
MonthlyPaidCountCard |
quantidade de invoices pagas no mês + variação % |
PendingInvoicesCard |
total de invoices pendentes + quantas há mais de 7 dias (alerta) |
PendingTotalCard |
valor total em invoices pendentes, para planejamento de caixa |
Padrão dos cards de comparação:
```ruby class Avo::Cards::ProfessionalInvoices::MonthlyPaidTotalCard < Avo::Cards::MetricCard self.id = “monthly_paid_total” self.label = “Total Pago Este Mês” self.description = “Comparado com o mês anterior”
def query current_month_total = ProfessionalPaymentInvoice.paid .where(paid_at: Time.current.beginning_of_month..Time.current.end_of_month).sum(:total)
last_month_total = ProfessionalPaymentInvoice.paid
.where(paid_at: 1.month.ago.beginning_of_month..1.month.ago.end_of_month).sum(:total)
result current_month_total
prefix "R$"
if last_month_total > 0
change = ((current_month_total - last_month_total) / last_month_total * 100).round(1)
suffix "#{change > 0 ? '+' : ''}#{change}% vs mês anterior"
end end end ```
Scopes: InvoicesCurrentMonth, InvoicesLastMonth, PaidCurrentMonth, PendingOld (pendentes há mais de 7 dias), todos exibindo a contagem no nome.
2. Erros de pagamento — cards mensais
| Card | Exibe |
|---|---|
MonthlyErrorsCountCard |
quantidade de erros no mês + variação % vs. mês anterior |
MonthlyAffectedValueCard |
valor total (R$) afetado por erros, para priorizar por impacto |
CriticalErrorsCard |
erros com retry_count >= 5 ou created_at < 10.days.ago + valor total |
RetrySuccessRateCard |
% de invoices pagas após retry nos últimos 30 dias |
Erros são identificados por where.not(last_error_message: [nil, ""]).
Scopes: ErrorsCurrentMonth, ErrorsLastMonth, CriticalErrors.
3. Terapeutas à pagar — cards de resumo
| Card | Exibe |
|---|---|
TotalAvailableCard |
soma do repasse líquido de todos os profissionais elegíveis |
EligibleCountCard |
profissionais com conta aprovada e sessões, sobre o total da lista |
PendingApprovalCard |
profissionais com sessões mas sem conta aprovada + valor retido |
TotalMeetingsCard |
total de sessões acumuladas esperando pagamento |
O cálculo do líquido em cada card segue subtotal - CalculateFee.call(subtotal) - 3.0, com piso em zero.
Arquivos
Modificados: app/avo/resources/professional_payment_invoice.rb, app/avo/resources/payment_error.rb, app/avo/resources/invoiceable_professional.rb.
Novos: os 12 cards acima em app/avo/cards/professional_invoices/, app/avo/cards/payment_errors/ e app/avo/cards/invoiceable_professionals/, mais os 7 scopes em app/avo/scopes/.
Desvio verificado em 2026-08-06: os cards e scopes foram entregues, mas o conjunto final difere do proposto. O resource de invoices carrega hoje
MonthlyPaidTotalCard,PendingTotalCard,TotalInvoicesCard,MonthlyPaidCountCard,PendingInvoicesCardeTotalMeetingsCard—TotalInvoicesCardnão estava previsto aqui. Eminvoiceable_professionals/existem tambémtotal_pending_card.rb,total_professionals_card.rbenext_month_card.rb, fora do escopo original. Os cards deinvoiceable_professionalscontinuam consumindoProfessionalPaymentInvoices::InvoiceableProfessionalsQuery, cuja remoção era objetivo da parte 1.
Como verificar
- Abrir cada uma das três telas no Avo e confirmar que os cards renderizam sem erro, mesmo com o mês anterior zerado (não deve haver divisão por zero).
- Conferir o valor de
MonthlyPaidTotalCardcontraProfessionalPaymentInvoice.paid.where(paid_at: <mês>).sum(:total)no console. - Aplicar cada scope temporal e confirmar que a contagem exibida no nome bate com a lista filtrada.
Documentação
As telas e cards atuais estão descritos em invoice_payment_flow.