spree_core 2.1.12 → 2.2.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.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +4 -7
  3. data/app/helpers/spree/products_helper.rb +11 -9
  4. data/app/helpers/spree/store_helper.rb +5 -0
  5. data/app/models/spree/ability.rb +4 -0
  6. data/app/models/spree/address.rb +6 -6
  7. data/app/models/spree/adjustment.rb +40 -61
  8. data/app/models/spree/app_configuration.rb +1 -14
  9. data/app/models/spree/calculator.rb +12 -4
  10. data/app/models/spree/calculator/default_tax.rb +42 -38
  11. data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
  12. data/app/models/spree/calculator/free_shipping.rb +5 -2
  13. data/app/models/spree/calculator/percent_on_line_item.rb +15 -0
  14. data/app/models/spree/calculator/percent_per_item.rb +3 -0
  15. data/app/models/spree/classification.rb +3 -2
  16. data/app/models/spree/credit_card.rb +7 -25
  17. data/app/models/spree/gateway/bogus.rb +5 -5
  18. data/app/models/spree/gateway/bogus_simple.rb +0 -8
  19. data/app/models/spree/image.rb +0 -9
  20. data/app/models/spree/inventory_unit.rb +10 -4
  21. data/app/models/spree/item_adjustments.rb +65 -0
  22. data/app/models/spree/legacy_user.rb +1 -0
  23. data/app/models/spree/line_item.rb +33 -13
  24. data/app/models/spree/option_type.rb +2 -2
  25. data/app/models/spree/option_value.rb +1 -1
  26. data/app/models/spree/order.rb +109 -89
  27. data/app/models/spree/order/checkout.rb +48 -0
  28. data/app/models/spree/order_contents.rb +72 -37
  29. data/app/models/spree/order_inventory.rb +65 -68
  30. data/app/models/spree/order_populator.rb +3 -17
  31. data/app/models/spree/order_updater.rb +63 -44
  32. data/app/models/spree/payment.rb +20 -5
  33. data/app/models/spree/payment/processing.rb +19 -25
  34. data/app/models/spree/payment_capture_event.rb +9 -0
  35. data/app/models/spree/payment_method/check.rb +0 -2
  36. data/app/models/spree/price.rb +1 -1
  37. data/app/models/spree/product.rb +14 -16
  38. data/app/models/spree/product/scopes.rb +4 -6
  39. data/app/models/spree/product_option_type.rb +2 -2
  40. data/app/models/spree/product_property.rb +2 -2
  41. data/app/models/spree/promotion.rb +71 -50
  42. data/app/models/spree/promotion/actions/create_adjustment.rb +31 -32
  43. data/app/models/spree/promotion/actions/create_item_adjustments.rb +83 -0
  44. data/app/models/spree/promotion/actions/free_shipping.rb +36 -0
  45. data/app/models/spree/promotion/rules/first_order.rb +4 -0
  46. data/app/models/spree/promotion/rules/item_total.rb +5 -1
  47. data/app/models/spree/promotion/rules/product.rb +4 -0
  48. data/app/models/spree/promotion/rules/user.rb +5 -6
  49. data/app/models/spree/promotion/rules/user_logged_in.rb +4 -0
  50. data/app/models/spree/promotion_action.rb +1 -5
  51. data/app/models/spree/promotion_handler/cart.rb +38 -0
  52. data/app/models/spree/promotion_handler/coupon.rb +76 -0
  53. data/app/models/spree/promotion_handler/free_shipping.rb +31 -0
  54. data/app/models/spree/promotion_handler/page.rb +24 -0
  55. data/app/models/spree/promotion_rule.rb +15 -7
  56. data/app/models/spree/property.rb +1 -1
  57. data/app/models/spree/return_authorization.rb +7 -1
  58. data/app/models/spree/shipment.rb +113 -49
  59. data/app/models/spree/shipping_calculator.rb +4 -5
  60. data/app/models/spree/shipping_category.rb +2 -2
  61. data/app/models/spree/shipping_method.rb +12 -6
  62. data/app/models/spree/shipping_rate.rb +27 -7
  63. data/app/models/spree/stock/availability_validator.rb +1 -1
  64. data/app/models/spree/stock/estimator.rb +13 -1
  65. data/app/models/spree/stock/package.rb +11 -7
  66. data/app/models/spree/stock/packer.rb +3 -3
  67. data/app/models/spree/stock/quantifier.rb +9 -1
  68. data/app/models/spree/stock_item.rb +11 -6
  69. data/app/models/spree/stock_movement.rb +1 -2
  70. data/app/models/spree/tax_category.rb +6 -1
  71. data/app/models/spree/tax_rate.rb +57 -49
  72. data/app/models/spree/taxon.rb +10 -5
  73. data/app/models/spree/taxonomy.rb +5 -2
  74. data/app/models/spree/variant.rb +33 -16
  75. data/app/models/spree/zone.rb +24 -24
  76. data/app/views/spree/shared/_routes.html.erb +3 -0
  77. data/config/locales/en.yml +42 -26
  78. data/db/migrate/20130213191427_create_default_stock.rb +3 -3
  79. data/db/migrate/20130413230529_add_name_to_spree_credit_cards.rb +5 -0
  80. data/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb +13 -0
  81. data/db/migrate/20130417120035_update_adjustment_states.rb +2 -2
  82. data/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb +1 -1
  83. data/db/migrate/20130509115210_add_number_to_stock_transfer.rb +1 -1
  84. data/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb +0 -5
  85. data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +7 -5
  86. data/db/migrate/20130807024301_upgrade_adjustments.rb +39 -0
  87. data/db/migrate/20130807024302_rename_adjustment_fields.rb +17 -0
  88. data/db/migrate/20130813004002_add_shipment_total_to_spree_orders.rb +5 -0
  89. data/db/migrate/20130813232134_rename_activators_to_promotions.rb +5 -0
  90. data/db/migrate/20130815000406_add_adjustment_total_to_line_items.rb +5 -0
  91. data/db/migrate/20130815024413_add_adjustment_total_to_shipments.rb +5 -0
  92. data/db/migrate/20130828234942_add_tax_total_to_line_items_shipments_and_orders.rb +8 -0
  93. data/db/migrate/20130830001159_migrate_old_shipping_calculators.rb +1 -1
  94. data/db/migrate/20130903183026_add_code_to_spree_promotion_rules.rb +5 -0
  95. data/db/migrate/20130917024658_remove_promotions_event_name_field.rb +5 -0
  96. data/db/migrate/20130924040529_add_promo_total_to_line_items_and_shipments_and_orders.rb +7 -0
  97. data/db/migrate/20131001013410_remove_unused_credit_card_fields.rb +7 -3
  98. data/db/migrate/20131107132123_add_tax_category_to_variants.rb +6 -0
  99. data/db/migrate/20131118043959_add_included_to_adjustments.rb +5 -0
  100. data/db/migrate/20131118050234_rename_tax_total_fields.rb +11 -0
  101. data/db/migrate/20131118183431_add_line_item_id_to_spree_inventory_units.rb +21 -0
  102. data/db/migrate/20131127001002_add_position_to_classifications.rb +5 -0
  103. data/db/migrate/20131211112807_create_spree_orders_promotions.rb +8 -0
  104. data/db/migrate/20131218054603_add_item_count_to_spree_orders.rb +5 -0
  105. data/db/migrate/20140106224208_rename_permalink_to_slug_for_products.rb +5 -0
  106. data/db/migrate/20140124023232_rename_activator_id_in_rules_and_actions_to_promotion_id.rb +6 -0
  107. data/db/migrate/20140203161722_add_approver_id_and_approved_at_to_orders.rb +6 -0
  108. data/db/migrate/20140204115338_add_confirmation_delivered_to_spree_orders.rb +5 -0
  109. data/db/migrate/20140205120320_create_spree_payment_capture_events.rb +12 -0
  110. data/db/migrate/20140205144710_add_uncaptured_amount_to_payments.rb +5 -0
  111. data/db/migrate/20140207085910_add_tax_category_id_to_shipping_methods.rb +5 -0
  112. data/db/migrate/20140207093021_add_tax_rate_id_to_shipping_rates.rb +5 -0
  113. data/db/migrate/20140211040159_add_pre_tax_amount_to_line_items_and_shipments.rb +6 -0
  114. data/db/migrate/20140213184916_add_more_indexes.rb +13 -0
  115. data/db/migrate/20140219060952_add_considered_risky_to_orders.rb +5 -0
  116. data/lib/generators/spree/dummy/dummy_generator.rb +1 -6
  117. data/lib/generators/spree/install/install_generator.rb +6 -6
  118. data/lib/generators/spree/install/templates/{app/assets/javascripts/admin → vendor/assets/javascripts/spree/backend}/all.js +3 -3
  119. data/lib/generators/spree/install/templates/{app/assets/javascripts/store → vendor/assets/javascripts/spree/frontend}/all.js +3 -3
  120. data/lib/generators/spree/install/templates/{app/assets/stylesheets/store → vendor/assets/stylesheets/spree/backend}/all.css +3 -3
  121. data/lib/generators/spree/install/templates/{app/assets/stylesheets/admin → vendor/assets/stylesheets/spree/frontend}/all.css +3 -3
  122. data/lib/spree/core.rb +21 -8
  123. data/lib/spree/core/calculated_adjustments.rb +0 -40
  124. data/lib/spree/core/controller_helpers.rb +5 -0
  125. data/lib/spree/core/controller_helpers/auth.rb +2 -2
  126. data/lib/spree/core/controller_helpers/common.rb +0 -5
  127. data/lib/spree/core/controller_helpers/order.rb +8 -9
  128. data/lib/spree/core/engine.rb +10 -17
  129. data/lib/spree/core/permalinks.rb +1 -1
  130. data/lib/spree/core/product_duplicator.rb +3 -8
  131. data/lib/spree/core/user_address.rb +1 -1
  132. data/lib/spree/core/validators/email.rb +23 -1
  133. data/lib/spree/core/version.rb +1 -1
  134. data/lib/spree/money.rb +1 -1
  135. data/lib/spree/permitted_attributes.rb +2 -2
  136. data/lib/spree/testing_support/caching.rb +47 -0
  137. data/lib/spree/testing_support/factories/adjustment_factory.rb +11 -2
  138. data/lib/spree/testing_support/factories/credit_card_factory.rb +2 -1
  139. data/lib/spree/testing_support/factories/order_factory.rb +10 -5
  140. data/lib/spree/testing_support/factories/payment_factory.rb +2 -2
  141. data/lib/spree/testing_support/factories/payment_method_factory.rb +3 -3
  142. data/lib/spree/testing_support/factories/promotion_factory.rb +16 -1
  143. data/lib/spree/testing_support/factories/shipment_factory.rb +8 -4
  144. data/lib/spree/testing_support/factories/shipping_method_factory.rb +1 -3
  145. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  146. data/lib/spree/testing_support/factories/tax_rate_factory.rb +2 -2
  147. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  148. data/lib/tasks/core.rake +2 -2
  149. data/vendor/assets/fonts/FontAwesome.otf +0 -0
  150. data/vendor/assets/fonts/fontawesome-webfont.eot +0 -0
  151. data/vendor/assets/fonts/fontawesome-webfont.svg +399 -0
  152. data/vendor/assets/fonts/fontawesome-webfont.ttf +0 -0
  153. data/vendor/assets/fonts/fontawesome-webfont.woff +0 -0
  154. data/vendor/assets/stylesheets/font-awesome.scss +1475 -0
  155. metadata +73 -44
  156. data/app/assets/javascripts/admin/handlebar_extensions.js +0 -9
  157. data/app/helpers/spree/admin/adjustments_helper.rb +0 -26
  158. data/app/helpers/spree/admin/images_helper.rb +0 -18
  159. data/app/helpers/spree/promotion_rules_helper.rb +0 -13
  160. data/app/models/spree/activator.rb +0 -29
  161. data/app/models/spree/calculator/per_item.rb +0 -41
  162. data/app/models/spree/stock/remaining_packer.rb +0 -22
  163. data/app/views/spree/payments/_payment.html.erb +0 -18
  164. data/db/migrate/20131118041203_add_tax_total_to_spree_orders.rb +0 -5
  165. data/db/migrate/20131118043021_add_order_id_to_spree_adjustments.rb +0 -6
  166. data/db/migrate/20131118074808_add_included_to_spree_adjustments.rb +0 -5
  167. data/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb +0 -5
  168. data/lib/spree/core/gateway_error.rb +0 -5
  169. data/lib/spree/core/preference_rescue.rb +0 -25
  170. data/lib/spree/core/s3_support.rb +0 -25
  171. data/lib/spree/promo/coupon_applicator.rb +0 -71
  172. 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: :originator
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 credit_exists_on_order?(order)
21
-
22
- self.create_adjustment("#{Spree.t(:promotion)} (#{promotion.name})", order, order)
23
- end
21
+ return if promotion_credit_exists?(order)
24
22
 
25
- # Override of CalculatedAdjustments#create_adjustment so promotional
26
- # adjustments are added all the time. They will get their eligibility
27
- # set to false if the amount is 0.
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
- source: calculable,
40
- originator: self,
41
- label: label,
42
- mandatory: mandatory
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.each do |adjustment|
61
- if adjustment.adjustable.complete?
62
- adjustment.originator = nil
63
- adjustment.save
64
- else
65
- adjustment.destroy
66
- end
67
- end
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
@@ -4,6 +4,10 @@ module Spree
4
4
  class FirstOrder < PromotionRule
5
5
  attr_reader :user, :email
6
6
 
7
+ def applicable?(promotable)
8
+ promotable.is_a?(Spree::Order)
9
+ end
10
+
7
11
  def eligible?(order, options = {})
8
12
  @user = order.try(:user) || options[:user]
9
13
  @email = order.email
@@ -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.line_items.map(&:amount).sum
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
@@ -16,6 +16,10 @@ module Spree
16
16
  products
17
17
  end
18
18
 
19
+ def applicable?(promotable)
20
+ promotable.is_a?(Spree::Order)
21
+ end
22
+
19
23
  def eligible?(order, options = {})
20
24
  return true if eligible_products.empty?
21
25
  if preferred_match_policy == 'all'
@@ -2,12 +2,11 @@ module Spree
2
2
  class Promotion
3
3
  module Rules
4
4
  class User < PromotionRule
5
- if Spree.user_class
6
- belongs_to :user, class_name: Spree.user_class.to_s
7
- has_and_belongs_to_many :users, class_name: Spree.user_class.to_s, join_table: 'spree_promotion_rules_users', foreign_key: 'promotion_rule_id'
8
- else
9
- belongs_to :user
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,6 +2,10 @@ module Spree
2
2
  class Promotion
3
3
  module Rules
4
4
  class UserLoggedIn < PromotionRule
5
+ def applicable?(promotable)
6
+ promotable.is_a?(Spree::Order)
7
+ end
8
+
5
9
  def eligible?(order, options = {})
6
10
  return order.user.present?
7
11
  end
@@ -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, foreign_key: 'activator_id', class_name: 'Spree::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