Conversation
## Context The customer detail page is gaining a per-(currency × billing entity) breakdown table for credit notes balances, replacing the single "Total amount available" card. The previous resolver only grouped by currency, so multi-entity customers could not see balances split by entity, and there was no count of credit notes per bucket. ## Description `CustomerCreditNotesBalance` now exposes `billingEntityId` and `creditsAvailableCount` alongside `currency` and `amountCents`. The `customer.creditNotesBalances` resolver groups by both currency and billing entity (joining invoices) and emits one row per pair, with the sum of balances and the count of finalized credit notes that issued credits. The `balance_amount_cents > 0` filter is removed so buckets with fully-consumed credits still flow through; the consumer applies visibility client-side. Specs cover single, multi-currency, multi-entity, combined, fully-consumed, and empty-customer scenarios.
## Context CI's `GraphQL/ResolverMethodLength` cop caps resolver methods at 10 lines; the new `#credit_notes_balances` came in at 11. ## Description Chain `.joins(:invoice)` onto the same line as `object.credit_notes.finalized` to drop one line. No behavior change.
aquinofb
commented
May 22, 2026
domenicofalco
approved these changes
May 22, 2026
mariohd
approved these changes
May 25, 2026
D1353L
pushed a commit
that referenced
this pull request
May 26, 2026
## Context
The customer detail page is replacing its single "Total amount
available" credit-notes summary card with a per-(currency × billing
entity) breakdown table, mirroring the Invoice balances treatment that's
landing alongside it. Today the `customer.creditNotesBalances` GraphQL
field only groups by currency, so customers transacting across multiple
billing entities cannot see their balances split by entity, and there's
no per-bucket count of credit notes for the row subtitle.
## Description
`CustomerCreditNotesBalance` now exposes two new non-null fields
(`billingEntityId: ID!` and `creditsAvailableCount: Int!`) in addition
to the existing `currency` and `amountCents`. The
`customer.creditNotesBalances` resolver groups finalized credit notes by
both currency and billing entity (joining `invoices`) and emits one row
per `(currency, billingEntity)` pair, returning the sum of
`balance_amount_cents` and the count of finalized credit notes with
`credit_amount_cents > 0`.
The previous `balance_amount_cents > 0` filter is removed so buckets
with fully-consumed credits still flow through: this is required so the
consumer can render the "credit notes issued & credited" subtitle even
when the available balance is zero. The consumer applies visibility
client-side via `amountCents > 0 OR creditsAvailableCount > 0`.
Old vs new response shape for the same customer (one billing entity,
single currency, and one cross-entity case):
| Customer | Before | After |
| --- | --- | --- |
| Single-entity, single-currency | `[{ currency: "EUR", amountCents:
1500 }]` | `[{ currency: "EUR", billingEntityId: "...", amountCents:
1500, creditsAvailableCount: 3 }]` |
| Cross-currency, cross-entity (3 buckets) | 1 row per currency only | 1
row per `(currency, billingEntity)` pair |
| Fully-consumed bucket | omitted | included with `amountCents: 0` and
`creditsAvailableCount > 0` |
Implementation notes:
- Uses PostgreSQL's `COUNT(*) FILTER (WHERE ...)` rather than
`COUNT(CASE WHEN ...)`. Lago is PG-only; the FILTER form is equivalent
and more idiomatic.
- Raw SQL aggregates are wrapped in `Arel.sql(...)` to satisfy Rails 8's
`disable_joins`-style protection against unsafe `pluck` inputs.
- `joins(:invoice)` is safe: `credit_notes.invoice_id` is `NOT NULL`
with a FK, and `invoices.billing_entity_id` is `NOT NULL` at the DB
level, so no rows are dropped and the new `ID!` typing is sound.
- `schema.graphql` and `schema.json` are regenerated.
## How to try locally
1. Check out the branch and rebuild the API container if needed.
2. Seed or open a customer with credit notes across multiple currencies
and billing entities (a quick `rails console` script that creates a
couple of `:billing_entity`, a couple of `:invoice`, and several
`:credit_note` records works).
3. Run the GraphQL query against the customer:
```graphql
query($id: ID!) {
customer(id: $id) {
creditNotesBalances {
currency
billingEntityId
amountCents
creditsAvailableCount
}
}
}
```
Confirm: one row per `(currency, billingEntityId)` pair, amounts sum
correctly per bucket, and counts only include credit notes with
`credit_amount_cents > 0`.
4. Verify a single-entity, single-currency customer still returns
exactly one row (no regression).
5. `bundle exec rspec spec/graphql/types/customers/object_spec.rb
spec/graphql/lago_api_schema_spec.rb` to confirm the new behavior specs
and the schema-dump cross-check both pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
The customer detail page is replacing its single "Total amount available" credit-notes summary card with a per-(currency × billing entity) breakdown table, mirroring the Invoice balances treatment that's landing alongside it. Today the
customer.creditNotesBalancesGraphQL field only groups by currency, so customers transacting across multiple billing entities cannot see their balances split by entity, and there's no per-bucket count of credit notes for the row subtitle.Description
CustomerCreditNotesBalancenow exposes two new non-null fields (billingEntityId: ID!andcreditsAvailableCount: Int!) in addition to the existingcurrencyandamountCents. Thecustomer.creditNotesBalancesresolver groups finalized credit notes by both currency and billing entity (joininginvoices) and emits one row per(currency, billingEntity)pair, returning the sum ofbalance_amount_centsand the count of finalized credit notes withcredit_amount_cents > 0.The previous
balance_amount_cents > 0filter is removed so buckets with fully-consumed credits still flow through: this is required so the consumer can render the "credit notes issued & credited" subtitle even when the available balance is zero. The consumer applies visibility client-side viaamountCents > 0 OR creditsAvailableCount > 0.Old vs new response shape for the same customer (one billing entity, single currency, and one cross-entity case):
[{ currency: "EUR", amountCents: 1500 }][{ currency: "EUR", billingEntityId: "...", amountCents: 1500, creditsAvailableCount: 3 }](currency, billingEntity)pairamountCents: 0andcreditsAvailableCount > 0Implementation notes:
COUNT(*) FILTER (WHERE ...)rather thanCOUNT(CASE WHEN ...). Lago is PG-only; the FILTER form is equivalent and more idiomatic.Arel.sql(...)to satisfy Rails 8'sdisable_joins-style protection against unsafepluckinputs.joins(:invoice)is safe:credit_notes.invoice_idisNOT NULLwith a FK, andinvoices.billing_entity_idisNOT NULLat the DB level, so no rows are dropped and the newID!typing is sound.schema.graphqlandschema.jsonare regenerated.How to try locally
rails consolescript that creates a couple of:billing_entity, a couple of:invoice, and several:credit_noterecords works).(currency, billingEntityId)pair, amounts sum correctly per bucket, and counts only include credit notes withcredit_amount_cents > 0.bundle exec rspec spec/graphql/types/customers/object_spec.rb spec/graphql/lago_api_schema_spec.rbto confirm the new behavior specs and the schema-dump cross-check both pass.