From a83b869a2bb70d4a0cd3157813acb6063b6c5146 Mon Sep 17 00:00:00 2001 From: Evandro Myller Date: Fri, 10 Apr 2026 18:26:47 -0300 Subject: [PATCH 1/2] Remove unused Chargebee plan ID constants Co-Authored-By: Claude Opus 4.6 (1M context) --- api/organisations/subscriptions/constants.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/organisations/subscriptions/constants.py b/api/organisations/subscriptions/constants.py index 2bb858a3fd0f..cd9914f1e4be 100644 --- a/api/organisations/subscriptions/constants.py +++ b/api/organisations/subscriptions/constants.py @@ -41,12 +41,7 @@ FREE_PLAN_ID = "free" TRIAL_SUBSCRIPTION_ID = "trial" SCALE_UP = "scale-up" -SCALE_UP_12_MONTHS_V2 = "scale-up-12-months-v2" -SCALE_UP_QUARTERLY_V2_SEMIANNUAL = "scale-up-quarterly-v2-semiannual" -SCALE_UP_V2 = "scale-up-v2" STARTUP = "startup" -STARTUP_ANNUAL_V2 = "startup-annual-v2" -STARTUP_V2 = "startup-v2" ENTERPRISE = "enterprise" From ba488b08e38e414c1bfda25bcb4902e209e13ec7 Mon Sep 17 00:00:00 2001 From: Evandro Myller Date: Fri, 10 Apr 2026 19:50:44 -0300 Subject: [PATCH 2/2] Cap scale-up seats at 20 --- api/organisations/models.py | 2 + api/organisations/subscriptions/constants.py | 2 + .../test_unit_organisations_models.py | 55 +++++++++++++++---- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/api/organisations/models.py b/api/organisations/models.py index bc24160e7a1c..9ee71ac1b162 100644 --- a/api/organisations/models.py +++ b/api/organisations/models.py @@ -42,6 +42,7 @@ FREE_PLAN_SUBSCRIPTION_METADATA, MAX_API_CALLS_IN_FREE_PLAN, MAX_SEATS_IN_FREE_PLAN, + MAX_SEATS_IN_SCALE_UP_PLAN, SUBSCRIPTION_BILLING_STATUSES, SUBSCRIPTION_PAYMENT_METHODS, TRIAL_SUBSCRIPTION_ID, @@ -272,6 +273,7 @@ def can_auto_upgrade_seats(self) -> bool: return ( is_saas() and self.subscription_plan_family == SubscriptionPlanFamily.SCALE_UP + and self.organisation.num_seats < MAX_SEATS_IN_SCALE_UP_PLAN ) @property diff --git a/api/organisations/subscriptions/constants.py b/api/organisations/subscriptions/constants.py index cd9914f1e4be..b0f433ce11e0 100644 --- a/api/organisations/subscriptions/constants.py +++ b/api/organisations/subscriptions/constants.py @@ -12,6 +12,8 @@ settings.MAX_PROJECTS_IN_FREE_PLAN, ) +MAX_SEATS_IN_SCALE_UP_PLAN = 20 + CHARGEBEE = "CHARGEBEE" XERO = "XERO" AWS_MARKETPLACE = "AWS_MARKETPLACE" diff --git a/api/tests/unit/organisations/test_unit_organisations_models.py b/api/tests/unit/organisations/test_unit_organisations_models.py index 55ada3104971..a97b860c55a0 100644 --- a/api/tests/unit/organisations/test_unit_organisations_models.py +++ b/api/tests/unit/organisations/test_unit_organisations_models.py @@ -209,22 +209,53 @@ def test_over_plan_seats_limit__no_subscription_metadata__returns_true( # type: @pytest.mark.saas_mode -def test_is_auto_seat_upgrade_available__scale_up_plan__returns_true( +@pytest.mark.parametrize( + "plan, num_seats, expected", + [ + ("scale-up-v2", 19, True), + ("scale-up-v2", 20, False), + ("scale-up-v2", 21, False), + ("startup-v2", 1, False), + ], +) +def test_is_auto_seat_upgrade_available__given_plan_and_seat_count__returns_expected( organisation: Organisation, + plan: str, + num_seats: int, + expected: bool, ) -> None: # Given - plan = "Scale-Up" - subscription_id = "subscription-id" + subscription = organisation.subscription + subscription.plan = plan + subscription.subscription_id = "subscription-id" + subscription.save() - Subscription.objects.filter(organisation=organisation).update( - subscription_id=subscription_id, plan=plan - ) + organisation.users.all().delete() + for i in range(num_seats): + user = FFAdminUser.objects.create(email=f"seat-{i}@test.com") + user.add_organisation(organisation) # When - organisation.refresh_from_db() + result = organisation.is_auto_seat_upgrade_available() + + # Then + assert result is expected + + +def test_is_auto_seat_upgrade_available__not_saas__returns_false( + organisation: Organisation, +) -> None: + # Given + subscription = organisation.subscription + subscription.plan = "scale-up-v2" + subscription.subscription_id = "subscription-id" + subscription.save() + + # When + result = organisation.is_auto_seat_upgrade_available() # Then - assert organisation.is_auto_seat_upgrade_available() is True + assert result is False def test_subscription__default_for_new_organisation__has_one_max_seat( @@ -458,16 +489,16 @@ def test_get_subscription_metadata__self_hosted_open_source__returns_free_plan_m @pytest.mark.saas_mode -def test_add_single_seat__upgradable_plan__calls_chargebee_add_seat( +def test_add_single_seat__upgradable_plan__calls_chargebee_add_single_seat( + organisation: Organisation, mocker: MockerFixture, ) -> None: # Given subscription_id = "subscription-id" subscription = Subscription(subscription_id=subscription_id, plan="scale-up") + mocker.patch.object(Subscription, "can_auto_upgrade_seats", new=True) + mocked_add_single_seat = mocker.patch("organisations.models.add_single_seat") - mocked_add_single_seat = mocker.patch( - "organisations.models.add_single_seat", autospec=True - ) # When subscription.add_single_seat() # type: ignore[no-untyped-call]