Skip to content

feat(fixed-charges): skip plan override on units-only update (PoC)#5505

Draft
ancorcruz wants to merge 6 commits into
mainfrom
feat/fixed-charge-subscription-units-poc
Draft

feat(fixed-charges): skip plan override on units-only update (PoC)#5505
ancorcruz wants to merge 6 commits into
mainfrom
feat/fixed-charge-subscription-units-poc

Conversation

@ancorcruz
Copy link
Copy Markdown
Contributor

Summary

Background

Updating a fixed charge's units for one subscription today materialises a full plan override plus a fixed-charge override.

That's correct semantically but expensive in practice: organisations with many subscriptions on a shared plan accumulate one plan override per subscription, even though only units differs. This PoC offers a units-only fast path that avoids the plan override entirely while preserving the legacy path for any other field change.

Out of scope for this PoC

Read sites still consume fixed_charge.units directly. They need to be switched to fixed_charge.units_for(subscription) in a follow-up before this can ship beyond PoC:

  • app/services/fees/fixed_charge_service.rb
  • app/services/fees/build_pay_in_advance_fixed_charge_service.rb
  • app/services/fees/init_from_adjusted_fixed_charge_fee_service.rb
  • app/services/fixed_charge_events/aggregations/preview_aggregation_service.rb

The scenario spec deliberately stops at the event level until those reads are wired.

ancorcruz added 6 commits May 13, 2026 13:18
…rride table

## Context

PoC for letting subscriptions store a per-fixed-charge unit value
without materialising a plan override. The original implementation was
landed under PR #4011 and reverted under PRs #4318 and #4876 while
waiting for the wider plan-override refactor. Reintroducing it as the
foundation for a units-only override path.

## Description

Recreate the subscription_fixed_charge_units_overrides table, model,
factory and model spec. No application code consumes it yet; the
service and read-path wiring follow in subsequent commits.
…fixed charge

## Context

Wire the SubscriptionFixedChargeUnitsOverride model into its two
parents so the override can be read or built from either side without
going through a join lookup.

## Description

Add `has_many :fixed_charge_units_overrides` on Subscription and
`has_many :subscription_units_overrides` on FixedCharge, plus the
matching association assertions in the existing model specs.
… units

## Context

Read-paths that consume a fixed charge's units in a subscription
context need to honour the per-subscription override when one exists.
Adding a single accessor on FixedCharge so callers can drop in a
subscription-aware lookup without rewriting every read site.

## Description

Add `FixedCharge#units_for(subscription)`. Returns the override units
when a SubscriptionFixedChargeUnitsOverride exists for the pair, and
falls back to the fixed charge's own `units` otherwise. Soft-discarded
overrides are ignored thanks to the default scope on the override
model.
…ange

## Context

When a subscription's fixed-charge units are updated through the
API, the current implementation always materialises a plan override
plus a fixed-charge override. For customers with thousands of
subscriptions on a shared plan this produces an explosion of plan
overrides that are otherwise identical to the parent plan. This
change introduces a units-only fast path that records the override on
the dedicated table instead of cloning the plan.

## Description

In `Subscriptions::UpdateOrOverrideFixedChargeService`, branch on the
incoming params: when only `units` (optionally with
`apply_units_immediately`) is supplied and the subscription is not
already on an overridden plan, create or update a
`SubscriptionFixedChargeUnitsOverride` against the parent fixed
charge and emit billing events as before. Any other field change, or
a subscription already on an overridden plan, keeps the existing
plan-override behaviour to avoid splitting the unit value across two
storage mechanisms.
## Context

The fixed-charge event is the value that drives downstream billing.
With the units-only override path landing, the parent fixed charge
is reused for emission and its `units` no longer reflects the
subscription-specific value. The event must resolve the effective
units for the subscription it belongs to so billing stays accurate.

## Description

In `FixedChargeEvents::CreateService`, build the event with
`fixed_charge.units_for(subscription)` instead of `fixed_charge.units`.
`FixedCharge#units_for` is taught to fall back to its own `units` when
no subscription is passed so the existing nil-subscription validation
path keeps working.
…override

## Context

Unit specs cover the new service path and the units lookup helper in
isolation. To trust the round-trip against the real API and job
pipeline, a scenario spec drives the subscription fixed-charge endpoint
end-to-end and asserts the side effects observable from the database.

## Description

Add `update_subscription_fixed_charge` to the scenarios helper for
hitting `PUT /api/v1/subscriptions/:external_id/fixed_charges/:code`.
Add a scenario spec that creates a subscription, hits the endpoint
with a units-only payload, runs the enqueued jobs, and asserts no
plan override is created, no fixed-charge override is created, a
SubscriptionFixedChargeUnitsOverride row is created, and the emitted
fixed charge event carries the override units.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant