Feature: Recipe list shows the group and is also sortable on name, group and dates, etc.

This commit is contained in:
tanshu 2016-03-17 18:18:09 +05:30
parent 1439befc46
commit e9504753a5
5 changed files with 136 additions and 80 deletions

View File

@ -1,7 +1,7 @@
<h1>Recipes</h1> <h1>Recipes</h1>
<div layout="row"> <div layout="row">
<md-input-container flex="grow"> <md-input-container flex="grow">
<input type="text" ng-model="search" md-autofocus placeholder="Search: n:Name, s:Start Date, f:Finish Date"> <input type="text" ng-model="search" md-autofocus placeholder="Search: n:Name, t:Type, s:Start Date, f:Finish Date, c: Costing, cp: CostPrice, sp: SalePrice">
</md-input-container> </md-input-container>
<md-input-container flex> <md-input-container flex>
<md-button ng-href="/Recipe">Add <i <md-button ng-href="/Recipe">Add <i
@ -19,11 +19,18 @@
<md-datepicker md-placeholder="Finish Date" ng-model="finishDate" <md-datepicker md-placeholder="Finish Date" ng-model="finishDate"
ng-model-options="{ getterSetter: true }"></md-datepicker> ng-model-options="{ getterSetter: true }"></md-datepicker>
</div> </div>
<md-input-container flex="30">
<label>Product Group</label>
<md-select ng-model="productGroup" ng-model-options="{ getterSetter: true }" placeholder="Product Group">
<md-option ng-repeat="pg in product_groups" value="{{pg}}">{{pg}}</md-option>
</md-select>
</md-input-container>
</div> </div>
<table class="table table-condensed table-bordered table-striped"> <table class="table table-condensed table-bordered table-striped">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Type</th>
<th>Valid From</th> <th>Valid From</th>
<th>Valid To</th> <th>Valid To</th>
<th class="text-right">Cost Price</th> <th class="text-right">Cost Price</th>
@ -34,6 +41,7 @@
<tbody ng-repeat="items in recipes"> <tbody ng-repeat="items in recipes">
<tr ng-repeat="item in items.Prices"> <tr ng-repeat="item in items.Prices">
<td ng-if="$first" rowspan="{{items.Prices.length}}">{{items.Name}}</td> <td ng-if="$first" rowspan="{{items.Prices.length}}">{{items.Name}}</td>
<td ng-if="$first" rowspan="{{items.Prices.length}}">{{items.ProductGroup}}</td>
<td><a href="{{item.Url}}">{{item.ValidFrom}}</a></td> <td><a href="{{item.Url}}">{{item.ValidFrom}}</a></td>
<td>{{item.ValidTo}}</td> <td>{{item.ValidTo}}</td>
<td class="text-right">{{item.CostPrice | currency}}</td> <td class="text-right">{{item.CostPrice | currency}}</td>

View File

@ -366,7 +366,7 @@ overlordService.factory('Tokenizer', ['$filter', function ($filter) {
return expression.Comparator(item[expression.Col], expression.Value); return expression.Comparator(item[expression.Col], expression.Value);
}); });
}); });
arrayCopy = sortPredicates.length === 0 ? arrayCopy : $filter('orderBy')(arrayCopy, sortPredicates); arrayCopy = $filter('orderBy')(arrayCopy, sortPredicates);
return arrayCopy; return arrayCopy;
}); });

View File

@ -17,8 +17,9 @@ var ProductListController = ['$scope', '$location', '$routeParams', 'Tokenizer',
$scope._isPurchased = isPurchased; $scope._isPurchased = isPurchased;
var re = /(('[p]'|"[p]"|[p]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, var re = /(('[p]'|"[p]"|[p]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi,
matches = $scope.search.match(re), matches = $scope.search.match(re),
st = isPurchased === 'null' ? '' : 'p:' + isPurchased; st = isPurchased === 'null' ? '' : 'p:' + isPurchased,
$scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st);
$scope.search = search.trim();
}; };
$scope.isSold = function (isSold) { $scope.isSold = function (isSold) {
@ -28,8 +29,9 @@ var ProductListController = ['$scope', '$location', '$routeParams', 'Tokenizer',
$scope._isSold = isSold; $scope._isSold = isSold;
var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi,
matches = $scope.search.match(re), matches = $scope.search.match(re),
st = isSold === 'null' ? '' : 's:' + isSold; st = isSold === 'null' ? '' : 's:' + isSold,
$scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st);
$scope.search = search.trim();
}; };
$scope.productGroup = function (productGroup) { $scope.productGroup = function (productGroup) {
@ -40,8 +42,9 @@ var ProductListController = ['$scope', '$location', '$routeParams', 'Tokenizer',
$scope._productGroup = productGroup; $scope._productGroup = productGroup;
var re = /(('[t]'|"[t]"|[t]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, var re = /(('[t]'|"[t]"|[t]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi,
matches = $scope.search.match(re), matches = $scope.search.match(re),
st = 't:"' + productGroup + '"'; st = 't:"' + productGroup + '"',
$scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st); search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st);
$scope.search = search.trim();
}; };
$scope.$watch('search', function (value) { $scope.$watch('search', function (value) {

View File

@ -1,80 +1,119 @@
'use strict'; 'use strict';
var RecipeListController = ['$scope', 'recipes', '$location', '$routeParams', 'Tokenizer', function ($scope, recipes, $location, $routeParams, Tokenizer) { var RecipeListController = ['$scope', 'recipes', '$location', '$routeParams', '$filter', 'Tokenizer', function ($scope, recipes, $location, $routeParams, $filter, Tokenizer) {
$scope.info = recipes; $scope.info = recipes;
$scope.search = $routeParams.q || ''; $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) { $scope.startDate = function (startDate) {
if (arguments.length === 0) { if (arguments.length === 0) {
return $scope._startDate; 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; $scope.$apply(function () {
var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi, $scope.recipes = $scope.doFilter(q);
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); }, 350);
}; $scope.doFilter = _.memoize(function (q) {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo),
array = angular.copy($scope.info);
$scope.finishDate = function (finishDate) { array = $scope.subFilter(null, array, matches.q, matches.o);
if (arguments.length === 0) { array.forEach(function (prod) {
return $scope._finishDate; prod.Prices = $scope.subFilter('Prices.', prod.Prices, matches.q, matches.o);
}
$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 = {};
}); });
return array;
});
$scope.searchInfo = { $scope.subFilter = function (prefix, arr, q, o) {
comparator: { q = angular.copy(q);
'n': {'Col': 'Name', 'Comparator': 'text'}, o = angular.copy(o);
's': {'Col': 'ValidFrom', 'Comparator': 'date'}, if (prefix === null) {
'f': {'Col': 'ValidTo', 'Comparator': 'date'} q = q.filter(x => !x.Col.includes('.'));
}, o = o.filter(x => !x.includes('.'));
def: 'n', } else {
sorter: { q = q.filter(x => x.Col.startsWith(prefix));
'n': 'Name', q.forEach(function (x) {x.Col = x.Col.substr(prefix.length)});
's': 'ValidFrom', o = o.filter(x => x.substr(1).startsWith(prefix)).map(x => x[0] + x.substr(prefix.length + 1));
'f': 'ValidTo', }
'c': 'Costing', if (q.length !== 0) {
'cp': 'CostPrice', arr = arr.filter(item => q.every(exp => exp.Comparator(item[exp.Col], exp.Value)));
'sp': 'SalePrice' }
}, return $filter('orderBy')(arr, o);
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);
}]
;
RecipeListController.resolve = { RecipeListController.resolve = {
recipes: ['Recipe', function (Recipe) { recipes: ['Recipe', function (Recipe) {
@ -181,7 +220,12 @@ var RecipeController = ['$scope', '$location', '$window', '$modal', 'asDateFilte
}); });
}; };
$scope.products = function ($viewValue, options) { $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;
}; };

View File

@ -226,7 +226,8 @@ def show_list(request):
product_id = None product_id = None
for item in list: for item in list:
if item.product_id != product_id: 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) recipes.append(recipe)
product_id = item.product_id product_id = item.product_id
costing = 0 costing = 0