diff --git a/core/app/models/spree/promotion_handler/cart.rb b/core/app/models/spree/promotion_handler/cart.rb index 8e7db01e5f8..d2f0673fe0d 100644 --- a/core/app/models/spree/promotion_handler/cart.rb +++ b/core/app/models/spree/promotion_handler/cart.rb @@ -42,14 +42,49 @@ def connected_order_promotions where(spree_orders_promotions: { order_id: order.id }).readonly(false).to_a end - def sale_promotions - Spree::Promotion.where(apply_automatically: true).active.includes(:promotion_rules) - end - def promotion_code(promotion) order_promotion = Spree::OrderPromotion.where(order: order, promotion: promotion).first order_promotion.present? ? order_promotion.promotion_code : nil end + + def sale_promotions + scope = Spree::Promotion.where(apply_automatically: true).active.includes(:promotion_rules).distinct + + Rails.application.config.spree.promotions.rules.each do |rule_class| + next unless rule_class.respond_to? :excluded_promotions_for_order + + scope = scope.where.not(id: rule_class.excluded_promotions_for_order(order).select(:id)) + end + + # Filter promotions that are not eligible for current_user. + scope = scope.select do |promotion| + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::User'").none? || + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::User'") + .flat_map(&:user_ids).include?(order.user_id) + end + + # Filter promotions that are not eligible for the selected products in the order. + scope = scope.select do |promotion| + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::Product'").none? || + order.product_ids.flat_map { |p_id| promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::Product'").flat_map(&:product_ids).include?(p_id) }.include?(true) + end + + # Filter promotions that are not eligible for the order's store. + scope = scope.select do |promotion| + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::Store'").none? || + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::Store'").flat_map(&:store_ids) + .include?(order.store_id) + end + + scope = scope.select do |promotion| + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::Taxon'").none? || + order.products.flat_map(&:taxon_ids).uniq.map { |taxon_id| + promotion.rules.where("spree_promotion_rules.type = 'Spree::Promotion::Rules::Taxon'").flat_map(&:taxon_ids).include?(taxon_id) + }.include?(true) + end + + scope + end end end end diff --git a/core/spec/models/spree/promotion_handler/cart_spec.rb b/core/spec/models/spree/promotion_handler/cart_spec.rb index bb41cdd60af..2af237d6314 100644 --- a/core/spec/models/spree/promotion_handler/cart_spec.rb +++ b/core/spec/models/spree/promotion_handler/cart_spec.rb @@ -60,6 +60,68 @@ module PromotionHandler include_context "creates an order promotion" end + context "promotion does not activate for other items" do + let(:other_line_item) { create(:line_item) } + let!(:rule) { Promotion::Rules::Product.create(products: [line_item.product], promotion: promotion) } + let!(:other_rule) { Promotion::Rules::Product.create(products: [other_line_item.product], promotion: promotion) } + + include_context "creates the adjustment" + include_context "creates an order promotion" + end + + context "promotion activates for store" do + let!(:rule) { Promotion::Rules::Store.create(stores: [order.store], promotion: promotion) } + + include_context "creates the adjustment" + include_context "creates an order promotion" + end + + context "promotion does not activate for other store" do + let(:other_store) { create(:store) } + let(:other_promotion) { create(:promotion, apply_automatically: true) } + let!(:other_rule) { Promotion::Rules::Store.create(stores: [other_store], promotion: other_promotion) } + let!(:rule) { Promotion::Rules::Store.create(stores: [order.store], promotion: promotion) } + + include_context "creates the adjustment" + include_context "creates an order promotion" + + it "doesn't connect the promotion to the order" do + expect { + subject.activate + }.to change { order.promotions.count }.by(1) + end + + it "doesn't create an adjustment" do + expect { + subject.activate + }.to change { adjustable.adjustments.count }.by(1) + end + end + + context "promotion activates for user" do + let!(:rule) { Promotion::Rules::User.create(users: [order.user], promotion: promotion) } + + include_context "creates the adjustment" + include_context "creates an order promotion" + end + + context "promotion does not activate for other user" do + let(:user) { create(:user) } + let!(:rule) { Promotion::Rules::User.create(users: [user], promotion: promotion) } + + it "doesn't connect the promotion to the order" do + expect { + subject.activate + }.to change { order.promotions.count }.by(0) + end + + it "doesn't create an adjustment" do + expect { + subject.activate + }.to change { adjustable.adjustments.count }.by(0) + end + end + context "promotion has item total rule" do let(:shirt) { create(:product) } let!(:rule) { Promotion::Rules::ItemTotal.create(preferred_operator: 'gt', preferred_amount: 50, promotion: promotion) }