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
@@ -1,5 +1,24 @@
1
1
  module Spree
2
2
  class Store < Spree::Base
3
+ include TranslatableResource
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
7
+ if defined?(Spree::Security::Stores)
8
+ include Spree::Security::Stores
9
+ end
10
+
11
+ TRANSLATABLE_FIELDS = %i[name meta_description meta_keywords seo_title facebook
12
+ twitter instagram customer_support_email description
13
+ address contact_phone new_order_notifications_email].freeze
14
+ translates(*TRANSLATABLE_FIELDS)
15
+
16
+ self::Translation.class_eval do
17
+ acts_as_paranoid
18
+ # deleted translation values still need to be accessible - remove deleted_at scope
19
+ default_scope { unscope(where: :deleted_at) }
20
+ end
21
+
3
22
  typed_store :settings, coder: ActiveRecord::TypedStore::IdentityCoder do |s|
4
23
  # Spree Digital Asset Configurations
5
24
  s.boolean :limit_digital_download_count, default: true, null: false
@@ -9,8 +28,7 @@ module Spree
9
28
  s.integer :digital_asset_link_expire_time, default: 300, null: false # 5 minutes in seconds
10
29
  end
11
30
 
12
- MAILER_LOGO_CONTENT_TYPES = ['image/png', 'image/jpg', 'image/jpeg'].freeze
13
- FAVICON_CONTENT_TYPES = ['image/png', 'image/x-icon', 'image/vnd.microsoft.icon'].freeze
31
+ attr_accessor :skip_validate_not_last
14
32
 
15
33
  acts_as_paranoid
16
34
 
@@ -49,6 +67,8 @@ module Spree
49
67
 
50
68
  has_many :wishlists, class_name: 'Spree::Wishlist'
51
69
 
70
+ has_many :data_feeds, class_name: 'Spree::DataFeed'
71
+
52
72
  belongs_to :default_country, class_name: 'Spree::Country'
53
73
  belongs_to :checkout_zone, class_name: 'Spree::Zone'
54
74
 
@@ -58,7 +78,7 @@ module Spree
58
78
 
59
79
  validates :digital_asset_authorized_clicks, numericality: { only_integer: true, greater_than: 0 }
60
80
  validates :digital_asset_authorized_days, numericality: { only_integer: true, greater_than: 0 }
61
- validates :code, uniqueness: { conditions: -> { with_deleted } }
81
+ validates :code, uniqueness: { case_sensitive: false, conditions: -> { with_deleted } }
62
82
  validates :mail_from_address, email: { allow_blank: false }
63
83
 
64
84
  # FIXME: we should remove this condition in v5
@@ -71,19 +91,18 @@ module Spree
71
91
 
72
92
  default_scope { order(created_at: :asc) }
73
93
 
74
- has_one_attached :logo
75
- has_one_attached :mailer_logo
76
- has_one_attached :favicon_image
94
+ has_one :logo, class_name: 'Spree::StoreLogo', dependent: :destroy, as: :viewable
95
+ accepts_nested_attributes_for :logo, reject_if: :all_blank
96
+
97
+ has_one :mailer_logo, class_name: 'Spree::StoreMailerLogo', dependent: :destroy, as: :viewable
98
+ accepts_nested_attributes_for :mailer_logo, reject_if: :all_blank
77
99
 
78
- validates :mailer_logo, content_type: MAILER_LOGO_CONTENT_TYPES
79
- validates :favicon_image, content_type: FAVICON_CONTENT_TYPES,
80
- dimension: { max: 256..256 },
81
- aspect_ratio: :square,
82
- size: { less_than_or_equal_to: 1.megabyte }
100
+ has_one :favicon_image, class_name: 'Spree::StoreFaviconImage', dependent: :destroy, as: :viewable
101
+ accepts_nested_attributes_for :favicon_image, reject_if: :all_blank
83
102
 
84
103
  before_save :ensure_default_exists_and_is_unique
85
104
  before_save :ensure_supported_currencies, :ensure_supported_locales, :ensure_default_country
86
- before_destroy :validate_not_last
105
+ before_destroy :validate_not_last, unless: :skip_validate_not_last
87
106
  before_destroy :pass_default_flag_to_other_store
88
107
 
89
108
  scope :by_url, ->(url) { where('url like ?', "%#{url}%") }
@@ -104,7 +123,12 @@ module Spree
104
123
  # this behaviour is very buggy and unpredictable
105
124
  def self.default
106
125
  Rails.cache.fetch('default_store') do
107
- where(default: true).first_or_initialize
126
+ # workaround for Mobility bug with first_or_initialize
127
+ if where(default: true).any?
128
+ where(default: true).first
129
+ else
130
+ new(default: true)
131
+ end
108
132
  end
109
133
  end
110
134
 
@@ -176,9 +200,9 @@ module Spree
176
200
  end
177
201
 
178
202
  def favicon
179
- return unless favicon_image.attached?
203
+ return unless favicon_image&.attachment&.attached?
180
204
 
181
- favicon_image.variant(resize: '32x32')
205
+ favicon_image.attachment.variant(resize_to_limit: [32, 32])
182
206
  end
183
207
 
184
208
  def can_be_deleted?
@@ -2,6 +2,9 @@ module Spree
2
2
  class StoreCredit < Spree::Base
3
3
  include SingleStoreResource
4
4
  include Metadata
5
+ if defined?(Spree::Webhooks)
6
+ include Spree::Webhooks::HasWebhooks
7
+ end
5
8
 
6
9
  acts_as_paranoid
7
10
 
@@ -15,7 +18,7 @@ module Spree
15
18
 
16
19
  DEFAULT_CREATED_BY_EMAIL = 'spree@example.com'.freeze
17
20
 
18
- belongs_to :user, class_name: Spree.user_class.to_s, foreign_key: 'user_id'
21
+ belongs_to :user, class_name: "::#{Spree.user_class}", foreign_key: 'user_id'
19
22
  belongs_to :category, class_name: 'Spree::StoreCreditCategory'
20
23
  belongs_to :created_by, class_name: Spree.admin_user_class.to_s, foreign_key: 'created_by_id'
21
24
  belongs_to :credit_type, class_name: 'Spree::StoreCreditType', foreign_key: 'type_id'
@@ -0,0 +1,17 @@
1
+ module Spree
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
+ VALID_CONTENT_TYPES = ['image/png', 'image/x-icon', 'image/vnd.microsoft.icon'].freeze
10
+
11
+ validates :attachment,
12
+ content_type: VALID_CONTENT_TYPES,
13
+ dimension: { max: 256..256 },
14
+ aspect_ratio: :square,
15
+ size: { less_than_or_equal_to: 1.megabyte }
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
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
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
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
+ VALID_CONTENT_TYPES = ['image/png', 'image/jpg', 'image/jpeg'].freeze
10
+
11
+ validates :attachment, content_type: VALID_CONTENT_TYPES
12
+ end
13
+ end
@@ -1,9 +1,15 @@
1
1
  module Spree
2
2
  class TaxCategory < Spree::Base
3
+ if defined?(Spree::Webhooks)
4
+ include Spree::Webhooks::HasWebhooks
5
+ end
6
+
3
7
  acts_as_paranoid
4
8
  validates :name, presence: true, uniqueness: { case_sensitive: false, scope: spree_base_uniqueness_scope.push(:deleted_at) }
5
9
 
6
10
  has_many :tax_rates, dependent: :destroy, inverse_of: :tax_category
11
+ has_many :products, dependent: :nullify
12
+ has_many :variants, dependent: :nullify
7
13
 
8
14
  before_save :set_default_category
9
15
 
@@ -4,6 +4,10 @@ module Spree
4
4
 
5
5
  include Spree::CalculatedAdjustments
6
6
  include Spree::AdjustmentSource
7
+ include Spree::Metadata
8
+ if defined?(Spree::Webhooks)
9
+ include Spree::Webhooks::HasWebhooks
10
+ end
7
11
 
8
12
  with_options inverse_of: :tax_rates do
9
13
  belongs_to :zone, class_name: 'Spree::Zone', optional: true
@@ -114,7 +118,8 @@ module Spree
114
118
 
115
119
  ' ' + ActiveSupport::NumberHelper::NumberToPercentageConverter.convert(
116
120
  amount * 100,
117
- locale: I18n.locale
121
+ locale: I18n.locale,
122
+ strip_insignificant_zeros: true
118
123
  )
119
124
  end
120
125
  end
@@ -3,7 +3,12 @@ require 'stringex'
3
3
 
4
4
  module Spree
5
5
  class Taxon < Spree::Base
6
+ include TranslatableResource
7
+ include TranslatableResourceSlug
6
8
  include Metadata
9
+ if defined?(Spree::Webhooks)
10
+ include Spree::Webhooks::HasWebhooks
11
+ end
7
12
 
8
13
  extend FriendlyId
9
14
  friendly_id :permalink, slug_column: :permalink, use: :history
@@ -24,7 +29,7 @@ module Spree
24
29
  has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', dependent: :destroy
25
30
  has_many :promotion_rules, through: :promotion_rule_taxons, class_name: 'Spree::PromotionRule'
26
31
 
27
- validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], allow_blank: true, case_sensitive: false }
32
+ validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], case_sensitive: false }
28
33
  validates :taxonomy, presence: true
29
34
  validates :permalink, uniqueness: { case_sensitive: false, scope: [:parent_id, :taxonomy_id] }
30
35
  validates :hide_from_nav, inclusion: { in: [true, false] }
@@ -39,7 +44,7 @@ module Spree
39
44
 
40
45
  before_validation :copy_taxonomy_from_parent
41
46
  after_save :touch_ancestors_and_taxonomy
42
- after_save :sync_taxonomy_name
47
+ after_update :sync_taxonomy_name
43
48
  after_touch :touch_ancestors_and_taxonomy
44
49
 
45
50
  has_one :icon, as: :viewable, dependent: :destroy, class_name: 'Spree::TaxonImage'
@@ -51,6 +56,24 @@ module Spree
51
56
 
52
57
  scope :for_stores, ->(stores) { joins(:taxonomy).where(spree_taxonomies: { store_id: stores.ids }) }
53
58
 
59
+ TRANSLATABLE_FIELDS = %i[name description permalink].freeze
60
+ translates(*TRANSLATABLE_FIELDS)
61
+
62
+ self::Translation.class_eval do
63
+ alias_attribute :slug, :permalink
64
+
65
+ before_create :set_permalink
66
+
67
+ def set_permalink
68
+ parent = translated_model.parent
69
+ if parent.present?
70
+ self.permalink = [parent.permalink, (permalink.blank? ? name.to_url : permalink.split('/').last)].join('/')
71
+ else
72
+ self.permalink = name.to_url if permalink.blank?
73
+ end
74
+ end
75
+ end
76
+
54
77
  # indicate which filters should be used for a taxon
55
78
  # this method should be customized to your own site
56
79
  def applicable_filters
@@ -70,11 +93,7 @@ module Spree
70
93
 
71
94
  # Creates permalink base for friendly_id
72
95
  def set_permalink
73
- if parent.present?
74
- self.permalink = [parent.permalink, (permalink.blank? ? name.to_url : permalink.split('/').last)].join('/')
75
- else
76
- self.permalink = name.to_url if permalink.blank?
77
- end
96
+ translations.each(&:set_permalink)
78
97
  end
79
98
 
80
99
  def active_products
@@ -5,7 +5,11 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- has_one_attached :attachment
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
9
13
 
10
14
  validates :attachment, content_type: /\Aimage\/.*\z/
11
15
 
@@ -6,10 +6,11 @@ module Spree
6
6
 
7
7
  def styles
8
8
  self.class.styles.map do |_, size|
9
- width, height = size[/(\d+)x(\d+)/].split('x')
9
+ width, height = size[/(\d+)x(\d+)/].split('x').map(&:to_i)
10
10
 
11
11
  {
12
- url: polymorphic_path(attachment.variant(resize: size), only_path: true),
12
+ url: generate_url(size: size),
13
+ size: size,
13
14
  width: width,
14
15
  height: height
15
16
  }
@@ -1,6 +1,13 @@
1
1
  module Spree
2
2
  class Taxonomy < Spree::Base
3
+ include TranslatableResource
3
4
  include Metadata
5
+ if defined?(Spree::Webhooks)
6
+ include Spree::Webhooks::HasWebhooks
7
+ end
8
+
9
+ TRANSLATABLE_FIELDS = %i[name].freeze
10
+ translates(*TRANSLATABLE_FIELDS)
4
11
 
5
12
  acts_as_list
6
13
 
@@ -12,7 +19,7 @@ module Spree
12
19
  belongs_to :store, class_name: 'Spree::Store'
13
20
 
14
21
  after_create :set_root
15
- after_save :set_root_taxon_name
22
+ after_update :set_root_taxon_name
16
23
 
17
24
  default_scope { order("#{table_name}.position, #{table_name}.created_at") }
18
25
 
@@ -5,20 +5,26 @@ module Spree
5
5
 
6
6
  include MemoizedData
7
7
  include Metadata
8
+ if defined?(Spree::Webhooks)
9
+ include Spree::Webhooks::HasWebhooks
10
+ end
8
11
 
9
12
  MEMOIZED_METHODS = %w(purchasable in_stock backorderable tax_category options_text compare_at_price)
10
13
 
11
14
  belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product', inverse_of: :variants
12
15
  belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true
13
16
 
14
- delegate :name, :name=, :description, :slug, :available_on, :shipping_category_id,
17
+ delegate :name, :name=, :description, :slug, :available_on, :make_active_at, :shipping_category_id,
15
18
  :meta_description, :meta_keywords, :shipping_category, to: :product
16
19
 
20
+ auto_strip_attributes :sku, nullify: false
21
+
17
22
  # we need to have this callback before any dependent: :destroy associations
18
23
  # https://github.com/rails/rails/issues/3458
19
- before_destroy :ensure_no_line_items
24
+ before_destroy :ensure_not_in_complete_orders
25
+ after_destroy :remove_line_items_from_incomplete_orders
20
26
 
21
- # must include this after ensure_no_line_items to make sure price won't be deleted before validation
27
+ # must include this after ensure_not_in_complete_orders to make sure price won't be deleted before validation
22
28
  include Spree::DefaultPrice
23
29
 
24
30
  with_options inverse_of: :variant do
@@ -116,16 +122,13 @@ module Spree
116
122
  self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku)
117
123
 
118
124
  def self.product_name_or_sku_cont(query)
119
- joins(:product).where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
125
+ joins(:product).join_translation_table(Product).
126
+ where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)
127
+ OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
120
128
  end
121
129
 
122
130
  def self.search_by_product_name_or_sku(query)
123
- if defined?(SpreeGlobalize)
124
- joins(product: :translations).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)",
125
- query: "%#{query}%")
126
- else
127
- product_name_or_sku_cont(query)
128
- end
131
+ product_name_or_sku_cont(query)
129
132
  end
130
133
 
131
134
  def available?
@@ -140,7 +143,7 @@ module Spree
140
143
  @tax_category ||= if self[:tax_category_id].nil?
141
144
  product.tax_category
142
145
  else
143
- Spree::TaxCategory.find(self[:tax_category_id])
146
+ Spree::TaxCategory.find_by(id: self[:tax_category_id]) || product.tax_category
144
147
  end
145
148
  end
146
149
 
@@ -166,6 +169,8 @@ module Spree
166
169
 
167
170
  def options=(options = {})
168
171
  options.each do |option|
172
+ next if option[:name].blank? || option[:value].blank?
173
+
169
174
  set_option_value(option[:name], option[:value])
170
175
  end
171
176
  end
@@ -174,12 +179,12 @@ module Spree
174
179
  # no option values on master
175
180
  return if is_master
176
181
 
177
- option_type = Spree::OptionType.where(name: opt_name).first_or_initialize do |o|
178
- o.presentation = opt_name
182
+ option_type = Spree::OptionType.where(['LOWER(name) = ?', opt_name.downcase.strip]).first_or_initialize do |o|
183
+ o.name = o.presentation = opt_name
179
184
  o.save!
180
185
  end
181
186
 
182
- current_value = option_values.detect { |o| o.option_type.name == opt_name }
187
+ current_value = find_option_value(opt_name)
183
188
 
184
189
  if current_value.nil?
185
190
  # then we have to check to make sure that the product has the option type
@@ -187,13 +192,13 @@ module Spree
187
192
  product.option_types << option_type
188
193
  end
189
194
  else
190
- return if current_value.name == opt_value
195
+ return if current_value.name.downcase.strip == opt_value.downcase.strip
191
196
 
192
197
  option_values.delete(current_value)
193
198
  end
194
199
 
195
- option_value = Spree::OptionValue.where(option_type_id: option_type.id, name: opt_value).first_or_initialize do |o|
196
- o.presentation = opt_value
200
+ option_value = option_type.option_values.where(['LOWER(name) = ?', opt_value.downcase.strip]).first_or_initialize do |o|
201
+ o.name = o.presentation = opt_value
197
202
  o.save!
198
203
  end
199
204
 
@@ -201,12 +206,29 @@ module Spree
201
206
  save
202
207
  end
203
208
 
209
+ def find_option_value(opt_name)
210
+ option_values.detect { |o| o.option_type.name.downcase.strip == opt_name.downcase.strip }
211
+ end
212
+
204
213
  def option_value(opt_name)
205
- option_values.detect { |o| o.option_type.name == opt_name }.try(:presentation)
214
+ find_option_value(opt_name).try(:presentation)
206
215
  end
207
216
 
208
217
  def price_in(currency)
209
- prices.detect { |price| price.currency == currency&.upcase } || prices.build(currency: currency&.upcase)
218
+ currency = currency&.upcase
219
+ find_or_build_price = lambda do
220
+ if prices.loaded?
221
+ prices.detect { |price| price.currency == currency } || prices.build(currency: currency)
222
+ else
223
+ prices.find_or_initialize_by(currency: currency)
224
+ end
225
+ end
226
+
227
+ Rails.cache.fetch("spree/prices/#{cache_key_with_version}/price_in/#{currency}") do
228
+ find_or_build_price.call
229
+ end
230
+ rescue TypeError
231
+ find_or_build_price.call
210
232
  end
211
233
 
212
234
  def amount_in(currency)
@@ -312,13 +334,17 @@ module Spree
312
334
 
313
335
  private
314
336
 
315
- def ensure_no_line_items
316
- if line_items.any?
337
+ def ensure_not_in_complete_orders
338
+ if orders.complete.any?
317
339
  errors.add(:base, :cannot_destroy_if_attached_to_line_items)
318
340
  throw(:abort)
319
341
  end
320
342
  end
321
343
 
344
+ def remove_line_items_from_incomplete_orders
345
+ Spree::Variants::RemoveFromIncompleteOrdersJob.perform_later(self)
346
+ end
347
+
322
348
  def quantifier
323
349
  Spree::Stock::Quantifier.new(self)
324
350
  end
@@ -1,5 +1,9 @@
1
1
  module Spree
2
2
  class WishedItem < Spree::Base
3
+ if defined?(Spree::Webhooks)
4
+ include Spree::Webhooks::HasWebhooks
5
+ end
6
+
3
7
  extend DisplayMoney
4
8
  money_methods :total, :price
5
9
 
@@ -1,10 +1,13 @@
1
1
  module Spree
2
2
  class Wishlist < Spree::Base
3
3
  include SingleStoreResource
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
4
7
 
5
8
  has_secure_token
6
9
 
7
- belongs_to :user, class_name: Spree.user_class.name, touch: true
10
+ belongs_to :user, class_name: "::#{Spree.user_class}", touch: true
8
11
  belongs_to :store, class_name: 'Spree::Store'
9
12
 
10
13
  has_many :wished_items, class_name: 'Spree::WishedItem', dependent: :destroy
@@ -1,6 +1,9 @@
1
1
  module Spree
2
2
  class Zone < Spree::Base
3
3
  include UniqueName
4
+ if defined?(Spree::Webhooks)
5
+ include Spree::Webhooks::HasWebhooks
6
+ end
4
7
 
5
8
  with_options dependent: :destroy, inverse_of: :zone do
6
9
  has_many :zone_members, class_name: 'Spree::ZoneMember'
@@ -7,7 +7,7 @@ module Spree
7
7
  attr_accessor :country
8
8
 
9
9
  def call(address_params: {}, user: nil)
10
- fill_country_and_state_ids(address_params)
10
+ address_params = fill_country_and_state_ids(address_params)
11
11
 
12
12
  address = Spree::Address.new(address_params)
13
13
  address.user = user if user.present?
@@ -8,12 +8,17 @@ module Spree
8
8
 
9
9
  def call(address:, address_params:)
10
10
  address_params[:country_id] ||= address.country_id
11
- fill_country_and_state_ids(address_params)
11
+ address_params = fill_country_and_state_ids(address_params)
12
12
 
13
13
  if address&.editable?
14
14
  address.update(address_params) ? success(address) : failure(address)
15
15
  else
16
- new_address(address_params).valid? ? address.destroy && success(new_address) : failure(new_address)
16
+ if new_address(address_params).valid?
17
+ address.destroy
18
+ success(new_address)
19
+ else
20
+ failure(new_address)
21
+ end
17
22
  end
18
23
  end
19
24
 
@@ -11,6 +11,7 @@ module Spree
11
11
  line_item: line_item,
12
12
  options: options)
13
13
  end
14
+ order.reload
14
15
  success(line_item)
15
16
  end
16
17
  end
@@ -0,0 +1,23 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class OptionalAttributes
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(input)
8
+ information = {}
9
+
10
+ input[:product].property_ids.each do |key|
11
+ name = Spree::Property.find(key)&.name
12
+ value = input[:product].property(name)
13
+ unless value.nil?
14
+ information[name] = value
15
+ end
16
+ end
17
+
18
+ success(information: information)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class OptionalSubAttributes
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(input)
8
+ information = {}
9
+
10
+ # This is a place where you can put attributes that have sub-attributes, example for shipping:
11
+ #
12
+ # information['shipping'] = {}
13
+ # information['shipping']['price'] = calculate_shipping(input[:product])
14
+ # information['shipping']['country'] = input[:store].default_country
15
+
16
+ success(information: information)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class ProductsList
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(store)
8
+ products = store.products.active
9
+ success(products: products)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end