spree_core 2.3.13 → 2.4.0.rc1

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 (232) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spree.js.coffee.erb +1 -5
  3. data/app/helpers/spree/base_helper.rb +22 -11
  4. data/app/helpers/spree/products_helper.rb +8 -7
  5. data/app/mailers/spree/base_mailer.rb +1 -0
  6. data/app/mailers/spree/reimbursement_mailer.rb +10 -0
  7. data/app/mailers/spree/test_mailer.rb +2 -3
  8. data/app/models/concerns/spree/adjustment_source.rb +24 -0
  9. data/app/models/concerns/spree/calculated_adjustments.rb +33 -0
  10. data/app/models/concerns/spree/named_type.rb +12 -0
  11. data/app/models/concerns/spree/user_address.rb +30 -0
  12. data/app/models/concerns/spree/user_payment_source.rb +19 -0
  13. data/app/models/spree/address.rb +13 -6
  14. data/app/models/spree/adjustment.rb +5 -5
  15. data/app/models/spree/app_configuration.rb +8 -4
  16. data/app/models/spree/asset.rb +1 -1
  17. data/app/models/spree/base.rb +0 -3
  18. data/app/models/spree/calculator/flat_rate.rb +1 -5
  19. data/app/models/spree/calculator/returns/default_refund_amount.rb +36 -0
  20. data/app/models/spree/classification.rb +1 -1
  21. data/app/models/spree/credit_card.rb +18 -22
  22. data/app/models/spree/customer_return.rb +70 -0
  23. data/app/models/spree/exchange.rb +42 -0
  24. data/app/models/spree/gateway/bogus.rb +3 -3
  25. data/app/models/spree/image.rb +1 -1
  26. data/app/models/spree/inventory_unit.rb +32 -8
  27. data/app/models/spree/item_adjustments.rb +7 -11
  28. data/app/models/spree/legacy_user.rb +2 -2
  29. data/app/models/spree/line_item.rb +25 -12
  30. data/app/models/spree/option_type.rb +1 -1
  31. data/app/models/spree/option_value.rb +1 -8
  32. data/app/models/spree/order.rb +163 -145
  33. data/app/models/spree/order/checkout.rb +35 -23
  34. data/app/models/spree/order/payments.rb +66 -0
  35. data/app/models/spree/order_contents.rb +34 -24
  36. data/app/models/spree/order_populator.rb +6 -4
  37. data/app/models/spree/order_updater.rb +10 -1
  38. data/app/models/spree/payment.rb +19 -16
  39. data/app/models/spree/payment/processing.rb +40 -72
  40. data/app/models/spree/payment_method.rb +1 -1
  41. data/app/models/spree/payment_method/check.rb +0 -2
  42. data/app/models/spree/preference.rb +1 -1
  43. data/app/models/spree/preferences/preferable.rb +20 -0
  44. data/app/models/spree/price.rb +13 -3
  45. data/app/models/spree/product.rb +24 -29
  46. data/app/models/spree/product_property.rb +0 -2
  47. data/app/models/spree/promotion.rb +66 -24
  48. data/app/models/spree/promotion/actions/create_adjustment.rb +2 -2
  49. data/app/models/spree/promotion/actions/create_item_adjustments.rb +15 -11
  50. data/app/models/spree/promotion/actions/create_line_items.rb +2 -12
  51. data/app/models/spree/promotion/rules/first_order.rb +6 -2
  52. data/app/models/spree/promotion/rules/item_total.rb +42 -4
  53. data/app/models/spree/promotion/rules/one_use_per_user.rb +24 -0
  54. data/app/models/spree/promotion/rules/product.rb +13 -11
  55. data/app/models/spree/promotion/rules/taxon.rb +61 -0
  56. data/app/models/spree/promotion/rules/user.rb +1 -1
  57. data/app/models/spree/promotion/rules/user_logged_in.rb +4 -1
  58. data/app/models/spree/promotion_category.rb +6 -0
  59. data/app/models/spree/promotion_handler/cart.rb +14 -18
  60. data/app/models/spree/promotion_handler/coupon.rb +25 -16
  61. data/app/models/spree/promotion_rule.rb +13 -0
  62. data/app/models/spree/property.rb +1 -3
  63. data/app/models/spree/refund.rb +91 -0
  64. data/app/models/spree/refund_reason.rb +13 -0
  65. data/app/models/spree/reimbursement.rb +148 -0
  66. data/app/models/spree/reimbursement/credit.rb +25 -0
  67. data/app/models/spree/reimbursement/reimbursement_type_engine.rb +56 -0
  68. data/app/models/spree/reimbursement/reimbursement_type_validator.rb +12 -0
  69. data/app/models/spree/reimbursement_performer.rb +43 -0
  70. data/app/models/spree/reimbursement_tax_calculator.rb +38 -0
  71. data/app/models/spree/reimbursement_type.rb +16 -0
  72. data/app/models/spree/reimbursement_type/credit.rb +13 -0
  73. data/app/models/spree/reimbursement_type/exchange.rb +9 -0
  74. data/app/models/spree/reimbursement_type/original_payment.rb +13 -0
  75. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +50 -0
  76. data/app/models/spree/return_authorization.rb +52 -68
  77. data/app/models/spree/return_authorization_reason.rb +7 -0
  78. data/app/models/spree/return_item.rb +230 -0
  79. data/app/models/spree/return_item/default_eligibility_validator.rb +27 -0
  80. data/app/models/spree/return_item/eligibility_validator/base_validator.rb +24 -0
  81. data/app/models/spree/return_item/eligibility_validator/rma_required.rb +17 -0
  82. data/app/models/spree/return_item/eligibility_validator/time_since_purchase.rb +16 -0
  83. data/app/models/spree/return_item/exchange_variant_eligibility/same_option_value.rb +34 -0
  84. data/app/models/spree/return_item/exchange_variant_eligibility/same_product.rb +10 -0
  85. data/app/models/spree/returns_calculator.rb +8 -0
  86. data/app/models/spree/shipment.rb +209 -154
  87. data/app/models/spree/shipment_handler.rb +43 -0
  88. data/app/models/spree/shipping_calculator.rb +1 -1
  89. data/app/models/spree/shipping_category.rb +2 -2
  90. data/app/models/spree/shipping_method.rb +1 -1
  91. data/app/models/spree/shipping_method_category.rb +1 -1
  92. data/app/models/spree/shipping_rate.rb +4 -0
  93. data/app/models/spree/stock/adjuster.rb +10 -11
  94. data/app/models/spree/stock/availability_validator.rb +6 -10
  95. data/app/models/spree/stock/content_item.rb +48 -0
  96. data/app/models/spree/stock/coordinator.rb +14 -7
  97. data/app/models/spree/stock/estimator.rb +1 -1
  98. data/app/models/spree/stock/inventory_unit_builder.rb +21 -0
  99. data/app/models/spree/stock/package.rb +38 -51
  100. data/app/models/spree/stock/packer.rb +13 -11
  101. data/app/models/spree/stock/prioritizer.rb +7 -7
  102. data/app/models/spree/stock/splitter/base.rb +2 -2
  103. data/app/models/spree/stock_item.rb +6 -9
  104. data/app/models/spree/stock_location.rb +17 -25
  105. data/app/models/spree/stock_movement.rb +1 -8
  106. data/app/models/spree/stock_transfer.rb +0 -2
  107. data/app/models/spree/store.rb +1 -1
  108. data/app/models/spree/tax_category.rb +2 -2
  109. data/app/models/spree/tax_rate.rb +16 -22
  110. data/app/models/spree/taxon.rb +1 -1
  111. data/app/models/spree/variant.rb +45 -23
  112. data/app/models/spree/zone.rb +17 -17
  113. data/app/models/spree/zone_member.rb +1 -1
  114. data/app/views/layouts/spree/base_mailer.html.erb +784 -0
  115. data/app/views/spree/order_mailer/cancel_email.html.erb +45 -0
  116. data/app/views/spree/order_mailer/cancel_email.text.erb +2 -2
  117. data/app/views/spree/order_mailer/confirm_email.html.erb +84 -0
  118. data/app/views/spree/order_mailer/confirm_email.text.erb +2 -2
  119. data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +22 -0
  120. data/app/views/spree/shared/_base_mailer_footer.html.erb +20 -0
  121. data/app/views/spree/shared/_base_mailer_header.html.erb +31 -0
  122. data/app/views/spree/shipment_mailer/shipped_email.html.erb +34 -0
  123. data/app/views/spree/test_mailer/test_email.html.erb +40 -0
  124. data/app/views/spree/test_mailer/test_email.text.erb +2 -2
  125. data/config/initializers/friendly_id.rb +88 -0
  126. data/config/initializers/premailer_assets.rb +1 -0
  127. data/config/initializers/user_class_extensions.rb +9 -22
  128. data/config/locales/en.yml +180 -12
  129. data/db/default/spree/states.rb +73 -55
  130. data/db/migrate/20130213191427_create_default_stock.rb +1 -0
  131. data/db/migrate/20130807024301_upgrade_adjustments.rb +4 -5
  132. data/db/migrate/20140309033438_create_store_from_preferences.rb +0 -7
  133. data/db/migrate/20140318191500_create_spree_taxons_promotion_rules.rb +8 -0
  134. data/db/migrate/20140530024945_move_order_token_from_tokenized_permission.rb +1 -1
  135. data/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb +5 -3
  136. data/db/migrate/20140625214618_create_spree_refunds.rb +12 -0
  137. data/db/migrate/20140702140656_create_spree_return_authorization_inventory_unit.rb +12 -0
  138. data/db/migrate/20140707125621_rename_return_authorization_inventory_unit_to_return_items.rb +5 -0
  139. data/db/migrate/20140709160534_backfill_line_item_pre_tax_amount.rb +10 -0
  140. data/db/migrate/20140710041921_recreate_spree_return_authorizations.rb +55 -0
  141. data/db/migrate/20140710181204_add_amount_fields_to_return_items.rb +7 -0
  142. data/db/migrate/20140710190048_drop_return_authorization_amount.rb +5 -0
  143. data/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb +28 -0
  144. data/db/migrate/20140713140527_create_spree_refund_reasons.rb +14 -0
  145. data/db/migrate/20140713142214_rename_return_authorization_reason.rb +5 -0
  146. data/db/migrate/20140715182625_create_spree_promotion_categories.rb +11 -0
  147. data/db/migrate/20140716204111_drop_received_at_on_return_items.rb +9 -0
  148. data/db/migrate/20140716212330_add_reception_and_acceptance_status_to_return_items.rb +6 -0
  149. data/db/migrate/20140717155155_create_default_refund_reason.rb +9 -0
  150. data/db/migrate/20140717185932_add_default_to_spree_stock_locations.rb +5 -0
  151. data/db/migrate/20140718133010_create_spree_customer_returns.rb +9 -0
  152. data/db/migrate/20140718133349_add_customer_return_id_to_return_item.rb +6 -0
  153. data/db/migrate/20140718195325_create_friendly_id_slugs.rb +15 -0
  154. data/db/migrate/20140723004419_rename_spree_refund_return_authorization_id.rb +5 -0
  155. data/db/migrate/20140723152808_increase_return_item_pre_tax_amount_precision.rb +13 -0
  156. data/db/migrate/20140723214541_copy_product_slugs_to_slug_history.rb +15 -0
  157. data/db/migrate/20140725131539_create_spree_reimbursements.rb +21 -0
  158. data/db/migrate/20140728225422_add_promotionable_to_spree_products.rb +5 -0
  159. data/db/migrate/20140729133613_add_exchange_inventory_unit_foreign_keys.rb +7 -0
  160. data/db/migrate/20140730155938_add_acceptance_status_errors_to_return_item.rb +5 -0
  161. data/db/migrate/20140731150017_create_spree_reimbursement_types.rb +20 -0
  162. data/db/migrate/20140805171035_add_default_to_spree_credit_cards.rb +5 -0
  163. data/db/migrate/20140805171219_make_existing_credit_cards_default.rb +10 -0
  164. data/db/migrate/20140806144901_add_type_to_reimbursement_type.rb +9 -0
  165. data/db/migrate/20140808184039_create_spree_reimbursement_credits.rb +10 -0
  166. data/db/migrate/20140827170513_add_meta_title_to_spree_products.rb +7 -0
  167. data/db/migrate/20140924164824_add_code_to_spree_tax_categories.rb +5 -0
  168. data/db/migrate/20141002191113_add_code_to_spree_shipping_methods.rb +5 -0
  169. data/db/migrate/20141007230328_add_cancel_audit_fields_to_spree_orders.rb +6 -0
  170. data/db/migrate/20141009204607_add_store_id_to_orders.rb +8 -0
  171. data/lib/generators/spree/install/install_generator.rb +7 -3
  172. data/lib/spree/core.rb +11 -10
  173. data/lib/spree/core/controller_helpers/common.rb +3 -10
  174. data/lib/spree/core/controller_helpers/order.rb +15 -12
  175. data/lib/spree/core/controller_helpers/strong_parameters.rb +9 -9
  176. data/lib/spree/core/engine.rb +8 -1
  177. data/lib/spree/core/importer/order.rb +5 -17
  178. data/lib/spree/core/search/base.rb +1 -1
  179. data/lib/spree/core/validators/email.rb +1 -1
  180. data/lib/spree/core/version.rb +1 -1
  181. data/lib/spree/instrumentation.rb +41 -0
  182. data/lib/spree/migrations.rb +3 -7
  183. data/lib/spree/money.rb +2 -2
  184. data/lib/spree/permitted_attributes.rb +10 -9
  185. data/lib/spree/testing_support/ability_helpers.rb +25 -25
  186. data/lib/spree/testing_support/authorization_helpers.rb +3 -5
  187. data/lib/spree/testing_support/capybara_ext.rb +2 -2
  188. data/lib/spree/testing_support/factories/calculator_factory.rb +0 -8
  189. data/lib/spree/testing_support/factories/credit_card_factory.rb +1 -1
  190. data/lib/spree/testing_support/factories/customer_return_factory.rb +31 -0
  191. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +1 -0
  192. data/lib/spree/testing_support/factories/line_item_factory.rb +2 -1
  193. data/lib/spree/testing_support/factories/order_factory.rb +11 -6
  194. data/lib/spree/testing_support/factories/promotion_category_factory.rb +6 -0
  195. data/lib/spree/testing_support/factories/promotion_factory.rb +9 -7
  196. data/lib/spree/testing_support/factories/refund_factory.rb +14 -0
  197. data/lib/spree/testing_support/factories/reimbursement_factory.rb +16 -0
  198. data/lib/spree/testing_support/factories/reimbursement_type_factory.rb +7 -0
  199. data/lib/spree/testing_support/factories/return_authorization_factory.rb +9 -3
  200. data/lib/spree/testing_support/factories/return_item_factory.rb +10 -0
  201. data/lib/spree/testing_support/factories/shipment_factory.rb +1 -0
  202. data/lib/spree/testing_support/factories/shipping_method_factory.rb +3 -2
  203. data/lib/spree/testing_support/factories/stock_factory.rb +12 -11
  204. data/lib/spree/testing_support/flash.rb +2 -2
  205. data/lib/tasks/email.rake +7 -0
  206. data/lib/tasks/exchanges.rake +70 -0
  207. data/vendor/assets/javascripts/jquery.validate/localization/messages_et.js +23 -0
  208. data/vendor/assets/javascripts/jquery.validate/localization/messages_eu.js +25 -0
  209. data/vendor/assets/javascripts/jquery.validate/localization/messages_hr.js +25 -0
  210. data/vendor/assets/javascripts/jquery.validate/localization/messages_ka.js +25 -0
  211. data/vendor/assets/javascripts/jquery.validate/localization/messages_ko.js +25 -0
  212. data/vendor/assets/javascripts/jquery.validate/localization/messages_my.js +25 -0
  213. data/vendor/assets/javascripts/jquery.validate/localization/messages_pt_BR.js +26 -0
  214. data/vendor/assets/javascripts/jquery.validate/localization/messages_pt_PT.js +26 -0
  215. data/vendor/assets/javascripts/jquery.validate/localization/messages_sl.js +25 -0
  216. data/vendor/assets/javascripts/jquery.validate/localization/messages_sv.js +23 -0
  217. data/vendor/assets/javascripts/jquery.validate/localization/messages_uk.js +25 -0
  218. data/vendor/assets/javascripts/jquery.validate/localization/messages_zh.js +25 -0
  219. data/vendor/assets/javascripts/jquery.validate/localization/messages_zh_TW.js +26 -0
  220. metadata +163 -47
  221. data/app/models/concerns/spree/ransackable_attributes.rb +0 -19
  222. data/db/migrate/20141021194502_add_state_lock_version_to_order.rb +0 -5
  223. data/db/migrate/20141101231208_fix_adjustment_order_presence.rb +0 -13
  224. data/db/migrate/20141105213646_update_classifications_positions.rb +0 -9
  225. data/db/migrate/20141120135441_add_guest_token_index_to_spree_orders.rb +0 -5
  226. data/db/migrate/20150515211137_fix_adjustment_order_id.rb +0 -70
  227. data/lib/spree/core/adjustment_source.rb +0 -26
  228. data/lib/spree/core/calculated_adjustments.rb +0 -35
  229. data/lib/spree/core/controller_helpers.rb +0 -20
  230. data/lib/spree/core/user_address.rb +0 -32
  231. data/lib/spree/core/user_payment_source.rb +0 -20
  232. data/lib/spree/localized_number.rb +0 -20
@@ -2,8 +2,8 @@ module Spree
2
2
  class Promotion
3
3
  module Actions
4
4
  class CreateAdjustment < PromotionAction
5
- include Spree::Core::CalculatedAdjustments
6
- include Spree::Core::AdjustmentSource
5
+ include Spree::CalculatedAdjustments
6
+ include Spree::AdjustmentSource
7
7
 
8
8
  has_many :adjustments, as: :source
9
9
 
@@ -2,8 +2,8 @@ module Spree
2
2
  class Promotion
3
3
  module Actions
4
4
  class CreateItemAdjustments < PromotionAction
5
- include Spree::Core::CalculatedAdjustments
6
- include Spree::Core::AdjustmentSource
5
+ include Spree::CalculatedAdjustments
6
+ include Spree::AdjustmentSource
7
7
 
8
8
  has_many :adjustments, as: :source
9
9
 
@@ -14,14 +14,11 @@ module Spree
14
14
 
15
15
  def perform(payload = {})
16
16
  order = payload[:order]
17
- # Find only the orders' line items which have not already been adjusted by this promotion
18
- # HACK: Need to use [0] because `pluck` may return an empty array, which AR helpfully
19
- # coverts to meaning NOT IN (NULL) and the DB isn't happy about that.
20
- already_adjusted_line_items = [0] + self.adjustments.
21
- where(adjustable_id: order.line_items.pluck(:id), adjustable_type: 'Spree::LineItem').
22
- pluck(:adjustable_id)
17
+ promotion = payload[:promotion]
18
+
23
19
  result = false
24
- order.line_items.where("id NOT IN (?)", already_adjusted_line_items).find_each do |line_item|
20
+
21
+ line_items_to_adjust(promotion, order).each do |line_item|
25
22
  current_result = self.create_adjustment(line_item, order)
26
23
  result ||= current_result
27
24
  end
@@ -31,7 +28,6 @@ module Spree
31
28
  def create_adjustment(adjustable, order)
32
29
  amount = self.compute_amount(adjustable)
33
30
  return if amount == 0
34
- return if promotion.product_ids.present? and !promotion.product_ids.include?(adjustable.product.id)
35
31
  self.adjustments.create!(
36
32
  amount: amount,
37
33
  adjustable: adjustable,
@@ -44,8 +40,9 @@ module Spree
44
40
  # Ensure a negative amount which does not exceed the sum of the order's
45
41
  # item_total and ship_total
46
42
  def compute_amount(adjustable)
43
+ return 0 unless promotion.line_item_actionable? adjustable.order, adjustable
47
44
  promotion_amount = self.calculator.compute(adjustable).to_f.abs
48
-
45
+
49
46
  [adjustable.amount, promotion_amount].min * -1
50
47
  end
51
48
 
@@ -65,6 +62,13 @@ module Spree
65
62
  self.calculator = Calculator::PercentOnLineItem.new
66
63
  end
67
64
 
65
+ def line_items_to_adjust(promotion, order)
66
+ excluded_ids = self.adjustments.pluck(:adjustable_id)
67
+ order.line_items.where.not(id: excluded_ids).select do |line_item|
68
+ promotion.line_item_actionable? order, line_item
69
+ end
70
+ end
71
+
68
72
  end
69
73
  end
70
74
  end
@@ -39,23 +39,13 @@ module Spree
39
39
  order = options[:order]
40
40
  return unless self.eligible? order
41
41
 
42
- action_taken = false
43
42
  promotion_action_line_items.each do |item|
44
43
  current_quantity = order.quantity_of(item.variant)
45
- if current_quantity < item.quantity && item_available?(item)
46
- line_item = order.contents.add(item.variant, item.quantity - current_quantity)
47
- action_taken = true if line_item.try(:valid?)
44
+ if current_quantity < item.quantity
45
+ order.contents.add(item.variant, item.quantity - current_quantity)
48
46
  end
49
47
  end
50
- action_taken
51
48
  end
52
-
53
- # Checks that there's enough stock to add the line item to the order
54
- def item_available?(item)
55
- quantifier = Spree::Stock::Quantifier.new(item.variant)
56
- quantifier.can_supply? item.quantity
57
- end
58
-
59
49
  end
60
50
  end
61
51
  end
@@ -13,10 +13,14 @@ module Spree
13
13
  @email = order.email
14
14
 
15
15
  if user || email
16
- completed_orders.blank? || (completed_orders.first == order)
16
+ if !completed_orders.blank? && completed_orders.first != order
17
+ eligibility_errors.add(:base, eligibility_error_message(:not_first_order))
18
+ end
17
19
  else
18
- false
20
+ eligibility_errors.add(:base, eligibility_error_message(:no_user_or_email_specified))
19
21
  end
22
+
23
+ eligibility_errors.empty?
20
24
  end
21
25
 
22
26
  private
@@ -4,10 +4,14 @@ module Spree
4
4
  class Promotion
5
5
  module Rules
6
6
  class ItemTotal < PromotionRule
7
- preference :amount, :decimal, default: 100.00
8
- preference :operator, :string, default: '>'
7
+ preference :amount_min, :decimal, default: 100.00
8
+ preference :operator_min, :string, default: '>'
9
+ preference :amount_max, :decimal, default: 1000.00
10
+ preference :operator_max, :string, default: '<'
9
11
 
10
- OPERATORS = ['gt', 'gte']
12
+
13
+ OPERATORS_MIN = ['gt', 'gte']
14
+ OPERATORS_MAX = ['lt','lte']
11
15
 
12
16
  def applicable?(promotable)
13
17
  promotable.is_a?(Spree::Order)
@@ -15,8 +19,42 @@ module Spree
15
19
 
16
20
  def eligible?(order, options = {})
17
21
  item_total = order.item_total
18
- item_total.send(preferred_operator == 'gte' ? :>= : :>, BigDecimal.new(preferred_amount.to_s))
22
+
23
+ lower_limit_condition = item_total.send(preferred_operator_min == 'gte' ? :>= : :>, BigDecimal.new(preferred_amount_min.to_s))
24
+ upper_limit_condition = item_total.send(preferred_operator_max == 'lte' ? :<= : :<, BigDecimal.new(preferred_amount_max.to_s))
25
+
26
+ eligibility_errors.add(:base, ineligible_message_max) unless upper_limit_condition
27
+ eligibility_errors.add(:base, ineligible_message_min) unless lower_limit_condition
28
+
29
+ eligibility_errors.empty?
30
+ end
31
+
32
+ private
33
+ def formatted_amount_min
34
+ Spree::Money.new(preferred_amount_min).to_s
35
+ end
36
+
37
+ def formatted_amount_max
38
+ Spree::Money.new(preferred_amount_max).to_s
39
+ end
40
+
41
+
42
+ def ineligible_message_max
43
+ if preferred_operator_max == 'gte'
44
+ eligibility_error_message(:item_total_more_than_or_equal, amount: formatted_amount_max)
45
+ else
46
+ eligibility_error_message(:item_total_more_than, amount: formatted_amount_max)
47
+ end
19
48
  end
49
+
50
+ def ineligible_message_min
51
+ if preferred_operator_min == 'gte'
52
+ eligibility_error_message(:item_total_less_than, amount: formatted_amount_min)
53
+ else
54
+ eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount_min)
55
+ end
56
+ end
57
+
20
58
  end
21
59
  end
22
60
  end
@@ -0,0 +1,24 @@
1
+ module Spree
2
+ class Promotion
3
+ module Rules
4
+ class OneUsePerUser < PromotionRule
5
+ def applicable?(promotable)
6
+ promotable.is_a?(Spree::Order)
7
+ end
8
+
9
+ def eligible?(order, options = {})
10
+ if order.user.present?
11
+ if promotion.used_by?(order.user, [order])
12
+ eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user))
13
+ end
14
+ else
15
+ eligibility_errors.add(:base, eligibility_error_message(:no_user_specified))
16
+ end
17
+
18
+ eligibility_errors.empty?
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -21,24 +21,26 @@ module Spree
21
21
 
22
22
  def eligible?(order, options = {})
23
23
  return true if eligible_products.empty?
24
+
24
25
  if preferred_match_policy == 'all'
25
- eligible_products.all? {|p| order.products.include?(p) }
26
+ unless eligible_products.all? {|p| order.products.include?(p) }
27
+ eligibility_errors.add(:base, eligibility_error_message(:missing_product))
28
+ end
26
29
  elsif preferred_match_policy == 'any'
27
- order.products.any? {|p| eligible_products.include?(p) }
30
+ unless order.products.any? {|p| eligible_products.include?(p) }
31
+ eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products))
32
+ end
28
33
  else
29
- order.products.none? {|p| eligible_products.include?(p) }
34
+ unless order.products.none? {|p| eligible_products.include?(p) }
35
+ eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product))
36
+ end
30
37
  end
38
+
39
+ eligibility_errors.empty?
31
40
  end
32
41
 
33
42
  def actionable?(line_item)
34
- case preferred_match_policy
35
- when 'any', 'all'
36
- product_ids.include? line_item.variant.product_id
37
- when 'none'
38
- product_ids.exclude? line_item.variant.product_id
39
- else
40
- raise "unexpected match policy: #{preferred_match_policy.inspect}"
41
- end
43
+ product_ids.include? line_item.variant.product_id
42
44
  end
43
45
 
44
46
  def product_ids_string
@@ -0,0 +1,61 @@
1
+ module Spree
2
+ class Promotion
3
+ module Rules
4
+ class Taxon < PromotionRule
5
+ has_and_belongs_to_many :taxons, class_name: '::Spree::Taxon', join_table: 'spree_taxons_promotion_rules', foreign_key: 'promotion_rule_id'
6
+
7
+ MATCH_POLICIES = %w(any all)
8
+ preference :match_policy, default: MATCH_POLICIES.first
9
+
10
+ def applicable?(promotable)
11
+ promotable.is_a?(Spree::Order)
12
+ end
13
+
14
+ def eligible?(order, options = {})
15
+ if preferred_match_policy == 'all'
16
+ unless (taxons.to_a - taxons_in_order_including_parents(order)).empty?
17
+ eligibility_errors.add(:base, eligibility_error_message(:missing_taxon))
18
+ end
19
+ else
20
+ order_taxons = taxons_in_order_including_parents(order)
21
+ unless taxons.any?{ |taxon| order_taxons.include? taxon }
22
+ eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons))
23
+ end
24
+ end
25
+
26
+ eligibility_errors.empty?
27
+ end
28
+
29
+ def taxon_ids_string
30
+ taxons.pluck(:id).join(',')
31
+ end
32
+
33
+ def taxon_ids_string=(s)
34
+ ids = s.to_s.split(',').map(&:strip)
35
+ self.taxons = Spree::Taxon.find(ids)
36
+ end
37
+
38
+ private
39
+
40
+ # All taxons in an order
41
+ def order_taxons(order)
42
+ Spree::Taxon.joins(products: {variants_including_master: :line_items}).where(spree_line_items: {order_id: order.id}).uniq
43
+ end
44
+
45
+ # ids of taxons rules and taxons rules children
46
+ def taxons_including_children_ids
47
+ taxons.inject([]){ |ids,taxon| ids += taxon.self_and_descendants.ids }
48
+ end
49
+
50
+ # taxons order vs taxons rules and taxons rules children
51
+ def order_taxons_in_taxons_and_children(order)
52
+ order_taxons(order).where(id: taxons_including_children_ids)
53
+ end
54
+
55
+ def taxons_in_order_including_parents(order)
56
+ order_taxons_in_taxons_and_children(order).inject([]){ |taxons, taxon| taxons << taxon.self_and_ancestors }.flatten.uniq
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -8,7 +8,7 @@ module Spree
8
8
  join_table: 'spree_promotion_rules_users',
9
9
  foreign_key: 'promotion_rule_id',
10
10
  association_foreign_key: :user_id
11
-
11
+
12
12
  def applicable?(promotable)
13
13
  promotable.is_a?(Spree::Order)
14
14
  end
@@ -7,7 +7,10 @@ module Spree
7
7
  end
8
8
 
9
9
  def eligible?(order, options = {})
10
- return order.user.present?
10
+ unless order.user.present?
11
+ eligibility_errors.add(:base, eligibility_error_message(:no_user_specified))
12
+ end
13
+ eligibility_errors.empty?
11
14
  end
12
15
  end
13
16
  end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ class PromotionCategory < Spree::Base
3
+ validates_presence_of :name
4
+ has_many :promotions
5
+ end
6
+ end
@@ -29,26 +29,22 @@ module Spree
29
29
  end
30
30
 
31
31
  private
32
+ def promotions
33
+ promo_table = Promotion.arel_table
34
+ join_table = Arel::Table.new(:spree_orders_promotions)
32
35
 
33
- def promotions
34
- # AR cannot bind raw ASTs to prepared statements. There always must be a manager around.
35
- # Also Postgresql requires an aliased table for `SELECT * FROM (subexpression) AS alias`.
36
- # And Sqlite3 cannot work on outher parenthesis from `(left UNION right)`.
37
- # So this construct makes both happy.
38
- select = Arel::SelectManager.new(
39
- Promotion,
40
- Promotion.arel_table.create_table_alias(
41
- order.promotions.active.union(Promotion.active.where(code: nil, path: nil)),
42
- Promotion.table_name
43
- ),
44
- )
45
- select.project(Arel.star)
36
+ join_condition = promo_table.join(join_table, Arel::Nodes::OuterJoin).on(
37
+ promo_table[:id].eq(join_table[:promotion_id])
38
+ ).join_sources
46
39
 
47
- Promotion.find_by_sql(
48
- select,
49
- order.promotions.bind_values
50
- )
51
- end
40
+ Promotion.active.includes(:promotion_rules).
41
+ joins(join_condition).
42
+ where(
43
+ promo_table[:code].eq(nil).and(
44
+ promo_table[:path].eq(nil)
45
+ ).or(join_table[:order_id].eq(order.id))
46
+ ).distinct
47
+ end
52
48
  end
53
49
  end
54
50
  end
@@ -2,7 +2,7 @@ module Spree
2
2
  module PromotionHandler
3
3
  class Coupon
4
4
  attr_reader :order
5
- attr_accessor :error, :success
5
+ attr_accessor :error, :success, :status_code
6
6
 
7
7
  def initialize(order)
8
8
  @order = order
@@ -14,9 +14,9 @@ module Spree
14
14
  handle_present_promotion(promotion)
15
15
  else
16
16
  if Promotion.with_coupon_code(order.coupon_code).try(:expired?)
17
- self.error = Spree.t(:coupon_code_expired)
17
+ set_error_code :coupon_code_expired
18
18
  else
19
- self.error = Spree.t(:coupon_code_not_found)
19
+ set_error_code :coupon_code_not_found
20
20
  end
21
21
  end
22
22
  end
@@ -24,6 +24,16 @@ module Spree
24
24
  self
25
25
  end
26
26
 
27
+ def set_success_code(c)
28
+ @status_code = c
29
+ @success = Spree.t(c)
30
+ end
31
+
32
+ def set_error_code(c)
33
+ @status_code = c
34
+ @error = Spree.t(c)
35
+ end
36
+
27
37
  def promotion
28
38
  @promotion ||= Promotion.active.includes(:promotion_rules, :promotion_actions).with_coupon_code(order.coupon_code)
29
39
  end
@@ -37,7 +47,10 @@ module Spree
37
47
  def handle_present_promotion(promotion)
38
48
  return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded?(order)
39
49
  return promotion_applied if promotion_exists_on_order?(order, promotion)
40
- return ineligible_for_this_order unless promotion.eligible?(order)
50
+ unless promotion.eligible?(order)
51
+ self.error = promotion.eligibility_errors.full_messages.first unless promotion.eligibility_errors.blank?
52
+ return (self.error || ineligible_for_this_order)
53
+ end
41
54
 
42
55
  # If any of the actions for the promotion return `true`,
43
56
  # then result here will also be `true`.
@@ -45,20 +58,20 @@ module Spree
45
58
  if result
46
59
  determine_promotion_application_result
47
60
  else
48
- self.error = Spree.t(:coupon_code_unknown_error)
61
+ set_error_code :coupon_code_unknown_error
49
62
  end
50
63
  end
51
64
 
52
65
  def promotion_usage_limit_exceeded
53
- self.error = Spree.t(:coupon_code_max_usage)
66
+ set_error_code :coupon_code_max_usage
54
67
  end
55
68
 
56
69
  def ineligible_for_this_order
57
- self.error = Spree.t(:coupon_code_not_eligible)
70
+ set_error_code :coupon_code_not_eligible
58
71
  end
59
72
 
60
73
  def promotion_applied
61
- self.error = Spree.t(:coupon_code_already_applied)
74
+ set_error_code :coupon_code_already_applied
62
75
  end
63
76
 
64
77
  def promotion_exists_on_order?(order, promotion)
@@ -72,26 +85,22 @@ module Spree
72
85
  end
73
86
  }
74
87
 
75
- # Check for applied adjustments.
76
88
  discount = order.line_item_adjustments.promotion.detect(&detector)
77
89
  discount ||= order.shipment_adjustments.promotion.detect(&detector)
78
90
  discount ||= order.adjustments.promotion.detect(&detector)
79
91
 
80
- # Check for applied line items.
81
- created_line_items = promotion.actions.detect { |a| a.type == 'Spree::Promotion::Actions::CreateLineItems' }
82
-
83
- if (discount && discount.eligible) || created_line_items
92
+ if discount.eligible
84
93
  order.update_totals
85
94
  order.persist_totals
86
- self.success = Spree.t(:coupon_code_applied)
95
+ set_success_code :coupon_code_applied
87
96
  else
88
97
  # if the promotion exists on an order, but wasn't found above,
89
98
  # we've already selected a better promotion
90
99
  if order.promotions.with_coupon_code(order.coupon_code)
91
- self.error = Spree.t(:coupon_code_better_exists)
100
+ set_error_code :coupon_code_better_exists
92
101
  else
93
102
  # if the promotion was created after the order
94
- self.error = Spree.t(:coupon_code_not_found)
103
+ set_error_code :coupon_code_not_found
95
104
  end
96
105
  end
97
106
  end