def is_satisfied(self, offer, basket): # pylint: disable=unused-argument """ Determines if a user is eligible for an enterprise customer offer based on their association with the enterprise customer. Args: basket (Basket): Contains information about order line items, the current site, and the user attempting to make the purchase. Returns: bool """ if not basket.owner: # An anonymous user is never linked to any EnterpriseCustomer. return False try: learner_data = fetch_enterprise_learner_data( basket.site, basket.owner)['results'][0] except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout): logger.exception( 'Failed to retrieve enterprise learner data for site [%s] and user [%s].', basket.site.domain, basket.owner.username, ) return False except IndexError: return False enterprise_customer = learner_data['enterprise_customer'] if str(self.enterprise_customer_uuid) != enterprise_customer['uuid']: # Learner is not linked to the EnterpriseCustomer associated with this condition. return False course_run_ids = [] for line in basket.all_lines(): course = line.product.course if not course: # Basket contains products not related to a course_run. return False course_run_ids.append(course.id) if not catalog_contains_course_runs( basket.site, course_run_ids, self.enterprise_customer_uuid, enterprise_customer_catalog_uuid=self. enterprise_customer_catalog_uuid): # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs # associated with the EnterpriseCustomer. return False return True
def _assert_contains_course_runs(self, expected, course_run_ids, enterprise_customer_uuid, enterprise_customer_catalog_uuid): """ Helper method to validate the response from the method `catalog_contains_course_runs`. """ actual = enterprise_api.catalog_contains_course_runs( self.site, course_run_ids, enterprise_customer_uuid, enterprise_customer_catalog_uuid=enterprise_customer_catalog_uuid, ) self.assertEqual(expected, actual)
def is_satisfied(self, offer, basket): # pylint: disable=unused-argument """ Determines if a user is eligible for an enterprise customer offer based on their association with the enterprise customer. It also filter out the offer if the `enterprise_customer_catalog_uuid` value set on the offer condition does not match with the basket catalog value when explicitly provided by the enterprise learner. Note: Currently there is no mechanism to prioritize or apply multiple offers that may apply as opposed to disqualifying offers if the catalog doesn't explicitly match. Arguments: basket (Basket): Contains information about order line items, the current site, and the user attempting to make the purchase. Returns: bool """ if not basket.owner: # An anonymous user is never linked to any EnterpriseCustomer. return False enterprise_customer = str(self.enterprise_customer_uuid) enterprise_catalog = str(self.enterprise_customer_catalog_uuid) username = basket.owner.username course_run_ids = [] for line in basket.all_lines(): course = line.product.course if not course: # Basket contains products not related to a course_run. # Only log for non-site offers to avoid noise. if offer.offer_type != ConditionalOffer.SITE: logger.warning( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'the Basket contains a product not related to a course_run. ' 'User: %s, Offer: %s, Product: %s, Enterprise: %s, Catalog: %s', username, offer.id, line.product.id, enterprise_customer, enterprise_catalog) return False course_run_ids.append(course.id) courses_in_basket = ','.join(course_run_ids) learner_data = {} try: learner_data = fetch_enterprise_learner_data( basket.site, basket.owner)['results'][0] except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout) as exc: logger.exception( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'we failed to retrieve enterprise learner data for the user. ' 'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s', username, offer.id, exc, enterprise_customer, enterprise_catalog, courses_in_basket) return False except IndexError: if offer.offer_type == ConditionalOffer.SITE: logger.debug( 'Unable to apply enterprise site offer %s because no learner data was returned for user %s', offer.id, basket.owner) return False if (learner_data and 'enterprise_customer' in learner_data and enterprise_customer != learner_data['enterprise_customer']['uuid']): # Learner is not linked to the EnterpriseCustomer associated with this condition. if offer.offer_type == ConditionalOffer.VOUCHER: logger.warning( '[Code Redemption Failure] Unable to apply enterprise offer because Learner\'s ' 'enterprise (%s) does not match this conditions\'s enterprise (%s). ' 'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s', learner_data['enterprise_customer']['uuid'], enterprise_customer, username, offer.id, enterprise_customer, enterprise_catalog, courses_in_basket) return False # Verify that the current conditional offer is related to the provided # enterprise catalog, this will also filter out offers which don't # have `enterprise_customer_catalog_uuid` value set on the condition. catalog = self._get_enterprise_catalog_uuid_from_basket(basket) if catalog: if offer.condition.enterprise_customer_catalog_uuid != catalog: logger.warning( 'Unable to apply enterprise offer %s because ' 'Enterprise catalog id on the basket (%s) ' 'does not match the catalog for this condition (%s).', offer.id, catalog, offer.condition.enterprise_customer_catalog_uuid) return False try: catalog_contains_course = catalog_contains_course_runs( basket.site, course_run_ids, enterprise_customer, enterprise_customer_catalog_uuid=enterprise_catalog) except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout) as exc: logger.exception( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'we failed to check if course_runs exist in the catalog. ' 'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s', username, offer.id, exc, enterprise_customer, enterprise_catalog, courses_in_basket) return False if not catalog_contains_course: # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs # associated with the EnterpriseCustomer. logger.warning( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'Enterprise catalog does not contain the course(s) in this basket. ' 'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s', username, offer.id, enterprise_customer, enterprise_catalog, courses_in_basket) return False return True
def is_satisfied(self, offer, basket): # pylint: disable=unused-argument """ Determines if a user is eligible for an enterprise customer offer based on their association with the enterprise customer. It also filter out the offer if the `enterprise_customer_catalog_uuid` value set on the offer condition does not match with the basket catalog value when explicitly provided by the enterprise learner. Note: Currently there is no mechanism to prioritize or apply multiple offers that may apply as opposed to disqualifying offers if the catalog doesn't explicitly match. Arguments: basket (Basket): Contains information about order line items, the current site, and the user attempting to make the purchase. Returns: bool """ if not basket.owner: # An anonymous user is never linked to any EnterpriseCustomer. return False if offer.offer_type == ConditionalOffer.VOUCHER: logger.info( 'Skipping Voucher type enterprise conditional offer until we are ready to support it.' ) return False try: learner_data = fetch_enterprise_learner_data( basket.site, basket.owner)['results'][0] except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout): logger.exception( 'Failed to retrieve enterprise learner data for site [%s] and user [%s].', basket.site.domain, basket.owner.username, ) return False except IndexError: return False enterprise_customer = learner_data['enterprise_customer'] if str(self.enterprise_customer_uuid) != enterprise_customer['uuid']: # Learner is not linked to the EnterpriseCustomer associated with this condition. return False course_run_ids = [] for line in basket.all_lines(): course = line.product.course if not course: # Basket contains products not related to a course_run. return False course_run_ids.append(course.id) # Verify that the current conditional offer is related to the provided # enterprise catalog, this will also filter out offers which don't # have `enterprise_customer_catalog_uuid` value set on the condition. catalog = self._get_enterprise_catalog_uuid_from_basket(basket) if catalog: if offer.condition.enterprise_customer_catalog_uuid != catalog: return False if not catalog_contains_course_runs( basket.site, course_run_ids, self.enterprise_customer_uuid, enterprise_customer_catalog_uuid=self. enterprise_customer_catalog_uuid): # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs # associated with the EnterpriseCustomer. return False return True
def is_satisfied(self, offer, basket): # pylint: disable=unused-argument """ Determines if a user is eligible for an enterprise customer offer based on their association with the enterprise customer. It also filter out the offer if the `enterprise_customer_catalog_uuid` value set on the offer condition does not match with the basket catalog value when explicitly provided by the enterprise learner. Note: Currently there is no mechanism to prioritize or apply multiple offers that may apply as opposed to disqualifying offers if the catalog doesn't explicitly match. Arguments: basket (Basket): Contains information about order line items, the current site, and the user attempting to make the purchase. Returns: bool """ if not basket.owner: # An anonymous user is never linked to any EnterpriseCustomer. return False if (offer.offer_type == ConditionalOffer.VOUCHER and not waffle.switch_is_active( ENTERPRISE_OFFERS_FOR_COUPONS_SWITCH)): logger.info( 'Skipping Voucher type enterprise conditional offer until we are ready to support it.' ) return False learner_data = {} try: learner_data = fetch_enterprise_learner_data( basket.site, basket.owner)['results'][0] except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout): logger.exception( 'Failed to retrieve enterprise learner data for site [%s] and user [%s].', basket.site.domain, basket.owner.username, ) return False except IndexError: if offer.offer_type == ConditionalOffer.SITE: logger.debug( 'Unable to apply enterprise site offer %s because no learner data was returned for user %s', offer.id, basket.owner) return False if (learner_data and 'enterprise_customer' in learner_data and str(self.enterprise_customer_uuid) != learner_data['enterprise_customer']['uuid']): # Learner is not linked to the EnterpriseCustomer associated with this condition. logger.debug( 'Unable to apply enterprise offer %s because Learner\'s enterprise (%s)' 'does not match this conditions\'s enterprise (%s).', offer.id, learner_data['enterprise_customer']['uuid'], str(self.enterprise_customer_uuid)) return False course_run_ids = [] for line in basket.all_lines(): course = line.product.course if not course: # Basket contains products not related to a course_run. logger.warning( 'Unable to apply enterprise offer %s because ' 'the Basket (#%s) contains a product (#%s) not related to a course_run.', offer.id, basket.id, line.product.id) return False course_run_ids.append(course.id) # Verify that the current conditional offer is related to the provided # enterprise catalog, this will also filter out offers which don't # have `enterprise_customer_catalog_uuid` value set on the condition. catalog = self._get_enterprise_catalog_uuid_from_basket(basket) if catalog: if offer.condition.enterprise_customer_catalog_uuid != catalog: logger.warning( 'Unable to apply enterprise offer %s because ' 'Enterprise catalog id on the basket (%s) ' 'does not match the catalog for this condition (%s).', offer.id, catalog, offer.condition.enterprise_customer_catalog_uuid) return False if not catalog_contains_course_runs( basket.site, course_run_ids, self.enterprise_customer_uuid, enterprise_customer_catalog_uuid=self. enterprise_customer_catalog_uuid): # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs # associated with the EnterpriseCustomer. logger.warning( 'Unable to apply enterprise offer %s because ' 'Enterprise catalog (%s) does not contain the course(s) (%s) in this basket.', offer.id, self.enterprise_customer_catalog_uuid, ','.join(course_run_ids)) return False return True
def is_satisfied(self, offer, basket): # pylint: disable=unused-argument """ Determines if a user is eligible for an enterprise customer offer based on their association with the enterprise customer. It also filter out the offer if the `enterprise_customer_catalog_uuid` value set on the offer condition does not match with the basket catalog value when explicitly provided by the enterprise learner. Note: Currently there is no mechanism to prioritize or apply multiple offers that may apply as opposed to disqualifying offers if the catalog doesn't explicitly match. Arguments: basket (Basket): Contains information about order line items, the current site, and the user attempting to make the purchase. Returns: bool """ if not basket.owner: # An anonymous user is never linked to any EnterpriseCustomer. return False enterprise_in_condition = str(self.enterprise_customer_uuid) enterprise_catalog = str(self.enterprise_customer_catalog_uuid) if self.enterprise_customer_catalog_uuid \ else None enterprise_name_in_condition = str(self.enterprise_customer_name) username = basket.owner.username course_run_ids = [] for line in basket.all_lines(): course = line.product.course if not course: # Basket contains products not related to a course_run. # Only log for non-site offers to avoid noise. if offer.offer_type != ConditionalOffer.SITE: logger.warning( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'the Basket contains a product not related to a course_run. ' 'User: %s, Offer: %s, Product: %s, Enterprise: %s, Catalog: %s', username, offer.id, line.product.id, enterprise_in_condition, enterprise_catalog) return False course_run_ids.append(course.id) courses_in_basket = ','.join(course_run_ids) user_enterprise = get_enterprise_id_for_user(basket.site, basket.owner) if user_enterprise and enterprise_in_condition != user_enterprise: # Learner is not linked to the EnterpriseCustomer associated with this condition. if offer.offer_type == ConditionalOffer.VOUCHER: logger.warning( '[Code Redemption Failure] Unable to apply enterprise offer because Learner\'s ' 'enterprise (%s) does not match this conditions\'s enterprise (%s). ' 'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s', user_enterprise, enterprise_in_condition, username, offer.id, enterprise_in_condition, enterprise_catalog, courses_in_basket) logger.info( '[Code Redemption Issue] Linking learner with the enterprise in Condition. ' 'User [%s], Enterprise [%s]', username, enterprise_in_condition) get_or_create_enterprise_customer_user( basket.site, enterprise_in_condition, username, False) msg = _( 'This coupon has been made available through {new_enterprise}. ' 'To redeem this coupon, you must first logout. When you log back in, ' 'please select {new_enterprise} as your enterprise ' 'and try again.').format( new_enterprise=enterprise_name_in_condition) messages.warning( crum.get_current_request(), msg, ) return False # Verify that the current conditional offer is related to the provided # enterprise catalog, this will also filter out offers which don't # have `enterprise_customer_catalog_uuid` value set on the condition. catalog = self._get_enterprise_catalog_uuid_from_basket(basket) if catalog: if offer.condition.enterprise_customer_catalog_uuid != catalog: logger.warning( 'Unable to apply enterprise offer %s because ' 'Enterprise catalog id on the basket (%s) ' 'does not match the catalog for this condition (%s).', offer.id, catalog, offer.condition.enterprise_customer_catalog_uuid) return False try: catalog_contains_course = catalog_contains_course_runs( basket.site, course_run_ids, enterprise_in_condition, enterprise_customer_catalog_uuid=enterprise_catalog) except (ReqConnectionError, KeyError, SlumberHttpBaseException, Timeout) as exc: logger.exception( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'we failed to check if course_runs exist in the catalog. ' 'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s', username, offer.id, exc, enterprise_in_condition, enterprise_catalog, courses_in_basket) return False if not catalog_contains_course: # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs # associated with the EnterpriseCustomer. logger.warning( '[Code Redemption Failure] Unable to apply enterprise offer because ' 'Enterprise catalog does not contain the course(s) in this basket. ' 'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s', username, offer.id, enterprise_in_condition, enterprise_catalog, courses_in_basket) return False if not is_offer_max_discount_available(basket, offer): logger.warning( '[Enterprise Offer Failure] Unable to apply enterprise offer because bookings limit is consumed.' 'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s, BookingsLimit: %s, TotalDiscount: %s', username, offer.id, enterprise_in_condition, enterprise_catalog, courses_in_basket, offer.max_discount, offer.total_discount, ) return False if not is_offer_max_user_discount_available(basket, offer): logger.warning( '[Enterprise Offer Failure] Unable to apply enterprise offer because user bookings limit is consumed.' 'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s, UserBookingsLimit: %s', username, offer.id, enterprise_in_condition, enterprise_catalog, courses_in_basket, offer.max_user_discount) return False return True