diff --git a/kfet/static/kfet/css/kpsul.css b/kfet/static/kfet/css/kpsul.css
index 32331d07..2631d333 100644
--- a/kfet/static/kfet/css/kpsul.css
+++ b/kfet/static/kfet/css/kpsul.css
@@ -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%;
}
}
diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js
index ca693e1a..2b634280 100644
--- a/kfet/static/kfet/js/history.js
+++ b/kfet/static/kfet/js/history.js
@@ -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;
diff --git a/kfet/static/kfet/js/kfet.api.js b/kfet/static/kfet/js/kfet.api.js
index 9900fe21..e92d9f54 100644
--- a/kfet/static/kfet/js/kfet.api.js
+++ b/kfet/static/kfet/js/kfet.api.js
@@ -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 = $('
');
- $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.
@@ -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;
diff --git a/kfet/static/kfet/js/kpsul.js b/kfet/static/kfet/js/kpsul.js
index 64508ed6..447dde14 100644
--- a/kfet/static/kfet/js/kpsul.js
+++ b/kfet/static/kfet/js/kpsul.js
@@ -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)
- that.validate(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) {
- this.manager.validate(this.matching[0]);
- this.showAll();
- } else {
- this.manager.unset();
- this.updateDisplay();
- }
- } else if (this.matching.length > 1) {
+ if (this.matching.length === 1 && autofill) {
+ this.manager.validate(this.matching[0]);
+ this.showAll();
+ } else {
this.manager.unset();
- this.updateDisplay();
- if (!backspace)
- this.updatePrefix();
+ if (this.matching.length >= 1) {
+ this.updateDisplay();
+ if (autofill)
+ this.updateInput();
+ }
}
}
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;
}
diff --git a/kfet/views.py b/kfet/views.py
index 94f38ec9..1f4495eb 100644
--- a/kfet/views.py
+++ b/kfet/views.py
@@ -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()