Skip to content

Step 1b — the Subscription / SubscriptionItem data model - #809

Draft
mitchelljkotler wants to merge 4 commits into
pricing-modelsfrom
pricing-models-item-schema
Draft

Step 1b — the Subscription / SubscriptionItem data model#809
mitchelljkotler wants to merge 4 commits into
pricing-modelsfrom
pricing-models-item-schema

Conversation

@mitchelljkotler

Copy link
Copy Markdown
Member

Stacks on #781. Split out of #784 so the structural change and the behaviour change can be read separately.

⚠️ Do not merge this to master on its own

CI/CD deploys automatically on merge to master, and this half has the columns but not the behaviour that keeps billing pages, renewal dates and the reconciliation commands correct. Merge #784 into this branch first, then merge this to master — the two are one deployable unit.

Everything above #784 in the stack stays individually deployable; this pair is the exception.

What this is

The rename and the restructure, and nothing that changes what the system does. The question to read it with is "does this still mean the same thing?" — almost every hunk is the same expression reached a different way.

  • SubscriptionSubscriptionItem, and a new Subscription parent that owns the Stripe subscription. Stripe requires every item on a subscription to share a billing interval and collection method, so an organization needs one subscription per combination it holds — that's what the uniqueness constraint encodes.
  • Subscription-level state (status, period end, cancellation) moves to the parent, so a renewal webhook updates one row rather than fanning out across items that could then disagree.
  • 50 files of mechanical repointing.

What it deliberately isn't

Per-item cancellation, the Stripe sync logic, the billing pages, the renewal date moving off the API, the reconciliation commands — all in #784, which is 25 files and where every review finding so far has landed.

Migrations

0083 and 0084 land here in draft form; #784 finalises them (Squash the split migrations and make them reversible) and adds 0085. Since this branch never reaches master without #784 merged into it, the intermediate migrations are never applied anywhere — but it does mean the migration files you see here are not the ones that ship. Read them in #784.

QA

There should be nothing to test behaviourally — that's the claim this PR is making. Worth confirming on the preview deploy that the organization payment page, the plan list and the admin all render as they do on master.

🤖 Generated with Claude Code

https://claude.ai/code/session_0151rwxtvLg1vKEiz7XsJfzf

mitchelljkotler and others added 4 commits September 8, 2026 16:14
Follows the model split by moving every call site onto the right half of
the pair, and fixes the behaviour that the split had silently changed.

Adding a plan now joins the organization's existing subscription when the
billing shape matches, so an org receives one invoice instead of several.
SubscriptionItemQuerySet.start() picks the subscription by interval and
collection method, creates it only when nothing matches, and otherwise
pushes the new line onto the live Stripe subscription.

Fixes carried in with it:
- Subscription.free read item.free, which is a Plan attribute
- Subscription.cancel() called send_slack_notification, which is on the
  line; it now announces every line it cancelled
- SubscriptionItem had no cancel(), so removing one plan cancelled the
  whole subscription.  The last line cancels the subscription at period
  end; any other is dropped from Stripe with proration suppressed
- subscription_cancelled() and the invoice metadata assumed one plan per
  subscription
- the admin's plan filter used to_attr on a nested prefetch path, so
  plan_subscriptions was always None
- the Mailchimp journey and "started" Slack notification were lost in the
  split; both are back on the line, which is what names a plan
- webhook handlers looked up Stripe subscription ids on the line
- a bulk rename had mangled user-facing strings into "SubscriptionItem
  Payment", "SubscriptionItem cancelled." and similar

SubscriptionItem.organization is a read-only property reaching through
the parent, so the column stays on Subscription and a line can never
disagree with the subscription it bills on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pack entitlement tests were added on pricing-models, after the commit
that renamed SubscriptionFactory across the test suite, so their usages
were never converted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants