diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index 16376ed14a..9048f71eda 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -104,15 +104,38 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, else StatusMessage.display 'failure', t "unsaved_changes_error" + $scope.cancelOrder = (order) -> + return $http( + method: 'GET' + url: "/admin/orders/#{order.number}/fire?e=cancel") + $scope.deleteLineItem = (lineItem) -> - if ($scope.confirmDelete && confirm(t "are_you_sure")) || !$scope.confirmDelete - LineItems.delete lineItem + if lineItem.order.item_count == 1 + if confirm(t('js.admin.deleting_item_will_cancel_order')) + $scope.cancelOrder(lineItem.order).then(-> $scope.refreshData()) + else if ($scope.confirmDelete && confirm(t "are_you_sure")) || !$scope.confirmDelete + LineItems.delete(lineItem, () -> $scope.refreshData()) - $scope.deleteLineItems = (lineItemsToDelete) -> - existingState = $scope.confirmDelete - $scope.confirmDelete = false - $scope.deleteLineItem lineItem for lineItem in lineItemsToDelete when lineItem.checked - $scope.confirmDelete = existingState + $scope.deleteLineItems = (lineItems) -> + lineItemsToDelete = lineItems.filter (item) -> item.checked + willCancelOrders = false + itemsPerOrder = new Map() + for item in lineItemsToDelete + { order } = item + if itemsPerOrder.has(order) + itemsPerOrder.get(order).push(item) + else + itemsPerOrder.set(order, [item]) + willCancelOrders = true if (order.item_count == itemsPerOrder.get(order).length) + + if willCancelOrders + return unless confirm(t("js.admin.deleting_item_will_cancel_order")) + + itemsPerOrder.forEach (items, order) => + if order.item_count == items.length + $scope.cancelOrder(order).then(-> $scope.refreshData()) + else + Promise.all(LineItems.delete(item) for item in items).then(-> $scope.refreshData()) $scope.allBoxesChecked = -> checkedCount = $scope.filteredLineItems.reduce (count,lineItem) -> diff --git a/app/serializers/api/admin/order_serializer.rb b/app/serializers/api/admin/order_serializer.rb index 4a35d593fa..c13d22240b 100644 --- a/app/serializers/api/admin/order_serializer.rb +++ b/app/serializers/api/admin/order_serializer.rb @@ -7,7 +7,7 @@ module Api :edit_path, :state, :payment_state, :shipment_state, :payments_path, :ready_to_ship, :ready_to_capture, :created_at, :distributor_name, :special_instructions, :display_outstanding_balance, - :item_total, :adjustment_total, :payment_total, :total + :item_total, :adjustment_total, :payment_total, :total, :item_count has_one :distributor, serializer: Api::Admin::IdSerializer has_one :order_cycle, serializer: Api::Admin::IdSerializer @@ -69,6 +69,10 @@ module Api object.completed_at.blank? ? "" : I18n.l(object.completed_at, format: '%B %d, %Y') end + def item_count + object.line_items.count + end + private def spree_routes_helper diff --git a/config/locales/en.yml b/config/locales/en.yml index 585bc57ff0..e031fa6c61 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2847,6 +2847,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using admin: unit_price_tooltip: "The unit price increases transparency by allowing your customers to easily compare prices between different products and packaging sizes. Note, that the final unit price displayed in the shopfront might differ as it is includes taxes & fees." enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it." + deleting_item_will_cancel_order: "This operation will result in one or more empty orders, which will be cancelled. Do you wish to proceed?" modals: got_it: "Got it" close: "Close" @@ -3345,6 +3346,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using last: "Last" spree: + order_updated: "Order Updated" add_country: "Add country" add_state: "Add state" adjustment: "Adjustment" diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index e54b71995b..352002c83b 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -44,6 +44,9 @@ describe "LineItemsCtrl", -> LineItems = index: jasmine.createSpy('index').and.returnValue(lineItem) all: [lineItem] + delete: (lineItem, callback=null) -> + callback() if callback + return Promise.resolve() httpBackend.expectGET("/api/v0/orders.json?q%5Bcompleted_at_gteq%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bdistributor_id_eq%5D=&q%5Border_cycle_id_eq%5D=&q%5Bshipment_state_not_eq%5D=shipped&q%5Bstate_not_eq%5D=canceled").respond {orders: [order], pagination: {page: 1, pages: 1, results: 1}} httpBackend.expectGET("/admin/enterprises/visible.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor] @@ -98,23 +101,102 @@ describe "LineItemsCtrl", -> it "resets the form state to pristine", -> expect(scope.bulk_order_form.$setPristine.calls.count()).toBe 1 - describe "deleting 'checked' line items", -> - line_item1 = line_item2 = line_item3 = line_item4 = null + describe "deleting a line item", -> + line_item1 = line_item2 = null + order1 = order2 = null beforeEach -> - line_item1 = { name: "line item 1", checked: false } - line_item2 = { name: "line item 2", checked: true } - line_item3 = { name: "line item 3", checked: false } - line_item4 = { name: "line item 4", checked: true } + order1 = { id: 1, item_count: 1 } + order2 = { id: 2, item_count: 2 } + + line_item1 = { + name: "line item 1", + order: order1 + } + line_item2 = { + name: "line item 2", + order: order2 + } + scope.line_items = [ line_item1, line_item2 ] + + it "shows a different message if order will be canceled", -> + spyOn(window, "confirm") + scope.deleteLineItem(line_item2) + expect(confirm).not.toHaveBeenCalledWith("This operation will result in one or more empty orders, which will be cancelled. Do you wish to proceed?") + scope.deleteLineItem(line_item1) + expect(confirm).toHaveBeenCalledWith("This operation will result in one or more empty orders, which will be cancelled. Do you wish to proceed?") + + it "deletes the line item", -> + spyOn(window, "confirm").and.callFake(-> return true) + spyOn(LineItems, "delete") + scope.deleteLineItem(line_item2) + expect(LineItems.delete).toHaveBeenCalledWith(line_item2, jasmine.anything()) + + it "cancels empty order", -> + spyOn(window, "confirm").and.callFake(-> return true) + spyOn(scope, "cancelOrder").and.callFake(-> return Promise.resolve()) + scope.deleteLineItem(line_item1) + expect(scope.cancelOrder).toHaveBeenCalledWith(order1) + + describe "deleting 'checked' line items", -> + line_item1 = line_item2 = line_item3 = line_item4 = null + order1 = order2 = order3 = null + + beforeEach -> + order1 = { id: 1, item_count: 1 } + order2 = { id: 2, item_count: 1 } + order3 = { id: 3, item_count: 2 } + line_item1 = { + name: "line item 1", + order: order1 + checked: false + } + line_item2 = { + name: "line item 2", + order: order2, + checked: false + } + line_item3 = { + name: "line item 3", + order: order3, + checked: false + } + line_item4 = { + name: "line item 4", + order: order3, + checked: false + } scope.line_items = [ line_item1, line_item2, line_item3, line_item4 ] - it "calls deletedLineItem for each 'checked' line item", -> - spyOn(scope, "deleteLineItem") + it "asks for confirmation only if orders will be canceled", -> + spyOn(window, "confirm") + line_item3.checked = true scope.deleteLineItems(scope.line_items) - expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item2) - expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item4) - expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item1) - expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item3) + expect(confirm).not.toHaveBeenCalled() + line_item1.checked = true + scope.deleteLineItems(scope.line_items) + expect(confirm).toHaveBeenCalledWith("This operation will result in one or more empty orders, which will be cancelled. Do you wish to proceed?") + + + it "deletes checked line items for non-empty orders", -> + line_item1.checked = true + line_item3.checked = true + spyOn(window, "confirm").and.callFake(-> return true) + spyOn(LineItems, "delete") + scope.deleteLineItems(scope.line_items) + expect(LineItems.delete).toHaveBeenCalledWith(line_item3) + expect(LineItems.delete).not.toHaveBeenCalledWith(line_item1) + expect(LineItems.delete).not.toHaveBeenCalledWith(line_item2) + expect(LineItems.delete).not.toHaveBeenCalledWith(line_item4) + + it "cancels all empty orders", -> + line_item1.checked = true + line_item3.checked = true + spyOn(window, "confirm").and.callFake(-> return true) + spyOn(scope, "cancelOrder").and.callFake(-> return Promise.resolve()) + scope.deleteLineItems(scope.line_items) + expect(scope.cancelOrder).toHaveBeenCalledWith(order1) + expect(scope.cancelOrder).not.toHaveBeenCalledWith(order3) describe "check boxes for line items", -> line_item1 = line_item2 = null diff --git a/spec/system/admin/bulk_order_management_spec.rb b/spec/system/admin/bulk_order_management_spec.rb index d05949130e..86332b06b0 100644 --- a/spec/system/admin/bulk_order_management_spec.rb +++ b/spec/system/admin/bulk_order_management_spec.rb @@ -670,8 +670,10 @@ describe ' within("tr#li_#{li2.id} td.bulk") do check "bulk" end - find("div#bulk-actions-dropdown").click - find("div#bulk-actions-dropdown div.menu_item", text: "Delete Selected" ).click + page.driver.accept_modal :confirm do + find("div#bulk-actions-dropdown").click + find("div#bulk-actions-dropdown div.menu_item", text: "Delete Selected" ).click + end expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_no_selector "tr#li_#{li2.id}" end @@ -693,8 +695,10 @@ describe ' check "toggle_bulk" fill_in "quick_search", with: o1.number expect(page).to have_no_selector "tr#li_#{li2.id}" - find("div#bulk-actions-dropdown").click - find("div#bulk-actions-dropdown div.menu_item", text: "Delete Selected" ).click + page.driver.accept_modal :confirm do + find("div#bulk-actions-dropdown").click + find("div#bulk-actions-dropdown div.menu_item", text: "Delete Selected" ).click + end expect(page).to have_no_selector "tr#li_#{li1.id}" expect(page).to have_selector "#quick_search" fill_in "quick_search", with: ''