diff --git a/brewman/static/partial/recipe-list.html b/brewman/static/partial/recipe-list.html index ad51831b..a4ab179b 100644 --- a/brewman/static/partial/recipe-list.html +++ b/brewman/static/partial/recipe-list.html @@ -1,7 +1,7 @@
Name | +Type | Valid From | Valid To | Cost Price | @@ -34,6 +41,7 @@
---|---|---|---|---|
{{items.Name}} | +{{items.ProductGroup}} | {{item.ValidFrom}} | {{item.ValidTo}} | {{item.CostPrice | currency}} | diff --git a/brewman/static/scripts/angular_service.js b/brewman/static/scripts/angular_service.js index 2de838e1..9ecac5a2 100644 --- a/brewman/static/scripts/angular_service.js +++ b/brewman/static/scripts/angular_service.js @@ -366,7 +366,7 @@ overlordService.factory('Tokenizer', ['$filter', function ($filter) { return expression.Comparator(item[expression.Col], expression.Value); }); }); - arrayCopy = sortPredicates.length === 0 ? arrayCopy : $filter('orderBy')(arrayCopy, sortPredicates); + arrayCopy = $filter('orderBy')(arrayCopy, sortPredicates); return arrayCopy; }); diff --git a/brewman/static/scripts/product.js b/brewman/static/scripts/product.js index 5de37ebb..0f63a4b3 100644 --- a/brewman/static/scripts/product.js +++ b/brewman/static/scripts/product.js @@ -17,8 +17,9 @@ var ProductListController = ['$scope', '$location', '$routeParams', 'Tokenizer', $scope._isPurchased = isPurchased; var re = /(('[p]'|"[p]"|[p]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, matches = $scope.search.match(re), - st = isPurchased === 'null' ? '' : 'p:' + isPurchased; - $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + st = isPurchased === 'null' ? '' : 'p:' + isPurchased, + search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + $scope.search = search.trim(); }; $scope.isSold = function (isSold) { @@ -28,8 +29,9 @@ var ProductListController = ['$scope', '$location', '$routeParams', 'Tokenizer', $scope._isSold = isSold; var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, matches = $scope.search.match(re), - st = isSold === 'null' ? '' : 's:' + isSold; - $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + st = isSold === 'null' ? '' : 's:' + isSold, + search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + $scope.search = search.trim(); }; $scope.productGroup = function (productGroup) { @@ -40,8 +42,9 @@ var ProductListController = ['$scope', '$location', '$routeParams', 'Tokenizer', $scope._productGroup = productGroup; var re = /(('[t]'|"[t]"|[t]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, matches = $scope.search.match(re), - st = 't:"' + productGroup + '"'; - $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + st = 't:"' + productGroup + '"', + search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + $scope.search = search.trim(); }; $scope.$watch('search', function (value) { diff --git a/brewman/static/scripts/recipe.js b/brewman/static/scripts/recipe.js index 4bce7a61..0443354b 100644 --- a/brewman/static/scripts/recipe.js +++ b/brewman/static/scripts/recipe.js @@ -1,80 +1,119 @@ 'use strict'; -var RecipeListController = ['$scope', 'recipes', '$location', '$routeParams', 'Tokenizer', function ($scope, recipes, $location, $routeParams, Tokenizer) { - $scope.info = recipes; - $scope.search = $routeParams.q || ''; +var RecipeListController = ['$scope', 'recipes', '$location', '$routeParams', '$filter', 'Tokenizer', function ($scope, recipes, $location, $routeParams, $filter, Tokenizer) { + $scope.info = recipes; + $scope.search = $routeParams.q || ''; + $scope.product_groups = _.reduce(recipes, function (acculumator, item) { + if (!acculumator.includes(item.ProductGroup)){ + acculumator.push(item.ProductGroup); + } + return acculumator; + },[]).sort(); - $scope.startDate = function (startDate) { - if (arguments.length === 0) { - return $scope._startDate; + $scope.startDate = function (startDate) { + if (arguments.length === 0) { + return $scope._startDate; + } + $scope._startDate = startDate; + var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, + matches = $scope.search.match(re), + st = startDate === null ? '' : 's:>=' + moment(startDate).format('DD-MMM-YYYY'); + $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + }; + + $scope.finishDate = function (finishDate) { + if (arguments.length === 0) { + return $scope._finishDate; + } + $scope._finishDate = finishDate; + var re = /(('[f]'|"[f]"|[f]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, + matches = $scope.search.match(re), + st = finishDate === null ? '' : 'f:<=' + moment(finishDate).format('DD-MMM-YYYY'); + $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + }; + + $scope.productGroup = function (productGroup) { + if (arguments.length === 0) { + + return $scope._productGroup; + } + $scope._productGroup = productGroup; + var re = /(('[t]'|"[t]"|[t]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, + matches = $scope.search.match(re), + st = 't:"' + productGroup + '"', + search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); + $scope.search = search.trim(); + }; + + $scope.$watch('search', function (value) { + $scope.filterRecipes(value); + }, true); + + $scope.$on('$destroy', function () { + Tokenizer.doFilter.cache = {}; + }); + + $scope.searchInfo = { + comparator: { + 'n': {'Col': 'Name', 'Comparator': 'text'}, + 't': {'Col': 'ProductGroup', 'Comparator': 'text'}, + 's': {'Col': 'Prices.ValidFrom', 'Comparator': 'date'}, + 'f': {'Col': 'Prices.ValidTo', 'Comparator': 'date'} + }, + def: 'n', + sorter: { + 'n': 'Name', + 't': 'ProductGroup', + 's': 'Prices.ValidFrom', + 'f': 'Prices.ValidTo', + 'c': 'Prices.Costing', + 'cp': 'Prices.CostPrice', + 'sp': 'Prices.SalePrice' + }, + flags: {} + }; + + $scope.filterRecipes = _.debounce(function (q) { + if (q !== $scope._search) { + $scope._search = q; + if (angular.isUndefined(q) || q === '') { + $location.path('/Recipes').search('q', null).replace(); + } else { + $location.path('/Recipes').search({'q': q}).replace(); } - $scope._startDate = startDate; - var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, - matches = $scope.search.match(re), - st = startDate === null ? '' : 's:>=' + moment(startDate).format('DD-MMM-YYYY'); - $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); - }; + $scope.$apply(function () { + $scope.recipes = $scope.doFilter(q); + }); + } + }, 350); + $scope.doFilter = _.memoize(function (q) { + var matches = Tokenizer.parseFilterString(q, $scope.searchInfo), + array = angular.copy($scope.info); - $scope.finishDate = function (finishDate) { - if (arguments.length === 0) { - return $scope._finishDate; - } - $scope._finishDate = finishDate; - var re = /(('[f]'|"[f]"|[f]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, - matches = $scope.search.match(re), - st = finishDate === null ? '' : 'f:<=' + moment(finishDate).format('DD-MMM-YYYY'); - $scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); - }; - - $scope.$watch('search', function (value) { - $scope.filterRecipes(value); - }, true); - - $scope.$on('$destroy', function () { - Tokenizer.doFilter.cache = {}; + array = $scope.subFilter(null, array, matches.q, matches.o); + array.forEach(function (prod) { + prod.Prices = $scope.subFilter('Prices.', prod.Prices, matches.q, matches.o); }); + return array; + }); - $scope.searchInfo = { - comparator: { - 'n': {'Col': 'Name', 'Comparator': 'text'}, - 's': {'Col': 'ValidFrom', 'Comparator': 'date'}, - 'f': {'Col': 'ValidTo', 'Comparator': 'date'} - }, - def: 'n', - sorter: { - 'n': 'Name', - 's': 'ValidFrom', - 'f': 'ValidTo', - 'c': 'Costing', - 'cp': 'CostPrice', - 'sp': 'SalePrice' - }, - flags: {} - }; - - $scope.filterRecipes = _.debounce(function (q) { - if (q !== $scope._search) { - $scope._search = q; - if (angular.isUndefined(q) || q === '') { - $location.path('/Recipes').search('q', null).replace(); - } else { - $location.path('/Recipes').search({'q': q}).replace(); - } - $scope.$apply(function () { - var matches = Tokenizer.parseFilterString(q, $scope.searchInfo); - $scope.recipes = _.map($scope.info, function (prod) { - var item = angular.copy(prod); - item.Prices = Tokenizer.doFilter(prod.ProductID + ' ' + q, prod.Prices, matches); - return item; - }); - - - }); - } - }, 350); - - }] - ; + $scope.subFilter = function (prefix, arr, q, o) { + q = angular.copy(q); + o = angular.copy(o); + if (prefix === null) { + q = q.filter(x => !x.Col.includes('.')); + o = o.filter(x => !x.includes('.')); + } else { + q = q.filter(x => x.Col.startsWith(prefix)); + q.forEach(function (x) {x.Col = x.Col.substr(prefix.length)}); + o = o.filter(x => x.substr(1).startsWith(prefix)).map(x => x[0] + x.substr(prefix.length + 1)); + } + if (q.length !== 0) { + arr = arr.filter(item => q.every(exp => exp.Comparator(item[exp.Col], exp.Value))); + } + return $filter('orderBy')(arr, o); + } +}]; RecipeListController.resolve = { recipes: ['Recipe', function (Recipe) { @@ -181,7 +220,12 @@ var RecipeController = ['$scope', '$location', '$window', '$modal', 'asDateFilte }); }; $scope.products = function ($viewValue, options) { - return Product.autocomplete(angular.extend({term: $viewValue, count: 20, a: true, e: true}, options)).$promise; + return Product.autocomplete(angular.extend({ + term: $viewValue, + count: 20, + a: true, + e: true + }, options)).$promise; }; diff --git a/brewman/views/recipe.py b/brewman/views/recipe.py index 514cf879..630ef021 100644 --- a/brewman/views/recipe.py +++ b/brewman/views/recipe.py @@ -226,7 +226,8 @@ def show_list(request): product_id = None for item in list: if item.product_id != product_id: - recipe = {'ProductID': item.product_id, 'Name': item.product.name, 'Prices': []} + recipe = {'ProductID': item.product_id, 'Name': item.product.name, + 'ProductGroup': item.product.product_group.name, 'Prices': []} recipes.append(recipe) product_id = item.product_id costing = 0