From a0503d0c53d5f4f44cd1cb50dbf19054b0fe57bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 18 May 2017 02:13:42 +0200 Subject: [PATCH] Refactor K-Psul basket (big part) K-Psul - Basket refactor - Almost done. ModelForest - Add create method (based on previous get_or_create). Direction defaults to 0. - Add delete method. - Methods find, traverse, update, delete can also take a model (class) as first argument. String representation of model still works. - Fix child linking to parent in create method. ModelForest -> ForestDisplay - (One-way data binding) Changes on a ModelForest are directly reflected on listening ForestDisplay(s). - ArticleManager and KHistory become simpler. Config - Add addcost key, shorthand for double addcost keys check. K-Psul - Improve display for basket summary and previous operation. - Clean js code / duplicates. - Some components gains chance to trigger/handle events. They are really happy. Eg basket amounts and summary are updated thanks to these events if the selected account is changed. Formatters - Fixes addcost and amount display. History - Fix options management (api_options were overrided and K-Psul displayed more than the last day history). - Fix data display, thanks to formatters fixes and modelforest fixes. --- kfet/static/kfet/css/kpsul.css | 70 +++-- kfet/static/kfet/js/history.js | 51 ++-- kfet/static/kfet/js/kfet.api.js | 152 ++++++++--- kfet/static/kfet/js/kfet.js | 2 + kfet/static/kfet/js/kpsul.js | 461 ++++++++++++++++++++++++++++++-- kfet/templates/kfet/kpsul.html | 353 ++---------------------- kfet/views.py | 1 - 7 files changed, 632 insertions(+), 458 deletions(-) diff --git a/kfet/static/kfet/css/kpsul.css b/kfet/static/kfet/css/kpsul.css index d592ec3f..32331d07 100644 --- a/kfet/static/kfet/css/kpsul.css +++ b/kfet/static/kfet/css/kpsul.css @@ -315,7 +315,7 @@ input[type=number]::-webkit-outer-spin-button { } #articles_data .article[data_stock="low"] { - background:rgba(236,100,0,0.3); + background:rgba(236,100,0,0.3); } #articles_data span { @@ -377,7 +377,10 @@ input[type=number]::-webkit-outer-spin-button { #basket_rel, #previous_op { border-top:1px solid #C8102E; - padding-left: 3px; +} + +#basket_rel { + padding-top: 35px; } #basket { @@ -385,61 +388,80 @@ input[type=number]::-webkit-outer-spin-button { } @media (min-width:768px) { - #basket { - margin-right:7px; - } - #basket_rel { + #basket_rel, #previous_op { border-top:0; - margin-left:7px; - margin-right:7px; - } - #previous_op { - border-top:0; - margin-left:7px; + margin-left:15px; } } -#basket table { +#basket > .items { width:100%; } -#basket table tr { +#basket .basket-item { + width: 100%; height:25px; font-size:14px; } -#basket tr .amount { +#basket .basket-item > span { + display: inline-block; +} + +#basket .basket-item .amount { width:70px; padding-right:15px; text-align:right; } -#basket tr .number { - width:50px; +#basket .basket-item .number { + width:75px; padding-right:15px; text-align:right; } -#basket tr .lowstock { - display:none; - padding-right:15px; +#basket .basket-item > .lowstock { + width: 30px; + padding-right: 15px; } -#basket tr.ui-selected, #basket tr.ui-selecting { +#basket .basket-item .glyphicon.lowstock { + display: none; +} + +#basket .basket-item[low_stock=true] .glyphicon.lowstock { + display: inline-block; +} + +#basket .basket-item.ui-selected, +#basket .basket-item.ui-selecting { background-color:rgba(200,16,46,0.6); color:#FFF; } +.basket_summary table { + margin: 0 auto; +} + +.basket_summary table tr td:first-child { + padding-right: 15px; + font-weight: bold; +} + /* History */ #previous_op .trigramme { width:100%; + height: 30px; background-color:rgba(200,16,46,0.85); color:#FFF; font-weight:bold; - padding:3px; - margin-left: -3px; - margin-bottom: 3px; + padding:5px; + margin-bottom: 5px; + text-align:center; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .kpsul_middle_right_col { diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js index 0f07f73b..e110f0cc 100644 --- a/kfet/static/kfet/js/history.js +++ b/kfet/static/kfet/js/history.js @@ -62,7 +62,7 @@ class KHistory { } constructor(options) { - var all_options = $.extend({}, this.constructor.default_options, options); + var all_options = $.extend(true, {}, this.constructor.default_options, options); this.api_options = all_options.api_options; this._$container = $('#history'); @@ -93,20 +93,8 @@ class KHistory { } fetch(api_options) { - this.data.clear(); - $.extend(this.api_options, api_options); - - this.data.fromAPI(this.api_options) - .done( () => this.display_data() ); - - } - - display_data() { - this.display.clear(); - this.display.render(this.data); - var nb_opes = this._$container.find('.ope[canceled="false"]').length; - this._$nb_opes.text(nb_opes); + this.data.fromAPI(this.api_options); } _init_events() { @@ -120,6 +108,16 @@ class KHistory { that.cancel_operations(to_cancel); } }); + + $(this.data).on("changed", function() { + let nb_opes = 0; + that.data.traverse(Operation, function(o) { + if (!o.canceled_at) + nb_opes++; + }); + that._$nb_opes.text(nb_opes); + }); + } cancel_operations(to_cancel) { @@ -137,21 +135,6 @@ class KHistory { }); } - add_node(data) { - var node = this.data.get_or_create(data.modelname, data.content, 0); - this.display.add(node); - } - - update_node(modelname, id, update_data) { - var updated = this.data.update(modelname, id, update_data); - if (!updated) - return false; - - this.display.update(updated); - - return true; - } - is_valid(opegroup) { var options = this.api_options; @@ -190,10 +173,10 @@ class KHistory { 'canceled_by': ope.canceled_by, }; if (ope.modelname === 'ope') { - this.update_node('purchase', ope.id, update_data) - || this.update_node('specialope', ope.id, update_data); + this.data.update('purchase', ope.id, update_data) + || this.data.update('specialope', ope.id, update_data); } else if (ope.modelname === 'transfer') { - this.update_node('transfer', ope.id, update_data); + this.data.update('transfer', ope.id, update_data); } } } @@ -201,11 +184,11 @@ class KHistory { for (let opegroup of opegroups) { if (opegroup['cancellation']) { let update_data = { 'amount': opegroup.amount }; - this.update_node('opegroup', opegroup.id, update_data); + this.data.update('opegroup', opegroup.id, update_data); } if (opegroup['add'] && this.is_valid(opegroup)) { - this.add_node(opegroup); + this.data.create(opegroup.modelname, opegroup.content); } } diff --git a/kfet/static/kfet/js/kfet.api.js b/kfet/static/kfet/js/kfet.api.js index 89ea0143..125eaff7 100644 --- a/kfet/static/kfet/js/kfet.api.js +++ b/kfet/static/kfet/js/kfet.api.js @@ -747,7 +747,7 @@ class Operation extends ModelObject { * @see {@link Models.ModelObject.props|ModelObject.props} */ static get props() { - return ['id', 'amount', 'canceled_at', 'canceled_by', 'group']; + return ['id', 'amount', 'canceled_at', 'canceled_by']; } /** @@ -758,7 +758,6 @@ class Operation extends ModelObject { static get default_data() { return { 'id': '', 'amount': 0, 'canceled_at': undefined, 'canceled_by': '', - 'group': new HistoryGroup() }; } @@ -933,9 +932,16 @@ class ModelForest { * @param {Object[]} [datalist=[]] */ constructor(data) { + this._init_events(); this.from(data || {}); } + _init_events() { + $(this).on("created deleted loaded updated update_all", + (e) => $(this).trigger("changed", [e]) + ); + } + /** * Return true if instance is empty */ @@ -964,26 +970,40 @@ class ModelForest { * @param {Object} data * @param {number} direction */ - get_or_create(modelname, data, direction) { + get_or_create(modelname, data, options) { + if (options === undefined) + options = {}; + + var existing = this.find(modelname, options.find_callback || data.id); + + if (existing) + return existing; + + return this.create(modelname, data, options); + } + + create(modelname, data, options) { + options = $.extend({ + direction: 0, + fire_events: true, + }, options); + var struct = this.constructor.structure; var struct_data = struct[modelname]; var model = struct_data.model; - var existing = this.find(modelname, data.id); - if (existing) { - return existing; - } - var node; if (data instanceof ModelObject) node = data; else node = new model(data); - if (direction <= 0) { + if (options.direction <= 0) { var parent_name = struct_data.parent; if (!parent_name) { this.roots.push(node); + if (options.fire_events) + $(this).trigger("created", [node]); return node; } @@ -1011,24 +1031,33 @@ class ModelForest { parent_modelname = data.parent.modelname; } - var parent = this.get_or_create(parent_modelname, parent_data, -1); + var parent = this.get_or_create(parent_modelname, parent_data, { + direction: -1, + fire_events: false + }); var parent_childname = struct[parent_modelname].children; node[parent_name] = parent; parent[parent_childname].push(node); } - if (direction >= 0) { + if (options.direction >= 0) { var child_name = struct_data.children; if (data.children && data.children.length) { for (let child_data of data.children) { - var child = this.get_or_create(child_data.modelname, child_data.content, 1); - var child_parent = struct[child_data.modelname]; + var child = this.get_or_create(child_data.modelname, child_data.content, { + direction: 1, + fire_events: false + }); + var child_parent = struct[child_data.modelname].parent; child[child_parent] = node; node[child_name].push(child); } } } + if (options.fire_events) + $(this).trigger("created", [node]); + return node; } @@ -1042,9 +1071,12 @@ class ModelForest { this.related = data.related; for (let modelname in data.objects) { for (let obj_data of data.objects[modelname]) - this.get_or_create(modelname, obj_data, 0); + this.get_or_create(modelname, obj_data, { + fire_events: false + }); } } + $(this).trigger("loaded"); } /** @@ -1061,10 +1093,13 @@ class ModelForest { * @param {string} modelname * @param {function} callback */ - traverse(modelname, callback) { + traverse(model, callback) { var that = this; + if (typeof model === "string") + model = this.constructor.structure[model].model; + function recurse(node) { - if (node.constructor.verbose_name === modelname) { + if (node instanceof model) { if (callback(node)) { return true; } @@ -1090,36 +1125,53 @@ class ModelForest { * @param {string} modelname * @param {number} id */ - find(modelname, id) { + find(model, id) { + let test; + if (typeof id === "function") + test = id; + else + test = (node) => (node.id === id); + var result = null; function callback(node) { - if (node.id == id) { + if (test(node)) { result = node; return true; } } - this.traverse(modelname, callback); + this.traverse(model, callback); return result; } - update(modelname, id, update_data) { - var updated = null; + update(model, id, update_data) { + let node = this.find(model, id); - function callback(node) { - if (node.id == id) { - node.update(update_data); - updated = node; - return true; - } + if (node) { + node.update(update_data); + $(this).trigger("updated", [node]); } - this.traverse(modelname, callback); - - return updated; + return node; } + + delete(model, id) { + let node = this.find(model, id); + + if (!node) + return false; + + if (!this.get_parent(node)) { + this.roots.splice(this.roots.indexOf(node), 1); + $(this).trigger("deleted", [node]); + return node; + } + + // TODO: if it is not a root + } + } @@ -1275,9 +1327,27 @@ class ForestDisplay { constructor($container, templates, data) { this._templates = templates; this._$container = $container; - this.data = data || new ModelForest(); + this.data = data; + + this._init_events(); } + _init_events() { + var that = this; + $(this.data).on("created", (e, created) => this.add(created)); + $(this.data).on("deleted", (e, deleted) => this.delete(deleted)); + $(this.data).on("loaded", () => this.display()); + $(this.data).on("updated", (e, updated) => this.update(updated)); + $(this.data).on("update_all", function() { + for (let root of that.data.roots) + that.update(root); + }); + } + + display() { + this.clear(); + this.render(this.data); + } /** * Renders a node (and all its offspring) and returns the @@ -1326,7 +1396,6 @@ class ForestDisplay { * @param {Object} [options] Options for element render method */ add(node, options) { - var struct = this.data.constructor.structure; var existing = this.data.get_parent(node); var first_missing = node; @@ -1336,12 +1405,12 @@ class ForestDisplay { } var $to_insert = this.render_element(first_missing, options); - if (existing) { - this._$container.find('#'+existing.constructor.verbose_name+'-'+existing.id+'>.children') - .prepend($to_insert); - } else { + if (existing) + this._$container + .find('#'+existing.constructor.verbose_name+'-'+existing.id+'>.children') + .prepend($to_insert); + else this._$container.prepend($to_insert); - } } @@ -1379,6 +1448,11 @@ class ForestDisplay { $to_replace.replaceWith($new_elt); } + delete(data) { + let modelname = data.constructor.verbose_name; + this._$container.find('#'+modelname+'-'+data.id).remove(); + } + /** * Clears all elements from container */ @@ -1797,7 +1871,7 @@ class OperationFormatter extends Formatter { * a.amount displayed according to a.is_cof and a.trigramme values. */ static prop_amount(a) { - return amountDisplay(a.amount, a.group.is_cof, a.group.trigramme); + return amountDisplay(a.amount, a.opegroup.is_cof, a.opegroup.trigramme); } /** @@ -1836,7 +1910,7 @@ class PurchaseFormatter extends OperationFormatter { */ static prop_addcost(a) { if (a.addcost_for) { - return '('+amountDisplay(a.addcost_amount, a.is_cof) + return '('+amountDisplay(a.addcost_amount, a.opegroup.is_cof) +'UKF pour '+a.addcost_for+')'; } else { return ''; diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index a7e893b6..0c979bf6 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -116,6 +116,8 @@ class Config { * @param {string} key */ static get(key) { + if (key == "addcost") + return this.get("addcost_for") && this.get("addcost_amount"); return this._get_or_create_config()[key]; } diff --git a/kfet/static/kfet/js/kpsul.js b/kfet/static/kfet/js/kpsul.js index 9e14f709..94ade8df 100644 --- a/kfet/static/kfet/js/kpsul.js +++ b/kfet/static/kfet/js/kpsul.js @@ -12,9 +12,11 @@ class KPsulManager { this.account_manager = new AccountManager(this); this.checkout_manager = new CheckoutManager(this); this.article_manager = new ArticleManager(this); + this.basket = new BasketManager(this); this.history = new KHistory({ api_options: {'opesonly': true}, }); + this.previous_basket = new PreviousBasket(this); this._init_events(); } @@ -22,13 +24,17 @@ class KPsulManager { reset(soft) { soft = soft || false; + this.basket.reset(); this.account_manager.reset(); this.article_manager.reset(); if (!soft) { this.checkout_manager.reset(); - this.article_manager.reset_data(); - this.history.fetch(); + this.previous_basket.reset(); + Config.reset( () => { + this.article_manager.reset_data(); + this.history.fetch(); + }); } return this; @@ -135,8 +141,7 @@ class AccountManager { this.display(); kpsul.focus(); - kpsul._env.updateBasketAmount(); - kpsul._env.updateBasketRel(); + $(this).trigger("changed", [this.account]); } reset() { @@ -445,8 +450,8 @@ class CheckoutSelection { class ArticleManager { - constructor(env) { - this._env = env; // Global K-Psul Manager + constructor(kpsul) { + this.kpsul = kpsul; // Global K-Psul Manager this._$container = $('#articles_data'); this._$input = $('#article_autocomplete'); @@ -481,10 +486,6 @@ class ArticleManager { return this._$nb.val(); } - display_list() { - this.display.render(this.data); - } - validate(article) { this.selected.from(article); this._$input.val(article.name); @@ -504,18 +505,12 @@ class ArticleManager { reset_data() { this.display.clear(); this.data.clear(); - this.data.fromAPI() - .done( () => this.display_list() ); + this.data.fromAPI(); } update_data(data) { - for (let article_dict of data.articles) { - - var updated = this.data.update('article', article_dict.id, article_dict); - if (updated) { - this.display.update(updated); - } - } + for (let article_dict of data.articles) + this.data.update('article', article_dict.id, article_dict); } reset() { @@ -524,6 +519,7 @@ class ArticleManager { this._$nb.val(''); this._$input.val(''); this.autocomplete.showAll(); + return this; } _init_events() { @@ -536,7 +532,7 @@ class ArticleManager { //Global input event (to merge ?) this._$input.on('keydown', function(e) { if (e.keyCode == 13 && that._$input.val() == '') { - kpsul._env.performOperations(); + that.kpsul._env.performOperations(); } }); @@ -549,9 +545,8 @@ class ArticleManager { this._$nb.on('keydown', function(e) { if (e.keyCode == 13 && that.constructor.check_nb(that.nb) && !that.is_empty()) { - kpsul._env.addPurchase(that.selected, that.nb); - that.reset(); - that.focus(); + that.kpsul.basket.add_purchase(that.selected.id, parseInt(that.nb)); + that.reset().focus(); } if (normalKeys.test(e.keyCode) || arrowKeys.test(e.keyCode) || e.ctrlKey) { @@ -699,3 +694,423 @@ class ArticleAutocomplete { this.matching = []; } } + + +class BasketManager { + + constructor(kpsul) { + this.kpsul = kpsul; + + this._$container = $('#basket'); + + let item_template = '
'; + let templates = { + purchase: item_template, + specialope: item_template, + }; + this.data = new BasketData(); + this.display = new ForestDisplay(this._$container, templates, this.data); + + this.summary = new BasketSummary(this); + this.formset = new BasketFormset(this); + this.selection = new BasketSelection(this); + + this._init_events(); + } + + _init_events() { + $(this.data).on("changed", (e) => $(this).trigger("changed", [e])); + $(this.kpsul.account_manager).on("changed", (e) => $(this.data).trigger("update_all", [e])); + } + + is_empty() { + return this.data.is_empty(); + } + + reset() { + this.data.clear(); + this.formset.reset(); + this.selection.reset(); + } + + total_amount() { + let total = 0; + for (let ope of this.data.roots) + total += ope.amount; + return total; + } + + add_purchase(article_id, nb) { + let found = this.find_purchase(article_id); + if (found) { + let new_nb = found.article_nb + nb; + if (new_nb > 0) { + found.update({ + article_nb: found.article_nb + nb + }); + $(this.data).trigger("updated", [found]); + this.formset.update(found.id, found.for_formset()); + } else { + this.delete(found.id); + } + } else { + let created = this.data.create("purchase", { + id: this.formset.new_index(), + article: this.kpsul.article_manager.data.find("article", article_id), + article_nb: nb + }); + this.formset.create(created.for_formset()); + } + } + + find_purchase(article_id) { + return this.data.find("purchase", (purchase) => purchase.article.id === article_id); + } + + add_deposit(amount) { + this._add_special("deposit", amount); + } + + add_withdraw(amount) { + this._add_special("withdraw", amount); + } + + add_edit(amount) { + this._add_special("edit", amount); + } + + _add_special(type, amount) { + let created = this.data.create("specialope", { + id: this.formset.new_index(), + type: type, + amount: amount + }); + this.formset.create(created.for_formset()); + } + + delete(id) { + this.data.delete(Operation, id); + this.formset.delete(id); + } + +} + + +class BasketData extends ModelForest { + + static get structure() { + return { + purchase: { + model: PurchaseBasket + }, + specialope: { + model: SpecialOperationBasket + } + }; + } + +} + + +class PurchaseBasket extends Purchase { + + static get default_data() { + let defaults = $.extend({}, Purchase.default_data); + delete defaults.amount; + return defaults; + } + + formatter() { + return PurchaseBasketFormatter; + } + + for_formset() { + return { + id: this.id, + type: "purchase", + amount: 0, // avoid django error + article: this.article, + article_nb: this.article_nb + }; + } + + get amount() { + let amount_ukf = - this.article.price * this.article_nb; + if (Config.get('addcost') + && kpsul.account_manager.account.trigramme != Config.get('addcost_for') + && this.article.category.has_addcost) + amount_ukf -= Config.get('addcost_amount') * this.article_nb; + let reduc_divisor = 1; + if (kpsul.account_manager.account.is_cof) + reduc_divisor += Config.get('subvention_cof') / 100; + return amount_ukf / reduc_divisor; + } + +} + + +class SpecialOperationBasket extends SpecialOperation { + + formatter() { + return SpecialOperationBasketFormatter; + } + + for_formset() { + return { + id: this.id, + type: this.type, + amount: this.amount + }; + } + +} + + +class ItemBasketFormatter extends Formatter { + + static get props() { + return ['amount', 'number', 'name']; + } + + static prop_amount(o) { + let account = kpsul.account_manager.account; + return amountDisplay(o.amount, account.is_cof, account.trigramme); + } + +} + + +class PurchaseBasketFormatter extends ItemBasketFormatter { + + static get attrs() { + return ['article_id', 'low_stock']; + } + + static prop_number(o) { + return "(" + o.article_nb + "/" + o.article.stock + ")"; + } + + static prop_name(o) { + 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; + } + +} + + +class SpecialOperationBasketFormatter extends ItemBasketFormatter { + + static prop_number(o) { + return o.amount.toFixed(2) + '€'; + } + + static prop_name(o) { + return SpecialOperation.verbose_types[o.type] || ''; + } + +} + + +class BasketSummary { + + constructor(basket) { + this.basket = basket; + + this._$container = $("#basket_rel"); + + this._init_events(); + } + + _init_events() { + $(this.basket).on("changed", () => this.update_infos()); + } + + update_infos() { + let html = ''; + if (!this.basket.is_empty() && !kpsul.account_manager.is_empty()) { + let account = kpsul.account_manager.account; + let amount = this.basket.total_amount(); + html += ''; + if (account.trigramme == "LIQ") { + let abs_amount = Math.abs(amount); + for (let given of [5, 10, 20]) + if (abs_amount < given) + html += this.rendu(abs_amount, given); + } else { + let new_balance = account.balance + amount; + html += ''; + if (new_balance < 0) + html += ''; + } + } + html += '
Total' + amountDisplay(amount, account.is_cof, account.trigramme) + '
Nouveau solde' + amountDisplay(new_balance, account.is_cof) + '
Manque' + (- new_balance).toFixed(2) + '€
'; + this._$container.html(html); + } + + rendu(amount, given) { + return 'Sur ' + given.toString() +'€' + (given - amount).toFixed(2) + '€'; + } + +} + + +class BasketFormset { + + constructor(basket) { + this.basket = basket; + + this._$container = $('#operation_formset'); + this._$mngmt_total_forms_input = $('#id_form-TOTAL_FORMS'); + this._mngmt_total_forms = 1; + this._prefix_regex = /__prefix__/; + + this._$empty_html = + $('#operation_empty_html') + .removeAttr('id') + .find('label').remove().end() + .find('#id_form-__prefix__-DELETE').css('display', 'none').end(); + $('#id_form-0-DELETE').prop('checked', true); + } + + reset() { + this._mngmt_total_forms = 1; + this._$mngmt_total_forms_input.val(1); + this._$container.find("[data-opeindex]").remove(); + } + + new_index() { + let index = this._mngmt_total_forms++; + this._$mngmt_total_forms_input.val(index + 1); + return index; + } + + create(data) { + let that = this; + let $ope = this._$empty_html.clone(); + let index = data.id; + + $ope.attr('data-opeindex', index); + + $ope.find(':input').each( function() { + let name = $(this).attr('name').replace(that._prefix_regex, index); + let id = 'id_' + name; + $(this).attr({ + name: name, + id: id + }); + }); + + this._$container.append($ope); + + this._update(index, data); + + return index; + } + + update(index, data) { + this._update(index, data); + } + + delete(index) { + this.update(index, { + delete: true, + }); + } + + _update(index, data) { + let $ope = this._$container.find(`[data-opeindex=${index}]`); + let selector_prefix = `#id_form-${index}-`; + + $ope.find(selector_prefix + 'type').val(data.type); + + if (data.amount !== undefined) + $ope.find(selector_prefix + 'amount').val(data.amount.toFixed(2)); + if (data.article !== undefined) + $ope.find(selector_prefix + 'article').val(data.article.id); + if (data.article_nb !== undefined) + $ope.find(selector_prefix + 'article_nb').val(data.article_nb); + if (data.delete !== undefined) + $ope.find(selector_prefix + 'DELETE').prop('checked', true); + } + +} + + +class PreviousBasket { + + constructor(kpsul) { + this.kpsul = kpsul; + + this._$container = $("#previous_op"); + } + + update() { + let account = this.kpsul.account_manager.account; + let html = ``; + html += `
${account.trigramme} - ${account.name}
`; + html += this.kpsul.basket.summary._$container.html(); + this._$container.html(html); + } + + reset() { + this._$container.html(""); + } + +} + + +class BasketSelection { + + constructor(basket) { + this.basket = basket; + this._init(); + } + + _init() { + this.basket._$container.selectable({ + filter: ".basket-item" + }); + this._init_events(); + } + + _init_events() { + let basket = this.basket; + $(document).on('keydown', function (e) { + switch(e.which) { + 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); + }); + break; + case 38: + // Arrow up + basket._$container.find('.ui-selected').each( function() { + basket.add_purchase(parseInt($(this).attr('article_id')), 1); + }); + break; + case 40: + // Arrow down + basket._$container.find('.ui-selected').each( function() { + basket.add_purchase(parseInt($(this).attr('article_id')), -1); + }); + break; + } + }); + } + + reset() { + this.basket._$container.find('.ui-selected').removeClass('.ui-selected'); + } + +} diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index e94f5c6c..682a44b1 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -135,16 +135,14 @@
- -
-
+
-
+
@@ -210,7 +208,7 @@ $(document).ready(function() { url: Urls['kfet.kpsul.perform_operations'](), data: data, on_success: function() { - updatePreviousOp(); + kpsul.previous_basket.update(); coolReset(); }, on_400: function(response) { @@ -228,234 +226,6 @@ $(document).ready(function() { performOperations(); }); - // ----- - // Basket - // ----- - - var item_basket_default_html = ''; - var basket_container = $('#basket table'); - var arrowKeys = /^(37|38|39|40)$/; - - function amountEuroPurchase(article, nb) { - var amount_euro = - article.price * nb ; - if (Config.get('addcost_for') - && Config.get('addcost_amount') - && kpsul.account_manager.account.trigramme != Config.get('addcost_for') - && article.category.has_addcost) - amount_euro -= Config.get('addcost_amount') * nb; - var reduc_divisor = 1; - if (kpsul.account_manager.account.is_cof) - reduc_divisor = 1 + Config.get('subvention_cof') / 100; - return (amount_euro / reduc_divisor).toFixed(2); - } - - function addPurchase(article, nb) { - - var existing = false; - formset_container.find('[data-opeindex]').each(function () { - var opeindex = $(this).attr('data-opeindex'); - var article_id = $(this).find('#id_form-'+opeindex+'-article').val(); - if (article_id == article.id) { - existing = true ; - addExistingPurchase(opeindex, nb); - } - }); - if (!existing) { - var amount_euro = amountEuroPurchase(article, nb); - var index = addPurchaseToFormset(article.id, nb, amount_euro); - var article_basket_html = $(item_basket_default_html); - article_basket_html - .attr('data-opeindex', index) - .find('.number').text('('+nb+'/'+article.stock+')').end() - .find('.name').text(article.name).end() - .find('.amount').text(amountToUKF(amount_euro, kpsul.account_manager.account.is_cof)); - basket_container.prepend(article_basket_html); - if (article.is_low_stock(nb)) - article_basket_html.find('.lowstock') - .show(); - updateBasketRel(); - } - } - - function addDeposit(amount) { - var deposit_basket_html = $(item_basket_default_html); - var amount = parseFloat(amount).toFixed(2); - var index = addDepositToFormset(amount); - deposit_basket_html - .attr('data-opeindex', index) - .find('.number').text(amount+"€").end() - .find('.name').text('Charge').end() - .find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof)); - basket_container.prepend(deposit_basket_html); - updateBasketRel(); - } - - function addEdit(amount) { - var deposit_basket_html = $(item_basket_default_html); - var amount = parseFloat(amount).toFixed(2); - var index = addEditToFormset(amount); - deposit_basket_html - .attr('data-opeindex', index) - .find('.number').text(amount+"€").end() - .find('.name').text('Édition').end() - .find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof)); - basket_container.prepend(deposit_basket_html); - updateBasketRel(); - } - - function addWithdraw(amount) { - var withdraw_basket_html = $(item_basket_default_html); - var amount = (- parseFloat(amount)).toFixed(2); - var index = addWithdrawToFormset(amount); - withdraw_basket_html - .attr('data-opeindex', index) - .find('.number').text(amount+"€").end() - .find('.name').text('Retrait').end() - .find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof)); - basket_container.prepend(withdraw_basket_html); - updateBasketRel(); - } - - basket_container.selectable({ - filter: 'tr', - }); - - $(document).on('keydown', function (e) { - switch(e.which) { - case 46: - // DEL (Suppr) - basket_container.find('.ui-selected').each(function () { - deleteFromBasket($(this).data('opeindex')); - }); - break; - case 38: - // Arrow up - basket_container.find('.ui-selected').each(function () { - addExistingPurchase($(this).data('opeindex'), 1); - }); - break; - case 40: - // Arrow down - basket_container.find('.ui-selected').each(function () { - addExistingPurchase($(this).data('opeindex'), -1); - }); - break; - } - }); - - function isBasketEmpty() { - return !basket_container.find('[data-opeindex]').length; - } - - function getAmountBasket() { - var total = 0; - formset_container.find('[data-opeindex]').each(function () { - var opeindex = $(this).attr('data-opeindex'); - if (!$(this).find('#id_form-'+opeindex+'-DELETE').prop('checked')) - total += parseFloat($(this).find('#id_form-'+opeindex+'-amount').val()); - }); - return total; - } - - function updateBasketAmount() { - formset_container.find('[data-opeindex]').each(function () { - var opeindex = $(this).attr('data-opeindex'); - var deleted = $(this).find('#id_form-'+opeindex+'-DELETE').prop('checked'); - var type = $(this).find('#id_form-'+opeindex+'-type').val(); - var article_id = $(this).find('#id_form-'+opeindex+'-article').val(); - var article_nb = $(this).find('#id_form-'+opeindex+'-article_nb').val(); - var amount = $(this).find('#id_form-'+opeindex+'-amount'); - if (!deleted && type == "purchase") - amount.val(amountEuroPurchase(article_id, article_nb)); - basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), kpsul.account_manager.account.is_cof, false)); - }); - } - - var basketrel_container = $('#basket_rel'); - - function updateBasketRel() { - var basketrel_html = ''; - var account = kpsul.account_manager.account; - var trigramme = account.trigramme; - var is_cof = account.is_cof; - if (trigramme == 'LIQ' && !isBasketEmpty()) { - var amount = - getAmountBasket(); - basketrel_html += '
Total: '+amount.toFixed(2)+' €
'; - if (amount < 5) - basketrel_html += '
Sur 5€: '+ (5-amount).toFixed(2) +' €
'; - if (amount < 10) - basketrel_html += '
Sur 10€: '+ (10-amount).toFixed(2) +' €
'; - if (amount < 20) - basketrel_html += '
Sur 20€: '+ (20-amount).toFixed(2) +' €
'; - } else if (trigramme != '' && !isBasketEmpty()) { - var amount = getAmountBasket(); - var amountUKF = amountToUKF(amount, is_cof); - var newBalance = account.balance + amount; - var newBalanceUKF = amountToUKF(newBalance, is_cof, true); - basketrel_html += '
Total: '+amountUKF+'
'; - basketrel_html += '
Nouveau solde: '+newBalanceUKF+'
'; - if (newBalance < 0) - basketrel_html += '
Manque: '+ (-newBalance).toFixed(2) +' €
'; - } - basketrel_container.html(basketrel_html); - } - - function deleteFromBasket(opeindex) { - basket_container.find('[data-opeindex='+opeindex+']').remove(); - deleteFromFormset(opeindex); - updateBasketRel(); - } - - function addExistingPurchase(opeindex, nb) { - var type = formset_container.find("#id_form-"+opeindex+"-type").val(); - var id = formset_container.find("#id_form-"+opeindex+"-article").val(); - var article = kpsul.article_manager.get_article(parseInt(id)); - var nb_before = formset_container.find("#id_form-"+opeindex+"-article_nb").val(); - var nb_after = parseInt(nb_before) + parseInt(nb); - var amountEuro_after = amountEuroPurchase(article, nb_after); - var amountUKF_after = amountToUKF(amountEuro_after, kpsul.account_manager.account.is_cof, false); - - if (type == 'purchase') { - if (nb_after == 0) { - deleteFromBasket(opeindex); - } else if (nb_after > 0 && nb_after <= 25) { - if (nb_before > 0) { - var article_html = basket_container.find('[data-opeindex='+opeindex+']'); - article_html.find('.amount').text(amountUKF_after).end() - .find('.number').text('('+nb_after+'/'+article.stock+')').end() ; - - } else { - article_html = $(item_basket_default_html); - article_html - .attr('data-opeindex', opeindex) - .find('.number').text('('+nb_after+'/'+article.stock+')').end() - .find('.name').text(article.name).end() - .find('.amount').text(amountUKF_after); - basket_container.prepend(article_basket_html); - - } - - if (article.is_low_stock(nb_after)) - article_html.find('.lowstock') - .show(); - else - article_html.find('.lowstock') - .hide(); - updateExistingFormset(opeindex, nb_after, amountEuro_after); - updateBasketRel(); - } - } - } - - function resetBasket() { - basket_container.find('tr').remove(); - mngmt_total_forms = 1; - mngmt_total_forms_input.val(1); - formset_container.find('div').remove(); - updateBasketRel(); - kpsul.article_manager.reset(); - } - // ----- // Ask deposit or withdraw // ----- @@ -469,12 +239,12 @@ $(document).ready(function() { function callback(amount) { if (!$.isNumeric(amount) || amount <= 0) return false; - addDeposit(amount); + kpsul.basket.add_deposit(amount); } depositDialog.open({ callback: callback, - next_focus: kpsul.article_manager, + next_focus: kpsul, }); } @@ -487,12 +257,12 @@ $(document).ready(function() { function callback(amount) { if (!$.isNumeric(amount)) return false; - addEdit(amount); + kpsul.basket.add_edit(amount); } editDialog.open({ callback: callback, - next_focus: kpsul.article_manager, + next_focus: kpsul, }); } @@ -505,12 +275,12 @@ $(document).ready(function() { function callback(amount) { if (!$.isNumeric(amount) || amount <= 0) return false; - addWithdraw(amount); + kpsul.basket.add_withdraw(- amount); } withdrawDialog.open({ callback: callback, - next_focus: kpsul.article_manager, + next_focus: kpsul, }); } @@ -522,87 +292,6 @@ $(document).ready(function() { depositButton.on('click', function() { askDeposit(); }); withdrawButton.on('click', function() { askWithdraw(); }); - // ----- - // Operation formset management - // ----- - - var operation_empty_html = $('#operation_empty_html') - .removeAttr('id') - .find('label').remove().end() - .find('#id_form-__prefix__-DELETE').css('display','none').end(); - $('#id_form-0-DELETE').prop('checked',true); - - var formset_container = $('#operation_formset'); - var mngmt_total_forms_input = $('#id_form-TOTAL_FORMS'); - var mngmt_total_forms = 1; - var prefix_regex = /__prefix__/; - - function addOperationToFormset(type, amount, article='', article_nb='') { - var operation_html = operation_empty_html.clone(); - var index = mngmt_total_forms; - - operation_html.attr('data-opeindex', index); - - operation_html - .find('#id_form-__prefix__-type').val(type).end() - .find('#id_form-__prefix__-amount').val((parseFloat(amount)).toFixed(2)).end() - .find('#id_form-__prefix__-article').val(article).end() - .find('#id_form-__prefix__-article_nb').val(article_nb).end(); - - mngmt_total_forms_input.val(index+1); - mngmt_total_forms++; - - operation_html.find(':input').each(function() { - var name = $(this).attr('name').replace(prefix_regex, index); - var id = 'id_' + name; - $(this).attr({'name': name, 'id': id}); - }); - formset_container.append(operation_html); - - return index; - } - - function addDepositToFormset(amount) { - return addOperationToFormset('deposit', amount, '', ''); - } - - function addEditToFormset(amount) { - return addOperationToFormset('edit', amount, '', ''); - } - - function addWithdrawToFormset(amount) { - return addOperationToFormset('withdraw', amount, '', ''); - } - - function addPurchaseToFormset(article_id, article_nb, amount=0) { - return addOperationToFormset('purchase', amount, article_id, article_nb); - } - - function deleteFromFormset(opeindex) { - updateExistingFormset(opeindex, 0, '0.00'); - } - - function updateExistingFormset(opeindex, nb, amount) { - formset_container - .find('#id_form-'+opeindex+'-amount').val((parseFloat(amount)).toFixed(2)).end() - .find('#id_form-'+opeindex+'-article_nb').val(nb).end() - .find('#id_form-'+opeindex+'-DELETE').prop('checked', !nb); - } - - var previousop_container = $('#previous_op'); - - function updatePreviousOp() { - var previousop_html = ''; - var trigramme = kpsul.account_manager.account.trigramme; - previousop_html += '
Trigramme : '+trigramme+'
'; - previousop_html += basketrel_container.html(); - previousop_container.html(previousop_html); - } - - function resetPreviousOp() { - previousop_container.html(''); - } - // ----- // Addcost // ----- @@ -654,24 +343,18 @@ $(document).ready(function() { // Reset functions - function coolReset(give_tri_focus=true) { - kpsul.account_manager.reset(); - resetBasket(); + function coolReset() { resetComment(); resetSelectable(); - if (give_tri_focus) - kpsul.account_manager.focus(); + kpsul.reset(true).focus(); } - function hardReset(give_tri_focus=true) { - coolReset(give_tri_focus); - kpsul.checkout_manager.reset(); - resetPreviousOp(); + function hardReset() { + coolReset(); Config.reset(function() { - kpsul.article_manager.reset_data(); displayAddcost(); - kpsul.history.fetch(); }); + kpsul.reset().focus(); } function resetSelectable() { @@ -708,12 +391,12 @@ $(document).ready(function() { case 113: if (e.shiftKey) { // Shift+F2 - Account reset - account_manager + kpsul.account_manager .reset() .focus(); } else { // F2 - Basket reset - resetBasket(); + kpsul.basket.reset(); kpsul.article_manager.focus(); } return false; @@ -746,11 +429,7 @@ $(document).ready(function() { // ----- var env = { - addPurchase: addPurchase, - updateBasketAmount: updateBasketAmount, - updateBasketRel: updateBasketRel, performOperations: performOperations, - coolReset: coolReset, }; window.kpsul = new KPsulManager(env); diff --git a/kfet/views.py b/kfet/views.py index 6751d95b..da223e73 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1564,7 +1564,6 @@ def history_json(request): 'id': ope.id, 'amount': ope.amount, 'canceled_at': ope.canceled_at, - 'is_cof': opegroup.is_cof, 'trigramme': opegroup.on_acc and opegroup.on_acc.trigramme or None, 'opegroup__id': opegroup.id,