spree_core 5.2.0.rc1 → 5.2.0.rc3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20e9c99845e0d1dd7461034a9ec11d3e6d9130126a3ac517e0f7f387ebe43030
4
- data.tar.gz: ae4d3a101a3e95d7360747fea07426118191f84e8f12d2b180824f179d1d6b24
3
+ metadata.gz: 531657763d76e0ce76fb2845acfeb30c110343666bc5e8e727de1f21249da4cd
4
+ data.tar.gz: 2d4f33f07d6b90d61c9578575889c7c597a39b98e19597c90f902e312832af2c
5
5
  SHA512:
6
- metadata.gz: 32fd04918e07f12a433c2f0d2628dae894fcc022646deee37629f9626571e6b1e69c12fd608d3397a974c89ee40f4d071a11420eda7dd3a42f5205a3f4e6cdf4
7
- data.tar.gz: 339e7572e1e91a7d02ba5b31daddec4388dfa607fb39b23b8d84b63d68eb9baca5b212f5935e9a4d3855ac5799466be542d30cc8fba337269597fd8dbb237194
6
+ metadata.gz: 1a5aa4b5e32b63a67c2cb557a77014fe1d6cbd55d4f1df437480ff2c3502e4f9fbb533caea745e90a3cb7fd8013439d591f4247c5203e44e75dec638a62abf3e
7
+ data.tar.gz: 7b66c4d564e21310dbc962eb1398886441a0a374b85f955a1e317cdbaef8fb27ea312d1987c33c611138e6ff1bf48e9f4de6a7301f0666423658bf7f831ec0ab
@@ -1,10 +1,11 @@
1
1
  require 'open-uri'
2
+ require 'openssl'
2
3
 
3
4
  module Spree
4
5
  module Images
5
6
  class SaveFromUrlJob < ::Spree::BaseJob
6
7
  queue_as Spree.queues.images
7
- retry_on ActiveRecord::RecordInvalid, OpenURI::HTTPError, wait: :polynomially_longer, attempts: 2
8
+ retry_on ActiveRecord::RecordInvalid, OpenURI::HTTPError, wait: :polynomially_longer, attempts: Spree::Config.images_save_from_url_job_attempts.to_i
8
9
  discard_on URI::InvalidURIError
9
10
 
10
11
  def perform(viewable_id, viewable_type, external_url, external_id = nil, position = nil)
@@ -33,7 +34,17 @@ module Spree
33
34
  raise URI::InvalidURIError, "Invalid URL scheme: #{uri.scheme}. Only http and https are allowed."
34
35
  end
35
36
 
36
- file = uri.open
37
+ file = uri.open(
38
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
39
+ 'Accept' => 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
40
+ 'Accept-Language' => 'en-US,en;q=0.9',
41
+ 'Accept-Encoding' => 'gzip, deflate, br',
42
+ 'Cache-Control' => 'no-cache',
43
+ 'Pragma' => 'no-cache',
44
+ read_timeout: 60,
45
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
46
+ redirect: true
47
+ )
37
48
  filename = File.basename(uri.path)
38
49
 
39
50
  image.attachment.attach(io: file, filename: filename)
@@ -76,6 +76,7 @@ module Spree
76
76
  self.item = import.row_processor_class.new(self).process!
77
77
  complete!
78
78
  rescue StandardError => e
79
+ Rails.error.report(e, handled: true, context: { import_row_id: id }, source: 'spree.core')
79
80
  self.validation_errors = e.message
80
81
  fail!
81
82
  end
@@ -26,7 +26,7 @@ module Spree
26
26
  { name: 'inventory_count', label: 'Inventory Count' },
27
27
  { name: 'inventory_backorderable', label: 'Inventory Backorderable' },
28
28
  { name: 'tax_category', label: 'Tax Category' },
29
- { name: 'digital', label: 'Digital' },
29
+ { name: 'shipping_category', label: 'Shipping Category' },
30
30
  { name: 'image1_src', label: 'Image 1 URL' },
31
31
  { name: 'image2_src', label: 'Image 2 URL' },
32
32
  { name: 'image3_src', label: 'Image 3 URL' },
@@ -82,13 +82,19 @@ module Spree
82
82
  end
83
83
 
84
84
  extend DisplayMoney
85
- money_methods :amount, :subtotal, :discounted_amount, :final_amount, :total, :price,
85
+ money_methods :amount, :subtotal, :discounted_amount, :final_amount, :total, :price, :discounted_price,
86
86
  :adjustment_total, :additional_tax_total, :promo_total, :included_tax_total,
87
87
  :pre_tax_amount, :shipping_cost, :tax_total, :compare_at_amount
88
88
 
89
89
  alias single_money display_price
90
90
  alias single_display_amount display_price
91
91
 
92
+ def discounted_price
93
+ return price if quantity.zero?
94
+
95
+ price - (promo_total.abs / quantity)
96
+ end
97
+
92
98
  def amount
93
99
  price * quantity
94
100
  end
@@ -39,8 +39,8 @@ module Spree
39
39
  # Associations
40
40
  #
41
41
  belongs_to :author, class_name: Spree.admin_user_class.to_s, optional: true
42
- belongs_to :store, class_name: 'Spree::Store'
43
- belongs_to :post_category, class_name: 'Spree::PostCategory', optional: true
42
+ belongs_to :store, class_name: 'Spree::Store', inverse_of: :posts
43
+ belongs_to :post_category, class_name: 'Spree::PostCategory', optional: true, touch: true, inverse_of: :posts
44
44
  alias category post_category
45
45
 
46
46
  #
@@ -9,8 +9,8 @@ module Spree
9
9
  #
10
10
  # Associations
11
11
  #
12
- belongs_to :store, class_name: 'Spree::Store'
13
- has_many :posts, class_name: 'Spree::Post'
12
+ belongs_to :store, class_name: 'Spree::Store', inverse_of: :post_categories
13
+ has_many :posts, class_name: 'Spree::Post', dependent: :nullify, inverse_of: :post_category
14
14
 
15
15
  #
16
16
  # Validations
@@ -18,8 +18,16 @@ module Spree
18
18
  validates :title, :store, presence: true
19
19
  validates :slug, presence: true, uniqueness: { scope: :store_id }
20
20
 
21
+ #
22
+ # ActionText
23
+ #
21
24
  has_rich_text :description
22
25
 
26
+ #
27
+ # Ransack
28
+ #
29
+ self.whitelisted_ransackable_attributes = %w[title slug]
30
+
23
31
  def should_generate_new_friendly_id?
24
32
  slug.blank? || title_changed?
25
33
  end
@@ -7,9 +7,9 @@ module Spree
7
7
 
8
8
  money_methods :base_price, :final_price, :tax_amount
9
9
 
10
- delegate :order, :currency, :free?, :with_free_shipping_promotion?, to: :shipment
11
- delegate :name, to: :shipping_method
12
- delegate :code, to: :shipping_method, prefix: true
10
+ delegate :order, :currency, :with_free_shipping_promotion?, to: :shipment
11
+ delegate :name, to: :shipping_method
12
+ delegate :code, to: :shipping_method, prefix: true
13
13
 
14
14
  def display_price
15
15
  price = display_base_price.to_s
@@ -27,6 +27,10 @@ module Spree
27
27
  alias display_cost display_price
28
28
  alias_attribute :base_price, :cost
29
29
 
30
+ def free?
31
+ final_price.zero?
32
+ end
33
+
30
34
  def tax_amount
31
35
  @tax_amount ||= tax_rate&.calculator&.compute_shipping_rate(self) || BigDecimal(0)
32
36
  end
@@ -106,8 +106,8 @@ module Spree
106
106
  has_many :custom_domains, class_name: 'Spree::CustomDomain', dependent: :destroy
107
107
  has_one :default_custom_domain, -> { where(default: true) }, class_name: 'Spree::CustomDomain'
108
108
 
109
- has_many :posts, class_name: 'Spree::Post'
110
- has_many :post_categories, class_name: 'Spree::PostCategory'
109
+ has_many :posts, class_name: 'Spree::Post', dependent: :destroy, inverse_of: :store
110
+ has_many :post_categories, class_name: 'Spree::PostCategory', dependent: :destroy, inverse_of: :store
111
111
 
112
112
  has_many :integrations, class_name: 'Spree::Integration'
113
113
 
@@ -403,11 +403,10 @@ module Spree
403
403
 
404
404
  def set_stock(count_on_hand, backorderable = nil, stock_location = nil)
405
405
  stock_location ||= Spree::Store.current.default_stock_location
406
- stock_items.find_or_initialize_by(stock_location: stock_location) do |stock_item|
407
- stock_item.count_on_hand = count_on_hand
408
- stock_item.backorderable = backorderable if backorderable.present?
409
- stock_item.save!
410
- end
406
+ stock_item = stock_items.find_or_initialize_by(stock_location: stock_location)
407
+ stock_item.count_on_hand = count_on_hand
408
+ stock_item.backorderable = backorderable if backorderable.present?
409
+ stock_item.save!
411
410
  end
412
411
 
413
412
  def price_modifier_amount_in(currency, options = {})
@@ -32,7 +32,7 @@ module Spree
32
32
  'inventory_count',
33
33
  'inventory_backorderable',
34
34
  'tax_category',
35
- 'digital',
35
+ 'shipping_category',
36
36
  'image1_src',
37
37
  'image2_src',
38
38
  'image3_src',
@@ -66,7 +66,7 @@ module Spree
66
66
  # For the primary variant row (when the index is zero), product-level details such as name,
67
67
  # slug, status, vendor and brand names, description, meta tags, and tag/label lists are included.
68
68
  # In all cases, variant-specific attributes (e.g., id, SKU, pricing, dimensions, weight,
69
- # availability dates, inventory count, digital flag, tax category, image URLs via original_url,
69
+ # availability dates, inventory count, shipping category, tax category, image URLs via original_url,
70
70
  # and the first three option types and corresponding option values) are appended.
71
71
  # Additionally, when the index is zero, associated taxons and properties are added.
72
72
  #
@@ -103,7 +103,7 @@ module Spree
103
103
  total_on_hand == BigDecimal::INFINITY ? '∞' : total_on_hand,
104
104
  variant.backorderable?,
105
105
  variant.tax_category&.name,
106
- variant.digital?,
106
+ product.shipping_category&.name,
107
107
  spree_image_url(variant.images[0], image_url_options),
108
108
  spree_image_url(variant.images[1], image_url_options),
109
109
  spree_image_url(variant.images[2], image_url_options),
@@ -33,6 +33,12 @@ module Spree
33
33
  variant.depth = attributes['depth'] if attributes['depth'].present?
34
34
  variant.track_inventory = attributes['track_inventory'] if attributes['track_inventory'].present?
35
35
  variant.option_value_variants = prepare_option_value_variants
36
+
37
+ if attributes['tax_category'].present?
38
+ tax_category = prepare_tax_category
39
+ variant.tax_category = tax_category if tax_category.present?
40
+ end
41
+
36
42
  variant.save!
37
43
 
38
44
  if attributes['price'].present?
@@ -52,16 +58,26 @@ module Spree
52
58
  private
53
59
 
54
60
  def ensure_product_exists
55
- product = Spree::Product.new
56
- if attributes['slug'].present?
57
- existing_product = product_scope.find_by(slug: attributes['slug'].strip.downcase)
58
- product = existing_product if existing_product.present?
59
- end
61
+ if options.empty?
62
+ # For master variants, create or update the product
63
+ product = Spree::Product.new
64
+ if attributes['slug'].present?
65
+ existing_product = product_scope.find_by(slug: attributes['slug'].strip.downcase)
66
+ product = existing_product if existing_product.present?
67
+ end
60
68
 
61
- product = assign_attributes_to_product(product)
62
- product.save!
63
- handle_metafields(product)
64
- product
69
+ product = assign_attributes_to_product(product)
70
+ product.save!
71
+ handle_metafields(product)
72
+ product
73
+ else
74
+ # For non-master variants, only look up the product
75
+ if attributes['slug'].present?
76
+ product_scope.find_by!(slug: attributes['slug'].strip.downcase)
77
+ else
78
+ raise ActiveRecord::RecordNotFound, 'Product slug is required for variant rows'
79
+ end
80
+ end
65
81
  end
66
82
 
67
83
  def product_scope
@@ -84,10 +100,27 @@ module Spree
84
100
  product.status = to_spree_status(attributes['status']) if attributes['status'].present?
85
101
  product.tag_list = attributes['tags'] if attributes['tags'].present?
86
102
 
87
- product.taxons = prepare_taxons if options.empty?
103
+ if options.empty?
104
+ if attributes['shipping_category'].present?
105
+ shipping_category = prepare_shipping_category
106
+ product.shipping_category = shipping_category if shipping_category.present?
107
+ end
108
+ product.taxons = prepare_taxons
109
+ end
110
+
88
111
  product
89
112
  end
90
113
 
114
+ def prepare_shipping_category
115
+ shipping_category_name = attributes['shipping_category'].strip
116
+ Spree::ShippingCategory.find_by(name: shipping_category_name)
117
+ end
118
+
119
+ def prepare_tax_category
120
+ tax_category_name = attributes['tax_category'].strip
121
+ Spree::TaxCategory.find_by(name: tax_category_name)
122
+ end
123
+
91
124
  def prepare_taxons
92
125
  taxon_pretty_names = [
93
126
  attributes['category1'],
@@ -6,11 +6,11 @@ module Spree
6
6
  def call
7
7
  if Spree.admin_user_class.present? && Spree.admin_user_class.count.zero?
8
8
  user = Spree.admin_user_class.create!(
9
- email: 'spree@example.com',
10
- password: 'spree123',
11
- password_confirmation: 'spree123',
12
- first_name: 'Spree',
13
- last_name: 'Admin'
9
+ email: ENV.fetch('ADMIN_EMAIL', 'spree@example.com'),
10
+ password: ENV.fetch('ADMIN_PASSWORD', 'spree123'),
11
+ password_confirmation: ENV.fetch('ADMIN_PASSWORD', 'spree123'),
12
+ first_name: ENV.fetch('ADMIN_FIRST_NAME', 'Spree'),
13
+ last_name: ENV.fetch('ADMIN_LAST_NAME', 'Admin')
14
14
  )
15
15
  user.save!
16
16
 
@@ -48,6 +48,30 @@ module Spree
48
48
  say "Please replace < ApplicationRecord with < Spree.base_class in #{user_class_file}"
49
49
  end
50
50
 
51
+ if Spree.admin_user_class != Spree.user_class
52
+ admin_user_class_file = Rails.root.join('app', 'models', "#{Spree.admin_user_class.name.underscore}.rb")
53
+
54
+ if File.exist?(admin_user_class_file)
55
+ inject_into_file admin_user_class_file, after: "class #{Spree.admin_user_class.name} < ApplicationRecord\n" do
56
+ <<-RUBY
57
+ # Spree modules
58
+ include Spree::UserMethods
59
+ RUBY
60
+ end
61
+ gsub_file admin_user_class_file, "< ApplicationRecord", "< Spree.base_class"
62
+
63
+ say "Successfully added Spree admin user modules into #{admin_user_class_file}"
64
+ else
65
+ say "Could not locate admin user model file at #{admin_user_class_file}. Please add these lines manually:", :red
66
+ say <<~RUBY
67
+ # Spree modules
68
+ include Spree::UserMethods
69
+ RUBY
70
+
71
+ say "Please replace < ApplicationRecord with < Spree.base_class in #{admin_user_class_file}"
72
+ end
73
+ end
74
+
51
75
  append_file 'config/initializers/spree.rb' do
52
76
  %Q{
53
77
  if defined?(Devise) && Devise.respond_to?(:parent_controller)
@@ -14,6 +14,7 @@ module Spree
14
14
  class_option :install_admin, type: :boolean, default: false, banner: 'installs default rails admin'
15
15
  class_option :auto_accept, type: :boolean
16
16
  class_option :user_class, type: :string
17
+ class_option :admin_user_class, type: :string
17
18
  class_option :admin_email, type: :string
18
19
  class_option :admin_password, type: :string
19
20
  class_option :lib_name, type: :string, default: 'spree'
@@ -69,6 +70,11 @@ module Spree
69
70
  def install_admin
70
71
  if @install_admin && Spree::Core::Engine.admin_available?
71
72
  generate 'spree:admin:install'
73
+
74
+ # generate devise controllers if authentication is devise
75
+ if @authentication == 'devise'
76
+ generate 'spree:admin:devise'
77
+ end
72
78
  end
73
79
  end
74
80
 
@@ -95,7 +101,6 @@ module Spree
95
101
  append_file 'db/seeds.rb', <<-SEEDS.strip_heredoc
96
102
 
97
103
  Spree::Core::Engine.load_seed if defined?(Spree::Core)
98
- Spree::Auth::Engine.load_seed if defined?(Spree::Auth)
99
104
  SEEDS
100
105
  end
101
106
 
@@ -133,7 +138,7 @@ module Spree
133
138
  cmd.call
134
139
  end
135
140
  else
136
- say_status :skipping, 'seed data (you can always run rake db:seed)'
141
+ say_status :skipping, 'seed data (you can always run bin/rails db:seed)'
137
142
  end
138
143
  end
139
144
 
@@ -91,5 +91,4 @@ Rails.application.config.after_initialize do
91
91
  end
92
92
 
93
93
  Spree.user_class = <%= (options[:user_class].blank? ? 'Spree::LegacyUser' : options[:user_class]).inspect %>
94
- # Use a different class for admin users
95
- # Spree.admin_user_class = 'AdminUser'
94
+ Spree.admin_user_class = <%= (options[:admin_user_class].blank? ? (options[:user_class].blank? ? 'Spree::LegacyUser' : options[:user_class]) : options[:admin_user_class]).inspect %>
@@ -45,6 +45,7 @@ module Spree
45
45
  preference :expedited_exchanges, :boolean, default: false # NOTE this requires payment profiles to be supported on your gateway of choice as well as a delayed job handler to be configured with activejob. kicks off an exchange shipment upon return authorization save. charge customer if they do not return items within timely manner.
46
46
  preference :expedited_exchanges_days_window, :integer, default: 14 # the amount of days the customer has to return their item after the expedited exchange is shipped in order to avoid being charged
47
47
  preference :geocode_addresses, :boolean, default: true
48
+ preference :images_save_from_url_job_attempts, :integer, default: 5
48
49
  preference :layout, :string, deprecated: 'Please use Spree::Frontend::Config[:layout] instead'
49
50
  preference :logo, :string, deprecated: true
50
51
  preference :mailer_logo, :string, deprecated: true
@@ -118,11 +118,20 @@ module Spree
118
118
  value: token,
119
119
  expires: 90.days.from_now,
120
120
  secure: Rails.configuration.force_ssl || Rails.application.config.ssl_options[:secure_cookies],
121
- domain: current_store.url_or_custom_domain,
121
+ domain: cookie_domain_without_port,
122
122
  httponly: true
123
123
  }
124
124
  end
125
125
 
126
+ def cookie_domain_without_port
127
+ domain = current_store.url_or_custom_domain
128
+ return nil if domain.blank?
129
+
130
+ # Remove port from domain (e.g., "localhost:3000" -> "localhost")
131
+ # Cookies don't support port numbers in the domain attribute
132
+ domain.split(':').first
133
+ end
134
+
126
135
  def last_incomplete_order(includes = {})
127
136
  @last_incomplete_order ||= try_spree_current_user.last_incomplete_spree_order(current_store, includes: includes)
128
137
  end
@@ -3,6 +3,8 @@ module Spree
3
3
  module Importer
4
4
  class Order
5
5
  def self.import(user, params)
6
+ Spree::Deprecation.warn('Spree::Core::Importer::Order is deprecated and will be removed in Spree 6.0. Please use `Spree::Imports::Order` instead.')
7
+
6
8
  ensure_country_id_from_params params[:ship_address_attributes]
7
9
  ensure_state_id_from_params params[:ship_address_attributes]
8
10
  ensure_country_id_from_params params[:bill_address_attributes]
@@ -5,6 +5,8 @@ module Spree
5
5
  attr_reader :product, :product_attrs, :variants_attrs, :options_attrs, :store
6
6
 
7
7
  def initialize(product, product_params, options = {})
8
+ Spree::Deprecation.warn('Spree::Core::Importer::Product is deprecated and will be removed in Spree 6.0. Please use `Spree::Imports::Product` instead.')
9
+
8
10
  @store = options[:store] || Spree::Store.default
9
11
  @product = product || Spree::Product.new(product_params)
10
12
  @product.stores << @store if @product.stores.exclude?(@store)
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.2.0.rc1'.freeze
2
+ VERSION = '5.2.0.rc3'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -30,6 +30,7 @@ namespace :common do
30
30
  Spree::DummyGenerator.start dummy_app_args
31
31
 
32
32
  unless skip_javascript
33
+ system('bundle add sprockets-rails') # we need this until we will remove bootstrap/popper_js gems
33
34
  system('bundle exec rails importmap:install turbo:install stimulus:install')
34
35
  end
35
36
 
@@ -11,11 +11,11 @@ FactoryBot.define do
11
11
  end
12
12
 
13
13
  factory :shipping_calculator, class: Spree::Calculator::Shipping::FlatRate do
14
- after(:create) { |c| c.set_preference(:amount, 10.0) }
14
+ preferred_amount { 10.0 }
15
15
  end
16
16
 
17
17
  factory :shipping_no_amount_calculator, class: Spree::Calculator::Shipping::FlatRate do
18
- after(:create) { |c| c.set_preference(:amount, 0) }
18
+ preferred_amount { 0 }
19
19
  end
20
20
 
21
21
  factory :flat_rate_calculator, class: Spree::Calculator::FlatRate do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0.rc1
4
+ version: 5.2.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-11-04 00:00:00.000000000 Z
13
+ date: 2025-11-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -1656,9 +1656,9 @@ licenses:
1656
1656
  - BSD-3-Clause
1657
1657
  metadata:
1658
1658
  bug_tracker_uri: https://github.com/spree/spree/issues
1659
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.0.rc1
1659
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.0.rc3
1660
1660
  documentation_uri: https://docs.spreecommerce.org/
1661
- source_code_uri: https://github.com/spree/spree/tree/v5.2.0.rc1
1661
+ source_code_uri: https://github.com/spree/spree/tree/v5.2.0.rc3
1662
1662
  post_install_message:
1663
1663
  rdoc_options: []
1664
1664
  require_paths: