solidus_promotions 4.6.2 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIGRATING.md +10 -4
- data/README.md +7 -0
- data/app/javascript/backend/solidus_promotions/controllers/product_option_values_controller.js +3 -26
- data/app/javascript/backend/solidus_promotions/web_components/option_value_picker.js +36 -19
- data/app/javascript/backend/solidus_promotions/web_components/product_picker.js +6 -1
- data/app/jobs/solidus_promotions/promotion_code_batch_job.rb +1 -1
- data/app/models/concerns/solidus_promotions/adjustment_discounts.rb +20 -0
- data/app/models/concerns/solidus_promotions/benefits/line_item_benefit.rb +5 -0
- data/app/models/concerns/solidus_promotions/benefits/order_benefit.rb +5 -0
- data/app/models/concerns/solidus_promotions/benefits/shipment_benefit.rb +5 -0
- data/app/models/concerns/solidus_promotions/calculators/promotion_calculator.rb +7 -0
- data/app/models/concerns/solidus_promotions/conditions/line_item_applicable_order_level_condition.rb +17 -5
- data/app/models/concerns/solidus_promotions/conditions/line_item_level_condition.rb +15 -2
- data/app/models/concerns/solidus_promotions/conditions/option_value_condition.rb +21 -0
- data/app/models/concerns/solidus_promotions/conditions/order_level_condition.rb +15 -2
- data/app/models/concerns/solidus_promotions/conditions/product_condition.rb +28 -0
- data/app/models/concerns/solidus_promotions/conditions/shipment_level_condition.rb +15 -2
- data/app/models/concerns/solidus_promotions/conditions/taxon_condition.rb +77 -0
- data/app/models/concerns/solidus_promotions/coupon_code_normalizer.rb +37 -0
- data/app/models/concerns/solidus_promotions/discountable_amount.rb +3 -4
- data/app/models/concerns/solidus_promotions/discounted_amount.rb +54 -0
- data/app/models/solidus_promotions/benefit.rb +257 -36
- data/app/models/solidus_promotions/benefits/adjust_line_item.rb +28 -3
- data/app/models/solidus_promotions/benefits/adjust_line_item_quantity_groups.rb +1 -0
- data/app/models/solidus_promotions/benefits/adjust_shipment.rb +45 -3
- data/app/models/solidus_promotions/benefits/advertise_price.rb +28 -0
- data/app/models/solidus_promotions/benefits/create_discounted_item.rb +30 -7
- data/app/models/solidus_promotions/calculators/distributed_amount.rb +34 -8
- data/app/models/solidus_promotions/calculators/flat_rate.rb +52 -6
- data/app/models/solidus_promotions/calculators/flexi_rate.rb +69 -6
- data/app/models/solidus_promotions/calculators/percent.rb +40 -4
- data/app/models/solidus_promotions/calculators/percent_with_cap.rb +44 -3
- data/app/models/solidus_promotions/calculators/tiered_flat_rate.rb +81 -19
- data/app/models/solidus_promotions/calculators/tiered_percent.rb +96 -25
- data/app/models/solidus_promotions/calculators/tiered_percent_on_eligible_item_quantity.rb +101 -16
- data/app/models/solidus_promotions/condition.rb +186 -7
- data/app/models/solidus_promotions/conditions/first_order.rb +3 -1
- data/app/models/solidus_promotions/conditions/first_repeat_purchase_since.rb +3 -2
- data/app/models/solidus_promotions/conditions/item_total.rb +2 -1
- data/app/models/solidus_promotions/conditions/line_item_option_value.rb +4 -12
- data/app/models/solidus_promotions/conditions/line_item_product.rb +4 -22
- data/app/models/solidus_promotions/conditions/line_item_taxon.rb +7 -38
- data/app/models/solidus_promotions/conditions/minimum_quantity.rb +3 -2
- data/app/models/solidus_promotions/conditions/nth_order.rb +3 -2
- data/app/models/solidus_promotions/conditions/one_use_per_user.rb +2 -1
- data/app/models/solidus_promotions/conditions/option_value.rb +6 -11
- data/app/models/solidus_promotions/conditions/order_option_value.rb +19 -0
- data/app/models/solidus_promotions/conditions/order_product.rb +62 -0
- data/app/models/solidus_promotions/conditions/order_taxon.rb +60 -0
- data/app/models/solidus_promotions/conditions/price_option_value.rb +26 -0
- data/app/models/solidus_promotions/conditions/price_product.rb +36 -0
- data/app/models/solidus_promotions/conditions/price_taxon.rb +28 -0
- data/app/models/solidus_promotions/conditions/product.rb +17 -59
- data/app/models/solidus_promotions/conditions/shipping_method.rb +3 -5
- data/app/models/solidus_promotions/conditions/store.rb +2 -1
- data/app/models/solidus_promotions/conditions/taxon.rb +24 -73
- data/app/models/solidus_promotions/conditions/user.rb +2 -1
- data/app/models/solidus_promotions/conditions/user_logged_in.rb +1 -3
- data/app/models/solidus_promotions/conditions/user_role.rb +1 -3
- data/app/models/solidus_promotions/distributed_amounts_handler.rb +2 -6
- data/app/models/solidus_promotions/eligibility_results.rb +1 -0
- data/app/models/solidus_promotions/item_discount.rb +1 -0
- data/app/models/solidus_promotions/order_adjuster/discount_order.rb +29 -35
- data/app/models/solidus_promotions/order_adjuster/recalculate_promo_totals.rb +45 -0
- data/app/models/solidus_promotions/order_adjuster/set_discounts_to_zero.rb +33 -0
- data/app/models/solidus_promotions/order_adjuster.rb +4 -14
- data/app/models/solidus_promotions/order_promotion.rb +1 -0
- data/app/models/solidus_promotions/product_advertiser.rb +57 -0
- data/app/models/solidus_promotions/promotion.rb +12 -10
- data/app/models/solidus_promotions/promotion_code/batch_builder.rb +1 -1
- data/app/models/solidus_promotions/promotion_code.rb +4 -4
- data/app/models/solidus_promotions/promotion_code_batch.rb +1 -1
- data/app/models/solidus_promotions/promotion_handler/coupon.rb +1 -1
- data/app/models/solidus_promotions/promotion_handler/page.rb +1 -1
- data/app/models/solidus_promotions/promotion_lane.rb +48 -0
- data/app/models/solidus_promotions/shipping_rate_discount.rb +3 -0
- data/app/patches/models/solidus_promotions/line_item_patch.rb +2 -0
- data/app/patches/models/solidus_promotions/order_patch.rb +8 -0
- data/app/patches/models/solidus_promotions/order_recalculator_patch.rb +3 -1
- data/app/patches/models/solidus_promotions/price_patch.rb +31 -0
- data/app/patches/models/solidus_promotions/shipment_patch.rb +2 -0
- data/app/patches/models/solidus_promotions/shipping_rate_patch.rb +15 -0
- data/config/locales/en.yml +47 -11
- data/config/routes.rb +1 -1
- data/db/migrate/20230703101637_create_promotions.rb +2 -2
- data/db/migrate/20230703113625_create_promotion_benefits.rb +3 -3
- data/db/migrate/20230703141116_create_promotion_categories.rb +1 -1
- data/db/migrate/20230703143943_create_promotion_conditions.rb +1 -1
- data/db/migrate/20230704083830_add_condition_join_tables.rb +8 -8
- data/db/migrate/20230704102444_create_promotion_codes.rb +1 -1
- data/db/migrate/20230704102656_create_promotion_code_batches.rb +1 -1
- data/db/migrate/20230705171556_create_order_promotions.rb +3 -3
- data/db/migrate/20230725074235_create_shipping_rate_discounts.rb +2 -2
- data/db/migrate/20231104135812_add_managed_by_order_benefit_to_line_items.rb +1 -1
- data/db/migrate/20251104170913_update_promotion_code_value_collation.rb +38 -0
- data/db/migrate/20251104214304_separate_out_order_only_conditions.rb +41 -0
- data/eslint.config.mjs +29 -0
- data/lib/components/admin/solidus_promotions/promotion_categories/index/component.rb +6 -6
- data/lib/components/admin/solidus_promotions/promotions/index/component.rb +5 -5
- data/lib/solidus_promotions/configuration.rb +57 -12
- data/lib/solidus_promotions/promotion_map.rb +14 -14
- data/lib/solidus_promotions/testing_support/shared_examples/option_value_condition.rb +18 -0
- data/lib/solidus_promotions/testing_support/shared_examples/product_condition.rb +37 -0
- data/lib/solidus_promotions/testing_support/shared_examples/promotion_calculator.rb +11 -0
- data/lib/solidus_promotions/testing_support/shared_examples/taxon_condition.rb +37 -0
- data/lib/solidus_promotions/testing_support/shared_examples.rb +6 -0
- data/lib/views/backend/solidus_promotions/admin/benefit_fields/_advertise_price.html.erb +7 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/_default_fields.html.erb +1 -1
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/percent/_fields.html.erb +1 -1
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_option_value.html.erb +6 -5
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_option_value.html.erb +6 -12
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_price_option_value.html.erb +26 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_price_product.html.erb +21 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_price_taxon.html.erb +17 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_product.html.erb +0 -7
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_taxon.html.erb +0 -7
- data/lib/views/backend/solidus_promotions/admin/condition_fields/line_item_option_value/_option_value_fields.html.erb +10 -4
- data/solidus_promotions.gemspec +1 -1
- metadata +37 -6
- data/.eslintrc.json +0 -10
- data/app/models/solidus_promotions/order_adjuster/persist_discounted_order.rb +0 -79
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
class OrderOptionValue < Condition
|
|
6
|
+
include OptionValueCondition
|
|
7
|
+
|
|
8
|
+
def order_eligible?(order, _options = {})
|
|
9
|
+
order.line_items.any? do |line_item|
|
|
10
|
+
LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(line_item)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_partial_path
|
|
15
|
+
"solidus_promotions/admin/condition_fields/option_value"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
# A condition to limit a promotion based on products in the order. Can
|
|
6
|
+
# require all or any of the products to be present. Valid products
|
|
7
|
+
# either come from assigned product group or are assingned directly to
|
|
8
|
+
# the condition.
|
|
9
|
+
class OrderProduct < Condition
|
|
10
|
+
include ProductCondition
|
|
11
|
+
|
|
12
|
+
MATCH_POLICIES = %w[any all none only].freeze
|
|
13
|
+
|
|
14
|
+
validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES}
|
|
15
|
+
|
|
16
|
+
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
17
|
+
|
|
18
|
+
# scope/association that is used to test eligibility
|
|
19
|
+
def eligible_products
|
|
20
|
+
products
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def order_eligible?(order)
|
|
24
|
+
return true if eligible_products.empty?
|
|
25
|
+
|
|
26
|
+
case preferred_match_policy
|
|
27
|
+
when "all"
|
|
28
|
+
unless eligible_products.all? { |product| order_products(order).include?(product) }
|
|
29
|
+
eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product)
|
|
30
|
+
end
|
|
31
|
+
when "any"
|
|
32
|
+
unless order_products(order).any? { |product| eligible_products.include?(product) }
|
|
33
|
+
eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products),
|
|
34
|
+
error_code: :no_applicable_products)
|
|
35
|
+
end
|
|
36
|
+
when "none"
|
|
37
|
+
unless order_products(order).none? { |product| eligible_products.include?(product) }
|
|
38
|
+
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
|
|
39
|
+
error_code: :has_excluded_product)
|
|
40
|
+
end
|
|
41
|
+
when "only"
|
|
42
|
+
unless order_products(order).all? { |product| eligible_products.include?(product) }
|
|
43
|
+
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
|
|
44
|
+
error_code: :has_excluded_product)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
eligibility_errors.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_partial_path
|
|
52
|
+
"solidus_promotions/admin/condition_fields/product"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def order_products(order)
|
|
58
|
+
order.line_items.map(&:variant).map(&:product)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
class OrderTaxon < Condition
|
|
6
|
+
include TaxonCondition
|
|
7
|
+
|
|
8
|
+
MATCH_POLICIES = %w[any all none].freeze
|
|
9
|
+
|
|
10
|
+
validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES}
|
|
11
|
+
|
|
12
|
+
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
13
|
+
|
|
14
|
+
def order_eligible?(order, _options = {})
|
|
15
|
+
line_item_taxon_ids = taxon_ids_in_order(order)
|
|
16
|
+
|
|
17
|
+
case preferred_match_policy
|
|
18
|
+
when "all"
|
|
19
|
+
unless taxon_ids_with_children.all? { |taxon_and_descendant_ids| (line_item_taxon_ids & taxon_and_descendant_ids).any? }
|
|
20
|
+
|
|
21
|
+
eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon)
|
|
22
|
+
end
|
|
23
|
+
when "any"
|
|
24
|
+
if taxon_ids_with_children.none? { |taxon_and_descendant_ids| (line_item_taxon_ids & taxon_and_descendant_ids).any? }
|
|
25
|
+
|
|
26
|
+
eligibility_errors.add(
|
|
27
|
+
:base,
|
|
28
|
+
eligibility_error_message(:no_matching_taxons),
|
|
29
|
+
error_code: :no_matching_taxons
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
when "none"
|
|
33
|
+
if taxon_ids_with_children.any? { |taxon_and_descendant_ids| (line_item_taxon_ids & taxon_and_descendant_ids).any? }
|
|
34
|
+
|
|
35
|
+
eligibility_errors.add(
|
|
36
|
+
:base,
|
|
37
|
+
eligibility_error_message(:has_excluded_taxon),
|
|
38
|
+
error_code: :has_excluded_taxon
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
eligibility_errors.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_partial_path
|
|
47
|
+
"solidus_promotions/admin/condition_fields/taxon"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# All taxon IDs in an order
|
|
53
|
+
def taxon_ids_in_order(order)
|
|
54
|
+
order.line_items.flat_map do |line_item|
|
|
55
|
+
line_item.variant.product.classifications.map(&:taxon_id)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
class PriceOptionValue < Condition
|
|
6
|
+
include OptionValueCondition
|
|
7
|
+
|
|
8
|
+
def price_eligible?(price, _options = {})
|
|
9
|
+
pid = price.variant.product_id
|
|
10
|
+
ovids = price.variant.option_value_ids
|
|
11
|
+
|
|
12
|
+
product_ids.include?(pid) && (value_ids(pid) & ovids).present?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def product_ids
|
|
18
|
+
preferred_eligible_values.keys
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def value_ids(product_id)
|
|
22
|
+
preferred_eligible_values[product_id]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
# A condition to apply a promotion only to prices with or without selected products
|
|
6
|
+
class PriceProduct < Condition
|
|
7
|
+
include ProductCondition
|
|
8
|
+
|
|
9
|
+
MATCH_POLICIES = %w[include exclude].freeze
|
|
10
|
+
|
|
11
|
+
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
12
|
+
|
|
13
|
+
def price_eligible?(price, _options = {})
|
|
14
|
+
price_matches_products = products.include?(price.variant.product)
|
|
15
|
+
success = exclude_configured_products? ? !price_matches_products : price_matches_products
|
|
16
|
+
|
|
17
|
+
unless success
|
|
18
|
+
message_code = exclude_configured_products? ? :has_excluded_product : :no_applicable_products
|
|
19
|
+
eligibility_errors.add(
|
|
20
|
+
:base,
|
|
21
|
+
eligibility_error_message(message_code),
|
|
22
|
+
error_code: message_code
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
success
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def exclude_configured_products?
|
|
32
|
+
preferred_match_policy == "exclude"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
class PriceTaxon < Condition
|
|
6
|
+
include TaxonCondition
|
|
7
|
+
|
|
8
|
+
MATCH_POLICIES = %w[include exclude].freeze
|
|
9
|
+
|
|
10
|
+
validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES}
|
|
11
|
+
|
|
12
|
+
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
13
|
+
|
|
14
|
+
def price_eligible?(price, _options = {})
|
|
15
|
+
price_taxon_ids = price.variant.product.classifications.map(&:taxon_id)
|
|
16
|
+
|
|
17
|
+
case preferred_match_policy
|
|
18
|
+
when "include"
|
|
19
|
+
taxon_ids_with_children.any? { |taxon_and_descendant_ids| (price_taxon_ids & taxon_and_descendant_ids).any? }
|
|
20
|
+
when "exclude"
|
|
21
|
+
taxon_ids_with_children.none? { |taxon_and_descendant_ids| (price_taxon_ids & taxon_and_descendant_ids).any? }
|
|
22
|
+
else
|
|
23
|
+
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -9,77 +9,35 @@ module SolidusPromotions
|
|
|
9
9
|
class Product < Condition
|
|
10
10
|
include LineItemApplicableOrderLevelCondition
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
dependent: :destroy,
|
|
14
|
-
foreign_key: :condition_id,
|
|
15
|
-
class_name: "SolidusPromotions::ConditionProduct",
|
|
16
|
-
inverse_of: :condition
|
|
17
|
-
has_many :products, class_name: "Spree::Product", through: :condition_products
|
|
18
|
-
|
|
19
|
-
def preload_relations
|
|
20
|
-
[:products]
|
|
21
|
-
end
|
|
12
|
+
include ProductCondition
|
|
22
13
|
|
|
23
14
|
MATCH_POLICIES = %w[any all none only].freeze
|
|
24
15
|
|
|
25
|
-
validates :preferred_match_policy, inclusion: {
|
|
16
|
+
validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES}
|
|
26
17
|
|
|
27
18
|
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def order_eligible?(order)
|
|
35
|
-
return true if eligible_products.empty?
|
|
36
|
-
|
|
37
|
-
case preferred_match_policy
|
|
38
|
-
when "all"
|
|
39
|
-
unless eligible_products.all? { |product| order_products(order).include?(product) }
|
|
40
|
-
eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product)
|
|
41
|
-
end
|
|
42
|
-
when "any"
|
|
43
|
-
unless order_products(order).any? { |product| eligible_products.include?(product) }
|
|
44
|
-
eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products),
|
|
45
|
-
error_code: :no_applicable_products)
|
|
46
|
-
end
|
|
47
|
-
when "none"
|
|
48
|
-
unless order_products(order).none? { |product| eligible_products.include?(product) }
|
|
49
|
-
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
|
|
50
|
-
error_code: :has_excluded_product)
|
|
51
|
-
end
|
|
52
|
-
when "only"
|
|
53
|
-
unless order_products(order).all? { |product| eligible_products.include?(product) }
|
|
54
|
-
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
|
|
55
|
-
error_code: :has_excluded_product)
|
|
56
|
-
end
|
|
57
|
-
else
|
|
58
|
-
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
|
59
|
-
end
|
|
60
|
-
|
|
20
|
+
def order_eligible?(order, _options = {})
|
|
21
|
+
order_condition = OrderProduct.new(products:, preferred_match_policy:)
|
|
22
|
+
order_condition.order_eligible?(order)
|
|
23
|
+
@eligibility_errors = order_condition.eligibility_errors
|
|
61
24
|
eligibility_errors.empty?
|
|
62
25
|
end
|
|
63
26
|
|
|
64
27
|
def line_item_eligible?(line_item, _options = {})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def product_ids_string
|
|
72
|
-
product_ids.join(",")
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def product_ids_string=(product_ids)
|
|
76
|
-
self.product_ids = product_ids.to_s.split(",").map(&:strip)
|
|
28
|
+
line_item_match_policy = preferred_match_policy.in?(%w[any all only]) ? "include" : "exclude"
|
|
29
|
+
line_item_condition = LineItemProduct.new(products:, preferred_match_policy: line_item_match_policy)
|
|
30
|
+
line_item_condition.line_item_eligible?(line_item)
|
|
31
|
+
@eligibility_errors = line_item_condition.eligibility_errors
|
|
32
|
+
eligibility_errors.empty?
|
|
77
33
|
end
|
|
78
34
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
35
|
+
def price_eligible?(price, _options = {})
|
|
36
|
+
price_match_policy = preferred_match_policy.in?(%w[any all only]) ? "include" : "exclude"
|
|
37
|
+
price_condition = PriceProduct.new(products:, preferred_match_policy: price_match_policy)
|
|
38
|
+
price_condition.price_eligible?(price)
|
|
39
|
+
@eligibility_errors = price_condition.eligibility_errors
|
|
40
|
+
eligibility_errors.empty?
|
|
83
41
|
end
|
|
84
42
|
end
|
|
85
43
|
end
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
module SolidusPromotions
|
|
4
4
|
module Conditions
|
|
5
5
|
class ShippingMethod < Condition
|
|
6
|
+
# TODO: Remove in Solidus 5
|
|
6
7
|
include ShipmentLevelCondition
|
|
7
8
|
|
|
8
9
|
preference :shipping_method_ids, type: :array, default: []
|
|
9
10
|
|
|
10
|
-
def
|
|
11
|
-
promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def eligible?(promotable)
|
|
11
|
+
def shipment_eligible?(promotable, _options = {})
|
|
15
12
|
promotable.shipping_method&.id&.in?(preferred_shipping_method_ids.map(&:to_i))
|
|
16
13
|
end
|
|
14
|
+
alias_method :shipping_rate_eligible?, :shipment_eligible?
|
|
17
15
|
end
|
|
18
16
|
end
|
|
19
17
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module SolidusPromotions
|
|
4
4
|
module Conditions
|
|
5
5
|
class Store < Condition
|
|
6
|
+
# TODO: Remove in Solidus 5
|
|
6
7
|
include OrderLevelCondition
|
|
7
8
|
|
|
8
9
|
has_many :condition_stores,
|
|
@@ -16,7 +17,7 @@ module SolidusPromotions
|
|
|
16
17
|
[:stores]
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
def
|
|
20
|
+
def order_eligible?(order, _options = {})
|
|
20
21
|
stores.none? || stores.include?(order.store)
|
|
21
22
|
end
|
|
22
23
|
|
|
@@ -5,96 +5,47 @@ module SolidusPromotions
|
|
|
5
5
|
class Taxon < Condition
|
|
6
6
|
include LineItemApplicableOrderLevelCondition
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
class_name: "SolidusPromotions::ConditionTaxon",
|
|
10
|
-
foreign_key: :condition_id,
|
|
11
|
-
dependent: :destroy,
|
|
12
|
-
inverse_of: :condition
|
|
13
|
-
has_many :taxons, through: :condition_taxons, class_name: "Spree::Taxon"
|
|
14
|
-
|
|
15
|
-
def preload_relations
|
|
16
|
-
[:taxons]
|
|
17
|
-
end
|
|
8
|
+
include TaxonCondition
|
|
18
9
|
|
|
19
10
|
MATCH_POLICIES = %w[any all none].freeze
|
|
20
11
|
|
|
21
|
-
validates :preferred_match_policy, inclusion: {
|
|
12
|
+
validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES}
|
|
22
13
|
|
|
23
14
|
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
24
15
|
|
|
25
|
-
def order_eligible?(order)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
case preferred_match_policy
|
|
29
|
-
when "all"
|
|
30
|
-
matches_all = taxons.all? do |condition_taxon|
|
|
31
|
-
order_taxons.where(id: condition_taxon.self_and_descendants.ids).exists?
|
|
32
|
-
end
|
|
16
|
+
def order_eligible?(order, _options = {})
|
|
17
|
+
order_condition = OrderTaxon.new(taxons:, preferred_match_policy:)
|
|
33
18
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
37
|
-
when "any"
|
|
38
|
-
unless order_taxons.where(id: condition_taxon_ids_with_children).exists?
|
|
39
|
-
eligibility_errors.add(
|
|
40
|
-
:base,
|
|
41
|
-
eligibility_error_message(:no_matching_taxons),
|
|
42
|
-
error_code: :no_matching_taxons
|
|
43
|
-
)
|
|
44
|
-
end
|
|
45
|
-
when "none"
|
|
46
|
-
if order_taxons.where(id: condition_taxon_ids_with_children).exists?
|
|
47
|
-
eligibility_errors.add(
|
|
48
|
-
:base,
|
|
49
|
-
eligibility_error_message(:has_excluded_taxon),
|
|
50
|
-
error_code: :has_excluded_taxon
|
|
51
|
-
)
|
|
52
|
-
end
|
|
53
|
-
else
|
|
54
|
-
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
|
55
|
-
end
|
|
19
|
+
# Hydrate the instance cache with our @taxon_ids_with_children cache
|
|
20
|
+
order_condition.taxons_ids_with_children = taxon_ids_with_children
|
|
56
21
|
|
|
22
|
+
order_condition.order_eligible?(order)
|
|
23
|
+
@eligibility_errors = order_condition.eligibility_errors
|
|
57
24
|
eligibility_errors.empty?
|
|
58
25
|
end
|
|
59
26
|
|
|
60
|
-
def line_item_eligible?(line_item)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
raise "This should not happen" if preferred_match_policy == "none"
|
|
64
|
-
|
|
65
|
-
raise "unexpected match policy: #{preferred_match_policy.inspect}" unless preferred_match_policy.in?(MATCH_POLICIES)
|
|
66
|
-
|
|
67
|
-
Spree::Classification.where(
|
|
68
|
-
product_id: line_item.variant.product_id,
|
|
69
|
-
taxon_id: condition_taxon_ids_with_children
|
|
70
|
-
).exists?
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def taxon_ids_string
|
|
74
|
-
taxon_ids.join(",")
|
|
75
|
-
end
|
|
27
|
+
def line_item_eligible?(line_item, _options = {})
|
|
28
|
+
line_item_match_policy = preferred_match_policy.in?(%w[any all]) ? "include" : "exclude"
|
|
29
|
+
line_item_condition = LineItemTaxon.new(taxons:, preferred_match_policy: line_item_match_policy)
|
|
76
30
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
31
|
+
# Hydrate the instance cache with our @taxon_ids_with_children cache\
|
|
32
|
+
line_item_condition.taxons_ids_with_children = taxon_ids_with_children
|
|
80
33
|
|
|
81
|
-
|
|
82
|
-
|
|
34
|
+
result = line_item_condition.line_item_eligible?(line_item)
|
|
35
|
+
@eligibility_errors = line_item_condition.eligibility_errors
|
|
36
|
+
result
|
|
83
37
|
end
|
|
84
38
|
|
|
85
|
-
|
|
39
|
+
def price_eligible?(price, _options = {})
|
|
40
|
+
price_match_policy = preferred_match_policy.in?(%w[any all]) ? "include" : "exclude"
|
|
41
|
+
price_condition = PriceTaxon.new(taxons:, preferred_match_policy: price_match_policy)
|
|
86
42
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Spree::Taxon
|
|
90
|
-
.joins(products: { variants_including_master: :line_items })
|
|
91
|
-
.where(spree_line_items: { order_id: order.id })
|
|
92
|
-
.distinct
|
|
93
|
-
end
|
|
43
|
+
# Hydrate the instance cache with our @taxon_ids_with_children cache
|
|
44
|
+
price_condition.taxons_ids_with_children = taxon_ids_with_children
|
|
94
45
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
46
|
+
result = price_condition.price_eligible?(price)
|
|
47
|
+
@eligibility_errors = price_condition.eligibility_errors
|
|
48
|
+
result
|
|
98
49
|
end
|
|
99
50
|
end
|
|
100
51
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module SolidusPromotions
|
|
4
4
|
module Conditions
|
|
5
5
|
class User < Condition
|
|
6
|
+
# TODO: Remove in Solidus 5
|
|
6
7
|
include OrderLevelCondition
|
|
7
8
|
|
|
8
9
|
has_many :condition_users,
|
|
@@ -16,7 +17,7 @@ module SolidusPromotions
|
|
|
16
17
|
[:users]
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
def
|
|
20
|
+
def order_eligible?(order, _options = {})
|
|
20
21
|
users.include?(order.user)
|
|
21
22
|
end
|
|
22
23
|
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
module SolidusPromotions
|
|
4
4
|
module Conditions
|
|
5
5
|
class UserLoggedIn < Condition
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def eligible?(order, _options = {})
|
|
6
|
+
def order_eligible?(order, _options = {})
|
|
9
7
|
if order.user.blank?
|
|
10
8
|
eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified)
|
|
11
9
|
end
|
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
module SolidusPromotions
|
|
4
4
|
module Conditions
|
|
5
5
|
class UserRole < Condition
|
|
6
|
-
include OrderLevelCondition
|
|
7
|
-
|
|
8
6
|
preference :role_ids, :array, default: []
|
|
9
7
|
|
|
10
8
|
MATCH_POLICIES = %w[any all].freeze
|
|
11
9
|
preference :match_policy, default: MATCH_POLICIES.first
|
|
12
10
|
|
|
13
|
-
def
|
|
11
|
+
def order_eligible?(order, _options = {})
|
|
14
12
|
return false unless order.user
|
|
15
13
|
|
|
16
14
|
if all_match_policy?
|
|
@@ -12,7 +12,7 @@ module SolidusPromotions
|
|
|
12
12
|
# @param line_item [LineItem] one of the line_items distributed over
|
|
13
13
|
# @return [BigDecimal] the weighted adjustment for this line_item
|
|
14
14
|
def amount(line_item)
|
|
15
|
-
distributed_amounts[line_item
|
|
15
|
+
distributed_amounts[line_item].to_d
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
private
|
|
@@ -21,11 +21,7 @@ module SolidusPromotions
|
|
|
21
21
|
# @return [Hash<Integer, BigDecimal>] a hash of line item IDs and their
|
|
22
22
|
# corresponding weighted adjustments
|
|
23
23
|
def distributed_amounts
|
|
24
|
-
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def line_item_ids
|
|
28
|
-
line_items.map(&:id)
|
|
24
|
+
line_items.zip(allocated_amounts).to_h
|
|
29
25
|
end
|
|
30
26
|
|
|
31
27
|
def elligible_amounts
|