spree_core 4.8.3 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +57 -0
  3. data/app/finders/spree/line_items/find_by_variant.rb +4 -2
  4. data/app/finders/spree/orders/find_complete.rb +3 -13
  5. data/app/finders/spree/orders/find_current.rb +3 -1
  6. data/app/finders/spree/orders/finder_helper.rb +17 -0
  7. data/app/finders/spree/products/find.rb +1 -1
  8. data/app/finders/spree/variants/find.rb +30 -0
  9. data/app/models/spree/address.rb +12 -2
  10. data/app/models/spree/adjustment.rb +5 -0
  11. data/app/models/spree/asset.rb +8 -0
  12. data/app/models/spree/calculator/flat_rate.rb +3 -0
  13. data/app/models/spree/calculator/flexi_rate.rb +3 -0
  14. data/app/models/spree/calculator/percent_on_line_item.rb +3 -0
  15. data/app/models/spree/calculator/shipping/flat_rate.rb +13 -1
  16. data/app/models/spree/cms_section_image.rb +0 -6
  17. data/app/models/spree/customer_return.rb +1 -0
  18. data/app/models/spree/fulfilment_changer.rb +1 -0
  19. data/app/models/spree/icon.rb +0 -6
  20. data/app/models/spree/image/configuration/active_storage.rb +0 -8
  21. data/app/models/spree/image.rb +17 -0
  22. data/app/models/spree/line_item.rb +26 -7
  23. data/app/models/spree/option_value_variant.rb +4 -0
  24. data/app/models/spree/order/currency_updater.rb +2 -2
  25. data/app/models/spree/order/webhooks.rb +19 -0
  26. data/app/models/spree/order.rb +17 -6
  27. data/app/models/spree/order_merger.rb +6 -0
  28. data/app/models/spree/payment/gateway_options.rb +4 -0
  29. data/app/models/spree/payment/webhooks.rb +15 -0
  30. data/app/models/spree/payment.rb +7 -7
  31. data/app/models/spree/price.rb +53 -1
  32. data/app/models/spree/product/webhooks.rb +17 -0
  33. data/app/models/spree/product.rb +7 -9
  34. data/app/models/spree/promotion/actions/free_shipping.rb +13 -0
  35. data/app/models/spree/promotion/rules/first_order.rb +6 -1
  36. data/app/models/spree/promotion.rb +2 -2
  37. data/app/models/spree/promotion_handler/coupon.rb +3 -2
  38. data/app/models/spree/refund.rb +14 -2
  39. data/app/models/spree/reimbursement/emails.rb +11 -0
  40. data/app/models/spree/reimbursement.rb +1 -6
  41. data/app/models/spree/role_user.rb +1 -1
  42. data/app/models/spree/shipment/emails.rb +11 -0
  43. data/app/models/spree/shipment/webhooks.rb +11 -0
  44. data/app/models/spree/shipment.rb +60 -7
  45. data/app/models/spree/shipment_handler.rb +2 -6
  46. data/app/models/spree/shipping_method.rb +24 -2
  47. data/app/models/spree/shipping_method_zone.rb +4 -2
  48. data/app/models/spree/shipping_rate.rb +1 -1
  49. data/app/models/spree/stock/package.rb +4 -0
  50. data/app/models/spree/stock/quantifier.rb +22 -4
  51. data/app/models/spree/stock_item/webhooks.rb +6 -0
  52. data/app/models/spree/stock_item.rb +1 -3
  53. data/app/models/spree/stock_location.rb +15 -0
  54. data/app/models/spree/stock_movement/webhooks.rb +6 -0
  55. data/app/models/spree/stock_movement.rb +1 -3
  56. data/app/models/spree/store.rb +2 -9
  57. data/app/models/spree/store_credit.rb +11 -12
  58. data/app/models/spree/store_favicon_image.rb +0 -6
  59. data/app/models/spree/store_logo.rb +0 -5
  60. data/app/models/spree/store_mailer_logo.rb +0 -6
  61. data/app/models/spree/tax_rate.rb +3 -1
  62. data/app/models/spree/taxon.rb +83 -17
  63. data/app/models/spree/taxon_image/configuration/active_storage.rb +0 -6
  64. data/app/models/spree/taxonomy.rb +1 -0
  65. data/app/models/spree/variant/webhooks.rb +6 -0
  66. data/app/models/spree/variant.rb +29 -6
  67. data/app/models/spree/wishlist.rb +9 -0
  68. data/app/presenters/spree/variants/options_presenter.rb +34 -2
  69. data/app/services/spree/cart/add_item.rb +2 -0
  70. data/app/services/spree/cart/destroy.rb +14 -4
  71. data/app/services/spree/seeds/all.rb +3 -0
  72. data/app/services/spree/seeds/payment_methods.rb +18 -0
  73. data/app/services/spree/seeds/stock_locations.rb +1 -1
  74. data/app/services/spree/seeds/zones.rb +19 -19
  75. data/app/services/spree/tracking_numbers/base_service.rb +19 -0
  76. data/config/locales/en.yml +2 -0
  77. data/db/migrate/20240623172111_add_deleted_at_to_spree_stock_locations.rb +6 -0
  78. data/db/migrate/20240725124530_add_refunder_to_spree_refunds.rb +6 -0
  79. data/db/migrate/20240822163534_add_pretty_name_to_spree_taxons.rb +9 -0
  80. data/lib/spree/core/components.rb +6 -0
  81. data/lib/spree/core/controller_helpers/order.rb +5 -5
  82. data/lib/spree/core/dependencies.rb +5 -1
  83. data/lib/spree/core/engine.rb +1 -1
  84. data/lib/spree/core/preferences/preferable.rb +4 -2
  85. data/lib/spree/core/preferences/preferable_class_methods.rb +3 -2
  86. data/lib/spree/core/runtime_configuration.rb +1 -0
  87. data/lib/spree/core/version.rb +1 -1
  88. data/lib/spree/core/webhooks.rb +15 -7
  89. data/lib/spree/core.rb +1 -0
  90. data/lib/spree/money.rb +1 -1
  91. data/lib/spree/permitted_attributes.rb +1 -0
  92. data/lib/spree/testing_support/authorization_helpers.rb +2 -2
  93. data/lib/spree/testing_support/capybara_config.rb +8 -4
  94. data/lib/spree/testing_support/common_rake.rb +1 -1
  95. data/spree_core.gemspec +5 -3
  96. metadata +54 -8
  97. data/LICENSE +0 -26
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ class StockItem < Spree::Base
3
+ module Webhooks
4
+ end
5
+ end
6
+ end
@@ -3,9 +3,7 @@ module Spree
3
3
  acts_as_paranoid
4
4
 
5
5
  include Spree::Metadata
6
- if defined?(Spree::Webhooks::HasWebhooks)
7
- include Spree::Webhooks::HasWebhooks
8
- end
6
+ include Spree::StockItem::Webhooks
9
7
 
10
8
  with_options inverse_of: :stock_items do
11
9
  belongs_to :stock_location, class_name: 'Spree::StockLocation'
@@ -11,6 +11,8 @@ module Spree
11
11
  include Spree::VendorConcern
12
12
  end
13
13
 
14
+ acts_as_paranoid
15
+
14
16
  has_many :shipments
15
17
  has_many :stock_items, dependent: :delete_all, inverse_of: :stock_location
16
18
  has_many :variants, through: :stock_items
@@ -122,6 +124,19 @@ module Spree
122
124
  end
123
125
  end
124
126
 
127
+ def address
128
+ Spree::Address.new(
129
+ address1: address1,
130
+ address2: address2,
131
+ city: city,
132
+ state: state,
133
+ state_name: state_name,
134
+ country: country,
135
+ zipcode: zipcode,
136
+ phone: phone
137
+ )
138
+ end
139
+
125
140
  private
126
141
 
127
142
  def create_stock_items
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ class StockMovement < Spree::Base
3
+ module Webhooks
4
+ end
5
+ end
6
+ end
@@ -5,9 +5,7 @@ module Spree
5
5
  min: -2**31
6
6
  }.freeze
7
7
 
8
- if defined?(Spree::Webhooks::HasWebhooks)
9
- include Spree::Webhooks::HasWebhooks
10
- end
8
+ include Spree::StockMovement::Webhooks
11
9
 
12
10
  belongs_to :stock_item, class_name: 'Spree::StockItem', inverse_of: :stock_movements
13
11
  belongs_to :originator, polymorphic: true
@@ -110,11 +110,7 @@ module Spree
110
110
  after_commit :clear_cache
111
111
 
112
112
  def self.current(url = nil)
113
- Spree::Deprecation.warn(<<-DEPRECATION, caller)
114
- `Spree::Store.current` is deprecated and will be removed in Spree 5.0
115
- Please use `Spree::Stores::FindCurrent.new(url: "https://example.com").execute` instead
116
- DEPRECATION
117
- Stores::FindCurrent.new(url: url).execute
113
+ Spree::Dependencies.current_store_finder.constantize.new(url: url).execute
118
114
  end
119
115
 
120
116
  # FIXME: we need to drop `or_initialize` in v5
@@ -131,9 +127,7 @@ module Spree
131
127
  end
132
128
 
133
129
  def self.available_locales
134
- Rails.cache.fetch('stores_available_locales') do
135
- Spree::Store.all.map(&:supported_locales_list).flatten.uniq
136
- end
130
+ Spree::Store.all.map(&:supported_locales_list).flatten.uniq
137
131
  end
138
132
 
139
133
  def default_menu(location)
@@ -249,7 +243,6 @@ module Spree
249
243
 
250
244
  def clear_cache
251
245
  Rails.cache.delete('default_store')
252
- Rails.cache.delete('stores_available_locales')
253
246
  end
254
247
 
255
248
  def ensure_default_country
@@ -19,13 +19,16 @@ module Spree
19
19
  DEFAULT_CREATED_BY_EMAIL = 'spree@example.com'.freeze
20
20
 
21
21
  belongs_to :user, class_name: "::#{Spree.user_class}", foreign_key: 'user_id'
22
- belongs_to :category, class_name: 'Spree::StoreCreditCategory'
23
- belongs_to :created_by, class_name: Spree.admin_user_class.to_s, foreign_key: 'created_by_id'
24
- belongs_to :credit_type, class_name: 'Spree::StoreCreditType', foreign_key: 'type_id'
22
+ belongs_to :category, class_name: 'Spree::StoreCreditCategory', optional: true
23
+ belongs_to :created_by, class_name: "::#{Spree.admin_user_class}", foreign_key: 'created_by_id', optional: true
24
+ belongs_to :credit_type, class_name: 'Spree::StoreCreditType', foreign_key: 'type_id', optional: true
25
25
  belongs_to :store, class_name: 'Spree::Store'
26
+
26
27
  has_many :store_credit_events, class_name: 'Spree::StoreCreditEvent'
28
+ has_many :payments, as: :source, class_name: 'Spree::Payment'
29
+ has_many :orders, through: :payments, class_name: 'Spree::Order'
27
30
 
28
- validates :user, :category, :credit_type, :created_by, :currency, :store, presence: true
31
+ validates :currency, :store, presence: true
29
32
  validates :amount, numericality: { greater_than: 0 }
30
33
  validates :amount_used, numericality: { greater_than_or_equal_to: 0 }
31
34
  validate :amount_used_less_than_or_equal_to_amount
@@ -36,7 +39,10 @@ module Spree
36
39
 
37
40
  scope :order_by_priority, -> { includes(:credit_type).order('spree_store_credit_types.priority ASC') }
38
41
 
39
- before_validation :associate_credit_type
42
+ scope :not_authorized, -> { where(amount_authorized: 0) }
43
+ scope :not_used, -> { where(amount_authorized: 0) }
44
+ scope :available, -> { not_authorized.not_used }
45
+
40
46
  after_save :store_event
41
47
  before_destroy :validate_no_amount_used
42
48
 
@@ -249,12 +255,5 @@ module Spree
249
255
  throw(:abort)
250
256
  end
251
257
  end
252
-
253
- def associate_credit_type
254
- unless type_id
255
- credit_type_name = category.try(:non_expiring?) ? 'Non-expiring' : 'Expiring'
256
- self.credit_type = Spree::StoreCreditType.find_or_create_by(name: credit_type_name)
257
- end
258
- end
259
258
  end
260
259
  end
@@ -1,11 +1,5 @@
1
1
  module Spree
2
2
  class StoreFaviconImage < Asset
3
- if Spree.public_storage_service_name
4
- has_one_attached :attachment, service: Spree.public_storage_service_name
5
- else
6
- has_one_attached :attachment
7
- end
8
-
9
3
  VALID_CONTENT_TYPES = ['image/png', 'image/x-icon', 'image/vnd.microsoft.icon'].freeze
10
4
 
11
5
  validates :attachment,
@@ -1,9 +1,4 @@
1
1
  module Spree
2
2
  class StoreLogo < Asset
3
- if Spree.public_storage_service_name
4
- has_one_attached :attachment, service: Spree.public_storage_service_name
5
- else
6
- has_one_attached :attachment
7
- end
8
3
  end
9
4
  end
@@ -1,11 +1,5 @@
1
1
  module Spree
2
2
  class StoreMailerLogo < Asset
3
- if Spree.public_storage_service_name
4
- has_one_attached :attachment, service: Spree.public_storage_service_name
5
- else
6
- has_one_attached :attachment
7
- end
8
-
9
3
  VALID_CONTENT_TYPES = ['image/png', 'image/jpg', 'image/jpeg'].freeze
10
4
 
11
5
  validates :attachment, content_type: VALID_CONTENT_TYPES
@@ -115,11 +115,13 @@ module Spree
115
115
 
116
116
  def amount_for_label
117
117
  return '' unless show_rate_in_label?
118
+ return '' if amount.zero?
118
119
 
119
120
  ' ' + ActiveSupport::NumberHelper::NumberToPercentageConverter.convert(
120
121
  amount * 100,
121
122
  locale: I18n.locale,
122
- strip_insignificant_zeros: true
123
+ strip_insignificant_zeros: true,
124
+ precision: 2
123
125
  )
124
126
  end
125
127
  end
@@ -13,7 +13,6 @@ module Spree
13
13
  extend FriendlyId
14
14
  friendly_id :permalink, slug_column: :permalink, use: :history
15
15
  before_validation :set_permalink, on: :create, if: :name
16
- alias_attribute :slug, :permalink
17
16
 
18
17
  acts_as_nested_set dependent: :destroy
19
18
 
@@ -21,6 +20,8 @@ module Spree
21
20
  has_many :classifications, -> { order(:position) }, dependent: :delete_all, inverse_of: :taxon
22
21
  has_many :products, through: :classifications
23
22
 
23
+ delegate :store, to: :taxonomy
24
+
24
25
  has_many :menu_items, as: :linked_resource
25
26
  has_many :cms_sections, as: :linked_resource
26
27
 
@@ -44,9 +45,13 @@ module Spree
44
45
  end
45
46
 
46
47
  before_validation :copy_taxonomy_from_parent
48
+ before_save :set_pretty_name
49
+ before_save :set_permalink
47
50
  after_save :touch_ancestors_and_taxonomy
48
51
  after_update :sync_taxonomy_name
49
52
  after_touch :touch_ancestors_and_taxonomy
53
+ after_commit :regenerate_pretty_name_and_permalink, on: :update, if: :should_regenerate_pretty_name_and_permalink?
54
+ after_move :regenerate_pretty_name_and_permalink
50
55
 
51
56
  has_one :store, through: :taxonomy
52
57
 
@@ -59,17 +64,45 @@ module Spree
59
64
 
60
65
  scope :for_stores, ->(stores) { joins(:taxonomy).where(spree_taxonomies: { store_id: stores.ids }) }
61
66
 
62
- TRANSLATABLE_FIELDS = %i[name description permalink].freeze
67
+ TRANSLATABLE_FIELDS = %i[name pretty_name description permalink].freeze
63
68
  translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
64
69
 
70
+ def slug
71
+ permalink
72
+ end
73
+
74
+ def slug=(value)
75
+ self.permalink = value
76
+ end
77
+
65
78
  self::Translation.class_eval do
66
- alias_attribute :slug, :permalink
67
79
  before_save :set_permalink
80
+ before_save :set_pretty_name
81
+
82
+ def slug
83
+ permalink
84
+ end
85
+
86
+ def slug=(value)
87
+ self.permalink = value
88
+ end
68
89
 
69
90
  def set_permalink
70
91
  self.permalink = generate_slug
71
92
  end
72
93
 
94
+ def set_pretty_name
95
+ self[:pretty_name] = generate_pretty_name
96
+ end
97
+
98
+ def name_with_fallback
99
+ name.blank? ? translated_model.name : name
100
+ end
101
+
102
+ def pretty_name_with_fallback
103
+ pretty_name.blank? ? translated_model.pretty_name : pretty_name
104
+ end
105
+
73
106
  private
74
107
 
75
108
  def generate_slug
@@ -82,12 +115,22 @@ module Spree
82
115
  end
83
116
  end
84
117
 
118
+ def generate_pretty_name
119
+ if parent.present?
120
+ generate_pretty_name_including_parent
121
+ elsif pretty_name.blank?
122
+ pretty_name_with_fallback
123
+ else
124
+ pretty_name
125
+ end
126
+ end
127
+
85
128
  def generate_permalink_including_parent
86
129
  [parent_permalink_with_fallback, (permalink.blank? ? name_with_fallback.to_url : permalink.split('/').last.to_url)].join('/')
87
130
  end
88
131
 
89
- def name_with_fallback
90
- name.blank? ? translated_model.name : name
132
+ def generate_pretty_name_including_parent
133
+ [parent_pretty_name_with_fallback, (pretty_name.blank? ? name_with_fallback : pretty_name)].compact.join(' -> ')
91
134
  end
92
135
 
93
136
  def parent
@@ -98,6 +141,11 @@ module Spree
98
141
  localized_parent = parent.translations.find_by(locale: locale)
99
142
  localized_parent.present? ? localized_parent.permalink : parent.permalink
100
143
  end
144
+
145
+ def parent_pretty_name_with_fallback
146
+ localized_parent = parent.translations.find_by(locale: locale)
147
+ localized_parent.present? ? localized_parent.pretty_name : parent.pretty_name
148
+ end
101
149
  end
102
150
 
103
151
  # indicate which filters should be used for a taxon
@@ -117,13 +165,12 @@ module Spree
117
165
  meta_title.blank? ? name : meta_title
118
166
  end
119
167
 
120
- # Creates permalink base for friendly_id
121
- def set_permalink
122
- if Spree.use_translations?
123
- translations.each(&:set_permalink)
124
- else
125
- self.permalink = generate_slug
126
- end
168
+ def set_pretty_name
169
+ self[:pretty_name] = generate_pretty_name
170
+ end
171
+
172
+ def generate_pretty_name
173
+ [parent&.pretty_name, name].compact.join(' -> ')
127
174
  end
128
175
 
129
176
  def generate_slug
@@ -136,15 +183,30 @@ module Spree
136
183
  end
137
184
  end
138
185
 
186
+ def set_permalink
187
+ if Spree.use_translations?
188
+ translations.each(&:set_permalink)
189
+ else
190
+ self.permalink = generate_slug
191
+ end
192
+ end
193
+
139
194
  def active_products
140
195
  products.active
141
196
  end
142
197
 
143
- def pretty_name
144
- ancestor_chain = ancestors.inject('') do |name, ancestor|
145
- name += "#{ancestor.name} -> "
146
- end
147
- ancestor_chain + name.to_s
198
+ def regenerate_pretty_name_and_permalink
199
+ set_permalink
200
+ update_columns(pretty_name: generate_pretty_name, updated_at: Time.current)
201
+
202
+ children.reload.each(&:regenerate_pretty_name_and_permalink_as_child)
203
+ end
204
+
205
+ def regenerate_pretty_name_and_permalink_as_child
206
+ set_permalink
207
+ update_columns(pretty_name: generate_pretty_name, updated_at: Time.current)
208
+
209
+ children.reload.each(&:regenerate_pretty_name_and_permalink_as_child)
148
210
  end
149
211
 
150
212
  def cached_self_and_descendants_ids
@@ -165,6 +227,10 @@ module Spree
165
227
 
166
228
  private
167
229
 
230
+ def should_regenerate_pretty_name_and_permalink?
231
+ saved_changes.key?(:name) || saved_changes.key?(:permalink)
232
+ end
233
+
168
234
  def sync_taxonomy_name
169
235
  if saved_changes.key?(:name) && root?
170
236
  return if taxonomy.name.to_s == name.to_s
@@ -5,12 +5,6 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- if Spree.public_storage_service_name
9
- has_one_attached :attachment, service: Spree.public_storage_service_name
10
- else
11
- has_one_attached :attachment
12
- end
13
-
14
8
  validates :attachment, content_type: /\Aimage\/.*\z/
15
9
 
16
10
  default_scope { includes(attachment_attachment: :blob) }
@@ -2,6 +2,7 @@ module Spree
2
2
  class Taxonomy < Spree::Base
3
3
  include Spree::TranslatableResource
4
4
  include Spree::Metadata
5
+ include Spree::SingleStoreResource
5
6
  if defined?(Spree::Webhooks::HasWebhooks)
6
7
  include Spree::Webhooks::HasWebhooks
7
8
  end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ class Variant < Spree::Base
3
+ module Webhooks
4
+ end
5
+ end
6
+ end
@@ -5,9 +5,7 @@ module Spree
5
5
 
6
6
  include Spree::MemoizedData
7
7
  include Spree::Metadata
8
- if defined?(Spree::Webhooks::HasWebhooks)
9
- include Spree::Webhooks::HasWebhooks
10
- end
8
+ include Spree::Variant::Webhooks
11
9
 
12
10
  MEMOIZED_METHODS = %w(purchasable in_stock backorderable tax_category options_text compare_at_price)
13
11
 
@@ -108,6 +106,14 @@ module Spree
108
106
  not_discontinued.not_deleted.
109
107
  for_currency_and_available_price_amount(currency)
110
108
  end
109
+
110
+ scope :with_option_value, lambda { |option_name, option_value|
111
+ option_type_ids = OptionType.where(name: option_name).ids
112
+ return none if option_type_ids.empty?
113
+
114
+ joins(:option_values).where(Spree::OptionValue.table_name => { name: option_value, option_type_id: option_type_ids })
115
+ }
116
+
111
117
  # FIXME: cost price should be represented with DisplayMoney class
112
118
  LOCALIZED_NUMBERS = %w(cost_price weight depth width height)
113
119
 
@@ -122,13 +128,18 @@ module Spree
122
128
  self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku)
123
129
 
124
130
  def self.product_name_or_sku_cont(query)
131
+ sanitized_query = ActiveRecord::Base.sanitize_sql_like(query.to_s.downcase.strip)
132
+ query_pattern = "%#{sanitized_query}%"
133
+ sku_condition = arel_table[:sku].lower.matches(query_pattern)
134
+
125
135
  if Spree.use_translations?
136
+ product_name_condition = Product.translation_table[:name].lower.matches(query_pattern)
126
137
  joins(:product).
127
138
  join_translation_table(Product).
128
- where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)
129
- OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
139
+ where(product_name_condition.or(sku_condition))
130
140
  else
131
- joins(:product).where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
141
+ product_name_condition = Product.arel_table[:name].lower.matches(query_pattern)
142
+ joins(:product).where(product_name_condition.or(sku_condition))
132
143
  end
133
144
  end
134
145
 
@@ -172,6 +183,18 @@ module Spree
172
183
  !!deleted_at
173
184
  end
174
185
 
186
+ # Returns an array of hashes with the option type name, value and presentation
187
+ # @return [Array<Hash>]
188
+ def options
189
+ option_values.includes(:option_type).map do |option_value|
190
+ {
191
+ name: option_value.option_type.name,
192
+ value: option_value.name,
193
+ presentation: option_value.presentation
194
+ }
195
+ end
196
+ end
197
+
175
198
  def options=(options = {})
176
199
  options.each do |option|
177
200
  next if option[:name].blank? || option[:value].blank?
@@ -15,6 +15,8 @@ module Spree
15
15
  belongs_to :store, class_name: 'Spree::Store'
16
16
 
17
17
  has_many :wished_items, class_name: 'Spree::WishedItem', dependent: :destroy
18
+ has_many :variants, through: :wished_items, source: :variant, class_name: 'Spree::Variant'
19
+ has_many :products, -> { distinct }, through: :variants, source: :product, class_name: 'Spree::Product'
18
20
 
19
21
  after_commit :ensure_default_exists_and_is_unique
20
22
  validates :name, :store, :user, presence: true
@@ -27,6 +29,13 @@ module Spree
27
29
  token
28
30
  end
29
31
 
32
+ # returns the number of wished items in the wishlist
33
+ #
34
+ # @return [Integer]
35
+ def wished_items_count
36
+ @wished_items_count ||= variant_ids.count
37
+ end
38
+
30
39
  def self.get_by_param(param)
31
40
  find_by(token: param)
32
41
  end
@@ -19,10 +19,22 @@ module Spree
19
19
  join_options(options)
20
20
  end
21
21
 
22
+ def to_hash
23
+ options = option_values
24
+ options = sort_options(options)
25
+ options = present_options_as_hash(options)
26
+
27
+ join_hash_options(options)
28
+ end
29
+
22
30
  private
23
31
 
24
32
  def sort_options(options)
25
- options.sort_by { |o| o.option_type.position }
33
+ if options.first&.association(:option_type)&.loaded?
34
+ options.sort_by { |o| o.option_type.position }
35
+ else
36
+ options.includes(:option_type).sort_by { |o| o.option_type.position }
37
+ end
26
38
  end
27
39
 
28
40
  def present_options(options)
@@ -34,7 +46,7 @@ module Spree
34
46
  end
35
47
 
36
48
  def present_color_option(option)
37
- "#{option.option_type.presentation}: #{option.name}"
49
+ "#{option.option_type.presentation}: #{option.presentation}"
38
50
  end
39
51
 
40
52
  def present_option(option)
@@ -44,6 +56,26 @@ module Spree
44
56
  def join_options(options)
45
57
  options.to_sentence(words_connector: WORDS_CONNECTOR, two_words_connector: WORDS_CONNECTOR)
46
58
  end
59
+
60
+ def present_options_as_hash(options)
61
+ options.map do |ov|
62
+ method = "present_#{ov.option_type.name}_option_as_hash"
63
+
64
+ respond_to?(method, true) ? send(method, ov) : present_option_as_hash(ov)
65
+ end
66
+ end
67
+
68
+ def present_option_as_hash(option)
69
+ {}.tap do |hash|
70
+ hash.store(option.option_type.presentation.downcase, option.presentation)
71
+ end
72
+ end
73
+
74
+ def join_hash_options(options)
75
+ return {} if options.empty?
76
+
77
+ options.inject(:merge).symbolize_keys
78
+ end
47
79
  end
48
80
  end
49
81
  end
@@ -16,6 +16,8 @@ module Spree
16
16
  options ||= {}
17
17
  quantity ||= 1
18
18
 
19
+ return failure(variant, "#{variant.name} is not available in #{order.currency}") if variant.amount_in(order.currency).nil?
20
+
19
21
  line_item = Spree::Dependencies.line_item_by_variant_finder.constantize.new.execute(order: order, variant: variant, options: options)
20
22
 
21
23
  line_item_created = line_item.nil?
@@ -7,31 +7,41 @@ module Spree
7
7
  run :check_if_can_be_destroyed
8
8
  run :cancel_shipments
9
9
  run :void_payments
10
+ run :clear_addresses
10
11
  run :destroy_order
11
12
  end
12
13
 
13
14
  private
14
15
 
15
16
  def check_if_can_be_destroyed(order:)
16
- return failure(Spree.t(:cannot_be_destroyed)) unless order&.can_be_destroyed?
17
+ return failure(false, Spree.t(:cannot_be_destroyed)) unless order&.can_be_destroyed?
17
18
 
18
19
  success(order: order)
19
20
  end
20
21
 
21
22
  def cancel_shipments(order:)
22
- order.shipments.each(&:cancel!)
23
+ order.shipments.each(&:cancel)
23
24
 
24
25
  success(order: order)
25
26
  end
26
27
 
27
28
  def void_payments(order:)
28
- order.payments.each(&:void!)
29
+ order.payments.each(&:void)
30
+
31
+ success(order: order)
32
+ end
33
+
34
+ def clear_addresses(order:)
35
+ order.ship_address = nil unless order.ship_address&.can_be_deleted?
36
+ order.bill_address = nil unless order.bill_address&.can_be_deleted?
29
37
 
30
38
  success(order: order)
31
39
  end
32
40
 
33
41
  def destroy_order(order:)
34
- order.destroy
42
+ destroyed_result = order.destroy
43
+
44
+ return failure(false, Spree.t(:cannot_be_destroyed)) unless destroyed_result.present?
35
45
 
36
46
  success(order)
37
47
  end
@@ -21,6 +21,9 @@ module Spree
21
21
  # store & stock location
22
22
  Stores.call
23
23
  StockLocations.call
24
+
25
+ # add store resources
26
+ PaymentMethods.call
24
27
  end
25
28
  end
26
29
  end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Seeds
3
+ class PaymentMethods
4
+ prepend Spree::ServiceModule::Base
5
+
6
+ def call
7
+ payment_method = Spree::PaymentMethod::StoreCredit.find_or_initialize_by(
8
+ name: Spree.t(:store_credit_name),
9
+ description: Spree.t(:store_credit_name),
10
+ active: true
11
+ )
12
+
13
+ payment_method.stores = Spree::Store.all if payment_method.new_record?
14
+ payment_method.save!
15
+ end
16
+ end
17
+ end
18
+ end
@@ -6,7 +6,7 @@ module Spree
6
6
  def call
7
7
  country = Spree::Store.default.default_country
8
8
  Spree::StockLocation.find_or_create_by!(
9
- name: 'default',
9
+ name: Spree.t(:default_stock_location_name),
10
10
  propagate_all_variants: false,
11
11
  country: country,
12
12
  active: true,