spree_core 5.6.0.rc4 → 5.6.0.rc5

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: 81bf3080a8d43c9bc6a6d76561c9fae751c9282a1e8b30dfb388ca6abe2511d7
4
- data.tar.gz: 423cf415d52c180297db3e26b581ea4078cfcf0e7816fc3084d12034b3d74a87
3
+ metadata.gz: 1b3d64f54fca96fb946e3ee9b49d80648677ce7f5c36d0d6bb10769bf8c98df9
4
+ data.tar.gz: 82a353e9cad247a2be8f909d569ab286f323a63fe04b1ef5676d66c72001fdf7
5
5
  SHA512:
6
- metadata.gz: 351598372ce3b7d938815929ac76d050a45679aad1a4734c07a5642f932b81e0e688e0903538e2446c30516545083d2c6d52963b82598650d6e998606aeb8341
7
- data.tar.gz: 258324d4c6e23afbe5702782daf6e19811af60136b4f93bbd99d27824830ee7211426fde12f1330175132b4c82c281cfc6295ca0d7cd6f2cc400cce5e3f7e729
6
+ metadata.gz: ee39b451ea0e40f7ea1b0811ae858b7a58580952c943b3356a642acf3b04dc5a461c2ae8864db916a14f1dcbe4b46428b924e852fdb21e271d071e3917388561
7
+ data.tar.gz: a90ca190815ef4e7ba9fec0cfe9bce1902b4c00eb35c1cd27c64e2edbe20980d69353826c612b4534cea402723a0cb5db8e9f2da4aea5023d5e51e05764c2175
@@ -29,7 +29,15 @@ module Spree
29
29
 
30
30
  attribute :active, :boolean, default: true
31
31
 
32
+ # acts_as_list only assigns bottom positions in before_create — too late
33
+ # for the presence validation below, so API creates without an explicit
34
+ # position would 422. Default to the end of the channel's list instead.
35
+ before_validation :set_position_to_end, on: :create, if: -> { position.blank? && channel.present? }
36
+
32
37
  validates :type, :channel, presence: true
38
+ # One instance of each rule kind per channel — a duplicate signal would
39
+ # either be a no-op or fight itself in the reducer walk.
40
+ validates :type, uniqueness: { scope: [:channel_id, *spree_base_uniqueness_scope] }
33
41
  validates :position, presence: true, numericality: { only_integer: true }
34
42
  validate :channel_belongs_to_store
35
43
 
@@ -43,6 +51,25 @@ module Spree
43
51
 
44
52
  validate :type_must_be_registered
45
53
 
54
+ # @return [String] localized display name for the rule kind, used by admin pickers
55
+ def self.human_name
56
+ Spree.t("order_routing_rule_types.#{api_type}.name", default: api_type.titleize)
57
+ end
58
+
59
+ # @return [String] localized description for the rule kind
60
+ def self.human_description
61
+ Spree.t("order_routing_rule_types.#{api_type}.description", default: '')
62
+ end
63
+
64
+ # Feeds the `description` field of `subclasses_with_preference_schema`
65
+ # (the `/types` discovery payload), which only reads `.description`.
66
+ def self.description
67
+ human_description
68
+ end
69
+
70
+ def human_name = self.class.human_name
71
+ def human_description = self.class.human_description
72
+
46
73
  # Subclasses override. Returns an Array<LocationRanking> — one per location,
47
74
  # with rank=nil to abstain.
48
75
  #
@@ -53,8 +80,24 @@ module Spree
53
80
  raise NotImplementedError, "#{self.class} must implement #rank(order, locations)"
54
81
  end
55
82
 
83
+ # Routes Spree::PreferenceSchema's subclass discovery
84
+ # (`subclasses_with_preference_schema`, `find_by_api_type`) to the
85
+ # order-routing rule registry.
86
+ module ClassMethods
87
+ private
88
+
89
+ def registered_subclasses
90
+ Spree.order_routing.rules
91
+ end
92
+ end
93
+ extend ClassMethods
94
+
56
95
  private
57
96
 
97
+ def set_position_to_end
98
+ self.position = bottom_position_in_list + 1
99
+ end
100
+
58
101
  # The +type+ presence validation already covers blank; here we only reject
59
102
  # a present-but-unregistered STI type so arbitrary class names can't be
60
103
  # persisted via the +type+ column.
@@ -45,8 +45,9 @@ module Spree
45
45
  can :manage, Spree::ReimbursementType
46
46
  can :manage, Spree::ReturnReason
47
47
 
48
- # Channels
48
+ # Channels + per-channel order routing rules
49
49
  can :manage, Spree::Channel
50
+ can :manage, Spree::OrderRoutingRule
50
51
 
51
52
  # Restrictions on immutable types
52
53
  cannot [:edit, :update], Spree::RefundReason, mutable: false
@@ -1190,6 +1190,16 @@ en:
1190
1190
  strategies:
1191
1191
  legacy: Legacy
1192
1192
  rules: Rules (ordered)
1193
+ order_routing_rule_types:
1194
+ default_location:
1195
+ description: Prefer the stock location marked as default
1196
+ name: Default location
1197
+ minimize_splits:
1198
+ description: Prefer the location that can fulfill the most items on its own, avoiding split shipments
1199
+ name: Minimize splits
1200
+ preferred_location:
1201
+ description: Fulfill from the location pinned on the order, when one is set
1202
+ name: Preferred location
1193
1203
  order_summary: Order Summary
1194
1204
  order_total: Order Total
1195
1205
  orders: Orders
@@ -0,0 +1,7 @@
1
+ class AddUniqueTypeIndexToSpreeOrderRoutingRules < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_index :spree_order_routing_rules, [:channel_id, :type],
4
+ unique: true,
5
+ name: 'idx_order_routing_rules_channel_type'
6
+ end
7
+ end
@@ -20,7 +20,19 @@ end
20
20
  wholesale_group.add_customers([buyer.id])
21
21
 
22
22
  price_list = store.price_lists.find_or_create_by!(name: 'Wholesale') do |list|
23
- list.description = 'Wholesale pricing for approved B2B buyers (40% off retail)'
23
+ list.description = 'Wholesale pricing for approved B2B buyers (40% off retail) on orders of 10+ per item'
24
+ end
25
+
26
+ # Case-pack minimum: the trade price applies only when a buyer orders at least
27
+ # WHOLESALE_MIN_QUANTITY of a single item (VolumeRule matches per line item, not
28
+ # per order). Below the threshold the buyer falls back to the retail price — no
29
+ # hard checkout block. This is what makes the demo feel like real wholesale.
30
+ wholesale_min_quantity = 10
31
+
32
+ unless price_list.price_rules.any? { |rule| rule.is_a?(Spree::PriceRules::VolumeRule) }
33
+ volume_rule = Spree::PriceRules::VolumeRule.new(price_list: price_list)
34
+ volume_rule.preferred_min_quantity = wholesale_min_quantity
35
+ volume_rule.save!
24
36
  end
25
37
 
26
38
  unless price_list.price_rules.any? { |rule| rule.is_a?(Spree::PriceRules::CustomerGroupRule) }
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.6.0.rc4'.freeze
2
+ VERSION = '5.6.0.rc5'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -0,0 +1,14 @@
1
+ FactoryBot.define do
2
+ # STI base can't be persisted (type must be a registered subclass), so the
3
+ # factory defaults to DefaultLocation. Channels seed all built-in kinds on
4
+ # create and `type` is unique per channel, so the factory clears the seeded
5
+ # list — specs get a pristine channel whose only rule is the one built here.
6
+ # Position defaults to the end of the channel's list via the model callback.
7
+ factory :order_routing_rule, class: Spree::OrderRouting::Rules::DefaultLocation do
8
+ channel { association(:channel).tap { |c| c.order_routing_rules.delete_all } }
9
+ store { channel.store }
10
+
11
+ factory :preferred_location_routing_rule, class: Spree::OrderRouting::Rules::PreferredLocation
12
+ factory :minimize_splits_routing_rule, class: Spree::OrderRouting::Rules::MinimizeSplits
13
+ end
14
+ end
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.6.0.rc4
4
+ version: 5.6.0.rc5
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: 2026-07-20 00:00:00.000000000 Z
13
+ date: 2026-07-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -1604,6 +1604,7 @@ files:
1604
1604
  - db/migrate/20260710000001_add_import_id_status_index_to_spree_import_rows.rb
1605
1605
  - db/migrate/20260711000001_add_preferences_to_spree_exports.rb
1606
1606
  - db/migrate/20260719000001_add_channel_id_to_spree_api_keys.rb
1607
+ - db/migrate/20260721000001_add_unique_type_index_to_spree_order_routing_rules.rb
1607
1608
  - db/sample_data/channels.rb
1608
1609
  - db/sample_data/customers.csv
1609
1610
  - db/sample_data/markets.rb
@@ -1748,6 +1749,7 @@ files:
1748
1749
  - lib/spree/testing_support/factories/options_factory.rb
1749
1750
  - lib/spree/testing_support/factories/order_factory.rb
1750
1751
  - lib/spree/testing_support/factories/order_promotion_factory.rb
1752
+ - lib/spree/testing_support/factories/order_routing_rule_factory.rb
1751
1753
  - lib/spree/testing_support/factories/payment_capture_event_factory.rb
1752
1754
  - lib/spree/testing_support/factories/payment_factory.rb
1753
1755
  - lib/spree/testing_support/factories/payment_method_factory.rb
@@ -1865,9 +1867,9 @@ licenses:
1865
1867
  - BSD-3-Clause
1866
1868
  metadata:
1867
1869
  bug_tracker_uri: https://github.com/spree/spree/issues
1868
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc4
1870
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc5
1869
1871
  documentation_uri: https://docs.spreecommerce.org/
1870
- source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc4
1872
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc5
1871
1873
  post_install_message:
1872
1874
  rdoc_options: []
1873
1875
  require_paths: