def update_voucher_offer(offer, benefit_value, benefit_type=None, max_uses=None, email_domains=None, program_uuid=None, site=None): """ Update voucher offer with new benefit value. Args: offer (Offer): Offer associated with a voucher. benefit_value (Decimal): Value of benefit associated with vouchers. benefit_type (str): Type of benefit associated with vouchers. Kwargs: max_uses (int): number of maximum global application number an offer can have. email_domains (str): a comma-separated string of email domains allowed to apply this offer. program_uuid (str): Program UUID site (Site): Site for this offer Returns: Offer """ return _get_or_create_offer( product_range=offer.benefit.range, benefit_value=benefit_value or offer.benefit.value, benefit_type=benefit_type or get_benefit_type(offer.benefit), offer_name=offer.name, max_uses=max_uses, email_domains=email_domains, program_uuid=program_uuid or offer.condition.program_uuid, site=site or offer.site, )
def is_offer_max_discount_available(basket, offer): # no need to do anything if this is not an enterprise offer or `max_discount` is not set if offer.priority != OFFER_PRIORITY_ENTERPRISE or offer.max_discount is None: return True # get course price product = basket.lines.first().product seat = product.course.seat_products.get(id=product.id) stock_record = StockRecord.objects.get(product=seat, partner=product.course.partner) course_price = stock_record.price_excl_tax # calculate discount value that will be covered by the offer benefit_type = get_benefit_type(offer.benefit) benefit_value = offer.benefit.value if benefit_type == Benefit.PERCENTAGE: discount_value = get_discount_value(float(offer.benefit.value), float(course_price)) discount_value = Decimal(discount_value) else: # Benefit.FIXED # There is a possibility that the discount value could be greater than the course price # ie, discount value is $100, course price is $75, in this case the full price of the course will be covered # and learner will owe $0 to checkout. if benefit_value > course_price: discount_value = course_price else: discount_value = benefit_value # check if offer has discount available new_total_discount = discount_value + offer.total_discount if new_total_discount <= offer.max_discount: return True return False
def update_voucher_with_enterprise_offer(offer, benefit_value, enterprise_customer, benefit_type=None, max_uses=None, email_domains=None, enterprise_catalog=None, site=None): """ Update voucher with enteprise offer. Args: offer (Offer): Offer associated with a voucher. benefit_value (Decimal): Value of benefit associated with vouchers. benefit_type (str): Type of benefit associated with vouchers. coupon (Product): The coupon whos offer(s) is updated. enterprise_customer (str): The uuid of the enterprise customer. Kwargs: max_uses (int): number of maximum global application number an offer can have. email_domains (str): a comma-separated string of email domains allowed to apply this offer. enterprise_catalog (str): Enterprise Catalog UUID site (Site): Site for this offer Returns: Offer """ return get_or_create_enterprise_offer( benefit_value=benefit_value or offer.benefit.value, benefit_type=benefit_type or get_benefit_type(offer.benefit), enterprise_customer=enterprise_customer or offer.condition.enterprise_customer_uuid, enterprise_customer_catalog=enterprise_catalog or offer.condition.enterprise_customer_catalog_uuid, offer_name=offer.name, max_uses=max_uses, email_domains=email_domains, site=site or offer.site, )
def test_get_offers_for_multiple_courses_voucher(self): """ Verify that the course offers data is returned for a multiple courses voucher. """ self.mock_access_token_response() course, seat = self.create_course_and_seat() self.mock_course_runs_endpoint( self.site_configuration.discovery_api_url, query='*:*', course_run=course ) new_range, __ = Range.objects.get_or_create(catalog_query='*:*', course_seat_types='verified') new_range.add_product(seat) voucher, __ = prepare_voucher(_range=new_range, benefit_value=10) benefit = voucher.offers.first().benefit request = self.prepare_offers_listing_request(voucher.code) offers = VoucherViewSet().get_offers(request=request, voucher=voucher)['results'] first_offer = offers[0] self.assertEqual(len(offers), 1) self.assertDictEqual(first_offer, { 'benefit': { 'type': get_benefit_type(benefit), 'value': benefit.value }, 'contains_verified': True, 'course_start_date': '2016-05-01T00:00:00Z', 'id': course.id, 'image_url': 'path/to/the/course/image', 'multiple_credit_providers': False, 'organization': CourseKey.from_string(course.id).org, 'credit_provider_price': None, 'seat_type': seat.attr.certificate_type, 'stockrecords': serializers.StockRecordSerializer(seat.stockrecords.first()).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime, })
def test_get_offers_for_enterprise_catalog_voucher(self): """ Verify that the course offers data is returned for an enterprise catalog voucher. """ Switch.objects.update_or_create( name=ENTERPRISE_OFFERS_FOR_COUPONS_SWITCH, defaults={'active': False}) self.mock_access_token_response() course, seat = self.create_course_and_seat() enterprise_catalog_id = str(uuid4()) self.mock_enterprise_catalog_course_endpoint( self.site_configuration.enterprise_api_url, enterprise_catalog_id, course_run=course) new_range, __ = Range.objects.get_or_create( catalog_query='*:*', course_seat_types='verified', enterprise_customer=str(uuid4()), enterprise_customer_catalog=enterprise_catalog_id, ) new_range.add_product(seat) voucher, __ = prepare_voucher(_range=new_range, benefit_value=10) benefit = voucher.offers.first().benefit request = self.prepare_offers_listing_request(voucher.code) offers = VoucherViewSet().get_offers(request=request, voucher=voucher)['results'] first_offer = offers[0] self.assertEqual(len(offers), 1) self.assertDictEqual( first_offer, { 'benefit': { 'type': get_benefit_type(benefit), 'value': benefit.value }, 'contains_verified': True, 'course_start_date': '2016-05-01T00:00:00Z', 'id': course.id, 'image_url': 'path/to/the/course/image', 'multiple_credit_providers': False, 'organization': CourseKey.from_string(course.id).org, 'credit_provider_price': None, 'seat_type': course.type, 'stockrecords': serializers.StockRecordSerializer( seat.stockrecords.first()).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime, })
def _add_offers(self, response): response['offers'] = [ { 'provider': offer.condition.enterprise_customer_name, 'name': offer.name, 'benefit_type': get_benefit_type(offer.benefit) if offer.benefit else None, 'benefit_value': get_quantized_benefit_value(offer.benefit) if offer.benefit else None, } for offer in self.request.basket.applied_offers().values() if (offer.condition.enterprise_customer_name or (offer.condition.name and offer.offer_type == ConditionalOffer.SITE)) ]
def _add_coupons(self, response, context): response['show_coupon_form'] = context['show_voucher_form'] benefit = context['total_benefit_object'] response['coupons'] = [ { 'id': voucher.id, 'code': voucher.code, 'benefit_type': get_benefit_type(benefit) if benefit else None, 'benefit_value': get_quantized_benefit_value(benefit) if benefit else None, } for voucher in self.request.basket.vouchers.all() if response['show_coupon_form'] and self.request.basket.contains_a_voucher ]
def test_get_offers_for_enterprise_offer(self): """ Verify that the course offers data is returned for an enterprise catalog voucher. """ self.mock_access_token_response() course, seat = self.create_course_and_seat() enterprise_customer_id = str(uuid4()) enterprise_catalog_id = str(uuid4()) self.mock_enterprise_catalog_course_endpoint( self.site_configuration.enterprise_api_url, enterprise_catalog_id, course_run=course) voucher = prepare_enterprise_voucher( benefit_value=10, enterprise_customer=enterprise_customer_id, enterprise_customer_catalog=enterprise_catalog_id) benefit = voucher.offers.first().benefit request = self.prepare_offers_listing_request(voucher.code) offers = VoucherViewSet().get_offers(request=request, voucher=voucher)['results'] first_offer = offers[0] self.assertEqual(len(offers), 1) self.assertDictEqual( first_offer, { 'benefit': { 'type': get_benefit_type(benefit), 'value': benefit.value }, 'contains_verified': True, 'course_start_date': '2016-05-01T00:00:00Z', 'id': course.id, 'image_url': 'path/to/the/course/image', 'multiple_credit_providers': False, 'organization': CourseKey.from_string(course.id).org, 'credit_provider_price': None, 'seat_type': seat.attr.certificate_type, 'stockrecords': serializers.StockRecordSerializer( seat.stockrecords.first()).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime, })
def test_offers_api_endpoint_for_course_catalog_voucher(self): """ Verify that the course offers data is returned for a course catalog voucher. """ catalog_id = 1 catalog_query = '*:*' self.mock_access_token_response() # Populate database for the test case. course, seat = self.create_course_and_seat() new_range, __ = Range.objects.get_or_create(course_catalog=catalog_id, course_seat_types='verified') new_range.add_product(seat) voucher, __ = prepare_voucher(_range=new_range, benefit_value=10) # Mock network calls self.mock_catalog_detail_endpoint( catalog_id=catalog_id, expected_query=catalog_query, discovery_api_url=self.site_configuration.discovery_api_url ) self.mock_course_runs_endpoint( discovery_api_url=self.site_configuration.discovery_api_url, query=catalog_query, course_run=course ) benefit = voucher.offers.first().benefit request = self.prepare_offers_listing_request(voucher.code) response = self.endpointView(request) # Verify that offers are returned when voucher is created using course catalog self.assertEqual(response.status_code, 200) self.assertListEqual( response.data['results'], [{ 'benefit': { 'type': get_benefit_type(benefit), 'value': benefit.value }, 'contains_verified': True, 'course_start_date': '2016-05-01T00:00:00Z', 'id': course.id, 'image_url': 'path/to/the/course/image', 'multiple_credit_providers': False, 'organization': CourseKey.from_string(course.id).org, 'credit_provider_price': None, 'seat_type': seat.attr.certificate_type, 'stockrecords': serializers.StockRecordSerializer(seat.stockrecords.first()).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime, }], )
def test_get_offers_for_single_course_voucher(self): """ Verify that the course offers data is returned for a single course voucher. """ self.mock_access_token_response() course, seat = self.create_course_and_seat() new_range = RangeFactory(products=[ seat, ]) voucher, __ = prepare_voucher(_range=new_range, benefit_value=10) benefit = voucher.offers.first().benefit request = self.prepare_offers_listing_request(voucher.code) self.mock_course_run_detail_endpoint( course, discovery_api_url=self.site_configuration.discovery_api_url) offers = VoucherViewSet().get_offers(request=request, voucher=voucher)['results'] first_offer = offers[0] self.assertEqual(len(offers), 1) self.assertDictEqual( first_offer, { 'benefit': { 'type': get_benefit_type(benefit), 'value': benefit.value }, 'contains_verified': True, 'course_start_date': '2013-02-05T05:00:00Z', 'id': course.id, 'image_url': '/path/to/image.jpg', 'multiple_credit_providers': False, 'organization': CourseKey.from_string(course.id).org, 'credit_provider_price': None, 'seat_type': course.type, 'stockrecords': serializers.StockRecordSerializer( seat.stockrecords.first()).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime, })
def test_get_course_offer_data(self): """ Verify that the course offers data is properly formatted. """ benefit = BenefitFactory() course, seat = self.create_course_and_seat() course_info = { 'start': '2016-05-01T00:00:00Z', 'image': { 'src': 'path/to/the/course/image' } } stock_record = seat.stockrecords.first() voucher = VoucherFactory() offer = VoucherViewSet().get_course_offer_data( benefit=benefit, course=course, course_info=course_info, credit_provider_price=None, multiple_credit_providers=False, is_verified=True, product=seat, stock_record=stock_record, voucher=voucher) self.assertDictEqual( offer, { 'benefit': { 'type': get_benefit_type(benefit), 'value': benefit.value }, 'contains_verified': True, 'course_start_date': course_info['start'], 'id': course.id, 'image_url': course_info['image']['src'], 'multiple_credit_providers': False, 'organization': CourseKey.from_string(course.id).org, 'credit_provider_price': None, 'seat_type': seat.attr.certificate_type, 'stockrecords': serializers.StockRecordSerializer(stock_record).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime, })
def _get_basket_discount_value(basket, offer): """Calculate the discount value based on benefit type and value""" sum_basket_lines = basket.all_lines().aggregate( total=Sum('stockrecord__price_excl_tax'))['total'] or Decimal(0.0) # calculate discount value that will be covered by the offer benefit_type = get_benefit_type(offer.benefit) benefit_value = offer.benefit.value if benefit_type == Benefit.PERCENTAGE: discount_value = get_discount_value(float(offer.benefit.value), float(sum_basket_lines)) discount_value = Decimal(discount_value) else: # Benefit.FIXED # There is a possibility that the discount value could be greater than the sum of basket lines # ie, discount value is $100, basket lines are $75, in this case the full price of the basket lines # will be covered and learner will owe $0 to checkout. if benefit_value > sum_basket_lines: discount_value = sum_basket_lines else: discount_value = benefit_value return discount_value
def _get_course_discount_value(basket, offer): """Calculate the discount value based on benefit type and value""" product = basket.lines.first().product seat = product.course.seat_products.get(id=product.id) stock_record = StockRecord.objects.get(product=seat, partner=product.course.partner) course_price = stock_record.price_excl_tax # calculate discount value that will be covered by the offer benefit_type = get_benefit_type(offer.benefit) benefit_value = offer.benefit.value if benefit_type == Benefit.PERCENTAGE: discount_value = get_discount_value(float(offer.benefit.value), float(course_price)) discount_value = Decimal(discount_value) else: # Benefit.FIXED # There is a possibility that the discount value could be greater than the course price # ie, discount value is $100, course price is $75, in this case the full price of the course will be covered # and learner will owe $0 to checkout. if benefit_value > course_price: discount_value = course_price else: discount_value = benefit_value return discount_value
def _get_info_for_coupon_report(coupon, voucher): created_date = coupon.date_updated.strftime( "%b %d, %y") if coupon.date_updated else 'N/A' category_name = ProductCategory.objects.get(product=coupon).category.name try: note = coupon.attr.note except AttributeError: note = '' coupon_stockrecord = StockRecord.objects.get(product=coupon) invoiced_amount = currency(coupon_stockrecord.price_excl_tax) offer = voucher.best_offer offer_range = offer.condition.range program_uuid = offer.condition.program_uuid benefit = offer.benefit course_id = None seat_stockrecord = None course_organization = None catalog_query = None course_seat_types = None if program_uuid: course_id = None elif offer_range and offer_range.catalog: seat_stockrecord = offer_range.catalog.stock_records.first() course_id, course_organization = _get_course_id_and_organization( seat_stockrecord) elif offer_range and offer_range.catalog_query: catalog_query = offer_range.catalog_query course_id = None course_seat_types = offer_range.course_seat_types if course_id: price = currency(seat_stockrecord.price_excl_tax) discount_data = get_voucher_discount_info( benefit, seat_stockrecord.price_excl_tax) coupon_type, discount_percentage, discount_amount = _get_discount_info( discount_data) else: benefit_type = get_benefit_type(benefit) if benefit_type == Benefit.PERCENTAGE: coupon_type = _('Discount') if benefit.value < 100 else _( 'Enrollment') else: coupon_type = None discount_percentage = _('{percentage} %').format( percentage=benefit.value ) if benefit_type == Benefit.PERCENTAGE else None discount_amount = None price = None coupon_data = { _('Code'): _('This row applies to all vouchers'), _('Category'): category_name, _('Coupon Expiry Date'): voucher.end_datetime.strftime("%b %d, %y"), _('Coupon Name'): voucher.name, _('Coupon Start Date'): voucher.start_datetime.strftime("%b %d, %y"), _('Coupon Type'): coupon_type, _('Create Date'): created_date, _('Discount Percentage'): discount_percentage, _('Discount Amount'): discount_amount, _('Email Domains'): offer.email_domains, _('Invoiced Amount'): invoiced_amount, _('Note'): note, _('Price'): price } if course_id: coupon_data[_('Course ID')] = course_id coupon_data[_('Organization')] = course_organization elif program_uuid: coupon_data[_('Program UUID')] = program_uuid else: coupon_data[_('Catalog Query')] = catalog_query coupon_data[_('Course Seat Types')] = course_seat_types return coupon_data
def benefit_type(benefit): return get_benefit_type(benefit)