diff --git a/app/models/proxy_order.rb b/app/models/proxy_order.rb index 63f7bc769d..b5b80c0c4c 100644 --- a/app/models/proxy_order.rb +++ b/app/models/proxy_order.rb @@ -42,21 +42,8 @@ class ProxyOrder < ActiveRecord::Base def initialise_order! return order if order.present? - create_order!( - customer_id: subscription.customer_id, - email: subscription.customer.email, - order_cycle_id: order_cycle_id, - distributor_id: subscription.shop_id, - shipping_method_id: subscription.shipping_method_id - ) - order.update_attribute(:user, subscription.customer.user) - subscription.subscription_line_items.each do |sli| - order.line_items.build(variant_id: sli.variant_id, quantity: sli.quantity, skip_stock_check: true) - end - order.update_attributes(bill_address: subscription.bill_address.dup, ship_address: subscription.ship_address.dup) - order.update_distribution_charge! - order.payments.create(payment_method_id: subscription.payment_method_id, amount: order.reload.total) - + factory = OrderFactory.new(order_attrs, skip_stock_check: true) + self.order = factory.create save! order end @@ -75,4 +62,16 @@ class ProxyOrder < ActiveRecord::Base order.andand.state == 'complete' && order_cycle.orders_close_at > Time.zone.now end + + def order_attrs + attrs = subscription.attributes.slice("customer_id", "payment_method_id", "shipping_method_id") + attrs[:distributor_id] = subscription.shop_id + attrs[:order_cycle_id] = order_cycle_id + attrs[:bill_address_attributes] = subscription.bill_address.attributes.except("id") + attrs[:ship_address_attributes] = subscription.ship_address.attributes.except("id") + attrs[:line_items] = subscription.subscription_line_items.map do |sli| + { variant_id: sli.variant_id, quantity: sli.quantity } + end + attrs + end end diff --git a/app/services/order_factory.rb b/app/services/order_factory.rb new file mode 100644 index 0000000000..df5ce3e50d --- /dev/null +++ b/app/services/order_factory.rb @@ -0,0 +1,71 @@ +# Builds orders based on a set of attributes +# There are some idiosyncracies in the order creation process, +# and it is nice to have them dealt with in one place. + +class OrderFactory + def initialize(attrs, opts = {}) + @attrs = attrs.with_indifferent_access + @opts = opts.with_indifferent_access + end + + def create + create_order + set_user + build_line_items + set_addresses + create_payment + @order + end + + private + + attr_reader :attrs, :opts + + def customer + @customer ||= Customer.find(attrs[:customer_id]) + end + + def create_order + @order = Spree::Order.create!(create_attrs) + end + + def create_attrs + create_attrs = attrs.slice(:customer_id, :order_cycle_id, :distributor_id, :shipping_method_id) + create_attrs[:email] = customer.email + create_attrs + end + + def build_line_items + attrs[:line_items].each do |li| + next unless variant = Spree::Variant.find_by_id(li[:variant_id]) + li[:quantity] = stock_limited_quantity(variant.on_hand, li[:quantity]) + build_item_from(li) + end + end + + def build_item_from(attrs) + @order.line_items.build( + variant_id: attrs[:variant_id], + quantity: attrs[:quantity], + skip_stock_check: opts[:skip_stock_check] + ) + end + + def set_user + @order.update_attribute(:user_id, customer.user_id) + end + + def set_addresses + @order.update_attributes(attrs.slice(:bill_address_attributes, :ship_address_attributes)) + end + + def create_payment + @order.update_distribution_charge! + @order.payments.create(payment_method_id: attrs[:payment_method_id], amount: @order.reload.total) + end + + def stock_limited_quantity(stock, requested) + return requested if opts[:skip_stock_check] + [stock, requested].min + end +end diff --git a/spec/models/proxy_order_spec.rb b/spec/models/proxy_order_spec.rb index c85733d5d3..67d4af87f2 100644 --- a/spec/models/proxy_order_spec.rb +++ b/spec/models/proxy_order_spec.rb @@ -158,66 +158,29 @@ describe ProxyOrder, type: :model do end describe "initialise_order!" do + let(:order) { create(:order) } + let(:factory) { instance_double(OrderFactory) } + let!(:proxy_order) { create(:proxy_order) } + context "when the order has not already been initialised" do - let(:subscription) { create(:subscription, with_items: true) } - let!(:proxy_order) { create(:proxy_order, subscription: subscription) } - - it "builds a new order based the subscription" do - expect{ proxy_order.initialise_order! }.to change{ Spree::Order.count }.by(1) - expect(proxy_order.reload.order).to be_a Spree::Order - order = proxy_order.order - expect(order.line_items.count).to eq subscription.subscription_line_items.count - expect(order.customer).to eq subscription.customer - expect(order.user).to eq subscription.customer.user - expect(order.distributor).to eq subscription.shop - expect(order.order_cycle).to eq proxy_order.order_cycle - expect(order.shipping_method).to eq subscription.shipping_method - expect(order.shipments.first.shipping_method).to eq subscription.shipping_method - expect(order.payments.first.payment_method).to eq subscription.payment_method - expect(order.bill_address).to eq subscription.bill_address - expect(order.ship_address).to eq subscription.ship_address - expect(order.complete?).to be false - end - - context "when a requested quantity is greater than available stock" do - let(:sli) { subscription.subscription_line_items.first } - let(:variant) { sli.variant } - - before do - variant.update_attribute(:count_on_hand, 2) - sli.update_attribute(:quantity, 5) - end - - it "initialises the order with the requested quantity regardless" do - expect{ proxy_order.initialise_order! }.to change{ Spree::Order.count }.by(1) - expect(proxy_order.reload.order).to be_a Spree::Order - order = proxy_order.order - expect(order.line_items.find_by_variant_id(variant.id).quantity).to eq 5 - end - end - - context "when the customer does not have a user associated with it" do - before do - subscription.customer.update_attribute(:user_id, nil) - end - - it "initialises the order without a user_id" do - expect{ proxy_order.initialise_order! }.to change{ Spree::Order.count }.by(1) - expect(proxy_order.reload.order).to be_a Spree::Order - order = proxy_order.order - expect(order.user).to be nil - end + it "creates a new order using the OrderFactory, and returns it" do + expect(OrderFactory).to receive(:new) { factory } + expect(factory).to receive(:create) { order } + expect(proxy_order.initialise_order!).to eq order end end context "when the order has already been initialised" do let(:existing_order) { create(:order) } - let!(:proxy_order) { create(:proxy_order, order: existing_order) } + + before do + proxy_order.update_attributes(order: existing_order) + end it "returns the existing order" do - expect do - expect(proxy_order.initialise_order!).to eq existing_order - end.to_not change{ Spree::Order.count } + expect(OrderFactory).to_not receive(:new) + expect(proxy_order).to_not receive(:save!) + expect(proxy_order.initialise_order!).to eq existing_order end end end diff --git a/spec/services/order_factory_spec.rb b/spec/services/order_factory_spec.rb new file mode 100644 index 0000000000..c8758c9f0c --- /dev/null +++ b/spec/services/order_factory_spec.rb @@ -0,0 +1,81 @@ +describe OrderFactory do + let(:variant1) { create(:variant) } + let(:variant2) { create(:variant) } + let(:user) { create(:user) } + let(:customer) { create(:customer, user: user) } + let(:shop) { create(:distributor_enterprise) } + let(:order_cycle) { create(:simple_order_cycle) } + let(:shipping_method) { create(:shipping_method) } + let(:payment_method) { create(:payment_method) } + let(:ship_address) { create(:address) } + let(:bill_address) { create(:address) } + let(:opts) { {} } + let(:factory) { OrderFactory.new(attrs, opts) } + let(:order) { factory.create } + + describe "create" do + let(:attrs) do + attrs = {} + attrs[:line_items] = [{ variant_id: variant1.id, quantity: 2 }, { variant_id: variant2.id, quantity: 4 }] + attrs[:customer_id] = customer.id + attrs[:distributor_id] = shop.id + attrs[:order_cycle_id] = order_cycle.id + attrs[:shipping_method_id] = shipping_method.id + attrs[:payment_method_id] = payment_method.id + attrs[:bill_address_attributes] = bill_address.attributes.except("id") + attrs[:ship_address_attributes] = ship_address.attributes.except("id") + attrs + end + + it "builds a new order based the provided attributes" do + expect{ order }.to change{ Spree::Order.count }.by(1) + expect(order).to be_a Spree::Order + expect(order.line_items.count).to eq 2 + expect(order.customer).to eq customer + expect(order.user).to eq user + expect(order.distributor).to eq shop + expect(order.order_cycle).to eq order_cycle + expect(order.shipping_method).to eq shipping_method + expect(order.shipments.reload.first.shipping_method).to eq shipping_method + expect(order.payments.first.payment_method).to eq payment_method + expect(order.bill_address).to eq bill_address + expect(order.ship_address).to eq ship_address + expect(order.complete?).to be false + end + + context "when the customer does not have a user associated with it" do + before { customer.update_attribute(:user_id, nil) } + + it "initialises the order without a user_id" do + expect{ order }.to change{ Spree::Order.count }.by(1) + expect(order).to be_a Spree::Order + expect(order.user).to be nil + end + end + + context "when requested quantity is greater than available stock" do + before do + variant1.update_attribute(:count_on_hand, 2) + attrs[:line_items].first[:quantity] = 5 + end + + context "when skip_stock_check is not requested" do + it "initialised the order but limits stock to the available amount" do + expect{ order }.to change{ Spree::Order.count }.by(1) + expect(order).to be_a Spree::Order + expect(order.line_items.find_by_variant_id(variant1.id).quantity).to eq 2 + end + end + + context "when skip_stock_check is requested" do + let(:opts) { { skip_stock_check: true } } + + it "initialises the order with the requested quantity regardless" do + expect{ order }.to change{ Spree::Order.count }.by(1) + expect(order).to be_a Spree::Order + expect(order.line_items.find_by_variant_id(variant1.id).quantity).to eq 5 + end + end + end + end +end