-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #523 from openedx/ammar/associate-opportunity-id-w…
…ith-licenses feat: add models to associate an opportunity id with a subscription license
- Loading branch information
Showing
8 changed files
with
313 additions
and
1 deletion.
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
44 changes: 44 additions & 0 deletions
44
license_manager/apps/subscriptions/migrations/0057_auto_20230915_0722.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,44 @@ | ||
# Generated by Django 3.2.21 on 2023-09-15 07:22 | ||
|
||
import django.core.validators | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('subscriptions', '0056_auto_20230530_1901'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='historicalsubscriptionplan', | ||
name='salesforce_opportunity_id', | ||
field=models.CharField(blank=True, help_text='Deprecated -- 18 character value, derived from Salesforce Opportunity record.', max_length=18, null=True, validators=[django.core.validators.MinLengthValidator(18)]), | ||
), | ||
migrations.AlterField( | ||
model_name='historicalsubscriptionplan', | ||
name='salesforce_opportunity_line_item', | ||
field=models.CharField(blank=True, help_text='18 character value -- Locate the appropriate Salesforce Opportunity Line Item record and copy it here.', max_length=18, null=True, validators=[django.core.validators.MinLengthValidator(18)]), | ||
), | ||
migrations.AlterField( | ||
model_name='historicalsubscriptionplanrenewal', | ||
name='salesforce_opportunity_id', | ||
field=models.CharField(help_text='Locate the appropriate Salesforce Opportunity record and copy the Opportunity ID field (18 characters). Note that this is not the same Salesforce Opportunity ID associated with the linked subscription.', max_length=18, validators=[django.core.validators.MinLengthValidator(18)], verbose_name='Salesforce Opportunity Line Item'), | ||
), | ||
migrations.AlterField( | ||
model_name='subscriptionplan', | ||
name='salesforce_opportunity_id', | ||
field=models.CharField(blank=True, help_text='Deprecated -- 18 character value, derived from Salesforce Opportunity record.', max_length=18, null=True, validators=[django.core.validators.MinLengthValidator(18)]), | ||
), | ||
migrations.AlterField( | ||
model_name='subscriptionplan', | ||
name='salesforce_opportunity_line_item', | ||
field=models.CharField(blank=True, help_text='18 character value -- Locate the appropriate Salesforce Opportunity Line Item record and copy it here.', max_length=18, null=True, validators=[django.core.validators.MinLengthValidator(18)]), | ||
), | ||
migrations.AlterField( | ||
model_name='subscriptionplanrenewal', | ||
name='salesforce_opportunity_id', | ||
field=models.CharField(help_text='Locate the appropriate Salesforce Opportunity record and copy the Opportunity ID field (18 characters). Note that this is not the same Salesforce Opportunity ID associated with the linked subscription.', max_length=18, validators=[django.core.validators.MinLengthValidator(18)], verbose_name='Salesforce Opportunity Line Item'), | ||
), | ||
] |
44 changes: 44 additions & 0 deletions
44
.../subscriptions/migrations/0058_subscriptionlicensesource_subscriptionlicensesourcetype.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,44 @@ | ||
# Generated by Django 3.2.21 on 2023-09-18 04:32 | ||
|
||
import django.core.validators | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
import model_utils.fields | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('subscriptions', '0057_auto_20230915_0722'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='SubscriptionLicenseSourceType', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('name', models.CharField(max_length=64)), | ||
('slug', models.SlugField(max_length=30, unique=True)), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='SubscriptionLicenseSource', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('source_id', models.CharField(help_text='18 character value -- Salesforce Opportunity ID', max_length=18, validators=[django.core.validators.MinLengthValidator(18)])), | ||
('license', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='source', to='subscriptions.license')), | ||
('source_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='subscriptions.subscriptionlicensesourcetype')), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
] |
34 changes: 34 additions & 0 deletions
34
license_manager/apps/subscriptions/migrations/0059_add_subscriptionlicensesourcetypes.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,34 @@ | ||
# Generated by Django 3.2.21 on 2023-09-18 04:32 | ||
|
||
from django.db import migrations | ||
|
||
|
||
LICENSE_SOURCE_TYPES = { | ||
'Application Management Technology': 'AMT' | ||
} | ||
|
||
|
||
def add_license_source_types(apps, schema_editor): | ||
license_source_type_model = apps.get_model('subscriptions', 'SubscriptionLicenseSourceType') | ||
for name, slug in LICENSE_SOURCE_TYPES.items(): | ||
license_source_type_model.objects.update_or_create(name=name, slug=slug) | ||
|
||
|
||
def delete_license_source_types(apps, schema_editor): | ||
license_source_type_model = apps.get_model('subscriptions', 'SubscriptionLicenseSourceType') | ||
license_source_type_model.objects.filter(name__in=LICENSE_SOURCE_TYPES).delete() | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('subscriptions', '0058_subscriptionlicensesource_subscriptionlicensesourcetype'), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython( | ||
code=add_license_source_types, | ||
reverse_code=delete_license_source_types | ||
) | ||
|
||
] |
42 changes: 42 additions & 0 deletions
42
license_manager/apps/subscriptions/migrations/0060_historicalsubscriptionlicensesource.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,42 @@ | ||
# Generated by Django 3.2.21 on 2023-09-19 06:38 | ||
|
||
from django.conf import settings | ||
import django.core.validators | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
import model_utils.fields | ||
import simple_history.models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
('subscriptions', '0059_add_subscriptionlicensesourcetypes'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='HistoricalSubscriptionLicenseSource', | ||
fields=[ | ||
('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), | ||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), | ||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), | ||
('source_id', models.CharField(help_text='18 character value -- Salesforce Opportunity ID', max_length=18, validators=[django.core.validators.MinLengthValidator(18)])), | ||
('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)), | ||
('license', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='subscriptions.license')), | ||
('source_type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='subscriptions.subscriptionlicensesourcetype')), | ||
], | ||
options={ | ||
'verbose_name': 'historical subscription license source', | ||
'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 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 |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
import ddt | ||
import freezegun | ||
import pytest | ||
from django.forms import ValidationError | ||
from django.test import TestCase | ||
from requests.exceptions import HTTPError | ||
|
@@ -16,10 +17,15 @@ | |
SegmentEvents, | ||
) | ||
from license_manager.apps.subscriptions.exceptions import CustomerAgreementError | ||
from license_manager.apps.subscriptions.models import License, Notification | ||
from license_manager.apps.subscriptions.models import ( | ||
License, | ||
Notification, | ||
SubscriptionLicenseSourceType, | ||
) | ||
from license_manager.apps.subscriptions.tests.factories import ( | ||
CustomerAgreementFactory, | ||
LicenseFactory, | ||
SubscriptionLicenseSourceFactory, | ||
SubscriptionPlanFactory, | ||
SubscriptionPlanRenewalFactory, | ||
) | ||
|
@@ -386,3 +392,60 @@ def test_net_days_until_expiration(self): | |
with freezegun.freeze_time(today): | ||
expected_days = (self.subscription_plan_b.expiration_date - today).days | ||
assert self.customer_agreement.net_days_until_expiration == expected_days | ||
|
||
|
||
@ddt.ddt | ||
class SubscriptionLicenseSourceModelTests(TestCase): | ||
""" | ||
Tests for the `SubscriptionLicenseSource` model. | ||
""" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
|
||
self.user_email = '[email protected]' | ||
self.enterprise_customer_uuid = uuid.uuid4() | ||
self.customer_agreement = CustomerAgreementFactory.create( | ||
enterprise_customer_uuid=self.enterprise_customer_uuid, | ||
) | ||
|
||
self.active_current_plan = SubscriptionPlanFactory.create( | ||
customer_agreement=self.customer_agreement, | ||
is_active=True, | ||
start_date=localized_datetime(2021, 1, 1), | ||
expiration_date=localized_datetime_from_datetime(datetime.now() + timedelta(days=365)), | ||
) | ||
|
||
self.active_current_license = LicenseFactory.create( | ||
user_email=self.user_email, | ||
subscription_plan=self.active_current_plan, | ||
) | ||
|
||
def test_license_source_creation(self): | ||
""" | ||
Tests license souce model object creation. | ||
""" | ||
license_source = SubscriptionLicenseSourceFactory( | ||
license=self.active_current_license, | ||
source_id='000000000000000000', | ||
source_type=SubscriptionLicenseSourceType.get_source_type(SubscriptionLicenseSourceType.AMT) | ||
) | ||
str_repr = 'SubscriptionLicenseSource: LicenseID: {license_uuid}, SourceID: {source_id}, SourceType: AMT' | ||
assert str(license_source) == str_repr.format( | ||
license_uuid=self.active_current_license.uuid, | ||
source_id='000000000000000000', | ||
) | ||
|
||
def test_license_source_creation_with_invalid_souce_id(self): | ||
""" | ||
Verify that SubscriptionLicenseSource model raises exception if source id format is wrong. | ||
""" | ||
with pytest.raises(ValidationError) as raised_exception: | ||
SubscriptionLicenseSourceFactory( | ||
license=self.active_current_license, | ||
source_id='000000000', | ||
source_type=SubscriptionLicenseSourceType.get_source_type(SubscriptionLicenseSourceType.AMT) | ||
) | ||
|
||
exception_message = ['Ensure this value has at least 18 characters (it has 9).'] | ||
assert raised_exception.value.args[0]['source_id'][0].messages == exception_message |