mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-28 01:53:25 +00:00
Create new OrderFactory service object for initializing order from attr hash
This commit is contained in:
@@ -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
|
||||
|
||||
71
app/services/order_factory.rb
Normal file
71
app/services/order_factory.rb
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
81
spec/services/order_factory_spec.rb
Normal file
81
spec/services/order_factory_spec.rb
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user