spree_core 5.2.5 → 5.2.6
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 +4 -4
- data/app/helpers/spree/addresses_helper.rb +8 -0
- data/app/models/concerns/spree/ransackable_attributes.rb +6 -3
- data/app/models/spree/line_item.rb +2 -2
- data/app/models/spree/order/digital.rb +13 -5
- data/app/models/spree/payment/processing.rb +1 -0
- data/app/models/spree/policy.rb +9 -0
- data/app/models/spree/product.rb +1 -1
- data/app/models/spree/promotion/rules/option_value.rb +12 -3
- data/app/models/spree/promotion/rules/product.rb +22 -9
- data/app/models/spree/promotion/rules/taxon.rb +34 -26
- data/app/models/spree/promotion/rules/user.rb +17 -4
- data/app/models/spree/shipping_category.rb +6 -0
- data/app/models/spree/stock/quantifier.rb +1 -1
- data/app/models/spree/store.rb +56 -26
- data/app/models/spree/variant.rb +5 -1
- data/app/presenters/spree/variants/options_presenter.rb +1 -0
- data/app/services/spree/cart/remove_out_of_stock_items.rb +6 -12
- data/app/views/spree/addresses/_form.html.erb +4 -2
- data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +2 -2
- data/lib/generators/spree/dummy/dummy_generator.rb +34 -27
- data/lib/spree/core/ransack_configuration.rb +79 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +14 -0
- data/lib/spree/database_type_utilities.rb +4 -1
- data/lib/spree/testing_support/common_rake.rb +5 -2
- data/lib/spree/testing_support/factories/order_factory.rb +3 -0
- data/lib/spree/testing_support/factories/store_credit_factory.rb +1 -1
- data/lib/spree/testing_support/store.rb +6 -2
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e0552e5475aa54e072158a70ea648d02d5514550eca293f1c0e0c79554c4216
|
|
4
|
+
data.tar.gz: afe49cf6d48fa819396d1518e3531435ffc22d038ce1a75aef430082bbc429fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46b12717f12408f073b31a2a4f70d8850204bcfd59d32b20a6d891bc0326b7ea0c563c9b876e81156c6ba40f6ba88705553f87bc349598f52e8d761a7ea887a7
|
|
7
|
+
data.tar.gz: 37e34cbc4020055babe24354344aae7306dde8343267b2bac666fe691881936e5ebf6daeaa982ad4ce1ea4df88eecc77cb168c5321c57c2329b920dba37d05d7
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# https://github.com/spree-contrib/spree_address_book/blob/master/app/helpers/spree/addresses_helper.rb
|
|
2
2
|
module Spree
|
|
3
3
|
module AddressesHelper
|
|
4
|
+
def address_form_countries_states_cache_key
|
|
5
|
+
@address_form_countries_states_cache_key ||= [
|
|
6
|
+
I18n.locale,
|
|
7
|
+
current_store.cache_key_with_version,
|
|
8
|
+
current_store.checkout_zone&.cache_key_with_version
|
|
9
|
+
].compact
|
|
10
|
+
end
|
|
11
|
+
|
|
4
12
|
def address_field(form, method, address_id = 'b', required = false, text_field_attributes: {}, &handler)
|
|
5
13
|
content_tag :div, id: [address_id, method].join, class: 'mb-4' do
|
|
6
14
|
if handler
|
|
@@ -9,15 +9,18 @@ module Spree::RansackableAttributes
|
|
|
9
9
|
self.default_ransackable_attributes = %w[id name updated_at created_at]
|
|
10
10
|
|
|
11
11
|
def self.ransackable_associations(*_args)
|
|
12
|
-
whitelisted_ransackable_associations || []
|
|
12
|
+
base = whitelisted_ransackable_associations || []
|
|
13
|
+
base | Spree.ransack.custom_associations_for(self)
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def self.ransackable_attributes(*_args)
|
|
16
|
-
default_ransackable_attributes | (whitelisted_ransackable_attributes || [])
|
|
17
|
+
base = default_ransackable_attributes | (whitelisted_ransackable_attributes || [])
|
|
18
|
+
base | Spree.ransack.custom_attributes_for(self)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def self.ransackable_scopes(*_args)
|
|
20
|
-
whitelisted_ransackable_scopes || []
|
|
22
|
+
base = whitelisted_ransackable_scopes || []
|
|
23
|
+
base | Spree.ransack.custom_scopes_for(self)
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
end
|
|
@@ -50,7 +50,7 @@ module Spree
|
|
|
50
50
|
delegate :name, :description, :sku, :should_track_inventory?, :product, :options_text, :slug, :product_id, :dimensions_unit, :weight_unit, to: :variant
|
|
51
51
|
delegate :brand, :category, to: :product
|
|
52
52
|
delegate :tax_zone, to: :order
|
|
53
|
-
delegate :digital?, to: :variant
|
|
53
|
+
delegate :digital?, :can_supply?, to: :variant
|
|
54
54
|
|
|
55
55
|
scope :with_digital_assets, -> { joins(:variant).merge(Spree::Variant.with_digital_assets) }
|
|
56
56
|
|
|
@@ -134,7 +134,7 @@ module Spree
|
|
|
134
134
|
alias money display_total
|
|
135
135
|
|
|
136
136
|
def sufficient_stock?
|
|
137
|
-
|
|
137
|
+
can_supply? quantity
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def insufficient_stock?
|
|
@@ -5,10 +5,10 @@ module Spree
|
|
|
5
5
|
#
|
|
6
6
|
# @return [Boolean]
|
|
7
7
|
def digital?
|
|
8
|
-
if line_items.empty?
|
|
8
|
+
if item_count.zero? || line_items.empty?
|
|
9
9
|
false
|
|
10
10
|
else
|
|
11
|
-
line_items.all?(&:digital?)
|
|
11
|
+
line_items.includes(variant: :product).all?(&:digital?)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -16,21 +16,29 @@ module Spree
|
|
|
16
16
|
#
|
|
17
17
|
# @return [Boolean]
|
|
18
18
|
def some_digital?
|
|
19
|
-
line_items.
|
|
19
|
+
if item_count.zero? || line_items.empty?
|
|
20
|
+
false
|
|
21
|
+
else
|
|
22
|
+
line_items.includes(variant: :product).any?(&:digital?)
|
|
23
|
+
end
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
# Returns true if any order line item has digital assets
|
|
23
27
|
#
|
|
24
28
|
# @return [Boolean]
|
|
25
29
|
def with_digital_assets?
|
|
26
|
-
line_items.
|
|
30
|
+
if item_count.zero? || line_items.empty?
|
|
31
|
+
false
|
|
32
|
+
else
|
|
33
|
+
line_items.includes(:variant).any?(&:with_digital_assets?)
|
|
34
|
+
end
|
|
27
35
|
end
|
|
28
36
|
|
|
29
37
|
# Returns all line items with digital assets
|
|
30
38
|
#
|
|
31
39
|
# @return [Array<Spree::LineItem>]
|
|
32
40
|
def digital_line_items
|
|
33
|
-
line_items.with_digital_assets.distinct
|
|
41
|
+
line_items.joins(:variant).with_digital_assets.distinct
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
# Returns all digital links for the order
|
data/app/models/spree/policy.rb
CHANGED
|
@@ -39,6 +39,15 @@ module Spree
|
|
|
39
39
|
#
|
|
40
40
|
scope :with_body, -> { joins(:rich_text_body).distinct }
|
|
41
41
|
scope :without_body, -> { where.missing(:rich_text_body) }
|
|
42
|
+
scope :with_matching_name, ->(name_to_match) do
|
|
43
|
+
value = name_to_match.to_s.strip.downcase
|
|
44
|
+
|
|
45
|
+
if Spree.use_translations?
|
|
46
|
+
i18n { name.lower.eq(value) }
|
|
47
|
+
else
|
|
48
|
+
where(arel_table[:name].lower.eq(value))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
42
51
|
|
|
43
52
|
#
|
|
44
53
|
# Ransack
|
data/app/models/spree/product.rb
CHANGED
|
@@ -13,15 +13,24 @@ module Spree
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def eligible?(promotable, _options = {})
|
|
16
|
+
return false if eligible_option_value_variant_ids.empty?
|
|
17
|
+
|
|
16
18
|
case preferred_match_policy
|
|
17
19
|
when 'any'
|
|
18
|
-
|
|
20
|
+
Spree::OptionValueVariant.where(id: eligible_option_value_variant_ids, variant_id: promotable.variant_ids).exists?
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def actionable?(line_item)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
return false if eligible_option_value_variant_ids.empty?
|
|
26
|
+
|
|
27
|
+
Spree::OptionValueVariant.where(id: eligible_option_value_variant_ids, variant_id: line_item.variant_id).exists?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def eligible_option_value_variant_ids
|
|
33
|
+
@eligible_option_value_variant_ids ||= preferred_eligible_values.map(&:to_s)
|
|
25
34
|
end
|
|
26
35
|
end
|
|
27
36
|
end
|
|
@@ -34,23 +34,27 @@ module Spree
|
|
|
34
34
|
products
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def eligible_product_ids
|
|
38
|
+
@eligible_product_ids ||= product_promotion_rules.pluck(:product_id)
|
|
39
|
+
end
|
|
40
|
+
|
|
37
41
|
def applicable?(promotable)
|
|
38
42
|
promotable.is_a?(Spree::Order)
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def eligible?(order, _options = {})
|
|
42
|
-
return true if
|
|
46
|
+
return true if eligible_product_ids.empty?
|
|
43
47
|
|
|
44
48
|
if preferred_match_policy == 'all'
|
|
45
|
-
unless
|
|
49
|
+
unless eligible_product_ids.all? { |p| order.product_ids.include?(p) }
|
|
46
50
|
eligibility_errors.add(:base, eligibility_error_message(:missing_product))
|
|
47
51
|
end
|
|
48
52
|
elsif preferred_match_policy == 'any'
|
|
49
|
-
unless order.
|
|
53
|
+
unless order.product_ids.any? { |p| eligible_product_ids.include?(p) }
|
|
50
54
|
eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products))
|
|
51
55
|
end
|
|
52
56
|
else
|
|
53
|
-
unless order.
|
|
57
|
+
unless order.product_ids.none? { |p| eligible_product_ids.include?(p) }
|
|
54
58
|
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product))
|
|
55
59
|
end
|
|
56
60
|
end
|
|
@@ -61,9 +65,9 @@ module Spree
|
|
|
61
65
|
def actionable?(line_item)
|
|
62
66
|
case preferred_match_policy
|
|
63
67
|
when 'any', 'all'
|
|
64
|
-
|
|
68
|
+
eligible_product_ids.include? line_item.variant.product_id
|
|
65
69
|
when 'none'
|
|
66
|
-
|
|
70
|
+
eligible_product_ids.exclude? line_item.variant.product_id
|
|
67
71
|
else
|
|
68
72
|
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
|
69
73
|
end
|
|
@@ -89,9 +93,18 @@ module Spree
|
|
|
89
93
|
return if product_ids_to_add.nil?
|
|
90
94
|
|
|
91
95
|
product_promotion_rules.delete_all
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
|
|
97
|
+
if product_ids_to_add.any?
|
|
98
|
+
Spree::ProductPromotionRule.insert_all(
|
|
99
|
+
product_ids_to_add.map { |product_id| { product_id: product_id, promotion_rule_id: id } }
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Invalidate cache after bulk operations
|
|
104
|
+
touch
|
|
105
|
+
|
|
106
|
+
# Clear memoized values
|
|
107
|
+
@eligible_product_ids = nil
|
|
95
108
|
end
|
|
96
109
|
end
|
|
97
110
|
end
|
|
@@ -30,14 +30,21 @@ module Spree
|
|
|
30
30
|
promotable.is_a?(Spree::Order)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def eligible_taxon_ids
|
|
34
|
+
@eligible_taxon_ids ||= promotion_rule_taxons.pluck(:taxon_id)
|
|
35
|
+
end
|
|
36
|
+
|
|
33
37
|
def eligible?(order, _options = {})
|
|
38
|
+
return true if eligible_taxon_ids.empty?
|
|
39
|
+
|
|
34
40
|
if preferred_match_policy == 'all'
|
|
35
|
-
|
|
41
|
+
order_taxon_ids_with_ancestors = taxon_ids_in_order_including_ancestors(order)
|
|
42
|
+
unless eligible_taxon_ids.all? { |id| order_taxon_ids_with_ancestors.include?(id) }
|
|
36
43
|
eligibility_errors.add(:base, eligibility_error_message(:missing_taxon))
|
|
37
44
|
end
|
|
38
45
|
else
|
|
39
|
-
|
|
40
|
-
unless
|
|
46
|
+
order_taxon_ids_with_ancestors = taxon_ids_in_order_including_ancestors(order)
|
|
47
|
+
unless eligible_taxon_ids.any? { |id| order_taxon_ids_with_ancestors.include?(id) }
|
|
41
48
|
eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons))
|
|
42
49
|
end
|
|
43
50
|
end
|
|
@@ -46,12 +53,7 @@ module Spree
|
|
|
46
53
|
end
|
|
47
54
|
|
|
48
55
|
def actionable?(line_item)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
store.products.
|
|
52
|
-
joins(:classifications).
|
|
53
|
-
where(Spree::Classification.table_name => { taxon_id: taxon_ids, product_id: line_item.product_id }).
|
|
54
|
-
exists?
|
|
56
|
+
Spree::Classification.where(taxon_id: eligible_taxon_ids_including_children, product_id: line_item.product_id).exists?
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def taxon_ids_string
|
|
@@ -71,34 +73,40 @@ module Spree
|
|
|
71
73
|
|
|
72
74
|
private
|
|
73
75
|
|
|
74
|
-
#
|
|
75
|
-
def
|
|
76
|
-
|
|
76
|
+
# IDs of taxons in rule including all their children
|
|
77
|
+
def eligible_taxon_ids_including_children
|
|
78
|
+
@eligible_taxon_ids_including_children ||= begin
|
|
79
|
+
return [] if eligible_taxon_ids.empty?
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
Spree::Taxon.where(id: eligible_taxon_ids).flat_map(&:cached_self_and_descendants_ids).uniq
|
|
82
|
+
end
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
#
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
# IDs of taxons in order that match rule taxons (or their children), plus all ancestors
|
|
86
|
+
def taxon_ids_in_order_including_ancestors(order)
|
|
87
|
+
# Get taxon IDs from order products that are within rule taxons or their children
|
|
88
|
+
order_taxon_ids = Spree::Classification.where(product_id: order.product_ids, taxon_id: eligible_taxon_ids_including_children).pluck(:taxon_id).uniq
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
def order_taxons_in_taxons_and_children(order)
|
|
88
|
-
order_taxons(order).where(id: taxons_including_children_ids)
|
|
89
|
-
end
|
|
90
|
+
return [] if order_taxon_ids.empty?
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
# Get those taxons plus all their ancestors
|
|
93
|
+
Spree::Taxon.where(id: order_taxon_ids).flat_map { |taxon| taxon.self_and_ancestors.ids }.uniq
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def add_taxons
|
|
96
97
|
return if taxon_ids_to_add.nil?
|
|
97
98
|
|
|
98
99
|
promotion_rule_taxons.delete_all
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
|
|
101
|
+
if taxon_ids_to_add.any?
|
|
102
|
+
Spree::PromotionRuleTaxon.insert_all(
|
|
103
|
+
taxon_ids_to_add.map { |taxon_id| { taxon_id: taxon_id, promotion_rule_id: id } }
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Clear memoized values
|
|
108
|
+
@eligible_taxon_ids = nil
|
|
109
|
+
@eligible_taxon_ids_including_children = nil
|
|
102
110
|
end
|
|
103
111
|
end
|
|
104
112
|
end
|
|
@@ -24,8 +24,12 @@ module Spree
|
|
|
24
24
|
promotable.is_a?(Spree::Order)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def eligible_user_ids
|
|
28
|
+
@eligible_user_ids ||= promotion_rule_users.pluck(:user_id)
|
|
29
|
+
end
|
|
30
|
+
|
|
27
31
|
def eligible?(order, _options = {})
|
|
28
|
-
|
|
32
|
+
eligible_user_ids.include?(order.user_id)
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def user_ids_string
|
|
@@ -50,9 +54,18 @@ module Spree
|
|
|
50
54
|
return if user_ids_to_add.nil?
|
|
51
55
|
|
|
52
56
|
promotion_rule_users.delete_all
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
|
|
58
|
+
if user_ids_to_add.any?
|
|
59
|
+
Spree::PromotionRuleUser.insert_all(
|
|
60
|
+
user_ids_to_add.map { |user_id| { user_id: user_id, promotion_rule_id: id } }
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Invalidate cache after bulk operations
|
|
65
|
+
touch
|
|
66
|
+
|
|
67
|
+
# Clear memoized values
|
|
68
|
+
@eligible_user_ids = nil
|
|
56
69
|
end
|
|
57
70
|
end
|
|
58
71
|
end
|
|
@@ -16,5 +16,11 @@ module Spree
|
|
|
16
16
|
def self.digital
|
|
17
17
|
find_by(name: DIGITAL_NAME)
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
def includes_digital_shipping_method?
|
|
21
|
+
Rails.cache.fetch("#{cache_key_with_version}/includes-digital-shipping-method") do
|
|
22
|
+
shipping_methods.digital.exists?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
19
25
|
end
|
|
20
26
|
end
|
data/app/models/spree/store.rb
CHANGED
|
@@ -496,48 +496,78 @@ module Spree
|
|
|
496
496
|
end
|
|
497
497
|
|
|
498
498
|
def ensure_default_taxonomies_are_created
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
499
|
+
[
|
|
500
|
+
translate_with_store_locale_fallback('spree.taxonomy_categories_name'),
|
|
501
|
+
translate_with_store_locale_fallback('spree.taxonomy_brands_name'),
|
|
502
|
+
translate_with_store_locale_fallback('spree.taxonomy_collections_name')
|
|
503
|
+
].each do |taxonomy_name|
|
|
504
|
+
# Manual exists?/create to work around Mobility bug with find_or_create_by
|
|
505
|
+
next if taxonomies.with_matching_name(taxonomy_name).exists?
|
|
506
|
+
|
|
507
|
+
taxonomies.create(name: taxonomy_name)
|
|
508
|
+
end
|
|
503
509
|
end
|
|
504
510
|
|
|
505
511
|
def ensure_default_automatic_taxons
|
|
506
|
-
|
|
512
|
+
# Use Mobility-safe lookup for taxonomy
|
|
513
|
+
collections_taxonomy = taxonomies.with_matching_name(translate_with_store_locale_fallback('spree.taxonomy_collections_name')).first
|
|
514
|
+
return unless collections_taxonomy.present?
|
|
507
515
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
end
|
|
516
|
+
automatic_taxons_config = [
|
|
517
|
+
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.on_sale'), rule_type: 'Spree::TaxonRules::Sale', rule_value: 'true' },
|
|
518
|
+
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.new_arrivals'), rule_type: 'Spree::TaxonRules::AvailableOn', rule_value: 30 }
|
|
519
|
+
]
|
|
513
520
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
end
|
|
521
|
+
automatic_taxons_config.map do |config|
|
|
522
|
+
# Manual exists?/create to work around Mobility bug with first_or_create
|
|
523
|
+
taxon_scope = collections_taxonomy.taxons.automatic.with_matching_name(config[:name])
|
|
518
524
|
|
|
519
|
-
|
|
525
|
+
if taxon_scope.exists?
|
|
526
|
+
taxon_scope.first
|
|
527
|
+
else
|
|
528
|
+
collections_taxonomy.taxons.create!(
|
|
529
|
+
name: config[:name],
|
|
530
|
+
automatic: true,
|
|
531
|
+
parent: collections_taxonomy.root,
|
|
532
|
+
taxon_rules: [TaxonRule.new(type: config[:rule_type], value: config[:rule_value])]
|
|
533
|
+
)
|
|
534
|
+
end
|
|
520
535
|
end
|
|
521
536
|
end
|
|
522
537
|
|
|
523
538
|
def ensure_default_post_categories_are_created
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
539
|
+
[
|
|
540
|
+
translate_with_store_locale_fallback('spree.default_post_categories.resources'),
|
|
541
|
+
translate_with_store_locale_fallback('spree.default_post_categories.articles'),
|
|
542
|
+
translate_with_store_locale_fallback('spree.default_post_categories.news')
|
|
543
|
+
].each do |category_title|
|
|
544
|
+
# Use exists?/create pattern for safety
|
|
545
|
+
next if post_categories.where(title: category_title).exists?
|
|
546
|
+
|
|
547
|
+
post_categories.create(title: category_title)
|
|
548
|
+
end
|
|
527
549
|
end
|
|
528
550
|
|
|
529
551
|
def create_default_policies
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
552
|
+
[
|
|
553
|
+
translate_with_store_locale_fallback('spree.terms_of_service'),
|
|
554
|
+
translate_with_store_locale_fallback('spree.privacy_policy'),
|
|
555
|
+
translate_with_store_locale_fallback('spree.returns_policy'),
|
|
556
|
+
translate_with_store_locale_fallback('spree.shipping_policy')
|
|
557
|
+
].each do |policy_name|
|
|
558
|
+
# Manual exists?/create to work around Mobility bug with find_or_create_by
|
|
559
|
+
next if policies.with_matching_name(policy_name).exists?
|
|
560
|
+
|
|
561
|
+
policies.create(name: policy_name)
|
|
538
562
|
end
|
|
539
563
|
end
|
|
540
564
|
|
|
565
|
+
# Translates a key using the store's default locale with fallback to :en
|
|
566
|
+
def translate_with_store_locale_fallback(key)
|
|
567
|
+
locale = default_locale.presence&.to_sym || :en
|
|
568
|
+
I18n.t(key, locale: locale, default: I18n.t(key, locale: :en))
|
|
569
|
+
end
|
|
570
|
+
|
|
541
571
|
# code is slug, so we don't want to generate new slug when code changes
|
|
542
572
|
# we use friendlyId only for history feature
|
|
543
573
|
def should_generate_new_friendly_id?
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -245,7 +245,11 @@ module Spree
|
|
|
245
245
|
end
|
|
246
246
|
|
|
247
247
|
def options_text
|
|
248
|
-
@options_text ||=
|
|
248
|
+
@options_text ||= if option_values.loaded?
|
|
249
|
+
option_values.sort_by { |ov| ov.option_type.position }.map { |ov| "#{ov.option_type.presentation}: #{ov.presentation}" }.to_sentence(words_connector: ', ', two_words_connector: ', ')
|
|
250
|
+
else
|
|
251
|
+
option_values.includes(:option_type).joins(:option_type).order("#{Spree::OptionType.table_name}.position").map { |ov| "#{ov.option_type.presentation}: #{ov.presentation}" }.to_sentence(words_connector: ', ', two_words_connector: ', ')
|
|
252
|
+
end
|
|
249
253
|
end
|
|
250
254
|
|
|
251
255
|
# Default to master name
|
|
@@ -8,6 +8,7 @@ module Spree
|
|
|
8
8
|
delegate :option_values, to: :variant
|
|
9
9
|
|
|
10
10
|
def initialize(variant)
|
|
11
|
+
Spree::Deprecation.warn('Spree::Variants::OptionsPresenter is deprecated and will be removed in Spree 5.5. Please use Spree::Variant#options_text instead.')
|
|
11
12
|
@variant = variant
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -5,8 +5,13 @@ module Spree
|
|
|
5
5
|
|
|
6
6
|
def call(order:)
|
|
7
7
|
@messages = []
|
|
8
|
+
|
|
9
|
+
return success([order, @messages]) if order.item_count.zero? || order.line_items.none?
|
|
10
|
+
|
|
11
|
+
line_items = order.line_items.includes(variant: [:product, :stock_items, :stock_locations, { stock_items: :stock_location }])
|
|
12
|
+
|
|
8
13
|
ActiveRecord::Base.transaction do
|
|
9
|
-
line_items
|
|
14
|
+
line_items.each do |line_item|
|
|
10
15
|
cart_remove_line_item_service.call(order: order, line_item: line_item) if !valid_status?(line_item) || !stock_available?(line_item)
|
|
11
16
|
end
|
|
12
17
|
end
|
|
@@ -20,17 +25,6 @@ module Spree
|
|
|
20
25
|
|
|
21
26
|
private
|
|
22
27
|
|
|
23
|
-
def line_items(order)
|
|
24
|
-
if order.line_items.empty?
|
|
25
|
-
[]
|
|
26
|
-
elsif order.line_items.first.association_cached?(:variant) && order.line_items.first.variant.association_cached?(:product)
|
|
27
|
-
# Don't include associations if it is already included, because it breaks other includes
|
|
28
|
-
order.line_items
|
|
29
|
-
else
|
|
30
|
-
order.line_items.includes(variant: :product)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
28
|
def valid_status?(line_item)
|
|
35
29
|
product = line_item.product
|
|
36
30
|
if !product.active? || product.deleted? || product.discontinued? || line_item.variant.discontinued?
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
<div class="inner"
|
|
5
5
|
data-controller="address-form address-autocomplete"
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
<% cache address_form_countries_states_cache_key do %>
|
|
7
|
+
data-address-form-countries-value="<%= available_countries.to_json %>"
|
|
8
|
+
data-address-form-states-value="<%= available_states.to_json %>"
|
|
9
|
+
<% end %>
|
|
8
10
|
data-address-form-current-state-id-value="<%= address.state_id || address_form.object.state_id %>"
|
|
9
11
|
>
|
|
10
12
|
<div id="<%= "#{address_id}country" %>">
|
|
@@ -148,7 +148,7 @@ class MyAddToCartService < Spree::Cart::AddItem
|
|
|
148
148
|
def call(order:, variant:, quantity: nil, public_metadata: {}, private_metadata: {}, options: {})
|
|
149
149
|
ApplicationRecord.transaction do
|
|
150
150
|
run :add_to_line_item
|
|
151
|
-
run Spree
|
|
151
|
+
run Spree.cart_recalculate_service
|
|
152
152
|
run :update_external_system
|
|
153
153
|
end
|
|
154
154
|
end
|
|
@@ -363,7 +363,7 @@ module Spree
|
|
|
363
363
|
private
|
|
364
364
|
|
|
365
365
|
def serialized_collection
|
|
366
|
-
Spree
|
|
366
|
+
Spree.api.storefront_product_serializer.new(
|
|
367
367
|
collection,
|
|
368
368
|
include: resource_includes,
|
|
369
369
|
fields: sparse_fields
|
|
@@ -10,6 +10,7 @@ module Spree
|
|
|
10
10
|
|
|
11
11
|
class_option :lib_name, default: ''
|
|
12
12
|
class_option :database, default: ''
|
|
13
|
+
class_option :api, type: :boolean, default: false
|
|
13
14
|
|
|
14
15
|
def self.source_paths
|
|
15
16
|
paths = superclass.source_paths
|
|
@@ -21,34 +22,40 @@ module Spree
|
|
|
21
22
|
remove_directory_if_exists(dummy_path)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
PASSTHROUGH_OPTIONS = [
|
|
25
|
-
:skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip
|
|
26
|
-
]
|
|
27
|
-
|
|
28
25
|
def generate_test_dummy
|
|
29
|
-
# calling slice on a Thor::CoreExtensions::HashWithIndifferentAccess
|
|
30
|
-
# object has been known to return nil
|
|
31
|
-
opts = {}.merge(options).slice(*PASSTHROUGH_OPTIONS)
|
|
32
|
-
opts[:database] = 'sqlite3' if opts[:database].blank?
|
|
33
|
-
opts[:force] = true
|
|
34
|
-
opts[:skip_bundle] = true
|
|
35
|
-
opts[:skip_git] = true
|
|
36
|
-
opts[:skip_listen] = true
|
|
37
|
-
opts[:skip_rc] = true
|
|
38
|
-
opts[:skip_spring] = true
|
|
39
|
-
opts[:skip_test] = true
|
|
40
|
-
opts[:skip_bootsnap] = true
|
|
41
|
-
opts[:skip_docker] = true
|
|
42
|
-
opts[:skip_rubocop] = true
|
|
43
|
-
opts[:skip_brakeman] = true
|
|
44
|
-
opts[:skip_ci] = true
|
|
45
|
-
opts[:skip_kamal] = true
|
|
46
|
-
opts[:skip_devcontainer] = true
|
|
47
|
-
opts[:skip_solid] = true
|
|
48
|
-
|
|
49
26
|
puts 'Generating dummy Rails application...'
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
|
|
28
|
+
args = [File.expand_path(dummy_path, destination_root)]
|
|
29
|
+
|
|
30
|
+
# Database
|
|
31
|
+
args << "--database=#{options[:database].presence || 'sqlite3'}"
|
|
32
|
+
|
|
33
|
+
# Skip options
|
|
34
|
+
args << '--force'
|
|
35
|
+
args << '--skip-bundle'
|
|
36
|
+
args << '--skip-git'
|
|
37
|
+
args << '--skip-keeps'
|
|
38
|
+
args << '--skip-rc'
|
|
39
|
+
args << '--skip-spring'
|
|
40
|
+
args << '--skip-test'
|
|
41
|
+
args << '--skip-bootsnap'
|
|
42
|
+
args << '--skip-docker'
|
|
43
|
+
args << '--skip-rubocop'
|
|
44
|
+
args << '--skip-brakeman'
|
|
45
|
+
args << '--skip-ci'
|
|
46
|
+
args << '--skip-kamal'
|
|
47
|
+
args << '--skip-devcontainer'
|
|
48
|
+
args << '--skip-solid'
|
|
49
|
+
args << '--skip-thruster'
|
|
50
|
+
args << '--skip-bundler-audit'
|
|
51
|
+
args << '--skip-dev-gems'
|
|
52
|
+
args << '--skip-action-mailbox'
|
|
53
|
+
args << '--skip-jbuilder'
|
|
54
|
+
|
|
55
|
+
# API mode (implies skip-asset-pipeline, skip-javascript, skip-hotwire)
|
|
56
|
+
args << '--api' if options[:api]
|
|
57
|
+
|
|
58
|
+
Rails::Generators.invoke('app', args)
|
|
52
59
|
inject_yaml_permitted_classes
|
|
53
60
|
end
|
|
54
61
|
|
|
@@ -62,7 +69,7 @@ module Spree
|
|
|
62
69
|
template 'rails/routes.rb', "#{dummy_path}/config/routes.rb", force: true
|
|
63
70
|
template 'rails/test.rb', "#{dummy_path}/config/environments/test.rb", force: true
|
|
64
71
|
template 'initializers/devise.rb', "#{dummy_path}/config/initializers/devise.rb", force: true
|
|
65
|
-
template "app/assets/config/manifest.js", "#{dummy_path}/app/assets/config/manifest.js", force: true
|
|
72
|
+
template "app/assets/config/manifest.js", "#{dummy_path}/app/assets/config/manifest.js", force: true unless options[:api]
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
def test_dummy_inject_extension_requirements
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# Centralized configuration for Ransack searchable attributes, associations, and scopes.
|
|
3
|
+
#
|
|
4
|
+
# This class allows developers to extend Spree models with custom ransackable
|
|
5
|
+
# configurations without using decorators.
|
|
6
|
+
#
|
|
7
|
+
# @example Adding custom searchable fields
|
|
8
|
+
# Spree.ransack.add_attribute(Spree::Product, :vendor_id)
|
|
9
|
+
# Spree.ransack.add_scope(Spree::Product, :by_vendor)
|
|
10
|
+
# Spree.ransack.add_association(Spree::Product, :vendor)
|
|
11
|
+
#
|
|
12
|
+
class RansackConfiguration
|
|
13
|
+
def initialize
|
|
14
|
+
@custom_attributes = Hash.new { |h, k| h[k] = [] }
|
|
15
|
+
@custom_associations = Hash.new { |h, k| h[k] = [] }
|
|
16
|
+
@custom_scopes = Hash.new { |h, k| h[k] = [] }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Add a custom ransackable attribute to a model.
|
|
20
|
+
#
|
|
21
|
+
# @param model [Class] the model class to configure (e.g., Spree::Product)
|
|
22
|
+
# @param attribute [String, Symbol] the attribute to add
|
|
23
|
+
# @return [Array<String>] the updated list of custom attributes
|
|
24
|
+
def add_attribute(model, attribute)
|
|
25
|
+
@custom_attributes[model.name.to_sym] |= [attribute.to_s]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Add a custom ransackable association to a model.
|
|
29
|
+
#
|
|
30
|
+
# @param model [Class] the model class to configure (e.g., Spree::Product)
|
|
31
|
+
# @param association [String, Symbol] the association to add
|
|
32
|
+
# @return [Array<String>] the updated list of custom associations
|
|
33
|
+
def add_association(model, association)
|
|
34
|
+
@custom_associations[model.name.to_sym] |= [association.to_s]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Add a custom ransackable scope to a model.
|
|
38
|
+
#
|
|
39
|
+
# @param model [Class] the model class to configure (e.g., Spree::Product)
|
|
40
|
+
# @param scope [String, Symbol] the scope to add
|
|
41
|
+
# @return [Array<String>] the updated list of custom scopes
|
|
42
|
+
def add_scope(model, scope)
|
|
43
|
+
@custom_scopes[model.name.to_sym] |= [scope.to_s]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get custom ransackable attributes for a model.
|
|
47
|
+
#
|
|
48
|
+
# @param model [Class] the model class to query
|
|
49
|
+
# @return [Array<String>] the custom attributes
|
|
50
|
+
def custom_attributes_for(model)
|
|
51
|
+
@custom_attributes[model.name.to_sym]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get custom ransackable associations for a model.
|
|
55
|
+
#
|
|
56
|
+
# @param model [Class] the model class to query
|
|
57
|
+
# @return [Array<String>] the custom associations
|
|
58
|
+
def custom_associations_for(model)
|
|
59
|
+
@custom_associations[model.name.to_sym]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get custom ransackable scopes for a model.
|
|
63
|
+
#
|
|
64
|
+
# @param model [Class] the model class to query
|
|
65
|
+
# @return [Array<String>] the custom scopes
|
|
66
|
+
def custom_scopes_for(model)
|
|
67
|
+
@custom_scopes[model.name.to_sym]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Reset all custom configurations. Useful for testing.
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
def reset!
|
|
74
|
+
@custom_attributes.clear
|
|
75
|
+
@custom_associations.clear
|
|
76
|
+
@custom_scopes.clear
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/spree/core/version.rb
CHANGED
data/lib/spree/core.rb
CHANGED
|
@@ -374,6 +374,19 @@ module Spree
|
|
|
374
374
|
@permissions ||= PermissionConfiguration.new
|
|
375
375
|
end
|
|
376
376
|
|
|
377
|
+
# Ransack configuration accessor for managing custom ransackable attributes,
|
|
378
|
+
# associations, and scopes across Spree models.
|
|
379
|
+
#
|
|
380
|
+
# @example Adding custom searchable fields
|
|
381
|
+
# Spree.ransack.add_attribute(Spree::Product, :vendor_id)
|
|
382
|
+
# Spree.ransack.add_scope(Spree::Product, :by_vendor)
|
|
383
|
+
# Spree.ransack.add_association(Spree::Product, :vendor)
|
|
384
|
+
#
|
|
385
|
+
# @return [Spree::RansackConfiguration] the ransack configuration instance
|
|
386
|
+
def self.ransack
|
|
387
|
+
@ransack ||= RansackConfiguration.new
|
|
388
|
+
end
|
|
389
|
+
|
|
377
390
|
class << self
|
|
378
391
|
# Dynamic methods for core dependencies
|
|
379
392
|
#
|
|
@@ -451,3 +464,4 @@ require 'spree/core/preferences/runtime_configuration'
|
|
|
451
464
|
|
|
452
465
|
require 'spree/core/webhooks'
|
|
453
466
|
require 'spree/core/permission_configuration'
|
|
467
|
+
require 'spree/core/ransack_configuration'
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module DatabaseTypeUtilities
|
|
3
|
+
# Maximum value for a 4-byte signed integer (default database integer type)
|
|
4
|
+
INTEGER_MAX = (2**31) - 1
|
|
5
|
+
|
|
3
6
|
def self.maximum_value_for(data_type)
|
|
4
7
|
case data_type
|
|
5
8
|
when :integer
|
|
6
|
-
|
|
9
|
+
INTEGER_MAX
|
|
7
10
|
else
|
|
8
11
|
raise ArgumentError, 'Currently only :integer argument is acceptable'
|
|
9
12
|
end
|
|
@@ -19,12 +19,15 @@ namespace :common do
|
|
|
19
19
|
ENV['RAILS_ENV'] = 'test'
|
|
20
20
|
Rails.env = 'test'
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
api_only = ['spree/api', 'spree/core', 'spree/sample'].include?(ENV['LIB_NAME'])
|
|
23
|
+
skip_javascript = api_only || ENV['LIB_NAME'] == 'spree/emails'
|
|
23
24
|
|
|
24
25
|
dummy_app_args = [
|
|
25
26
|
"--lib_name=#{ENV['LIB_NAME']}"
|
|
26
27
|
]
|
|
27
|
-
if
|
|
28
|
+
if api_only
|
|
29
|
+
dummy_app_args << '--api'
|
|
30
|
+
elsif skip_javascript
|
|
28
31
|
dummy_app_args << '--skip_javascript'
|
|
29
32
|
end
|
|
30
33
|
Spree::DummyGenerator.start dummy_app_args
|
|
@@ -24,6 +24,9 @@ FactoryBot.define do
|
|
|
24
24
|
after(:create) do |order, evaluator|
|
|
25
25
|
create(:line_item, order: order, price: evaluator.line_items_price)
|
|
26
26
|
order.line_items.reload # to ensure order.line_items is accessible after
|
|
27
|
+
|
|
28
|
+
order.update_column(:item_count, order.line_items.count)
|
|
29
|
+
order.reload
|
|
27
30
|
end
|
|
28
31
|
end
|
|
29
32
|
|
|
@@ -13,8 +13,12 @@ RSpec.configure do |config|
|
|
|
13
13
|
|
|
14
14
|
config.before(:all) do
|
|
15
15
|
unless self.class.metadata[:without_global_store]
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
# Ensure locale is set to :en before creating store to avoid translation issues
|
|
17
|
+
# when previous tests left the locale in a different language
|
|
18
|
+
I18n.with_locale(:en) do
|
|
19
|
+
@default_country = Spree::Country.find_by(iso: 'US') || FactoryBot.create(:country_us)
|
|
20
|
+
@default_store = Spree::Store.find_by(default: true) || FactoryBot.create(:store, default: true, default_country: @default_country, default_currency: 'USD')
|
|
21
|
+
end
|
|
18
22
|
end
|
|
19
23
|
end
|
|
20
24
|
|
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.
|
|
4
|
+
version: 5.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean Schofield
|
|
@@ -34,7 +34,7 @@ dependencies:
|
|
|
34
34
|
version: '7.2'
|
|
35
35
|
- - "<"
|
|
36
36
|
- !ruby/object:Gem::Version
|
|
37
|
-
version: '8.
|
|
37
|
+
version: '8.2'
|
|
38
38
|
type: :runtime
|
|
39
39
|
prerelease: false
|
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -44,7 +44,7 @@ dependencies:
|
|
|
44
44
|
version: '7.2'
|
|
45
45
|
- - "<"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '8.
|
|
47
|
+
version: '8.2'
|
|
48
48
|
- !ruby/object:Gem::Dependency
|
|
49
49
|
name: activemerchant
|
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -1549,6 +1549,7 @@ files:
|
|
|
1549
1549
|
- lib/spree/core/query_filters/date.rb
|
|
1550
1550
|
- lib/spree/core/query_filters/number.rb
|
|
1551
1551
|
- lib/spree/core/query_filters/text.rb
|
|
1552
|
+
- lib/spree/core/ransack_configuration.rb
|
|
1552
1553
|
- lib/spree/core/routes.rb
|
|
1553
1554
|
- lib/spree/core/search/base.rb
|
|
1554
1555
|
- lib/spree/core/token_generator.rb
|
|
@@ -1703,9 +1704,9 @@ licenses:
|
|
|
1703
1704
|
- BSD-3-Clause
|
|
1704
1705
|
metadata:
|
|
1705
1706
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
1706
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.
|
|
1707
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.6
|
|
1707
1708
|
documentation_uri: https://docs.spreecommerce.org/
|
|
1708
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.2.
|
|
1709
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.2.6
|
|
1709
1710
|
rdoc_options: []
|
|
1710
1711
|
require_paths:
|
|
1711
1712
|
- lib
|