solidus_core 4.5.1 → 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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/jobs/spree/base_job.rb +12 -0
  4. data/app/jobs/spree/state_change_tracking_job.rb +32 -0
  5. data/app/models/concerns/spree/state_change_tracking.rb +26 -0
  6. data/app/models/concerns/spree/user_address_book.rb +24 -6
  7. data/app/models/concerns/spree/user_methods.rb +52 -14
  8. data/app/models/concerns/spree/user_reporting.rb +1 -1
  9. data/app/models/spree/address.rb +8 -2
  10. data/app/models/spree/adjustment_reason.rb +1 -1
  11. data/app/models/spree/calculator.rb +0 -2
  12. data/app/models/spree/carton.rb +7 -0
  13. data/app/models/spree/classification.rb +3 -3
  14. data/app/models/spree/core/state_machines/order/class_methods.rb +0 -16
  15. data/app/models/spree/core/state_machines/order.rb +1 -0
  16. data/app/models/spree/core/state_machines/payment.rb +1 -8
  17. data/app/models/spree/core/state_machines/shipment.rb +1 -8
  18. data/app/models/spree/country.rb +16 -5
  19. data/app/models/spree/customer_return.rb +1 -2
  20. data/app/models/spree/fulfilment_changer.rb +38 -11
  21. data/app/models/spree/inventory_unit.rb +1 -1
  22. data/app/models/spree/line_item.rb +3 -2
  23. data/app/models/spree/order.rb +5 -7
  24. data/app/models/spree/order_cancellations.rb +17 -20
  25. data/app/models/spree/order_shipping.rb +2 -9
  26. data/app/models/spree/order_taxation.rb +3 -2
  27. data/app/models/spree/order_updater.rb +11 -7
  28. data/app/models/spree/payment_method.rb +0 -1
  29. data/app/models/spree/product.rb +6 -5
  30. data/app/models/spree/product_option_type.rb +2 -2
  31. data/app/models/spree/product_property.rb +2 -2
  32. data/app/models/spree/role_user.rb +3 -3
  33. data/app/models/spree/shipment.rb +26 -12
  34. data/app/models/spree/shipping_category.rb +2 -2
  35. data/app/models/spree/shipping_method_category.rb +2 -2
  36. data/app/models/spree/state.rb +7 -4
  37. data/app/models/spree/stock/quantifier.rb +33 -9
  38. data/app/models/spree/stock_item.rb +1 -1
  39. data/app/models/spree/stock_location.rb +3 -2
  40. data/app/models/spree/store.rb +6 -0
  41. data/app/models/spree/store_credit.rb +3 -3
  42. data/app/models/spree/store_credit_event.rb +1 -1
  43. data/app/models/spree/tax_category.rb +1 -1
  44. data/app/models/spree/taxon.rb +0 -3
  45. data/app/models/spree/taxonomy.rb +2 -2
  46. data/app/models/spree/user_address.rb +4 -4
  47. data/app/models/spree/variant.rb +1 -1
  48. data/app/models/spree/variant_property_rule.rb +1 -1
  49. data/app/models/spree/variant_property_rule_condition.rb +1 -1
  50. data/app/models/spree/wallet_payment_source.rb +2 -2
  51. data/app/subscribers/spree/carton_shipped_mailer_subscriber.rb +30 -0
  52. data/app/subscribers/spree/order_cancel_mailer_subscriber.rb +21 -0
  53. data/app/subscribers/spree/order_confirmation_mailer_subscriber.rb +23 -0
  54. data/app/subscribers/spree/order_inventory_cancellation_mailer_subscriber.rb +27 -0
  55. data/app/subscribers/spree/order_mailer_subscriber.rb +8 -26
  56. data/app/subscribers/spree/reimbursement_mailer_subscriber.rb +20 -0
  57. data/config/locales/en.yml +9 -0
  58. data/db/migrate/20160101010000_solidus_one_four.rb +92 -0
  59. data/db/migrate/20180416083007_add_apply_to_all_to_variant_property_rule.rb +1 -1
  60. data/db/migrate/20250129061658_add_metadata_to_spree_resources.rb +1 -1
  61. data/db/migrate/20250214094207_add_reverse_charge_status_to_store.rb +8 -0
  62. data/db/migrate/20250225051308_add_vat_id_email_and_reverse_charge_status_to_addresses.rb +10 -0
  63. data/db/migrate/20250508145917_add_email_to_stock_locations.rb +5 -0
  64. data/db/migrate/20250530102541_add_addressbook_foreign_key.rb +29 -0
  65. data/db/migrate/20250604072105_add_fk_products_variant_property_rules.rb +30 -0
  66. data/db/migrate/20250604072555_add_fk_to_product_properties.rb +52 -0
  67. data/db/migrate/20250604072948_add_fk_to_product_option_types.rb +52 -0
  68. data/db/migrate/20250604073219_add_fk_to_classifications.rb +52 -0
  69. data/db/migrate/20250605105424_add_shipping_category_foreign_keys.rb +73 -0
  70. data/db/migrate/20250626112117_add_foreign_key_to_spree_role_users.rb +30 -0
  71. data/db/migrate/20250628094037_change_countries_iso_to_unique.rb +9 -0
  72. data/db/migrate/20250708120317_add_adjustment_reason_foreign_keys.rb +30 -0
  73. data/db/migrate/20250709073151_add_country_foreign_keys.rb +75 -0
  74. data/db/migrate/20250709084513_add_state_foreign_keys.rb +30 -0
  75. data/db/migrate/20250726220709_add_fk_to_customer_return.rb +30 -0
  76. data/lib/generators/solidus/install/app_templates/frontend/starter.rb +1 -1
  77. data/lib/generators/spree/dummy/dummy_generator.rb +2 -1
  78. data/lib/generators/spree/dummy/templates/rails/{manifest.js → manifest.js.tt} +2 -0
  79. data/lib/spree/app_configuration.rb +8 -0
  80. data/lib/spree/core/engine.rb +7 -2
  81. data/lib/spree/core/environment.rb +1 -0
  82. data/lib/spree/core/version.rb +6 -10
  83. data/lib/spree/core.rb +2 -0
  84. data/lib/spree/permitted_attributes.rb +2 -1
  85. data/lib/spree/preferences/preferable.rb +1 -5
  86. data/lib/spree/testing_support/capybara_driver.rb +9 -0
  87. data/lib/spree/testing_support/common_rake.rb +6 -3
  88. data/lib/spree/testing_support/dummy_app/migrations.rb +2 -2
  89. data/lib/spree/testing_support/dummy_app/rake_tasks.rb +0 -6
  90. data/lib/spree/testing_support/dummy_app.rb +7 -0
  91. data/lib/spree/testing_support/factories/zone_factory.rb +4 -0
  92. data/lib/spree/testing_support/job_helpers.rb +6 -15
  93. data/lib/spree/testing_support/translations.rb +2 -2
  94. data/lib/spree/zero.rb +3 -0
  95. data/solidus_core.gemspec +6 -2
  96. metadata +56 -5
  97. /data/{lib → app/models/concerns}/spree/preferences/persistable.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 995355f29d96abe41d7beb6b28cbe8bd8198d8c4ebee544dedc223f15b856548
4
- data.tar.gz: 07b683686244717fc86fe1a83777e29845365dbed70c2117d1aacddf8c729717
3
+ metadata.gz: f616e30c09a8f81468fd2f3d646b4ba5624ad26af29aae44985622adf3db57b8
4
+ data.tar.gz: 982dc95e2dc4518445e5dd6d133354cd283663028902c55ad4850af1e18ea4cd
5
5
  SHA512:
6
- metadata.gz: '0963f51448492bca5190edb6641ea96206670f8ce17bea75e67c429838b5b98c041a936d64f57b994ca731df036995ef402eb24e723840a3b00efb109bf4e5c0'
7
- data.tar.gz: 0fc6248571ff6027d618e4840d9c154bba8674fdbfd96805a58fbbc051b7c36d9c31d6e1d20b042bce38b8c5fc2b48d8b3201acb2ec264be2466c29704612c02
6
+ metadata.gz: 9a4cd294113878e692b12af0214b7200522d4ce583a2d515e7d848c342fe787d64dd78c9b9a9b6de02fdf7b2c47a5bab7e3a5054cb0d76935851c7935d3b79d3
7
+ data.tar.gz: aa69ad3e6b7616a701675aca113da85e7b9472f6f95ffe2bc42ea1fa1115ceffdcb1c710482e8d4a509616d1a6c5c0036a157dd92405f6937eea261fbdc38c77
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  Summary
2
2
  ------
3
- Solidus Core provides the essential ecommerce data models upon which the
3
+ Solidus Core provides the essential e-commerce data models upon which the
4
4
  Solidus system depends.
5
5
 
6
6
  Core models
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Base class for all Solidus background jobs
5
+ class BaseJob < ActiveJob::Base # rubocop:disable Rails/ApplicationJob
6
+ # Automatically retry jobs that encountered a deadlock
7
+ retry_on ActiveRecord::Deadlocked
8
+
9
+ # Most jobs are safe to ignore if the underlying records are no longer available
10
+ discard_on ActiveJob::DeserializationError
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Background job to track state changes asynchronously
5
+ # This avoids performance impact during checkout and prevents
6
+ # callback-related issues with recent versions of the state_machines gem.
7
+ class StateChangeTrackingJob < BaseJob
8
+ # @param stateful [GlobalId] The stateful object to track changes for
9
+ # @param previous_state [String] The previous state of the order
10
+ # @param current_state [String] The current state of the order
11
+ # @param transition_timestamp [Time] When the state transition occurred
12
+ # @param name [String] The element name of the state transition being
13
+ # tracked. It defaults to the `stateful` model element name.
14
+ def perform(
15
+ stateful,
16
+ previous_state,
17
+ current_state,
18
+ transition_timestamp,
19
+ name = stateful.class.model_name.element
20
+ )
21
+ Spree::StateChange.create!(
22
+ name: name,
23
+ stateful: stateful,
24
+ previous_state: previous_state,
25
+ next_state: current_state,
26
+ created_at: transition_timestamp,
27
+ updated_at: transition_timestamp,
28
+ user_id: stateful.try(:user_id) || stateful.try(:order)&.user_id
29
+ )
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module StateChangeTracking
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_update :enqueue_state_change_tracking, if: :saved_change_to_state?
9
+ end
10
+
11
+ private
12
+
13
+ # Enqueue background job to track state changes asynchronously
14
+ def enqueue_state_change_tracking
15
+ previous_state, current_state = saved_changes['state']
16
+
17
+ # Enqueue the job to track this state change
18
+ StateChangeTrackingJob.perform_later(
19
+ self,
20
+ previous_state,
21
+ current_state,
22
+ Time.current
23
+ )
24
+ end
25
+ end
26
+ end
@@ -5,7 +5,7 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- has_many :user_addresses, foreign_key: "user_id", class_name: "Spree::UserAddress" do
8
+ has_many :user_addresses, foreign_key: "user_id", class_name: "Spree::UserAddress", inverse_of: :user, dependent: :destroy do
9
9
  def find_first_by_address_values(address_attrs)
10
10
  detect { |ua| ua.address == Spree::Address.new(address_attrs) }
11
11
  end
@@ -32,11 +32,29 @@ module Spree
32
32
 
33
33
  has_many :addresses, through: :user_addresses
34
34
 
35
- has_one :default_user_bill_address, ->{ default_billing }, class_name: 'Spree::UserAddress', foreign_key: 'user_id'
36
- has_one :bill_address, through: :default_user_bill_address, source: :address
37
-
38
- has_one :default_user_ship_address, ->{ default_shipping }, class_name: 'Spree::UserAddress', foreign_key: 'user_id'
39
- has_one :ship_address, through: :default_user_ship_address, source: :address
35
+ has_one :default_user_bill_address,
36
+ ->{ default_billing },
37
+ class_name: 'Spree::UserAddress',
38
+ foreign_key: 'user_id',
39
+ inverse_of: false,
40
+ dependent: false
41
+ has_one :bill_address,
42
+ through: :default_user_bill_address,
43
+ source: :address,
44
+ inverse_of: false,
45
+ dependent: false
46
+
47
+ has_one :default_user_ship_address,
48
+ ->{ default_shipping },
49
+ class_name: 'Spree::UserAddress',
50
+ foreign_key: 'user_id',
51
+ inverse_of: false,
52
+ dependent: false
53
+ has_one :ship_address,
54
+ through: :default_user_ship_address,
55
+ source: :address,
56
+ inverse_of: false,
57
+ dependent: false
40
58
 
41
59
  accepts_nested_attributes_for :ship_address
42
60
  accepts_nested_attributes_for :bill_address
@@ -11,20 +11,58 @@ module Spree
11
11
  included do
12
12
  extend Spree::DisplayMoney
13
13
 
14
- has_many :role_users, foreign_key: "user_id", class_name: "Spree::RoleUser", dependent: :destroy
15
- has_many :spree_roles, through: :role_users, source: :role, class_name: "Spree::Role"
16
-
17
- has_many :user_stock_locations, foreign_key: "user_id", class_name: "Spree::UserStockLocation"
18
- has_many :stock_locations, through: :user_stock_locations
19
-
20
- has_many :spree_orders, foreign_key: "user_id", class_name: "Spree::Order"
21
- has_many :orders, foreign_key: "user_id", class_name: "Spree::Order"
22
-
23
- has_many :store_credits, -> { includes(:credit_type) }, foreign_key: "user_id", class_name: "Spree::StoreCredit"
24
- has_many :store_credit_events, through: :store_credits
25
-
26
- has_many :credit_cards, class_name: "Spree::CreditCard", foreign_key: :user_id
27
- has_many :wallet_payment_sources, foreign_key: 'user_id', class_name: 'Spree::WalletPaymentSource', inverse_of: :user
14
+ has_many :role_users,
15
+ foreign_key: "user_id",
16
+ class_name: "Spree::RoleUser",
17
+ dependent: :destroy,
18
+ inverse_of: :user
19
+ has_many :spree_roles,
20
+ through: :role_users,
21
+ source: :role,
22
+ class_name: "Spree::Role",
23
+ inverse_of: :users
24
+
25
+ has_many :user_stock_locations,
26
+ foreign_key: "user_id",
27
+ class_name: "Spree::UserStockLocation",
28
+ inverse_of: :user,
29
+ dependent: :destroy
30
+ has_many :stock_locations,
31
+ through: :user_stock_locations,
32
+ inverse_of: :users
33
+
34
+ has_many :spree_orders,
35
+ foreign_key: "user_id",
36
+ class_name: "Spree::Order",
37
+ inverse_of: :user,
38
+ dependent: false
39
+ has_many :orders,
40
+ foreign_key: "user_id",
41
+ class_name: "Spree::Order",
42
+ inverse_of: :user,
43
+ dependent: false
44
+
45
+ has_many :store_credits,
46
+ -> { includes(:credit_type) },
47
+ foreign_key: "user_id",
48
+ class_name: "Spree::StoreCredit",
49
+ dependent: :nullify,
50
+ inverse_of: :user
51
+ has_many :store_credit_events,
52
+ through: :store_credits,
53
+ inverse_of: false
54
+
55
+ has_many :credit_cards,
56
+ class_name: "Spree::CreditCard",
57
+ foreign_key: :user_id,
58
+ dependent: :nullify,
59
+ inverse_of: :user
60
+
61
+ has_many :wallet_payment_sources,
62
+ foreign_key: 'user_id',
63
+ class_name: 'Spree::WalletPaymentSource',
64
+ inverse_of: :user,
65
+ dependent: :destroy
28
66
 
29
67
  after_create :auto_generate_spree_api_key
30
68
  before_destroy :check_for_deletion
@@ -17,7 +17,7 @@ module Spree
17
17
  if order_count.to_i > 0
18
18
  lifetime_value / order_count
19
19
  else
20
- BigDecimal("0.00")
20
+ Spree::ZERO
21
21
  end
22
22
  end
23
23
  end
@@ -11,10 +11,10 @@ module Spree
11
11
  mattr_accessor :state_validator_class
12
12
  self.state_validator_class = Spree::Address::StateValidator
13
13
 
14
- belongs_to :country, class_name: "Spree::Country", optional: true
14
+ belongs_to :country, class_name: "Spree::Country"
15
15
  belongs_to :state, class_name: "Spree::State", optional: true
16
16
 
17
- validates :address1, :city, :country_id, :name, presence: true
17
+ validates :address1, :city, :name, presence: true
18
18
  validates :zipcode, presence: true, if: :require_zipcode?
19
19
  validates :phone, presence: true, if: :require_phone?
20
20
 
@@ -28,6 +28,12 @@ module Spree
28
28
 
29
29
  self.allowed_ransackable_attributes = %w[name]
30
30
 
31
+ enum :reverse_charge_status, {
32
+ disabled: 0,
33
+ enabled: 1,
34
+ not_validated: 2
35
+ }, prefix: true
36
+
31
37
  unless ActiveRecord::Relation.method_defined? :with_values # Rails 7.1+
32
38
  scope :with_values, ->(attributes) do
33
39
  where(value_attributes(attributes))
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Spree
4
4
  class AdjustmentReason < Spree::Base
5
- has_many :adjustments, inverse_of: :adjustment_reason
5
+ has_many :adjustments, inverse_of: :adjustment_reason, dependent: :restrict_with_error
6
6
 
7
7
  validates :name, presence: true, uniqueness: { case_sensitive: false, allow_blank: true }
8
8
  validates :code, presence: true, uniqueness: { case_sensitive: false, allow_blank: true }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spree/preferences/persistable'
4
-
5
3
  module Spree
6
4
  class Calculator < Spree::Base
7
5
  include Spree::Preferences::Persistable
@@ -15,6 +15,13 @@ class Spree::Carton < Spree::Base
15
15
  validates :inventory_units, presence: true
16
16
  validates :shipped_at, presence: true
17
17
 
18
+ # This accessor is used by the `Spree::CartonShippedMailerSubscriber`
19
+ # on carton creation to either send or suppress the associated
20
+ # carton shipped email.
21
+ #
22
+ # @return [boolean, NilClass] Whether the email should be suppressed.
23
+ attr_accessor :suppress_email
24
+
18
25
  make_permalink field: :number, length: 11, prefix: 'C'
19
26
 
20
27
  scope :trackable, -> { where("tracking IS NOT NULL AND tracking != ''") }
@@ -4,10 +4,10 @@ module Spree
4
4
  class Classification < Spree::Base
5
5
  self.table_name = 'spree_products_taxons'
6
6
  acts_as_list scope: :taxon
7
- belongs_to :product, class_name: "Spree::Product", inverse_of: :classifications, touch: true, optional: true
8
- belongs_to :taxon, class_name: "Spree::Taxon", inverse_of: :classifications, touch: true, optional: true
7
+ belongs_to :product, class_name: "Spree::Product", inverse_of: :classifications, touch: true
8
+ belongs_to :taxon, class_name: "Spree::Taxon", inverse_of: :classifications, touch: true
9
9
 
10
10
  # For https://github.com/spree/spree/issues/3494
11
- validates_uniqueness_of :taxon_id, scope: :product_id, message: :already_linked
11
+ validates :taxon_id, uniqueness: { scope: :product_id, message: :already_linked }
12
12
  end
13
13
  end
@@ -38,22 +38,6 @@ module Spree
38
38
  state_machine :state, initial: :cart, use_transactions: false do
39
39
  klass.next_event_transitions.each { |state| transition(state.merge(on: :next)) }
40
40
 
41
- # Persist the state on the order
42
- after_transition do |order, transition|
43
- # Hard to say if this is really necessary, it was introduced in this commit:
44
- # https://github.com/mamhoff/solidus/commit/fa1d66c42e4c04ee7cd1c20d87e4cdb74a226d3d
45
- # But it seems to be harmless, so we'll keep it for now.
46
- order.state = order.state # rubocop:disable Lint/SelfAssignment
47
-
48
- order.state_changes.create(
49
- previous_state: transition.from,
50
- next_state: transition.to,
51
- name: 'order',
52
- user_id: order.user_id
53
- )
54
- order.save
55
- end
56
-
57
41
  event :cancel do
58
42
  transition to: :canceled, if: :allow_cancel?, from: :complete
59
43
  end
@@ -6,6 +6,7 @@ module Spree
6
6
  module Order
7
7
  def self.included(klass)
8
8
  klass.extend ClassMethods
9
+ klass.include StateChangeTracking
9
10
  end
10
11
 
11
12
  def checkout_steps
@@ -15,6 +15,7 @@ module Spree
15
15
  #
16
16
  module Payment
17
17
  extend ActiveSupport::Concern
18
+ include StateChangeTracking
18
19
 
19
20
  included do
20
21
  state_machine initial: :checkout do
@@ -45,14 +46,6 @@ module Spree
45
46
  event :invalidate do
46
47
  transition from: [:checkout], to: :invalid
47
48
  end
48
-
49
- after_transition do |payment, transition|
50
- payment.state_changes.create!(
51
- previous_state: transition.from,
52
- next_state: transition.to,
53
- name: 'payment'
54
- )
55
- end
56
49
  end
57
50
  end
58
51
  end
@@ -15,6 +15,7 @@ module Spree
15
15
  #
16
16
  module Shipment
17
17
  extend ActiveSupport::Concern
18
+ include StateChangeTracking
18
19
 
19
20
  included do
20
21
  state_machine initial: :pending, use_transactions: false do
@@ -42,14 +43,6 @@ module Spree
42
43
  transition from: :canceled, to: :pending
43
44
  end
44
45
  after_transition from: :canceled, to: [:pending, :ready, :shipped], do: :after_resume
45
-
46
- after_transition do |shipment, transition|
47
- shipment.state_changes.create!(
48
- previous_state: transition.from,
49
- next_state: transition.to,
50
- name: 'shipment'
51
- )
52
- end
53
46
  end
54
47
  end
55
48
  end
@@ -2,11 +2,22 @@
2
2
 
3
3
  module Spree
4
4
  class Country < Spree::Base
5
- has_many :states, -> { order(:name) }, dependent: :destroy
6
- has_many :addresses, dependent: :nullify
7
- has_many :prices, class_name: "Spree::Price", foreign_key: "country_iso", primary_key: "iso"
8
-
9
- validates :name, :iso_name, presence: true
5
+ has_many :states,
6
+ -> { order(:name) },
7
+ dependent: :destroy,
8
+ inverse_of: :country
9
+ has_many :addresses,
10
+ dependent: :restrict_with_error,
11
+ inverse_of: :country
12
+ has_many :prices,
13
+ class_name: "Spree::Price",
14
+ foreign_key: "country_iso",
15
+ primary_key: "iso",
16
+ dependent: :restrict_with_error,
17
+ inverse_of: :country
18
+
19
+ validates :name, :iso_name, :iso, presence: true
20
+ validates :iso, uniqueness: true
10
21
 
11
22
  self.allowed_ransackable_attributes = %w[name]
12
23
 
@@ -4,7 +4,7 @@ module Spree
4
4
  class CustomerReturn < Spree::Base
5
5
  include Metadata
6
6
 
7
- belongs_to :stock_location, optional: true
7
+ belongs_to :stock_location
8
8
 
9
9
  has_many :return_items, inverse_of: :customer_return
10
10
  has_many :return_authorizations, through: :return_items
@@ -14,7 +14,6 @@ module Spree
14
14
  before_create :generate_number
15
15
 
16
16
  validates :return_items, presence: true
17
- validates :stock_location, presence: true
18
17
  validate :return_items_belong_to_same_order
19
18
 
20
19
  accepts_nested_attributes_for :return_items
@@ -86,15 +86,21 @@ module Spree
86
86
  # we can take from the desired location, we could end up with some items being backordered.
87
87
  def run_tracking_inventory
88
88
  # Retrieve how many on hand items we can take from desired stock location
89
- available_quantity = [desired_shipment.stock_location.count_on_hand(variant), default_on_hand_quantity].max
90
-
89
+ available_quantity = get_available_quantity
91
90
  new_on_hand_quantity = [available_quantity, quantity].min
92
- unstock_quantity = desired_shipment.stock_location.backorderable?(variant) ? quantity : new_on_hand_quantity
91
+ backordered_quantity = get_backordered_quantity(available_quantity, new_on_hand_quantity)
92
+
93
+ # Determine how many backordered and on_hand items we'll need to move. We
94
+ # don't want to move more than what's being asked. And we can't move a
95
+ # negative amount, which is why we need to perform our min/max logic here.
96
+ backordered_quantity_to_move = [backordered_quantity, quantity].min
97
+ on_hand_quantity_to_move = [quantity - backordered_quantity_to_move, 0].max
93
98
 
94
99
  ActiveRecord::Base.transaction do
95
100
  if handle_stock_counts?
96
101
  # We only run this query if we need it.
97
102
  current_on_hand_quantity = [current_shipment.inventory_units.pre_shipment.size, quantity].min
103
+ unstock_quantity = desired_shipment.stock_location.backorderable?(variant) ? quantity : new_on_hand_quantity
98
104
 
99
105
  # Restock things we will not fulfil from the current shipment anymore
100
106
  current_stock_location.restock(variant, current_on_hand_quantity, current_shipment)
@@ -105,19 +111,21 @@ module Spree
105
111
  # These two statements are the heart of this class. We change the number
106
112
  # of inventory units requested from one shipment to the other.
107
113
  # We order by state, because `'backordered' < 'on_hand'`.
114
+ # We start to move the new actual backordered quantity, so the remaining
115
+ # quantity can be set to on_hand state.
108
116
  current_shipment.
109
117
  inventory_units.
110
118
  where(variant:).
111
119
  order(state: :asc).
112
- limit(new_on_hand_quantity).
113
- update_all(shipment_id: desired_shipment.id, state: :on_hand)
120
+ limit(backordered_quantity_to_move).
121
+ update_all(shipment_id: desired_shipment.id, state: :backordered)
114
122
 
115
123
  current_shipment.
116
124
  inventory_units.
117
125
  where(variant:).
118
126
  order(state: :asc).
119
- limit(quantity - new_on_hand_quantity).
120
- update_all(shipment_id: desired_shipment.id, state: :backordered)
127
+ limit(on_hand_quantity_to_move).
128
+ update_all(shipment_id: desired_shipment.id, state: :on_hand)
121
129
  end
122
130
  end
123
131
 
@@ -141,11 +149,22 @@ module Spree
141
149
  current_shipment.order.completed? && current_stock_location != desired_stock_location
142
150
  end
143
151
 
144
- def default_on_hand_quantity
152
+ def get_available_quantity
145
153
  if current_stock_location != desired_stock_location
146
- 0
154
+ desired_location_quantifier.positive_stock
147
155
  else
148
- current_shipment.inventory_units.where(variant:).on_hand.count
156
+ sl_availability = current_location_quantifier.positive_stock
157
+ shipment_availability = current_shipment.inventory_units.where(variant: variant).on_hand.count
158
+ sl_availability + shipment_availability
159
+ end
160
+ end
161
+
162
+ def get_backordered_quantity(available_quantity, new_on_hand_quantity)
163
+ if current_stock_location != desired_stock_location
164
+ quantity - new_on_hand_quantity
165
+ else
166
+ shipment_quantity = current_shipment.inventory_units.where(variant: variant).size
167
+ shipment_quantity - available_quantity
149
168
  end
150
169
  end
151
170
 
@@ -156,11 +175,19 @@ module Spree
156
175
  end
157
176
 
158
177
  def enough_stock_at_desired_location
159
- unless Spree::Stock::Quantifier.new(variant, desired_stock_location).can_supply?(quantity)
178
+ unless desired_location_quantifier.can_supply?(quantity)
160
179
  errors.add(:desired_shipment, :not_enough_stock_at_desired_location)
161
180
  end
162
181
  end
163
182
 
183
+ def desired_location_quantifier
184
+ @desired_location_quantifier ||= Spree::Stock::Quantifier.new(variant, desired_stock_location)
185
+ end
186
+
187
+ def current_location_quantifier
188
+ @current_location_quantifier ||= Spree::Stock::Quantifier.new(variant, current_stock_location)
189
+ end
190
+
164
191
  def desired_shipment_different_from_current
165
192
  if desired_shipment.id == current_shipment.id
166
193
  errors.add(:desired_shipment, :can_not_transfer_within_same_shipment)
@@ -24,7 +24,7 @@ module Spree
24
24
  raise "The order association has been removed from InventoryUnit. The order is now determined from the shipment."
25
25
  end
26
26
 
27
- validates_presence_of :shipment, :line_item, :variant
27
+ validates :shipment, :line_item, :variant, presence: true
28
28
 
29
29
  before_destroy :ensure_can_destroy
30
30
 
@@ -18,11 +18,11 @@ module Spree
18
18
 
19
19
  has_one :product, through: :variant
20
20
 
21
- has_many :adjustments, as: :adjustable, inverse_of: :adjustable, dependent: :destroy
21
+ has_many :adjustments, as: :adjustable, inverse_of: :adjustable, dependent: :destroy, autosave: true
22
22
  has_many :inventory_units, inverse_of: :line_item
23
23
 
24
24
  before_validation :normalize_quantity
25
- before_validation :set_required_attributes
25
+ after_initialize :set_required_attributes
26
26
 
27
27
  validates :variant, presence: true
28
28
  validates :quantity, numericality: {
@@ -159,6 +159,7 @@ module Spree
159
159
  # Sets tax category, price-related attributes from
160
160
  # its variant if they are nil and a variant is present.
161
161
  def set_required_attributes
162
+ return if persisted?
162
163
  return unless variant
163
164
  self.tax_category ||= variant.tax_category
164
165
  set_pricing_attributes
@@ -52,7 +52,8 @@ module Spree
52
52
  :total_available_store_credit,
53
53
  :item_total_before_tax,
54
54
  :shipment_total_before_tax,
55
- :item_total_excluding_vat
55
+ :item_total_excluding_vat,
56
+ :promo_total
56
57
  )
57
58
  alias :display_ship_total :display_shipment_total
58
59
 
@@ -101,7 +102,7 @@ module Spree
101
102
  has_many :cartons, -> { distinct }, through: :inventory_units
102
103
 
103
104
  # Adjustments and promotions
104
- has_many :adjustments, -> { order(:created_at) }, as: :adjustable, inverse_of: :adjustable, dependent: :destroy
105
+ has_many :adjustments, -> { order(:created_at) }, as: :adjustable, inverse_of: :adjustable, dependent: :destroy, autosave: true
105
106
  has_many :line_item_adjustments, through: :line_items, source: :adjustments
106
107
  has_many :shipment_adjustments, through: :shipments, source: :adjustments
107
108
  has_many :all_adjustments,
@@ -844,9 +845,10 @@ module Spree
844
845
  cancel_shipments!
845
846
  cancel_payments!
846
847
 
847
- send_cancel_email
848
848
  update_column(:canceled_at, Time.current)
849
849
  recalculate
850
+
851
+ Spree::Bus.publish :order_canceled, order: self
850
852
  end
851
853
 
852
854
  def cancel_shipments!
@@ -862,10 +864,6 @@ module Spree
862
864
  end
863
865
  end
864
866
 
865
- def send_cancel_email
866
- Spree::Config.order_mailer_class.cancel_email(self).deliver_later
867
- end
868
-
869
867
  def after_resume
870
868
  shipments.each(&:resume!)
871
869
  end
@@ -9,9 +9,22 @@ class Spree::OrderCancellations
9
9
  # #call(unit_cancels)
10
10
  class_attribute :short_ship_tax_notifier
11
11
 
12
- # allows sending an email when inventory is cancelled
13
- class_attribute :send_cancellation_mailer
14
- self.send_cancellation_mailer = true
12
+ class << self
13
+ def send_cancellation_mailer=(value)
14
+ @send_cancellation_mailer = value
15
+
16
+ unless value
17
+ Spree.deprecator.warn "Using the `:send_cancellation_mailer` class " \
18
+ "attribute is deprecated in favor of including or omitting the " \
19
+ "`Spree::OrderInventoryCancellationMailerSubscriber` from " \
20
+ "`Spree::Config.environment.subscribers` in an initializer."
21
+ end
22
+ end
23
+
24
+ def send_cancellation_mailer
25
+ @send_cancellation_mailer || @send_cancellation_mailer.nil?
26
+ end
27
+ end
15
28
 
16
29
  def initialize(order)
17
30
  @order = order
@@ -38,9 +51,6 @@ class Spree::OrderCancellations
38
51
  inventory_units.each do |iu|
39
52
  unit_cancels << short_ship_unit(iu, created_by:)
40
53
  end
41
-
42
- update_shipped_shipments(inventory_units)
43
- Spree::Config.order_mailer_class.inventory_cancellation_email(@order, inventory_units.to_a).deliver_later if Spree::OrderCancellations.send_cancellation_mailer
44
54
  end
45
55
 
46
56
  @order.recalculate
@@ -50,6 +60,7 @@ class Spree::OrderCancellations
50
60
  end
51
61
  end
52
62
 
63
+ Spree::Bus.publish(:order_short_shipped, order: @order, inventory_units:)
53
64
  unit_cancels
54
65
  end
55
66
 
@@ -109,18 +120,4 @@ class Spree::OrderCancellations
109
120
 
110
121
  unit_cancel
111
122
  end
112
-
113
- # if any shipments are now fully shipped then mark them as such
114
- def update_shipped_shipments(inventory_units)
115
- shipments = Spree::Shipment.
116
- includes(:inventory_units).
117
- where(id: inventory_units.map(&:shipment_id)).
118
- to_a
119
-
120
- shipments.each do |shipment|
121
- if shipment.inventory_units.all? { |iu| iu.shipped? || iu.canceled? }
122
- shipment.update!(state: 'shipped', shipped_at: Time.current)
123
- end
124
- end
125
- end
126
123
  end