Melhorar a interface de invoices no Avo (parte 3 de 4)

TLDR: Corrige os badges de status (que apontavam para valores inexistentes no enum), expõe as informações de retry na index e no show, e torna as actions condicionais ao estado real da invoice.

Sequência: parte 3 de uma iniciativa de 4 specs escritas entre 27/01 e 03/02/2026 — parte 4: métricas mensais.

Contexto

Badges de status incorretos

O resource apontava para símbolos que não existiam no enum:

ruby field :status, as: :badge, options: { info: [:scheduled, ...], # :scheduled não existe — status 0 é :created danger: [:canceled, ...], # único correto success: [:paid, ...], # :paid não existe — status 2 é :done warning: [:in_analysis, ...] # :in_analysis não existe }

Faltavam badges para created, pending, failed, blocked e in_bank_processing.

Outras lacunas

  • Index sem campos-chave — sem created_at, sem tempo desde a criação, sem número de tentativas de retry, sem status de retry.
  • Show sem os campos de retry — retry_count, last_retry_at, last_error_message e a próxima tentativa calculada não apareciam.
  • Transfers sem indicação de duplicata — o campo duplicated existia no schema mas não era exibido.
  • Actions sempre visíveis — “Re-agendar transferência” aparecia mesmo quando não fazia sentido; faltavam “Cancelar Invoice e Liberar Meetings” e “Criar Nova Invoice para Professional”.
  • Sem indicadores visuais de retry — o admin não conseguia identificar rapidamente quais invoices estavam em período de retry, quantas tentativas já haviam ocorrido, quando seria a próxima e por que a última falhou.

Objetivos

  • Mapear corretamente todos os status do enum nos badges.
  • Expor idade da invoice e estado de retry na index.
  • Expor retry_count, last_retry_at, last_error_message e próxima tentativa no show.
  • Exibir o campo duplicated nas transfers, com histórico de auditoria.
  • Tornar as actions condicionais ao estado da invoice e adicionar as duas actions faltantes.
  • Adicionar um card de alerta com o estado de retry no topo do show.

Fora de escopo

  • Métricas mensais e comparação entre períodos — é a parte 4.

Mudanças

1. Corrigir os badges de status

ruby field :status, as: :badge, options: { info: [:created, :pending], success: [:done], warning: [:in_bank_processing], danger: [:failed, :canceled, :blocked] }, format_using: -> { I18n.t("professional_payment_invoice_statuses.#{value}") }

Azul: aguardando processamento. Verde: pago. Amarelo: processando no banco. Vermelho: erro ou bloqueado.

2. Campos na index

id, professional (com link), status (badge), total (moeda), created_at, retry_status (badge derivado de retry_count/blocked?/done?) e paid_at.

3. Campos de retry no show

Visíveis apenas quando record.failed? || record.blocked?:

  • retry_count — “Tentativas de Retry”
  • last_retry_at — “Última Tentativa”
  • last_error_message — “Último Erro” (readonly)
  • next_retry — próxima tentativa calculada, ou “limite atingido” / “invoice bloqueada”
  • days_until_blocked — dias restantes até o bloqueio

4. Campo duplicated nas transfers

Em app/avo/resources/professional_payment_invoice_transfer.rb, adicionar o boolean duplicated (“Duplicada?”) e o painel de audits — as transfers já usam a gem audited via ApplicationRecord, e o histórico é útil para rastrear mudanças de status. Visível apenas quando há audits registrados.

5. Novos scopes

Avo::Scopes::WithStatusBlocked, WithStatusCreated, InRetryPeriod e ExpiredRetryPeriod, todos herdando de Avo::Advanced::Scopes::BaseScope e exibindo a contagem no nome. Remover WithStatusScheduled (status inexistente) e renomear WithStatusPaid para WithStatusDone.

6. Actions condicionais

Action Visibilidade Efeito
Cancelar Invoice e Liberar Meetings show + developer + status em blocked/canceled/failed destrói as associações de meetings (liberando-as para nova invoice) e marca a invoice como canceled, em transação
Criar Nova Invoice show + developer + o profissional tem meetings invoiceáveis chama ProfessionalPaymentInvoices::Create e reporta sucesso/erro
Re-agendar transferência show + developer + status em created/failed + created_at > 10.days.ago enfileira ProfessionalPaymentInvoiceTransferCreationJob; a mensagem alerta quando retry_count >= 5

7. Card de alerta no show

Avo::Cards::ProfessionalInvoices::RetryStatusCard, visível quando failed? ou blocked?, com três estados: bloqueada (requer intervenção manual), limite de tentativas atingido, e em período de retry (com a próxima tentativa e o último erro).

Arquivos

Modificados: app/avo/resources/professional_payment_invoice.rb, app/avo/resources/professional_payment_invoice_transfer.rb, app/avo/actions/professional_invoices/execute_transfer.rb.

Novos: app/avo/scopes/with_status_blocked.rb, with_status_created.rb, in_retry_period.rb, expired_retry_period.rb, app/avo/actions/professional_invoices/cancel_and_release_meetings.rb, create_for_professional.rb, app/avo/cards/professional_invoices/retry_status_card.rb.

Removidos: app/avo/scopes/with_status_scheduled.rb.

Desvio verificado em 2026-08-06: as actions CancelAndReleaseMeetings e CreateForProfessional, o RetryStatusCard e os campos de retry foram entregues. O mapeamento de badges desta spec, porém, foi superado: o enum de ProfessionalPaymentInvoice hoje é pending: 0, paid: 1, canceled: 2 — os status created, done, in_bank_processing, failed e blocked pertencem a ProfessionalPaymentInvoiceTransfer, não à invoice. Os scopes InRetryPeriod/ExpiredRetryPeriod não existem; o resource usa WithStatusInBankProcessing, WithStatusPaid, WithStatusCanceled, PaidCurrentMonth e PendingOld. Os arquivos with_status_blocked.rb e with_status_created.rb existem mas não são referenciados pelo resource de invoice.

Como verificar

  • Abrir a index de invoices no Avo e confirmar que todo status renderiza um badge (nenhum em branco).
  • Abrir o show de uma invoice com falha e confirmar que os campos de retry e o RetryStatusCard aparecem.
  • Confirmar que “Cancelar Invoice e Liberar Meetings” só aparece nos estados previstos e que, após executá-la, as meetings voltam a ser invoiceáveis.

Documentação

O estado atual das telas e ações está descrito em invoice_payment_flow.