-
Notifications
You must be signed in to change notification settings - Fork 429
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Organisation reverts to free plan (#3096)
- Loading branch information
Showing
9 changed files
with
220 additions
and
50 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
48 changes: 48 additions & 0 deletions
48
api/organisations/migrations/0050_add_historical_subscription.py
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Generated by Django 3.2.23 on 2023-12-06 17:10 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import simple_history.models | ||
import uuid | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
('organisations', '0049_subscription_billing_status'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='HistoricalSubscription', | ||
fields=[ | ||
('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), | ||
('deleted_at', models.DateTimeField(blank=True, db_index=True, default=None, editable=False, null=True)), | ||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)), | ||
('subscription_id', models.CharField(blank=True, max_length=100, null=True)), | ||
('subscription_date', models.DateTimeField(blank=True, null=True)), | ||
('plan', models.CharField(blank=True, default='free', max_length=100, null=True)), | ||
('max_seats', models.IntegerField(default=1)), | ||
('max_api_calls', models.BigIntegerField(default=50000)), | ||
('cancellation_date', models.DateTimeField(blank=True, null=True)), | ||
('customer_id', models.CharField(blank=True, max_length=100, null=True)), | ||
('billing_status', models.CharField(blank=True, choices=[('ACTIVE', 'Active'), ('DUNNING', 'Dunning')], max_length=20, null=True)), | ||
('payment_method', models.CharField(blank=True, choices=[('CHARGEBEE', 'Chargebee'), ('XERO', 'Xero'), ('AWS_MARKETPLACE', 'AWS Marketplace')], max_length=20, null=True)), | ||
('notes', models.CharField(blank=True, max_length=500, null=True)), | ||
('history_id', models.AutoField(primary_key=True, serialize=False)), | ||
('history_date', models.DateTimeField()), | ||
('history_change_reason', models.CharField(max_length=100, null=True)), | ||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), | ||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), | ||
('organisation', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='organisations.organisation')), | ||
], | ||
options={ | ||
'verbose_name': 'historical subscription', | ||
'ordering': ('-history_date', '-history_id'), | ||
'get_latest_by': 'history_date', | ||
}, | ||
bases=(simple_history.models.HistoricalChanges, models.Model), | ||
), | ||
] |
This file contains 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
This file was deleted.
Oops, something went wrong.
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,15 +3,18 @@ | |
|
||
import pytest | ||
from django.utils import timezone | ||
from pytest_mock import MockerFixture | ||
|
||
from organisations.chargebee.metadata import ChargebeeObjMetadata | ||
from organisations.models import ( | ||
Organisation, | ||
OrganisationRole, | ||
OrganisationSubscriptionInformationCache, | ||
UserOrganisation, | ||
) | ||
from organisations.subscriptions.constants import ( | ||
FREE_PLAN_ID, | ||
MAX_API_CALLS_IN_FREE_PLAN, | ||
MAX_SEATS_IN_FREE_PLAN, | ||
) | ||
from organisations.subscriptions.xero.metadata import XeroSubscriptionMetadata | ||
|
@@ -20,6 +23,7 @@ | |
ALERT_EMAIL_SUBJECT, | ||
finish_subscription_cancellation, | ||
send_org_over_limit_alert, | ||
send_org_subscription_cancelled_alert, | ||
) | ||
from users.models import FFAdminUser | ||
|
||
|
@@ -76,8 +80,62 @@ def test_send_org_over_limit_alert_for_organisation_with_subscription( | |
assert kwargs["subject"] == ALERT_EMAIL_SUBJECT | ||
|
||
|
||
def test_finish_subscription_cancellation(db: None): | ||
organisation1 = Organisation.objects.create() | ||
def test_subscription_cancellation(db: None) -> None: | ||
# Given | ||
organisation = Organisation.objects.create() | ||
OrganisationSubscriptionInformationCache.objects.create( | ||
organisation=organisation, | ||
) | ||
UserOrganisation.objects.create( | ||
organisation=organisation, | ||
user=FFAdminUser.objects.create(email=f"{uuid.uuid4()}@example.com"), | ||
role=OrganisationRole.ADMIN, | ||
) | ||
|
||
assert organisation.subscription_information_cache | ||
subscription = organisation.subscription | ||
notes = "Notes to be kept" | ||
subscription.subscription_id = "id" | ||
subscription.subscription_date = timezone.now() | ||
subscription.plan = "plan_code" | ||
subscription.max_seats = 1_000_000 | ||
subscription.max_api_calls = 1_000_000 | ||
subscription.cancellation_date = timezone.now() | ||
subscription.customer_id = "customer23" | ||
subscription.billing_status = "ACTIVE" | ||
subscription.payment_method = "CHARGEBEE" | ||
subscription.notes = notes | ||
|
||
subscription.save() | ||
|
||
# When | ||
finish_subscription_cancellation() | ||
|
||
# Then | ||
organisation.refresh_from_db() | ||
subscription.refresh_from_db() | ||
assert getattr(organisation, "subscription_information_cache", None) is None | ||
assert subscription.subscription_id is None | ||
assert subscription.subscription_date is None | ||
assert subscription.plan == FREE_PLAN_ID | ||
assert subscription.max_seats == MAX_SEATS_IN_FREE_PLAN | ||
assert subscription.max_api_calls == MAX_API_CALLS_IN_FREE_PLAN | ||
assert subscription.cancellation_date is None | ||
assert subscription.customer_id is None | ||
assert subscription.billing_status is None | ||
assert subscription.payment_method is None | ||
assert subscription.cancellation_date is None | ||
assert subscription.notes == notes | ||
|
||
|
||
@pytest.mark.freeze_time("2023-01-19T09:12:34+00:00") | ||
def test_finish_subscription_cancellation(db: None, mocker: MockerFixture) -> None: | ||
# Given | ||
send_org_subscription_cancelled_alert_task = mocker.patch( | ||
"organisations.tasks.send_org_subscription_cancelled_alert" | ||
) | ||
|
||
organisation1 = Organisation.objects.create(name="TestCorp") | ||
organisation2 = Organisation.objects.create() | ||
organisation3 = Organisation.objects.create() | ||
organisation4 = Organisation.objects.create() | ||
|
@@ -91,7 +149,15 @@ def test_finish_subscription_cancellation(db: None): | |
role=OrganisationRole.ADMIN, | ||
) | ||
future = timezone.now() + timedelta(days=20) | ||
organisation1.subscription.cancel(cancellation_date=future) | ||
organisation1.subscription.prepare_for_cancel(cancellation_date=future) | ||
|
||
# Test one of the send org alerts. | ||
send_org_subscription_cancelled_alert_task.delay.assert_called_once_with( | ||
kwargs={ | ||
"organisation_name": organisation1.name, | ||
"formatted_cancellation_date": "2023-02-08 09:12:34", | ||
} | ||
) | ||
|
||
# Two organisations are impacted. | ||
for __ in range(organisation_user_count): | ||
|
@@ -101,7 +167,7 @@ def test_finish_subscription_cancellation(db: None): | |
role=OrganisationRole.ADMIN, | ||
) | ||
|
||
organisation2.subscription.cancel( | ||
organisation2.subscription.prepare_for_cancel( | ||
cancellation_date=timezone.now() - timedelta(hours=2) | ||
) | ||
|
||
|
@@ -111,7 +177,7 @@ def test_finish_subscription_cancellation(db: None): | |
user=FFAdminUser.objects.create(email=f"{uuid.uuid4()}@example.com"), | ||
role=OrganisationRole.ADMIN, | ||
) | ||
organisation3.subscription.cancel( | ||
organisation3.subscription.prepare_for_cancel( | ||
cancellation_date=timezone.now() - timedelta(hours=4) | ||
) | ||
|
||
|
@@ -136,3 +202,23 @@ def test_finish_subscription_cancellation(db: None): | |
assert organisation2.num_seats == 1 | ||
assert organisation3.num_seats == 1 | ||
assert organisation4.num_seats == organisation_user_count | ||
|
||
|
||
def test_send_org_subscription_cancelled_alert(db: None, mocker: MockerFixture) -> None: | ||
# Given | ||
send_mail_mock = mocker.patch("users.models.send_mail") | ||
|
||
# When | ||
send_org_subscription_cancelled_alert( | ||
organisation_name="TestCorp", | ||
formatted_cancellation_date="2023-02-08 09:12:34", | ||
) | ||
|
||
# Then | ||
send_mail_mock.assert_called_once_with( | ||
subject="Organisation TestCorp has cancelled their subscription", | ||
message="Organisation TestCorp has cancelled their subscription on 2023-02-08 09:12:34", | ||
from_email="[email protected]", | ||
recipient_list=[], | ||
fail_silently=True, | ||
) |
Oops, something went wrong.
e5efdc8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
docs – ./docs
docs.bullet-train.io
docs.flagsmith.com
docs-git-main-flagsmith.vercel.app
docs-flagsmith.vercel.app
e5efdc8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
flagsmith-frontend-preview – ./frontend
flagsmith-frontend-production-native.vercel.app
flagsmith-frontend-preview-git-main-flagsmith.vercel.app
flagsmith-frontend-preview-flagsmith.vercel.app
e5efdc8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
flagsmith-frontend-staging – ./frontend
flagsmith-frontend-staging-flagsmith.vercel.app
staging.flagsmith.com
flagsmith-frontend-staging-git-main-flagsmith.vercel.app
flagsmith-staging-frontend.vercel.app