@@ -852,6 +852,12 @@ def get_auto_apply_discounts_for_basket(basket_id: int) -> QuerySet[Discount]:
852852 """
853853 Get the auto-apply discounts that can be applied to a basket.
854854
855+ This includes the financial assistant discounts for the user and products,
856+ if there are any, since those are also automatically applied regardless of
857+ the flag on the discount. This does not include B2B discounts because those
858+ are applied in a different manner - either through a separate API, or by the
859+ learner via the cart interface.
860+
855861 Args:
856862 basket_id (int): The ID of the basket to get the auto-apply discounts for.
857863
@@ -861,29 +867,122 @@ def get_auto_apply_discounts_for_basket(basket_id: int) -> QuerySet[Discount]:
861867 basket = Basket .objects .get (pk = basket_id )
862868 products = basket .get_products ()
863869
870+ finaid_discounts = []
871+
872+ for product in products :
873+ finaid_discount = determine_courseware_flexible_price_discount (
874+ product , basket .user
875+ )
876+
877+ if finaid_discount :
878+ finaid_discounts .append (finaid_discount .id )
879+
864880 return Discount .objects .filter (
865- Q (products__product__in = products ) | Q (products__isnull = True ),
881+ Q (activation_date__lte = now_in_utc ()) | Q (activation_date = None ),
882+ Q (expiration_date__gt = now_in_utc ()) | Q (expiration_date = None ),
883+ ).filter (
866884 Q (user_discount_discount__user = basket .user )
867- | Q (user_discount_discount__isnull = True ),
868- automatic = True ,
885+ | Q (pk__in = finaid_discounts )
886+ | Q ( automatic = True )
869887 )
870888
871889
872- def apply_discount_to_basket (basket : Basket , discount : Discount , * , allow_finaid = False ):
890+ def apply_discount_to_basket (basket : Basket , discount : Discount , * , allow_finaid = False ): # noqa: C901
873891 """
874892 Apply a discount to a basket.
875893
894+ Discount application is subject to rules:
895+ - The discount itself must be valid on its face (not inactive, applies to products, etc.)
896+ - The discount is not a financial assistance tier discount, unless allow_finaid is set
897+ - The discount provides a better price to the learner than any other applied discount
898+ - The discount is not overriding a user discount
899+
900+ If a user discount is supplied to this function, then that discount will be
901+ applied _unless_ a financial assistance discount is also applied. User
902+ discounts take precedence over any other discount, other than financial
903+ assistance discounts.
904+
905+ This function is not for use with B2B or verified program enrollment code
906+ redemption. Those use cases have their own redemption code paths because
907+ they need different verification steps.
908+
876909 Args:
877910 discount (Discount): The Discount to apply to the basket.
878911 Keyword Args:
879912 allow_finaid (bool): Allow a financial assistance discount through.
880913 """
881914 if discount .is_valid (basket , allow_finaid = allow_finaid ):
882- BasketDiscount .objects .create (
915+ defaults = {
916+ "redeemed_discount" : discount ,
917+ "redemption_date" : now_in_utc (),
918+ }
919+
920+ if basket .discounts .count () > 0 and basket .basket_items .count () > 0 :
921+ # Check to make sure the supplied discount can be applied. This means
922+ # that it should not override any user discounts that are applied,
923+ # and it should be better than the other discounts in the basket.
924+
925+ if discount .user_discount_discount .filter (user = basket .user ).exists ():
926+ # This is a user discount.
927+ # Check for an existing tier discount - user discount shouldn't override that
928+ finaid_discounts = [
929+ basket_discount
930+ for basket_discount in basket .discounts .all ()
931+ if basket_discount .redeemed_discount .flexible_price_tiers .count ()
932+ > 0
933+ ]
934+
935+ if len (finaid_discounts ) > 0 :
936+ # There is a finaid discount, so don't apply this user one.
937+ return
938+ else :
939+ is_finaid_discount = discount .flexible_price_tiers .exists ()
940+ has_user_discount = (
941+ basket .discounts .filter (
942+ redeemed_discount__user_discount_discount__user = basket .user
943+ ).count ()
944+ > 0
945+ )
946+
947+ if is_finaid_discount and not allow_finaid :
948+ # Financial assistance discount; bail unless the flag is set
949+ return
950+
951+ if has_user_discount and is_finaid_discount and allow_finaid :
952+ # Basket has a user discount applied; this is a finaid
953+ # discount (and we're allowed to apply it); apply the
954+ # discount without further evaluation.
955+
956+ BasketDiscount .objects .update_or_create (
957+ redeemed_by = basket .user ,
958+ redeemed_basket = basket ,
959+ defaults = defaults ,
960+ create_defaults = defaults ,
961+ )
962+ return
963+
964+ if has_user_discount :
965+ # This basket has a user discount applied; this isn't a
966+ # finaid discount that we're permitting to be applied; so
967+ # skip this one.
968+ return
969+
970+ found_better = False
971+
972+ for item in basket .basket_items .all ():
973+ test_price = discount .discount_product (item .product , basket .user )
974+ if test_price and item .discounted_price >= test_price :
975+ found_better = True
976+ break
977+
978+ if not found_better :
979+ return
980+
981+ BasketDiscount .objects .update_or_create (
883982 redeemed_by = basket .user ,
884- redeemed_discount = discount ,
885983 redeemed_basket = basket ,
886- redemption_date = now_in_utc (),
984+ defaults = defaults ,
985+ create_defaults = defaults ,
887986 )
888987
889988
0 commit comments