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.
This commit is contained in:
Aurélien Delobelle 2017-05-18 02:13:42 +02:00
parent bacc079778
commit a0503d0c53
7 changed files with 632 additions and 458 deletions

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -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 {
* <tt>a.amount</tt> displayed according to <tt>a.is_cof</tt> and <tt>a.trigramme</tt> 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 '';

View file

@ -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];
}

View file

@ -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 = '<div class="basket-item"><span class="amount"></span><span class="number"></span><span class="lowstock"><span class="lowstock glyphicon glyphicon-alert"></span></span><span class="name"></span></div>';
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 = '<table class="text-right">';
if (!this.basket.is_empty() && !kpsul.account_manager.is_empty()) {
let account = kpsul.account_manager.account;
let amount = this.basket.total_amount();
html += '<tr><td>Total</td><td>' + amountDisplay(amount, account.is_cof, account.trigramme) + '</td></tr>';
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 += '<tr><td>Nouveau solde</td><td>' + amountDisplay(new_balance, account.is_cof) + '</td></tr>';
if (new_balance < 0)
html += '<tr><td>Manque</td><td>' + (- new_balance).toFixed(2) + '€</td></tr>';
}
}
html += '</table>';
this._$container.html(html);
}
rendu(amount, given) {
return '<tr><td>Sur ' + given.toString() +'€</td><td>' + (given - amount).toFixed(2) + '€</td></tr>';
}
}
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 += `<div class="trigramme">${account.trigramme} - ${account.name}</div>`;
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');
}
}

View file

@ -135,16 +135,14 @@
<div class="row kpsul_middle_left_bottom">
<div class="col-sm-6">
<div id="basket">
<table id="basket_table">
</table>
</div>
</div>
<div class="col-sm-3">
<div id="basket_rel">
<div id="basket_rel" class="basket_summary">
</div>
</div>
<div class="col-sm-3">
<div id="previous_op">
<div id="previous_op" class="basket_summary">
</div>
</div>
</div>
@ -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 = '<tr><td class="amount"></td><td class="number"></td><td ><span class="lowstock glyphicon glyphicon-alert"></span></td><td class="name"></td></tr>';
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 += '<div>Total: '+amount.toFixed(2)+' €</div>';
if (amount < 5)
basketrel_html += '<div>Sur 5€: '+ (5-amount).toFixed(2) +' €</div>';
if (amount < 10)
basketrel_html += '<div>Sur 10€: '+ (10-amount).toFixed(2) +' €</div>';
if (amount < 20)
basketrel_html += '<div>Sur 20€: '+ (20-amount).toFixed(2) +' €</div>';
} 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 += '<div>Total: '+amountUKF+'</div>';
basketrel_html += '<div>Nouveau solde: '+newBalanceUKF+'</div>';
if (newBalance < 0)
basketrel_html += '<div>Manque: '+ (-newBalance).toFixed(2) +' €</div>';
}
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 += '<div class="trigramme">Trigramme : '+trigramme+'</div>';
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);

View file

@ -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,