Skip to content

Commit

Permalink
feat: associate an opportunity id with a subscription license
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammad-ammar committed Sep 18, 2023
1 parent 65f6466 commit 399420e
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 1 deletion.
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'),
),
]
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,
},
),
]
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 = {
'AMT': '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
)

]
61 changes: 61 additions & 0 deletions license_manager/apps/subscriptions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,67 @@ def _clean_up_duplicate_licenses(cls, duplicate_licenses):
return sorted_licenses[0]


class SubscriptionLicenseSourceType(TimeStampedModel):
"""
Subscription License Source Type
.. no_pii: This model has no PII
"""

AMT = 'AMT'

name = models.CharField(max_length=64)
slug = models.SlugField(max_length=30, unique=True)

@classmethod
def get_source_type(cls, source_slug):
"""
Retrieve the source type based on the slug.
"""
try:
return cls.objects.get(slug=source_slug)
except SubscriptionLicenseSourceType.DoesNotExist:
return None

def __str__(self):
"""
String representation of source type.
"""
return "SubscriptionLicenseSourceType: Name: {name}, Slug: {slug}".format(name=self.name, slug=self.slug)


class SubscriptionLicenseSource(TimeStampedModel):
"""
Subscription License Source
.. no_pii: This model has no PII
"""

license = models.OneToOneField(
License,
related_name='source',
on_delete=models.CASCADE,
)
source_id = models.CharField(
max_length=SALESFORCE_ID_LENGTH,
validators=[MinLengthValidator(SALESFORCE_ID_LENGTH)],
help_text=_(
"18 character value -- Salesforce Opportunity ID"
)
)
source_type = models.ForeignKey(SubscriptionLicenseSourceType, on_delete=models.CASCADE)

def __str__(self):
"""
String representation of source.
"""
return "SubscriptionLicenseSource: LicenseID: {license}, SourceID: {source}, SourceType: {source_type}".format(
license=self.license.uuid,
source=self.source_id,
source_type=self.source_type.slug,
)


class SubscriptionsFeatureRole(UserRole):
"""
User role definitions specific to subscriptions.
Expand Down
15 changes: 15 additions & 0 deletions license_manager/apps/subscriptions/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
License,
PlanType,
Product,
SubscriptionLicenseSource,
SubscriptionLicenseSourceType,
SubscriptionPlan,
SubscriptionPlanRenewal,
)
Expand Down Expand Up @@ -160,3 +162,16 @@ class UserFactory(factory.django.DjangoModelFactory):

class Meta:
model = User


class SubscriptionLicenseSourceFactory(factory.django.DjangoModelFactory):
"""
Test factory for the `SubscriptionLicenseSource` model.
"""

license = factory.SubFactory(LicenseFactory)
source_id = factory.LazyFunction(get_random_salesforce_id)
source_type = factory.Iterator(SubscriptionLicenseSourceType.objects.all())

class Meta:
model = SubscriptionLicenseSource
50 changes: 49 additions & 1 deletion license_manager/apps/subscriptions/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,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,
)
Expand Down Expand Up @@ -386,3 +391,46 @@ 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',
)

0 comments on commit 399420e

Please sign in to comment.