spree_core 4.2.0.rc2 → 4.2.1

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spree.js +20 -0
  3. data/app/controllers/spree/base_controller.rb +2 -3
  4. data/app/controllers/spree/errors_controller.rb +11 -0
  5. data/app/finders/spree/addresses/find.rb +1 -12
  6. data/app/finders/spree/base_finder.rb +14 -0
  7. data/app/finders/spree/countries/find.rb +11 -3
  8. data/app/finders/spree/credit_cards/find.rb +2 -2
  9. data/app/finders/spree/orders/find_current.rb +2 -2
  10. data/app/finders/spree/products/find.rb +14 -3
  11. data/app/helpers/spree/base_helper.rb +3 -8
  12. data/app/helpers/spree/currency_helper.rb +34 -0
  13. data/app/helpers/spree/locale_helper.rb +31 -0
  14. data/app/helpers/spree/products_helper.rb +37 -12
  15. data/app/mailers/spree/base_mailer.rb +4 -4
  16. data/app/mailers/spree/order_mailer.rb +3 -3
  17. data/app/mailers/spree/reimbursement_mailer.rb +1 -1
  18. data/app/mailers/spree/shipment_mailer.rb +1 -1
  19. data/app/models/concerns/spree/default_price.rb +1 -5
  20. data/app/models/concerns/spree/product_scopes.rb +1 -1
  21. data/app/models/concerns/spree/user_methods.rb +2 -2
  22. data/app/models/concerns/spree/user_payment_source.rb +1 -1
  23. data/app/models/spree/ability.rb +45 -34
  24. data/app/models/spree/address.rb +4 -0
  25. data/app/models/spree/app_configuration.rb +2 -2
  26. data/app/models/spree/app_dependencies.rb +6 -2
  27. data/app/models/spree/base.rb +5 -0
  28. data/app/models/spree/credit_card.rb +4 -0
  29. data/app/models/spree/fulfilment_changer.rb +58 -16
  30. data/app/models/spree/image.rb +14 -14
  31. data/app/models/spree/inventory_unit.rb +2 -7
  32. data/app/models/spree/line_item.rb +7 -15
  33. data/app/models/spree/order.rb +1 -0
  34. data/app/models/spree/payment.rb +18 -4
  35. data/app/models/spree/payment/processing.rb +2 -2
  36. data/app/models/spree/payment_method.rb +3 -3
  37. data/app/models/spree/price.rb +2 -7
  38. data/app/models/spree/product.rb +41 -17
  39. data/app/models/spree/promotion/rules/option_value.rb +1 -1
  40. data/app/models/spree/promotion/rules/product.rb +2 -1
  41. data/app/models/spree/promotion/rules/user.rb +2 -1
  42. data/app/models/spree/refund.rb +2 -2
  43. data/app/models/spree/return_item/eligibility_validator/default.rb +0 -2
  44. data/app/models/spree/return_item/eligibility_validator/{r_m_a_required.rb → rma_required.rb} +0 -0
  45. data/app/models/spree/shipment.rb +1 -1
  46. data/app/models/spree/shipping_method.rb +1 -5
  47. data/app/models/spree/shipping_rate.rb +2 -11
  48. data/app/models/spree/stock/availability_validator.rb +3 -4
  49. data/app/models/spree/stock_item.rb +1 -5
  50. data/app/models/spree/store.rb +55 -2
  51. data/app/models/spree/store_credit.rb +1 -1
  52. data/app/models/spree/variant.rb +9 -16
  53. data/app/models/spree/zone.rb +13 -4
  54. data/app/paginators/spree/shared/paginate.rb +8 -1
  55. data/app/presenters/spree/variant_presenter.rb +2 -5
  56. data/app/services/spree/account/addresses/create.rb +6 -1
  57. data/app/services/spree/account/addresses/{base.rb → helper.rb} +1 -3
  58. data/app/services/spree/account/addresses/update.rb +6 -1
  59. data/app/services/spree/build_localized_redirect_url.rb +101 -0
  60. data/app/services/spree/cart/estimate_shipping_rates.rb +1 -1
  61. data/app/services/spree/compare_line_items.rb +4 -2
  62. data/app/sorters/spree/base_sorter.rb +35 -0
  63. data/app/sorters/spree/orders/sort.rb +1 -37
  64. data/app/sorters/spree/products/sort.rb +9 -32
  65. data/app/validators/email_validator.rb +1 -1
  66. data/app/views/spree/errors/forbidden.html.erb +0 -0
  67. data/app/views/spree/errors/unauthorized.html.erb +0 -0
  68. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +13 -2
  69. data/app/views/spree/shared/_purchased_items_table.html.erb +15 -6
  70. data/app/views/spree/shared/purchased_items_table/_adjustment.html.erb +2 -2
  71. data/app/views/spree/shared/purchased_items_table/_line_item.html.erb +2 -2
  72. data/config/initializers/inflections.rb +3 -0
  73. data/config/initializers/rails61_fixes.rb +3 -0
  74. data/config/locales/en.yml +31 -60
  75. data/config/routes.rb +2 -1
  76. data/db/default/spree/stores.rb +1 -0
  77. data/db/default/spree/zones.rb +4 -1
  78. data/db/migrate/20130326175857_add_stock_location_to_rma.rb +1 -1
  79. data/db/migrate/20191017121054_add_supported_currencies_to_store.rb +1 -0
  80. data/db/migrate/20201012091259_add_filterable_column_to_spree_option_types.rb +6 -2
  81. data/db/migrate/20201127084048_add_default_country_kind_to_spree_zones.rb +5 -0
  82. data/db/migrate/20210112193440_remove_contact_email_from_spree_stores.rb +5 -0
  83. data/db/migrate/20210114182625_create_spree_payment_methods_stores.rb +10 -0
  84. data/db/migrate/20210114220232_migrate_data_payment_methods_stores.rb +15 -0
  85. data/db/migrate/20210117112551_remove_store_id_from_spree_payment_methods.rb +5 -0
  86. data/db/migrate/20210120142527_ensure_default_locale_in_spree_stores.rb +5 -0
  87. data/db/migrate/20210205211040_add_supported_locales_to_spree_stores.rb +11 -0
  88. data/db/migrate/20210215202602_migrate_spree_i18n_globalize_config.rb +22 -0
  89. data/lib/generators/spree/install/install_generator.rb +9 -6
  90. data/lib/spree/core.rb +2 -1
  91. data/lib/spree/core/controller_helpers/auth.rb +3 -1
  92. data/lib/spree/core/controller_helpers/common.rb +6 -8
  93. data/lib/spree/core/controller_helpers/currency.rb +54 -0
  94. data/lib/spree/core/controller_helpers/locale.rb +58 -0
  95. data/lib/spree/core/controller_helpers/search.rb +1 -1
  96. data/lib/spree/core/controller_helpers/store.rb +4 -16
  97. data/lib/spree/core/product_filters.rb +3 -3
  98. data/lib/spree/core/version.rb +3 -1
  99. data/lib/spree/i18n.rb +17 -19
  100. data/lib/spree/permitted_attributes.rb +2 -2
  101. data/lib/spree/service_module.rb +8 -4
  102. data/lib/spree/testing_support/capybara_config.rb +1 -1
  103. data/lib/spree/testing_support/common_rake.rb +1 -1
  104. data/lib/spree/testing_support/controller_requests.rb +10 -10
  105. data/lib/spree/testing_support/factories/shipment_factory.rb +7 -9
  106. data/lib/spree/testing_support/factories/stock_location_factory.rb +2 -2
  107. data/lib/spree/testing_support/factories/store_factory.rb +1 -0
  108. data/lib/spree/testing_support/factories/zone_factory.rb +16 -13
  109. data/lib/spree/testing_support/flatpickr_capybara.rb +101 -0
  110. data/lib/spree/testing_support/locale_helpers.rb +78 -0
  111. data/lib/spree/testing_support/next_instance_of.rb +38 -0
  112. data/lib/spree/testing_support/order_walkthrough.rb +8 -3
  113. data/lib/spree/testing_support/rspec_retry_config.rb +10 -0
  114. data/spree_core.gemspec +5 -4
  115. metadata +71 -26
  116. data/lib/generators/spree/install/templates/config/initializers/spree_storefront.rb +0 -1
  117. data/lib/generators/spree/install/templates/config/spree_storefront.yml +0 -67
  118. data/lib/spree/core/controller_helpers/currency_helpers.rb +0 -15
  119. data/lib/spree/i18n/base.rb +0 -17
  120. data/lib/spree/i18n/initializer.rb +0 -1
@@ -68,7 +68,7 @@ module Spree
68
68
  end
69
69
 
70
70
  def validate_authorization(amount, order_currency)
71
- if amount_remaining.to_d < amount.to_d
71
+ if BigDecimal(amount_remaining, 3) < BigDecimal(amount, 3)
72
72
  errors.add(:base, Spree.t('store_credit_payment_method.insufficient_funds'))
73
73
  elsif currency != order_currency
74
74
  errors.add(:base, Spree.t('store_credit_payment_method.currency_mismatch'))
@@ -3,7 +3,7 @@ module Spree
3
3
  acts_as_paranoid
4
4
  acts_as_list scope: :product
5
5
 
6
- belongs_to :product, touch: true, class_name: 'Spree::Product', inverse_of: :variants
6
+ belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product', inverse_of: :variants
7
7
  belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true
8
8
 
9
9
  delegate :name, :name=, :description, :slug, :available_on, :shipping_category_id,
@@ -123,15 +123,15 @@ module Spree
123
123
  end
124
124
 
125
125
  def tax_category
126
- if self[:tax_category_id].nil?
127
- product.tax_category
128
- else
129
- Spree::TaxCategory.find(self[:tax_category_id])
130
- end
126
+ @tax_category ||= if self[:tax_category_id].nil?
127
+ product.tax_category
128
+ else
129
+ Spree::TaxCategory.find(self[:tax_category_id])
130
+ end
131
131
  end
132
132
 
133
133
  def options_text
134
- Spree::Variants::OptionsPresenter.new(self).to_sentence
134
+ @options_text ||= Spree::Variants::OptionsPresenter.new(self).to_sentence
135
135
  end
136
136
 
137
137
  # Default to master name
@@ -150,13 +150,6 @@ module Spree
150
150
  !!deleted_at
151
151
  end
152
152
 
153
- # Product may be created with deleted_at already set,
154
- # which would make AR's default finder return nil.
155
- # This is a stopgap for that little problem.
156
- def product
157
- Spree::Product.unscoped { super }
158
- end
159
-
160
153
  def options=(options = {})
161
154
  options.each do |option|
162
155
  set_option_value(option[:name], option[:value])
@@ -199,7 +192,7 @@ module Spree
199
192
  end
200
193
 
201
194
  def price_in(currency)
202
- prices.detect { |price| price.currency == currency } || prices.build(currency: currency)
195
+ prices.detect { |price| price.currency == currency&.upcase } || prices.build(currency: currency&.upcase)
203
196
  end
204
197
 
205
198
  def amount_in(currency)
@@ -233,7 +226,7 @@ module Spree
233
226
  end
234
227
 
235
228
  def compare_at_price
236
- price_in(cost_currency).try(:compare_at_amount)
229
+ @compare_at_price ||= price_in(cost_currency).try(:compare_at_amount)
237
230
  end
238
231
 
239
232
  def name_and_sku
@@ -101,8 +101,6 @@ module Spree
101
101
  zone_member.zoneable_id == address.country_id
102
102
  when 'Spree::State'
103
103
  zone_member.zoneable_id == address.state_id
104
- else
105
- false
106
104
  end
107
105
  end
108
106
  end
@@ -114,8 +112,6 @@ module Spree
114
112
  zoneables
115
113
  when 'state' then
116
114
  zoneables.collect(&:country)
117
- else
118
- []
119
115
  end.flatten.compact.uniq
120
116
  end
121
117
 
@@ -171,6 +167,19 @@ module Spree
171
167
  true
172
168
  end
173
169
 
170
+ def state_list
171
+ case kind
172
+ when 'country'
173
+ zoneables.map(&:states)
174
+ when 'state'
175
+ zoneables
176
+ end.flatten.compact.uniq
177
+ end
178
+
179
+ def state_list_for(country)
180
+ state_list.select { |state| state.country == country }
181
+ end
182
+
174
183
  private
175
184
 
176
185
  def remove_defunct_members
@@ -4,7 +4,14 @@ module Spree
4
4
  def initialize(collection, params)
5
5
  @collection = collection
6
6
  @page = params[:page]
7
- @per_page = params[:per_page]
7
+
8
+ per_page_limit = Spree::Api::Config[:api_v2_per_page_limit]
9
+
10
+ @per_page = if params[:per_page].to_i.between?(1, per_page_limit)
11
+ params[:per_page]
12
+ else
13
+ Kaminari.config.default_per_page
14
+ end
8
15
  end
9
16
 
10
17
  def call
@@ -2,6 +2,7 @@ module Spree
2
2
  class VariantPresenter
3
3
  include Rails.application.routes.url_helpers
4
4
  include Spree::BaseHelper
5
+ include Spree::ProductsHelper
5
6
 
6
7
  attr_reader :current_currency, :current_price_options, :current_store
7
8
 
@@ -19,7 +20,7 @@ module Spree
19
20
  display_price: display_price(variant),
20
21
  price: variant.price_in(current_currency),
21
22
  display_compare_at_price: display_compare_at_price(variant),
22
- should_display_compare_at_price: should_display_compare_at_price(variant),
23
+ should_display_compare_at_price: should_display_compare_at_price?(variant),
23
24
  is_product_available_in_currency: @is_product_available_in_currency,
24
25
  backorderable: backorderable?(variant),
25
26
  in_stock: in_stock?(variant),
@@ -75,9 +76,5 @@ module Spree
75
76
  purchasable: variant.purchasable?
76
77
  }
77
78
  end
78
-
79
- def should_display_compare_at_price(variant)
80
- variant.compare_at_price.present? && variant.compare_at_price > variant.price
81
- end
82
79
  end
83
80
  end
@@ -1,7 +1,12 @@
1
1
  module Spree
2
2
  module Account
3
3
  module Addresses
4
- class Create < ::Spree::Account::Addresses::Base
4
+ class Create
5
+ prepend Spree::ServiceModule::Base
6
+ include Spree::Account::Addresses::Helper
7
+
8
+ attr_accessor :country
9
+
5
10
  def call(user:, address_params:)
6
11
  fill_country_and_state_ids(address_params)
7
12
 
@@ -1,9 +1,7 @@
1
1
  module Spree
2
2
  module Account
3
3
  module Addresses
4
- class Base
5
- prepend Spree::ServiceModule::Base
6
-
4
+ module Helper
7
5
  private
8
6
 
9
7
  attr_accessor :country
@@ -1,7 +1,12 @@
1
1
  module Spree
2
2
  module Account
3
3
  module Addresses
4
- class Update < ::Spree::Account::Addresses::Base
4
+ class Update
5
+ prepend Spree::ServiceModule::Base
6
+ include Spree::Account::Addresses::Helper
7
+
8
+ attr_accessor :country
9
+
5
10
  def call(address:, address_params:)
6
11
  address_params[:country_id] ||= address.country_id
7
12
  fill_country_and_state_ids(address_params)
@@ -0,0 +1,101 @@
1
+ require 'uri'
2
+
3
+ module Spree
4
+ class BuildLocalizedRedirectUrl
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ LOCALE_REGEX = /^\/[A-Za-z]{2}\/|^\/[A-Za-z]{2}-[A-Za-z]{2}\/|^\/[A-Za-z]{2}$|^\/[A-Za-z]{2}-[A-Za-z]{2}$/.freeze
8
+
9
+ SUPPORTED_PATHS_REGEX = /\/(products|t\/|cart|checkout|addresses|content)/.freeze
10
+
11
+ # rubocop:disable Lint/UnusedMethodArgument
12
+ def call(url:, locale:, default_locale: nil)
13
+ run :initialize_url_object
14
+ run :generate_new_path
15
+ run :append_locale_param
16
+ run :build_url
17
+ end
18
+ # rubocop:enable Lint/UnusedMethodArgument
19
+
20
+ protected
21
+
22
+ def initialize_url_object(url:, locale:, default_locale:)
23
+ success(
24
+ url: URI(url),
25
+ locale: locale,
26
+ default_locale_supplied: default_locale_supplied?(locale, default_locale)
27
+ )
28
+ end
29
+
30
+ def generate_new_path(url:, locale:, default_locale_supplied:)
31
+ unless supported_path?(url.path)
32
+ return success(
33
+ url: url,
34
+ locale: locale,
35
+ path: cleanup_path(url.path),
36
+ default_locale_supplied: default_locale_supplied,
37
+ locale_added_to_path: false
38
+ )
39
+ end
40
+
41
+ new_path = if default_locale_supplied
42
+ maches_locale_regex?(url.path) ? url.path.gsub(LOCALE_REGEX, '/') : url.path
43
+ else
44
+ maches_locale_regex?(url.path) ? url.path.gsub(LOCALE_REGEX, "/#{locale}/") : "/#{locale}#{url.path}"
45
+ end
46
+
47
+ success(
48
+ url: url,
49
+ locale: locale,
50
+ path: cleanup_path(new_path),
51
+ default_locale_supplied: default_locale_supplied,
52
+ locale_added_to_path: true
53
+ )
54
+ end
55
+
56
+ def append_locale_param(url:, locale:, path:, default_locale_supplied:, locale_added_to_path:)
57
+ return success(url: url, path: path, query: url.query) if locale_added_to_path
58
+
59
+ query_params = Rack::Utils.parse_nested_query(url.query)
60
+
61
+ if default_locale_supplied
62
+ query_params.delete('locale')
63
+ else
64
+ query_params.merge!('locale' => locale)
65
+ end
66
+
67
+ query_string = query_params.any? ? query_params.to_query : nil
68
+
69
+ success(url: url, path: path, query: query_string)
70
+ end
71
+
72
+ def build_url(url:, path:, query:)
73
+ localized_url = builder_class(url).build(host: url.host, port: url.port, path: path, query: query).to_s
74
+ success(localized_url)
75
+ end
76
+
77
+ private
78
+
79
+ def supported_path?(path)
80
+ return true if path.blank? || path == '/' || maches_locale_regex?(path)
81
+
82
+ path.match(SUPPORTED_PATHS_REGEX)
83
+ end
84
+
85
+ def maches_locale_regex?(path)
86
+ path.match(LOCALE_REGEX)[0].gsub('/', '') if path.match(LOCALE_REGEX)
87
+ end
88
+
89
+ def default_locale_supplied?(locale, default_locale)
90
+ default_locale.present? && default_locale.to_s == locale.to_s
91
+ end
92
+
93
+ def cleanup_path(path)
94
+ path.chomp('/').gsub('//', '/')
95
+ end
96
+
97
+ def builder_class(url)
98
+ url.scheme == 'http' ? URI::HTTP : URI::HTTPS
99
+ end
100
+ end
101
+ end
@@ -24,7 +24,7 @@ module Spree
24
24
  if country_iso.present?
25
25
  ::Spree::Country.by_iso(country_iso)&.id
26
26
  else
27
- ::Spree::Country.default.id
27
+ Spree::Config[:default_country_id]
28
28
  end
29
29
  end
30
30
 
@@ -1,9 +1,11 @@
1
1
  # This class should be refactored
2
2
  module Spree
3
3
  class CompareLineItems
4
- prepend Spree::ServiceModule:: Base
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call(order:, line_item:, options: {}, comparison_hooks: nil)
7
+ comparison_hooks ||= Rails.application.config.spree.line_item_comparison_hooks
5
8
 
6
- def call(order:, line_item:, options: {}, comparison_hooks: Rails.application.config.spree.line_item_comparison_hooks)
7
9
  legacy_part = comparison_hooks.all? do |hook|
8
10
  order.send(hook, line_item, options)
9
11
  end
@@ -0,0 +1,35 @@
1
+ module Spree
2
+ class BaseSorter
3
+ def initialize(scope, params = {}, allowed_sort_attributes = [])
4
+ @scope = scope
5
+ @sort = params[:sort]
6
+ @allowed_sort_attributes = allowed_sort_attributes
7
+ end
8
+
9
+ def call
10
+ by_param_attribute(scope)
11
+ end
12
+
13
+ protected
14
+
15
+ attr_reader :scope, :collection, :sort, :allowed_sort_attributes
16
+
17
+ def by_param_attribute(scope)
18
+ return scope if sort_field.blank? || !allowed_sort_attributes.include?(sort_field.to_sym)
19
+
20
+ scope.order("#{sort_field}": order_direction)
21
+ end
22
+
23
+ def desc_order
24
+ @desc_order ||= String(sort)[0] == '-'
25
+ end
26
+
27
+ def sort_field
28
+ @sort_field ||= desc_order ? sort[1..-1] : sort
29
+ end
30
+
31
+ def order_direction
32
+ desc_order ? :desc : :asc
33
+ end
34
+ end
35
+ end
@@ -1,42 +1,6 @@
1
1
  module Spree
2
2
  module Orders
3
- class Sort
4
- attr_reader :scope, :sort
5
-
6
- def initialize(scope, params)
7
- @scope = scope
8
- @sort = params[:sort]
9
- end
10
-
11
- def call
12
- orders = completed_at(scope)
13
-
14
- orders
15
- end
16
-
17
- private
18
-
19
- def desc_order
20
- @desc_order ||= String(sort)[0] == '-'
21
- end
22
-
23
- def sort_field
24
- @sort_field ||= desc_order ? sort[1..-1] : sort
25
- end
26
-
27
- def order_direction
28
- desc_order ? :asc : :desc
29
- end
30
-
31
- def completed_at?
32
- sort_field.eql?('completed_at')
33
- end
34
-
35
- def completed_at(orders)
36
- return orders unless completed_at?
37
-
38
- orders.order(completed_at: order_direction)
39
- end
3
+ class Sort < ::Spree::BaseSorter
40
4
  end
41
5
  end
42
6
  end
@@ -1,53 +1,30 @@
1
1
  module Spree
2
2
  module Products
3
- class Sort
4
- def initialize(scope, params, current_currency)
5
- @scope = scope
6
- @sort = params[:sort]
3
+ class Sort < ::Spree::BaseSorter
4
+ def initialize(scope, current_currency, params = {}, allowed_sort_attributes = [])
5
+ super(scope, params, allowed_sort_attributes)
7
6
  @currency = params[:currency] || current_currency
8
7
  end
9
8
 
10
9
  def call
11
- products = updated_at(scope)
12
- products = price(products)
10
+ products = by_param_attribute(scope)
11
+ products = by_price(products)
13
12
 
14
13
  products.distinct
15
14
  end
16
15
 
17
16
  private
18
17
 
19
- attr_reader :sort, :scope, :currency
20
-
21
- def desc_order
22
- @desc_order ||= String(sort)[0] == '-'
23
- end
24
-
25
- def sort_field
26
- @sort_field ||= desc_order ? sort[1..-1] : sort
27
- end
28
-
29
- def updated_at?
30
- sort_field == 'updated_at'
31
- end
18
+ attr_reader :sort, :scope, :currency, :allowed_sort_attributes
32
19
 
33
20
  def price?
34
21
  sort_field == 'price'
35
22
  end
36
23
 
37
- def order_direction
38
- desc_order ? :desc : :asc
39
- end
40
-
41
- def updated_at(products)
42
- return products unless updated_at?
43
-
44
- products.order(updated_at: order_direction)
45
- end
46
-
47
- def price(products)
48
- return products unless price?
24
+ def by_price(scope)
25
+ return scope unless price?
49
26
 
50
- products.joins(master: :prices).
27
+ scope.joins(master: :prices).
51
28
  select("#{Spree::Product.table_name}.*, #{Spree::Price.table_name}.amount").
52
29
  distinct.
53
30
  where(spree_prices: { currency: currency }).