Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop setting line items with quantity of zero #5275

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/app/controllers/spree/api/line_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def create
variant = Spree::Variant.find(params[:line_item][:variant_id])
@line_item = @order.contents.add(
variant,
params[:line_item][:quantity] || 1,
params[:line_item][:quantity].presence || 1,
options: line_item_params[:options].to_h
)

Expand Down
16 changes: 15 additions & 1 deletion api/spec/requests/spree/api/line_items_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,27 @@ module Spree::Api
expect(response.status).to eq(201)
end

it "default quantity to 1 if none is given" do
it "sets default quantity to 1 if none is given" do
post spree.api_order_line_items_path(order), params: { line_item: { variant_id: product.master.to_param } }
expect(response.status).to eq(201)
expect(json_response).to have_attributes(attributes)
expect(json_response[:quantity]).to eq 1
end

it "sets default quantity to 1 if empty values are given" do
post spree.api_order_line_items_path(order), params: { line_item: { variant_id: product.master.to_param, quantity: '' } }
expect(response.status).to eq(201)
expect(json_response).to have_attributes(attributes)
expect(json_response[:quantity]).to eq 1
end

it "allows to explicitly set quantity to 0" do
post spree.api_order_line_items_path(order), params: { line_item: { variant_id: product.master.to_param, quantity: 0 } }
expect(response.status).to eq(201)
expect(json_response).to have_attributes(attributes)
expect(json_response[:quantity]).to eq 0
end

it "increases a line item's quantity if it exists already" do
order.line_items.create(variant_id: product.master.id, quantity: 10)
post spree.api_order_line_items_path(order), params: { line_item: { variant_id: product.master.to_param, quantity: 1 } }
Expand Down
17 changes: 16 additions & 1 deletion core/app/models/spree/order_contents.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ def remove_line_item(line_item, options = {})
end

def update_cart(params)
params = destroy_non_positive_line_items(params)

if order.update(params)
unless order.completed?
order.line_items = order.line_items.select { |li| li.quantity > 0 }
# Update totals, then check if the order is eligible for any cart promotions.
# If we do not update first, then the item total will be wrong and ItemTotal
# promotion rules would not be triggered.
Expand Down Expand Up @@ -83,6 +84,20 @@ def reload_totals
@order.recalculate
end

def destroy_non_positive_line_items(params)
line_items_to_destroy_ids = []
# Could be a single LineItem or multiple via nesting
if id = params[:line_items_attributes][:id].present?
line_items_to_destroy_ids << id if params[:line_items_attributes][:quantity].to_i <= 0
else
params[:line_items_attributes].reject! do |_index, attributes|
line_items_to_destroy_ids << attributes[:id] if attributes[:quantity].to_i <= 0
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which cases do we have this difference in passing the attributes?

Copy link
Contributor Author

@RyanofWoods RyanofWoods Jul 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean whether it's nested or not? Like so:

# Case 1:
line_items_attributes: {
  id: 1,
  quantity: 1
}

# Case 2:
line_items_attributes: {
  "01" => {
    id: 1,
    quantity: 1
  },
  "02" => {
    id: 3,
    quantity: 0
  }
}

I am supporting single nesting for Api::LineItemsController, though it seems that Rails supports these different levels by default - though I couldn't find any explicit documentation about it 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @RyanofWoods, I'm not able to detect any code in the Api::LineItemsController or its specs that seem to support this way of passing attributes. Can you please help me adding more details about your discovery?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case 1 is needed for Spree::Api::LineItemsController:

def line_items_attributes
{ line_items_attributes: {
id: params[:id],
quantity: params[:line_item][:quantity],
options: line_item_params[:options] || {}
} }
end

Case 2 was needed for how the OrderContents spec is setup.

Both these formats are supported by the nested_attributes in Rails.

end
order.line_items.where(id: line_items_to_destroy_ids).destroy_all
RyanofWoods marked this conversation as resolved.
Show resolved Hide resolved
params
end

def add_to_line_item(variant, quantity, options = {})
line_item = grab_line_item_by_variant(variant, false, options)

Expand Down
70 changes: 48 additions & 22 deletions core/spec/models/spree/order_contents_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,40 +263,66 @@
context "update cart" do
let!(:shirt) { subject.add variant, 1 }

let(:params) do
{ line_items_attributes: {
"0" => { id: shirt.id, quantity: 3 }
} }
end
context "with a multi-level nested params hash" do
let(:params) do
{ line_items_attributes: {
"0" => { id: shirt.id, quantity: 3 }
} }
end

it "changes item quantity" do
subject.update_cart params
expect(shirt.reload.quantity).to eq 3
end
it "changes item quantity" do
subject.update_cart params
expect(shirt.reload.quantity).to eq 3
end

it "updates order totals" do
expect {
it "updates order totals" do
expect {
subject.update_cart params
}.to change { subject.order.total }
end

context "submits item quantity 0" do
let(:params) do
{ line_items_attributes: {
"0" => { id: shirt.id, quantity: 0 }
} }
end

it "removes item from order" do
expect {
subject.update_cart params
}.to change { subject.order.line_items.count }.from(1).to(0)
end
end

it "ensures updated shipments" do
expect(subject.order).to receive(:check_shipments_and_restart_checkout)
subject.update_cart params
}.to change { subject.order.total }
end
end

context "submits item quantity 0" do
context "with a single-level nested params hash" do
let(:params) do
{ line_items_attributes: {
"0" => { id: shirt.id, quantity: 0 }
id: shirt.id, quantity: new_quantity
} }
end
let(:new_quantity) { 3 }

it "removes item from order" do
expect {
subject.update_cart params
}.to change { subject.order.line_items.count }
it "changes item quantity" do
subject.update_cart params
expect(shirt.reload.quantity).to eq 3
end
end

it "ensures updated shipments" do
expect(subject.order).to receive(:check_shipments_and_restart_checkout)
subject.update_cart params
context "submits item quantity 0" do
let(:new_quantity) { 0 }

it "removes item from order" do
expect {
subject.update_cart params
}.to change { subject.order.line_items.count }.from(1).to(0)
end
end
end
end

Expand Down