mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Moved Spree::OrderController.populate to new CartController.
This was done to make order populate independent of Spree::OrdersController
This commit is contained in:
@@ -41,7 +41,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
update: =>
|
||||
@update_running = true
|
||||
|
||||
$http.post('/orders/populate', @data()).success (data, status)=>
|
||||
$http.post('/cart/populate', @data()).success (data, status)=>
|
||||
@saved()
|
||||
@update_running = false
|
||||
|
||||
|
||||
96
app/controllers/cart_controller.rb
Normal file
96
app/controllers/cart_controller.rb
Normal file
@@ -0,0 +1,96 @@
|
||||
require 'spree/core/controller_helpers/order_decorator'
|
||||
|
||||
class CartController < BaseController
|
||||
before_filter :check_authorization
|
||||
|
||||
def populate
|
||||
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
|
||||
# during cart population, for both taxation and enterprise fees. This operation triggers a
|
||||
# costly Spree::Order#update!, which only needs to be run once. We avoid this by disabling
|
||||
# callbacks on Spree::Adjustment and then manually invoke Spree::Order#update! on success.
|
||||
Spree::Adjustment.without_callbacks do
|
||||
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
|
||||
|
||||
if populator.populate(params.slice(:products, :variants, :quantity), true)
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
|
||||
current_order.cap_quantity_at_stock!
|
||||
current_order.update!
|
||||
|
||||
variant_ids = variant_ids_in(populator.variants_h)
|
||||
|
||||
render json: { error: false, stock_levels: stock_levels(current_order, variant_ids) },
|
||||
status: 200
|
||||
|
||||
else
|
||||
render json: { error: true }, status: 412
|
||||
end
|
||||
end
|
||||
populate_variant_attributes
|
||||
end
|
||||
|
||||
# Report the stock levels in the order for all variant ids requested
|
||||
def stock_levels(order, variant_ids)
|
||||
stock_levels = li_stock_levels(order)
|
||||
|
||||
li_variant_ids = stock_levels.keys
|
||||
(variant_ids - li_variant_ids).each do |variant_id|
|
||||
stock_levels[variant_id] = { quantity: 0, max_quantity: 0, on_hand: Spree::Variant.find(variant_id).on_hand }
|
||||
end
|
||||
|
||||
stock_levels
|
||||
end
|
||||
|
||||
def variant_ids_in(variants_h)
|
||||
variants_h.map { |v| v[:variant_id].to_i }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
session[:access_token] ||= params[:token]
|
||||
order = Spree::Order.find_by_number(params[:id]) || current_order
|
||||
|
||||
if order
|
||||
authorize! :edit, order, session[:access_token]
|
||||
else
|
||||
authorize! :create, Spree::Order
|
||||
end
|
||||
end
|
||||
|
||||
def populate_variant_attributes
|
||||
order = current_order.reload
|
||||
|
||||
if params.key? :variant_attributes
|
||||
params[:variant_attributes].each do |variant_id, attributes|
|
||||
order.set_variant_attributes(Spree::Variant.find(variant_id), attributes)
|
||||
end
|
||||
end
|
||||
|
||||
if params.key? :quantity
|
||||
params[:products].each do |_product_id, variant_id|
|
||||
max_quantity = params[:max_quantity].to_i
|
||||
order.set_variant_attributes(Spree::Variant.find(variant_id),
|
||||
max_quantity: max_quantity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def li_stock_levels(order)
|
||||
Hash[
|
||||
order.line_items.map do |li|
|
||||
[li.variant.id,
|
||||
{ quantity: li.quantity,
|
||||
max_quantity: li.max_quantity,
|
||||
on_hand: wrap_json_infinity(li.variant.on_hand) }]
|
||||
end
|
||||
]
|
||||
end
|
||||
|
||||
# Rails to_json encodes Float::INFINITY as Infinity, which is not valid JSON
|
||||
# Return it as a large integer (max 32 bit signed int)
|
||||
def wrap_json_infinity(number)
|
||||
number == Float::INFINITY ? 2147483647 : number
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,6 @@
|
||||
require 'spree/core/controller_helpers/order_decorator'
|
||||
|
||||
Spree::OrdersController.class_eval do
|
||||
after_filter :populate_variant_attributes, only: :populate
|
||||
before_filter :update_distribution, only: :update
|
||||
before_filter :filter_order_params, only: :update
|
||||
before_filter :enable_embedded_shopfront
|
||||
@@ -71,61 +70,6 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
def populate
|
||||
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
|
||||
# during cart population, for both taxation and enterprise fees. This operation triggers a
|
||||
# costly Spree::Order#update!, which only needs to be run once. We avoid this by disabling
|
||||
# callbacks on Spree::Adjustment and then manually invoke Spree::Order#update! on success.
|
||||
|
||||
Spree::Adjustment.without_callbacks do
|
||||
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
|
||||
|
||||
if populator.populate(params.slice(:products, :variants, :quantity), true)
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
|
||||
current_order.cap_quantity_at_stock!
|
||||
current_order.update!
|
||||
|
||||
variant_ids = variant_ids_in(populator.variants_h)
|
||||
|
||||
render json: {error: false, stock_levels: stock_levels(current_order, variant_ids)},
|
||||
status: 200
|
||||
|
||||
else
|
||||
render json: {error: true}, status: 412
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Report the stock levels in the order for all variant ids requested
|
||||
def stock_levels(order, variant_ids)
|
||||
stock_levels = li_stock_levels(order)
|
||||
|
||||
li_variant_ids = stock_levels.keys
|
||||
(variant_ids - li_variant_ids).each do |variant_id|
|
||||
stock_levels[variant_id] = {quantity: 0, max_quantity: 0,
|
||||
on_hand: Spree::Variant.find(variant_id).on_hand}
|
||||
end
|
||||
|
||||
stock_levels
|
||||
end
|
||||
|
||||
def variant_ids_in(variants_h)
|
||||
variants_h.map { |v| v[:variant_id].to_i }
|
||||
end
|
||||
|
||||
def li_stock_levels(order)
|
||||
Hash[
|
||||
order.line_items.map do |li|
|
||||
[li.variant.id,
|
||||
{quantity: li.quantity,
|
||||
max_quantity: li.max_quantity,
|
||||
on_hand: wrap_json_infinity(li.variant.on_hand)}]
|
||||
end
|
||||
]
|
||||
end
|
||||
|
||||
def update_distribution
|
||||
@order = current_order(true)
|
||||
|
||||
@@ -184,30 +128,6 @@ Spree::OrdersController.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def populate_variant_attributes
|
||||
order = current_order.reload
|
||||
|
||||
if params.key? :variant_attributes
|
||||
params[:variant_attributes].each do |variant_id, attributes|
|
||||
order.set_variant_attributes(Spree::Variant.find(variant_id), attributes)
|
||||
end
|
||||
end
|
||||
|
||||
if params.key? :quantity
|
||||
params[:products].each do |product_id, variant_id|
|
||||
max_quantity = params[:max_quantity].to_i
|
||||
order.set_variant_attributes(Spree::Variant.find(variant_id),
|
||||
{:max_quantity => max_quantity})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Rails to_json encodes Float::INFINITY as Infinity, which is not valid JSON
|
||||
# Return it as a large integer (max 32 bit signed int)
|
||||
def wrap_json_infinity(n)
|
||||
n == Float::INFINITY ? 2147483647 : n
|
||||
end
|
||||
|
||||
def order_to_update
|
||||
return @order_to_update if defined? @order_to_update
|
||||
return @order_to_update = current_order unless params[:id]
|
||||
|
||||
@@ -24,6 +24,14 @@ Openfoodnetwork::Application.routes.draw do
|
||||
get "/connect", to: redirect("https://openfoodnetwork.org/#{ENV['DEFAULT_COUNTRY_CODE'].andand.downcase}/connect/")
|
||||
get "/learn", to: redirect("https://openfoodnetwork.org/#{ENV['DEFAULT_COUNTRY_CODE'].andand.downcase}/learn/")
|
||||
|
||||
get "/cart", :to => "spree/orders#edit", :as => :cart
|
||||
put "/cart", :to => "spree/orders#update", :as => :update_cart
|
||||
put "/cart/empty", :to => 'spree/orders#empty', :as => :empty_cart
|
||||
|
||||
resource :cart, controller: "cart" do
|
||||
post :populate
|
||||
end
|
||||
|
||||
resource :shop, controller: "shop" do
|
||||
get :products
|
||||
post :order_cycle
|
||||
|
||||
104
spec/controllers/cart_controller_spec.rb
Normal file
104
spec/controllers/cart_controller_spec.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe CartController, type: :controller do
|
||||
let(:order) { create(:order) }
|
||||
|
||||
describe "returning stock levels in JSON on success" do
|
||||
let(:product) { create(:simple_product) }
|
||||
|
||||
it "returns stock levels as JSON" do
|
||||
controller.stub(:variant_ids_in) { [123] }
|
||||
controller.stub(:stock_levels) { 'my_stock_levels' }
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate) { true }
|
||||
populator.stub(:variants_h) { {} }
|
||||
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
|
||||
data = JSON.parse(response.body)
|
||||
data['stock_levels'].should == 'my_stock_levels'
|
||||
end
|
||||
|
||||
describe "generating stock levels" do
|
||||
let!(:order) { create(:order) }
|
||||
let!(:li) { create(:line_item, order: order, variant: v, quantity: 2, max_quantity: 3) }
|
||||
let!(:v) { create(:variant, count_on_hand: 4) }
|
||||
let!(:v2) { create(:variant, count_on_hand: 2) }
|
||||
|
||||
before do
|
||||
order.reload
|
||||
controller.stub(:current_order) { order }
|
||||
end
|
||||
|
||||
it "returns a hash with variant id, quantity, max_quantity and stock on hand" do
|
||||
controller.stock_levels(order, [v.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4}}
|
||||
end
|
||||
|
||||
it "includes all line items, even when the variant_id is not specified" do
|
||||
controller.stock_levels(order, []).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4}}
|
||||
end
|
||||
|
||||
it "includes an empty quantity entry for variants that aren't in the order" do
|
||||
controller.stock_levels(order, [v.id, v2.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4},
|
||||
v2.id => {quantity: 0, max_quantity: 0, on_hand: 2}}
|
||||
end
|
||||
|
||||
describe "encoding Infinity" do
|
||||
let!(:v) { create(:variant, on_demand: true, count_on_hand: 0) }
|
||||
|
||||
it "encodes Infinity as a large, finite integer" do
|
||||
controller.stock_levels(order, [v.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 2147483647}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "extracts variant ids from the populator" do
|
||||
variants_h = [{:variant_id=>"900", :quantity=>2, :max_quantity=>nil},
|
||||
{:variant_id=>"940", :quantity=>3, :max_quantity=>3}]
|
||||
|
||||
controller.variant_ids_in(variants_h).should == [900, 940]
|
||||
end
|
||||
end
|
||||
|
||||
context "adding a group buy product to the cart" do
|
||||
it "sets a variant attribute for the max quantity" do
|
||||
distributor_product = create(:distributor_enterprise)
|
||||
p = create(:product, :distributors => [distributor_product], :group_buy => true)
|
||||
|
||||
order = subject.current_order(true)
|
||||
order.stub(:distributor) { distributor_product }
|
||||
order.should_receive(:set_variant_attributes).with(p.master, {'max_quantity' => '3'})
|
||||
controller.stub(:current_order).and_return(order)
|
||||
|
||||
expect do
|
||||
spree_post :populate, :variants => {p.master.id => 1}, :variant_attributes => {p.master.id => {:max_quantity => 3}}
|
||||
end.to change(Spree::LineItem, :count).by(1)
|
||||
end
|
||||
|
||||
it "returns HTTP success when successful" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate) { true }
|
||||
populator.stub(:variants_h) { {} }
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
response.status.should == 200
|
||||
end
|
||||
|
||||
it "returns failure when unsuccessful" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate).and_return false
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
response.status.should == 412
|
||||
end
|
||||
|
||||
it "tells populator to overwrite" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.should_receive(:populate).with({}, true)
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,104 +63,6 @@ describe Spree::OrdersController, type: :controller do
|
||||
end
|
||||
end
|
||||
|
||||
describe "returning stock levels in JSON on success" do
|
||||
let(:product) { create(:simple_product) }
|
||||
|
||||
it "returns stock levels as JSON" do
|
||||
controller.stub(:variant_ids_in) { [123] }
|
||||
controller.stub(:stock_levels) { 'my_stock_levels' }
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate) { true }
|
||||
populator.stub(:variants_h) { {} }
|
||||
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
|
||||
data = JSON.parse(response.body)
|
||||
data['stock_levels'].should == 'my_stock_levels'
|
||||
end
|
||||
|
||||
describe "generating stock levels" do
|
||||
let!(:order) { create(:order) }
|
||||
let!(:li) { create(:line_item, order: order, variant: v, quantity: 2, max_quantity: 3) }
|
||||
let!(:v) { create(:variant, count_on_hand: 4) }
|
||||
let!(:v2) { create(:variant, count_on_hand: 2) }
|
||||
|
||||
before do
|
||||
order.reload
|
||||
controller.stub(:current_order) { order }
|
||||
end
|
||||
|
||||
it "returns a hash with variant id, quantity, max_quantity and stock on hand" do
|
||||
controller.stock_levels(order, [v.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4}}
|
||||
end
|
||||
|
||||
it "includes all line items, even when the variant_id is not specified" do
|
||||
controller.stock_levels(order, []).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4}}
|
||||
end
|
||||
|
||||
it "includes an empty quantity entry for variants that aren't in the order" do
|
||||
controller.stock_levels(order, [v.id, v2.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4},
|
||||
v2.id => {quantity: 0, max_quantity: 0, on_hand: 2}}
|
||||
end
|
||||
|
||||
describe "encoding Infinity" do
|
||||
let!(:v) { create(:variant, on_demand: true, count_on_hand: 0) }
|
||||
|
||||
it "encodes Infinity as a large, finite integer" do
|
||||
controller.stock_levels(order, [v.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 2147483647}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "extracts variant ids from the populator" do
|
||||
variants_h = [{:variant_id=>"900", :quantity=>2, :max_quantity=>nil},
|
||||
{:variant_id=>"940", :quantity=>3, :max_quantity=>3}]
|
||||
|
||||
controller.variant_ids_in(variants_h).should == [900, 940]
|
||||
end
|
||||
end
|
||||
|
||||
context "adding a group buy product to the cart" do
|
||||
it "sets a variant attribute for the max quantity" do
|
||||
distributor_product = create(:distributor_enterprise)
|
||||
p = create(:product, :distributors => [distributor_product], :group_buy => true)
|
||||
|
||||
order = subject.current_order(true)
|
||||
order.stub(:distributor) { distributor_product }
|
||||
order.should_receive(:set_variant_attributes).with(p.master, {'max_quantity' => '3'})
|
||||
controller.stub(:current_order).and_return(order)
|
||||
|
||||
expect do
|
||||
spree_post :populate, :variants => {p.master.id => 1}, :variant_attributes => {p.master.id => {:max_quantity => 3}}
|
||||
end.to change(Spree::LineItem, :count).by(1)
|
||||
end
|
||||
|
||||
it "returns HTTP success when successful" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate) { true }
|
||||
populator.stub(:variants_h) { {} }
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
response.status.should == 200
|
||||
end
|
||||
|
||||
it "returns failure when unsuccessful" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate).and_return false
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
response.status.should == 412
|
||||
end
|
||||
|
||||
it "tells populator to overwrite" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.should_receive(:populate).with({}, true)
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
describe "removing line items from cart" do
|
||||
describe "when I pass params that includes a line item no longer in our cart" do
|
||||
it "should silently ignore the missing line item" do
|
||||
|
||||
@@ -80,7 +80,7 @@ describe 'Cart service', ->
|
||||
data = {variants: {}}
|
||||
|
||||
it "sets update_running during the update, and clears it on success", ->
|
||||
$httpBackend.expectPOST("/orders/populate", data).respond 200, {}
|
||||
$httpBackend.expectPOST("/cart/populate", data).respond 200, {}
|
||||
expect(Cart.update_running).toBe(false)
|
||||
Cart.update()
|
||||
expect(Cart.update_running).toBe(true)
|
||||
@@ -88,7 +88,7 @@ describe 'Cart service', ->
|
||||
expect(Cart.update_running).toBe(false)
|
||||
|
||||
it "sets update_running during the update, and clears it on failure", ->
|
||||
$httpBackend.expectPOST("/orders/populate", data).respond 404, {}
|
||||
$httpBackend.expectPOST("/cart/populate", data).respond 404, {}
|
||||
expect(Cart.update_running).toBe(false)
|
||||
Cart.update()
|
||||
expect(Cart.update_running).toBe(true)
|
||||
@@ -97,7 +97,7 @@ describe 'Cart service', ->
|
||||
|
||||
it "marks the form as saved on success", ->
|
||||
spyOn(Cart, 'saved')
|
||||
$httpBackend.expectPOST("/orders/populate", data).respond 200, {}
|
||||
$httpBackend.expectPOST("/cart/populate", data).respond 200, {}
|
||||
Cart.update()
|
||||
$httpBackend.flush()
|
||||
expect(Cart.saved).toHaveBeenCalled()
|
||||
@@ -106,7 +106,7 @@ describe 'Cart service', ->
|
||||
Cart.update_enqueued = true
|
||||
spyOn(Cart, 'saved')
|
||||
spyOn(Cart, 'popQueue')
|
||||
$httpBackend.expectPOST("/orders/populate", data).respond 200, {}
|
||||
$httpBackend.expectPOST("/cart/populate", data).respond 200, {}
|
||||
Cart.update()
|
||||
$httpBackend.flush()
|
||||
expect(Cart.popQueue).toHaveBeenCalled()
|
||||
@@ -115,14 +115,14 @@ describe 'Cart service', ->
|
||||
Cart.update_enqueued = false
|
||||
spyOn(Cart, 'saved')
|
||||
spyOn(Cart, 'popQueue')
|
||||
$httpBackend.expectPOST("/orders/populate", data).respond 200, {}
|
||||
$httpBackend.expectPOST("/cart/populate", data).respond 200, {}
|
||||
Cart.update()
|
||||
$httpBackend.flush()
|
||||
expect(Cart.popQueue).not.toHaveBeenCalled()
|
||||
|
||||
it "retries the update on failure", ->
|
||||
spyOn(Cart, 'scheduleRetry')
|
||||
$httpBackend.expectPOST("/orders/populate", data).respond 404, {}
|
||||
$httpBackend.expectPOST("/cart/populate", data).respond 404, {}
|
||||
Cart.update()
|
||||
$httpBackend.flush()
|
||||
expect(Cart.scheduleRetry).toHaveBeenCalled()
|
||||
|
||||
Reference in New Issue
Block a user