From f3d778af1a30386a06c352cf90181c2fc330469f Mon Sep 17 00:00:00 2001 From: Rob H Date: Fri, 17 May 2013 19:27:12 +0530 Subject: [PATCH] BPUR: can update master on_hand --- .../javascripts/admin/bulk_product_update.js | 41 +++++++++++-- .../spree/admin/products/bulk_index.html.haml | 6 +- app/views/spree/admin/products/bulk_index.rep | 5 ++ spec/controllers/product_controller_spec.rb | 22 +++++-- .../admin/bulk_product_update_spec.rb | 61 ++++++++++++++++++- .../unit/bulk_product_update_spec.js | 53 ++++++++++++---- 6 files changed, 164 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/admin/bulk_product_update.js b/app/assets/javascripts/admin/bulk_product_update.js index 62c71abeb0..8af03310e5 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js +++ b/app/assets/javascripts/admin/bulk_product_update.js @@ -36,22 +36,25 @@ productsApp.directive('ngDecimal', function () { } }); -productsApp.controller('AdminBulkProductsCtrl', function($scope, $timeout, $http) { +productsApp.controller('AdminBulkProductsCtrl', function($scope, $timeout, $http, dataFetcher) { $scope.refreshSuppliers = function(){ - $http.get('/enterprises/suppliers.json').success(function(data) { + dataFetcher('/enterprises/suppliers.json').then(function(data){ $scope.suppliers = data; }); }; $scope.refreshProducts = function(){ - $http({ method: 'GET', url:'/admin/products/bulk_index.json' }).success(function(data) { + dataFetcher('/admin/products/bulk_index.json').then(function(data){ $scope.products = angular.copy(data); $scope.cleanProducts = angular.copy(data); }); - } + }; - $scope.refreshSuppliers(); - $scope.refreshProducts(); + //accessible from scope + $scope.onHand = function(products){ + return onHand(products); + } + $scope.updateStatusMessage = { text: "", style: {} @@ -66,6 +69,7 @@ productsApp.controller('AdminBulkProductsCtrl', function($scope, $timeout, $http }) .success(function(data){ if (angular.toJson($scope.products) == angular.toJson(data)){ + $scope.cleanProducts = angular.copy(data); $scope.displaySuccess(); } else{ @@ -104,6 +108,31 @@ productsApp.controller('AdminBulkProductsCtrl', function($scope, $timeout, $http } }); +productsApp.factory('dataFetcher', function($http,$q){ + return function(dataLocation){ + var deferred = $q.defer(); + $http.get(dataLocation).success(function(data) { + deferred.resolve(data); + }).error(function(){ + deferred.reject(); + }); + return deferred.promise; + }; +}); + +function onHand(product){ + var onHand = 0; + if(product.hasOwnProperty('variants') && product.variants instanceof Array){ + angular.forEach(product.variants, function(variant) { + onHand = parseInt( onHand ) + parseInt( variant.on_hand > 0 ? variant.on_hand : 0 ); + }); + } + else{ + onHand = 'error'; + } + return onHand; +} + function sortByID(array){ var sortedArray = []; for (var i in array){ diff --git a/app/views/spree/admin/products/bulk_index.html.haml b/app/views/spree/admin/products/bulk_index.html.haml index 3c5bd8f6a8..0f2de43fe3 100644 --- a/app/views/spree/admin/products/bulk_index.html.haml +++ b/app/views/spree/admin/products/bulk_index.html.haml @@ -11,13 +11,14 @@ %div#new_product(data-hook) -%div{ 'ng-app' => 'bulk_product_update', 'ng-controller' => 'AdminBulkProductsCtrl' } +%div{ 'ng-app' => 'bulk_product_update', 'ng-controller' => 'AdminBulkProductsCtrl', 'ng-init' => 'refreshSuppliers(); refreshProducts()' } %table.index#listing_products %thead %tr %th Name %th Supplier %th Price + %th On Hand %th Av. On %tbody{ 'ng-repeat' => 'product in products | filter:query' } %tr @@ -27,6 +28,9 @@ %select.select2{ :name => 'supplier_id', 'ng-model' => 'product.supplier_id', 'ng-options' => 's.id as s.name for s in suppliers' } %td %input{ 'ng-model' => 'product.master.price', 'ng-decimal' => :true, :name => 'master_price', :type => 'text' } + %td + %span{ 'ng-bind' => 'onHand(product)', :name => 'master_on_hand', 'ng-show' => 'product.variants.length > 0' } + %input.field{ 'ng-model' => 'product.master.on_hand', :name => 'master_on_hand', 'ng-show' => 'product.variants.length == 0', :type => 'number' } %td %input{ 'ng-model' => 'product.available_on', :name => 'available_on', :type => 'text' } %input{:type => 'button', :value => 'Update', 'ng-click' => 'prepareProductsForSubmit()'} diff --git a/app/views/spree/admin/products/bulk_index.rep b/app/views/spree/admin/products/bulk_index.rep index f095c2ab01..b7cd059876 100644 --- a/app/views/spree/admin/products/bulk_index.rep +++ b/app/views/spree/admin/products/bulk_index.rep @@ -6,5 +6,10 @@ r.list_of :products, @collection do r.element :master do r.element :id r.element :price + r.element :on_hand + end + r.list_of :variants, Proc.new{ |product| product.variants.sort { |a,b| a.id <=> b.id } } do + r.element :id + r.element :on_hand end end \ No newline at end of file diff --git a/spec/controllers/product_controller_spec.rb b/spec/controllers/product_controller_spec.rb index 077179618c..ea84c2ce5c 100644 --- a/spec/controllers/product_controller_spec.rb +++ b/spec/controllers/product_controller_spec.rb @@ -22,6 +22,10 @@ describe Spree::Admin::ProductsController do it "collects returns products in an array formatted as json" do p1 = FactoryGirl.create(:product) p2 = FactoryGirl.create(:product) + v11 = FactoryGirl.create(:variant, product: p1, on_hand: 1) + v12 = FactoryGirl.create(:variant, product: p1, on_hand: 2) + v13 = FactoryGirl.create(:variant, product: p1, on_hand: 3) + v21 = FactoryGirl.create(:variant, product: p2, on_hand: 4) spree_get :bulk_index, { format: :json } p1r = { @@ -31,8 +35,14 @@ describe Spree::Admin::ProductsController do "available_on" => p1.available_on.strftime("%F %T"), "master" => { "id" => p1.master.id, - "price" => p1.master.price.to_s - } + "price" => p1.master.price.to_s, + "on_hand" => p1.master.on_hand + }, + "variants" => [ #ordered by id + { "id" => v11.id, "on_hand" => v11.on_hand }, + { "id" => v12.id, "on_hand" => v12.on_hand }, + { "id" => v13.id, "on_hand" => v13.on_hand } + ] } p2r = { "id" => p2.id, @@ -41,8 +51,12 @@ describe Spree::Admin::ProductsController do "available_on" => p2.available_on.strftime("%F %T"), "master" => { "id" => p2.master.id, - "price" => p2.master.price.to_s - } + "price" => p2.master.price.to_s, + "on_hand" => p2.master.on_hand + }, + "variants" => [ #ordered by id + { "id" => v21.id, "on_hand" => v21.on_hand } + ] } json_response = JSON.parse(response.body) #json_response = Hash[json_response.map{ |k, v| [k.to_sym, v] }] diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index ef0b0e0eca..b08299a45e 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -67,6 +67,37 @@ feature %q{ page.should have_field "master_price", with: "22.0" page.should have_field "master_price", with: "44.0" end + + it "displays an on hand count input (for master variant) for each product if no other variants exist" do + p1 = FactoryGirl.create(:product) + p2 = FactoryGirl.create(:product) + p1.master.on_hand = 15 + p2.master.on_hand = 12 + p1.save! + p2.save! + + visit '/admin/products/bulk_index' + + page.should_not have_selector "span[name='master_on_hand']", text: "0" + page.should have_field "master_on_hand", with: "15" + page.should have_field "master_on_hand", with: "12" + end + + it "displays an on hand count in a span (for master variant) for each product if other variants exist" do + p1 = FactoryGirl.create(:product) + p2 = FactoryGirl.create(:product) + v1 = FactoryGirl.create(:variant, product: p1, is_master: false, on_hand: 4) + p1.master.on_hand = 15 + p2.master.on_hand = 12 + p1.save! + p2.save! + + visit '/admin/products/bulk_index' + + page.should_not have_field "master_on_hand", with: "15" + page.should have_selector "span[name='master_on_hand']", text: "4" + page.should have_field "master_on_hand", with: "12" + end end scenario "create a new product" do @@ -94,11 +125,12 @@ feature %q{ page.should have_field "product_name", with: 'Big Bag Of Apples' end - scenario "updating a product" do + scenario "updating a product with no variants (except master)" do s1 = FactoryGirl.create(:supplier_enterprise) s2 = FactoryGirl.create(:supplier_enterprise) p = FactoryGirl.create(:product, supplier: s1, available_on: Date.today) p.master.price = 10.0 + p.master.on_hand = 6; p.save! login_to_admin_section @@ -109,11 +141,13 @@ feature %q{ page.should have_select "supplier_id", selected: s1.name page.should have_field "available_on", with: p.available_on.strftime("%F %T") page.should have_field "master_price", with: "10.0" + page.should have_field "master_on_hand", with: "6" fill_in "product_name", with: "Big Bag Of Potatoes" select(s2.name, :from => 'supplier_id') fill_in "available_on", with: (Date.today-3).strftime("%F %T") fill_in "master_price", with: "20" + fill_in "master_on_hand", with: "18" click_button 'Update' page.find("span#update-status-message").should have_content "Update complete" @@ -124,5 +158,30 @@ feature %q{ page.should have_select "supplier_id", selected: s2.name page.should have_field "available_on", with: (Date.today-3).strftime("%F %T") page.should have_field "master_price", with: "20.0" + page.should have_field "master_on_hand", with: "18" + end + + scenario "updating a product mutiple times without refresh" do + p = FactoryGirl.create(:product, name: 'original name') + login_to_admin_section + + visit '/admin/products/bulk_index' + + page.should have_field "product_name", with: "original name" + + fill_in "product_name", with: "new name 1" + + click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" + + fill_in "product_name", with: "new name 2" + + click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" + + fill_in "product_name", with: "original name" + + click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" 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 9500649074..b21be8d777 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js +++ b/spec/javascripts/unit/bulk_product_update_spec.js @@ -281,22 +281,28 @@ describe("AdminBulkProductsCtrl", function(){ scope = $rootScope.$new(); ctrl = $controller; httpBackend = $httpBackend; + + ctrl('AdminBulkProductsCtrl', { $scope: scope } ); })); - it("gets a list of suppliers, a list of products and stores a clean list of products", function(){ + it("gets a list of suppliers", function(){ httpBackend.expectGET('/enterprises/suppliers.json').respond("list of suppliers"); - httpBackend.expectGET('/admin/products/bulk_index.json').respond("list of products"); - ctrl('AdminBulkProductsCtrl', { $scope: scope } ); + scope.refreshSuppliers(); httpBackend.flush(); expect(scope.suppliers).toEqual("list of suppliers"); + }); + + it("gets a list of products and stores a clean list of products", function(){ + httpBackend.expectGET('/admin/products/bulk_index.json').respond("list of products"); + scope.refreshProducts(); + httpBackend.flush(); expect(scope.products).toEqual("list of products"); expect(scope.cleanProducts).toEqual("list of products"); }); it("does not affect clean products list when products list is altered", function(){ - httpBackend.expectGET('/enterprises/suppliers.json').respond("list of suppliers"); httpBackend.expectGET('/admin/products/bulk_index.json').respond( [1,2,3,4,5] ); - ctrl('AdminBulkProductsCtrl', { $scope: scope } ); + scope.refreshProducts(); httpBackend.flush(); expect(scope.products).toEqual( [1,2,3,4,5] ); expect(scope.cleanProducts).toEqual( [1,2,3,4,5] ); @@ -305,6 +311,33 @@ describe("AdminBulkProductsCtrl", function(){ expect(scope.cleanProducts).toEqual( [1,2,3,4,5] ); }); }); + + describe("getting on_hand counts when products have variants", function(){ + var p1, p2, p3; + beforeEach(function(){ + p1 = { variants: [ { on_hand: 1 }, { on_hand: 2 }, { on_hand: 3 } ] }; + p2 = { variants: [ { not_on_hand: 1 }, { on_hand: 2 }, { on_hand: 3 } ] }; + p3 = { not_variants: [ { on_hand: 1 }, { on_hand: 2 } ], variants: [ { on_hand: 3 } ] }; + }); + + it("sums variant on_hand properties", function(){ + expect(onHand(p1)).toEqual(6);r + }); + + it("ignores items in variants without an on_hand property (adds 0)", function(){ + expect(onHand(p2)).toEqual(5); + }); + + it("ignores on_hand properties of objects in arrays which are not named 'variants' (adds 0)", function(){ + expect(onHand(p3)).toEqual(3); + }); + + it("returns 'error' if not given an object with a variants property that is an array", function(){ + expect( onHand([]) ).toEqual('error'); + expect( onHand( { not_variants: [] } ) ).toEqual('error'); + expect( onHand( { variants: {} } ) ).toEqual('error'); + }); + }); describe("submitting products to be updated", function(){ ctrl = null; @@ -325,13 +358,12 @@ describe("AdminBulkProductsCtrl", function(){ describe("preparing products for submit",function(){ beforeEach(function(){ - httpBackend.expectGET('/enterprises/suppliers.json').respond("list of suppliers"); - httpBackend.expectGET('/admin/products/bulk_index.json').respond( [1,2,3,4,5] ); ctrl('AdminBulkProductsCtrl', { $scope: scope } ); - httpBackend.flush(); spyOn(window, "getDirtyObjects").andReturn( [ { id: 1, value: 1 }, { id:2, value: 2 } ] ); spyOn(window, "filterSubmitProducts").andReturn( [ { id: 1, value: 3 }, { id:2, value: 4 } ] ); spyOn(scope, "updateProducts"); + scope.products = [1,2,3,4,5]; + scope.cleanProducts = [1,2,3,4,5]; scope.prepareProductsForSubmit(); }); @@ -350,10 +382,7 @@ describe("AdminBulkProductsCtrl", function(){ describe("updating products",function(){ beforeEach(function(){ - httpBackend.expectGET('/enterprises/suppliers.json').respond("list of suppliers"); - httpBackend.expectGET('/admin/products/bulk_index.json').respond("list of products"); ctrl('AdminBulkProductsCtrl', { $scope: scope, $timeout: timeout } ); - httpBackend.flush(); }); it("submits products to be updated with a http post request to /admin/products/bulk_update", function(){ @@ -387,7 +416,7 @@ describe("AdminBulkProductsCtrl", function(){ scope.updateProducts("updated list of products"); httpBackend.flush(); expect(scope.displayFailure).toHaveBeenCalled(); - }); + }); }); });