466 lines
12 KiB
JavaScript
466 lines
12 KiB
JavaScript
/**
|
|
* @file Miscellaneous JS definitions for <tt>k-fet</tt> app.
|
|
* @copyright 2017 cof-geek
|
|
* @license MIT
|
|
*/
|
|
|
|
/**
|
|
* String method
|
|
* @memberof String
|
|
* @return {String} String formatted as trigramme
|
|
*/
|
|
String.prototype.formatTri = function() {
|
|
return this.toUpperCase().substr(0, 3);
|
|
}
|
|
|
|
|
|
/**
|
|
* String method
|
|
* @global
|
|
* @return {Boolean} true iff String follows trigramme pattern
|
|
*/
|
|
String.prototype.isValidTri = function() {
|
|
var pattern = /^[^a-z]{3}$/;
|
|
return pattern.test(this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks if given argument is float ;
|
|
* if not, parses given argument to float value.
|
|
* @global
|
|
* @return {float}
|
|
*/
|
|
function floatCheck(v) {
|
|
if (typeof v === 'number')
|
|
return v;
|
|
return Number.parseFloat(v);
|
|
}
|
|
|
|
|
|
|
|
function intCheck(v) {
|
|
return Number.parseInt(v);
|
|
}
|
|
|
|
function floatCheck(v) {
|
|
if (typeof v === 'number')
|
|
return v;
|
|
return Number.parseFloat(v);
|
|
}
|
|
|
|
function booleanCheck(v) {
|
|
return v == true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Short: Equivalent to python str format.
|
|
* Source: [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals}.
|
|
* @example
|
|
* var t1Closure = template`${0}${1}${0}!`;
|
|
* t1Closure('Y', 'A'); // "YAY!"
|
|
* @example
|
|
* var t2Closure = template`${0} ${'foo'}!`;
|
|
* t2Closure('Hello', {foo: 'World'}); // "Hello World!"
|
|
*/
|
|
function template(strings, ...keys) {
|
|
return (function(...values) {
|
|
var dict = values[values.length - 1] || {};
|
|
var result = [strings[0]];
|
|
keys.forEach(function(key, i) {
|
|
var value = Number.isInteger(key) ? values[key] : dict[key];
|
|
result.push(value, strings[i + 1]);
|
|
});
|
|
return result.join('');
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Get and store K-Psul config from API.
|
|
* <br><br>
|
|
*
|
|
* Config should be accessed statically only.
|
|
*/
|
|
class Config {
|
|
|
|
/**
|
|
* Get or create config object.
|
|
* @private
|
|
* @return {object} object - config keys/values
|
|
*/
|
|
static _get_or_create_config() {
|
|
if (window.config === undefined)
|
|
window.config = {};
|
|
return window.config;
|
|
}
|
|
|
|
/**
|
|
* Get config from API.
|
|
* @param {jQueryAjaxComplete} [callback] - A function to be called when
|
|
* the request finishes.
|
|
*/
|
|
static reset(callback) {
|
|
$.getJSON(Urls['kfet.kpsul.get_settings']())
|
|
.done(function(data) {
|
|
for (var key in data) {
|
|
Config.set(key, data[key]);
|
|
}
|
|
})
|
|
.always(callback);
|
|
}
|
|
|
|
/**
|
|
* Get value for key in config.
|
|
* @param {string} key
|
|
*/
|
|
static get(key) {
|
|
return this._get_or_create_config()[key];
|
|
}
|
|
|
|
/**
|
|
* Set value for key in config.
|
|
* @param {string} key
|
|
* @param {*} value
|
|
*/
|
|
static set(key, value) {
|
|
// API currently returns string for Decimal type
|
|
if (['addcost_amount', 'subvention_cof'].indexOf(key) > -1)
|
|
value = floatCheck(value);
|
|
this._get_or_create_config()[key] = value;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* CSRF Token
|
|
*/
|
|
|
|
var csrftoken = '';
|
|
if (typeof Cookies !== 'undefined')
|
|
csrftoken = Cookies.get('csrftoken');
|
|
|
|
// Add CSRF token in header of AJAX requests.
|
|
|
|
function csrfSafeMethod(method) {
|
|
// these HTTP methods do not require CSRF protection
|
|
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
|
}
|
|
|
|
$.ajaxSetup({
|
|
beforeSend: function(xhr, settings) {
|
|
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
function add_csrf_form($form) {
|
|
$form.append(
|
|
$('<input>', {'name': 'csrfmiddlewaretoken', 'value': csrftoken})
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* Capslock management
|
|
*/
|
|
|
|
window.capslock = -1;
|
|
$(document).on('keypress', function(e) {
|
|
var s = String.fromCharCode(e.which);
|
|
if ((s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey)|| //caps on, shift off
|
|
(s.toUpperCase() !== s && s.toLowerCase() === s && e.shiftKey)) { //caps on, shift on
|
|
$('body').addClass('capslock_on')
|
|
} else if ((s.toLowerCase() === s && s.toUpperCase() !== s && !e.shiftKey)|| //caps off, shift off
|
|
(s.toLowerCase() !== s && s.toUpperCase() === s && e.shiftKey)) { //caps off, shift on
|
|
$('body').removeClass('capslock_on')
|
|
}
|
|
});
|
|
|
|
$(document).on('keydown', function(e) {
|
|
if (e.which == 20) {
|
|
$('body').toggleClass('capslock_on')
|
|
}
|
|
});
|
|
|
|
|
|
/*
|
|
* Generic Websocket class and k-psul ws instanciation
|
|
*/
|
|
|
|
class KfetWebsocket {
|
|
|
|
static get defaults() {
|
|
return {
|
|
relative_url: '',
|
|
default_msg: {},
|
|
handlers: [],
|
|
base_path: '/ws/k-fet/'
|
|
};
|
|
}
|
|
|
|
constructor(data) {
|
|
$.extend(this, this.constructor.defaults, data);
|
|
if (window.location.pathname.startsWith('/gestion/'))
|
|
this.base_path = '/gestion' + this.base_path;
|
|
}
|
|
|
|
get url() {
|
|
var protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
|
var host = window.location.host;
|
|
return protocol + "://" + host + this.base_path + this.relative_url;
|
|
}
|
|
|
|
add_handler(handler) {
|
|
if (!this.socket)
|
|
this.listen();
|
|
|
|
this.handlers.push(handler);
|
|
}
|
|
|
|
listen() {
|
|
var that = this;
|
|
this.socket = new ReconnectingWebSocket(this.url);
|
|
|
|
this.socket.onmessage = function(e) {
|
|
var data = $.extend({}, that.default_msg, JSON.parse(e.data));
|
|
for (let handler of that.handlers) {
|
|
handler(data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var OperationWebSocket = new KfetWebsocket({
|
|
'relative_url': 'k-psul/',
|
|
'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]},
|
|
});
|
|
|
|
function dateUTCToParis(date) {
|
|
return moment.tz(date, 'UTC').tz('Europe/Paris');
|
|
}
|
|
|
|
function amountDisplay(amount, is_cof=false, tri='') {
|
|
if (tri == 'LIQ')
|
|
return (- amount).toFixed(2) +'€';
|
|
return amountToUKF(amount, is_cof);
|
|
}
|
|
|
|
function amountToUKF(amount, is_cof=false, account=false) {
|
|
var rounding = account ? Math.floor : Math.round ;
|
|
var coef_cof = is_cof ? 1 + Config.get('subvention_cof') / 100 : 1;
|
|
return rounding(amount * coef_cof * 10);
|
|
}
|
|
|
|
function isValidTrigramme(trigramme) {
|
|
var pattern = /^[^a-z]{3}$/;
|
|
return trigramme.match(pattern);
|
|
}
|
|
|
|
/**
|
|
* Dialogs with user via jconfirm
|
|
*/
|
|
|
|
class UserDialog {
|
|
|
|
static get defaults() {
|
|
return {'title': '', 'content': ''};
|
|
}
|
|
|
|
constructor(data) {
|
|
$.extend(this, this.constructor.defaults, data);
|
|
}
|
|
|
|
open(settings) {
|
|
// Arg management
|
|
var pre_content = settings.pre_content || '';
|
|
var post_content = settings.post_content || '';
|
|
var callback = settings.callback || $.noop;
|
|
|
|
var that = this;
|
|
$.confirm({
|
|
title: this.title,
|
|
content: pre_content + this.content + post_content,
|
|
backgroundDismiss: true,
|
|
animation:'top',
|
|
closeAnimation:'bottom',
|
|
keyboardEnabled: true,
|
|
confirm: function() {
|
|
var inputs = {};
|
|
this.$content.find('input').each(function () {
|
|
inputs[$(this).attr('name')] = $(this).val();
|
|
});
|
|
if (Object.keys(inputs).length > 1)
|
|
return callback(inputs);
|
|
else
|
|
return callback(inputs[Object.keys(inputs)[0]]);
|
|
},
|
|
onOpen: function() {
|
|
var that = this
|
|
this.$content.find('input').on('keydown', function(e) {
|
|
if (e.keyCode == 13) {
|
|
e.preventDefault();
|
|
that.$confirmButton.click();
|
|
}
|
|
});
|
|
},
|
|
onClose: function() { if (settings.next_focus) { this._lastFocused = settings.next_focus; } }
|
|
});
|
|
}
|
|
}
|
|
|
|
function getErrorsHtml(data) {
|
|
var content = '';
|
|
if (!data)
|
|
return "L'utilisateur n'est pas dans l'équipe";
|
|
if ('operation_group' in data['errors']) {
|
|
content += 'Général';
|
|
content += '<ul>';
|
|
if (data['errors']['operation_group'].indexOf('on_acc') != -1)
|
|
content += '<li>Pas de compte sélectionné</li>';
|
|
if (data['errors']['operation_group'].indexOf('checkout') != -1)
|
|
content += '<li>Pas de caisse sélectionnée</li>';
|
|
content += '</ul>';
|
|
}
|
|
if ('missing_perms' in data['errors']) {
|
|
content += 'Permissions manquantes';
|
|
content += '<ul>';
|
|
for (var i=0; i<data['errors']['missing_perms'].length; i++)
|
|
content += '<li>'+data['errors']['missing_perms'][i]+'</li>';
|
|
content += '</ul>';
|
|
}
|
|
if ('negative' in data['errors']) {
|
|
if (window.location.pathname.startsWith('/gestion/')) {
|
|
var url_base = '/gestion/k-fet/accounts/';
|
|
} else {
|
|
var url_base = '/k-fet/accounts/';
|
|
}
|
|
for (var i=0; i<data['errors']['negative'].length; i++) {
|
|
content += '<a class="btn btn-primary" href="'+url_base+data['errors']['negative'][i]+'/edit" target="_blank" style="width:100%">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
|
|
}
|
|
}
|
|
if ('addcost' in data['errors']) {
|
|
content += '<ul>';
|
|
if (data['errors']['addcost'].indexOf('__all__') != -1)
|
|
content += '<li>Compte invalide</li>';
|
|
if (data['errors']['addcost'].indexOf('amount') != -1)
|
|
content += '<li>Montant invalide</li>';
|
|
content += '</ul>';
|
|
}
|
|
if ('account' in data['errors']) {
|
|
content += 'Général';
|
|
content += '<ul>';
|
|
content += '<li>Opération invalide sur le compte '+data['errors']['account']+'</li>';
|
|
content += '</ul>';
|
|
}
|
|
return content;
|
|
}
|
|
|
|
function displayErrors(html) {
|
|
$.alert({
|
|
title: 'Erreurs',
|
|
content: html,
|
|
backgroundDismiss: true,
|
|
animation: 'top',
|
|
closeAnimation: 'bottom',
|
|
keyboardEnabled: true,
|
|
});
|
|
}
|
|
|
|
var authDialog = new UserDialog({
|
|
'title': 'Authentification requise',
|
|
'content': '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
|
|
});
|
|
|
|
|
|
//Note/TODO: the returned ajax object can be improved by allowing chaining on errors 403/400
|
|
function api_with_auth(settings, password) {
|
|
if (window.api_lock == 1)
|
|
return false;
|
|
window.api_lock = 1;
|
|
|
|
var url = settings.url;
|
|
if (!url)
|
|
return false;
|
|
var data = settings.data || {} ;
|
|
var on_success = settings.on_success || $.noop ;
|
|
var on_400 = settings.on_400 || $.noop ;
|
|
|
|
return $.ajax({
|
|
dataType: "json",
|
|
url: url,
|
|
method: "POST",
|
|
data: data,
|
|
beforeSend: function ($xhr) {
|
|
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
|
if (password)
|
|
$xhr.setRequestHeader("KFetPassword", password);
|
|
},
|
|
})
|
|
.done(function(data) {
|
|
on_success(data);
|
|
})
|
|
.fail(function($xhr) {
|
|
var response = $xhr.responseJSON;
|
|
switch ($xhr.status) {
|
|
case 403:
|
|
authDialog.open({
|
|
callback: function(password) {
|
|
api_with_auth(settings, password)
|
|
},
|
|
pre_content: getErrorsHtml(response),
|
|
next_focus: settings.next_focus,
|
|
});
|
|
break;
|
|
case 400:
|
|
on_400(response);
|
|
break;
|
|
}
|
|
})
|
|
.always(function() {
|
|
window.api_lock = 0;
|
|
});
|
|
}
|
|
|
|
String.prototype.pluralize = function(count, irreg_plural) {
|
|
if (Math.abs(count) >= 2)
|
|
return irreg_plural ? irreg_plural : this+'s' ;
|
|
return this ;
|
|
}
|
|
|
|
/**
|
|
* Setup jquery-confirm
|
|
*/
|
|
|
|
jconfirm.defaults = {
|
|
confirmButton: '<span class="glyphicon glyphicon-ok"></span>',
|
|
cancelButton: '<span class="glyphicon glyphicon-remove"></span>'
|
|
};
|
|
|
|
|
|
/**
|
|
* Create form node, given an url used as 'action', with csrftoken set.
|
|
*/
|
|
function create_form(url) {
|
|
let $form = $('<form>', {
|
|
'action': url,
|
|
'method': 'post',
|
|
});
|
|
add_csrf_form($form);
|
|
return $form;
|
|
}
|
|
|
|
|
|
/**
|
|
* Emit a POST request from <a> tag.
|
|
*
|
|
* Usage:
|
|
* <a href="#" data-url="{target url}" onclick="submit_url(this)">{…}</a>
|
|
*/
|
|
function submit_url(el) {
|
|
let url = $(el).data('url');
|
|
create_form(url).appendTo($('body')).submit();
|
|
}
|