Integração backend da fila de negativação — Plano de implementação
TLDR: job diário popula
registry_negativations; GET endpoint lê do registry; frontend conectado sem mocks.
Spec:
.project/docs/specs/20260917115417_negativation_backend_integration.mdBranch:feat/RegistryNegativation
Arquitetura: NegativationQueueJob roda às 01:00 via solid_queue, avalia Debit.eligible_for_negativation e popula registry_negativations. O controller lê do registry diretamente — sem derivar elegibilidade em tempo real.
Stack: Rails 8.1 + solid_queue (minitest, fixtures), React + TypeScript (vitest).
Status das tasks
| Task | Descrição | Status |
|---|---|---|
| 1 | Migration + Model RegistryNegativation |
✅ feito |
| 2 | NegativationQueueJob + solid_queue |
✅ feito |
| 3 | Controller + Rota (queue) + Serializer |
✅ feito |
| 4 | Debit.eligible_for_negativation scope + testes unitários diretos |
✅ feito |
| 5 | Testes para NegativationQueueJob |
✅ feito |
| 6 | Controller lendo do registry + testes | ✅ feito |
Task 1: Migration + Model RegistryNegativation ✅
Files criados/modificados:
- db/migrate/20260917143700_create_registry_negativations.rb
- db/schema.rb
- app/models/registry_negativation.rb
- app/models/debit.rb — has_many :registry_negativations
- app/models/installment.rb — belongs_to :registry_negativation, optional: true
- test/fixtures/registry_negativations.yml
- test/models/registry_negativation_test.rb
O que foi feito:
- Tabela registry_negativations com colunas: debit_id, executed_by_id, closed_by_id, executed_at, closed_at, gateway_ref, status (default pending)
- Coluna registry_negativation_id adicionada em installments (FK opcional)
- Status enumerado: pending, negative, closed, cancelled
- Scope active_with_debit no model: exclui closed e cancelled, faz eager load de debit: [:customer, :installments, :product_debits, :user]
- Schema atualizado: installments_count com default 0, total_cents removido, status default de installment alterado para upcoming
Testes implementados (registry_negativation_test.rb):
- defaults to pending status
- requires executed_at
- debit has many registry_negativations
- associates installments directly
Task 2: NegativationQueueJob + solid_queue ✅
Files criados/modificados:
- Gemfile + Gemfile.lock — gem solid_queue
- config/environments/production.rb — adapter solid_queue, connects_to queue
- config/queue.yml — workers com 3 threads, polling 1s
- config/recurring.yml — NegativationQueueJob todo dia às 01:00 (production)
- bin/jobs — entrypoint do solid_queue CLI
- db/queue_schema.rb — schema separado para o banco de filas
- app/jobs/negativation_queue_job.rb
- app/use_cases/negativation/register_eligible_debit.rb
O que foi feito:
- NegativationQueueJob#perform chama Negativation::RegisterEligibleDebit.call(manager:), onde o manager é buscado via ENV["MANAGER_DEFAULT_ID"]
- Negativation::RegisterEligibleDebit.call: itera Debit.open.eligible_for_negativation e faz find_or_create_by! em RegistryNegativation — idempotente por design
- Job configurado na fila :default
Task 3: Controller + Rota + Serializer ✅
Files criados/modificados:
- config/routes.rb — GET /api/v1/negativation/queue
- app/controllers/api/v1/negativation_controller.rb
- app/serializers/negativation_queue_serializer.rb
O que foi feito:
- Controller usa RegistryNegativation.active_with_debit (scope do model)
- NegativationQueueSerializer.call(registries) — retorna array de rows, uma por product_debit
- Cada row: name, product, progress, installments (contagem overdue/defaulted), amount (centavos → reais), status (pending / negativated / eligible_for_removal), collaborator
- Lógica de frontend_status: pending se registry pendente; se negativado, verifica se ainda há installments overdue para distinguir negativated de eligible_for_removal
Testes implementados (negativation_controller_test.rb):
- GET queue retorna 200 com array
- GET queue retorna 401 sem token
- GET queue retorna uma row por product_debit no registry
- GET queue retorna amount em reais (74_100 centavos × 3 = 2223.0)
- GET queue exclui registries closed e cancelled
Task 4: Debit.eligible_for_negativation ⚠️
Files modificados:
- app/models/debit.rb — scope implementado
- test/models/debit_test.rb — sem testes novos (só atualização de schema comment)
O que foi feito:
ruby
scope :eligible_for_negativation, -> {
joins(:product_debits)
.where(product_debits: { consumer_progress: 26.. })
.joins(:installments)
.where(installments: { status: %w[overdue defaulted] })
.group("debits.id")
.having("COUNT(DISTINCT installments.id) >= 3")
}
Pendente:
- [ ] Adicionar testes unitários diretos em test/models/debit_test.rb:
- retorna debit com progress > 25 e 3+ installments overdue
- exclui debit com progress ≤ 25
- exclui debit com menos de 3 installments overdue
Cobertura indireta já existe em
register_eligible_debit_test.rb(test “skips ineligible debits”), mas testes diretos no model são necessários.
bash
docker compose exec backend-api bundle exec rails test test/models/debit_test.rb
Task 5: Testes para NegativationQueueJob ✅
Files criados:
- test/jobs/negativation_queue_job_test.rb
Testes implementados:
- cria RegistryNegativation para debit elegível
- não duplica registry para o mesmo debit (idempotência)
Task 6: Controller lendo do registry ✅
Implementado junto com a Task 3. O controller já lê de RegistryNegativation.active_with_debit desde o início — não houve need de refatoração intermediária. Os testes do controller criam RegistryNegativation explicitamente antes do GET.