Importar só pagamentos com parcela em atraso
TLDR: o SQL de leitura do checkout passa a exigir, além do
payments.status = 'overdue', ao menos uma parcelaoverdueque não foi deletada no gateway. Pagamento e parcela deletados no gateway são sempre desconsiderados, e ogateway_deletedé lido comCOALESCEnos dois.
Continuação de Importação dos pagamentos atrasados do checkout como débitos, que só filtrava o status do pagamento.
Contexto
Hoje o import lê os pagamentos com payments.status = 'overdue', criados desde 01/01/2026 e com
gateway_deleted falso ou nulo. As parcelas só são lidas depois, e a parcela deletada só é
descartada na hora de copiá-las para o débito. Nada garante que o pagamento importado tem uma
parcela atrasada e ativa. Um pagamento marcado como overdue cuja parcela atrasada foi deletada no
gateway, ou que não tem parcela atrasada, viraria um débito sem nada a cobrar.
Objetivos
- Importar só o pagamento que tem ao menos 1 parcela
overduee ativa. - Desconsiderar pagamento e parcela deletados no gateway, tanto para escolher o pagamento quanto para copiar as parcelas.
- Ler
gateway_deletedcomCOALESCE(..., false) = false, no pagamento e na parcela. - Manter a leitura paginada por keyset, numa consulta por página.
Fora de escopo
- Remover ou alterar débitos já importados que não atendem à nova regra: o reimport só pula.
- Mudar quais parcelas o débito recebe: continuam todas as ativas (
paid,pendingeoverdue). - Mudar o corte de data (
created_at >= since, 01/01/2026 00:00 em Brasília). - Índices no banco do checkout: o nectar só lê, e o import roda sob demanda.
Regras
Fonte: pagamentos do checkout
Entra o pagamento que atende a todos estes critérios:
payments.status = 'overdue'payments.created_at >= since.in_time_zone.beginning_of_day(default2026-01-01)COALESCE(payments.gateway_deleted, false) = false- existe ao menos uma parcela do pagamento com
installments.status = 'overdue'eCOALESCE(installments.gateway_deleted, false) = false
Uma parcela deletada no gateway não conta para o EXISTS: um pagamento cuja única parcela atrasada
foi deletada fica de fora.
SQL
Primeira página, com batch_size 500:
sql
SELECT payments.*, organizations.id AS organization_id, organizations.slug AS organization_slug
FROM payments
INNER JOIN checkouts ON checkouts.id = payments.checkout_id
INNER JOIN organizations ON organizations.id = checkouts.organization_id
WHERE payments.status = 'overdue'
AND payments.created_at >= '2026-01-01 03:00:00'
AND COALESCE(payments.gateway_deleted, false) = false
AND EXISTS (SELECT 1 FROM installments
WHERE installments.payment_id = payments.id
AND installments.status = 'overdue'
AND COALESCE(installments.gateway_deleted, false) = false)
ORDER BY payments.id ASC
LIMIT 500
Da segunda página em diante o find_in_batches acrescenta
AND payments.id > <último id da página anterior> antes do ORDER BY. A paginação, o join com
organizations e o corte de fuso não mudam em relação à spec original.
Parcela ativa em um só lugar
Checkout::Installment.active passa a ser COALESCE(installments.gateway_deleted, false) = false, e
ganha o scope overdue (installments.status = 'overdue'). O EXISTS do SQL e a leitura das
parcelas de cada página usam esse mesmo scope, para “parcela ativa” nunca divergir entre as duas.
Mudanças
app/models/checkout/installment.rb:activecomCOALESCE, e o scopeoverdue.app/models/checkout/payment.rb: o scopeoverdue_sincepassa a usar oCOALESCEempayments.gateway_deletede a exigir oEXISTSsobreCheckout::Installment.active.overduecorrelacionado porpayment_id. Opayments.status = 'overdue'continua.test/fixtures/checkout/installments.yml: parcelas para os pagamentosoverdueque hoje não têm parcela atrasada (overdue_settlement,overdue_renewal,overdue_rafael,overdue_boundary_after,overdue_no_email,overdue_orphan_customer,overdue_no_document,overdue_no_reference,overdue_unsupported_provider), para os testes do import continuarem cobrindo cada motivo de pulo.test/fixtures/checkout/payments.yml: casos novos (ver abaixo).test/models/checkout/payment_test.rb: testes do scope.test/use_cases/debits/import_overdue_from_checkout_test.rb: ajusta as contagens que dependem dos fixtures novos.
Casos novos de fixture:
| Pagamento | Situação | Entra? |
|---|---|---|
overdue_only_paid_installments |
status = overdue, parcelas só paid e pending |
não |
overdue_deleted_installment_only |
status = overdue, única parcela overdue com gateway_deleted = true |
não |
overdue_null_gateway_deleted |
status = overdue, gateway_deleted nulo, parcela overdue ativa |
sim |
pending_with_overdue_installment |
status = pending, com parcela overdue ativa |
não (status) |
Como verificar
bin/rails temmodules/backendpassa.Checkout::Payment.overdue_since(Date.new(2026, 1, 1)).to_sqlgera o SQL da seção SQL, e oEXPLAINnão faz varredura sequencial deinstallmentspor pagamento (usaindex_installments_on_payment_id).- Em development, com
CHECKOUT_DATABASE_URLno banco do checkout, comparar a contagem antes e depois:Checkout::Payment.overdue_since(since).countcai apenas pelos pagamentosoverduesem parcela atrasada ativa, e a diferença bate com a consulta direta no checkout. - Rodar o job e conferir que nenhum débito novo tem parcela
overdueausente:Debit.where(provider_payment_id: ...)seminstallments.status = 'overdue'dá zero para os importados depois desta mudança.
Documentação
.project/docs/rules/collections/checkout_overdue_import.md: nova redação daR-009.1(parcelaoverdueativa,COALESCEnogateway_deleted) eupdatedcom a data do dia..project/docs/specs/20260928171636_import_overdue_checkout_payments.md: em Fonte, apontar para esta spec..project/docs/README.md: registrar esta spec e o futuro plano.