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 76a7c5e89d..90e4c9a14d 100644 --- a/app/assets/javascripts/admin/spree/orders/variant_autocomplete.js.erb +++ b/app/assets/javascripts/admin/spree/orders/variant_autocomplete.js.erb @@ -82,10 +82,23 @@ adjustItems = function(shipment_number, variant_id, quantity){ var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id}); if (quantity == 0 && inventory_units.length == shipment.inventory_units.length) { - ofnAlert(t("js.admin.orders.cannot_remove_last_item")); + ofnCancelOrderAlert((confirm, sendEmailCancellation) => { + if (confirm) { + doAdjustItems(shipment_number, variant_id, quantity, inventory_units, () => { + var redirectTo = new URL(Spree.routes.cancel_order.toString()); + redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation); + window.location.href = redirectTo.toString(); + }); + } + }); return; } + doAdjustItems(shipment_number, variant_id, quantity, inventory_units, () => { + window.location.reload(); + }); +} +doAdjustItems = function(shipment_number, variant_id, quantity, inventory_units, callback) { var url = Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number; var new_quantity = 0; @@ -106,7 +119,7 @@ adjustItems = function(shipment_number, variant_id, quantity){ url: Spree.url(url), data: { variant_id: variant_id, quantity: new_quantity } }).done(function( msg ) { - window.location.reload(); + callback(); }); } } @@ -181,6 +194,24 @@ ofnAlert = function(message) { $('#custom-alert').show(); } +ofnCancelOrderAlert = function(callback) { + $('#custom-confirm .message').html( + ` ${t("js.admin.orders.cancel_the_order_html")} +
+ + +
`); + $('#custom-confirm button.confirm').unbind( "click" ).click(() => { + $('#custom-confirm').hide(); + callback(true, $('#send_cancellation_email').is(':checked')); + }); + $('#custom-confirm button.cancel').click(() => { + $('#custom-confirm').hide(); + callback(false) + }); + $('#custom-confirm').show(); +} + ofnConfirm = function(callback) { $('#custom-confirm').data($(event.target).data()); $('#custom-confirm button.confirm').click(callback); diff --git a/app/controllers/spree/admin/orders_controller.rb b/app/controllers/spree/admin/orders_controller.rb index c1f1ce10bd..55035f6a07 100644 --- a/app/controllers/spree/admin/orders_controller.rb +++ b/app/controllers/spree/admin/orders_controller.rb @@ -65,6 +65,7 @@ module Spree def fire event = params[:e] + @order.send_cancellation_email = params[:send_cancellation_email] == "true" if @order.public_send(event.to_s) flash[:success] = Spree.t(:order_updated) else diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index 87c063007a..5a0cd48b19 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -105,6 +105,7 @@ module Spree after_save_commit DefaultAddressUpdater + attribute :send_cancellation_email, type: :boolean, default: true # -- Scopes scope :not_empty, -> { left_outer_joins(:line_items).where.not(spree_line_items: { id: nil }) @@ -659,7 +660,7 @@ module Spree def after_cancel shipments.each(&:cancel!) - OrderMailer.cancel_email(id).deliver_later + OrderMailer.cancel_email(id).deliver_later if send_cancellation_email self.payment_state = 'credit_owed' unless shipped? end diff --git a/app/views/spree/admin/shared/_routes.html.erb b/app/views/spree/admin/shared/_routes.html.erb index a0a854781f..dc8fac6b9d 100644 --- a/app/views/spree/admin/shared/_routes.html.erb +++ b/app/views/spree/admin/shared/_routes.html.erb @@ -9,6 +9,7 @@ :variants_search => spree.admin_search_variants_url(:format => 'json'), :taxons_search => main_app.api_v0_taxons_url(:format => 'json'), :orders_api => main_app.api_v0_orders_url, - :states_search => main_app.api_v0_states_url(:format => 'json') + :states_search => main_app.api_v0_states_url(:format => 'json'), + :cancel_order => spree.fire_admin_order_url(id: @order ? @order.number : "", e: 'cancel') }.to_json %>; diff --git a/app/webpacker/css/admin/orders.scss b/app/webpacker/css/admin/orders.scss index 2316b27f66..8f5b724c35 100644 --- a/app/webpacker/css/admin/orders.scss +++ b/app/webpacker/css/admin/orders.scss @@ -148,5 +148,9 @@ table.index td.actions { .message { font-size: $h5-size; padding: 1.2em 0; + + .form { + padding: 1.2em 0; + } } } diff --git a/config/locales/en.yml b/config/locales/en.yml index 1b53cf8ffe..585bc57ff0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3031,7 +3031,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using invalid: "invalid" quantity_adjusted: "Insufficient stock available. Line item updated to maximum available quantity." quantity_unchanged: "Quantity unchanged from previous amount." - cannot_remove_last_item: "Cannot remove last item from order. Please cancel order instead." + 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" resend_user_email_confirmation: resend: "Resend" sending: "Resend..." @@ -3416,6 +3417,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using tracking: "Tracking" tracking_number: "Tracking Number" order_total: "Order Total" + order_updated: "Order updated" customer_details: "Customer Details" customer_details_updated: "Customer Details updated" customer_search: "Customer Search" diff --git a/spec/system/admin/order_spec.rb b/spec/system/admin/order_spec.rb index 7182da0851..7e726839d1 100644 --- a/spec/system/admin/order_spec.rb +++ b/spec/system/admin/order_spec.rb @@ -126,6 +126,88 @@ describe ' expect(page).not_to have_select2 "add_variant_id", with_options: [product.name] end + context "deleting item of an order" do + context "when there a more than one items in the order" do + let(:line_item) { create(:line_item) } + + before do + order.line_items << line_item + login_as_admin_and_visit spree.edit_admin_order_path(order) + find("a.delete-item").click + expect(page).to have_content "Are you sure?" + end + + it "show a modal 'Are you sure?' that the user can close and then nothing change" do + within(".modal", visible: true) do + expect do + click_on("Cancel") + expect(page).not_to have_content "Are you sure?" + end.not_to change { order.line_items.length } + end + end + + it "show a modal 'Are you sure?' that the user confirm and then the item is deleted" do + expect(order.line_items.length).to eq(2) + within(".modal", visible: true) do + expect do + click_on("OK") + expect(page).not_to have_css(".modal", visible: true) + end.to change { order.reload.line_items.length }.by(-1) + end + end + end + + context "when it is the last item of an order" do + before do + # specify that order has only one line item + 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 + it "that the user can close and then nothing change" do + expect(page).to have_content "This will cancel the current order." + within(".modal", visible: true) do + click_on("Cancel") + end + + expect(order.reload.line_items.length).to eq(1) + expect(page).to have_selector('tr.stock-item', count: 1) + end + + context "that the user can confirm" do + it "and then the order is cancelled and no email is sent by default" do + expect do + within(".modal", visible: true) do + click_on("OK") + end + expect(page).to have_content "Cannot add item to canceled order" + expect(order.reload.line_items.length).to eq(0) + expect(order.reload.state).to eq("canceled") + end.to_not have_enqueued_mail(Spree::OrderMailer, :cancel_email) + end + + it "and check the checkbox to send an email to the customer about its order cancellation" do + expect do + within(".modal", visible: true) do + check("send_cancellation_email") + click_on("OK") + end + expect(page).to have_content "Cannot add item to canceled order" + expect(order.reload.line_items.length).to eq(0) + expect(order.reload.state).to eq("canceled") + end.to have_enqueued_mail(Spree::OrderMailer, :cancel_email) + end + end + end + end + end + it "can't add more items than are available" do # Move the order back to the cart state order.state = 'cart'