spree_core 4.4.0 → 4.6.0

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/option_values/find_available.rb +1 -1
  3. data/app/finders/spree/product_properties/find_available.rb +1 -1
  4. data/app/finders/spree/products/find.rb +21 -15
  5. data/app/finders/spree/taxons/find.rb +11 -8
  6. data/app/helpers/spree/base_helper.rb +14 -11
  7. data/app/helpers/spree/locale_helper.rb +6 -2
  8. data/app/helpers/spree/products_helper.rb +9 -4
  9. data/app/jobs/spree/variants/remove_from_incomplete_orders_job.rb +9 -0
  10. data/app/jobs/spree/variants/remove_line_item_job.rb +9 -0
  11. data/app/models/concerns/spree/calculated_adjustments.rb +1 -1
  12. data/app/models/concerns/spree/display_link.rb +17 -29
  13. data/app/models/concerns/spree/image_methods.rb +21 -9
  14. data/app/models/concerns/spree/metadata.rb +2 -2
  15. data/app/models/concerns/spree/product_scopes.rb +34 -28
  16. data/app/models/concerns/spree/translatable_resource.rb +25 -0
  17. data/app/models/concerns/spree/translatable_resource_scopes.rb +24 -0
  18. data/app/models/concerns/spree/translatable_resource_slug.rb +17 -0
  19. data/app/models/spree/address.rb +7 -1
  20. data/app/models/spree/asset/support/active_storage.rb +3 -2
  21. data/app/models/spree/asset.rb +3 -0
  22. data/app/models/spree/base.rb +1 -0
  23. data/app/models/spree/cms_page.rb +4 -0
  24. data/app/models/spree/cms_section.rb +12 -12
  25. data/app/models/spree/cms_section_image.rb +15 -0
  26. data/app/models/spree/cms_section_image_one.rb +4 -0
  27. data/app/models/spree/cms_section_image_three.rb +4 -0
  28. data/app/models/spree/cms_section_image_two.rb +4 -0
  29. data/app/models/spree/credit_card.rb +10 -4
  30. data/app/models/spree/customer_return.rb +3 -0
  31. data/app/models/spree/data_feed/google.rb +15 -0
  32. data/app/models/spree/data_feed.rb +40 -0
  33. data/app/models/spree/digital.rb +4 -0
  34. data/app/models/spree/digital_link.rb +7 -0
  35. data/app/models/spree/fulfilment_changer.rb +1 -1
  36. data/app/models/spree/gateway/bogus.rb +1 -1
  37. data/app/models/spree/icon.rb +5 -1
  38. data/app/models/spree/image/configuration/active_storage.rb +5 -1
  39. data/app/models/spree/image.rb +3 -3
  40. data/app/models/spree/inventory_unit.rb +5 -2
  41. data/app/models/spree/legacy_user.rb +1 -2
  42. data/app/models/spree/line_item.rb +4 -1
  43. data/app/models/spree/linkable/homepage.rb +3 -0
  44. data/app/models/spree/linkable/uri.rb +3 -0
  45. data/app/models/spree/log_entry.rb +9 -1
  46. data/app/models/spree/menu.rb +3 -0
  47. data/app/models/spree/menu_item.rb +7 -11
  48. data/app/models/spree/option_type.rb +8 -0
  49. data/app/models/spree/option_value.rb +9 -0
  50. data/app/models/spree/order/address_book.rb +1 -0
  51. data/app/models/spree/order.rb +12 -3
  52. data/app/models/spree/order_merger.rb +1 -1
  53. data/app/models/spree/payment/processing.rb +1 -1
  54. data/app/models/spree/payment.rb +7 -1
  55. data/app/models/spree/payment_capture_event.rb +4 -0
  56. data/app/models/spree/payment_method/store_credit.rb +1 -1
  57. data/app/models/spree/payment_method.rb +3 -0
  58. data/app/models/spree/payment_source.rb +10 -0
  59. data/app/models/spree/preference.rb +4 -0
  60. data/app/models/spree/price.rb +3 -0
  61. data/app/models/spree/product.rb +97 -24
  62. data/app/models/spree/product_property.rb +13 -3
  63. data/app/models/spree/promotion/rules/option_value.rb +2 -2
  64. data/app/models/spree/promotion.rb +6 -0
  65. data/app/models/spree/promotion_rule.rb +1 -1
  66. data/app/models/spree/promotion_rule_user.rb +1 -1
  67. data/app/models/spree/property.rb +10 -1
  68. data/app/models/spree/prototype.rb +3 -0
  69. data/app/models/spree/refund.rb +8 -0
  70. data/app/models/spree/reimbursement.rb +3 -0
  71. data/app/models/spree/return_authorization.rb +3 -0
  72. data/app/models/spree/return_item.rb +4 -0
  73. data/app/models/spree/role.rb +1 -1
  74. data/app/models/spree/role_user.rb +1 -1
  75. data/app/models/spree/shipment.rb +8 -1
  76. data/app/models/spree/shipping_category.rb +3 -0
  77. data/app/models/spree/shipping_method.rb +6 -0
  78. data/app/models/spree/state_change.rb +1 -1
  79. data/app/models/spree/stock/availability_validator.rb +9 -3
  80. data/app/models/spree/stock/content_item.rb +1 -1
  81. data/app/models/spree/stock/quantifier.rb +1 -1
  82. data/app/models/spree/stock_item.rb +5 -0
  83. data/app/models/spree/stock_location.rb +19 -5
  84. data/app/models/spree/stock_movement.rb +4 -0
  85. data/app/models/spree/stock_transfer.rb +3 -0
  86. data/app/models/spree/store.rb +39 -15
  87. data/app/models/spree/store_credit.rb +4 -1
  88. data/app/models/spree/store_favicon_image.rb +17 -0
  89. data/app/models/spree/store_logo.rb +9 -0
  90. data/app/models/spree/store_mailer_logo.rb +13 -0
  91. data/app/models/spree/tax_category.rb +6 -0
  92. data/app/models/spree/tax_rate.rb +6 -1
  93. data/app/models/spree/taxon.rb +26 -7
  94. data/app/models/spree/taxon_image/configuration/active_storage.rb +5 -1
  95. data/app/models/spree/taxon_image.rb +3 -2
  96. data/app/models/spree/taxonomy.rb +8 -1
  97. data/app/models/spree/variant.rb +47 -21
  98. data/app/models/spree/wished_item.rb +4 -0
  99. data/app/models/spree/wishlist.rb +4 -1
  100. data/app/models/spree/zone.rb +3 -0
  101. data/app/services/spree/addresses/create.rb +1 -1
  102. data/app/services/spree/addresses/update.rb +7 -2
  103. data/app/services/spree/cart/remove_line_item.rb +1 -0
  104. data/app/services/spree/data_feeds/google/optional_attributes.rb +23 -0
  105. data/app/services/spree/data_feeds/google/optional_sub_attributes.rb +21 -0
  106. data/app/services/spree/data_feeds/google/products_list.rb +14 -0
  107. data/app/services/spree/data_feeds/google/required_attributes.rb +67 -0
  108. data/app/services/spree/data_feeds/google/rss.rb +107 -0
  109. data/app/services/spree/variants/remove_line_items.rb +15 -0
  110. data/app/sorters/spree/products/sort.rb +23 -0
  111. data/brakeman.ignore +326 -18
  112. data/config/initializers/friendly_id.rb +2 -0
  113. data/config/initializers/mobility.rb +18 -0
  114. data/config/locales/en.yml +6 -2
  115. data/config/routes.rb +43 -0
  116. data/db/migrate/20211201202851_update_linkable_resource_types.rb +10 -0
  117. data/db/migrate/20211203082008_add_settings_to_payment_methods.rb +11 -0
  118. data/db/migrate/20211229162122_disable_propagate_all_variants_by_default.rb +5 -0
  119. data/db/migrate/20220103082046_add_status_and_make_active_at_to_spree_products.rb +7 -0
  120. data/db/migrate/20220106230929_add_internal_note_to_spree_orders.rb +5 -0
  121. data/db/migrate/20220113052823_create_payment_sources.rb +22 -0
  122. data/db/migrate/20220117100333_add_make_active_at_to_spree_products.rb +17 -0
  123. data/db/migrate/20220120092821_add_metadata_to_spree_tax_rates.rb +13 -0
  124. data/db/migrate/20220201103922_add_first_name_and_last_name_to_spree_users.rb +9 -0
  125. data/db/migrate/20220222083546_add_barcode_to_spree_variants.rb +6 -0
  126. data/db/migrate/20220329113557_fix_cms_pages_unique_indexes.rb +8 -0
  127. data/db/migrate/20220613133029_add_metadata_to_spree_stock_items.rb +13 -0
  128. data/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb +27 -0
  129. data/db/migrate/20220715083542_create_spree_product_translations_for_mobility.rb +7 -0
  130. data/db/migrate/20220715120222_change_product_name_null_to_true.rb +5 -0
  131. data/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb +27 -0
  132. data/db/migrate/20220718100948_change_taxon_name_null_to_true.rb +5 -0
  133. data/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb +11 -0
  134. data/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb +5 -0
  135. data/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb +66 -0
  136. data/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb +8 -0
  137. data/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb +6 -0
  138. data/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb +5 -0
  139. data/db/migrate/20221229132350_create_spree_data_feed_settings.rb +14 -0
  140. data/db/migrate/20230103144439_create_option_type_translations.rb +26 -0
  141. data/db/migrate/20230103151034_create_option_value_translations.rb +26 -0
  142. data/db/migrate/20230109084253_create_product_property_translations.rb +25 -0
  143. data/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb +58 -0
  144. data/db/migrate/20230109105943_create_property_translations.rb +26 -0
  145. data/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb +59 -0
  146. data/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb +15 -0
  147. data/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb +8 -0
  148. data/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb +82 -0
  149. data/db/migrate/20230117115531_create_taxonomy_translations.rb +24 -0
  150. data/db/migrate/20230117120430_allow_null_taxonomy_name.rb +5 -0
  151. data/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb +11 -0
  152. data/db/migrate/20230210142732_create_store_translations.rb +50 -0
  153. data/db/migrate/20230210142849_transfer_store_data_to_translatable_tables.rb +11 -0
  154. data/db/migrate/20230210230434_add_deleted_at_to_store_translations.rb +6 -0
  155. data/db/migrate/20230415155958_rename_data_feed_settings_table.rb +5 -0
  156. data/db/migrate/20230415160828_rename_data_feed_table_columns.rb +7 -0
  157. data/db/migrate/20230415161226_add_indexes_to_data_feeds_table.rb +5 -0
  158. data/db/migrate/20230512094803_rename_data_feeds_column_provider_to_type.rb +5 -0
  159. data/db/migrate/20230514162157_add_index_on_locale_and_permalink_to_spree_taxons.rb +5 -0
  160. data/lib/friendly_id/paranoia.rb +4 -0
  161. data/lib/generators/spree/dummy/dummy_generator.rb +13 -2
  162. data/lib/generators/spree/dummy/templates/package.json +12 -0
  163. data/lib/generators/spree/dummy/templates/rails/test.rb +2 -0
  164. data/lib/spree/core/configuration.rb +91 -0
  165. data/lib/spree/core/controller_helpers/auth.rb +3 -1
  166. data/lib/spree/core/controller_helpers/currency.rb +7 -5
  167. data/lib/spree/core/controller_helpers/locale.rb +34 -8
  168. data/lib/spree/core/controller_helpers/order.rb +4 -2
  169. data/lib/spree/core/controller_helpers/search.rb +1 -1
  170. data/lib/spree/core/controller_helpers/store.rb +5 -3
  171. data/lib/spree/core/dependencies.rb +106 -0
  172. data/lib/spree/core/dependencies_helper.rb +19 -0
  173. data/lib/spree/core/engine.rb +53 -38
  174. data/{app/models/spree → lib/spree/core}/preferences/configuration.rb +3 -0
  175. data/{app/models/spree → lib/spree/core}/preferences/preferable.rb +3 -0
  176. data/lib/spree/core/product_duplicator.rb +1 -1
  177. data/lib/spree/core/product_filters.rb +7 -4
  178. data/lib/spree/core/search/base.rb +10 -6
  179. data/lib/spree/core/version.rb +1 -1
  180. data/lib/spree/core.rb +27 -5
  181. data/lib/spree/permitted_attributes.rb +9 -7
  182. data/lib/spree/testing_support/common_rake.rb +1 -0
  183. data/lib/spree/testing_support/factories/favicon_image_factory.rb +9 -0
  184. data/lib/spree/testing_support/factories/google_data_feed_factory.rb +8 -0
  185. data/lib/spree/testing_support/factories/icon_factory.rb +3 -1
  186. data/lib/spree/testing_support/factories/image_factory.rb +3 -1
  187. data/lib/spree/testing_support/factories/menu_item_factory.rb +1 -1
  188. data/lib/spree/testing_support/factories/order_factory.rb +2 -1
  189. data/lib/spree/testing_support/factories/product_factory.rb +12 -1
  190. data/lib/spree/testing_support/factories/product_property_factory.rb +1 -0
  191. data/lib/spree/testing_support/factories/product_translation_factory.rb +6 -0
  192. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  193. data/lib/spree/testing_support/factories/return_authorization_factory.rb +1 -1
  194. data/lib/spree/testing_support/factories/role_factory.rb +1 -1
  195. data/lib/spree/testing_support/factories/shipping_category_factory.rb +1 -1
  196. data/lib/spree/testing_support/factories/stock_location_factory.rb +3 -2
  197. data/lib/spree/testing_support/factories/store_factory.rb +2 -1
  198. data/lib/spree/testing_support/factories/taxon_image_factory.rb +3 -1
  199. data/lib/spree/testing_support/factories/user_factory.rb +4 -0
  200. data/lib/spree/testing_support/factories/variant_factory.rb +8 -0
  201. data/lib/spree/translation_migrations.rb +40 -0
  202. data/lib/spree_core.rb +2 -1
  203. data/lib/tasks/core.rake +12 -0
  204. data/spree_core.gemspec +5 -3
  205. metadata +152 -52
  206. data/app/models/friendly_id/slug_decorator.rb +0 -9
  207. data/app/models/spree/app_configuration.rb +0 -86
  208. data/lib/friendly_id/slug_rails5_patch.rb +0 -11
  209. data/lib/spree/core/app_dependencies.rb +0 -126
  210. /data/{app/models/spree → lib/spree/core}/preferences/preferable_class_methods.rb +0 -0
  211. /data/{app/models/spree → lib/spree/core}/preferences/scoped_store.rb +0 -0
  212. /data/{app/models/spree → lib/spree/core}/preferences/store.rb +0 -0
@@ -3,4 +3,8 @@ class Spree::Preference < Spree::Base
3
3
 
4
4
  validates :key, presence: true,
5
5
  uniqueness: { case_sensitive: false, allow_blank: true, scope: spree_base_uniqueness_scope }
6
+
7
+ if defined?(Spree::Security::Preferences)
8
+ include Spree::Security::Preferences
9
+ end
6
10
  end
@@ -1,6 +1,9 @@
1
1
  module Spree
2
2
  class Price < Spree::Base
3
3
  include VatPriceCalculation
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
4
7
 
5
8
  acts_as_paranoid
6
9
 
@@ -23,20 +23,37 @@ module Spree
23
23
  extend FriendlyId
24
24
  include ProductScopes
25
25
  include MultiStoreResource
26
+ include TranslatableResource
27
+ include TranslatableResourceSlug
26
28
  include MemoizedData
27
29
  include Metadata
30
+ if defined?(Spree::Webhooks)
31
+ include Spree::Webhooks::HasWebhooks
32
+ end
33
+ if defined?(Spree::VendorConcern)
34
+ include Spree::VendorConcern
35
+ end
28
36
 
29
- MEMOIZED_METHODS = %w(total_on_hand taxonomy_ids taxon_and_ancestors category
37
+ MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors category
30
38
  default_variant_id tax_category default_variant
31
- purchasable? in_stock? backorderable?)
39
+ purchasable? in_stock? backorderable?]
40
+
41
+ TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze
42
+ translates(*TRANSLATABLE_FIELDS)
32
43
 
33
- friendly_id :slug_candidates, use: :history
44
+ self::Translation.class_eval do
45
+ acts_as_paranoid
46
+ # deleted translation values also need to be accessible for index views listing deleted resources
47
+ default_scope { unscope(where: :deleted_at) }
48
+ end
34
49
 
50
+ friendly_id :slug_candidates, use: [:history, :mobility]
35
51
  acts_as_paranoid
52
+ auto_strip_attributes :name
36
53
 
37
54
  # we need to have this callback before any dependent: :destroy associations
38
55
  # https://github.com/rails/rails/issues/3458
39
- before_destroy :ensure_no_line_items
56
+ before_destroy :ensure_not_in_complete_orders
40
57
 
41
58
  has_many :product_option_types, dependent: :destroy, inverse_of: :product
42
59
  has_many :option_types, through: :product_option_types
@@ -118,7 +135,7 @@ module Spree
118
135
  end
119
136
 
120
137
  validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
121
- validate :discontinue_on_must_be_later_than_available_on, if: -> { available_on && discontinue_on }
138
+ validate :discontinue_on_must_be_later_than_make_active_at, if: -> { make_active_at && discontinue_on }
122
139
 
123
140
  scope :for_store, ->(store) { joins(:store_products).where(StoreProduct.table_name => { store_id: store.id }) }
124
141
 
@@ -129,11 +146,11 @@ module Spree
129
146
  alias options product_option_types
130
147
 
131
148
  self.whitelisted_ransackable_associations = %w[taxons stores variants_including_master master variants]
132
- self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on]
149
+ self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status]
133
150
  self.whitelisted_ransackable_scopes = %w[not_discontinued search_by_name in_taxon price_between]
134
151
 
135
152
  [
136
- :sku, :price, :currency, :weight, :height, :width, :depth, :is_master,
153
+ :sku, :barcode, :price, :currency, :weight, :height, :width, :depth, :is_master,
137
154
  :cost_currency, :price_in, :amount_in, :cost_price, :compare_at_price, :compare_at_amount_in
138
155
  ].each do |method_name|
139
156
  delegate method_name, :"#{method_name}=", to: :find_or_build_master
@@ -144,6 +161,23 @@ module Spree
144
161
 
145
162
  alias master_images images
146
163
 
164
+ state_machine :status, initial: :draft do
165
+ event :activate do
166
+ transition to: :active
167
+ end
168
+ after_transition to: :active, do: :after_activate
169
+
170
+ event :archive do
171
+ transition to: :archived
172
+ end
173
+ after_transition to: :archived, do: :after_archive
174
+
175
+ event :draft do
176
+ transition to: :draft
177
+ end
178
+ after_transition to: :draft, do: :after_draft
179
+ end
180
+
147
181
  # Can't use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
148
182
  def purchasable?
149
183
  default_variant.purchasable? || variants.any?(&:purchasable?)
@@ -225,14 +259,16 @@ module Spree
225
259
  end
226
260
 
227
261
  # determine if product is available.
228
- # deleted products and products with nil or future available_on date
262
+ # deleted products and products with status different than active
229
263
  # are not available
230
264
  def available?
231
- !(available_on.nil? || available_on.future?) && !deleted? && !discontinued?
265
+ active? && !deleted?
232
266
  end
233
267
 
234
268
  def discontinue!
235
- update_attribute(:discontinue_on, Time.current)
269
+ self.discontinue_on = Time.current
270
+ self.status = 'archived'
271
+ save(validate: false)
236
272
  end
237
273
 
238
274
  def discontinued?
@@ -280,14 +316,26 @@ module Spree
280
316
  end
281
317
 
282
318
  def property(property_name)
283
- product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value)
319
+ product_properties.joins(:property).
320
+ join_translation_table(Property).
321
+ find_by(Property.translation_table_alias => { name: property_name }).try(:value)
284
322
  end
285
323
 
286
324
  def set_property(property_name, property_value, property_presentation = property_name)
287
325
  ApplicationRecord.transaction do
288
- # Works around spree_i18n #301
289
- property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name)
290
- product_property = ProductProperty.where(product: self, property: property).first_or_initialize
326
+ # Manual first_or_create to work around Mobility bug
327
+ property = if Property.where(name: property_name).exists?
328
+ Property.where(name: property_name).first
329
+ else
330
+ Property.create(name: property_name, presentation: property_presentation)
331
+ end
332
+
333
+ product_property = if ProductProperty.where(product: self, property: property).exists?
334
+ ProductProperty.where(product: self, property: property).first
335
+ else
336
+ ProductProperty.create(product: self, property: property)
337
+ end
338
+
291
339
  product_property.value = property_value
292
340
  product_property.save!
293
341
  end
@@ -296,7 +344,7 @@ module Spree
296
344
  def total_on_hand
297
345
  @total_on_hand ||= Rails.cache.fetch(['product-total-on-hand', cache_key_with_version]) do
298
346
  if any_variants_not_track_inventory?
299
- Float::INFINITY
347
+ BigDecimal::INFINITY
300
348
  else
301
349
  stock_items.sum(:count_on_hand)
302
350
  end
@@ -311,11 +359,16 @@ module Spree
311
359
  end
312
360
 
313
361
  def brand
314
- @brand ||= taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) })
362
+ @brand ||= taxons.joins(:taxonomy).
363
+ join_translation_table(Taxonomy).
364
+ find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) })
315
365
  end
316
366
 
317
367
  def category
318
- @category ||= taxons.joins(:taxonomy).order(depth: :desc).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) })
368
+ @category ||= taxons.joins(:taxonomy).
369
+ join_translation_table(Taxonomy).
370
+ order(depth: :desc).
371
+ find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_categories_name) })
319
372
  end
320
373
 
321
374
  def taxons_for_store(store)
@@ -327,17 +380,21 @@ module Spree
327
380
  def any_variant_in_stock_or_backorderable?
328
381
  if variants.any?
329
382
  variants_including_master.in_stock_or_backorderable.exists?
330
- else
383
+ else
331
384
  master.in_stock_or_backorderable?
332
385
  end
333
386
  end
334
387
 
388
+ def digital?
389
+ shipping_category&.name == I18n.t('spree.seed.shipping.categories.digital')
390
+ end
391
+
335
392
  private
336
393
 
337
394
  def add_associations_from_prototype
338
395
  if prototype_id && prototype = Spree::Prototype.find_by(id: prototype_id)
339
396
  prototype.properties.each do |property|
340
- product_properties.create(property: property)
397
+ product_properties.create(property: property, value: 'Placeholder')
341
398
  end
342
399
  self.option_types = prototype.option_types
343
400
  self.taxons = prototype.taxons
@@ -385,7 +442,11 @@ module Spree
385
442
 
386
443
  def punch_slug
387
444
  # punch slug with date prefix to allow reuse of original
388
- update_column :slug, "#{Time.current.to_i}_#{slug}"[0..254] unless frozen?
445
+ return if frozen?
446
+
447
+ translations.with_deleted.each do |t|
448
+ t.update_column :slug, "#{Time.current.to_i}_#{t.slug}"[0..254]
449
+ end
389
450
  end
390
451
 
391
452
  def update_slug_history
@@ -473,8 +534,8 @@ module Spree
473
534
  Spree::Taxonomy.where(id: taxonomy_ids).update_all(updated_at: Time.current)
474
535
  end
475
536
 
476
- def ensure_no_line_items
477
- if line_items.any?
537
+ def ensure_not_in_complete_orders
538
+ if orders.complete.any?
478
539
  errors.add(:base, :cannot_destroy_if_attached_to_line_items)
479
540
  throw(:abort)
480
541
  end
@@ -485,8 +546,8 @@ module Spree
485
546
  removed_classifications.each &:remove_from_list
486
547
  end
487
548
 
488
- def discontinue_on_must_be_later_than_available_on
489
- if discontinue_on < available_on
549
+ def discontinue_on_must_be_later_than_make_active_at
550
+ if discontinue_on < make_active_at
490
551
  errors.add(:discontinue_on, :invalid_date_range)
491
552
  end
492
553
  end
@@ -502,5 +563,17 @@ module Spree
502
563
  def downcase_slug
503
564
  slug&.downcase!
504
565
  end
566
+
567
+ def after_activate
568
+ # this method is prepended in api/ to queue Webhooks requests
569
+ end
570
+
571
+ def after_archive
572
+ # this method is prepended in api/ to queue Webhooks requests
573
+ end
574
+
575
+ def after_draft
576
+ # this method is prepended in api/ to queue Webhooks requests
577
+ end
505
578
  end
506
579
  end
@@ -1,8 +1,14 @@
1
1
  module Spree
2
2
  class ProductProperty < Spree::Base
3
3
  include Spree::FilterParam
4
+ include TranslatableResource
4
5
 
5
- auto_strip_attributes :value
6
+ TRANSLATABLE_FIELDS = %i[value filter_param].freeze
7
+ translates(*TRANSLATABLE_FIELDS)
8
+
9
+ self::Translation.class_eval do
10
+ auto_strip_attributes :value
11
+ end
6
12
 
7
13
  acts_as_list scope: :product
8
14
 
@@ -13,6 +19,7 @@ module Spree
13
19
 
14
20
  validates :property, presence: true
15
21
  validates :property_id, uniqueness: { scope: :product_id }
22
+ validates :value, presence: true
16
23
 
17
24
  default_scope { order(:position) }
18
25
 
@@ -30,8 +37,11 @@ module Spree
30
37
  `ProductProperty#property_name=` is deprecated and will be removed in Spree 5.0.
31
38
  DEPRECATION
32
39
  if name.present?
33
- # don't use `find_by :name` to workaround globalize/globalize#423 bug
34
- self.property = Property.where(name: name).first_or_create(presentation: name)
40
+ self.property = if Property.where(name: name).exists?
41
+ Property.where(name: name).first
42
+ else
43
+ Property.create(name: name, presentation: name)
44
+ end
35
45
  end
36
46
  end
37
47
 
@@ -4,9 +4,9 @@ module Spree
4
4
  module OptionValueWithNumerificationSupport
5
5
  def preferred_eligible_values
6
6
  values = super || {}
7
- Hash[values.keys.map(&:to_i).zip(
7
+ Hash[values.keys.zip(
8
8
  values.values.map do |v|
9
- (v.is_a?(Array) ? v : v.split(',')).map(&:to_i)
9
+ (v.is_a?(Array) ? v : v.split(','))
10
10
  end
11
11
  )]
12
12
  end
@@ -2,6 +2,12 @@ module Spree
2
2
  class Promotion < Spree::Base
3
3
  include MultiStoreResource
4
4
  include Metadata
5
+ if defined?(Spree::Webhooks)
6
+ include Spree::Webhooks::HasWebhooks
7
+ end
8
+ if defined?(Spree::Security::Promotions)
9
+ include Spree::Security::Promotions
10
+ end
5
11
 
6
12
  MATCH_POLICIES = %w(all any)
7
13
  UNACTIVATABLE_ORDER_STATES = ['complete', 'awaiting_return', 'returned']
@@ -35,7 +35,7 @@ module Spree
35
35
 
36
36
  def unique_per_promotion
37
37
  if Spree::PromotionRule.exists?(promotion_id: promotion_id, type: self.class.name)
38
- errors[:base] << 'Promotion already contains this rule type'
38
+ errors.add(:base, 'Promotion already contains this rule type')
39
39
  end
40
40
  end
41
41
 
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class PromotionRuleUser < Spree::Base
3
3
  belongs_to :promotion_rule, class_name: 'Spree::PromotionRule'
4
- belongs_to :user, class_name: Spree.user_class.to_s
4
+ belongs_to :user, class_name: "::#{Spree.user_class}"
5
5
 
6
6
  validates :user, :promotion_rule, presence: true
7
7
  validates :user_id, uniqueness: { scope: :promotion_rule_id }, allow_nil: true
@@ -2,8 +2,17 @@ module Spree
2
2
  class Property < Spree::Base
3
3
  include Spree::FilterParam
4
4
  include Metadata
5
+ include TranslatableResource
6
+ if defined?(Spree::Webhooks)
7
+ include Spree::Webhooks::HasWebhooks
8
+ end
9
+
10
+ TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze
11
+ translates(*TRANSLATABLE_FIELDS)
5
12
 
6
- auto_strip_attributes :name, :presentation
13
+ self::Translation.class_eval do
14
+ auto_strip_attributes :name, :presentation
15
+ end
7
16
 
8
17
  has_many :property_prototypes, class_name: 'Spree::PropertyPrototype'
9
18
  has_many :prototypes, through: :property_prototypes, class_name: 'Spree::Prototype'
@@ -1,6 +1,9 @@
1
1
  module Spree
2
2
  class Prototype < Spree::Base
3
3
  include Metadata
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
4
7
 
5
8
  has_many :property_prototypes, class_name: 'Spree::PropertyPrototype'
6
9
  has_many :properties, through: :property_prototypes, class_name: 'Spree::Property'
@@ -1,6 +1,12 @@
1
1
  module Spree
2
2
  class Refund < Spree::Base
3
3
  include Metadata
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
7
+ if defined?(Spree::Security::Refunds)
8
+ include Spree::Security::Refunds
9
+ end
4
10
 
5
11
  with_options inverse_of: :refunds do
6
12
  belongs_to :payment
@@ -23,6 +29,8 @@ module Spree
23
29
 
24
30
  scope :non_reimbursement, -> { where(reimbursement_id: nil) }
25
31
 
32
+ attr_reader :response
33
+
26
34
  def money
27
35
  Spree::Money.new(amount, currency: payment.currency)
28
36
  end
@@ -2,6 +2,9 @@ module Spree
2
2
  class Reimbursement < Spree::Base
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'RI', length: 9)
4
4
  include NumberIdentifier
5
+ if defined?(Spree::Webhooks)
6
+ include Spree::Webhooks::HasWebhooks
7
+ end
5
8
 
6
9
  class IncompleteReimbursementError < StandardError; end
7
10
 
@@ -2,6 +2,9 @@ module Spree
2
2
  class ReturnAuthorization < Spree::Base
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'RA', length: 9)
4
4
  include NumberIdentifier
5
+ if defined?(Spree::Webhooks)
6
+ include Spree::Webhooks::HasWebhooks
7
+ end
5
8
 
6
9
  belongs_to :order, class_name: 'Spree::Order', inverse_of: :return_authorizations
7
10
 
@@ -2,6 +2,10 @@ module Spree
2
2
  class ReturnItem < Spree::Base
3
3
  COMPLETED_RECEPTION_STATUSES = %w(received given_to_customer)
4
4
 
5
+ if defined?(Spree::Webhooks)
6
+ include Spree::Webhooks::HasWebhooks
7
+ end
8
+
5
9
  class_attribute :return_eligibility_validator
6
10
  self.return_eligibility_validator = ReturnItem::EligibilityValidator::Default
7
11
 
@@ -3,6 +3,6 @@ module Spree
3
3
  include UniqueName
4
4
 
5
5
  has_many :role_users, class_name: 'Spree::RoleUser', dependent: :destroy
6
- has_many :users, through: :role_users, class_name: Spree.user_class.to_s
6
+ has_many :users, through: :role_users, class_name: "::#{Spree.user_class}"
7
7
  end
8
8
  end
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class RoleUser < Spree::Base
3
3
  belongs_to :role, class_name: 'Spree::Role'
4
- belongs_to :user, class_name: Spree.user_class.to_s
4
+ belongs_to :user, class_name: "::#{Spree.user_class}"
5
5
  end
6
6
  end
@@ -6,6 +6,12 @@ module Spree
6
6
  include NumberIdentifier
7
7
  include NumberAsParam
8
8
  include Metadata
9
+ if defined?(Spree::Webhooks)
10
+ include Spree::Webhooks::HasWebhooks
11
+ end
12
+ if defined?(Spree::Security::Shipments)
13
+ include Spree::Security::Shipments
14
+ end
9
15
 
10
16
  with_options inverse_of: :shipments do
11
17
  belongs_to :address, class_name: 'Spree::Address'
@@ -243,7 +249,8 @@ module Spree
243
249
  end
244
250
 
245
251
  def selected_shipping_rate_id=(id)
246
- shipping_rates.update_all(selected: false)
252
+ # Explicitly updates the timestamp in order to bust cache dependent on "updated_at"
253
+ shipping_rates.update_all(selected: false, updated_at: Time.current)
247
254
  shipping_rates.update(id, selected: true)
248
255
  save!
249
256
  end
@@ -1,6 +1,9 @@
1
1
  module Spree
2
2
  class ShippingCategory < Spree::Base
3
3
  include UniqueName
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
4
7
 
5
8
  with_options inverse_of: :shipping_category do
6
9
  has_many :products
@@ -3,6 +3,12 @@ module Spree
3
3
  acts_as_paranoid
4
4
  include Spree::CalculatedAdjustments
5
5
  include Metadata
6
+ if defined?(Spree::Webhooks)
7
+ include Spree::Webhooks::HasWebhooks
8
+ end
9
+ if defined?(Spree::VendorConcern)
10
+ include Spree::VendorConcern
11
+ end
6
12
 
7
13
  DISPLAY = [:both, :front_end, :back_end]
8
14
 
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class StateChange < Spree::Base
3
- belongs_to :user, class_name: Spree.user_class.to_s, optional: true
3
+ belongs_to :user, class_name: "::#{Spree.user_class}", optional: true
4
4
  belongs_to :stateful, polymorphic: true
5
5
 
6
6
  def <=>(other)
@@ -14,9 +14,15 @@ module Spree
14
14
  display_name = variant.name.to_s
15
15
  display_name += " (#{variant.options_text})" unless variant.options_text.blank?
16
16
 
17
- line_item.errors.add(:quantity,
18
- :selected_quantity_not_available,
19
- message: Spree.t(:selected_quantity_not_available, item: display_name.inspect))
17
+ if variant.available?
18
+ line_item.errors.add(:quantity,
19
+ :selected_quantity_not_available,
20
+ message: Spree.t(:selected_quantity_not_available, item: display_name.inspect))
21
+ else
22
+ line_item.errors.add(:base,
23
+ :only_active_products_can_be_added_to_cart,
24
+ message: Spree.t(:only_active_products_can_be_added_to_cart))
25
+ end
20
26
  end
21
27
 
22
28
  private
@@ -12,7 +12,7 @@ module Spree
12
12
  delegate :line_item,
13
13
  :quantity,
14
14
  :variant, to: :inventory_unit
15
- delegate :price, to: :variant
15
+ delegate :price, to: :line_item
16
16
  delegate :dimension,
17
17
  :volume,
18
18
  :weight, to: :variant, prefix: true
@@ -12,7 +12,7 @@ module Spree
12
12
  @total_on_hand ||= if variant.should_track_inventory?
13
13
  stock_items.sum(:count_on_hand)
14
14
  else
15
- Float::INFINITY
15
+ BigDecimal::INFINITY
16
16
  end
17
17
  end
18
18
 
@@ -2,6 +2,11 @@ module Spree
2
2
  class StockItem < Spree::Base
3
3
  acts_as_paranoid
4
4
 
5
+ include Metadata
6
+ if defined?(Spree::Webhooks)
7
+ include Spree::Webhooks::HasWebhooks
8
+ end
9
+
5
10
  with_options inverse_of: :stock_items do
6
11
  belongs_to :stock_location, class_name: 'Spree::StockLocation'
7
12
  belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
@@ -1,6 +1,15 @@
1
1
  module Spree
2
2
  class StockLocation < Spree::Base
3
3
  include UniqueName
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
7
+ if defined?(Spree::Security::StockLocations)
8
+ include Spree::Security::StockLocations
9
+ end
10
+ if defined?(Spree::VendorConcern)
11
+ include Spree::VendorConcern
12
+ end
4
13
 
5
14
  has_many :shipments
6
15
  has_many :stock_items, dependent: :delete_all, inverse_of: :stock_location
@@ -51,9 +60,15 @@ module Spree
51
60
  # @param variant Variant instance or Variant ID
52
61
  #
53
62
  # @return [StockItem] Corresponding StockItem for the StockLocation's variant.
54
- def stock_item_or_create(variant)
55
- variant_id = variant.is_a?(Spree::Variant) ? variant.id : variant
56
- stock_item(variant_id) || stock_items.create(variant_id: variant_id)
63
+ def stock_item_or_create(variant_or_variant_id)
64
+ if variant_or_variant_id.is_a?(Spree::Variant)
65
+ variant_id = variant_or_variant_id.id
66
+ variant = variant_or_variant_id
67
+ else
68
+ variant_id = variant_or_variant_id
69
+ variant = Spree::Variant.find(variant_or_variant_id)
70
+ end
71
+ stock_item(variant_id) || propagate_variant(variant)
57
72
  end
58
73
 
59
74
  def count_on_hand(variant)
@@ -86,8 +101,7 @@ module Spree
86
101
  end
87
102
 
88
103
  def fill_status(variant, quantity)
89
- if item = stock_item(variant)
90
-
104
+ if item = stock_item_or_create(variant)
91
105
  if item.count_on_hand >= quantity
92
106
  on_hand = quantity
93
107
  backordered = 0
@@ -5,6 +5,10 @@ module Spree
5
5
  min: -2**31
6
6
  }.freeze
7
7
 
8
+ if defined?(Spree::Webhooks)
9
+ include Spree::Webhooks::HasWebhooks
10
+ end
11
+
8
12
  belongs_to :stock_item, class_name: 'Spree::StockItem', inverse_of: :stock_movements
9
13
  belongs_to :originator, polymorphic: true
10
14
 
@@ -4,6 +4,9 @@ module Spree
4
4
  include NumberIdentifier
5
5
  include NumberAsParam
6
6
  include Metadata
7
+ if defined?(Spree::Webhooks)
8
+ include Spree::Webhooks::HasWebhooks
9
+ end
7
10
 
8
11
  has_many :stock_movements, as: :originator
9
12