spree_core 2.1.12 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/helpers/spree/base_helper.rb +4 -7
- data/app/helpers/spree/products_helper.rb +11 -9
- data/app/helpers/spree/store_helper.rb +5 -0
- data/app/models/spree/ability.rb +4 -0
- data/app/models/spree/address.rb +6 -6
- data/app/models/spree/adjustment.rb +40 -61
- data/app/models/spree/app_configuration.rb +1 -14
- data/app/models/spree/calculator.rb +12 -4
- data/app/models/spree/calculator/default_tax.rb +42 -38
- data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
- data/app/models/spree/calculator/free_shipping.rb +5 -2
- data/app/models/spree/calculator/percent_on_line_item.rb +15 -0
- data/app/models/spree/calculator/percent_per_item.rb +3 -0
- data/app/models/spree/classification.rb +3 -2
- data/app/models/spree/credit_card.rb +7 -25
- data/app/models/spree/gateway/bogus.rb +5 -5
- data/app/models/spree/gateway/bogus_simple.rb +0 -8
- data/app/models/spree/image.rb +0 -9
- data/app/models/spree/inventory_unit.rb +10 -4
- data/app/models/spree/item_adjustments.rb +65 -0
- data/app/models/spree/legacy_user.rb +1 -0
- data/app/models/spree/line_item.rb +33 -13
- data/app/models/spree/option_type.rb +2 -2
- data/app/models/spree/option_value.rb +1 -1
- data/app/models/spree/order.rb +109 -89
- data/app/models/spree/order/checkout.rb +48 -0
- data/app/models/spree/order_contents.rb +72 -37
- data/app/models/spree/order_inventory.rb +65 -68
- data/app/models/spree/order_populator.rb +3 -17
- data/app/models/spree/order_updater.rb +63 -44
- data/app/models/spree/payment.rb +20 -5
- data/app/models/spree/payment/processing.rb +19 -25
- data/app/models/spree/payment_capture_event.rb +9 -0
- data/app/models/spree/payment_method/check.rb +0 -2
- data/app/models/spree/price.rb +1 -1
- data/app/models/spree/product.rb +14 -16
- data/app/models/spree/product/scopes.rb +4 -6
- data/app/models/spree/product_option_type.rb +2 -2
- data/app/models/spree/product_property.rb +2 -2
- data/app/models/spree/promotion.rb +71 -50
- data/app/models/spree/promotion/actions/create_adjustment.rb +31 -32
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +83 -0
- data/app/models/spree/promotion/actions/free_shipping.rb +36 -0
- data/app/models/spree/promotion/rules/first_order.rb +4 -0
- data/app/models/spree/promotion/rules/item_total.rb +5 -1
- data/app/models/spree/promotion/rules/product.rb +4 -0
- data/app/models/spree/promotion/rules/user.rb +5 -6
- data/app/models/spree/promotion/rules/user_logged_in.rb +4 -0
- data/app/models/spree/promotion_action.rb +1 -5
- data/app/models/spree/promotion_handler/cart.rb +38 -0
- data/app/models/spree/promotion_handler/coupon.rb +76 -0
- data/app/models/spree/promotion_handler/free_shipping.rb +31 -0
- data/app/models/spree/promotion_handler/page.rb +24 -0
- data/app/models/spree/promotion_rule.rb +15 -7
- data/app/models/spree/property.rb +1 -1
- data/app/models/spree/return_authorization.rb +7 -1
- data/app/models/spree/shipment.rb +113 -49
- data/app/models/spree/shipping_calculator.rb +4 -5
- data/app/models/spree/shipping_category.rb +2 -2
- data/app/models/spree/shipping_method.rb +12 -6
- data/app/models/spree/shipping_rate.rb +27 -7
- data/app/models/spree/stock/availability_validator.rb +1 -1
- data/app/models/spree/stock/estimator.rb +13 -1
- data/app/models/spree/stock/package.rb +11 -7
- data/app/models/spree/stock/packer.rb +3 -3
- data/app/models/spree/stock/quantifier.rb +9 -1
- data/app/models/spree/stock_item.rb +11 -6
- data/app/models/spree/stock_movement.rb +1 -2
- data/app/models/spree/tax_category.rb +6 -1
- data/app/models/spree/tax_rate.rb +57 -49
- data/app/models/spree/taxon.rb +10 -5
- data/app/models/spree/taxonomy.rb +5 -2
- data/app/models/spree/variant.rb +33 -16
- data/app/models/spree/zone.rb +24 -24
- data/app/views/spree/shared/_routes.html.erb +3 -0
- data/config/locales/en.yml +42 -26
- data/db/migrate/20130213191427_create_default_stock.rb +3 -3
- data/db/migrate/20130413230529_add_name_to_spree_credit_cards.rb +5 -0
- data/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb +13 -0
- data/db/migrate/20130417120035_update_adjustment_states.rb +2 -2
- data/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb +1 -1
- data/db/migrate/20130509115210_add_number_to_stock_transfer.rb +1 -1
- data/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb +0 -5
- data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +7 -5
- data/db/migrate/20130807024301_upgrade_adjustments.rb +39 -0
- data/db/migrate/20130807024302_rename_adjustment_fields.rb +17 -0
- data/db/migrate/20130813004002_add_shipment_total_to_spree_orders.rb +5 -0
- data/db/migrate/20130813232134_rename_activators_to_promotions.rb +5 -0
- data/db/migrate/20130815000406_add_adjustment_total_to_line_items.rb +5 -0
- data/db/migrate/20130815024413_add_adjustment_total_to_shipments.rb +5 -0
- data/db/migrate/20130828234942_add_tax_total_to_line_items_shipments_and_orders.rb +8 -0
- data/db/migrate/20130830001159_migrate_old_shipping_calculators.rb +1 -1
- data/db/migrate/20130903183026_add_code_to_spree_promotion_rules.rb +5 -0
- data/db/migrate/20130917024658_remove_promotions_event_name_field.rb +5 -0
- data/db/migrate/20130924040529_add_promo_total_to_line_items_and_shipments_and_orders.rb +7 -0
- data/db/migrate/20131001013410_remove_unused_credit_card_fields.rb +7 -3
- data/db/migrate/20131107132123_add_tax_category_to_variants.rb +6 -0
- data/db/migrate/20131118043959_add_included_to_adjustments.rb +5 -0
- data/db/migrate/20131118050234_rename_tax_total_fields.rb +11 -0
- data/db/migrate/20131118183431_add_line_item_id_to_spree_inventory_units.rb +21 -0
- data/db/migrate/20131127001002_add_position_to_classifications.rb +5 -0
- data/db/migrate/20131211112807_create_spree_orders_promotions.rb +8 -0
- data/db/migrate/20131218054603_add_item_count_to_spree_orders.rb +5 -0
- data/db/migrate/20140106224208_rename_permalink_to_slug_for_products.rb +5 -0
- data/db/migrate/20140124023232_rename_activator_id_in_rules_and_actions_to_promotion_id.rb +6 -0
- data/db/migrate/20140203161722_add_approver_id_and_approved_at_to_orders.rb +6 -0
- data/db/migrate/20140204115338_add_confirmation_delivered_to_spree_orders.rb +5 -0
- data/db/migrate/20140205120320_create_spree_payment_capture_events.rb +12 -0
- data/db/migrate/20140205144710_add_uncaptured_amount_to_payments.rb +5 -0
- data/db/migrate/20140207085910_add_tax_category_id_to_shipping_methods.rb +5 -0
- data/db/migrate/20140207093021_add_tax_rate_id_to_shipping_rates.rb +5 -0
- data/db/migrate/20140211040159_add_pre_tax_amount_to_line_items_and_shipments.rb +6 -0
- data/db/migrate/20140213184916_add_more_indexes.rb +13 -0
- data/db/migrate/20140219060952_add_considered_risky_to_orders.rb +5 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -6
- data/lib/generators/spree/install/install_generator.rb +6 -6
- data/lib/generators/spree/install/templates/{app/assets/javascripts/admin → vendor/assets/javascripts/spree/backend}/all.js +3 -3
- data/lib/generators/spree/install/templates/{app/assets/javascripts/store → vendor/assets/javascripts/spree/frontend}/all.js +3 -3
- data/lib/generators/spree/install/templates/{app/assets/stylesheets/store → vendor/assets/stylesheets/spree/backend}/all.css +3 -3
- data/lib/generators/spree/install/templates/{app/assets/stylesheets/admin → vendor/assets/stylesheets/spree/frontend}/all.css +3 -3
- data/lib/spree/core.rb +21 -8
- data/lib/spree/core/calculated_adjustments.rb +0 -40
- data/lib/spree/core/controller_helpers.rb +5 -0
- data/lib/spree/core/controller_helpers/auth.rb +2 -2
- data/lib/spree/core/controller_helpers/common.rb +0 -5
- data/lib/spree/core/controller_helpers/order.rb +8 -9
- data/lib/spree/core/engine.rb +10 -17
- data/lib/spree/core/permalinks.rb +1 -1
- data/lib/spree/core/product_duplicator.rb +3 -8
- data/lib/spree/core/user_address.rb +1 -1
- data/lib/spree/core/validators/email.rb +23 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/money.rb +1 -1
- data/lib/spree/permitted_attributes.rb +2 -2
- data/lib/spree/testing_support/caching.rb +47 -0
- data/lib/spree/testing_support/factories/adjustment_factory.rb +11 -2
- data/lib/spree/testing_support/factories/credit_card_factory.rb +2 -1
- data/lib/spree/testing_support/factories/order_factory.rb +10 -5
- data/lib/spree/testing_support/factories/payment_factory.rb +2 -2
- data/lib/spree/testing_support/factories/payment_method_factory.rb +3 -3
- data/lib/spree/testing_support/factories/promotion_factory.rb +16 -1
- data/lib/spree/testing_support/factories/shipment_factory.rb +8 -4
- data/lib/spree/testing_support/factories/shipping_method_factory.rb +1 -3
- data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
- data/lib/spree/testing_support/factories/tax_rate_factory.rb +2 -2
- data/lib/spree/testing_support/order_walkthrough.rb +1 -1
- data/lib/tasks/core.rake +2 -2
- data/vendor/assets/fonts/FontAwesome.otf +0 -0
- data/vendor/assets/fonts/fontawesome-webfont.eot +0 -0
- data/vendor/assets/fonts/fontawesome-webfont.svg +399 -0
- data/vendor/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/vendor/assets/fonts/fontawesome-webfont.woff +0 -0
- data/vendor/assets/stylesheets/font-awesome.scss +1475 -0
- metadata +73 -44
- data/app/assets/javascripts/admin/handlebar_extensions.js +0 -9
- data/app/helpers/spree/admin/adjustments_helper.rb +0 -26
- data/app/helpers/spree/admin/images_helper.rb +0 -18
- data/app/helpers/spree/promotion_rules_helper.rb +0 -13
- data/app/models/spree/activator.rb +0 -29
- data/app/models/spree/calculator/per_item.rb +0 -41
- data/app/models/spree/stock/remaining_packer.rb +0 -22
- data/app/views/spree/payments/_payment.html.erb +0 -18
- data/db/migrate/20131118041203_add_tax_total_to_spree_orders.rb +0 -5
- data/db/migrate/20131118043021_add_order_id_to_spree_adjustments.rb +0 -6
- data/db/migrate/20131118074808_add_included_to_spree_adjustments.rb +0 -5
- data/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb +0 -5
- data/lib/spree/core/gateway_error.rb +0 -5
- data/lib/spree/core/preference_rescue.rb +0 -25
- data/lib/spree/core/s3_support.rb +0 -25
- data/lib/spree/promo/coupon_applicator.rb +0 -71
- data/lib/spree/testing_support/factories/activator_factory.rb +0 -8
@@ -1,12 +1,10 @@
|
|
1
1
|
module Spree
|
2
2
|
class Promotion
|
3
3
|
module Actions
|
4
|
-
# Responsible for the creation and management of an adjustment since an
|
5
|
-
# an adjustment uses its originator to also update its eligiblity and amount
|
6
4
|
class CreateAdjustment < PromotionAction
|
7
5
|
include Spree::Core::CalculatedAdjustments
|
8
6
|
|
9
|
-
has_many :adjustments, as: :
|
7
|
+
has_many :adjustments, as: :source
|
10
8
|
|
11
9
|
delegate :eligible?, to: :promotion
|
12
10
|
|
@@ -15,32 +13,23 @@ module Spree
|
|
15
13
|
|
16
14
|
# Creates the adjustment related to a promotion for the order passed
|
17
15
|
# through options hash
|
16
|
+
#
|
17
|
+
# Returns `true` if an adjustment is applied to an order,
|
18
|
+
# `false` if the promotion has already been applied.
|
18
19
|
def perform(options = {})
|
19
20
|
order = options[:order]
|
20
|
-
return if
|
21
|
-
|
22
|
-
self.create_adjustment("#{Spree.t(:promotion)} (#{promotion.name})", order, order)
|
23
|
-
end
|
21
|
+
return if promotion_credit_exists?(order)
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
# Currently an adjustment is created even when its promotion is not eligible.
|
30
|
-
# This helps to figure out later which adjustments should be eligible
|
31
|
-
# as the order is being updated
|
32
|
-
#
|
33
|
-
# BTW The order is updated (through order#update) every time an adjustment
|
34
|
-
# is saved
|
35
|
-
def create_adjustment(label, target, calculable, mandatory=false)
|
36
|
-
amount = compute_amount(calculable)
|
37
|
-
target.adjustments.create(
|
23
|
+
amount = compute_amount(order)
|
24
|
+
return if amount == 0
|
25
|
+
Spree::Adjustment.create!(
|
38
26
|
amount: amount,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
27
|
+
order: order,
|
28
|
+
adjustable: order,
|
29
|
+
source: self,
|
30
|
+
label: "#{Spree.t(:promotion)} (#{promotion.name})"
|
43
31
|
)
|
32
|
+
true
|
44
33
|
end
|
45
34
|
|
46
35
|
# Ensure a negative amount which does not exceed the sum of the order's
|
@@ -51,20 +40,30 @@ module Spree
|
|
51
40
|
end
|
52
41
|
|
53
42
|
private
|
43
|
+
# Tells us if there if the specified promotion is already associated with the line item
|
44
|
+
# regardless of whether or not its currently eligible. Useful because generally
|
45
|
+
# you would only want a promotion action to apply to order no more than once.
|
46
|
+
#
|
47
|
+
# Receives an adjustment +source+ (here a PromotionAction object) and tells
|
48
|
+
# if the order has adjustments from that already
|
49
|
+
def promotion_credit_exists?(adjustable)
|
50
|
+
self.adjustments.where(:adjustable_id => adjustable.id).exists?
|
51
|
+
end
|
52
|
+
|
54
53
|
def ensure_action_has_calculator
|
55
54
|
return if self.calculator
|
56
55
|
self.calculator = Calculator::FlatPercentItemTotal.new
|
57
56
|
end
|
58
57
|
|
59
58
|
def deals_with_adjustments
|
60
|
-
self.adjustments.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
59
|
+
adjustment_scope = self.adjustments.joins("LEFT OUTER JOIN spree_orders ON spree_orders.id = spree_adjustments.adjustable_id")
|
60
|
+
# For incomplete orders, remove the adjustment completely.
|
61
|
+
adjustment_scope.where("spree_orders.completed_at IS NULL").readonly(false).destroy_all
|
62
|
+
|
63
|
+
# For complete orders, the source will be invalid.
|
64
|
+
# Therefore we nullify the source_id, leaving the adjustment in place.
|
65
|
+
# This would mean that the order's total is not altered at all.
|
66
|
+
adjustment_scope.where("spree_orders.completed_at IS NOT NULL").update_all("source_id = NULL")
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Spree
|
2
|
+
class Promotion
|
3
|
+
module Actions
|
4
|
+
class CreateItemAdjustments < PromotionAction
|
5
|
+
include Spree::Core::CalculatedAdjustments
|
6
|
+
|
7
|
+
has_many :adjustments, as: :source
|
8
|
+
|
9
|
+
delegate :eligible?, to: :promotion
|
10
|
+
|
11
|
+
before_validation :ensure_action_has_calculator
|
12
|
+
before_destroy :deals_with_adjustments
|
13
|
+
|
14
|
+
def perform(payload = {})
|
15
|
+
order = payload[:order]
|
16
|
+
# Find only the line items which have not already been adjusted by this promotion
|
17
|
+
# HACK: Need to use [0] because `pluck` may return an empty array, which AR helpfully
|
18
|
+
# coverts to meaning NOT IN (NULL) and the DB isn't happy about that.
|
19
|
+
already_adjusted_line_items = [0] + self.adjustments.pluck(:adjustable_id)
|
20
|
+
result = false
|
21
|
+
order.line_items.where("id NOT IN (?)", already_adjusted_line_items).find_each do |line_item|
|
22
|
+
current_result = self.create_adjustment(line_item, order)
|
23
|
+
result ||= current_result
|
24
|
+
end
|
25
|
+
return result
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_adjustment(adjustable, order)
|
29
|
+
amount = self.compute_amount(adjustable)
|
30
|
+
return if amount == 0
|
31
|
+
self.adjustments.create!(
|
32
|
+
amount: amount,
|
33
|
+
adjustable: adjustable,
|
34
|
+
order: order,
|
35
|
+
label: "#{Spree.t(:promotion)} (#{promotion.name})",
|
36
|
+
)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Ensure a negative amount which does not exceed the sum of the order's
|
41
|
+
# item_total and ship_total
|
42
|
+
def compute_amount(adjustable)
|
43
|
+
amount = self.calculator.compute(adjustable).to_f.abs
|
44
|
+
[adjustable.total, amount].min * -1
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
# Tells us if there if the specified promotion is already associated with the line item
|
49
|
+
# regardless of whether or not its currently eligible. Useful because generally
|
50
|
+
# you would only want a promotion action to apply to line item no more than once.
|
51
|
+
#
|
52
|
+
# Receives an adjustment +source+ (here a PromotionAction object) and tells
|
53
|
+
# if the order has adjustments from that already
|
54
|
+
def promotion_credit_exists?(adjustable)
|
55
|
+
self.adjustments.where(:adjustable_id => adjustable.id).exists?
|
56
|
+
end
|
57
|
+
|
58
|
+
def ensure_action_has_calculator
|
59
|
+
return if self.calculator
|
60
|
+
self.calculator = Calculator::PercentOnLineItem.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def deals_with_adjustments
|
64
|
+
adjustment_scope = Adjustment.includes(:order).references(:spree_orders)
|
65
|
+
# For incomplete orders, remove the adjustment completely.
|
66
|
+
adjustment_scope.where("spree_orders.completed_at IS NULL").each do |adjustment|
|
67
|
+
adjustment.destroy
|
68
|
+
end
|
69
|
+
|
70
|
+
# For complete orders, the source will be invalid.
|
71
|
+
# Therefore we nullify the source_id, leaving the adjustment in place.
|
72
|
+
# This would mean that the order's total is not altered at all.
|
73
|
+
adjustment_scope.where("spree_orders.completed_at IS NOT NULL").each do |adjustment|
|
74
|
+
adjustment.update_columns(
|
75
|
+
source_id: nil,
|
76
|
+
updated_at: Time.now,
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Spree
|
2
|
+
class Promotion
|
3
|
+
module Actions
|
4
|
+
class FreeShipping < Spree::PromotionAction
|
5
|
+
def perform(payload={})
|
6
|
+
order = payload[:order]
|
7
|
+
|
8
|
+
label = "#{Spree.t(:promotion)} (#{promotion.name})"
|
9
|
+
results = order.shipments.map do |shipment|
|
10
|
+
return false if promotion_credit_exists?(shipment)
|
11
|
+
shipment.adjustments.create!(
|
12
|
+
order: shipment.order,
|
13
|
+
amount: compute_amount(shipment),
|
14
|
+
source: self,
|
15
|
+
label: label,
|
16
|
+
)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
# Did we actually end up applying any adjustments?
|
20
|
+
# If so, then this action should be classed as 'successful'
|
21
|
+
results.any? { |r| r == true }
|
22
|
+
end
|
23
|
+
|
24
|
+
def compute_amount(shipment)
|
25
|
+
shipment.cost * -1
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def promotion_credit_exists?(shipment)
|
31
|
+
shipment.adjustments.where(:source_id => self.id).exists?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -9,8 +9,12 @@ module Spree
|
|
9
9
|
|
10
10
|
OPERATORS = ['gt', 'gte']
|
11
11
|
|
12
|
+
def applicable?(promotable)
|
13
|
+
promotable.is_a?(Spree::Order)
|
14
|
+
end
|
15
|
+
|
12
16
|
def eligible?(order, options = {})
|
13
|
-
item_total = order.
|
17
|
+
item_total = order.item_total
|
14
18
|
item_total.send(preferred_operator == 'gte' ? :>= : :>, BigDecimal.new(preferred_amount.to_s))
|
15
19
|
end
|
16
20
|
end
|
@@ -2,12 +2,11 @@ module Spree
|
|
2
2
|
class Promotion
|
3
3
|
module Rules
|
4
4
|
class User < PromotionRule
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
has_and_belongs_to_many :users, join_table: 'spree_promotion_rules_users', foreign_key: 'promotion_rule_id', :class_name => Spree.user_class
|
5
|
+
belongs_to :user, class_name: Spree.user_class
|
6
|
+
has_and_belongs_to_many :users, class_name: Spree.user_class, join_table: 'spree_promotion_rules_users', foreign_key: 'promotion_rule_id'
|
7
|
+
|
8
|
+
def applicable?(promotable)
|
9
|
+
promotable.is_a?(Spree::Order)
|
11
10
|
end
|
12
11
|
|
13
12
|
def eligible?(order, options = {})
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# PromotionActions perform the necessary tasks when a promotion is activated by an event and determined to be eligible.
|
3
3
|
module Spree
|
4
4
|
class PromotionAction < ActiveRecord::Base
|
5
|
-
belongs_to :promotion,
|
5
|
+
belongs_to :promotion, class_name: 'Spree::Promotion'
|
6
6
|
|
7
7
|
scope :of_type, ->(t) { where(type: t) }
|
8
8
|
|
@@ -13,9 +13,5 @@ module Spree
|
|
13
13
|
def perform(options = {})
|
14
14
|
raise 'perform should be implemented in a sub-class of PromotionAction'
|
15
15
|
end
|
16
|
-
|
17
|
-
def credit_exists_on_order?(order)
|
18
|
-
!!order.promotion_credit_exists?(self)
|
19
|
-
end
|
20
16
|
end
|
21
17
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Spree
|
2
|
+
module PromotionHandler
|
3
|
+
# Decides which promotion should be activated given the current order context
|
4
|
+
#
|
5
|
+
# By activated it doesn't necessarily mean that the order will have a
|
6
|
+
# discount for every activated promotion. It means that the discount will be
|
7
|
+
# created and might eventually become eligible. The intention here is to
|
8
|
+
# reduce overhead. e.g. a promotion that requires item A to be eligible
|
9
|
+
# shouldn't be eligible unless item A is added to the order.
|
10
|
+
#
|
11
|
+
# It can be used as a wrapper for custom handlers as well. Different
|
12
|
+
# applications might have completely different requirements to make
|
13
|
+
# the promotions system accurate and performant. Here they can plug custom
|
14
|
+
# handler to activate promos as they wish once an item is added to cart
|
15
|
+
class Cart
|
16
|
+
attr_reader :line_item, :order
|
17
|
+
attr_accessor :error, :success
|
18
|
+
|
19
|
+
def initialize(order, line_item=nil)
|
20
|
+
@order, @line_item = order, line_item
|
21
|
+
end
|
22
|
+
|
23
|
+
def activate
|
24
|
+
promotions.each do |promotion|
|
25
|
+
if (line_item && promotion.eligible?(line_item)) || promotion.eligible?(order)
|
26
|
+
promotion.activate(line_item: line_item, order: order)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def promotions
|
34
|
+
Promotion.active.includes(:promotion_rules).where(:code => nil, :path => nil)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Spree
|
2
|
+
module PromotionHandler
|
3
|
+
class Coupon
|
4
|
+
attr_reader :order
|
5
|
+
attr_accessor :error, :success
|
6
|
+
|
7
|
+
def initialize(order)
|
8
|
+
@order = order
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply
|
12
|
+
if order.coupon_code.present?
|
13
|
+
if promotion.present? && promotion.actions.exists?
|
14
|
+
handle_present_promotion(promotion)
|
15
|
+
else
|
16
|
+
if Promotion.with_coupon_code(order.coupon_code).try(:expired?)
|
17
|
+
self.error = Spree.t(:coupon_code_expired)
|
18
|
+
else
|
19
|
+
self.error = Spree.t(:coupon_code_not_found)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def promotion
|
28
|
+
@promotion ||= Promotion.active.includes(:promotion_rules, :promotion_actions).with_coupon_code(order.coupon_code)
|
29
|
+
end
|
30
|
+
|
31
|
+
def successful?
|
32
|
+
success.present? && error.blank?
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def handle_present_promotion(promotion)
|
38
|
+
return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded?(order)
|
39
|
+
return ineligible_for_this_order unless promotion.eligible?(order)
|
40
|
+
|
41
|
+
# If any of the actions for the promotion return `true`,
|
42
|
+
# then result here will also be `true`.
|
43
|
+
result = promotion.activate(:order => order)
|
44
|
+
if result
|
45
|
+
determine_promotion_application_result(result)
|
46
|
+
else
|
47
|
+
self.error = Spree.t(:coupon_code_already_applied)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def promotion_usage_limit_exceeded
|
52
|
+
self.error = Spree.t(:coupon_code_max_usage)
|
53
|
+
end
|
54
|
+
|
55
|
+
def ineligible_for_this_order
|
56
|
+
self.error = Spree.t(:coupon_code_not_eligible)
|
57
|
+
end
|
58
|
+
|
59
|
+
def determine_promotion_application_result(result)
|
60
|
+
detector = lambda { |p| p.source.promotion.code.downcase == order.coupon_code.downcase }
|
61
|
+
discount = order.line_item_adjustments.promotion.detect(&detector)
|
62
|
+
discount ||= order.shipment_adjustments.promotion.detect(&detector)
|
63
|
+
discount ||= order.adjustments.promotion.detect(&detector)
|
64
|
+
|
65
|
+
if result and discount.eligible
|
66
|
+
order.update_totals
|
67
|
+
order.persist_totals
|
68
|
+
self.success = Spree.t(:coupon_code_applied)
|
69
|
+
else
|
70
|
+
# if the promotion was created after the order
|
71
|
+
self.error = Spree.t(:coupon_code_not_found)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Spree
|
2
|
+
module PromotionHandler
|
3
|
+
# Used for activating promotions with shipping rules
|
4
|
+
class FreeShipping
|
5
|
+
attr_reader :order
|
6
|
+
attr_accessor :error, :success
|
7
|
+
|
8
|
+
def initialize(order)
|
9
|
+
@order = order
|
10
|
+
end
|
11
|
+
|
12
|
+
def activate
|
13
|
+
promotions.each do |promotion|
|
14
|
+
if promotion.eligible?(order)
|
15
|
+
promotion.activate(order: order)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def promotions
|
23
|
+
Spree::Promotion.active.where({
|
24
|
+
:id => Spree::Promotion::Actions::FreeShipping.pluck(:promotion_id),
|
25
|
+
:code => nil,
|
26
|
+
:path => nil
|
27
|
+
})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|