diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b4ef66b1821..5bd856f79d9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -512,7 +512,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 diff --git a/core/app/models/spree/core/state_machines/inventory_unit.rb b/core/app/models/spree/core/state_machines/inventory_unit.rb new file mode 100644 index 00000000000..4bc743a4427 --- /dev/null +++ b/core/app/models/spree/core/state_machines/inventory_unit.rb @@ -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: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + 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 diff --git a/core/app/models/spree/core/state_machines/order.rb b/core/app/models/spree/core/state_machines/order.rb new file mode 100644 index 00000000000..691b20e212a --- /dev/null +++ b/core/app/models/spree/core/state_machines/order.rb @@ -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 diff --git a/core/app/models/spree/core/state_machines/payment.rb b/core/app/models/spree/core/state_machines/payment.rb new file mode 100644 index 00000000000..9c0ba3cdd97 --- /dev/null +++ b/core/app/models/spree/core/state_machines/payment.rb @@ -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: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + 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 diff --git a/core/app/models/spree/core/state_machines/reimbursement.rb b/core/app/models/spree/core/state_machines/reimbursement.rb new file mode 100644 index 00000000000..ecb449a5351 --- /dev/null +++ b/core/app/models/spree/core/state_machines/reimbursement.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Spree + module Core + class StateMachines + # Reimbursement' state machine + # + # for each event the following instance methods are dynamically implemented: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + module Reimbursement + extend ActiveSupport::Concern + + included do + state_machine :reimbursement_status, initial: :pending do + event :errored do + transition to: :errored, from: [:pending, :errored] + end + + event :reimbursed do + transition to: :reimbursed, from: [:pending, :errored] + end + end + end + end + end + end +end diff --git a/core/app/models/spree/core/state_machines/return_authorization.rb b/core/app/models/spree/core/state_machines/return_authorization.rb new file mode 100644 index 00000000000..1ecb27da86b --- /dev/null +++ b/core/app/models/spree/core/state_machines/return_authorization.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Spree + module Core + class StateMachines + # Return Authorizations' state machine + # + # for each event the following instance methods are dynamically implemented: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + module ReturnAuthorization + extend ActiveSupport::Concern + + included do + state_machine initial: :authorized do + before_transition to: :canceled, do: :cancel_return_items + + event :cancel do + transition to: :canceled, from: :authorized, + if: lambda { |return_authorization| return_authorization.can_cancel_return_items? } + end + end + end + end + end + end +end diff --git a/core/app/models/spree/core/state_machines/return_item/acceptance_status.rb b/core/app/models/spree/core/state_machines/return_item/acceptance_status.rb new file mode 100644 index 00000000000..1009fbd747c --- /dev/null +++ b/core/app/models/spree/core/state_machines/return_item/acceptance_status.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Spree + module Core + class StateMachines + module ReturnItem + # Return Items' acceptance status state machine + # + # for each event the following instance methods are dynamically implemented: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + module AcceptanceStatus + extend ActiveSupport::Concern + + included do + state_machine :acceptance_status, initial: :pending do + event :attempt_accept do + transition to: :accepted, from: :accepted + transition to: :accepted, from: :pending, if: ->(return_item) { return_item.eligible_for_return? } + transition to: :manual_intervention_required, from: :pending, if: ->(return_item) { return_item.requires_manual_intervention? } + transition to: :rejected, from: :pending + end + + # bypasses eligibility checks + event :accept do + transition to: :accepted, from: [:accepted, :pending, :manual_intervention_required] + end + + # bypasses eligibility checks + event :reject do + transition to: :rejected, from: [:accepted, :pending, :manual_intervention_required] + end + + # bypasses eligibility checks + event :require_manual_intervention do + transition to: :manual_intervention_required, from: [:accepted, :pending, :manual_intervention_required] + end + + after_transition any => any, do: :persist_acceptance_status_errors + end + end + end + end + end + end +end diff --git a/core/app/models/spree/core/state_machines/return_item/reception_status.rb b/core/app/models/spree/core/state_machines/return_item/reception_status.rb new file mode 100644 index 00000000000..fa71fe83c6c --- /dev/null +++ b/core/app/models/spree/core/state_machines/return_item/reception_status.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Spree + module Core + class StateMachines + module ReturnItem + # Return Items' reception status state machine + # + # for each event the following instance methods are dynamically implemented: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + module ReceptionStatus + extend ActiveSupport::Concern + + included do + state_machine :reception_status, initial: :awaiting do + after_transition to: ::Spree::ReturnItem::COMPLETED_RECEPTION_STATUSES, do: :attempt_accept, if: :can_attempt_accept? + after_transition to: ::Spree::ReturnItem::COMPLETED_RECEPTION_STATUSES, do: :check_unexchange + after_transition to: :received, do: :process_inventory_unit! + + event(:cancel) { transition to: :cancelled, from: :awaiting } + + event(:receive) { transition to: :received, from: ::Spree::ReturnItem::INTERMEDIATE_RECEPTION_STATUSES + [:awaiting] } + event(:unexchange) { transition to: :unexchanged, from: [:awaiting] } + event(:give) { transition to: :given_to_customer, from: :awaiting } + event(:lost) { transition to: :lost_in_transit, from: :awaiting } + event(:wrong_item_shipped) { transition to: :shipped_wrong_item, from: :awaiting } + event(:short_shipped) { transition to: :short_shipped, from: :awaiting } + event(:in_transit) { transition to: :in_transit, from: :awaiting } + event(:expired) { transition to: :expired, from: :awaiting } + end + end + end + end + end + end +end diff --git a/core/app/models/spree/core/state_machines/shipment.rb b/core/app/models/spree/core/state_machines/shipment.rb new file mode 100644 index 00000000000..33ced9b991e --- /dev/null +++ b/core/app/models/spree/core/state_machines/shipment.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Spree + module Core + class StateMachines + # Shipments' state machine + # + # for each event the following instance methods are dynamically implemented: + # # + # #! + # #can_? + # + # for each state the following instance methods are implemented: + # #? + # + module Shipment + extend ActiveSupport::Concern + + included do + state_machine initial: :pending, use_transactions: false do + event :ready do + transition from: :pending, to: :shipped, if: :can_transition_from_pending_to_shipped? + transition from: :pending, to: :ready, if: :can_transition_from_pending_to_ready? + end + + event :pend do + transition from: :ready, to: :pending + end + + event :ship do + transition from: [:ready, :canceled], to: :shipped + end + after_transition to: :shipped, do: :after_ship + + event :cancel do + transition to: :canceled, from: [:pending, :ready] + end + after_transition to: :canceled, do: :after_cancel + + event :resume do + transition from: :canceled, to: :ready, if: :can_transition_from_canceled_to_ready? + transition from: :canceled, to: :pending + end + after_transition from: :canceled, to: [:pending, :ready, :shipped], do: :after_resume + + after_transition do |shipment, transition| + shipment.state_changes.create!( + previous_state: transition.from, + next_state: transition.to, + name: 'shipment' + ) + end + end + end + end + end + end +end diff --git a/core/lib/spree/core/state_machines.rb b/core/lib/spree/core/state_machines.rb index f8e629b37db..7bb9d373543 100644 --- a/core/lib/spree/core/state_machines.rb +++ b/core/lib/spree/core/state_machines.rb @@ -2,87 +2,54 @@ module Spree module Core - class StateMachines - attr_writer :reimbursement, - :return_authorization, - :return_item_acceptance, - :return_item_reception, - :payment, - :inventory_unit, - :shipment, - :order - - def return_authorization - @return_authorization ||= begin - require 'spree/core/state_machines/return_authorization' - 'Spree::Core::StateMachines::ReturnAuthorization' - end - - @return_authorization.constantize - end - - def return_item_reception - @return_item_reception_status ||= begin - require 'spree/core/state_machines/return_item/reception_status' - 'Spree::Core::StateMachines::ReturnItem::ReceptionStatus' - end - - @return_item_reception_status.constantize - end - - def return_item_acceptance - @return_item_acceptance_status ||= begin - require 'spree/core/state_machines/return_item/acceptance_status' - 'Spree::Core::StateMachines::ReturnItem::AcceptanceStatus' - end - - @return_item_acceptance_status.constantize - end - - def payment - @payment ||= begin - require 'spree/core/state_machines/payment' - 'Spree::Core::StateMachines::Payment' - end - - @payment.constantize - end - - def inventory_unit - @inventory_unit ||= begin - require 'spree/core/state_machines/inventory_unit' - 'Spree::Core::StateMachines::InventoryUnit' - end - - @inventory_unit.constantize - end - - def shipment - @shipment ||= begin - require 'spree/core/state_machines/shipment' - 'Spree::Core::StateMachines::Shipment' - end - - @shipment.constantize - end - - def order - @order ||= begin - require 'spree/core/state_machines/order' - 'Spree::Core::StateMachines::Order' - end - - @order.constantize - end - - def reimbursement - @reimbursement ||= begin - require 'spree/core/state_machines/reimbursement' - 'Spree::Core::StateMachines::Reimbursement' - end - - @reimbursement.constantize - end + class StateMachines < Spree::Preferences::Configuration + # State Machine module for Reimbursements + # + # @!attribute [rw] reimbursement + # @return [Module] a module that implements the state machine for the `Spree::Reimbursement` model. + class_name_attribute :reimbursement, default: "Spree::Core::StateMachines::Reimbursement" + + # State Machine module for Return Authorizations + # + # @!attribute [rw] return_authorization + # @return [Module] a module that implements the state machine for the `Spree::ReturnAuthorization` model. + class_name_attribute :return_authorization, default: "Spree::Core::StateMachines::ReturnAuthorization" + + # State Machine module for Return Item Acceptances + # + # @!attribute [rw] return_item_acceptance + # @return [Module] a module that implements the acceptance part of the state machine for the `Spree::ReturnItem` model. + class_name_attribute :return_item_acceptance, default: "Spree::Core::StateMachines::ReturnItem::AcceptanceStatus" + + # State Machine module for Return Item Receptions + # + # @!attribute [rw] return_item_reception + # @return [Module] a module that implements the reception part of the state machine for the `Spree::ReturnItem` model. + class_name_attribute :return_item_reception, default: "Spree::Core::StateMachines::ReturnItem::ReceptionStatus" + + # State Machine module for Payments + # + # @!attribute [rw] payment + # @return [Module] a module that implements the state machine for the `Spree::Payment` model. + class_name_attribute :payment, default: "Spree::Core::StateMachines::Payment" + + # State Machine module for Inventory Units + # + # @!attribute [rw] inventory_unit + # @return [Module] a module that implements the state machine for the `Spree::InventoryUnit` model. + class_name_attribute :inventory_unit, default: "Spree::Core::StateMachines::InventoryUnit" + + # State Machine module for Shipments + # + # @!attribute [rw] shipment + # @return [Module] a module that implements the state machine for the `Spree::Shipment` model. + class_name_attribute :shipment, default: "Spree::Core::StateMachines::Shipment" + + # State Machine module for Orders + # + # @!attribute [rw] order + # @return [Module] a module that implements the state machine for the `Spree::Order` model. + class_name_attribute :order, default: "Spree::Core::StateMachines::Order" end end end diff --git a/core/lib/spree/core/state_machines/inventory_unit.rb b/core/lib/spree/core/state_machines/inventory_unit.rb index 4bc743a4427..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/inventory_unit.rb +++ b/core/lib/spree/core/state_machines/inventory_unit.rb @@ -1,42 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - # Inventory Units' state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - 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 +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/order.rb b/core/lib/spree/core/state_machines/order.rb index c52e107fe52..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/order.rb +++ b/core/lib/spree/core/state_machines/order.rb @@ -1,252 +1,7 @@ # 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 - attr_writer :checkout_steps - attr_writer :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 +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/payment.rb b/core/lib/spree/core/state_machines/payment.rb index 9c0ba3cdd97..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/payment.rb +++ b/core/lib/spree/core/state_machines/payment.rb @@ -1,61 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - # Payments' state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - 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 +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/reimbursement.rb b/core/lib/spree/core/state_machines/reimbursement.rb index ecb449a5351..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/reimbursement.rb +++ b/core/lib/spree/core/state_machines/reimbursement.rb @@ -1,33 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - # Reimbursement' state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - module Reimbursement - extend ActiveSupport::Concern - - included do - state_machine :reimbursement_status, initial: :pending do - event :errored do - transition to: :errored, from: [:pending, :errored] - end - - event :reimbursed do - transition to: :reimbursed, from: [:pending, :errored] - end - end - end - end - end - end -end +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/return_authorization.rb b/core/lib/spree/core/state_machines/return_authorization.rb index 1ecb27da86b..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/return_authorization.rb +++ b/core/lib/spree/core/state_machines/return_authorization.rb @@ -1,32 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - # Return Authorizations' state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - module ReturnAuthorization - extend ActiveSupport::Concern - - included do - state_machine initial: :authorized do - before_transition to: :canceled, do: :cancel_return_items - - event :cancel do - transition to: :canceled, from: :authorized, - if: lambda { |return_authorization| return_authorization.can_cancel_return_items? } - end - end - end - end - end - end -end +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/return_item/acceptance_status.rb b/core/lib/spree/core/state_machines/return_item/acceptance_status.rb index 1009fbd747c..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/return_item/acceptance_status.rb +++ b/core/lib/spree/core/state_machines/return_item/acceptance_status.rb @@ -1,51 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - module ReturnItem - # Return Items' acceptance status state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - module AcceptanceStatus - extend ActiveSupport::Concern - - included do - state_machine :acceptance_status, initial: :pending do - event :attempt_accept do - transition to: :accepted, from: :accepted - transition to: :accepted, from: :pending, if: ->(return_item) { return_item.eligible_for_return? } - transition to: :manual_intervention_required, from: :pending, if: ->(return_item) { return_item.requires_manual_intervention? } - transition to: :rejected, from: :pending - end - - # bypasses eligibility checks - event :accept do - transition to: :accepted, from: [:accepted, :pending, :manual_intervention_required] - end - - # bypasses eligibility checks - event :reject do - transition to: :rejected, from: [:accepted, :pending, :manual_intervention_required] - end - - # bypasses eligibility checks - event :require_manual_intervention do - transition to: :manual_intervention_required, from: [:accepted, :pending, :manual_intervention_required] - end - - after_transition any => any, do: :persist_acceptance_status_errors - end - end - end - end - end - end -end +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/return_item/reception_status.rb b/core/lib/spree/core/state_machines/return_item/reception_status.rb index fa71fe83c6c..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/return_item/reception_status.rb +++ b/core/lib/spree/core/state_machines/return_item/reception_status.rb @@ -1,42 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - module ReturnItem - # Return Items' reception status state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - module ReceptionStatus - extend ActiveSupport::Concern - - included do - state_machine :reception_status, initial: :awaiting do - after_transition to: ::Spree::ReturnItem::COMPLETED_RECEPTION_STATUSES, do: :attempt_accept, if: :can_attempt_accept? - after_transition to: ::Spree::ReturnItem::COMPLETED_RECEPTION_STATUSES, do: :check_unexchange - after_transition to: :received, do: :process_inventory_unit! - - event(:cancel) { transition to: :cancelled, from: :awaiting } - - event(:receive) { transition to: :received, from: ::Spree::ReturnItem::INTERMEDIATE_RECEPTION_STATUSES + [:awaiting] } - event(:unexchange) { transition to: :unexchanged, from: [:awaiting] } - event(:give) { transition to: :given_to_customer, from: :awaiting } - event(:lost) { transition to: :lost_in_transit, from: :awaiting } - event(:wrong_item_shipped) { transition to: :shipped_wrong_item, from: :awaiting } - event(:short_shipped) { transition to: :short_shipped, from: :awaiting } - event(:in_transit) { transition to: :in_transit, from: :awaiting } - event(:expired) { transition to: :expired, from: :awaiting } - end - end - end - end - end - end -end +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/lib/spree/core/state_machines/shipment.rb b/core/lib/spree/core/state_machines/shipment.rb index 33ced9b991e..72f6898b795 100644 --- a/core/lib/spree/core/state_machines/shipment.rb +++ b/core/lib/spree/core/state_machines/shipment.rb @@ -1,58 +1,7 @@ # frozen_string_literal: true -module Spree - module Core - class StateMachines - # Shipments' state machine - # - # for each event the following instance methods are dynamically implemented: - # # - # #! - # #can_? - # - # for each state the following instance methods are implemented: - # #? - # - module Shipment - extend ActiveSupport::Concern - - included do - state_machine initial: :pending, use_transactions: false do - event :ready do - transition from: :pending, to: :shipped, if: :can_transition_from_pending_to_shipped? - transition from: :pending, to: :ready, if: :can_transition_from_pending_to_ready? - end - - event :pend do - transition from: :ready, to: :pending - end - - event :ship do - transition from: [:ready, :canceled], to: :shipped - end - after_transition to: :shipped, do: :after_ship - - event :cancel do - transition to: :canceled, from: [:pending, :ready] - end - after_transition to: :canceled, do: :after_cancel - - event :resume do - transition from: :canceled, to: :ready, if: :can_transition_from_canceled_to_ready? - transition from: :canceled, to: :pending - end - after_transition from: :canceled, to: [:pending, :ready, :shipped], do: :after_resume - - after_transition do |shipment, transition| - shipment.state_changes.create!( - previous_state: transition.from, - next_state: transition.to, - name: 'shipment' - ) - end - end - end - end - end - end -end +Spree.deprecator.warn( + <<~MSG + The file "#{__FILE__}" does not need to be `require`d any longer, it is now autoloaded. + MSG +) diff --git a/core/spec/lib/spree/core/state_machines_spec.rb b/core/spec/lib/spree/core/state_machines_spec.rb new file mode 100644 index 00000000000..cce9ebde4b3 --- /dev/null +++ b/core/spec/lib/spree/core/state_machines_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "require 'spree/core/state_machines" do + %w( + inventory_unit + order + payment + reimbursement + return_authorization + shipment + return_item/acceptance_status + return_item/reception_status + ).each do |helper_name| + describe "require 'spree/core/state_machines/#{helper_name}" do + it "exists but will print a deprecation warning" do + expect(Spree.deprecator).to receive(:warn).with( + "The file \"#{Spree::Core::Engine.root}/lib/spree/core/state_machines/#{helper_name}.rb\" does not need to be `require`d any longer, it is now autoloaded.\n" + ) + require "spree/core/state_machines/#{helper_name}" + end + end + end +end