Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: solidusio/solidus
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 5ff52ff9eb1202855b0ac9e81631c60bd7f34b43
Choose a base ref
..
head repository: solidusio/solidus
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4e882384fd0af88165321ae6cf022a0490e7ba1e
Choose a head ref
Showing with 925 additions and 771 deletions.
  1. +1 −3 .rubocop_todo.yml
  2. +42 −0 core/app/models/spree/core/state_machines/inventory_unit.rb
  3. +250 −0 core/app/models/spree/core/state_machines/order.rb
  4. +61 −0 core/app/models/spree/core/state_machines/payment.rb
  5. +33 −0 core/app/models/spree/core/state_machines/reimbursement.rb
  6. +32 −0 core/app/models/spree/core/state_machines/return_authorization.rb
  7. +51 −0 core/app/models/spree/core/state_machines/return_item/acceptance_status.rb
  8. +42 −0 core/app/models/spree/core/state_machines/return_item/reception_status.rb
  9. +58 −0 core/app/models/spree/core/state_machines/shipment.rb
  10. +19 −0 core/app/models/spree/line_item.rb
  11. +120 −0 core/app/models/spree/money.rb
  12. +1 −0 core/app/models/spree/order_taxation.rb
  13. +12 −1 core/app/models/spree/tax/tax_helpers.rb
  14. +1 −1 core/app/models/spree/tax_calculator/default.rb
  15. +10 −1 core/app/models/spree/variant.rb
  16. +0 −1 core/lib/spree/core.rb
  17. +48 −81 core/lib/spree/core/state_machines.rb
  18. +5 −40 core/lib/spree/core/state_machines/inventory_unit.rb
  19. +5 −250 core/lib/spree/core/state_machines/order.rb
  20. +5 −59 core/lib/spree/core/state_machines/payment.rb
  21. +5 −31 core/lib/spree/core/state_machines/reimbursement.rb
  22. +5 −30 core/lib/spree/core/state_machines/return_authorization.rb
  23. +5 −49 core/lib/spree/core/state_machines/return_item/acceptance_status.rb
  24. +5 −40 core/lib/spree/core/state_machines/return_item/reception_status.rb
  25. +5 −56 core/lib/spree/core/state_machines/shipment.rb
  26. +5 −118 core/lib/spree/money.rb
  27. +9 −1 core/lib/spree/testing_support/factories/order_factory.rb
  28. +25 −0 core/spec/lib/spree/core/state_machines_spec.rb
  29. +5 −3 core/spec/models/spree/line_item_spec.rb
  30. 0 core/spec/{lib → models}/spree/money_spec.rb
  31. +21 −3 core/spec/models/spree/order_taxation_spec.rb
  32. +39 −3 core/spec/models/spree/tax/tax_helpers_spec.rb
4 changes: 1 addition & 3 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -166,7 +166,6 @@ Layout/SpaceAroundOperators:
- "backend/spec/features/admin/orders/order_details_spec.rb"
- "bin/__rspec"
- "bin/rspec"
- "core/lib/spree/money.rb"
- "core/spec/models/spree/order/number_generator_spec.rb"

# Offense count: 8
@@ -413,7 +412,7 @@ Rails/OutputSafety:
- "core/app/helpers/spree/base_helper.rb"
- "core/app/helpers/spree/checkout_helper.rb"
- "core/app/helpers/spree/products_helper.rb"
- "core/lib/spree/money.rb"
- "core/app/models/spree/money.rb"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
@@ -512,7 +511,6 @@ Style/AccessorGrouping:
- "core/app/models/spree/order.rb"
- "core/lib/generators/spree/dummy/dummy_generator.rb"
- "core/lib/spree/core/search/base.rb"
- "core/lib/spree/core/state_machines/order.rb"
- "core/lib/spree/core/stock_configuration.rb"

# Offense count: 1
42 changes: 42 additions & 0 deletions core/app/models/spree/core/state_machines/inventory_unit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

module Spree
module Core
class StateMachines
# Inventory Units' state machine
#
# for each event the following instance methods are dynamically implemented:
# #<event_name>
# #<event_name>!
# #can_<event_name>?
#
# for each state the following instance methods are implemented:
# #<state_name>?
#
module InventoryUnit
extend ActiveSupport::Concern

included do
state_machine initial: :on_hand do
event :fill_backorder do
transition to: :on_hand, from: :backordered
end
after_transition on: :fill_backorder, do: :fulfill_order

event :ship do
transition to: :shipped, if: :allow_ship?
end

event :return do
transition to: :returned, from: :shipped
end

event :cancel do
transition to: :canceled, from: ::Spree::InventoryUnit::CANCELABLE_STATES.map(&:to_sym)
end
end
end
end
end
end
end
250 changes: 250 additions & 0 deletions core/app/models/spree/core/state_machines/order.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# frozen_string_literal: true

module Spree
module Core
class StateMachines
module Order
def self.included(klass)
klass.extend ClassMethods
end

module ClassMethods
attr_accessor :previous_states
attr_writer :next_event_transitions, :checkout_steps, :removed_transitions

def checkout_flow(&block)
if block_given?
@checkout_flow = block
define_state_machine!
else
@checkout_flow
end
end

def define_state_machine!
self.checkout_steps = {}
self.next_event_transitions = []
self.previous_states = [:cart]
self.removed_transitions = []

# Build the checkout flow using the checkout_flow defined either
# within the Order class, or a decorator for that class.
#
# This method may be called multiple times depending on if the
# checkout_flow is re-defined in a decorator or not.
instance_eval(&checkout_flow)

klass = self

# To avoid multiple occurrences of the same transition being defined
# On first definition, state_machines will not be defined
state_machines.clear if respond_to?(:state_machines)
state_machine :state, initial: :cart, use_transactions: false do
klass.next_event_transitions.each { |state| transition(state.merge(on: :next)) }

# Persist the state on the order
after_transition do |order, transition|
# Hard to say if this is really necessary, it was introduced in this commit:
# https://github.com/mamhoff/solidus/commit/fa1d66c42e4c04ee7cd1c20d87e4cdb74a226d3d
# But it seems to be harmless, so we'll keep it for now.
order.state = order.state # rubocop:disable Lint/SelfAssignment

order.state_changes.create(
previous_state: transition.from,
next_state: transition.to,
name: 'order',
user_id: order.user_id
)
order.save
end

event :cancel do
transition to: :canceled, if: :allow_cancel?, from: :complete
end

event :return do
transition to: :returned, from: [:complete, :awaiting_return], if: :all_inventory_units_returned?
end

event :resume do
transition to: :resumed, from: :canceled, if: :canceled?
end

event :authorize_return do
transition to: :awaiting_return, from: :complete
end

event :complete do
transition to: :complete, from: klass.checkout_steps.keys.last
end

if states[:payment]
event :payment_failed do
transition to: :payment, from: :confirm
end

after_transition to: :complete, do: :add_payment_sources_to_wallet
before_transition to: :payment, do: :add_default_payment_from_wallet
before_transition to: :payment, do: :ensure_billing_address

before_transition to: :confirm, do: :add_store_credit_payments

# see also process_payments_before_complete below which needs to
# be added in the correct sequence.
end

before_transition from: :cart, do: :ensure_line_items_present

if states[:address]
before_transition to: :address, do: :assign_default_user_addresses
before_transition from: :address, do: :persist_user_address!
end

if states[:delivery]
before_transition to: :delivery, do: :ensure_shipping_address
before_transition to: :delivery, do: :create_proposed_shipments
before_transition to: :delivery, do: :ensure_available_shipping_rates
end

before_transition to: :resumed, do: :ensure_line_item_variants_are_not_deleted
before_transition to: :resumed, do: :validate_line_item_availability

# Sequence of before_transition to: :complete
# calls matter so that we do not process payments
# until validations have passed
before_transition to: :complete, do: :validate_line_item_availability
before_transition to: :complete, do: :ensure_promotions_eligible
before_transition to: :complete, do: :ensure_line_item_variants_are_not_deleted
before_transition to: :complete, do: :ensure_inventory_units
if states[:payment]
before_transition to: :complete, do: :process_payments_before_complete
end

after_transition to: :complete, do: :finalize
after_transition to: :resumed, do: :after_resume
after_transition to: :canceled, do: :after_cancel

after_transition from: any - :cart, to: any - [:confirm, :complete] do |order|
order.recalculate
end

after_transition do |order, transition|
order.logger.debug "Order #{order.number} transitioned from #{transition.from} to #{transition.to} via #{transition.event}"
end

after_failure do |order, transition|
order.logger.debug "Order #{order.number} halted transition on event #{transition.event} state #{transition.from}: #{order.errors.full_messages.join}"
end
end
end

def go_to_state(name, options = {})
checkout_steps[name] = options
previous_states.each do |state|
add_transition({ from: state, to: name }.merge(options))
end
if options[:if]
previous_states << name
else
self.previous_states = [name]
end
end

def insert_checkout_step(name, options = {})
before = options.delete(:before)
after = options.delete(:after) unless before
after = checkout_steps.keys.last unless before || after

cloned_steps = checkout_steps.clone
cloned_removed_transitions = removed_transitions.clone
checkout_flow do
cloned_steps.each_pair do |key, value|
go_to_state(name, options) if key == before
go_to_state(key, value)
go_to_state(name, options) if key == after
end
cloned_removed_transitions.each do |transition|
remove_transition(transition)
end
end
end

def remove_checkout_step(name)
cloned_steps = checkout_steps.clone
cloned_removed_transitions = removed_transitions.clone
checkout_flow do
cloned_steps.each_pair do |key, value|
go_to_state(key, value) unless key == name
end
cloned_removed_transitions.each do |transition|
remove_transition(transition)
end
end
end

def remove_transition(options = {})
removed_transitions << options
next_event_transitions.delete(find_transition(options))
end

def find_transition(options = {})
return nil if options.nil? || !options.include?(:from) || !options.include?(:to)

next_event_transitions.detect do |transition|
transition[options[:from].to_sym] == options[:to].to_sym
end
end

def next_event_transitions
@next_event_transitions ||= []
end

def checkout_steps
@checkout_steps ||= {}
end

def checkout_step_names
checkout_steps.keys
end

def add_transition(options)
next_event_transitions << { options.delete(:from) => options.delete(:to) }.merge(options)
end

def removed_transitions
@removed_transitions ||= []
end
end

def checkout_steps
steps = self.class.checkout_steps.each_with_object([]) { |(step, options), checkout_steps|
next if options.include?(:if) && !options[:if].call(self)

checkout_steps << step
}.map(&:to_s)
# Ensure there is always a complete step
steps << "complete" unless steps.include?("complete")
steps
end

def has_checkout_step?(step)
step.present? && checkout_steps.include?(step)
end

def passed_checkout_step?(step)
has_checkout_step?(step) && checkout_step_index(step) < checkout_step_index(state)
end

def checkout_step_index(step)
checkout_steps.index(step).to_i
end

def can_go_to_state?(state)
return false unless has_checkout_step?(self.state) && has_checkout_step?(state)

checkout_step_index(state) > checkout_step_index(self.state)
end
end
end
end
end
61 changes: 61 additions & 0 deletions core/app/models/spree/core/state_machines/payment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module Spree
module Core
class StateMachines
# Payments' state machine
#
# for each event the following instance methods are dynamically implemented:
# #<event_name>
# #<event_name>!
# #can_<event_name>?
#
# for each state the following instance methods are implemented:
# #<state_name>?
#
module Payment
extend ActiveSupport::Concern

included do
state_machine initial: :checkout do
# With card payments, happens before purchase or authorization happens
#
# Setting it after creating a profile and authorizing a full amount will
# prevent the payment from being authorized again once Order transitions
# to complete
event :started_processing do
transition from: [:checkout, :pending, :completed, :processing], to: :processing
end
# When processing during checkout fails
event :failure do
transition from: [:pending, :processing], to: :failed
end
# With card payments this represents authorizing the payment
event :pend do
transition from: [:checkout, :processing], to: :pending
end
# With card payments this represents completing a purchase or capture transaction
event :complete do
transition from: [:processing, :pending, :checkout], to: :completed
end
event :void do
transition from: [:pending, :processing, :completed, :checkout], to: :void
end
# when the card brand isnt supported
event :invalidate do
transition from: [:checkout], to: :invalid
end

after_transition do |payment, transition|
payment.state_changes.create!(
previous_state: transition.from,
next_state: transition.to,
name: 'payment'
)
end
end
end
end
end
end
end
Loading