diff --git a/app/assets/javascripts/admin/bulk_product_update.js b/app/assets/javascripts/admin/bulk_product_update.js index 20aec7794c..d60534fed9 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js +++ b/app/assets/javascripts/admin/bulk_product_update.js @@ -120,8 +120,22 @@ productsApp.controller('AdminBulkProductsCtrl', function($scope, $timeout, $http }) } + $scope.cloneProduct = function(product){ + dataFetcher("/admin/products/"+product.permalink_live+"/clone.json").then(function(data){ + // Ideally we would use Spree's built in respond_override helper here to redirect the user after a successful clone with .json in the accept headers + // However, at the time of writing there appears to be an issue which causes the repond_with block in the destroy action of Spree::Admin::Product to break + // when a respond_overrride for the clone action is used. + var id = data.product.id; + dataFetcher("/admin/products/bulk_index.json?q[id_eq]="+id).then(function(data){ + var newProduct = data[0]; + newProduct.variants = toObjectWithIDKeys(newProduct.variants) + $scope.products[newProduct.id] = newProduct; + }); + }); + } + $scope.hasVariants = function(product){ - return !angular.equals(product.variants,{}); + return Object.keys(product.variants).length > 0; } $scope.updateProducts = function(productsToSubmit){ @@ -135,6 +149,7 @@ productsApp.controller('AdminBulkProductsCtrl', function($scope, $timeout, $http data = toObjectWithIDKeys(data); if (angular.toJson($scope.products) == angular.toJson(data)){ $scope.products = data; + $scope.dirtyProducts = {}; $scope.displaySuccess(); } else{ diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index c16a2d384c..af0e8cda62 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -1,7 +1,11 @@ Spree::Admin::ProductsController.class_eval do before_filter :load_product_set, :only => :bulk_index - + alias_method :location_after_save_original, :location_after_save + + respond_to :json, :only => :clone + + #respond_override :clone => { :json => {:success => lambda { redirect_to bulk_index_admin_products_url+"?q[id_eq]=#{@new.id}" } } } def bulk_index respond_to do |format| diff --git a/app/views/spree/admin/products/bulk_index.html.haml b/app/views/spree/admin/products/bulk_index.html.haml index 11b017ac51..3f5e336377 100644 --- a/app/views/spree/admin/products/bulk_index.html.haml +++ b/app/views/spree/admin/products/bulk_index.html.haml @@ -36,6 +36,7 @@ %input{ 'ng-model' => 'product.available_on', :name => 'available_on', 'ng-track-product' => 'available_on', :type => 'text' } %td.actions %a{ 'ng-href' => '/admin/products/{{product.permalink_live}}/edit', :class => "edit-product icon-edit no-text" } + %a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text" } %a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text" } %tr{ 'ng-repeat' => 'variant in product.variants' } %td diff --git a/spec/controllers/product_controller_spec.rb b/spec/controllers/product_controller_spec.rb index a12e922bc3..7e0adb76e0 100644 --- a/spec/controllers/product_controller_spec.rb +++ b/spec/controllers/product_controller_spec.rb @@ -57,4 +57,22 @@ describe Spree::Admin::ProductsController do json_response.should == [ p1r, p2r ] end end + + context "cloning a product" do + let(:ability_user) { stub_model(Spree::LegacyUser, :has_spree_role? => true) } + + it "renders the newly created product as json" do + p1 = FactoryGirl.create(:product) + p2 = FactoryGirl.create(:product) + + spree_get :clone, { :format => :json, :id => p1.permalink } + + json_response = JSON.parse(response.body) + json_response.keys.should == ["product"] + json_response["product"]["id"].should_not == p1.id; + json_response["product"]["name"].should == "COPY OF #{p1.name}"; + json_response["product"]["available_on"].should == p1.available_on.strftime("%FT%TZ"); + json_response["product"]["supplier_id"].should == p1.supplier_id; + end + end end \ No newline at end of file diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index c90fc04ee2..c68a5657ad 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -330,5 +330,23 @@ feature %q{ URI.parse(current_url).path.should == "/admin/products/#{v1.product.permalink}/variants/#{v1.id}/edit" end end + + describe "using clone buttons" do + it "shows a clone button for products, which duplicates the product and adds it to the page when clicked" do + p1 = FactoryGirl.create(:product, :name => "P1") + p2 = FactoryGirl.create(:product, :name => "P2") + p3 = FactoryGirl.create(:product, :name => "P3") + login_to_admin_section + + visit '/admin/products/bulk_index' + + page.should have_selector "a.clone-product", :count => 3 + + first("a.clone-product").click + + page.should have_selector "a.clone-product", :count => 4 + page.should have_field "product_name", with: "COPY OF #{p1.name}" + end + end end end \ No newline at end of file diff --git a/spec/javascripts/unit/bulk_product_update_spec.js b/spec/javascripts/unit/bulk_product_update_spec.js index f0325bff08..13b66e61a3 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js +++ b/spec/javascripts/unit/bulk_product_update_spec.js @@ -362,6 +362,30 @@ describe("AdminBulkProductsCtrl", function(){ }); }); + describe("cloning products",function(){ + beforeEach(function(){ + ctrl('AdminBulkProductsCtrl', { $scope: scope, $timeout: timeout } ); + }); + + it("clones products using a http get request to /admin/products/(permalink)/clone.json", function(){ + scope.products = { 13: { id: 13, permalink_live: "oranges" } } + httpBackend.expectGET('/admin/products/oranges/clone.json').respond(200, { product: { id: 17, name: "new_product" } } ); + httpBackend.expectGET('/admin/products/bulk_index.json?q[id_eq]=17').respond(200, [ { id: 17, name: "new_product" } ] ); + scope.cloneProduct(scope.products[13]); + httpBackend.flush(); + }); + + it("adds the newly created product to scope.products, sending variants to toObjectWithIDKeys()", function(){ + spyOn(window, "toObjectWithIDKeys").andCallThrough(); + scope.products = { 13: { id: 13, permalink_live: "oranges" } }; + httpBackend.expectGET('/admin/products/oranges/clone.json').respond(200, { product: { id: 17, name: "new_product", variants: [ { id: 3, name: "V1" } ] } } ); + httpBackend.expectGET('/admin/products/bulk_index.json?q[id_eq]=17').respond(200, [ { id: 17, name: "new_product", variants: [ { id: 3, name: "V1" } ] } ] ); + scope.cloneProduct(scope.products[13]); + httpBackend.flush(); + expect(toObjectWithIDKeys).toHaveBeenCalledWith([ { id: 3, name: "V1" } ]) + expect(scope.products).toEqual( { 13: { id: 13, permalink_live: "oranges" }, 17: { id: 17, name: "new_product", variants: { 3: { id: 3, name: "V1" } } } } ); + }); + }); /*describe("directives",function(){ scope = null;