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,37 +2,20 @@ module Spree
2
2
  class Payment < Spree::Base
3
3
  module Processing
4
4
  def process!
5
- if payment_method && payment_method.source_required?
6
- if source
7
- if !processing?
8
- if payment_method.supports?(source) || token_based?
9
- if payment_method.auto_capture?
10
- purchase!
11
- else
12
- authorize!
13
- end
14
- else
15
- invalidate!
16
- raise Core::GatewayError.new(Spree.t(:payment_method_not_supported))
17
- end
18
- end
19
- else
20
- raise Core::GatewayError.new(Spree.t(:payment_processing_failed))
21
- end
5
+ if payment_method && payment_method.auto_capture?
6
+ purchase!
7
+ else
8
+ authorize!
22
9
  end
23
10
  end
24
11
 
25
12
  def authorize!
26
- started_processing!
27
- gateway_action(source, :authorize, :pend)
13
+ handle_payment_preconditions { process_authorization }
28
14
  end
29
15
 
30
16
  # Captures the entire amount of a payment.
31
17
  def purchase!
32
- started_processing!
33
- result = gateway_action(source, :purchase, :complete)
34
- # This won't be called if gateway_action raises a GatewayError
35
- capture_events.create!(amount: amount)
18
+ handle_payment_preconditions { process_purchase }
36
19
  end
37
20
 
38
21
  # Takes the amount in cents to capture.
@@ -80,56 +63,8 @@ module Spree
80
63
  end
81
64
  end
82
65
 
83
- def credit!(credit_amount=nil)
84
- raise Core::GatewayError.new(Spree.t(:payment_processing_failed)) if processing?
85
-
86
- # Calculate credit amount before marking as processing since it messes up the order totals not having payment in completed state.
87
- credit_amount ||= credit_allowed >= order.outstanding_balance.abs ? order.outstanding_balance.abs : credit_allowed.abs
88
- credit_amount = credit_amount.to_f
89
-
90
- # Mark as processing to avoid race condition that could send multiple credits to the gateway.
91
- started_processing!
92
- protect_from_connection_error do
93
- check_environment
94
-
95
- credit_cents = Spree::Money.new(credit_amount, currency: currency).money.cents
96
-
97
- if payment_method.payment_profiles_supported?
98
- response = payment_method.credit(credit_cents, source, response_code, gateway_options)
99
- else
100
- response = payment_method.credit(credit_cents, response_code, gateway_options)
101
- end
102
-
103
- record_response(response)
104
- # Always set back to 'completed' as initial payment record was successful.
105
- self.update_column(:state, 'completed')
106
-
107
- if response.success?
108
- self.class.create!(
109
- :order => order,
110
- :source => self,
111
- :payment_method => payment_method,
112
- :amount => credit_amount.abs * -1,
113
- :response_code => response.authorization,
114
- :state => 'completed'
115
- )
116
- else
117
- gateway_error(response)
118
- end
119
- end
120
- end
121
-
122
66
  def cancel!
123
- if payment_method.respond_to?(:cancel)
124
- payment_method.cancel(response_code)
125
- else
126
- credit!(credit_allowed.abs)
127
- end
128
- end
129
-
130
- def partial_credit(amount)
131
- return if amount > credit_allowed
132
- credit!(amount)
67
+ payment_method.cancel(response_code)
133
68
  end
134
69
 
135
70
  def gateway_options
@@ -158,6 +93,39 @@ module Spree
158
93
 
159
94
  private
160
95
 
96
+ def process_authorization
97
+ started_processing!
98
+ gateway_action(source, :authorize, :pend)
99
+ end
100
+
101
+ def process_purchase
102
+ started_processing!
103
+ result = gateway_action(source, :purchase, :complete)
104
+ # This won't be called if gateway_action raises a GatewayError
105
+ capture_events.create!(amount: amount)
106
+ end
107
+
108
+ def handle_payment_preconditions(&block)
109
+ unless block_given?
110
+ raise ArgumentError.new("handle_payment_preconditions must be called with a block")
111
+ end
112
+
113
+ if payment_method && payment_method.source_required?
114
+ if source
115
+ if !processing?
116
+ if payment_method.supports?(source) || token_based?
117
+ yield
118
+ else
119
+ invalidate!
120
+ raise Core::GatewayError.new(Spree.t(:payment_method_not_supported))
121
+ end
122
+ end
123
+ else
124
+ raise Core::GatewayError.new(Spree.t(:payment_processing_failed))
125
+ end
126
+ end
127
+ end
128
+
161
129
  def gateway_action(source, action, success_state)
162
130
  protect_from_connection_error do
163
131
  check_environment
@@ -8,7 +8,7 @@ module Spree
8
8
 
9
9
  validates :name, presence: true
10
10
 
11
- has_many :payments, class_name: "Spree::Payment", inverse_of: :payment_method
11
+ has_many :payments, class_name: "Spree::Payment"
12
12
  has_many :credit_cards, class_name: "Spree::CreditCard"
13
13
 
14
14
  def self.providers
@@ -18,8 +18,6 @@ module Spree
18
18
  ActiveMerchant::Billing::Response.new(true, "", {}, {})
19
19
  end
20
20
 
21
- def cancel(response); end
22
-
23
21
  def void(*args)
24
22
  ActiveMerchant::Billing::Response.new(true, "", {}, {})
25
23
  end
@@ -1,5 +1,5 @@
1
1
  class Spree::Preference < Spree::Base
2
2
  serialize :value
3
3
 
4
- validates :key, presence: true
4
+ validates :key, presence: true, uniqueness: true
5
5
  end
@@ -106,6 +106,26 @@ module Spree::Preferences::Preferable
106
106
  else
107
107
  true
108
108
  end
109
+ when :array
110
+ value.is_a?(Array) ? value : Array.wrap(value)
111
+ when :hash
112
+ case value.class.to_s
113
+ when "Hash"
114
+ value
115
+ when "String"
116
+ # only works with hashes whose keys are strings
117
+ JSON.parse value.gsub('=>', ':')
118
+ when "Array"
119
+ begin
120
+ value.try(:to_h)
121
+ rescue TypeError
122
+ Hash[*value]
123
+ rescue ArgumentError
124
+ raise 'An even count is required when passing an array to be converted to a hash'
125
+ end
126
+ else
127
+ value.class.ancestors.include?(Hash) ? value : {}
128
+ end
109
129
  else
110
130
  value
111
131
  end
@@ -12,8 +12,6 @@ module Spree
12
12
  end
13
13
  alias :display_price :display_amount
14
14
 
15
- self.whitelisted_ransackable_attributes = ['amount']
16
-
17
15
  def money
18
16
  Spree::Money.new(amount || 0, { currency: currency })
19
17
  end
@@ -23,7 +21,7 @@ module Spree
23
21
  end
24
22
 
25
23
  def price=(price)
26
- self[:amount] = Spree::LocalizedNumber.parse(price)
24
+ self[:amount] = parse_price(price)
27
25
  end
28
26
 
29
27
  # Remove variant default_scope `deleted_at: nil`
@@ -41,6 +39,18 @@ module Spree
41
39
  end
42
40
  end
43
41
 
42
+ # strips all non-price-like characters from the price, taking into account locale settings
43
+ def parse_price(price)
44
+ return price unless price.is_a?(String)
45
+
46
+ separator, delimiter = I18n.t([:'number.currency.format.separator', :'number.currency.format.delimiter'])
47
+ non_price_characters = /[^0-9\-#{separator}]/
48
+ price.gsub!(non_price_characters, '') # strip everything else first
49
+ price.gsub!(separator, '.') unless separator == '.' # then replace the locale-specific decimal separator with the standard separator if necessary
50
+
51
+ price.to_d
52
+ end
53
+
44
54
  def maximum_amount
45
55
  BigDecimal '999999.99'
46
56
  end
@@ -21,7 +21,7 @@
21
21
  module Spree
22
22
  class Product < Spree::Base
23
23
  extend FriendlyId
24
- friendly_id :slug_candidates, use: :slugged
24
+ friendly_id :slug_candidates, use: :history
25
25
 
26
26
  acts_as_paranoid
27
27
 
@@ -40,7 +40,8 @@ module Spree
40
40
  has_one :master,
41
41
  -> { where is_master: true },
42
42
  inverse_of: :product,
43
- class_name: 'Spree::Variant'
43
+ class_name: 'Spree::Variant',
44
+ dependent: :destroy
44
45
 
45
46
  has_many :variants,
46
47
  -> { where(is_master: false).order("#{::Spree::Variant.quoted_table_name}.position ASC") },
@@ -56,7 +57,7 @@ module Spree
56
57
  has_many :prices, -> { order('spree_variants.position, spree_variants.id, currency') }, through: :variants
57
58
 
58
59
  has_many :stock_items, through: :variants_including_master
59
-
60
+
60
61
  has_many :line_items, through: :variants_including_master
61
62
  has_many :orders, through: :line_items
62
63
 
@@ -64,32 +65,29 @@ module Spree
64
65
 
65
66
  delegate_belongs_to :master, :cost_price
66
67
 
67
- delegate :images, to: :master, prefix: true
68
- alias_method :images, :master_images
69
-
70
- has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
71
-
72
68
  after_create :set_master_variant_defaults
73
69
  after_create :add_properties_and_option_types_from_prototype
74
70
  after_create :build_variants_from_option_values_hash, if: :option_values_hash
75
71
 
76
- after_destroy :punch_slug
77
-
78
- after_initialize :ensure_master
79
-
80
72
  after_save :save_master
81
73
  after_save :run_touch_callbacks, if: :anything_changed?
82
74
  after_save :reset_nested_changes
83
75
  after_touch :touch_taxons
84
76
 
85
- before_validation :normalize_slug, on: :update
86
- before_validation :validate_master
77
+ delegate :images, to: :master, prefix: true
78
+ alias_method :images, :master_images
79
+
80
+ has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
87
81
 
88
- validates :meta_keywords, length: { maximum: 255 }
89
82
  validates :name, presence: true
90
83
  validates :price, presence: true, if: proc { Spree::Config[:require_master_price] }
91
84
  validates :shipping_category_id, presence: true
92
- validates :slug, length: { minimum: 3 }, uniqueness: { allow_blank: true }
85
+ validates :slug, length: { minimum: 3 }
86
+ validates :slug, uniqueness: true
87
+
88
+ before_validation :normalize_slug, on: :update
89
+
90
+ after_destroy :punch_slug
93
91
 
94
92
  attr_accessor :option_values_hash
95
93
 
@@ -97,8 +95,7 @@ module Spree
97
95
 
98
96
  alias :options :product_option_types
99
97
 
100
- self.whitelisted_ransackable_associations = %w[stores variants_including_master master variants]
101
- self.whitelisted_ransackable_attributes = %w[slug]
98
+ after_initialize :ensure_master
102
99
 
103
100
  # the master variant is not a member of the variants array
104
101
  def has_variants?
@@ -279,21 +276,19 @@ module Spree
279
276
  # when saving so we force a save using a hook
280
277
  # Fix for issue #5306
281
278
  def save_master
282
- if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?)))
283
- master.save!
284
- @nested_changes = true
285
- end
286
- end
279
+ begin
280
+ if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?)))
281
+ master.save!
282
+ @nested_changes = true
283
+ end
287
284
 
288
- # If the master cannot be saved, the Product object will get its errors
289
- # and will be destroyed
290
- def validate_master
291
- # We call master.default_price here to ensure price is initialized.
292
- # Required to avoid Variant#check_price validation failing on create.
293
- unless master.default_price && master.valid?
285
+ # If the master cannot be saved, the Product object will get its errors
286
+ # and will be destroyed
287
+ rescue ActiveRecord::RecordInvalid
294
288
  master.errors.each do |att, error|
295
289
  self.errors.add(att, error)
296
290
  end
291
+ raise
297
292
  end
298
293
  end
299
294
 
@@ -9,8 +9,6 @@ module Spree
9
9
 
10
10
  default_scope -> { order("#{self.table_name}.position") }
11
11
 
12
- self.whitelisted_ransackable_attributes = ['value']
13
-
14
12
  # virtual attributes for use with AJAX completion stuff
15
13
  def property_name
16
14
  property.name if property
@@ -3,6 +3,10 @@ module Spree
3
3
  MATCH_POLICIES = %w(all any)
4
4
  UNACTIVATABLE_ORDER_STATES = ["complete", "awaiting_return", "returned"]
5
5
 
6
+ attr_reader :eligibility_errors
7
+
8
+ belongs_to :promotion_category
9
+
6
10
  has_many :promotion_rules, autosave: true, dependent: :destroy
7
11
  alias_method :rules, :promotion_rules
8
12
 
@@ -16,23 +20,24 @@ module Spree
16
20
  validates_associated :rules
17
21
 
18
22
  validates :name, presence: true
19
- validates :path, uniqueness: { allow_blank: true }
23
+ validates :path, uniqueness: true, allow_blank: true
20
24
  validates :usage_limit, numericality: { greater_than: 0, allow_nil: true }
21
25
  validates :description, length: { maximum: 255 }
22
26
 
23
27
  before_save :normalize_blank_values
24
28
 
25
29
  scope :coupons, ->{ where("#{table_name}.code IS NOT NULL") }
26
- scope :applied, -> { joins(:orders).uniq }
27
30
 
28
- self.whitelisted_ransackable_attributes = ['code', 'path', 'promotion_category_id']
31
+ order_join_table = reflect_on_association(:orders).join_table
32
+
33
+ scope :applied, -> { joins("INNER JOIN #{order_join_table} ON #{order_join_table}.promotion_id = #{table_name}.id").uniq }
29
34
 
30
35
  def self.advertised
31
36
  where(advertise: true)
32
37
  end
33
38
 
34
39
  def self.with_coupon_code(coupon_code)
35
- where("lower(#{self.table_name}.code) = ?", coupon_code.strip.downcase).first
40
+ where("lower(code) = ?", coupon_code.strip.downcase).first
36
41
  end
37
42
 
38
43
  def self.active
@@ -52,6 +57,8 @@ module Spree
52
57
  order = payload[:order]
53
58
  return unless self.class.order_activatable?(order)
54
59
 
60
+ payload[:promotion] = self
61
+
55
62
  # Track results from actions to see if any action has been taken.
56
63
  # Actions should return nil/false if no action has been taken.
57
64
  # If an action returns true, then an action has been taken.
@@ -73,36 +80,39 @@ module Spree
73
80
 
74
81
  # called anytime order.update! happens
75
82
  def eligible?(promotable)
76
- return false if expired? || usage_limit_exceeded?(promotable)
77
- rules_are_eligible?(promotable, {})
83
+ return false if expired? || usage_limit_exceeded?(promotable) || blacklisted?(promotable)
84
+ !!eligible_rules(promotable, {})
78
85
  end
79
86
 
80
- def rules_are_eligible?(promotable, options = {})
87
+ # eligible_rules returns an array of promotion rules where eligible? is true for the promotable
88
+ # if there are no such rules, an empty array is returned
89
+ # if the rules make this promotable ineligible, then nil is returned (i.e. this promotable is not eligible)
90
+ def eligible_rules(promotable, options = {})
81
91
  # Promotions without rules are eligible by default.
82
- return true if rules.none?
92
+ return [] if rules.none?
83
93
  eligible = lambda { |r| r.eligible?(promotable, options) }
84
94
  specific_rules = rules.for(promotable)
85
- return true if specific_rules.none?
86
- if match_policy == 'all'
95
+ return [] if specific_rules.none?
96
+
97
+ if match_all?
87
98
  # If there are rules for this promotion, but no rules for this
88
99
  # particular promotable, then the promotion is ineligible by default.
89
- specific_rules.any? && specific_rules.all?(&eligible)
100
+ unless specific_rules.all?(&eligible)
101
+ @eligibility_errors = specific_rules.map(&:eligibility_errors).detect(&:present?)
102
+ return nil
103
+ end
104
+ specific_rules
90
105
  else
91
- # If there are no rules for this promotable, then this will return false.
92
- # If there are rules for this promotable, but they are ineligible, this will return false.
93
- specific_rules.any?(&eligible)
106
+ unless specific_rules.any?(&eligible)
107
+ @eligibility_errors = specific_rules.map(&:eligibility_errors).detect(&:present?)
108
+ return nil
109
+ end
110
+ specific_rules.select(&eligible)
94
111
  end
95
112
  end
96
113
 
97
- # Products assigned to all product rules
98
114
  def products
99
- @products ||= self.rules.to_a.inject([]) do |products, rule|
100
- rule.respond_to?(:products) ? products << rule.products : products
101
- end.flatten.uniq
102
- end
103
-
104
- def product_ids
105
- products.map(&:id)
115
+ rules.where(type: "Spree::Promotion::Rules::Product").map(&:products).flatten.uniq
106
116
  end
107
117
 
108
118
  def usage_limit_exceeded?(promotable)
@@ -110,8 +120,7 @@ module Spree
110
120
  end
111
121
 
112
122
  def adjusted_credits_count(promotable)
113
- adjustments = promotable.is_a?(Order) ? promotable.all_adjustments : promotable.adjustments
114
- credits_count - adjustments.promotion.where(:source_id => actions.pluck(:id)).count
123
+ credits_count - promotable.adjustments.promotion.where(:source_id => actions.pluck(:id)).count
115
124
  end
116
125
 
117
126
  def credits
@@ -122,11 +131,44 @@ module Spree
122
131
  credits.count
123
132
  end
124
133
 
134
+ def line_item_actionable?(order, line_item)
135
+ if eligible? order
136
+ rules = eligible_rules(order)
137
+ if rules.blank?
138
+ true
139
+ else
140
+ rules.send(match_all? ? :all? : :any?) do |rule|
141
+ rule.actionable? line_item
142
+ end
143
+ end
144
+ else
145
+ false
146
+ end
147
+ end
148
+
149
+ def used_by?(user, excluded_orders = [])
150
+ orders.where.not(id: excluded_orders.map(&:id)).complete.where(user_id: user.id).exists?
151
+ end
152
+
125
153
  private
154
+ def blacklisted?(promotable)
155
+ case promotable
156
+ when Spree::LineItem
157
+ !promotable.product.promotionable?
158
+ when Spree::Order
159
+ promotable.line_items.any? &&
160
+ !promotable.line_items.joins(:product).where(spree_products: {promotionable: true}).any?
161
+ end
162
+ end
163
+
126
164
  def normalize_blank_values
127
165
  [:code, :path].each do |column|
128
166
  self[column] = nil if self[column].blank?
129
167
  end
130
168
  end
169
+
170
+ def match_all?
171
+ match_policy == 'all'
172
+ end
131
173
  end
132
174
  end