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.
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