Skip to content

Commit d0cfb0c

Browse files
authored
Merge pull request #881 from openedx/pwnage101/ENT-11009-1
feat: allow provisioning of multiple subs plans with same opp line item
2 parents ffcf036 + 6e991ff commit d0cfb0c

File tree

6 files changed

+76
-14
lines changed

6 files changed

+76
-14
lines changed

enterprise_access/apps/api/serializers/provisioning.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ class SubscriptionPlanResponseSerializer(BaseSerializer):
188188
is_current = serializers.BooleanField()
189189
plan_type = serializers.CharField()
190190
enterprise_catalog_uuid = serializers.UUIDField()
191+
product = serializers.IntegerField()
191192

192193

193194
class CustomerAgreementResponseSerializer(BaseSerializer):

enterprise_access/apps/api/v1/tests/test_provisioning_views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"is_current": False,
6868
"plan_type": "Standard Paid",
6969
"enterprise_catalog_uuid": str(TEST_CATALOG_UUID),
70+
"product": 1,
7071
}
7172

7273
DEFAULT_AGREEMENT_RECORD = {

enterprise_access/apps/provisioning/api.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,23 @@ def get_or_create_customer_agreement(enterprise_customer_uuid, customer_slug, de
156156

157157

158158
def get_or_create_subscription_plan(
159-
customer_agreement_uuid, existing_subscription_list, plan_title, catalog_uuid, opp_line_item,
160-
start_date, expiration_date, desired_num_licenses, product_id, **kwargs
159+
customer_agreement_uuid: str,
160+
existing_subscription_list: list[dict],
161+
plan_title: str,
162+
catalog_uuid: str | None,
163+
opp_line_item: str,
164+
start_date: str,
165+
expiration_date: str,
166+
desired_num_licenses: int,
167+
product_id: int | None,
168+
**kwargs
161169
):
162170
"""
163171
Get or create a new subscription plan, provided an existing customer agreement dictionary.
164172
"""
165173
matching_subscription = next((
166174
_sub for _sub in existing_subscription_list
167-
if _sub.get('salesforce_opportunity_line_item') == opp_line_item
175+
if _sub.get('salesforce_opportunity_line_item') == opp_line_item and _sub.get('product') == product_id
168176
), None)
169177
if matching_subscription:
170178
logger.info(
@@ -186,7 +194,12 @@ def get_or_create_subscription_plan(
186194
**kwargs,
187195
)
188196
logger.info(
189-
'Provisioning: created new subscription plan with uuid %s and salesforce_opportunity_line_item %s',
190-
created_subscription['uuid'], created_subscription['salesforce_opportunity_line_item'],
197+
(
198+
'Provisioning: created new subscription plan with '
199+
'uuid %s and salesforce_opportunity_line_item %s and product_id %s'
200+
),
201+
created_subscription['uuid'],
202+
created_subscription['salesforce_opportunity_line_item'],
203+
created_subscription['product'],
191204
)
192205
return created_subscription

enterprise_access/apps/provisioning/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ class GetCreateSubscriptionPlanStepOutput(BaseInputOutput):
391391
is_current: bool = field(validator=is_bool)
392392
plan_type: str = field(validator=is_str)
393393
enterprise_catalog_uuid: UUID = field(validator=is_uuid)
394+
product: int = field(validator=is_int)
394395

395396

396397
class GetCreateSubscriptionPlanStep(AbstractWorkflowStep):

enterprise_access/apps/provisioning/tests/factories.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def create_complete_workflow(cls, **kwargs):
8888
'is_current': True,
8989
'plan_type': 'subscription',
9090
'enterprise_catalog_uuid': str(uuid.uuid4()),
91+
'product': 1,
9192
},
9293
}
9394

enterprise_access/apps/provisioning/tests/test_api.py

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"""
44
from unittest import mock
55

6+
import ddt
67
import requests
8+
from django.conf import settings
79
from django.test import TestCase
810
from rest_framework import status
911

@@ -382,6 +384,7 @@ def test_create_new_customer_agreement(self, mock_license_manager_client):
382384
)
383385

384386

387+
@ddt.ddt
385388
class TestGetOrCreateSubscriptionPlan(TestCase):
386389
"""
387390
Tests for the get_or_create_subscription_plan() function.
@@ -393,57 +396,99 @@ def test_get_existing_subscription_plan(self, mock_license_manager_client):
393396
'uuid': 'sub-uuid',
394397
'title': 'Test Plan',
395398
'salesforce_opportunity_line_item': 'opp-line-item',
399+
'product': 1,
396400
},
397401
]
398402

399403
result = provisioning_api.get_or_create_subscription_plan(
400-
customer_agreement_uuid=None,
404+
customer_agreement_uuid='customer-agreement-uuid',
401405
existing_subscription_list=existing_subscriptions,
402406
plan_title='Test Plan',
403407
catalog_uuid='catalog-uuid',
404408
opp_line_item='opp-line-item',
405409
start_date='2025-05-01',
406410
expiration_date='2026-05-01',
407411
desired_num_licenses=100,
408-
product_id=None,
412+
product_id=1,
409413
)
410414

411415
self.assertEqual(result, existing_subscriptions[0])
412416
mock_license_manager_client.assert_not_called()
413417

418+
@ddt.data(
419+
# Same prodcut_id, different opp_line_item.
420+
{
421+
'existing_opp_line_item': 'opp-line-item-1',
422+
'existing_product_id': 1,
423+
'requesting_opp_line_item': 'opp-line-item-2',
424+
'requesting_product_id': 1,
425+
},
426+
# Same opp_line_item, different prodcut_id.
427+
{
428+
'existing_opp_line_item': 'opp-line-item-1',
429+
'existing_product_id': 1,
430+
'requesting_opp_line_item': 'opp-line-item-1',
431+
'requesting_product_id': 2,
432+
},
433+
# Make sure requesting product_id=None falls back to a default which does not conflict.
434+
{
435+
'existing_opp_line_item': 'opp-line-item-1',
436+
'existing_product_id': settings.PROVISIONING_DEFAULTS['subscription']['product_id'] + 1,
437+
'requesting_opp_line_item': 'opp-line-item-1',
438+
'requesting_product_id': None, # fallback to settings.PROVISIONING_DEFAULTS['subscription']['product_id']
439+
},
440+
)
441+
@ddt.unpack
414442
@mock.patch.object(provisioning_api, 'LicenseManagerApiClient', autospec=True)
415-
def test_create_new_subscription_plan(self, mock_license_manager_client):
443+
def test_create_new_subscription_plan(
444+
self,
445+
mock_license_manager_client,
446+
existing_opp_line_item,
447+
existing_product_id,
448+
requesting_opp_line_item,
449+
requesting_product_id,
450+
):
451+
existing_subscriptions = [
452+
{
453+
'uuid': 'sub-uuid',
454+
'title': 'Test Plan',
455+
'salesforce_opportunity_line_item': existing_opp_line_item,
456+
'product': existing_product_id,
457+
},
458+
]
416459
created_subscription = {
417460
'uuid': 'new-sub-uuid',
418-
'salesforce_opportunity_line_item': 'opp-line-item',
461+
'salesforce_opportunity_line_item': requesting_opp_line_item,
419462
'title': 'New Plan',
463+
# Simulate the fallback logic within LicenseManagerApiClient.create_subscription_plan().
464+
'product': requesting_product_id or settings.PROVISIONING_DEFAULTS['subscription']['product_id'],
420465
}
421466
mock_client = mock_license_manager_client.return_value
422467
mock_client.create_subscription_plan.return_value = created_subscription
423468

424469
result = provisioning_api.get_or_create_subscription_plan(
425470
customer_agreement_uuid='agreement-uuid',
426-
existing_subscription_list=[],
471+
existing_subscription_list=existing_subscriptions,
427472
plan_title='New Plan',
428473
catalog_uuid='catalog-uuid',
429-
opp_line_item='opp-line-item',
474+
opp_line_item=requesting_opp_line_item,
430475
start_date='2025-05-01',
431476
expiration_date='2026-05-01',
432477
desired_num_licenses=50,
433478
extra_field='extra-value',
434-
product_id='the-product',
479+
product_id=requesting_product_id,
435480
)
436481

437482
self.assertEqual(result, created_subscription)
438483
mock_client.create_subscription_plan.assert_called_once_with(
439484
customer_agreement_uuid='agreement-uuid',
440485
enterprise_catalog_uuid='catalog-uuid',
441486
title='New Plan',
442-
salesforce_opportunity_line_item='opp-line-item',
487+
salesforce_opportunity_line_item=requesting_opp_line_item,
443488
start_date='2025-05-01',
444489
expiration_date='2026-05-01',
445490
desired_num_licenses=50,
446-
product_id='the-product',
491+
product_id=requesting_product_id,
447492
extra_field='extra-value'
448493
)
449494

0 commit comments

Comments
 (0)