Cleaning - Article autocomplete, ForestDisplay and more
K-Psul - Improve article autocompletion. ArticleManager - "selected" property becomes a reference to an article in the data properties. ForestDisplay - Add data property "object" linked to object being represented - Use class to identify objects instead of id. Allow multiple displays of same ModelForest. - New get_class method returns the class selector to find an object container in the DOM. - New get_dom method returns the DOM element from an object in the ModelForest. Cancellation view - Fix 500 on cancel with already canceled opes/transfers
This commit is contained in:
parent
51083f9195
commit
eff1b7ff19
5 changed files with 121 additions and 132 deletions
|
@ -254,6 +254,8 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
#article_selection {
|
||||
height:40px;
|
||||
width:100%;
|
||||
border-bottom: 1px solid #c8102e;
|
||||
line-height: 39px;
|
||||
}
|
||||
|
||||
#article_selection input, #article_selection span {
|
||||
|
@ -261,7 +263,6 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
float:left;
|
||||
border:0;
|
||||
border-right:1px solid #c8102e;
|
||||
border-bottom:1px solid #c8102e;
|
||||
border-radius:0;
|
||||
font-size:16px;
|
||||
font-weight:bold;
|
||||
|
@ -276,27 +277,17 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
padding-left:10px;
|
||||
}
|
||||
|
||||
#article_number {
|
||||
#article_number, #article_stock {
|
||||
width:10%;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
#article_stock {
|
||||
width:10%;
|
||||
line-height:38px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
@media (min-width:1200px) {
|
||||
#article_autocomplete {
|
||||
width:84%
|
||||
}
|
||||
|
||||
#article_number {
|
||||
width:8%;
|
||||
}
|
||||
|
||||
#article_stock {
|
||||
#article_number, #article_stock {
|
||||
width:8%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,12 +145,14 @@ class KHistory {
|
|||
'canceled_at': ope.canceled_at,
|
||||
'canceled_by': ope.canceled_by,
|
||||
};
|
||||
if (ope.modelname === 'ope') {
|
||||
this.data.update('purchase', ope.id, update_data)
|
||||
|| this.data.update('specialope', ope.id, update_data);
|
||||
} else if (ope.modelname === 'transfer') {
|
||||
this.data.update('transfer', ope.id, update_data);
|
||||
}
|
||||
|
||||
let model;
|
||||
if (ope.modelname === 'ope')
|
||||
model = Operation;
|
||||
else if (ope.modelname === 'transfer')
|
||||
model = Transfer;
|
||||
|
||||
this.data.update(model, ope.id, update_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,8 +167,6 @@ class KHistory {
|
|||
}
|
||||
}
|
||||
|
||||
var nb_opes = this._$container.find('.ope[canceled="false"]').length;
|
||||
$('#nb_opes').text(nb_opes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,14 +198,16 @@ class KHistorySelection {
|
|||
}
|
||||
|
||||
get_selected() {
|
||||
var selected = {'transfers': [], 'opes': [],};
|
||||
var selected = {
|
||||
transfers: [],
|
||||
opes: []
|
||||
};
|
||||
this._$container.find('.ope.ui-selected').each(function() {
|
||||
var [type, id] = $(this).parent().attr('id').split('-');
|
||||
|
||||
if (type === 'transfer')
|
||||
selected['transfers'].push(id);
|
||||
let object = $(this).parent().data("object");
|
||||
if (object instanceof Transfer)
|
||||
selected.transfers.push(object.id);
|
||||
else
|
||||
selected['opes'].push(id);
|
||||
selected.opes.push(object.id);
|
||||
});
|
||||
|
||||
return selected;
|
||||
|
|
|
@ -623,7 +623,7 @@ class HistoryGroup extends ModelObject {
|
|||
* @see {@link Models.ModelObject.props|ModelObject.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ['id', 'at', 'comment', 'valid_by', 'day'];
|
||||
return ['id', 'at', 'comment', 'valid_by'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -634,8 +634,7 @@ class HistoryGroup extends ModelObject {
|
|||
*/
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': 0, 'at': moment(), 'comment': '',
|
||||
'valid_by': '', 'day': new Day()
|
||||
'id': 0, 'at': null, 'comment': '', 'valid_by': ''
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -757,7 +756,7 @@ class Operation extends ModelObject {
|
|||
*/
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': '', 'amount': 0, 'canceled_at': undefined, 'canceled_by': '',
|
||||
'id': '', 'amount': 0, 'canceled_at': null, 'canceled_by': '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -769,7 +768,7 @@ class Operation extends ModelObject {
|
|||
if (v)
|
||||
this._canceled_at = dateUTCToParis(v);
|
||||
else
|
||||
this._canceled_at = undefined;
|
||||
this._canceled_at = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1435,7 +1434,8 @@ class ForestDisplay {
|
|||
options = options || {};
|
||||
|
||||
var $container = $('<div></div>');
|
||||
$container.attr('id', modelname+'-'+node.id);
|
||||
$container.addClass(this.get_class(node));
|
||||
$container.data('object', node);
|
||||
|
||||
var $rendered = node.display($(template), options);
|
||||
$container.append($rendered);
|
||||
|
@ -1459,6 +1459,14 @@ class ForestDisplay {
|
|||
return $container;
|
||||
}
|
||||
|
||||
get_class(object) {
|
||||
return `${object.constructor.verbose_name}-${object.id}`;
|
||||
}
|
||||
|
||||
get_dom(object) {
|
||||
return this._$container.find("."+this.get_class(object));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders node and adds it to the container.<br>
|
||||
|
@ -1470,15 +1478,15 @@ class ForestDisplay {
|
|||
var existing = this.data.get_parent(node);
|
||||
var first_missing = node;
|
||||
|
||||
while (existing && !(this._$container.find('#'+existing.modelname+'-'+existing.id))) {
|
||||
while (existing && !(this.get_dom(existing))) {
|
||||
first_missing = existing;
|
||||
existing = this.data.get_parent(existing);
|
||||
}
|
||||
|
||||
var $to_insert = this.render_element(first_missing, options);
|
||||
if (existing)
|
||||
this._$container
|
||||
.find('#'+existing.constructor.verbose_name+'-'+existing.id+'>.children')
|
||||
this.get_dom(existing)
|
||||
.children(".children")
|
||||
.prepend($to_insert);
|
||||
else
|
||||
this._$container.prepend($to_insert);
|
||||
|
@ -1492,12 +1500,9 @@ class ForestDisplay {
|
|||
render(options) {
|
||||
var forest = this.data;
|
||||
|
||||
if (forest.is_empty())
|
||||
return;
|
||||
|
||||
if (forest.constructor.root_sort)
|
||||
forest.roots.sort(forest.constructor.root_sort);
|
||||
else
|
||||
else if (forest.roots.length)
|
||||
forest.roots.sort(forest.roots[0].constructor.compare);
|
||||
|
||||
for (let root of forest.roots) {
|
||||
|
@ -1515,13 +1520,12 @@ class ForestDisplay {
|
|||
var modelname = data.constructor.verbose_name;
|
||||
var $new_elt = data.display($(this._templates[modelname]), {});
|
||||
|
||||
var $to_replace = this._$container.find('#'+modelname+'-'+data.id+'>:first-child');
|
||||
var $to_replace = this.get_dom(data).children(":first-child");
|
||||
$to_replace.replaceWith($new_elt);
|
||||
}
|
||||
|
||||
delete(data) {
|
||||
let modelname = data.constructor.verbose_name;
|
||||
this._$container.find('#'+modelname+'-'+data.id).remove();
|
||||
this.get_dom(data).remove();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2055,7 +2059,7 @@ class ItemBasketFormatter extends Formatter {
|
|||
class PurchaseBasketFormatter extends ItemBasketFormatter {
|
||||
|
||||
static get attrs() {
|
||||
return ['article_id', 'low_stock'];
|
||||
return ['low_stock'];
|
||||
}
|
||||
|
||||
static prop_number(o) {
|
||||
|
@ -2066,10 +2070,6 @@ class PurchaseBasketFormatter extends ItemBasketFormatter {
|
|||
return o.article.name;
|
||||
}
|
||||
|
||||
static attr_article_id(o) {
|
||||
return o.article.id;
|
||||
}
|
||||
|
||||
static attr_low_stock(o) {
|
||||
let stock = o.article.stock;
|
||||
return -5 <= stock && stock <= 5;
|
||||
|
|
|
@ -32,7 +32,7 @@ class KPsulManager {
|
|||
this.checkout_manager.reset();
|
||||
this.previous_basket.reset();
|
||||
Config.reset( () => {
|
||||
this.article_manager.reset_data();
|
||||
this.article_manager.fetch_data();
|
||||
this.history.fetch();
|
||||
});
|
||||
}
|
||||
|
@ -282,7 +282,9 @@ class AccountSearch {
|
|||
|
||||
class CheckoutManager {
|
||||
|
||||
constructor() {
|
||||
constructor(kpsul) {
|
||||
this.kpsul = kpsul;
|
||||
|
||||
this._$container = $('#checkout');
|
||||
this.display_prefix = '#checkout-';
|
||||
|
||||
|
@ -314,9 +316,8 @@ class CheckoutManager {
|
|||
|
||||
this.checkout.get_by_apipk(id, api_options)
|
||||
.done( (data) => this._update_on_success(data) )
|
||||
.fail( () => this.reset_data() );
|
||||
|
||||
kpsul.focus();
|
||||
.fail( () => this.reset_data() )
|
||||
.always( () => this.kpsul.focus() );
|
||||
}
|
||||
|
||||
_update_on_success(data) {
|
||||
|
@ -445,7 +446,7 @@ class ArticleManager {
|
|||
this._$nb = $('#article_number');
|
||||
this._$stock = $('#article_stock');
|
||||
|
||||
this.selected = new Article();
|
||||
this.selected = null;
|
||||
this.data = new ArticleList();
|
||||
var $container = $('#articles_data');
|
||||
var templates = {
|
||||
|
@ -464,7 +465,7 @@ class ArticleManager {
|
|||
}
|
||||
|
||||
validate(article) {
|
||||
this.selected.from(article);
|
||||
this.selected = article;
|
||||
this._$input.val(article.name);
|
||||
this._$nb.val('1');
|
||||
this._$stock.text('/'+article.stock);
|
||||
|
@ -472,22 +473,20 @@ class ArticleManager {
|
|||
}
|
||||
|
||||
unset() {
|
||||
this.selected.clear();
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
is_empty() {
|
||||
return this.selected.is_empty();
|
||||
return !this.selected || this.selected.is_empty();
|
||||
}
|
||||
|
||||
reset_data() {
|
||||
this.display.clear();
|
||||
this.data.clear();
|
||||
fetch_data() {
|
||||
this.data.fromAPI();
|
||||
}
|
||||
|
||||
update_data(data) {
|
||||
for (let article_dict of data.articles)
|
||||
this.data.update('article', article_dict.id, article_dict);
|
||||
for (let article_data of data.articles)
|
||||
this.data.update('article', article_data.id, article_data);
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
@ -514,15 +513,13 @@ class ArticleManager {
|
|||
});
|
||||
|
||||
this._$container.on('click', '.article', function() {
|
||||
var id = $(this).parent().attr('id').split('-')[1];
|
||||
var article = that.data.find('article', id);
|
||||
if (article)
|
||||
let article = $(this).parent().data("object");
|
||||
that.validate(article);
|
||||
});
|
||||
|
||||
this._$nb.on('keydown', function(e) {
|
||||
if (e.keyCode == 13 && that.constructor.check_nb(that.nb) && !that.is_empty()) {
|
||||
that.kpsul.basket.add_purchase(that.selected.id, parseInt(that.nb));
|
||||
that.kpsul.basket.add_purchase(that.selected, parseInt(that.nb));
|
||||
that.reset().focus();
|
||||
}
|
||||
|
||||
|
@ -573,85 +570,78 @@ class ArticleAutocomplete {
|
|||
_init_events() {
|
||||
var that = this;
|
||||
|
||||
// 8:Backspace|9:Tab|13:Enter|46:DEL|112-117:F1-6|119-123:F8-F12
|
||||
var normalKeys = /^(8|9|13|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
var arrowKeys = /^(37|38|39|40)$/;
|
||||
// 8:Backspace|9:Tab|13:Enter|35:End|36:Home|37-40:Arrows|46:DEL|112-123:F1-F12
|
||||
var normalKeys = /^(8|9|13|35|36|37|38|39|40|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
|
||||
this._$input
|
||||
.on('keydown', function(e) {
|
||||
var text = that._$input.val();
|
||||
if (normalKeys.test(e.keyCode) || arrowKeys.test(e.keyCode) || e.ctrlKey) {
|
||||
// For the backspace key, we suppose the cursor is at the very end
|
||||
if(e.keyCode == 8) {
|
||||
that.update(text.substring(0, text.length-1), true);
|
||||
}
|
||||
|
||||
if (normalKeys.test(e.keyCode) || e.ctrlKey)
|
||||
return true;
|
||||
}
|
||||
that.update(text+e.key, false);
|
||||
|
||||
let initial = that._$input.val();
|
||||
let future = initial.substr(0, this.selectionStart)
|
||||
+ e.key
|
||||
+ initial.substr(this.selectionEnd);
|
||||
|
||||
that.update(future);
|
||||
|
||||
return false;
|
||||
|
||||
});
|
||||
|
||||
this._$input
|
||||
.on('input', () => this.update(this._$input.val(), false));
|
||||
|
||||
}
|
||||
|
||||
update(prefix, backspace) {
|
||||
update(prefix, autofill) {
|
||||
if (autofill === undefined)
|
||||
autofill = true;
|
||||
|
||||
this.resetMatch();
|
||||
var article_list = this.manager.data;
|
||||
var lower = prefix.toLowerCase();
|
||||
var that = this;
|
||||
this.matching = this.find_matching(prefix);
|
||||
|
||||
article_list.traverse('article', function(article) {
|
||||
if (article.name.toLowerCase().startsWith(lower))
|
||||
that.matching.push(article);
|
||||
});
|
||||
|
||||
if (this.matching.length == 1) {
|
||||
if (!backspace) {
|
||||
if (this.matching.length === 1 && autofill) {
|
||||
this.manager.validate(this.matching[0]);
|
||||
this.showAll();
|
||||
} else {
|
||||
this.manager.unset();
|
||||
if (this.matching.length >= 1) {
|
||||
this.updateDisplay();
|
||||
if (autofill)
|
||||
this.updateInput();
|
||||
}
|
||||
} else if (this.matching.length > 1) {
|
||||
this.manager.unset();
|
||||
this.updateDisplay();
|
||||
if (!backspace)
|
||||
this.updatePrefix();
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
var that = this;
|
||||
let that = this;
|
||||
let display = this.manager.display;
|
||||
|
||||
this.manager.data.traverse('category', function(category) {
|
||||
var is_active = false;
|
||||
for (let article of category.articles) {
|
||||
let $article = display.get_dom(article);
|
||||
if (that.matching.indexOf(article) != -1) {
|
||||
is_active = true;
|
||||
that._$container.find('#article-'+article.id).show();
|
||||
$article.show();
|
||||
} else {
|
||||
that._$container.find('#article-'+article.id).hide();
|
||||
$article.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_active) {
|
||||
that._$container.find('#category-'+category.id).show();
|
||||
} else {
|
||||
that._$container.find('#category-'+category.id).hide();
|
||||
}
|
||||
let $category = display.get_dom(category);
|
||||
is_active ? $category.show() : $category.hide();
|
||||
});
|
||||
}
|
||||
|
||||
updatePrefix() {
|
||||
var lower = this.matching.map(function (article) {
|
||||
return article.name.toLowerCase();
|
||||
});
|
||||
updateInput() {
|
||||
if (!this.matching.length)
|
||||
return;
|
||||
|
||||
lower.sort();
|
||||
var first = lower[0], last = lower[lower.length-1],
|
||||
let names = this.matching.map( article => article.name.toLowerCase() ).sort();
|
||||
|
||||
let first = names[0], last = names[names.length-1],
|
||||
length = first.length, i = 0;
|
||||
while (i < length && first.charAt(i) === last.charAt(i)) i++;
|
||||
|
||||
|
@ -659,17 +649,20 @@ class ArticleAutocomplete {
|
|||
}
|
||||
|
||||
showAll() {
|
||||
var that = this;
|
||||
this.resetMatch();
|
||||
this.manager.data.traverse('article', function(article) {
|
||||
that.matching.push(article);
|
||||
});
|
||||
this.matching = this.find_matching("");
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
resetMatch() {
|
||||
this.matching = [];
|
||||
find_matching(start) {
|
||||
let lower = start.toLowerCase();
|
||||
let matching = [];
|
||||
this.manager.data.traverse('article', function(article) {
|
||||
if (article.name.toLowerCase().startsWith(lower))
|
||||
matching.push(article);
|
||||
});
|
||||
return matching;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -717,8 +710,8 @@ class BasketManager {
|
|||
return total;
|
||||
}
|
||||
|
||||
add_purchase(article_id, nb) {
|
||||
let found = this.find_purchase(article_id);
|
||||
add_purchase(article, nb) {
|
||||
let found = this.find_purchase(article);
|
||||
if (found) {
|
||||
let new_nb = found.article_nb + nb;
|
||||
if (new_nb > 0) {
|
||||
|
@ -733,15 +726,15 @@ class BasketManager {
|
|||
} else {
|
||||
let created = this.data.create("purchase", {
|
||||
id: this.formset.new_index(),
|
||||
article: this.kpsul.article_manager.data.find("article", article_id),
|
||||
article: article,
|
||||
article_nb: nb
|
||||
});
|
||||
this.formset.create(created.for_formset());
|
||||
}
|
||||
}
|
||||
|
||||
find_purchase(article_id) {
|
||||
return this.data.find("purchase", (purchase) => purchase.article.id === article_id);
|
||||
find_purchase(article) {
|
||||
return this.data.find("purchase", (purchase) => purchase.article.id === article.id);
|
||||
}
|
||||
|
||||
add_deposit(amount) {
|
||||
|
@ -942,21 +935,24 @@ class BasketSelection {
|
|||
case 46:
|
||||
// DEL (Suppr)
|
||||
basket._$container.find('.ui-selected').each( function() {
|
||||
let dom_id = $(this).parent().attr("id");
|
||||
let id = parseInt(dom_id.split("-")[1]);
|
||||
basket.delete(id);
|
||||
let item = $(this).parent().data("object");
|
||||
basket.delete(item.id);
|
||||
});
|
||||
break;
|
||||
case 38:
|
||||
// Arrow up
|
||||
basket._$container.find('.ui-selected').each( function() {
|
||||
basket.add_purchase(parseInt($(this).attr('article_id')), 1);
|
||||
let item = $(this).parent().data("object");
|
||||
if (item instanceof PurchaseBasket)
|
||||
basket.add_purchase(item.article, 1);
|
||||
});
|
||||
break;
|
||||
case 40:
|
||||
// Arrow down
|
||||
basket._$container.find('.ui-selected').each( function() {
|
||||
basket.add_purchase(parseInt($(this).attr('article_id')), -1);
|
||||
let item = $(this).parent().data("object");
|
||||
if (item instanceof PurchaseBasket)
|
||||
basket.add_purchase(item.article, -1);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1286,7 +1286,7 @@ def kpsul_cancel_operations(request):
|
|||
data['errors']['opes_notexisting'] = opes_notexisting
|
||||
return JsonResponse(data, status=400)
|
||||
|
||||
already_canceled = {} # Opération/Transfert déjà annulé
|
||||
already_canceled = defaultdict(list)
|
||||
opes = [] # Pas déjà annulée
|
||||
transfers = []
|
||||
required_perms = set()
|
||||
|
|
Loading…
Reference in a new issue