mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-03 02:21:33 +00:00
WIP: Convert bulk product update to coffeescript. 3 tests failing.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
380
app/assets/javascripts/admin/bulk_product_update.js.coffee
Normal file
380
app/assets/javascripts/admin/bulk_product_update.js.coffee
Normal file
@@ -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
|
||||
@@ -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*'
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user