Endpoint de débitos por cliente
TLDR: novo
GET /api/v1/customers/:customer_id/charges/que devolve todos os débitos de um cliente com parcelas, totais e produtos, para hidratar a aba Financeiro do detalhe do cliente.
Contexto
A aba Financeiro (ChargesTab) do detalhe do cliente mostra, por débito, a tabela de parcelas (Nº parcela, Valor original, Vencimento, Juros, Desconto, Valor atual, Status, Data de pagamento) e os totais do débito. Hoje não existe endpoint que traga os débitos de um cliente específico: GET /api/v1/debits só filtra por search (nome/CPF via LIKE) e status, e o DebitSerializer não inclui as parcelas.
Objetivos
- Expor
GET /api/v1/customers/:customer_id/charges/com todos os débitos do cliente, sem paginação. - Cada débito traz: parcelas, total de parcelas, total a pagar e produtos do débito.
- Cada parcela traz: número, valor original, vencimento, juros, desconto, valor atual, status e data de pagamento.
- Criar em
installmentsos campos de juros e desconto, que não existem hoje. - Mostrar todos os débitos do cliente para qualquer usuário autenticado, sem a restrição por responsável do
DebitsController(current_user.debits). - Débitos em aberto (
Debit::OPEN_STATUSES) vêm primeiro, depois os demais.
Fora de escopo
- Preenchimento de juros e desconto: esta mudança só cria as colunas (com
0por padrão) e as expõe. Calcular ou sincronizar os valores (gateway, regras de quitação R-002) é uma mudança separada. - Recalcular o valor atual:
current_amount_centscontinua sendopayable_amount_cents; os novos campos não entram nesse cálculo. - Integração do front-end: o detalhe do cliente ainda lê o cliente de mock (
INITIAL_CUSTOMERS), então ligar aChargesTabneste endpoint é uma mudança separada. - Paginação, filtros e ordenação configurável pelo cliente da API.
Mudanças
modules/backend/db/migrate/<timestamp>_add_interest_and_discount_to_installments.rb (novo)
Hoje installments não tem campo de juros nem de desconto (só amount_cents e updated_amount_cents, que já vem com os ajustes somados). A migration cria os dois:
ruby
class AddInterestAndDiscountToInstallments < ActiveRecord::Migration[8.1]
def change
add_column :installments, :interest_cents, :integer, default: 0, null: false
add_column :installments, :discount_cents, :integer, default: 0, null: false
end
end
Parcelas existentes ficam com 0 nos dois campos.
modules/backend/app/models/installment.rb
- Validações:
interest_centsediscount_centsinteiros,>= 0. - Testes em
test/models/installment_test.rb: valor negativo eminterest_centsoudiscount_centsé inválido. - Atualizar o bloco de anotação do schema (também em
test/models/installment_test.rbetest/fixtures/installments.yml).
modules/backend/db/seeds.rb
- Parcelas
overduedo seed passam a terinterest_centsaleatório (> 0);discount_centssegue0.
modules/backend/config/routes.rb
Nova rota dentro de namespace :api / :v1:
ruby
resources :customers, only: [] do
resources :charges, only: [ :index ], controller: :customer_charges
end
modules/backend/app/controllers/api/v1/customer_charges_controller.rb (novo)
Api::V1::CustomerChargesController < BaseController, action index: renderiza os débitos como { data: [...] } com DebitSerializer. Cliente existente sem débitos → 200 com data: [].
A consulta fica no método privado debits do controller:
- Busca o cliente com
Customer.find(params[:customer_id]); cliente inexistente levantaActiveRecord::RecordNotFound, que oBaseControllerresponde com404. - Escopo:
customer.debits— todos os débitos do cliente, para qualquer usuário autenticado. includes(:customer, :installments, :product_debits).- Ordenação no banco com
in_order_of(:status, Debit::OPEN_STATUSES, filter: false): primeiro os débitos em aberto, na ordem deOPEN_STATUSES(pending,no_forecast,no_response,bureau_report); depois os demais (negotiated,cancelled,negativated), sem ordem definida entre si.
modules/backend/app/serializers/debit_serializer.rb
Serializer único das listagens (GET /debits e GET /customers/:customer_id/charges). Os campos abaixo foram acrescentados aos que já existiam (customer_*, installment_amount_cents, next_due_on, opened_at, first_installment_due_on).
| Campo | Origem |
|---|---|
id |
debit.id |
status |
debit.status |
kind |
debit.payment_type |
installments_count |
quantidade de parcelas carregadas (Total de parcelas) |
total_cents |
soma de installments.amount_cents (valor original) |
total_payable_cents |
soma de installments.payable_amount_cents de todas as parcelas, pagas inclusas (Total a pagar) |
products |
[{ name: product_debit.product_name, external_id: product_debit.external_id }] |
installments |
lista ordenada por number (ver abaixo) |
Cada item de installments:
| Campo | Coluna da tela | Origem |
|---|---|---|
number |
Nº parcela | installment.number |
amount_cents |
Valor original | installment.amount_cents |
due_on |
Vencimento | installment.due_on (ISO 8601) |
interest_cents |
Juros | installment.interest_cents |
discount_cents |
Desconto | installment.discount_cents |
current_amount_cents |
Valor atual | installment.payable_amount_cents (updated_amount_cents ou, na falta, amount_cents) |
status |
Status | installment.status (paid, upcoming, overdue, defaulted) |
paid_at |
Data de pagamento | installment.paid_at (ISO 8601 ou null) |
Exemplo:
json
{
"data": [{
"id": 7,
"status": "pending",
"kind": "installment",
"installments_count": 12,
"total_cents": 120000,
"total_payable_cents": 126000,
"products": [{ "name": "Curso X", "external_id": "abc" }],
"installments": [{
"number": 1,
"amount_cents": 10000,
"due_on": "2026-08-10",
"interest_cents": 500,
"discount_cents": 0,
"current_amount_cents": 10500,
"status": "overdue",
"paid_at": null
}]
}]
}
modules/backend/test/controllers/api/v1/customer_charges_controller_test.rb (novo)
Testes de integração usando as fixtures existentes (customers, debits, installments, product_debits, users):
- lista todos os débitos do cliente, com os campos e totais esperados.
- attendant vê todos os débitos do cliente, inclusive os que não são atribuídos a ele.
- débitos em aberto vêm antes dos pagos.
- parcelas vêm ordenadas por
number, ecurrent_amount_centsusaupdated_amount_centsquando existe. interest_centsediscount_centsvêm na parcela.- débitos de outro cliente não aparecem.
- cliente inexistente →
404. - sem token →
401.
Como verificar
bin/rails db:migrateemmodules/backendroda sem erro edb/schema.rbpassa a terinterest_centsediscount_centseminstallments.make backend.run.testna raiz (oubin/rails test test/controllers/api/v1/customer_charges_controller_test.rbemmodules/backend) passa.- Com o servidor rodando e autenticado como admin,
GET /api/v1/customers/:customer_id/charges/de um cliente com seed devolve o payload no formato acima.
Documentação
.project/docs/README.md: adicionar esta spec ao índice despecs/.