From 634dd52a8005cc289979d95b6c3222be1bacb1b2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 Nov 2013 11:27:15 +1100 Subject: [PATCH] WIP: Convert bulk product update to coffeescript. 3 tests failing. --- .../javascripts/admin/bulk_product_update.js | 395 ------------------ .../admin/bulk_product_update.js.coffee | 380 +++++++++++++++++ config/ng-test.conf.js | 2 +- 3 files changed, 381 insertions(+), 396 deletions(-) delete mode 100644 app/assets/javascripts/admin/bulk_product_update.js create mode 100644 app/assets/javascripts/admin/bulk_product_update.js.coffee diff --git a/app/assets/javascripts/admin/bulk_product_update.js b/app/assets/javascripts/admin/bulk_product_update.js deleted file mode 100644 index 40d392e25a..0000000000 --- a/app/assets/javascripts/admin/bulk_product_update.js +++ /dev/null @@ -1,395 +0,0 @@ -var productsApp = angular.module('bulk_product_update', []) - -productsApp.config(["$httpProvider", function(provider) { - provider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content'); -}]); - -productsApp.directive('ngDecimal', function () { - return { - require: 'ngModel', - link: function(scope, element, attrs, ngModel) { - var numRegExp = /^\d+(\.\d+)?$/; - - element.bind('blur', function() { - scope.$apply(ngModel.$setViewValue(ngModel.$modelValue)); - ngModel.$render(); - }); - - ngModel.$parsers.push(function(viewValue){ - if (angular.isString(viewValue) && numRegExp.test(viewValue)){ - if (viewValue.indexOf(".") == -1){ - return viewValue+".0"; - } - } - return viewValue; - }); - } - } -}); - -productsApp.directive('ngTrackProduct', function(){ - return { - require: 'ngModel', - link: function(scope, element, attrs, ngModel) { - var property_name = attrs.ngTrackProduct; - ngModel.$parsers.push(function(viewValue){ - if (ngModel.$dirty) { - addDirtyProperty(scope.dirtyProducts, scope.product.id, property_name, viewValue); - scope.displayDirtyProducts(); - } - return viewValue; - }); - } - } -}); - -productsApp.directive('ngTrackVariant', function(){ - return { - require: 'ngModel', - link: function(scope, element, attrs, ngModel) { - var property_name = attrs.ngTrackVariant; - ngModel.$parsers.push(function(viewValue){ - var dirtyVariants = {}; - if (scope.dirtyProducts.hasOwnProperty(scope.product.id) && scope.dirtyProducts[scope.product.id].hasOwnProperty("variants")) dirtyVariants = scope.dirtyProducts[scope.product.id].variants; - if (ngModel.$dirty) { - addDirtyProperty(dirtyVariants, scope.variant.id, property_name, viewValue); - addDirtyProperty(scope.dirtyProducts, scope.product.id, "variants", dirtyVariants); - scope.displayDirtyProducts(); - } - return viewValue; - }); - } - } -}); - -productsApp.directive('ngToggleVariants',function(){ - return { - link: function(scope,element,attrs){ - if (scope.displayProperties[scope.product.id].showVariants) { element.removeClass('icon-chevron-right'); element.addClass('icon-chevron-down'); } - else { element.removeClass('icon-chevron-down'); element.addClass('icon-chevron-right'); } - element.on('click', function(){ - scope.$apply(function(){ - if (scope.displayProperties[scope.product.id].showVariants){ - scope.displayProperties[scope.product.id].showVariants = false; - element.removeClass('icon-chevron-down'); - element.addClass('icon-chevron-right'); - } - else { - scope.displayProperties[scope.product.id].showVariants = true; - element.removeClass('icon-chevron-right'); - element.addClass('icon-chevron-down'); - } - }); - }); - } - }; -}); - -productsApp.directive('ngToggleColumn',function(){ - return { - link: function(scope,element,attrs){ - if (!scope.column.visible) { element.addClass("unselected"); } - element.click('click', function(){ - scope.$apply(function(){ - if (scope.column.visible) { scope.column.visible = false; element.addClass("unselected"); } - else { scope.column.visible = true; element.removeClass("unselected"); } - }); - }); - } - }; -}); - -productsApp.directive('ngToggleColumnList', ["$compile", function($compile){ - return { - link: function(scope,element,attrs){ - var dialogDiv = element.next(); - element.on('click',function(){ - var pos = element.position(); - var height = element.outerHeight(); - dialogDiv.css({ - position: "absolute", - top: (pos.top + height) + "px", - left: pos.left + "px", - }).toggle(); - }); - } - } -}]); - -productsApp.directive('datetimepicker', ["$parse", function ($parse) { - return { - require: 'ngModel', - link: function (scope, element, attrs, ngModel) { - element.datetimepicker({ - dateFormat: 'yy-mm-dd', - timeFormat: 'HH:mm:ss', - stepMinute: 15, - onSelect:function (dateText, inst) { - scope.$apply(function(scope){ - ngModel.$setViewValue(dateText); // Fires ngModel.$parsers - }); - } - }); - } - } -}]); -productsApp.controller('AdminBulkProductsCtrl', ["$scope", "$timeout", "$http", "dataFetcher", function($scope, $timeout, $http, dataFetcher) { - $scope.updateStatusMessage = { - text: "", - style: {} - } - - $scope.columns = { - name: { name: 'Name', visible: true }, - supplier: { name: 'Supplier', visible: true }, - price: { name: 'Price', visible: true }, - on_hand: { name: 'On Hand', visible: true }, - available_on: { name: 'Available On', visible: true } - } - - $scope.initialise = function(spree_api_key){ - var authorise_api_reponse = ""; - dataFetcher('/api/users/authorise_api?token='+spree_api_key).then(function(data){ - authorise_api_reponse = data; - $scope.spree_api_key_ok = data.hasOwnProperty("success") && data["success"] == "Use of API Authorised"; - if ($scope.spree_api_key_ok){ - $http.defaults.headers.common['X-Spree-Token'] = spree_api_key; - dataFetcher('/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true').then(function(data){ - $scope.suppliers = data; - // Need to have suppliers before we get products so we can match suppliers to product.supplier - dataFetcher('/api/products/managed?template=bulk_index;page=1;per_page=500').then(function(data){ - $scope.resetProducts(data); - }); - }); - } - else if (authorise_api_reponse.hasOwnProperty("error")){ $scope.api_error_msg = authorise_api_reponse("error"); } - else{ api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access." } - }); - }; - - $scope.resetProducts = function(data){ - $scope.products = data; - $scope.dirtyProducts = {}; - $scope.displayProperties = $scope.displayProperties || {}; - angular.forEach($scope.products,function(product){ - $scope.displayProperties[product.id] = $scope.displayProperties[product.id] || { showVariants: false }; - $scope.matchSupplier(product); - }); - } - - $scope.matchSupplier = function(product){ - for (i in $scope.suppliers){ - var supplier = $scope.suppliers[i]; - if (angular.equals(supplier,product.supplier)){ - product.supplier = supplier; - break; - } - } - } - - $scope.updateOnHand = function(product){ - product.on_hand = onHand(product); - } - - $scope.editWarn = function(product,variant){ - if ( ( $scope.dirtyProductCount() > 0 && confirm("Unsaved changes will be lost. Continue anyway?") ) || ( $scope.dirtyProductCount() == 0 ) ){ - window.location = "/admin/products/"+product.permalink_live+(variant ? "/variants/"+variant.id : "")+"/edit"; - } - } - - $scope.deleteProduct = function(product){ - if (confirm("Are you sure?")){ - $http({ - method: 'DELETE', - url: '/api/products/'+product.id - }) - .success(function(data){ - $scope.products.splice($scope.products.indexOf(product),1); - if ($scope.dirtyProducts.hasOwnProperty(product.id)) delete $scope.dirtyProducts[product.id]; - $scope.displayDirtyProducts(); - }) - } - } - - $scope.deleteVariant = function(product,variant){ - if (confirm("Are you sure?")){ - $http({ - method: 'DELETE', - url: '/api/products/'+product.id+"/variants/"+variant.id - }) - .success(function(data){ - product.variants.splice(product.variants.indexOf(variant),1); - if ($scope.dirtyProducts.hasOwnProperty(product.id) && $scope.dirtyProducts[product.id].hasOwnProperty("variants") && $scope.dirtyProducts[product.id].variants.hasOwnProperty(variant.id)) delete $scope.dirtyProducts[product.id].variants[variant.id]; - $scope.displayDirtyProducts(); - }) - } - } - - $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 respond_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("/api/products/"+id+"?template=bulk_show").then(function(data){ - var newProduct = data; - $scope.matchSupplier(newProduct); - $scope.products.push(newProduct); - }); - }); - } - - $scope.hasVariants = function(product){ - return Object.keys(product.variants).length > 0; - } - - $scope.updateProducts = function(productsToSubmit){ - $scope.displayUpdating(); - $http({ - method: 'POST', - url: '/admin/products/bulk_update', - data: productsToSubmit - }) - .success(function(data){ - if (angular.toJson($scope.products) == angular.toJson(data)){ - $scope.resetProducts(data); - $scope.displaySuccess(); - } - else{ - $scope.displayFailure("Product lists do not match."); - } - }) - .error(function(data,status){ - $scope.displayFailure("Server returned with error status: "+status); - }); - } - - $scope.prepareProductsForSubmit = function(){ - var productsToSubmit = filterSubmitProducts($scope.dirtyProducts); - $scope.updateProducts(productsToSubmit); - } - - $scope.setMessage = function(model,text,style,timeout){ - model.text = text; - model.style = style; - if (model.timeout) $timeout.cancel(model.timeout); - if (timeout){ - model.timeout = $timeout(function() { $scope.setMessage(model,"",{},false); }, timeout, true); - } - } - - $scope.displayUpdating = function(){ - $scope.setMessage($scope.updateStatusMessage,"Updating...",{ color: "orange" },false); - } - - $scope.displaySuccess = function(){ - $scope.setMessage($scope.updateStatusMessage,"Update complete",{ color: "green" },3000); - } - - $scope.displayFailure = function(failMessage){ - $scope.setMessage($scope.updateStatusMessage,"Updating failed. "+failMessage,{ color: "red" },10000); - } - - $scope.displayDirtyProducts = function(){ - if ($scope.dirtyProductCount() > 0) $scope.setMessage($scope.updateStatusMessage,"Changes to "+$scope.dirtyProductCount()+" products remain unsaved.",{ color: "gray" },false); - else $scope.setMessage($scope.updateStatusMessage,"",{},false); - } - - $scope.dirtyProductCount = function(){ - return Object.keys($scope.dirtyProducts).length; - } -}]); - -productsApp.factory('dataFetcher', ["$http", "$q", 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 Object){ - angular.forEach(product.variants, function(variant) { - onHand = parseInt( onHand ) + parseInt( variant.on_hand > 0 ? variant.on_hand : 0 ); - }); - } - else{ - onHand = 'error'; - } - return onHand; -} - -function filterSubmitProducts(productsToFilter){ - var filteredProducts= []; - - if (productsToFilter instanceof Object){ - angular.forEach(productsToFilter, function(product){ - if (product.hasOwnProperty("id")){ - var filteredProduct = {}; - var filteredVariants = []; - - if (product.hasOwnProperty("variants")){ - angular.forEach(product.variants, function(variant){ - if (variant.deleted_at == null && variant.hasOwnProperty("id")){ - var hasUpdateableProperty = false; - var filteredVariant = {}; - filteredVariant.id = variant.id; - if (variant.hasOwnProperty("on_hand")) { filteredVariant.on_hand = variant.on_hand; hasUpdatableProperty = true; } - if (variant.hasOwnProperty("price")) { filteredVariant.price = variant.price; hasUpdatableProperty = true; } - if (hasUpdatableProperty) filteredVariants.push(filteredVariant); - } - }); - } - - var hasUpdatableProperty = false; - filteredProduct.id = product.id; - if (product.hasOwnProperty("name")) { filteredProduct.name = product.name; hasUpdatableProperty = true; } - if (product.hasOwnProperty("supplier")) { filteredProduct.supplier_id = product.supplier.id; hasUpdatableProperty = true; } - if (product.hasOwnProperty("price")) { filteredProduct.price = product.price; hasUpdatableProperty = true; } - if (product.hasOwnProperty("on_hand") && filteredVariants.length == 0) { filteredProduct.on_hand = product.on_hand; hasUpdatableProperty = true; } //only update if no variants present - if (product.hasOwnProperty("available_on")) { filteredProduct.available_on = product.available_on; hasUpdatableProperty = true; } - if (filteredVariants.length > 0) { filteredProduct.variants_attributes = filteredVariants; hasUpdatableProperty = true; } // Note that the name of the property changes to enable mass assignment of variants attributes with rails - - if (hasUpdatableProperty) filteredProducts.push(filteredProduct); - } - }); - } - return filteredProducts; -} - -function addDirtyProperty(dirtyObjects, objectID, propertyName, propertyValue){ - if (dirtyObjects.hasOwnProperty(objectID)){ - dirtyObjects[objectID][propertyName] = propertyValue; - } - else { - dirtyObjects[objectID] = {}; - dirtyObjects[objectID]["id"] = objectID; - dirtyObjects[objectID][propertyName] = propertyValue; - } -} - -function removeCleanProperty(dirtyObjects, objectID, propertyName){ - if (dirtyObjects.hasOwnProperty(objectID) && dirtyObjects[objectID].hasOwnProperty(propertyName)) delete dirtyObjects[objectID][propertyName]; - if (dirtyObjects.hasOwnProperty(objectID) && Object.keys(dirtyObjects[objectID]).length <= 1) delete dirtyObjects[objectID]; -} - -function toObjectWithIDKeys(array){ - var object = {}; - //if (array instanceof Array){ - for (i in array){ - if (array[i] instanceof Object && array[i].hasOwnProperty("id")){ - object[array[i].id] = angular.copy(array[i]); - if (array[i].hasOwnProperty("variants") && array[i].variants instanceof Array){ - object[array[i].id].variants = toObjectWithIDKeys(array[i].variants); - } - } - } - //} - return object; -} \ No newline at end of file diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee new file mode 100644 index 0000000000..12a7cda806 --- /dev/null +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -0,0 +1,380 @@ +productsApp = angular.module("bulk_product_update", []) + +productsApp.config [ + "$httpProvider" + (provider) -> + provider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content") +] + + +productsApp.directive "ngDecimal", -> + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + numRegExp = /^\d+(\.\d+)?$/ + element.bind "blur", -> + scope.$apply ngModel.$setViewValue(ngModel.$modelValue) + ngModel.$render() + + ngModel.$parsers.push (viewValue) -> + return viewValue + ".0" if viewValue.indexOf(".") is -1 if angular.isString(viewValue) and numRegExp.test(viewValue) + viewValue + + +productsApp.directive "ngTrackProduct", -> + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + property_name = attrs.ngTrackProduct + ngModel.$parsers.push (viewValue) -> + if ngModel.$dirty + addDirtyProperty scope.dirtyProducts, scope.product.id, property_name, viewValue + scope.displayDirtyProducts() + viewValue + + +productsApp.directive "ngTrackVariant", -> + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + property_name = attrs.ngTrackVariant + ngModel.$parsers.push (viewValue) -> + dirtyVariants = {} + dirtyVariants = scope.dirtyProducts[scope.product.id].variants if scope.dirtyProducts.hasOwnProperty(scope.product.id) and scope.dirtyProducts[scope.product.id].hasOwnProperty("variants") + if ngModel.$dirty + addDirtyProperty dirtyVariants, scope.variant.id, property_name, viewValue + addDirtyProperty scope.dirtyProducts, scope.product.id, "variants", dirtyVariants + scope.displayDirtyProducts() + viewValue + + +productsApp.directive "ngToggleVariants", -> + link: (scope, element, attrs) -> + if scope.displayProperties[scope.product.id].showVariants + element.removeClass "icon-chevron-right" + element.addClass "icon-chevron-down" + else + element.removeClass "icon-chevron-down" + element.addClass "icon-chevron-right" + element.on "click", -> + scope.$apply -> + if scope.displayProperties[scope.product.id].showVariants + scope.displayProperties[scope.product.id].showVariants = false + element.removeClass "icon-chevron-down" + element.addClass "icon-chevron-right" + else + scope.displayProperties[scope.product.id].showVariants = true + element.removeClass "icon-chevron-right" + element.addClass "icon-chevron-down" + + +productsApp.directive "ngToggleColumn", -> + link: (scope, element, attrs) -> + element.addClass "unselected" unless scope.column.visible + element.click "click", -> + scope.$apply -> + if scope.column.visible + scope.column.visible = false + element.addClass "unselected" + else + scope.column.visible = true + element.removeClass "unselected" + + +productsApp.directive "ngToggleColumnList", [ + "$compile" + ($compile) -> + return link: (scope, element, attrs) -> + dialogDiv = element.next() + element.on "click", -> + pos = element.position() + height = element.outerHeight() + dialogDiv.css( + position: "absolute" + top: (pos.top + height) + "px" + left: pos.left + "px" + ).toggle() + +] + + +productsApp.directive "datetimepicker", [ + "$parse" + ($parse) -> + return ( + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + element.datetimepicker + dateFormat: "yy-mm-dd" + timeFormat: "HH:mm:ss" + stepMinute: 15 + onSelect: (dateText, inst) -> + scope.$apply (scope) -> + # Fires ngModel.$parsers + ngModel.$setViewValue dateText + ) +] + + +productsApp.controller "AdminBulkProductsCtrl", [ + "$scope" + "$timeout" + "$http" + "dataFetcher" + ($scope, $timeout, $http, dataFetcher) -> + $scope.updateStatusMessage = + text: "" + style: {} + + $scope.columns = + name: + name: "Name" + visible: true + + supplier: + name: "Supplier" + visible: true + + price: + name: "Price" + visible: true + + on_hand: + name: "On Hand" + visible: true + + available_on: + name: "Available On" + visible: true + + $scope.initialise = (spree_api_key) -> + authorise_api_reponse = "" + dataFetcher("/api/users/authorise_api?token=" + spree_api_key).then (data) -> + authorise_api_reponse = data + $scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] is "Use of API Authorised" + if $scope.spree_api_key_ok + $http.defaults.headers.common["X-Spree-Token"] = spree_api_key + dataFetcher("/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true").then (data) -> + $scope.suppliers = data + # Need to have suppliers before we get products so we can match suppliers to product.supplier + dataFetcher("/api/products/managed?template=bulk_index;page=1;per_page=500").then (data) -> + $scope.resetProducts data + + + else if authorise_api_reponse.hasOwnProperty("error") + $scope.api_error_msg = authorise_api_reponse("error") + else + api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access." + + + $scope.resetProducts = (data) -> + $scope.products = data + $scope.dirtyProducts = {} + $scope.displayProperties = $scope.displayProperties or {} + angular.forEach $scope.products, (product) -> + $scope.displayProperties[product.id] = $scope.displayProperties[product.id] or showVariants: false + $scope.matchSupplier product + + + $scope.matchSupplier = (product) -> + for i of $scope.suppliers + supplier = $scope.suppliers[i] + if angular.equals(supplier, product.supplier) + product.supplier = supplier + break + + $scope.updateOnHand = (product) -> + product.on_hand = onHand(product) + + $scope.editWarn = (product, variant) -> + window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit" if ($scope.dirtyProductCount() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or ($scope.dirtyProductCount() is 0) + + $scope.deleteProduct = (product) -> + if confirm("Are you sure?") + $http( + method: "DELETE" + url: "/api/products/" + product.id + ).success (data) -> + $scope.products.splice $scope.products.indexOf(product), 1 + delete $scope.dirtyProducts[product.id] if $scope.dirtyProducts.hasOwnProperty(product.id) + $scope.displayDirtyProducts() + + + $scope.deleteVariant = (product, variant) -> + if confirm("Are you sure?") + $http( + method: "DELETE" + url: "/api/products/" + product.id + "/variants/" + variant.id + ).success (data) -> + product.variants.splice product.variants.indexOf(variant), 1 + delete $scope.dirtyProducts[product.id].variants[variant.id] if $scope.dirtyProducts.hasOwnProperty(product.id) and $scope.dirtyProducts[product.id].hasOwnProperty("variants") and $scope.dirtyProducts[product.id].variants.hasOwnProperty(variant.id) + $scope.displayDirtyProducts() + + + $scope.cloneProduct = (product) -> + dataFetcher("/admin/products/" + product.permalink_live + "/clone.json").then (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 + # respond_with block in the destroy action of Spree::Admin::Product to break + # when a respond_overrride for the clone action is used. + id = data.product.id + dataFetcher("/api/products/" + id + "?template=bulk_show").then (data) -> + newProduct = data + $scope.matchSupplier newProduct + $scope.products.push newProduct + + + + $scope.hasVariants = (product) -> + Object.keys(product.variants).length > 0 + + $scope.updateProducts = (productsToSubmit) -> + $scope.displayUpdating() + $http( + method: "POST" + url: "/admin/products/bulk_update" + data: productsToSubmit + ).success((data) -> + if angular.toJson($scope.products) is angular.toJson(data) + $scope.resetProducts data + $scope.displaySuccess() + else + $scope.displayFailure "Product lists do not match." + ).error (data, status) -> + $scope.displayFailure "Server returned with error status: " + status + + + $scope.prepareProductsForSubmit = -> + productsToSubmit = filterSubmitProducts($scope.dirtyProducts) + $scope.updateProducts productsToSubmit + + $scope.setMessage = (model, text, style, timeout) -> + model.text = text + model.style = style + $timeout.cancel model.timeout if model.timeout + if timeout + model.timeout = $timeout(-> + $scope.setMessage model, "", {}, false + , timeout, true) + + $scope.displayUpdating = -> + $scope.setMessage $scope.updateStatusMessage, "Updating...", + color: "orange" + , false + + $scope.displaySuccess = -> + $scope.setMessage $scope.updateStatusMessage, "Update complete", + color: "green" + , 3000 + + $scope.displayFailure = (failMessage) -> + $scope.setMessage $scope.updateStatusMessage, "Updating failed. " + failMessage, + color: "red" + , 10000 + + $scope.displayDirtyProducts = -> + if $scope.dirtyProductCount() > 0 + $scope.setMessage $scope.updateStatusMessage, "Changes to " + $scope.dirtyProductCount() + " products remain unsaved.", + color: "gray" + , false + else + $scope.setMessage $scope.updateStatusMessage, "", {}, false + + $scope.dirtyProductCount = -> + Object.keys($scope.dirtyProducts).length +] + + +productsApp.factory "dataFetcher", [ + "$http" + "$q" + ($http, $q) -> + return (dataLocation) -> + deferred = $q.defer() + $http.get(dataLocation).success((data) -> + deferred.resolve data + ).error -> + deferred.reject() + + deferred.promise +] + + +onHand = (product) -> + onHand = 0 + if product.hasOwnProperty("variants") and product.variants instanceof Object + angular.forEach product.variants, (variant) -> + onHand = parseInt(onHand) + parseInt((if variant.on_hand > 0 then variant.on_hand else 0)) + + else + onHand = "error" + onHand + + +filterSubmitProducts = (productsToFilter) -> + filteredProducts = [] + if productsToFilter instanceof Object + angular.forEach productsToFilter, (product) -> + if product.hasOwnProperty("id") + filteredProduct = {} + filteredVariants = [] + if product.hasOwnProperty("variants") + angular.forEach product.variants, (variant) -> + if not variant.deleted_at? and variant.hasOwnProperty("id") + hasUpdateableProperty = false + filteredVariant = {} + filteredVariant.id = variant.id + if variant.hasOwnProperty("on_hand") + filteredVariant.on_hand = variant.on_hand + hasUpdatableProperty = true + if variant.hasOwnProperty("price") + filteredVariant.price = variant.price + hasUpdatableProperty = true + filteredVariants.push filteredVariant if hasUpdatableProperty + + hasUpdatableProperty = false + filteredProduct.id = product.id + if product.hasOwnProperty("name") + filteredProduct.name = product.name + hasUpdatableProperty = true + if product.hasOwnProperty("supplier") + filteredProduct.supplier_id = product.supplier.id + hasUpdatableProperty = true + if product.hasOwnProperty("price") + filteredProduct.price = product.price + hasUpdatableProperty = true + if product.hasOwnProperty("on_hand") and filteredVariants.length is 0 #only update if no variants present + filteredProduct.on_hand = product.on_hand + hasUpdatableProperty = true + if product.hasOwnProperty("available_on") + filteredProduct.available_on = product.available_on + hasUpdatableProperty = true + if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails + filteredProduct.variants_attributes = filteredVariants + hasUpdatableProperty = true + filteredProducts.push filteredProduct if hasUpdatableProperty + + filteredProducts + + +addDirtyProperty = (dirtyObjects, objectID, propertyName, propertyValue) -> + if dirtyObjects.hasOwnProperty(objectID) + dirtyObjects[objectID][propertyName] = propertyValue + else + dirtyObjects[objectID] = {} + dirtyObjects[objectID]["id"] = objectID + dirtyObjects[objectID][propertyName] = propertyValue + + +removeCleanProperty = (dirtyObjects, objectID, propertyName) -> + delete dirtyObjects[objectID][propertyName] if dirtyObjects.hasOwnProperty(objectID) and dirtyObjects[objectID].hasOwnProperty(propertyName) + delete dirtyObjects[objectID] if dirtyObjects.hasOwnProperty(objectID) and Object.keys(dirtyObjects[objectID]).length <= 1 + + +toObjectWithIDKeys = (array) -> + object = {} + + for i of array + if array[i] instanceof Object and array[i].hasOwnProperty("id") + object[array[i].id] = angular.copy(array[i]) + object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array + + object diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js index 8c48c93db7..25c23ff83c 100644 --- a/config/ng-test.conf.js +++ b/config/ng-test.conf.js @@ -8,7 +8,7 @@ files = [ 'app/assets/javascripts/shared/angular-*.js', 'app/assets/javascripts/admin/order_cycle.js.erb.coffee', - 'app/assets/javascripts/admin/bulk_product_update.js', + 'app/assets/javascripts/admin/bulk_product_update.js.coffee', 'spec/javascripts/unit/**/*.js*' ];