Files
openfoodnetwork/app/models/spree/order/checkout.rb
2020-07-28 23:29:49 +01:00

192 lines
6.5 KiB
Ruby

# frozen_string_literal: true
module Spree
class Order < ActiveRecord::Base
module Checkout
def self.included(klass)
klass.class_eval do
class_attribute :next_event_transitions
class_attribute :previous_states
class_attribute :checkout_flow
class_attribute :checkout_steps
class_attribute :removed_transitions
def self.checkout_flow(&block)
if block_given?
@checkout_flow = block
define_state_machine!
else
@checkout_flow
end
end
def self.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 a ton of warnings when the state machine is re-defined
StateMachine::Machine.ignore_method_conflicts = true
# 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 do
klass.next_event_transitions.each { |t| transition(t.merge(on: :next)) }
# Persist the state on the order
after_transition do |order|
order.state = order.state
order.save
end
event :cancel do
transition to: :canceled, if: :allow_cancel?
end
event :return do
transition to: :returned, from: :awaiting_return, unless: :awaiting_returns?
end
event :resume do
transition to: :resumed, from: :canceled, if: :allow_resume?
end
event :authorize_return do
transition to: :awaiting_return
end
if states[:payment]
before_transition to: :complete do |order|
order.process_payments! if order.payment_required?
end
end
before_transition from: :cart, do: :ensure_line_items_present
before_transition to: :delivery, do: :create_proposed_shipments
before_transition to: :delivery, do: :ensure_available_shipping_rates
after_transition to: :complete, do: :finalize!
after_transition to: :delivery, do: :create_tax_charge!
after_transition to: :resumed, do: :after_resume
after_transition to: :canceled, do: :after_cancel
end
end
def self.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 self.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 self.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 self.remove_transition(options = {})
removed_transitions << options
next_event_transitions.delete(find_transition(options))
end
def self.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 self.next_event_transitions
@next_event_transitions ||= []
end
def self.checkout_steps
@checkout_steps ||= {}
end
def self.add_transition(options)
next_event_transitions << { options.delete(:from) => options.delete(:to) }.
merge(options)
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 checkout_step?(step)
step.present? ? checkout_steps.include?(step) : false
end
def checkout_step_index(step)
checkout_steps.index(step)
end
def self.removed_transitions
@removed_transitions ||= []
end
def can_go_to_state?(state)
return false unless self.state.present? &&
checkout_step?(state) &&
checkout_step?(self.state)
checkout_step_index(state) > checkout_step_index(self.state)
end
end
end
end
end
end