core/app/models/spree/order_merger.rb

Summary

Maintainability
A
45 mins
Test Coverage
A
100%
module Spree
  class OrderMerger
    attr_accessor :order
    delegate :updater, to: :order

    def initialize(order)
      @order = order
    end

    def merge!(other_order, user = nil)
      other_order.line_items.each do |other_order_line_item|
        next unless other_order_line_item.currency == order.currency

        current_line_item = find_matching_line_item(other_order_line_item)
        handle_merge(current_line_item, other_order_line_item)
      end

      set_user(user)
      persist_merge

      # So that the destroy doesn't take out line items which may have been re-assigned
      other_order.line_items.reload
      other_order.destroy
    end

    # Compare the line item of the other order with mine.
    # Make sure you allow any extensions to chime in on whether or
    # not the extension-specific parts of the line item match
    def find_matching_line_item(other_order_line_item)
      order.line_items.detect do |my_li|
        my_li.variant == other_order_line_item.variant &&
          Spree::Dependencies.cart_compare_line_items_service.constantize.new.call(order: order,
                                                                                   line_item: my_li,
                                                                                   options: other_order_line_item.serializable_hash).value
      end
    end

    def set_user(user = nil)
      order.associate_user!(user) if !order.user && !user.blank?
    end

    # The idea is the end developer can choose to override the merge
    # to their own choosing. Default is merge with errors.
    def handle_merge(current_line_item, other_order_line_item)
      if current_line_item
        current_line_item.quantity += other_order_line_item.quantity
        handle_error(current_line_item) unless current_line_item.save
      else
        other_order_line_item.order_id = order.id
        other_order_line_item.adjustments.update_all(order_id: order.id)
        handle_error(other_order_line_item) unless other_order_line_item.save
      end
    end

    # Change the error messages as you choose.
    def handle_error(line_item)
      order.errors.add(:base, line_item.errors.full_messages)
    end

    def persist_merge
      updater.update_item_count
      updater.update_item_total
      updater.persist_totals
    end
  end
end