diff --git a/app/assets/javascripts/admin/spree/orders/variant_autocomplete.js.erb b/app/assets/javascripts/admin/spree/orders/variant_autocomplete.js.erb index 23ba80d3fb..ea46231ece 100644 --- a/app/assets/javascripts/admin/spree/orders/variant_autocomplete.js.erb +++ b/app/assets/javascripts/admin/spree/orders/variant_autocomplete.js.erb @@ -4,6 +4,7 @@ $(document).ready(function() { initAlert() initConfirm() + initCancelOrder() if ($('#variant_autocomplete_template').length > 0) { window.variantTemplate = Handlebars.compile($('#variant_autocomplete_template').text()); @@ -52,12 +53,12 @@ $(document).ready(function() { } toggleItemEdit(); - adjustItems(shipment_number, variant_id, quantity); + adjustItems(shipment_number, variant_id, quantity, true); return false; } $('a.save-item').click(handle_save_click); - handle_delete_click = function(elementSelector){ + handle_delete_click = function(elementSelector, restock_item){ var del = $(elementSelector); del.hide() var shipment_number = del.data('shipment-number'); @@ -65,26 +66,37 @@ $(document).ready(function() { toggleItemEdit(); - adjustItems(shipment_number, variant_id, 0); + adjustItems(shipment_number, variant_id, 0, restock_item); } $('a.delete-item').click((event) => { - ofnConfirm(() => { - handle_delete_click('#custom-confirm'); - }); + try { + var del = $('a.delete-item'); + var shipment_number = del.data('shipment-number'); + var variant_id = del.data('variant-id'); + var shipment = _.findWhere(shipments, {number: shipment_number + ''}); + var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id}); + if (inventory_units.length !== shipment.inventory_units.length) { + ofnConfirm((reStockItem) => { + handle_delete_click('#custom-confirm', reStockItem); + }); + } else { + adjustItems(shipment_number, variant_id, 0); + } + } catch (e) { + } }); - } }); -adjustItems = function(shipment_number, variant_id, quantity){ +adjustItems = function(shipment_number, variant_id, quantity, restock_item){ var shipment = _.findWhere(shipments, {number: shipment_number + ''}); var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id}); - if (quantity == 0 && inventory_units.length == shipment.inventory_units.length) { - ofnCancelOrderAlert((confirm, sendEmailCancellation) => { + if (quantity === 0 && inventory_units.length === shipment.inventory_units.length) { + ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_item) => { if (confirm) { - doAdjustItems(shipment_number, variant_id, quantity, inventory_units, () => { + doAdjustItems(shipment_number, variant_id, quantity, inventory_units, restock_item, () => { var redirectTo = new URL(Spree.routes.cancel_order.toString()); redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation); window.location.href = redirectTo.toString(); @@ -93,23 +105,26 @@ adjustItems = function(shipment_number, variant_id, quantity){ }); return; } - doAdjustItems(shipment_number, variant_id, quantity, inventory_units, () => { + doAdjustItems(shipment_number, variant_id, quantity, inventory_units, restock_item, () => { window.location.reload(); }); } -doAdjustItems = function(shipment_number, variant_id, quantity, inventory_units, callback) { +doAdjustItems = function(shipment_number, variant_id, quantity, inventory_units, restock_item, callback) { var url = Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number; var new_quantity = 0; + var data = { variant_id: variant_id }; if (inventory_units.length < quantity) { url += "/add"; new_quantity = (quantity - inventory_units.length); } else if (inventory_units.length > quantity) { url += "/remove" new_quantity = (inventory_units.length - quantity); + data.restock_item = restock_item; } url += '.json'; + data.quantity = new_quantity; if (new_quantity == 0) { ofnAlert(t("js.admin.orders.quantity_unchanged")); @@ -117,7 +132,7 @@ doAdjustItems = function(shipment_number, variant_id, quantity, inventory_units, $.ajax({ type: "PUT", url: Spree.url(url), - data: { variant_id: variant_id, quantity: new_quantity } + data: data }).done(function( msg ) { callback(); }); @@ -171,7 +186,7 @@ addVariantFromStockLocation = function() { }); }else{ //add to existing shipment - adjustItems(shipment.number, variant_id, quantity); + adjustItems(shipment.number, variant_id, quantity, true); } return 1 } @@ -202,11 +217,13 @@ ofnCancelOrderAlert = function(callback, i18nKey) { ` ${t(i18nKey)}
- +
+ +
`); $('#custom-confirm button.confirm').unbind( "click" ).click(() => { $('#custom-confirm').hide(); - callback(true, $('#send_cancellation_email').is(':checked')); + callback(true, $('#send_cancellation_email').is(':checked'), $('#restock_items').is(':checked')); }); $('#custom-confirm button.cancel').click(() => { $('#custom-confirm').hide(); @@ -216,7 +233,30 @@ ofnCancelOrderAlert = function(callback, i18nKey) { } ofnConfirm = function(callback) { + $('#custom-confirm .message').html( + ` ${t("are_you_sure")} +
+ + +
`); $('#custom-confirm').data($(event.target).data()); - $('#custom-confirm button.confirm').click(callback); + $('#custom-confirm button.confirm').click(() => { + callback($('#restock_items').is(':checked')); + }); $('#custom-confirm').show(); } + +initCancelOrder = function() { + $('#cancel_order_form').submit(function(e){ + ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) => { + if (confirm) { + var redirectTo = new URL(Spree.routes.cancel_order.toString()); + redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation); + redirectTo.searchParams.append("restock_items", restock_items); + window.location.href = redirectTo.toString(); + } + }); + e.preventDefault(); + return false; + }); +} diff --git a/app/controllers/api/v0/shipments_controller.rb b/app/controllers/api/v0/shipments_controller.rb index cd0f31dcd3..af59069627 100644 --- a/app/controllers/api/v0/shipments_controller.rb +++ b/app/controllers/api/v0/shipments_controller.rb @@ -79,8 +79,9 @@ module Api def remove variant = scoped_variant(params[:variant_id]) quantity = params[:quantity].to_i + restock_item = params.fetch(:restock_item, "true") == "true" - @order.contents.remove(variant, quantity, @shipment) + @order.contents.remove(variant, quantity, @shipment, restock_item) @shipment.reload if @shipment.persisted? render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok diff --git a/app/controllers/spree/admin/orders_controller.rb b/app/controllers/spree/admin/orders_controller.rb index baa7b169b4..ccce49e27b 100644 --- a/app/controllers/spree/admin/orders_controller.rb +++ b/app/controllers/spree/admin/orders_controller.rb @@ -66,6 +66,8 @@ module Spree def fire event = params[:e] @order.send_cancellation_email = params[:send_cancellation_email] != "false" + @order.restock_items = params.fetch(:restock_items, "true") == "true" + if @order.public_send(event.to_s) flash[:success] = Spree.t(:order_updated) else diff --git a/app/helpers/spree/admin/navigation_helper.rb b/app/helpers/spree/admin/navigation_helper.rb index 24c33e4996..324e056330 100644 --- a/app/helpers/spree/admin/navigation_helper.rb +++ b/app/helpers/spree/admin/navigation_helper.rb @@ -115,7 +115,7 @@ module Spree if html_options[:method] && html_options[:method].to_s.downcase != 'get' && !html_options[:remote] - form_tag(url, method: html_options.delete(:method)) do + form_tag(url, method: html_options.delete(:method), id: html_options.delete(:form_id)) do button(text, html_options.delete(:icon), nil, html_options) end else diff --git a/app/helpers/spree/admin/orders_helper.rb b/app/helpers/spree/admin/orders_helper.rb index 02384b3ecd..d70560abad 100644 --- a/app/helpers/spree/admin/orders_helper.rb +++ b/app/helpers/spree/admin/orders_helper.rb @@ -5,8 +5,8 @@ module Spree module OrdersHelper def event_links links = [] - links << event_link("cancel") if @order.can_cancel? - links << event_link("resume") if @order.can_resume? + links << cancel_event_link if @order.can_cancel? + links << resume_event_link if @order.can_resume? links.join(' ').html_safe end @@ -114,12 +114,19 @@ module Spree confirm: t(:are_you_sure) } end - def event_link(event) - event_label = I18n.t(event, scope: "actions") + def cancel_event_link + event_label = I18n.t("cancel", scope: "actions") + button_link_to(event_label, + fire_admin_order_url(@order, e: "cancel"), + method: :put, icon: "icon-cancel", form_id: "cancel_order_form") + end + + def resume_event_link + event_label = I18n.t("resume", scope: "actions") confirm_message = I18n.t("admin.orders.edit.order_sure_want_to", event: event_label) button_link_to(event_label, - fire_admin_order_url(@order, e: event), - method: :put, icon: "icon-#{event}", + fire_admin_order_url(@order, e: "resume"), + method: :put, icon: "icon-resume", data: { confirm: confirm_message }) end diff --git a/app/models/spree/line_item.rb b/app/models/spree/line_item.rb index 4b698f3dbd..b1eb094558 100644 --- a/app/models/spree/line_item.rb +++ b/app/models/spree/line_item.rb @@ -50,6 +50,8 @@ module Spree attr_accessor :skip_stock_check, :target_shipment # Allows manual skipping of Stock::AvailabilityValidator + attribute :restock_item, type: :boolean, default: true + # -- Scopes scope :managed_by, lambda { |user| if user.has_spree_role?('admin') diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index e401dda2f6..b4b2c3bf75 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -106,6 +106,7 @@ module Spree after_save_commit DefaultAddressUpdater attribute :send_cancellation_email, type: :boolean, default: true + attribute :restock_items, type: :boolean, default: true # -- Scopes scope :not_empty, -> { left_outer_joins(:line_items).where.not(spree_line_items: { id: nil }) diff --git a/app/models/spree/order_contents.rb b/app/models/spree/order_contents.rb index 62978b2488..848906e82c 100644 --- a/app/models/spree/order_contents.rb +++ b/app/models/spree/order_contents.rb @@ -19,8 +19,8 @@ module Spree # Get current line item for variant # Remove variant qty from line_item - def remove(variant, quantity = nil, shipment = nil) - line_item = remove_from_line_item(variant, quantity, shipment) + def remove(variant, quantity = nil, shipment = nil, restock_item = true) + line_item = remove_from_line_item(variant, quantity, shipment, restock_item) update_shipment(shipment) order.update_order_fees! if order.completed? update_order @@ -97,9 +97,9 @@ module Spree line_item end - def remove_from_line_item(variant, quantity, shipment = nil) + def remove_from_line_item(variant, quantity, shipment = nil, restock_item = true) line_item = find_line_item_by_variant(variant, true) - + line_item.restock_item = restock_item quantity.present? ? line_item.quantity += -quantity : line_item.quantity = 0 line_item.target_shipment = shipment diff --git a/app/models/spree/order_inventory.rb b/app/models/spree/order_inventory.rb index 44ff226609..f1b468f128 100644 --- a/app/models/spree/order_inventory.rb +++ b/app/models/spree/order_inventory.rb @@ -44,12 +44,15 @@ module Spree quantity = variant_units.size - line_item.quantity if shipment.present? - remove_from_shipment(shipment, line_item.variant, quantity) + remove_from_shipment(shipment, line_item.variant, quantity, line_item.restock_item) else order.shipments.each do |each_shipment| break if quantity == 0 - quantity -= remove_from_shipment(each_shipment, line_item.variant, quantity) + quantity -= remove_from_shipment(each_shipment, + line_item.variant, + quantity, + line_item.restock_item) end end end @@ -83,7 +86,7 @@ module Spree quantity end - def remove_from_shipment(shipment, variant, quantity) + def remove_from_shipment(shipment, variant, quantity, restock_item) return 0 if quantity == 0 || shipment.shipped? shipment_units = shipment.inventory_units_for(variant).reject do |variant_unit| @@ -101,7 +104,7 @@ module Spree shipment.destroy if shipment.inventory_units.reload.count == 0 # removing this from shipment, and adding to stock_location - if order.completed? + if order.completed? && restock_item shipment.stock_location.restock variant, removed_quantity, shipment end diff --git a/app/models/spree/shipment.rb b/app/models/spree/shipment.rb index d03f63b20d..8ab08a51ac 100644 --- a/app/models/spree/shipment.rb +++ b/app/models/spree/shipment.rb @@ -192,7 +192,7 @@ module Spree end def after_cancel - manifest.each { |item| manifest_restock(item) } + manifest.each { |item| manifest_restock(item) } if order.restock_items end def after_resume diff --git a/config/locales/en.yml b/config/locales/en.yml index 854406c54d..1c555d3b09 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3083,6 +3083,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using quantity_unchanged: "Quantity unchanged from previous amount." cancel_the_order_html: "This will cancel the current order.
Are you sure you want to proceed?" cancel_the_order_send_cancelation_email: "Send a cancellation email to the customer" + restock_item: "Restock Items: return this item to stock" + restock_items: "Restock Items: return all items to stock" resend_user_email_confirmation: resend: "Resend" sending: "Resend..." diff --git a/spec/controllers/api/v0/shipments_controller_spec.rb b/spec/controllers/api/v0/shipments_controller_spec.rb index 77b6775cd4..233a13560e 100644 --- a/spec/controllers/api/v0/shipments_controller_spec.rb +++ b/spec/controllers/api/v0/shipments_controller_spec.rb @@ -153,6 +153,13 @@ describe Api::V0::ShipmentsController, type: :controller do api_put :remove, params.merge(variant_id: existing_variant.to_param) }.to change { existing_variant.reload.on_hand }.by(2) end + + it 'does not adjust stock when removing a variant' do + expect { + api_put :remove, params.merge(variant_id: existing_variant.to_param, + restock_item: 'false') + }.to change { existing_variant.reload.on_hand }.by(0) + end end context "for canceled orders" do diff --git a/spec/models/spree/order_inventory_spec.rb b/spec/models/spree/order_inventory_spec.rb index 09de81a866..bc8a035d53 100644 --- a/spec/models/spree/order_inventory_spec.rb +++ b/spec/models/spree/order_inventory_spec.rb @@ -89,12 +89,21 @@ describe Spree::OrderInventory do it "doesn't restock items" do expect(shipment.stock_location).not_to receive(:restock) - expect(subject.send(:remove_from_shipment, shipment, variant, 1)).to eq 1 + expect(subject.send(:remove_from_shipment, shipment, variant, 1, true)).to eq 1 + end + end + + context "order is completed" do + before { allow(order).to receive_messages completed?: true } + + it "doesn't restock items" do + expect(shipment.stock_location).not_to receive(:restock) + expect(subject.send(:remove_from_shipment, shipment, variant, 1, false)).to eq 1 end end it 'should create stock_movement' do - expect(subject.send(:remove_from_shipment, shipment, variant, 1)).to eq 1 + expect(subject.send(:remove_from_shipment, shipment, variant, 1, true)).to eq 1 stock_item = shipment.stock_location.stock_item(variant) movement = stock_item.stock_movements.last @@ -112,7 +121,7 @@ describe Spree::OrderInventory do expect(shipment.inventory_units_for[1]).not_to receive(:destroy) expect(shipment.inventory_units_for[2]).to receive(:destroy) - expect(subject.send(:remove_from_shipment, shipment, variant, 2)).to eq 2 + expect(subject.send(:remove_from_shipment, shipment, variant, 2, true)).to eq 2 end it 'should destroy unshipped units first' do @@ -123,7 +132,7 @@ describe Spree::OrderInventory do expect(shipment.inventory_units_for[0]).not_to receive(:destroy) expect(shipment.inventory_units_for[1]).to receive(:destroy) - expect(subject.send(:remove_from_shipment, shipment, variant, 1)).to eq 1 + expect(subject.send(:remove_from_shipment, shipment, variant, 1, true)).to eq 1 end it 'only attempts to destroy as many units as are eligible, and return amount destroyed' do @@ -134,14 +143,14 @@ describe Spree::OrderInventory do expect(shipment.inventory_units_for[0]).not_to receive(:destroy) expect(shipment.inventory_units_for[1]).to receive(:destroy) - expect(subject.send(:remove_from_shipment, shipment, variant, 1)).to eq 1 + expect(subject.send(:remove_from_shipment, shipment, variant, 1, true)).to eq 1 end it 'should destroy self if not inventory units remain' do allow(shipment.inventory_units).to receive_messages(count: 0) expect(shipment).to receive(:destroy) - expect(subject.send(:remove_from_shipment, shipment, variant, 1)).to eq 1 + expect(subject.send(:remove_from_shipment, shipment, variant, 1, true)).to eq 1 end end end diff --git a/spec/system/admin/order_spec.rb b/spec/system/admin/order_spec.rb index 35b4c26f1b..c4cbfc125e 100644 --- a/spec/system/admin/order_spec.rb +++ b/spec/system/admin/order_spec.rb @@ -163,13 +163,9 @@ describe ' order.line_items = [order.line_items.first] login_as_admin_and_visit spree.edit_admin_order_path(order) find("a.delete-item").click - within(".modal", visible: true) do - # ignore first modal by confirming it - click_on("OK") - end end - context "it shows a second modal about last item deletion and therefore about order cancellation" do + context "it shows a modal about last item deletion and therefore about order cancellation" do it "that the user can close and then nothing change" do expect(page).to have_content "This will cancel the current order." expect(page).to have_checked_field "Send a cancellation email to the customer" @@ -205,23 +201,18 @@ describe ' end.to have_enqueued_mail(Spree::OrderMailer, :cancel_email) end end - end - end - end - context "user can cancel an order" do - before do - login_as_admin_and_visit spree.edit_admin_order_path(order) - end - - it "by clicking on the cancel button" do - expect do - accept_alert do - click_button "Cancel" + context "that the user can choose to restock item" do + let(:shipment) { order.shipments.first } + it "uncheck the checkbox to not restock item" do + within(".modal", visible: true) do + check("restock_items") + click_on("OK") + end + expect(shipment.stock_location).not_to receive(:restock) + end end - expect(page).to have_content "Order updated" - expect(order.reload.state).to eq("canceled") - end.to have_enqueued_mail(Spree::OrderMailer, :cancel_email) + end end end