Skip to content

Step 2f — archive the legacy plans instead of deleting them - #806

Draft
mitchelljkotler wants to merge 2 commits into
pricing-models-entitlement-shapefrom
pricing-models-plan-archive
Draft

Step 2f — archive the legacy plans instead of deleting them#806
mitchelljkotler wants to merge 2 commits into
pricing-models-entitlement-shapefrom
pricing-models-plan-archive

Conversation

@mitchelljkotler

Copy link
Copy Markdown
Member

Stacks on #805.

A plan is finished once no live subscription sits on it and it has no active price: everyone has been moved off, and it cannot be bought because there is nothing to bill.

Why archive rather than delete

The step was drafted as "delete the now-empty legacy row", and the database will not allow it:

  • OrganizationChangeLog holds four PROTECT foreign keys to Plan and writes an entry on every subscription change. Any plan anyone ever subscribed to is referenced by history — deleting the row destroys the record of who used to be on what.
  • SubscriptionItem.plan is CASCADE. A plan still carrying cancelled lines would take them with it.

Also worth knowing: the plan inventory's "no subscribers" column is live subscribers, so rows that look never-used may well have cancelled lines and history behind them. Archiving sidesteps that question entirely, and is reversible where a delete is not.

What to look at

  • Cancelled lines don't block archiving. They're the record, and they keep pointing at the archived plan. Only a live line or an active price blocks it.
  • Archived plans drop out of Plan.objects.choices() — including for an organization currently on one. That clause matches the organization's own subscriptions and would otherwise offer the plan back to exactly the people being moved off it.
  • Adds Plan.archived (0086_plan_archived), default False.

QA

archive_legacy_plans --dry-run first. Plans reported "still in use" are not a failure — they mean 2d hasn't finished moving everyone off them.

Then check the plan list on the purchase page is unchanged, and that an org on an archived plan isn't offered it at renewal.

🤖 Generated with Claude Code

https://claude.ai/code/session_0151rwxtvLg1vKEiz7XsJfzf

@allanlasser
allanlasser temporarily deployed to squarelet-pi-pricing-mo-7gefih September 4, 2026 18:28 Inactive
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from 58a7876 to 11c49a8 Compare September 5, 2026 20:02
@mitchelljkotler
mitchelljkotler temporarily deployed to squarelet-pi-pricing-mo-7gefih September 5, 2026 20:02 Inactive
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from 11c49a8 to c8b87a9 Compare September 8, 2026 20:07
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from c8b87a9 to 23a8a85 Compare September 8, 2026 20:19
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from 23a8a85 to e0e7035 Compare September 8, 2026 20:43
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch 2 times, most recently from 2dab650 to 6cb8f5c Compare September 9, 2026 16:31
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from 6cb8f5c to ec1bce6 Compare September 9, 2026 17:45
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from ec1bce6 to 649ce3f Compare September 9, 2026 18:24
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch 2 times, most recently from 2b0ed4b to e98874f Compare September 9, 2026 19:25
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from e98874f to cc9e588 Compare September 9, 2026 20:34
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from cc9e588 to 4409699 Compare September 9, 2026 22:09
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from 4409699 to 111c888 Compare September 10, 2026 02:19
mitchelljkotler and others added 2 commits September 9, 2026 22:30
Step 2f.  A plan is finished once no live subscription sits on it and it
has no active price: everyone has been moved off, and it cannot be bought
because there is nothing to bill.

The step was drafted as "delete the now-empty legacy row", and the
database will not allow it.  OrganizationChangeLog holds four PROTECT
foreign keys to Plan and writes an entry on every subscription change, so
any plan anyone ever subscribed to is referenced by history -- deleting
the row would mean destroying the record of who used to be on what.
SubscriptionItem.plan is CASCADE on top of that, so a plan still carrying
cancelled lines would take them with it.

Mitch's call, and the right one: a flag instead.  Nothing here is worth
losing for a shorter list, and archiving is reversible where a delete is
not.  It also removes the "no subscribers" trap -- the inventory column is
*live* subscribers, so plans I had described as never-used may well have
cancelled lines and history behind them.

Cancelled lines therefore do not block archiving; they are the record, and
they keep pointing at the archived plan quite happily.  Only a live line
or an active price does.

Archived plans drop out of Plan.objects.choices(), including for an
organization currently on one -- that clause matches the organization's
own subscriptions, and would otherwise offer the plan back to exactly the
people being moved off it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A seeded database has legacy plans of its own, so the run archives sixteen
of them rather than the one the test made -- and the canonical-plan case
was asserting against `<slug>-2`, because PlanFactory get-or-creates on
name while slug is an AutoSlugField.

Adopt the seeded row rather than making a second one, and assert on the
plan in hand rather than on a global count.
@mitchelljkotler
mitchelljkotler force-pushed the pricing-models-plan-archive branch from 111c888 to 2b27200 Compare September 10, 2026 02:42
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