feat(products): add rate card GraphQL - #5707
Merged
Merged
Conversation
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
2 times, most recently
from
June 12, 2026 09:01
8760132 to
5d5175e
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
June 12, 2026 09:24
5b13f98 to
c42f976
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
from
June 12, 2026 09:24
5d5175e to
28bb3ac
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
June 18, 2026 10:21
c42f976 to
349bc65
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
from
June 18, 2026 10:21
28bb3ac to
77426a7
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
June 18, 2026 11:49
349bc65 to
78da4f2
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
2 times, most recently
from
June 18, 2026 12:00
c0227cc to
f03063e
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
June 18, 2026 12:00
78da4f2 to
7b15c60
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
from
June 18, 2026 12:36
f03063e to
ef03551
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
2 times, most recently
from
June 18, 2026 12:39
0961809 to
ac0e228
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
3 times, most recently
from
June 22, 2026 13:17
2f53905 to
8c25fb7
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
June 22, 2026 13:17
e5b933c to
bba2f03
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
from
June 22, 2026 13:24
8c25fb7 to
c1e5c6f
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
2 times, most recently
from
June 22, 2026 15:06
b56be52 to
2d19901
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
4 times, most recently
from
June 22, 2026 15:28
e2fde88 to
c0ab090
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
2 times, most recently
from
June 22, 2026 19:21
51b842a to
8393467
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
from
June 22, 2026 19:21
c0ab090 to
ec9724a
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
June 22, 2026 20:53
8393467 to
4eee616
Compare
rsempe
force-pushed
the
products-and-plans-api
branch
from
July 6, 2026 14:34
87b0967 to
3678f9f
Compare
rsempe
force-pushed
the
products-and-plans-rate-cards
branch
2 times, most recently
from
July 8, 2026 13:57
9e19a56 to
45d3660
Compare
This was referenced Jul 30, 2026
This was referenced Aug 7, 2026
endenis
approved these changes
Aug 12, 2026
GraphQL surface for rate cards and their rates, powering the rate cards tabs of the catalog and the rate timeline management. Add the RateCard and RateCardRate object types with their enums (status exposed as a derived value), create/update inputs, Create/Update/Destroy mutations for both entities backed by the services, and single + collection rate card resolvers with pagination, search and product scoping. Rate cards are exposed on the Product type and join the activity log resource union; a rate_cards permission family is registered. Regenerate the GraphQL schema dumps.
The rate cards list needs the active rate and the rate count per card, but the rate card type exposed the whole rates collection inline, which is a payload and N+1 liability and duplicates a dedicated rates query. Drop the nested rates field from the RateCard GraphQL type; add active_rate and rates_count instead, and expose the rates of a card through a new rateCardRates resolver / RateCardRatesQuery. The REST serializer gains rates_count and an on-demand active_rate. Add server-side rate card filters by code, product_code and product_filter_code. Creation still accepts a nested rates array (GraphQL input + service) so a card can be authored with its pricing in one call; later appends go through the dedicated rates endpoint.
regroup_paid_fees became a NOT NULL enum with none as its default. The services and validation on this branch still read null as the absence of regrouping, so every card carrying the new default would have failed the compatibility validation, and an explicit null in an update read as a locked-field change. Read none, not null, as the no-regrouping state in the compatibility validation. Creation falls back to the column default when the caller omits the value or sends null, and an update normalises an explicit null to none before the locked-field comparison, so resetting the field on a card with rates is not a phantom price change. The GraphQL field is now non-nullable.
## Context The rate column was renamed to effective_from on the data model, and arrears rates now require date granularity. ## Description Rename the remaining effective_datetime references on the rate card layer: services, query, serializer, GraphQL types and the schema dumps. Spec rate values normalise to midnight so they satisfy the arrears date rule; ordering between rates is unchanged.
## Context The strict arrears contract — refusing datetime strings at the REST boundary with must_be_a_date — was replaced by canonicalization: any date or datetime is accepted and an arrears value is normalized to its day's midnight by the model, keeping one rate per day through the unique index. ## Description Cover the lenient contract at the service level: a datetime on an arrears card is stored at midnight, a bare date is accepted, a second rate on the same day fails with value_already_exist whatever its time, and advance cards keep full instants while converting bare dates to midnight.
## Context Card lists ran per-row queries for rates_count and the attachment lock signal, the same shape the catalog types already batch through dataloader sources. ## Description Resolve rates_count through CountByForeignKey and the attachment flag through AttachedToPlanOrSubscription, which gains a join-less rate_card grouping. The serializer reads rates through size so the REST index can preload them.
## Context Review findings on the card GraphQL surface: active_rate and attached_to_subscriptions still ran per-row queries on lists, and the activity-log payloads silently lost their rates when the API payloads stopped nesting them. ## Description Batch active_rate through a DISTINCT ON source and the subscription attachment flag through its own source, and restore the opt-in rates include on the serializer so activity payloads carry the full timeline like plans carry their charges.
tiagolupepic
approved these changes
Aug 12, 2026
tiagolupepic
left a comment
Contributor
There was a problem hiding this comment.
just a small comment and it's not a blocker
![]()
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
Rate cards are where pricing lives: one card prices exactly one product, or one filter slice of it. Rates sit on the card as an effective-dated timeline, so a price change is an append rather than an edit.
Description
RateCard(billing timing, regroup), with mutations and resolvers behind therate_cards:*permissions.ratesCount, the attachment lock signal) batched through the dataloader sources.rateCardRates), keyed by card.regroup_paid_feesis exposed as a nullable enum where a null input meansnone(the column is NOT NULL).