diff --git a/ecommerce/extensions/api/serializers.py b/ecommerce/extensions/api/serializers.py index d45dd8d503f..da118422d2c 100644 --- a/ecommerce/extensions/api/serializers.py +++ b/ecommerce/extensions/api/serializers.py @@ -1137,7 +1137,7 @@ class Meta: def _serialize_remaining_balance_value(conditional_offer): """ - Change value into string and return it unless it is None. + Calculate and return remaining balance on the offer. """ remaining_balance = calculate_remaining_offer_balance(conditional_offer) if remaining_balance is not None: @@ -1145,38 +1145,63 @@ def _serialize_remaining_balance_value(conditional_offer): return remaining_balance -class EnterpriseLearnerOfferApiSerializer(serializers.BaseSerializer): # pylint: disable=abstract-method +def _serialize_remaining_balance_for_user(conditional_offer, request): """ - Serializer for EnterpriseOffer learner endpoint. + Determines the remaining balance for the user. + """ + if request and conditional_offer.max_user_discount is not None: + return str(conditional_offer.max_user_discount - sum_user_discounts_for_offer(request.user, conditional_offer)) + return None - Uses serializers.BaseSerializer to keep this lightweight. + +def _serialize_remaining_applications_value(conditional_offer): """ + Calculate and return remaining number of applications on the offer. + """ + if conditional_offer.max_global_applications is not None: + return conditional_offer.max_global_applications - conditional_offer.num_applications + return None - def _serialize_remaining_balance_for_user(self, instance): - request = self.context.get('request') - if request and instance.max_user_discount is not None: - return str(instance.max_user_discount - sum_user_discounts_for_offer(request.user, instance)) +def _serialize_remaining_applications_for_user(conditional_offer, request): + """ + Determines the remaining number of applications (enrollments) for the user. + """ + if request and conditional_offer.max_user_applications is not None: + return conditional_offer.max_user_applications - conditional_offer.get_num_user_applications(request.user) + return None - return None + +class EnterpriseLearnerOfferApiSerializer(serializers.BaseSerializer): # pylint: disable=abstract-method + """ + Serializer for EnterpriseOffer learner endpoint. + + Uses serializers.BaseSerializer to keep this lightweight. + """ def to_representation(self, instance): representation = OrderedDict() representation['id'] = instance.id - representation['max_discount'] = instance.max_discount + representation['enterprise_customer_uuid'] = instance.condition.enterprise_customer_uuid + representation['enterprise_catalog_uuid'] = instance.condition.enterprise_customer_catalog_uuid + representation['is_current'] = instance.is_current + representation['status'] = instance.status representation['start_datetime'] = instance.start_datetime representation['end_datetime'] = instance.end_datetime - representation['enterprise_catalog_uuid'] = instance.condition.enterprise_customer_catalog_uuid representation['usage_type'] = get_benefit_type(instance.benefit) representation['discount_value'] = instance.benefit.value - representation['status'] = instance.status - representation['remaining_balance'] = _serialize_remaining_balance_value(instance) - representation['is_current'] = instance.is_current + representation['max_discount'] = instance.max_discount representation['max_global_applications'] = instance.max_global_applications + representation['max_user_applications'] = instance.max_user_applications representation['max_user_discount'] = instance.max_user_discount representation['num_applications'] = instance.num_applications - representation['remaining_balance_for_user'] = self._serialize_remaining_balance_for_user(instance) + representation['remaining_balance'] = _serialize_remaining_balance_value(instance) + representation['remaining_applications'] = _serialize_remaining_applications_value(instance) + representation['remaining_balance_for_user'] = \ + _serialize_remaining_balance_for_user(instance, request=self.context.get('request')) + representation['remaining_applications_for_user'] = \ + _serialize_remaining_applications_for_user(instance, request=self.context.get('request')) return representation @@ -1199,6 +1224,7 @@ def to_representation(self, instance): representation['enterprise_catalog_uuid'] = instance.condition.enterprise_customer_catalog_uuid representation['display_name'] = generate_offer_display_name(instance) representation['remaining_balance'] = _serialize_remaining_balance_value(instance) + representation['remaining_applications'] = _serialize_remaining_applications_value(instance) representation['is_current'] = instance.is_current return representation diff --git a/ecommerce/extensions/api/v2/tests/test_serializers.py b/ecommerce/extensions/api/v2/tests/test_serializers.py index fb3547e5d33..448cc6aebd6 100644 --- a/ecommerce/extensions/api/v2/tests/test_serializers.py +++ b/ecommerce/extensions/api/v2/tests/test_serializers.py @@ -17,18 +17,19 @@ class EnterpriseLearnerOfferApiSerializerTests(TestCase): @ddt.data( - (100, '74.5'), - (None, None) + (100, 25.5, '74.5'), + (None, None, None) ) @ddt.unpack @mock.patch('ecommerce.extensions.api.serializers.sum_user_discounts_for_offer') def test_serialize_remaining_balance_for_user( self, max_user_discount, - expected_remaining_balance, + existing_user_spend, + expected_remaining_balance_for_user, mock_sum_user_discounts_for_offer ): - mock_sum_user_discounts_for_offer.return_value = 25.5 + mock_sum_user_discounts_for_offer.return_value = existing_user_spend enterprise_customer_uuid = str(uuid4()) condition = extended_factories.EnterpriseCustomerConditionFactory( enterprise_customer_uuid=enterprise_customer_uuid @@ -45,4 +46,90 @@ def test_serialize_remaining_balance_for_user( } ) data = serializer.to_representation(enterprise_offer) - assert data['remaining_balance_for_user'] == expected_remaining_balance + assert data['remaining_balance_for_user'] == expected_remaining_balance_for_user + + @ddt.data( + (5250, '5250'), + (None, None) + ) + @ddt.unpack + @mock.patch('ecommerce.extensions.api.serializers.calculate_remaining_offer_balance') + def test_serialize_remaining_balance( + self, + max_discount, + expected_remaining_balance, + mock_calculate_remaining_offer_balance + ): + mock_calculate_remaining_offer_balance.return_value = max_discount + enterprise_customer_uuid = str(uuid4()) + condition = extended_factories.EnterpriseCustomerConditionFactory( + enterprise_customer_uuid=enterprise_customer_uuid + ) + enterprise_offer = extended_factories.EnterpriseOfferFactory.create( + partner=self.partner, + condition=condition, + ) + serializer = EnterpriseLearnerOfferApiSerializer( + data=enterprise_offer, + context={ + 'request': mock.MagicMock(user=UserFactory()) + } + ) + data = serializer.to_representation(enterprise_offer) + assert data['remaining_balance'] == expected_remaining_balance + + @ddt.data( + (1000, 1000), + (None, None) + ) + @ddt.unpack + def test_serialize_remaining_applications_for_user( + self, + max_user_applications, + expected_remaining_applications_for_user, + ): + enterprise_customer_uuid = str(uuid4()) + condition = extended_factories.EnterpriseCustomerConditionFactory( + enterprise_customer_uuid=enterprise_customer_uuid + ) + enterprise_offer = extended_factories.EnterpriseOfferFactory.create( + partner=self.partner, + condition=condition, + max_user_applications=max_user_applications, + ) + serializer = EnterpriseLearnerOfferApiSerializer( + data=enterprise_offer, + context={ + 'request': mock.MagicMock(user=UserFactory()) + } + ) + data = serializer.to_representation(enterprise_offer) + assert data['remaining_applications_for_user'] == expected_remaining_applications_for_user + + @ddt.data( + (2, 2), + (None, None) + ) + @ddt.unpack + def test_serialize_remaining_applications( + self, + max_global_applications, + expected_remaining_applications, + ): + enterprise_customer_uuid = str(uuid4()) + condition = extended_factories.EnterpriseCustomerConditionFactory( + enterprise_customer_uuid=enterprise_customer_uuid + ) + enterprise_offer = extended_factories.EnterpriseOfferFactory.create( + partner=self.partner, + condition=condition, + max_global_applications=max_global_applications, + ) + serializer = EnterpriseLearnerOfferApiSerializer( + data=enterprise_offer, + context={ + 'request': mock.MagicMock(user=UserFactory()) + } + ) + data = serializer.to_representation(enterprise_offer) + assert data['remaining_applications'] == expected_remaining_applications