solidus_core 2.11.10 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (252) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -2
  3. data/app/assets/javascripts/spree.js.erb +0 -51
  4. data/app/controllers/spree/base_controller.rb +1 -1
  5. data/app/helpers/spree/base_helper.rb +1 -1
  6. data/app/helpers/spree/products_helper.rb +2 -2
  7. data/app/helpers/spree/store_helper.rb +0 -11
  8. data/app/mailers/spree/carton_mailer.rb +1 -5
  9. data/app/models/concerns/spree/active_storage_adapter/attachment.rb +30 -11
  10. data/app/models/concerns/spree/active_storage_adapter.rb +1 -1
  11. data/app/models/concerns/spree/adjustment_source.rb +0 -15
  12. data/app/models/concerns/spree/calculated_adjustments.rb +0 -18
  13. data/app/models/concerns/spree/default_price.rb +39 -10
  14. data/app/models/concerns/spree/ransackable_attributes.rb +24 -4
  15. data/app/models/concerns/spree/soft_deletable.rb +2 -4
  16. data/app/models/concerns/spree/user_address_book.rb +10 -37
  17. data/app/models/concerns/spree/user_methods.rb +38 -13
  18. data/app/models/spree/ability.rb +0 -37
  19. data/app/models/spree/address/name.rb +2 -20
  20. data/app/models/spree/address.rb +8 -186
  21. data/app/models/spree/adjustment.rb +7 -33
  22. data/app/models/spree/base.rb +0 -53
  23. data/app/models/spree/calculator/flat_fee.rb +21 -0
  24. data/app/models/spree/calculator/flexi_rate.rb +0 -5
  25. data/app/models/spree/calculator.rb +0 -7
  26. data/app/models/spree/carton.rb +1 -1
  27. data/app/models/spree/country.rb +2 -7
  28. data/app/models/spree/credit_card.rb +1 -28
  29. data/app/models/spree/customer_return.rb +5 -7
  30. data/app/models/spree/image/active_storage_attachment.rb +2 -7
  31. data/app/models/spree/image/paperclip_attachment.rb +2 -2
  32. data/app/models/spree/image.rb +0 -7
  33. data/app/models/spree/inventory_unit.rb +0 -21
  34. data/app/models/spree/line_item.rb +6 -49
  35. data/app/models/spree/log_entry.rb +74 -1
  36. data/app/models/spree/option_type.rb +1 -1
  37. data/app/models/spree/option_value.rb +10 -1
  38. data/app/models/spree/order/number_generator.rb +7 -1
  39. data/app/models/spree/order.rb +82 -170
  40. data/app/models/spree/order_cancellations.rb +4 -24
  41. data/app/models/spree/order_contents.rb +2 -1
  42. data/app/models/spree/order_inventory.rb +1 -1
  43. data/app/models/spree/order_merger.rb +2 -2
  44. data/app/models/spree/order_promotion.rb +1 -1
  45. data/app/models/spree/order_shipping.rb +6 -9
  46. data/app/models/spree/order_taxation.rb +6 -4
  47. data/app/models/spree/order_updater.rb +17 -16
  48. data/app/models/spree/payment/cancellation.rb +1 -1
  49. data/app/models/spree/payment/processing.rb +58 -55
  50. data/app/models/spree/payment.rb +0 -3
  51. data/app/models/spree/payment_create.rb +1 -13
  52. data/app/models/spree/payment_method/bogus_credit_card.rb +6 -7
  53. data/app/models/spree/payment_method/credit_card.rb +1 -3
  54. data/app/models/spree/payment_method/simple_bogus_credit_card.rb +8 -0
  55. data/app/models/spree/payment_method.rb +26 -110
  56. data/app/models/spree/price.rb +3 -3
  57. data/app/models/spree/product/scopes.rb +24 -33
  58. data/app/models/spree/product.rb +15 -42
  59. data/app/models/spree/product_property.rb +1 -1
  60. data/app/models/spree/promotion/actions/create_adjustment.rb +4 -3
  61. data/app/models/spree/promotion/actions/create_item_adjustments.rb +5 -9
  62. data/app/models/spree/promotion/actions/create_quantity_adjustments.rb +0 -3
  63. data/app/models/spree/promotion/actions/free_shipping.rb +1 -0
  64. data/app/models/spree/promotion/order_adjustments_recalculator.rb +92 -0
  65. data/app/models/spree/promotion/rules/item_total.rb +50 -6
  66. data/app/models/spree/promotion/rules/product.rb +20 -8
  67. data/app/models/spree/promotion/rules/store.rb +4 -0
  68. data/app/models/spree/promotion/rules/taxon.rb +6 -15
  69. data/app/models/spree/promotion/rules/user.rb +4 -0
  70. data/app/models/spree/promotion.rb +39 -32
  71. data/app/models/spree/promotion_action.rb +6 -9
  72. data/app/models/spree/promotion_code/batch_builder.rb +0 -14
  73. data/app/models/spree/promotion_code.rb +11 -7
  74. data/app/models/spree/promotion_handler/cart.rb +26 -6
  75. data/app/models/spree/promotion_rule.rb +5 -0
  76. data/app/models/spree/property.rb +1 -1
  77. data/app/models/spree/refund.rb +8 -52
  78. data/app/models/spree/reimbursement.rb +5 -43
  79. data/app/models/spree/reimbursement_performer.rb +2 -8
  80. data/app/models/spree/reimbursement_type/credit.rb +1 -4
  81. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +1 -2
  82. data/app/models/spree/reimbursement_type/store_credit.rb +1 -4
  83. data/app/models/spree/return_authorization.rb +2 -5
  84. data/app/models/spree/return_item.rb +4 -24
  85. data/app/models/spree/shipment.rb +3 -56
  86. data/app/models/spree/shipping_method.rb +0 -25
  87. data/app/models/spree/shipping_rate.rb +0 -2
  88. data/app/models/spree/shipping_rate_tax.rb +1 -1
  89. data/app/models/spree/state.rb +1 -5
  90. data/app/models/spree/stock/allocator/on_hand_first.rb +2 -2
  91. data/app/models/spree/stock/availability.rb +11 -3
  92. data/app/models/spree/stock/quantifier.rb +12 -8
  93. data/app/models/spree/stock/simple_coordinator.rb +8 -26
  94. data/app/models/spree/stock/splitter/base.rb +2 -7
  95. data/app/models/spree/stock_item.rb +2 -8
  96. data/app/models/spree/stock_location.rb +2 -2
  97. data/app/models/spree/stock_movement.rb +2 -2
  98. data/app/models/spree/store.rb +0 -12
  99. data/app/models/spree/store_credit.rb +14 -1
  100. data/app/models/spree/store_credit_category.rb +0 -32
  101. data/app/models/spree/store_credit_prioritizer.rb +17 -0
  102. data/app/models/spree/tax/item_tax.rb +3 -2
  103. data/app/models/spree/tax/order_tax.rb +3 -1
  104. data/app/models/spree/tax/tax_helpers.rb +2 -2
  105. data/app/models/spree/tax/tax_location.rb +4 -7
  106. data/app/models/spree/tax_calculator/default.rb +31 -0
  107. data/app/models/spree/tax_calculator/shipping_rate.rb +2 -13
  108. data/app/models/spree/tax_rate.rb +9 -27
  109. data/app/models/spree/taxon/active_storage_attachment.rb +2 -7
  110. data/app/models/spree/taxon/paperclip_attachment.rb +3 -8
  111. data/app/models/spree/taxon.rb +1 -12
  112. data/app/models/spree/taxonomy.rb +1 -1
  113. data/app/models/spree/user_address.rb +0 -5
  114. data/app/models/spree/user_last_url_storer/rules/authentication_rule.rb +1 -1
  115. data/app/models/spree/variant/price_selector.rb +34 -4
  116. data/app/models/spree/variant.rb +52 -66
  117. data/app/models/spree/zone.rb +1 -1
  118. data/app/subscribers/spree/mailer_subscriber.rb +4 -0
  119. data/app/subscribers/spree/order_mailer_subscriber.rb +35 -0
  120. data/config/i18n-tasks.yml +134 -0
  121. data/config/locales/en.yml +406 -263
  122. data/db/migrate/20180416083007_add_apply_to_all_to_variant_property_rule.rb +1 -1
  123. data/db/migrate/20201127212108_add_type_before_removal_to_spree_payment_methods.rb +7 -0
  124. data/db/migrate/20210312061050_change_column_null_on_prices.rb +7 -0
  125. data/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb +20 -0
  126. data/db/migrate/20220805202442_add_level_to_spree_tax_rates.rb +5 -0
  127. data/db/migrate/20221123152807_add_shipping_category_to_spree_variants.rb +5 -0
  128. data/db/seeds.rb +4 -1
  129. data/lib/generators/solidus/install/app_templates/authentication/custom.rb +21 -0
  130. data/lib/generators/solidus/install/app_templates/authentication/devise.rb +16 -0
  131. data/lib/generators/solidus/install/app_templates/authentication/existing.rb +10 -0
  132. data/lib/generators/solidus/install/app_templates/authentication/none.rb +1 -0
  133. data/lib/generators/solidus/install/app_templates/frontend/break_down_solidus_gem.rb +54 -0
  134. data/lib/generators/solidus/install/app_templates/frontend/classic.rb +16 -0
  135. data/lib/generators/solidus/install/app_templates/frontend/none.rb +2 -0
  136. data/lib/generators/solidus/install/app_templates/frontend/starter.rb +3 -0
  137. data/lib/generators/solidus/install/app_templates/payment_method/bolt.rb +13 -0
  138. data/lib/generators/solidus/install/app_templates/payment_method/none.rb +1 -0
  139. data/lib/generators/solidus/install/app_templates/payment_method/paypal.rb +10 -0
  140. data/lib/generators/solidus/install/install_generator.rb +247 -149
  141. data/lib/generators/solidus/install/templates/config/initializers/spree.rb.tt +15 -60
  142. data/lib/generators/solidus/install/templates/vendor/assets/javascripts/spree/backend/all.js +2 -2
  143. data/lib/generators/solidus/update/templates/config/initializers/new_solidus_defaults.rb.tt +30 -0
  144. data/lib/generators/solidus/update/update_generator.rb +112 -0
  145. data/lib/generators/spree/custom_user/custom_user_generator.rb +6 -4
  146. data/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt +2 -6
  147. data/lib/generators/spree/custom_user/templates/migration.rb.tt +7 -3
  148. data/lib/generators/spree/dummy/dummy_generator.rb +12 -9
  149. data/lib/generators/spree/dummy/templates/rails/application.rb.tt +0 -1
  150. data/lib/generators/spree/dummy/templates/rails/database.yml +81 -39
  151. data/lib/generators/spree/dummy/templates/rails/storage.yml +3 -0
  152. data/lib/generators/spree/dummy/templates/rails/test.rb +2 -0
  153. data/lib/spree/app_configuration.rb +134 -64
  154. data/lib/spree/bus.rb +20 -0
  155. data/lib/spree/core/class_constantizer.rb +2 -0
  156. data/lib/spree/core/controller_helpers/auth.rb +10 -15
  157. data/lib/spree/core/controller_helpers/current_host.rb +5 -3
  158. data/lib/spree/core/controller_helpers/order.rb +12 -32
  159. data/lib/spree/core/controller_helpers/payment_parameters.rb +0 -54
  160. data/lib/spree/core/controller_helpers/pricing.rb +0 -8
  161. data/lib/spree/core/controller_helpers/search.rb +1 -1
  162. data/lib/spree/core/controller_helpers/strong_parameters.rb +0 -4
  163. data/lib/spree/core/engine.rb +54 -50
  164. data/lib/spree/core/environment_extension.rb +0 -9
  165. data/lib/spree/core/product_filters.rb +1 -41
  166. data/lib/spree/core/role_configuration.rb +0 -14
  167. data/lib/spree/core/search/base.rb +18 -35
  168. data/lib/spree/core/state_machines/order.rb +2 -2
  169. data/lib/spree/core/state_machines.rb +2 -11
  170. data/lib/spree/core/stock_configuration.rb +18 -0
  171. data/lib/spree/core/validators/email.rb +5 -3
  172. data/lib/spree/core/version.rb +5 -1
  173. data/lib/spree/core/versioned_value.rb +75 -0
  174. data/lib/spree/core.rb +40 -11
  175. data/lib/spree/deprecation.rb +1 -1
  176. data/lib/spree/event/configuration.rb +0 -5
  177. data/lib/spree/event/subscriber.rb +0 -18
  178. data/lib/spree/event/subscriber_registry.rb +7 -7
  179. data/lib/spree/event.rb +1 -32
  180. data/lib/spree/i18n.rb +0 -22
  181. data/lib/spree/migrations.rb +13 -11
  182. data/lib/spree/money.rb +3 -18
  183. data/lib/spree/permission_sets/default_customer.rb +8 -1
  184. data/lib/spree/permitted_attributes.rb +17 -59
  185. data/lib/spree/preferences/configuration.rb +84 -0
  186. data/lib/spree/preferences/preferable.rb +13 -0
  187. data/lib/spree/preferences/preferable_class_methods.rb +37 -4
  188. data/lib/spree/preferences/preference_differentiator.rb +29 -0
  189. data/lib/spree/preferences/static_model_preferences.rb +25 -10
  190. data/lib/spree/rails_compatibility.rb +106 -0
  191. data/lib/spree/testing_support/blacklist_urls.rb +1 -1
  192. data/lib/spree/testing_support/bus_helpers.rb +101 -0
  193. data/lib/spree/testing_support/capybara_ext.rb +0 -30
  194. data/lib/spree/testing_support/common_rake.rb +71 -23
  195. data/lib/spree/testing_support/controller_requests.rb +0 -82
  196. data/lib/spree/testing_support/dummy_app/assets/javascripts/spree/backend/all.js +1 -1
  197. data/lib/spree/testing_support/dummy_app/assets/javascripts/spree/frontend/all.js +1 -1
  198. data/lib/spree/testing_support/dummy_app/database.yml +42 -22
  199. data/lib/spree/testing_support/dummy_app/migrations.rb +0 -3
  200. data/lib/spree/testing_support/dummy_app.rb +47 -34
  201. data/lib/spree/testing_support/factories/address_factory.rb +9 -6
  202. data/lib/spree/testing_support/factories/calculator_factory.rb +3 -0
  203. data/lib/spree/testing_support/factories/country_factory.rb +1 -2
  204. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +1 -1
  205. data/lib/spree/testing_support/factories/order_factory.rb +8 -5
  206. data/lib/spree/testing_support/factories/product_factory.rb +4 -1
  207. data/lib/spree/testing_support/factories/promotion_factory.rb +28 -14
  208. data/lib/spree/testing_support/factories/refund_factory.rb +0 -1
  209. data/lib/spree/testing_support/factories/state_factory.rb +8 -2
  210. data/lib/spree/testing_support/factories/store_credit_factory.rb +4 -4
  211. data/lib/spree/testing_support/factories/user_factory.rb +6 -0
  212. data/lib/spree/testing_support/factory_bot.rb +2 -2
  213. data/lib/spree/testing_support/order_walkthrough.rb +6 -8
  214. data/lib/spree/testing_support/preferences.rb +0 -25
  215. data/lib/spree/testing_support/silence_deprecations.rb +9 -0
  216. data/lib/tasks/colorado_delivery_fee.rake +28 -0
  217. data/lib/tasks/payment_method.rake +29 -0
  218. data/lib/tasks/solidus/check_orders_with_invalid_email.rake +18 -0
  219. data/lib/tasks/solidus/delete_prices_with_nil_amount.rake +8 -0
  220. data/lib/tasks/solidus/split_promotions_with_any_match_policy.rake +33 -0
  221. data/solidus_core.gemspec +14 -7
  222. metadata +127 -78
  223. data/app/mailers/spree/test_mailer.rb +0 -13
  224. data/app/models/concerns/spree/user_payment_source.rb +0 -26
  225. data/app/models/spree/calculator/free_shipping.rb +0 -22
  226. data/app/models/spree/calculator/percent_per_item.rb +0 -51
  227. data/app/models/spree/calculator/price_sack.rb +0 -28
  228. data/app/models/spree/gateway/bogus.rb +0 -13
  229. data/app/models/spree/gateway/bogus_simple.rb +0 -13
  230. data/app/models/spree/gateway.rb +0 -14
  231. data/app/models/spree/order/checkout.rb +0 -244
  232. data/app/models/spree/order_capturing.rb +0 -50
  233. data/app/models/spree/promotion_handler/free_shipping.rb +0 -9
  234. data/app/models/spree/tax/shipping_rate_taxer.rb +0 -24
  235. data/lib/generators/solidus/install/templates/vendor/assets/javascripts/spree/frontend/all.js +0 -10
  236. data/lib/generators/solidus/install/templates/vendor/assets/stylesheets/spree/frontend/all.css +0 -9
  237. data/lib/generators/spree/install/install_generator.rb +0 -15
  238. data/lib/solidus/migrations/rename_gateways.rb +0 -41
  239. data/lib/spree/core/current_store.rb +0 -24
  240. data/lib/spree/paranoia_deprecations.rb +0 -41
  241. data/lib/spree/promo/environment.rb +0 -12
  242. data/lib/spree/testing_support/bar_ability.rb +0 -19
  243. data/lib/tasks/core.rake +0 -104
  244. data/lib/tasks/email.rake +0 -12
  245. data/lib/tasks/migrations/copy_order_bill_address_to_credit_card.rake +0 -119
  246. data/lib/tasks/migrations/migrate_address_names.rake +0 -158
  247. data/lib/tasks/migrations/migrate_default_billing_addresses_to_address_book.rake +0 -26
  248. data/lib/tasks/migrations/migrate_shipping_rate_taxes.rake +0 -22
  249. data/lib/tasks/migrations/migrate_user_addresses.rake +0 -34
  250. data/lib/tasks/migrations/rename_gateways.rake +0 -23
  251. data/lib/tasks/order_capturing.rake +0 -27
  252. data/lib/tasks/upgrade.rake +0 -13
@@ -5,10 +5,6 @@ module Spree
5
5
  # variations, called variants. Product properties include description,
6
6
  # permalink, availability, shipping category, etc. that do not change by
7
7
  # variant.
8
- #
9
- # @note this model uses {https://github.com/radar/paranoia paranoia}.
10
- # +#destroy+ will only soft-destroy records and the default scope hides
11
- # soft-destroyed records using +WHERE deleted_at IS NULL+.
12
8
  class Product < Spree::Base
13
9
  extend FriendlyId
14
10
  friendly_id :slug_candidates, use: :history
@@ -65,6 +61,17 @@ module Spree
65
61
  has_many :line_items, through: :variants_including_master
66
62
  has_many :orders, through: :line_items
67
63
 
64
+ scope :sort_by_master_default_price_amount_asc, -> {
65
+ with_default_price.order('spree_prices.amount ASC')
66
+ }
67
+ scope :sort_by_master_default_price_amount_desc, -> {
68
+ with_default_price.order('spree_prices.amount DESC')
69
+ }
70
+ scope :with_default_price, -> {
71
+ left_joins(master: :prices)
72
+ .where(master: { spree_prices: Spree::Config.default_pricing_options.desired_attributes })
73
+ }
74
+
68
75
  def find_or_build_master
69
76
  master || build_master
70
77
  end
@@ -89,7 +96,7 @@ module Spree
89
96
  :has_default_price?,
90
97
  :images,
91
98
  :price_for,
92
- :price_in,
99
+ :price_for_options,
93
100
  :rebuild_vat_prices=,
94
101
  to: :find_or_build_master
95
102
 
@@ -124,11 +131,11 @@ module Spree
124
131
 
125
132
  alias :options :product_option_types
126
133
 
127
- self.whitelisted_ransackable_associations = %w[stores variants_including_master master variants]
128
- self.whitelisted_ransackable_attributes = %w[name slug]
134
+ self.allowed_ransackable_associations = %w[stores variants_including_master master variants]
135
+ self.allowed_ransackable_attributes = %w[name slug]
129
136
 
130
137
  def self.ransackable_scopes(_auth_object = nil)
131
- %i(with_discarded with_variant_sku_cont)
138
+ %i(available with_discarded with_variant_sku_cont with_all_variant_sku_cont with_kept_variant_sku_cont)
132
139
  end
133
140
 
134
141
  # @return [Boolean] true if there are any variants
@@ -186,19 +193,6 @@ module Spree
186
193
  !!discontinue_on&.past?
187
194
  end
188
195
 
189
- # Groups variants by the specified option type.
190
- #
191
- # @deprecated This method is not called in the Solidus codebase
192
- # @param opt_type [String] the name of the option type to group by
193
- # @param pricing_options [Spree::Config.pricing_options_class] the pricing options to search
194
- # for, default: the default pricing options
195
- # @return [Hash] option_type as keys, array of variants as values.
196
- def categorise_variants_from_option(opt_type, pricing_options = Spree::Config.default_pricing_options)
197
- return {} unless option_types.include?(opt_type)
198
- variants.with_prices(pricing_options).group_by { |variant| variant.option_values.detect { |option| option.option_type == opt_type } }
199
- end
200
- deprecate :categorise_variants_from_option, deprecator: Spree::Deprecation
201
-
202
196
  # Poor man's full text search.
203
197
  #
204
198
  # Filters products to those which have any of the strings in +values+ in
@@ -214,17 +208,6 @@ module Spree
214
208
  where conditions.inject(:or)
215
209
  end
216
210
 
217
- # @param current_currency [String] currency to filter variants by; defaults to Spree's default
218
- # @deprecated This method can only handle prices for currencies
219
- # @return [Array<Spree::Variant>] all variants with at least one option value
220
- def variants_and_option_values(current_currency = nil)
221
- variants.includes(:option_values).active(current_currency).select do |variant|
222
- variant.option_values.any?
223
- end
224
- end
225
- deprecate variants_and_option_values: :variants_and_option_values_for,
226
- deprecator: Spree::Deprecation
227
-
228
211
  # @param pricing_options [Spree::Variant::PricingOptions] the pricing options to search
229
212
  # for, default: the default pricing options
230
213
  # @return [Array<Spree::Variant>] all variants with at least one option value
@@ -296,16 +279,6 @@ module Spree
296
279
  end
297
280
  end
298
281
 
299
- # Image that can be used for the product.
300
- #
301
- # Will first search for images on the product, then those belonging to the
302
- # variants. If all else fails, will return a new image object.
303
- # @return [Spree::Image] the image to display
304
- def display_image
305
- Spree::Deprecation.warn('Spree::Product#display_image is DEPRECATED. Choose an image from Spree::Product#gallery instead.')
306
- images.first || variant_images.first || Spree::Image.new
307
- end
308
-
309
282
  # Finds the variant property rule that matches the provided option value ids.
310
283
  #
311
284
  # @param option_value_ids [Array<Integer>] list of option value ids
@@ -9,6 +9,6 @@ module Spree
9
9
  belongs_to :product, touch: true, class_name: 'Spree::Product', inverse_of: :product_properties, optional: true
10
10
  belongs_to :property, class_name: 'Spree::Property', inverse_of: :product_properties, optional: true
11
11
 
12
- self.whitelisted_ransackable_attributes = ['value']
12
+ self.allowed_ransackable_attributes = ['value']
13
13
  end
14
14
  end
@@ -15,6 +15,10 @@ module Spree
15
15
  before_destroy :remove_adjustments_from_incomplete_orders
16
16
  before_discard :remove_adjustments_from_incomplete_orders
17
17
 
18
+ def preload_relations
19
+ [:calculator]
20
+ end
21
+
18
22
  # Creates the adjustment related to a promotion for the order passed
19
23
  # through options hash
20
24
  #
@@ -39,9 +43,6 @@ module Spree
39
43
  # item_total and ship_total
40
44
  def compute_amount(calculable)
41
45
  amount = calculator.compute(calculable)
42
- if !amount.is_a?(BigDecimal)
43
- Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{amount.inspect}, it should return a BigDecimal"
44
- end
45
46
  amount ||= BigDecimal(0)
46
47
  amount = amount.abs
47
48
  [(calculable.item_total + calculable.ship_total), amount].min * -1
@@ -15,6 +15,10 @@ module Spree
15
15
  before_destroy :remove_adjustments_from_incomplete_orders
16
16
  before_discard :remove_adjustments_from_incomplete_orders
17
17
 
18
+ def preload_relations
19
+ [:calculator]
20
+ end
21
+
18
22
  def perform(payload = {})
19
23
  order = payload[:order]
20
24
  promotion = payload[:promotion]
@@ -33,9 +37,6 @@ module Spree
33
37
  order = adjustable.is_a?(Order) ? adjustable : adjustable.order
34
38
  return 0 unless promotion.line_item_actionable?(order, adjustable)
35
39
  promotion_amount = calculator.compute(adjustable)
36
- if !promotion_amount.is_a?(BigDecimal)
37
- Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{promotion_amount.inspect}, it should return a BigDecimal"
38
- end
39
40
  promotion_amount ||= BigDecimal(0)
40
41
  promotion_amount = promotion_amount.abs
41
42
  [adjustable.amount, promotion_amount].min * -1
@@ -86,13 +87,8 @@ module Spree
86
87
  end
87
88
 
88
89
  def line_items_to_adjust(promotion, order)
89
- excluded_ids = adjustments.
90
- where(adjustable_id: order.line_items.pluck(:id), adjustable_type: 'Spree::LineItem').
91
- pluck(:adjustable_id).
92
- to_set
93
-
94
90
  order.line_items.select do |line_item|
95
- !excluded_ids.include?(line_item.id) &&
91
+ line_item.adjustments.none? { |adjustment| adjustment.source == self } &&
96
92
  promotion.line_item_actionable?(order, line_item)
97
93
  end
98
94
  end
@@ -57,9 +57,6 @@ module Spree
57
57
  #
58
58
  def compute_amount(line_item)
59
59
  adjustment_amount = calculator.compute(PartialLineItem.new(line_item))
60
- if !adjustment_amount.is_a?(BigDecimal)
61
- Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{adjustment_amount.inspect}, it should return a BigDecimal"
62
- end
63
60
  adjustment_amount ||= BigDecimal(0)
64
61
  adjustment_amount = adjustment_amount.abs
65
62
 
@@ -7,6 +7,7 @@ module Spree
7
7
  def perform(payload = {})
8
8
  order = payload[:order]
9
9
  promotion_code = payload[:promotion_code]
10
+ return false unless promotion.eligible? order
10
11
 
11
12
  created_adjustments = order.shipments.map do |shipment|
12
13
  next if promotion_credit_exists?(shipment)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class Promotion < Spree::Base
5
+
6
+ # This class encapsulates all the things the promotion system does to
7
+ # an order. It is called from the `Spree::OrderUpdater` before taxes are
8
+ # calculated, such that taxes always respect promotions.
9
+
10
+ # This class iterates over all existing promotion adjustments and recalculates
11
+ # their amount and eligibility using their adjustment source.
12
+ class OrderAdjustmentsRecalculator
13
+ def initialize(order)
14
+ @order = order
15
+ end
16
+
17
+ def call
18
+ all_items = line_items + shipments
19
+ all_items.each do |item|
20
+ promotion_adjustments = item.adjustments.select(&:promotion?)
21
+
22
+ promotion_adjustments.each { |adjustment| recalculate(adjustment) }
23
+ Spree::Config.promotion_chooser_class.new(promotion_adjustments).update
24
+
25
+ item.promo_total = promotion_adjustments.select(&:eligible?).sum(&:amount)
26
+ end
27
+ # Update and select the best promotion adjustment for the order.
28
+ # We don't update the order.promo_total yet. Order totals are updated later
29
+ # in #update_adjustment_total since they include the totals from the order's
30
+ # line items and/or shipments.
31
+ order_promotion_adjustments = order.adjustments.select(&:promotion?)
32
+ order_promotion_adjustments.each { |adjustment| recalculate(adjustment) }
33
+ Spree::Config.promotion_chooser_class.new(order_promotion_adjustments).update
34
+
35
+ order.promo_total = all_items.sum(&:promo_total) +
36
+ order_promotion_adjustments.
37
+ select(&:eligible?).
38
+ select(&:promotion?).
39
+ sum(&:amount)
40
+ order
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :order
46
+ delegate :line_items, :shipments, to: :order
47
+
48
+ # Recalculate and persist the amount from this adjustment's source based on
49
+ # the adjustable ({Order}, {Shipment}, or {LineItem})
50
+ #
51
+ # If the adjustment has no source (such as when created manually from the
52
+ # admin) or is closed, this is a noop.
53
+ #
54
+ # @return [BigDecimal] New amount of this adjustment
55
+ def recalculate(adjustment)
56
+ if adjustment.finalized?
57
+ return adjustment.amount
58
+ end
59
+
60
+ # If the adjustment has no source, do not attempt to re-calculate the
61
+ # amount.
62
+ # Some scenarios where this happens:
63
+ # - Adjustments that are manually created via the admin backend
64
+ # - PromotionAction adjustments where the PromotionAction was deleted
65
+ # after the order was completed.
66
+ if adjustment.source.present?
67
+ adjustment.amount = adjustment.source.compute_amount(adjustment.adjustable)
68
+
69
+ adjustment.eligible = calculate_eligibility(adjustment)
70
+
71
+ # Persist only if changed
72
+ # This is only not a save! to avoid the extra queries to load the order
73
+ # (for validations) and to touch the adjustment.
74
+ adjustment.update_columns(eligible: adjustment.eligible, amount: adjustment.amount, updated_at: Time.current) if adjustment.changed?
75
+ end
76
+ adjustment.amount
77
+ end
78
+
79
+ # Calculates based on attached promotion (if this is a promotion
80
+ # adjustment) whether this promotion is still eligible.
81
+ # @api private
82
+ # @return [true,false] Whether this adjustment is eligible
83
+ def calculate_eligibility(adjustment)
84
+ if !adjustment.finalized? && adjustment.source
85
+ adjustment.source.promotion.eligible?(adjustment.adjustable, promotion_code: adjustment.promotion_code)
86
+ else
87
+ adjustment.eligible?
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -5,12 +5,38 @@ module Spree
5
5
  module Rules
6
6
  # A rule to apply to an order greater than (or greater than or equal to)
7
7
  # a specific amount
8
+ #
9
+ # To add extra operators please override `self.operators_map` or any other helper method.
10
+ # To customize the error message you can also override `ineligible_message`.
8
11
  class ItemTotal < PromotionRule
12
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
13
+
9
14
  preference :amount, :decimal, default: 100.00
10
15
  preference :currency, :string, default: ->{ Spree::Config[:currency] }
11
- preference :operator, :string, default: '>'
16
+ preference :operator, :string, default: 'gt'
17
+
18
+ # The list of allowed operators names mapped to their symbols.
19
+ def self.operators_map
20
+ {
21
+ gte: :>=,
22
+ gt: :>,
23
+ }
24
+ end
25
+
26
+ def self.operator_options
27
+ operators_map.map do |name, _method|
28
+ [I18n.t(name, scope: 'spree.item_total_rule.operators'), name]
29
+ end
30
+ end
12
31
 
13
- OPERATORS = ['gt', 'gte']
32
+ # @deprecated
33
+ OPERATORS = operators_map.keys.map(&:to_s)
34
+ deprecate_constant(
35
+ :OPERATORS,
36
+ :operators_map,
37
+ message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.",
38
+ deprecator: Spree::Deprecation,
39
+ )
14
40
 
15
41
  def applicable?(promotable)
16
42
  promotable.is_a?(Spree::Order)
@@ -18,8 +44,8 @@ module Spree
18
44
 
19
45
  def eligible?(order, _options = {})
20
46
  return false unless order.currency == preferred_currency
21
- item_total = order.item_total
22
- unless item_total.send(preferred_operator == 'gte' ? :>= : :>, BigDecimal(preferred_amount.to_s))
47
+
48
+ unless total_for_order(order).send(operator, threshold)
23
49
  eligibility_errors.add(:base, ineligible_message, error_code: ineligible_error_code)
24
50
  end
25
51
 
@@ -28,15 +54,33 @@ module Spree
28
54
 
29
55
  private
30
56
 
57
+ def operator
58
+ self.class.operators_map.fetch(
59
+ preferred_operator.to_sym,
60
+ preferred_operator_default,
61
+ )
62
+ end
63
+
64
+ def total_for_order(order)
65
+ order.item_total
66
+ end
67
+
68
+ def threshold
69
+ BigDecimal(preferred_amount.to_s)
70
+ end
71
+
31
72
  def formatted_amount
32
73
  Spree::Money.new(preferred_amount, currency: preferred_currency).to_s
33
74
  end
34
75
 
35
76
  def ineligible_message
36
- if preferred_operator == 'gte'
77
+ case preferred_operator.to_s
78
+ when 'gte'
37
79
  eligibility_error_message(:item_total_less_than, amount: formatted_amount)
38
- else
80
+ when 'gt'
39
81
  eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount)
82
+ else
83
+ eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator)
40
84
  end
41
85
  end
42
86
 
@@ -12,6 +12,10 @@ module Spree
12
12
  class_name: 'Spree::ProductPromotionRule'
13
13
  has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules
14
14
 
15
+ def preload_relations
16
+ [:products]
17
+ end
18
+
15
19
  MATCH_POLICIES = %w(any all none)
16
20
 
17
21
  validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES
@@ -31,17 +35,19 @@ module Spree
31
35
  return true if eligible_products.empty?
32
36
 
33
37
  case preferred_match_policy
34
- when 'all'
35
- unless eligible_products.all? { |product| order.products.include?(product) }
38
+ when "all"
39
+ unless eligible_products.all? { |product| order_products(order).include?(product) }
36
40
  eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product)
37
41
  end
38
- when 'any'
39
- unless order.products.any? { |product| eligible_products.include?(product) }
40
- eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), error_code: :no_applicable_products)
42
+ when "any"
43
+ unless order_products(order).any? { |product| eligible_products.include?(product) }
44
+ eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products),
45
+ error_code: :no_applicable_products)
41
46
  end
42
- when 'none'
43
- unless order.products.none? { |product| eligible_products.include?(product) }
44
- eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), error_code: :has_excluded_product)
47
+ when "none"
48
+ unless order_products(order).none? { |product| eligible_products.include?(product) }
49
+ eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
50
+ error_code: :has_excluded_product)
45
51
  end
46
52
  else
47
53
  raise "unexpected match policy: #{preferred_match_policy.inspect}"
@@ -68,6 +74,12 @@ module Spree
68
74
  def product_ids_string=(product_ids)
69
75
  self.product_ids = product_ids.to_s.split(',').map(&:strip)
70
76
  end
77
+
78
+ private
79
+
80
+ def order_products(order)
81
+ order.line_items.map(&:variant).map(&:product)
82
+ end
71
83
  end
72
84
  end
73
85
  end
@@ -9,6 +9,10 @@ module Spree
9
9
  dependent: :destroy
10
10
  has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store"
11
11
 
12
+ def preload_relations
13
+ [:stores]
14
+ end
15
+
12
16
  def applicable?(promotable)
13
17
  promotable.is_a?(Spree::Order)
14
18
  end
@@ -8,6 +8,10 @@ module Spree
8
8
  dependent: :destroy
9
9
  has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon'
10
10
 
11
+ def preload_relations
12
+ [:taxons]
13
+ end
14
+
11
15
  MATCH_POLICIES = %w(any all none)
12
16
 
13
17
  validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES
@@ -38,11 +42,7 @@ module Spree
38
42
  eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon)
39
43
  end
40
44
  else
41
- # Change this to an exception in a future version of Solidus
42
- warn_invalid_match_policy(assume: 'any')
43
- unless order_taxons.where(id: rule_taxon_ids_with_children).exists?
44
- eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons)
45
- end
45
+ raise "unexpected match policy: #{preferred_match_policy.inspect}"
46
46
  end
47
47
 
48
48
  eligibility_errors.empty?
@@ -60,9 +60,7 @@ module Spree
60
60
  when 'none'
61
61
  !found
62
62
  else
63
- # Change this to an exception in a future version of Solidus
64
- warn_invalid_match_policy(assume: 'any')
65
- found
63
+ raise "unexpected match policy: #{preferred_match_policy.inspect}"
66
64
  end
67
65
  end
68
66
 
@@ -77,13 +75,6 @@ module Spree
77
75
 
78
76
  private
79
77
 
80
- def warn_invalid_match_policy(assume:)
81
- Spree::Deprecation.warn(
82
- "#{self.class.name} id=#{id} has unexpected match policy #{preferred_match_policy.inspect}. "\
83
- "Interpreting it as '#{assume}'."
84
- )
85
- end
86
-
87
78
  # All taxons in an order
88
79
  def taxons_in_order(order)
89
80
  Spree::Taxon.joins(products: { variants_including_master: :line_items })
@@ -9,6 +9,10 @@ module Spree
9
9
  dependent: :destroy
10
10
  has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new
11
11
 
12
+ def preload_relations
13
+ [:users]
14
+ end
15
+
12
16
  def applicable?(promotable)
13
17
  promotable.is_a?(Spree::Order)
14
18
  end
@@ -3,6 +3,7 @@
3
3
  module Spree
4
4
  class Promotion < Spree::Base
5
5
  MATCH_POLICIES = %w(all any)
6
+
6
7
  UNACTIVATABLE_ORDER_STATES = ["complete", "awaiting_return", "returned"]
7
8
 
8
9
  attr_reader :eligibility_errors
@@ -38,11 +39,7 @@ module Spree
38
39
 
39
40
  scope :coupons, -> { joins(:codes).distinct }
40
41
  scope :advertised, -> { where(advertise: true) }
41
- scope :active, -> do
42
- return started_and_unexpired if Spree::Config.consider_actionless_promotion_active == true
43
-
44
- has_actions.started_and_unexpired
45
- end
42
+ scope :active, -> { has_actions.started_and_unexpired }
46
43
  scope :started_and_unexpired, -> do
47
44
  table = arel_table
48
45
  time = Time.current
@@ -51,12 +48,12 @@ module Spree
51
48
  where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time)))
52
49
  end
53
50
  scope :has_actions, -> do
54
- joins(:promotion_actions)
51
+ joins(:promotion_actions).distinct
55
52
  end
56
53
  scope :applied, -> { joins(:order_promotions).distinct }
57
54
 
58
- self.whitelisted_ransackable_associations = ['codes']
59
- self.whitelisted_ransackable_attributes = %w[name path promotion_category_id]
55
+ self.allowed_ransackable_associations = ['codes']
56
+ self.allowed_ransackable_attributes = %w[name path promotion_category_id]
60
57
  def self.ransackable_scopes(*)
61
58
  %i(active)
62
59
  end
@@ -71,6 +68,19 @@ module Spree
71
68
  ).first
72
69
  end
73
70
 
71
+ # All orders that have been discounted using this promotion
72
+ def discounted_orders
73
+ Spree::Order.
74
+ joins(:all_adjustments).
75
+ where(
76
+ spree_adjustments: {
77
+ source_type: "Spree::PromotionAction",
78
+ source_id: actions.map(&:id),
79
+ eligible: true
80
+ }
81
+ ).distinct
82
+ end
83
+
74
84
  def as_json(options = {})
75
85
  options[:except] ||= :code
76
86
  super
@@ -93,7 +103,7 @@ module Spree
93
103
  end
94
104
 
95
105
  def active?
96
- started? && not_expired? && (Spree::Config.consider_actionless_promotion_active || actions.present?)
106
+ started? && not_expired? && actions.present?
97
107
  end
98
108
 
99
109
  def inactive?
@@ -155,7 +165,7 @@ module Spree
155
165
  return [] if rules.none?
156
166
 
157
167
  eligible = lambda { |rule| rule.eligible?(promotable, options) }
158
- specific_rules = rules.for(promotable)
168
+ specific_rules = rules.select { |rule| rule.applicable?(promotable) }
159
169
  return [] if specific_rules.none?
160
170
 
161
171
  if match_all?
@@ -167,6 +177,12 @@ module Spree
167
177
  end
168
178
  specific_rules
169
179
  else
180
+ Spree::Deprecation.warn(
181
+ <<~WARN
182
+ Your promotion "#{name}" with ID #{id} has a match_policy of 'any'.
183
+ This is deprecated, please split the promotion into separate promotions for each rule.
184
+ WARN
185
+ )
170
186
  unless specific_rules.any?(&eligible)
171
187
  @eligibility_errors = specific_rules.map(&:eligibility_errors).detect(&:present?)
172
188
  return nil
@@ -194,11 +210,11 @@ module Spree
194
210
  # @param excluded_orders [Array<Spree::Order>] Orders to exclude from usage count
195
211
  # @return [Integer] usage count
196
212
  def usage_count(excluded_orders: [])
197
- Spree::Adjustment.promotion.
198
- eligible.
199
- in_completed_orders(excluded_orders: excluded_orders).
200
- where(source_id: actions).
201
- count(:order_id)
213
+ discounted_orders.
214
+ complete.
215
+ where.not(id: [excluded_orders.map(&:id)]).
216
+ where.not(spree_orders: { state: :canceled }).
217
+ count
202
218
  end
203
219
 
204
220
  def line_item_actionable?(order, line_item, promotion_code: nil)
@@ -219,21 +235,12 @@ module Spree
219
235
  end
220
236
 
221
237
  def used_by?(user, excluded_orders = [])
222
- [
223
- :adjustments,
224
- :line_item_adjustments,
225
- :shipment_adjustments
226
- ].any? do |adjustment_type|
227
- user.orders.complete.joins(adjustment_type).where(
228
- spree_adjustments: {
229
- source_type: "Spree::PromotionAction",
230
- source_id: actions.map(&:id),
231
- eligible: true
232
- }
233
- ).where.not(
234
- id: excluded_orders.map(&:id)
235
- ).any?
236
- end
238
+ discounted_orders.
239
+ complete.
240
+ where.not(id: excluded_orders.map(&:id)).
241
+ where(user: user).
242
+ where.not(spree_orders: { state: :canceled }).
243
+ exists?
237
244
  end
238
245
 
239
246
  # Removes a promotion and any adjustments or other side effects from an
@@ -255,9 +262,9 @@ module Spree
255
262
  def blacklisted?(promotable)
256
263
  case promotable
257
264
  when Spree::LineItem
258
- !promotable.product.promotionable?
265
+ !promotable.variant.product.promotionable?
259
266
  when Spree::Order
260
- promotable.line_items.joins(:product).where(spree_products: { promotionable: false }).exists?
267
+ promotable.line_items.any? { |line_item| !line_item.variant.product.promotionable? }
261
268
  end
262
269
  end
263
270
 
@@ -16,6 +16,10 @@ module Spree
16
16
  scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) }
17
17
  scope :shipping, -> { of_type(Spree::Config.environment.promotions.shipping_actions.to_a) }
18
18
 
19
+ def preload_relations
20
+ []
21
+ end
22
+
19
23
  # Updates the state of the order or performs some other action depending on
20
24
  # the subclass options will contain the payload from the event that
21
25
  # activated the promotion. This will include the key :user which allows
@@ -32,15 +36,8 @@ module Spree
32
36
  #
33
37
  # @param order [Spree::Order] the order to remove the action from
34
38
  # @return [void]
35
- def remove_from(order)
36
- Spree::Deprecation.warn("#{self.class.name.inspect} does not define #remove_from. The default behavior may be incorrect and will be removed in a future version of Solidus.", caller)
37
- [order, *order.line_items, *order.shipments].each do |item|
38
- item.adjustments.each do |adjustment|
39
- if adjustment.source == self
40
- item.adjustments.destroy(adjustment)
41
- end
42
- end
43
- end
39
+ def remove_from(_order)
40
+ raise 'remove_from should be implemented in a sub-class of PromotionAction'
44
41
  end
45
42
 
46
43
  def to_partial_path