WIP: Aureplop/kpsul js refactor #501
19 changed files with 3898 additions and 1843 deletions
|
@ -81,6 +81,7 @@ INSTALLED_APPS = [
|
|||
'kfet.open',
|
||||
'channels',
|
||||
'widget_tweaks',
|
||||
'django_js_reverse',
|
||||
'custommail',
|
||||
'djconfig',
|
||||
'wagtail.wagtailforms',
|
||||
|
|
|
@ -6,9 +6,11 @@ from django.conf import settings
|
|||
from django.conf.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.contrib.auth import views as django_views
|
||||
from django_cas_ng import views as django_cas_views
|
||||
from django_js_reverse.views import urls_js
|
||||
|
||||
from wagtail.wagtailadmin import urls as wagtailadmin_urls
|
||||
from wagtail.wagtailcore import urls as wagtail_urls
|
||||
|
@ -91,6 +93,7 @@ urlpatterns = [
|
|||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente,
|
||||
name="ml_bda_revente"),
|
||||
url(r'^k-fet/', include('kfet.urls')),
|
||||
url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'),
|
||||
url(r'^cms/', include(wagtailadmin_urls)),
|
||||
url(r'^documents/', include(wagtaildocs_urls)),
|
||||
# djconfig
|
||||
|
|
|
@ -44,6 +44,11 @@
|
|||
width:90px;
|
||||
}
|
||||
|
||||
#history .opegroup .infos {
|
||||
text-align:center;
|
||||
width:145px;
|
||||
}
|
||||
|
||||
#history .opegroup .valid_by {
|
||||
padding-left:20px
|
||||
}
|
||||
|
@ -71,6 +76,10 @@
|
|||
text-align:right;
|
||||
}
|
||||
|
||||
#history .ope .glyphicon {
|
||||
padding-left:15px;
|
||||
}
|
||||
|
||||
#history .ope .infos2 {
|
||||
padding-left:15px;
|
||||
}
|
||||
|
@ -88,11 +97,11 @@
|
|||
color:#FFF;
|
||||
}
|
||||
|
||||
#history .ope.canceled, #history .transfer.canceled {
|
||||
#history [canceled="true"] {
|
||||
color:#444;
|
||||
}
|
||||
|
||||
#history .ope.canceled::before, #history.transfer.canceled::before {
|
||||
#history [canceled="true"]::before {
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
width:100%;
|
||||
|
|
|
@ -49,10 +49,10 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
height:120px;
|
||||
}
|
||||
|
||||
#account[data-balance="ok"] #account_form input { background:#009011; color:#FFF;}
|
||||
#account[data-balance="low"] #account_form input { background:#EC6400; color:#FFF; }
|
||||
#account[data-balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
|
||||
#account[data-balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
|
||||
#account[data_balance="ok"] #account_form input { background:#009011; color:#FFF;}
|
||||
#account[data_balance="low"] #account_form input { background:#EC6400; color:#FFF; }
|
||||
#account[data_balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
|
||||
#account[data_balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
|
||||
|
||||
#account_form {
|
||||
padding:0;
|
||||
|
@ -90,7 +90,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
font-size:12px;
|
||||
}
|
||||
|
||||
#account_data #account-balance {
|
||||
#account_data #account-balance_ukf {
|
||||
height:40px;
|
||||
line-height:40px;
|
||||
|
||||
|
@ -102,6 +102,10 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
font-weight:bold;
|
||||
}
|
||||
|
||||
#account-is_cof {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#account .buttons {
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
|
@ -119,7 +123,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
font-size:14px;
|
||||
line-height:24px;
|
||||
}
|
||||
#account_data #account-balance {
|
||||
#account_data #account-balance_ukf {
|
||||
font-size:50px;
|
||||
line-height:60px;
|
||||
height:60px;
|
||||
|
@ -306,28 +310,40 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
#articles_data {
|
||||
overflow:auto;
|
||||
max-height:500px;
|
||||
}
|
||||
|
||||
#articles_data table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#articles_data table tr.article {
|
||||
#articles_data div.article {
|
||||
height:25px;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
#articles_data table tr.article td:first-child {
|
||||
#articles_data .article[data_stock="low"] {
|
||||
background:rgba(236,100,0,0.3);
|
||||
}
|
||||
|
||||
#articles_data span {
|
||||
height:25px;
|
||||
line-height:25px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#articles_data span.name {
|
||||
padding-left:10px;
|
||||
width:78%;
|
||||
}
|
||||
|
||||
#articles_data table tr.article td + td {
|
||||
padding-right:10px;
|
||||
text-align:right;
|
||||
#articles_data span.price {
|
||||
width:8%;
|
||||
}
|
||||
|
||||
#articles_data table tr.category {
|
||||
#articles_data span.stock {
|
||||
width:14%;
|
||||
}
|
||||
|
||||
#articles_data div.category {
|
||||
height:35px;
|
||||
line-height:35px;
|
||||
background-color:#c8102e;
|
||||
font-family:"Roboto Slab";
|
||||
font-size:16px;
|
||||
|
@ -335,7 +351,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
color:#FFF;
|
||||
}
|
||||
|
||||
#articles_data table tr.category>td:first-child {
|
||||
#articles_data div.category>span:first-child {
|
||||
padding-left:20px;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,14 +73,19 @@
|
|||
|
||||
.jconfirm .capslock .glyphicon {
|
||||
position: absolute;
|
||||
display:none;
|
||||
padding: 10px;
|
||||
right: 0px;
|
||||
top: 15px;
|
||||
font-size: 30px;
|
||||
display: none ;
|
||||
margin-left: 60px !important;
|
||||
}
|
||||
|
||||
.capslock_on .capslock .glyphicon{
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
|
||||
.jconfirm .capslock input {
|
||||
padding-right: 50px;
|
||||
padding-left: 50px;
|
||||
|
|
|
@ -1,148 +1,235 @@
|
|||
function KHistory(options={}) {
|
||||
$.extend(this, KHistory.default_options, options);
|
||||
var cancelHistory = new Event("cancel_done");
|
||||
|
||||
this.$container = $(this.container);
|
||||
class KHistory {
|
||||
|
||||
this.reset = function() {
|
||||
this.$container.html('');
|
||||
};
|
||||
static get default_options() {
|
||||
return {
|
||||
'templates': {
|
||||
'purchase': '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
|
||||
'specialope': '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
|
||||
'opegroup': '<div class="opegroup"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>',
|
||||
'transfergroup': '<div class="opegroup"><span class="time"></span><span class="infos"></span><span class="valid_by"></span><span class="comment"></span></div>',
|
||||
'day': '<div class="day"><span class="date"></span></div>',
|
||||
'transfer': '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="glyphicon glyphicon-arrow-right"></span><span class="infos2"></span><span class="canceled"></span></div>',
|
||||
},
|
||||
|
||||
this.addOpeGroup = function(opegroup) {
|
||||
var $day = this._getOrCreateDay(opegroup['at']);
|
||||
var $opegroup = this._opeGroupHtml(opegroup);
|
||||
'api_options': {
|
||||
from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
|
||||
$day.after($opegroup);
|
||||
|
||||
var trigramme = opegroup['on_acc_trigramme'];
|
||||
var is_cof = opegroup['is_cof'];
|
||||
for (var i=0; i<opegroup['opes'].length; i++) {
|
||||
var $ope = this._opeHtml(opegroup['opes'][i], is_cof, trigramme);
|
||||
$ope.data('opegroup', opegroup['id']);
|
||||
$opegroup.after($ope);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this._opeHtml = function(ope, is_cof, trigramme) {
|
||||
var $ope_html = $(this.template_ope);
|
||||
var parsed_amount = parseFloat(ope['amount']);
|
||||
var amount = amountDisplay(parsed_amount, is_cof, trigramme);
|
||||
var infos1 = '', infos2 = '';
|
||||
constructor(options) {
|
||||
var all_options = $.extend(true, {}, this.constructor.default_options, options);
|
||||
this.api_options = all_options.api_options;
|
||||
|
||||
if (ope['type'] == 'purchase') {
|
||||
infos1 = ope['article_nb'];
|
||||
infos2 = ope['article__name'];
|
||||
} else {
|
||||
infos1 = parsed_amount.toFixed(2)+'€';
|
||||
switch (ope['type']) {
|
||||
case 'initial':
|
||||
infos2 = 'Initial';
|
||||
break;
|
||||
case 'withdraw':
|
||||
infos2 = 'Retrait';
|
||||
break;
|
||||
case 'deposit':
|
||||
infos2 = 'Charge';
|
||||
break;
|
||||
case 'edit':
|
||||
infos2 = 'Édition';
|
||||
break;
|
||||
this._$container = $('#history');
|
||||
this._$nb_opes = $('#nb_opes');
|
||||
|
||||
this.data = new OperationList();
|
||||
|
||||
if (!all_options.no_select)
|
||||
this.selection = new KHistorySelection(this);
|
||||
|
||||
if (!all_options.static)
|
||||
OperationWebSocket.add_handler(data => this.update_data(data));
|
||||
|
||||
var templates = all_options.templates;
|
||||
if (all_options.no_trigramme)
|
||||
templates['opegroup'] = '<div class="opegroup"><span class="time"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>';
|
||||
|
||||
this.display = new ForestDisplay(this._$container, templates, this.data);
|
||||
|
||||
this._init_events();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
_init_events() {
|
||||
var that = this;
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.keyCode == 46 && that.selection) {
|
||||
//DEL key ; we delete the selected operations (if any)
|
||||
var to_cancel = that.selection.get_selected();
|
||||
|
||||
if (to_cancel['opes'].length > 0 || to_cancel['transfers'].length > 0)
|
||||
that.cancel_operations(to_cancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel_operations(to_cancel) {
|
||||
var that = this;
|
||||
var on_success = function() {
|
||||
if (that.selection)
|
||||
that.selection.reset();
|
||||
$(that).trigger("cancel_done");
|
||||
};
|
||||
|
||||
api_with_auth({
|
||||
url: Urls['kfet.kpsul.cancel_operations'](),
|
||||
data: to_cancel,
|
||||
on_success: on_success,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (options.from && dateUTCToParis(opegroup.content.at).isBefore(moment(options.from)))
|
||||
return false;
|
||||
|
||||
if (options.to && dateUTCToParis(opegroup.content.at).isAfter(moment(options.to)))
|
||||
return false;
|
||||
|
||||
var accounts_filter = options.accounts && options.accounts.length;
|
||||
var checkouts_filter = options.checkouts && options.checkouts.length;
|
||||
|
||||
if (opegroup.modelname == 'opegroup') {
|
||||
|
||||
if (options.transfersonly)
|
||||
return false;
|
||||
|
||||
if (accounts_filter && options.accounts.indexOf(opegroup.content.account_id) < 0)
|
||||
return false;
|
||||
|
||||
if (checkouts_filter && options.checkouts.indexOf(opegroup.content.checkout_id) < 0)
|
||||
return false;
|
||||
|
||||
} else if (opegroup.modelname == 'transfergroup') {
|
||||
|
||||
if (options.opesonly)
|
||||
return false;
|
||||
|
||||
if (checkouts_filter)
|
||||
return false;
|
||||
|
||||
if (accounts_filter) {
|
||||
opegroup.content.children =
|
||||
opegroup.content.children.filter( function(transfer) {
|
||||
var is_from_in =
|
||||
options.accounts.indexOf(transfer.content.from_acc_id) >= 0;
|
||||
var is_to_in =
|
||||
options.accounts.indexOf(transfer.content.to_acc_id) >= 0;
|
||||
return is_from_in || is_to_in;
|
||||
});
|
||||
if (opegroup.content.children.length == 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$ope_html
|
||||
.data('ope', ope['id'])
|
||||
.find('.amount').text(amount).end()
|
||||
.find('.infos1').text(infos1).end()
|
||||
.find('.infos2').text(infos2).end();
|
||||
return true;
|
||||
}
|
||||
|
||||
var addcost_for = ope['addcost_for__trigramme'];
|
||||
if (addcost_for) {
|
||||
var addcost_amount = parseFloat(ope['addcost_amount']);
|
||||
$ope_html.find('.addcost').text('('+amountDisplay(addcost_amount, is_cof)+'UKF pour '+addcost_for+')');
|
||||
update_data(data) {
|
||||
var opegroups = data['opegroups'];
|
||||
var opes = data['opes'];
|
||||
|
||||
for (let ope of opes) {
|
||||
if (ope['cancellation']) {
|
||||
let update_data = {
|
||||
'canceled_at': ope.canceled_at,
|
||||
'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);
|
||||
} else if (ope.modelname === 'transfer') {
|
||||
this.update_node('transfer', ope.id, update_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ope['canceled_at'])
|
||||
this.cancelOpe(ope, $ope_html);
|
||||
for (let opegroup of opegroups) {
|
||||
if (opegroup['cancellation']) {
|
||||
let update_data = { 'amount': opegroup.amount };
|
||||
this.update_node('opegroup', opegroup.id, update_data);
|
||||
}
|
||||
|
||||
return $ope_html;
|
||||
if (opegroup['add'] && this.is_valid(opegroup)) {
|
||||
this.add_node(opegroup);
|
||||
}
|
||||
}
|
||||
|
||||
var nb_opes = this._$container.find('.ope[canceled="false"]').length;
|
||||
$('#nb_opes').text(nb_opes);
|
||||
}
|
||||
|
||||
this.cancelOpe = function(ope, $ope = null) {
|
||||
if (!$ope)
|
||||
$ope = this.findOpe(ope['id']);
|
||||
|
||||
var cancel = 'Annulé';
|
||||
var canceled_at = dateUTCToParis(ope['canceled_at']);
|
||||
if (ope['canceled_by__trigramme'])
|
||||
cancel += ' par '+ope['canceled_by__trigramme'];
|
||||
cancel += ' le '+canceled_at.format('DD/MM/YY à HH:mm:ss');
|
||||
|
||||
$ope.addClass('canceled').find('.canceled').text(cancel);
|
||||
}
|
||||
|
||||
this._opeGroupHtml = function(opegroup) {
|
||||
var $opegroup_html = $(this.template_opegroup);
|
||||
|
||||
var at = dateUTCToParis(opegroup['at']).format('HH:mm:ss');
|
||||
var trigramme = opegroup['on_acc__trigramme'];
|
||||
var amount = amountDisplay(
|
||||
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
|
||||
var comment = opegroup['comment'] || '';
|
||||
|
||||
$opegroup_html
|
||||
.data('opegroup', opegroup['id'])
|
||||
.find('.time').text(at).end()
|
||||
.find('.amount').text(amount).end()
|
||||
.find('.comment').text(comment).end()
|
||||
.find('.trigramme').text(trigramme).end();
|
||||
|
||||
if (!this.display_trigramme)
|
||||
$opegroup_html.find('.trigramme').remove();
|
||||
|
||||
if (opegroup['valid_by__trigramme'])
|
||||
$opegroup_html.find('.valid_by').text('Par '+opegroup['valid_by__trigramme']);
|
||||
|
||||
return $opegroup_html;
|
||||
}
|
||||
|
||||
this._getOrCreateDay = function(date) {
|
||||
var at = dateUTCToParis(date);
|
||||
var at_ser = at.format('YYYY-MM-DD');
|
||||
var $day = this.$container.find('.day').filter(function() {
|
||||
return $(this).data('date') == at_ser
|
||||
});
|
||||
if ($day.length == 1)
|
||||
return $day;
|
||||
var $day = $(this.template_day).prependTo(this.$container);
|
||||
return $day.data('date', at_ser).text(at.format('D MMMM'));
|
||||
}
|
||||
|
||||
this.findOpeGroup = function(id) {
|
||||
return this.$container.find('.opegroup').filter(function() {
|
||||
return $(this).data('opegroup') == id
|
||||
});
|
||||
}
|
||||
|
||||
this.findOpe = function(id) {
|
||||
return this.$container.find('.ope').filter(function() {
|
||||
return $(this).data('ope') == id
|
||||
});
|
||||
}
|
||||
|
||||
this.cancelOpeGroup = function(opegroup) {
|
||||
var $opegroup = this.findOpeGroup(opegroup['id']);
|
||||
var trigramme = $opegroup.find('.trigramme').text();
|
||||
var amount = amountDisplay(
|
||||
parseFloat(opegroup['amount'], opegroup['is_cof'], trigramme));
|
||||
$opegroup.find('.amount').text(amount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
KHistory.default_options = {
|
||||
container: '#history',
|
||||
template_day: '<div class="day"></div>',
|
||||
template_opegroup: '<div class="opegroup"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>',
|
||||
template_ope: '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
|
||||
display_trigramme: true,
|
||||
class KHistorySelection {
|
||||
|
||||
constructor(history) {
|
||||
this._$container = history._$container;
|
||||
this._init();
|
||||
}
|
||||
|
||||
_init() {
|
||||
this._$container.selectable({
|
||||
filter: 'div.opegroup, div.ope',
|
||||
selected: function(e, ui) {
|
||||
$(ui.selected).each(function() {
|
||||
if ($(this).hasClass('opegroup')) {
|
||||
$(this).parent().find('.ope').addClass('ui-selected');
|
||||
}
|
||||
});
|
||||
},
|
||||
unselected: function(e, ui) {
|
||||
$(ui.unselected).each(function() {
|
||||
if ($(this).hasClass('opegroup')) {
|
||||
$(this).parent().find('.ope').removeClass('ui-selected');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get_selected() {
|
||||
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);
|
||||
else
|
||||
selected['opes'].push(id);
|
||||
});
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._$container.find('.ui-selected')
|
||||
.removeClass('.ui-selected');
|
||||
}
|
||||
}
|
||||
|
|
1893
kfet/static/kfet/js/kfet.api.js
Normal file
1893
kfet/static/kfet/js/kfet.api.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,139 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
@ -19,6 +154,7 @@ $.ajaxSetup({
|
|||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function add_csrf_form($form) {
|
||||
|
@ -28,6 +164,29 @@ function add_csrf_form($form) {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
@ -92,7 +251,7 @@ function amountDisplay(amount, is_cof=false, tri='') {
|
|||
|
||||
function amountToUKF(amount, is_cof=false, account=false) {
|
||||
var rounding = account ? Math.floor : Math.round ;
|
||||
var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1;
|
||||
var coef_cof = is_cof ? 1 + Config.get('subvention_cof') / 100 : 1;
|
||||
return rounding(amount * coef_cof * 10);
|
||||
}
|
||||
|
||||
|
@ -101,6 +260,58 @@ function isValidTrigramme(trigramme) {
|
|||
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)
|
||||
|
@ -148,59 +359,77 @@ function getErrorsHtml(data) {
|
|||
return content;
|
||||
}
|
||||
|
||||
function requestAuth(data, callback, focus_next = null) {
|
||||
var content = getErrorsHtml(data);
|
||||
content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
|
||||
$.confirm({
|
||||
title: 'Authentification requise',
|
||||
content: content,
|
||||
function displayErrors(html) {
|
||||
$.alert({
|
||||
title: 'Erreurs',
|
||||
content: html,
|
||||
backgroundDismiss: true,
|
||||
animation:'top',
|
||||
closeAnimation:'bottom',
|
||||
animation: 'top',
|
||||
closeAnimation: 'bottom',
|
||||
keyboardEnabled: true,
|
||||
confirm: function() {
|
||||
var password = this.$content.find('input').val();
|
||||
callback(password);
|
||||
},
|
||||
onOpen: function() {
|
||||
var that = this;
|
||||
var capslock = -1 ; // 1 -> caps on ; 0 -> caps off ; -1 or 2 -> unknown
|
||||
this.$content.find('input').on('keypress', function(e) {
|
||||
if (e.keyCode == 13)
|
||||
that.$confirmButton.click();
|
||||
|
||||
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
|
||||
capslock = 1 ;
|
||||
} 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
|
||||
capslock = 0 ;
|
||||
}
|
||||
if (capslock == 1)
|
||||
$('.capslock .glyphicon').show() ;
|
||||
else if (capslock == 0)
|
||||
$('.capslock .glyphicon').hide() ;
|
||||
});
|
||||
// Capslock key is not detected by keypress
|
||||
this.$content.find('input').on('keydown', function(e) {
|
||||
if (e.which == 20) {
|
||||
capslock = 1-capslock ;
|
||||
}
|
||||
if (capslock == 1)
|
||||
$('.capslock .glyphicon').show() ;
|
||||
else if (capslock == 0)
|
||||
$('.capslock .glyphicon').hide() ;
|
||||
});
|
||||
},
|
||||
onClose: function() {
|
||||
if (focus_next)
|
||||
this._lastFocused = focus_next;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
|
|
678
kfet/static/kfet/js/kpsul.js
Normal file
678
kfet/static/kfet/js/kpsul.js
Normal file
|
@ -0,0 +1,678 @@
|
|||
/**
|
||||
* @file K-Psul JS
|
||||
* @copyright 2017 cof-geek
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
class KPsulManager {
|
||||
|
||||
constructor(env) {
|
||||
this._env = env;
|
||||
this.account_manager = new AccountManager(this);
|
||||
this.checkout_manager = new CheckoutManager(this);
|
||||
this.article_manager = new ArticleManager(this);
|
||||
this.history = new KHistory({
|
||||
api_options: {'opesonly': true},
|
||||
});
|
||||
|
||||
this._init_events();
|
||||
}
|
||||
|
||||
reset(soft) {
|
||||
soft = soft || false;
|
||||
|
||||
this.account_manager.reset();
|
||||
this.article_manager.reset();
|
||||
|
||||
if (!soft) {
|
||||
this.checkout_manager.reset();
|
||||
this.article_manager.reset_data();
|
||||
this.history.fetch();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.checkout_manager.is_empty())
|
||||
this.checkout_manager.focus();
|
||||
else if (this.account_manager.is_empty())
|
||||
this.account_manager.focus();
|
||||
else
|
||||
this.article_manager.focus();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
_init_events() {
|
||||
$(this.history).on("cancel_done", () => this.reset(true).focus());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AccountManager {
|
||||
|
||||
constructor() {
|
||||
this._$container = $('#account');
|
||||
|
||||
this.account = new Account();
|
||||
this.selection = new AccountSelection(this);
|
||||
this.search = new AccountSearch(this);
|
||||
|
||||
// buttons: search, read or create
|
||||
this._$buttons_container = this._$container.find('.buttons');
|
||||
this._buttons_templates = {
|
||||
create: template`<a href="${'url'}" class="btn btn-primary" target="_blank" title="Créer ce compte"><span class="glyphicon glyphicon-plus"></span></a>`,
|
||||
read: template`<a href="${'url'}" class="btn btn-primary" target="_blank" title="Détails du compte"><span class="glyphicon glyphicon-info-sign"></span></a>`,
|
||||
search: template`<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>`,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
is_empty() { return this.account.is_empty(); }
|
||||
|
||||
display() {
|
||||
this._display_data();
|
||||
this._display_buttons();
|
||||
}
|
||||
|
||||
_display_data() {
|
||||
this.account.display(this._$container, {
|
||||
'prefix_prop': '#account-',
|
||||
});
|
||||
}
|
||||
|
||||
_display_buttons() {
|
||||
var buttons;
|
||||
|
||||
if (this.is_empty()) {
|
||||
var trigramme = this.selection.get();
|
||||
if (trigramme.isValidTri()) {
|
||||
let url = Account.url_create(trigramme);
|
||||
buttons = this._buttons_templates['create']({url: url});
|
||||
} else { /* trigramme input is empty or invalid */
|
||||
buttons = this._buttons_templates['search']();
|
||||
}
|
||||
} else { /* an account is loaded */
|
||||
let url = this.account.url_read;
|
||||
buttons = this._buttons_templates['read']({url: url});
|
||||
}
|
||||
|
||||
this._$buttons_container.html(buttons);
|
||||
}
|
||||
|
||||
update(trigramme) {
|
||||
if (trigramme !== undefined)
|
||||
this.selection.set(trigramme);
|
||||
|
||||
trigramme = trigramme || this.selection.get();
|
||||
|
||||
if (trigramme.isValidTri()) {
|
||||
this.account.get_by_apipk(trigramme)
|
||||
.done( () => this._update_on_success() )
|
||||
.fail( () => this.reset_data() );
|
||||
} else {
|
||||
this.reset_data();
|
||||
}
|
||||
}
|
||||
|
||||
_update_on_success() {
|
||||
$('#id_on_acc').val(this.account.id);
|
||||
this.display();
|
||||
|
||||
kpsul.focus();
|
||||
kpsul._env.updateBasketAmount();
|
||||
kpsul._env.updateBasketRel();
|
||||
}
|
||||
|
||||
reset() {
|
||||
$('#id_on_acc').val(0);
|
||||
this.selection.reset();
|
||||
this.search.reset();
|
||||
this.reset_data();
|
||||
}
|
||||
|
||||
reset_data() {
|
||||
this.account.clear();
|
||||
this.display();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.selection.focus();
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AccountSelection {
|
||||
|
||||
constructor(manager) {
|
||||
this.manager = manager;
|
||||
this._$input = $('#id_trigramme');
|
||||
|
||||
this._init_events();
|
||||
}
|
||||
|
||||
_init_events() {
|
||||
var that = this;
|
||||
|
||||
// user change trigramme
|
||||
this._$input
|
||||
.on('input', () => this.manager.update());
|
||||
|
||||
// LIQ shortcuts
|
||||
this._$input
|
||||
.on('keydown', function(e) {
|
||||
// keys: 13:Enter|40:Arrow-Down
|
||||
if (e.keyCode == 13 || e.keyCode == 40)
|
||||
that.manager.update('LIQ');
|
||||
});
|
||||
}
|
||||
|
||||
get() {
|
||||
return this._$input.val().formatTri();
|
||||
}
|
||||
|
||||
set(v) {
|
||||
this._$input.val(v);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._$input.focus();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.set('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AccountSearch {
|
||||
|
||||
constructor(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this._content = '<input type="text" name="q" id="search_autocomplete" autocomplete="off" spellcheck="false" autofocus><div id="account_results"></div>';
|
||||
this._input = '#search_autocomplete';
|
||||
this._results_container = '#account_results';
|
||||
|
||||
this._init_outer_events();
|
||||
}
|
||||
|
||||
open() {
|
||||
var that = this;
|
||||
this._$dialog = $.dialog({
|
||||
title: 'Recherche de compte',
|
||||
content: this._content,
|
||||
backgroundDismiss: true,
|
||||
animation: 'top',
|
||||
closeAnimation: 'bottom',
|
||||
keyboardEnabled: true,
|
||||
onOpen: function() {
|
||||
that._$input = $(that._input);
|
||||
that._$results_container = $(that._results_container);
|
||||
that._init_form()
|
||||
._init_inner_events();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_init_form() {
|
||||
var that = this;
|
||||
|
||||
this._$input.yourlabsAutocomplete({
|
||||
url: Urls['kfet.account.search.autocomplete'](),
|
||||
minimumCharacters: 2,
|
||||
id: 'search_autocomplete',
|
||||
choiceSelector: '.choice',
|
||||
placeholder: "Chercher un utilisateur K-Fêt",
|
||||
container: that._$results_container,
|
||||
box: that._$results_container,
|
||||
fixPosition: function() {},
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
_init_outer_events() {
|
||||
var that = this;
|
||||
|
||||
/* open search on button click */
|
||||
this.manager._$container
|
||||
.on('click', '.search', () => this.open());
|
||||
|
||||
/* open search on Ctrl-F */
|
||||
this.manager._$container
|
||||
.on('keydown', function(e) {
|
||||
if (e.which == 70 && e.ctrlKey) {
|
||||
that.open();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_init_inner_events() {
|
||||
this._$input.bind('selectChoice',
|
||||
(e, choice, autocomplete) => this._on_select(e, choice, autocomplete)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
_on_select(e, choice, autocomplete) {
|
||||
this.manager.update(choice.find('.trigramme').text());
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this._$dialog !== undefined) {
|
||||
this._$dialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CheckoutManager {
|
||||
|
||||
constructor() {
|
||||
this._$container = $('#checkout');
|
||||
this.display_prefix = '#checkout-';
|
||||
|
||||
this.checkout = new Checkout();
|
||||
this.selection = new CheckoutSelection(this);
|
||||
|
||||
this._$laststatement_container = $('#last_statement');
|
||||
this.laststatement = new Statement();
|
||||
this.laststatement_display_prefix = '#checkout-last_statement_';
|
||||
|
||||
this._$buttons_container = this._$container.find('.buttons');
|
||||
this._buttons_templates = {
|
||||
read: template`<a class="btn btn-primary" href="${'url'}" title="En savoir plus" target="_blank"><span class="glyphicon glyphicon-info-sign"></span></a>`,
|
||||
statement_create: template`<a href="${'url'}" title="Effectuer un relevé" class="btn btn-primary" target="_blank"><span class="glyphicon glyphicon-euro"></span></a>`,
|
||||
};
|
||||
|
||||
OperationWebSocket.add_handler(data => this.update_data(data));
|
||||
}
|
||||
|
||||
update(id) {
|
||||
if (id !== undefined)
|
||||
this.selection.set(id);
|
||||
|
||||
id = id || this.selection.get();
|
||||
|
||||
var api_options = {
|
||||
'last_statement': true,
|
||||
};
|
||||
|
||||
this.checkout.get_by_apipk(id, api_options)
|
||||
.done( (data) => this._update_on_success(data) )
|
||||
.fail( () => this.reset_data() );
|
||||
|
||||
kpsul.focus();
|
||||
}
|
||||
|
||||
_update_on_success(data) {
|
||||
if (data['laststatement'] !== undefined)
|
||||
this.laststatement.from(data['laststatement']);
|
||||
|
||||
$('#id_checkout').val(this.checkout.id);
|
||||
this.display();
|
||||
|
||||
}
|
||||
|
||||
update_data(ws_data) {
|
||||
let data = ws_data["checkouts"].find(o => o.id === this.checkout.id);
|
||||
if (!data)
|
||||
return;
|
||||
this.checkout.update(data);
|
||||
this._update_on_success(data);
|
||||
}
|
||||
|
||||
is_empty() { return this.checkout.is_empty(); }
|
||||
|
||||
display() {
|
||||
this._display_data();
|
||||
this._display_laststatement();
|
||||
this._display_buttons();
|
||||
}
|
||||
|
||||
_display_data() {
|
||||
this.checkout.display(this._$container, {
|
||||
'prefix_prop': this.display_prefix,
|
||||
});
|
||||
}
|
||||
|
||||
_display_laststatement() {
|
||||
if (this.laststatement.is_empty()) {
|
||||
this._$laststatement_container.hide();
|
||||
} else {
|
||||
this.laststatement.display(this._$laststatement_container, {
|
||||
'prefix_prop': this.laststatement_display_prefix
|
||||
});
|
||||
this._$laststatement_container.show();
|
||||
}
|
||||
}
|
||||
|
||||
_display_buttons() {
|
||||
var buttons = '';
|
||||
if (!this.is_empty()) {
|
||||
var url_newcheckout = Statement.url_create(this.checkout.id);
|
||||
buttons += this._buttons_templates['statement_create']({
|
||||
url: url_newcheckout});
|
||||
var url_read = this.checkout.url_read;
|
||||
buttons += this._buttons_templates['read']({url: url_read});
|
||||
}
|
||||
this._$buttons_container.html(buttons);
|
||||
}
|
||||
|
||||
reset() {
|
||||
$('#id_checkout').val(0);
|
||||
|
||||
this.selection.reset();
|
||||
this.reset_data();
|
||||
|
||||
if (this.selection.choices.length == 1)
|
||||
this.update(this.selection.choices[0]);
|
||||
}
|
||||
|
||||
reset_data() {
|
||||
this.checkout.clear();
|
||||
this.laststatement.clear();
|
||||
|
||||
this.display();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.selection.focus();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CheckoutSelection {
|
||||
|
||||
constructor(manager) {
|
||||
this.manager = manager;
|
||||
this._$input = $('#id_checkout_select');
|
||||
|
||||
this._init_events();
|
||||
|
||||
this.choices =
|
||||
this._$input.find('option[value!=""]')
|
||||
.toArray()
|
||||
.map(function(opt) {
|
||||
return parseInt($(opt).attr('value'));
|
||||
});
|
||||
}
|
||||
|
||||
_init_events() {
|
||||
this._$input.on('change', () => this.manager.update());
|
||||
}
|
||||
|
||||
get() {
|
||||
return this._$input.val() || 0;
|
||||
}
|
||||
|
||||
set(v) {
|
||||
this._$input.find('option[value='+ v +']').prop('selected', true);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._$input.find('option:first').prop('selected', true);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._$input.focus();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleManager {
|
||||
|
||||
constructor(env) {
|
||||
this._env = env; // Global K-Psul Manager
|
||||
|
||||
this._$container = $('#articles_data');
|
||||
this._$input = $('#article_autocomplete');
|
||||
this._$nb = $('#article_number');
|
||||
this._$stock = $('#article_stock');
|
||||
|
||||
this.selected = new Article();
|
||||
this.data = new ArticleList();
|
||||
var $container = $('#articles_data');
|
||||
var templates = {
|
||||
category: '<div class="category"><span class="name"></span></div>',
|
||||
article: '<div class="article"><span class="name"></span><span class="price"></span><span class="stock"></span></div>',
|
||||
};
|
||||
this.display = new ForestDisplay($container, templates, this.data);
|
||||
this.autocomplete = new ArticleAutocomplete(this, $container);
|
||||
|
||||
this._init_events();
|
||||
OperationWebSocket.add_handler(data => this.update_data(data));
|
||||
}
|
||||
|
||||
get nb() {
|
||||
return this._$nb.val();
|
||||
}
|
||||
|
||||
display_list() {
|
||||
this.display.render(this.data);
|
||||
}
|
||||
|
||||
validate(article) {
|
||||
this.selected.from(article);
|
||||
this._$input.val(article.name);
|
||||
this._$nb.val('1');
|
||||
this._$stock.text('/'+article.stock);
|
||||
this._$nb.focus().select();
|
||||
}
|
||||
|
||||
unset() {
|
||||
this.selected.clear();
|
||||
}
|
||||
|
||||
is_empty() {
|
||||
return this.selected.is_empty();
|
||||
}
|
||||
|
||||
reset_data() {
|
||||
this.display.clear();
|
||||
this.data.clear();
|
||||
this.data.fromAPI()
|
||||
.done( () => this.display_list() );
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.unset();
|
||||
this._$stock.text('');
|
||||
this._$nb.val('');
|
||||
this._$input.val('');
|
||||
this.autocomplete.showAll();
|
||||
}
|
||||
|
||||
_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)$/;
|
||||
|
||||
//Global input event (to merge ?)
|
||||
this._$input.on('keydown', function(e) {
|
||||
if (e.keyCode == 13 && that._$input.val() == '') {
|
||||
kpsul._env.performOperations();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (normalKeys.test(e.keyCode) || arrowKeys.test(e.keyCode) || e.ctrlKey) {
|
||||
if (e.ctrlKey && e.charCode == 65)
|
||||
that._$nb.val('');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (that.constructor.check_nb(that.nb+e.key))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//Note : this function may not be needed after the whole rework
|
||||
get_article(id) {
|
||||
return this.data.find('article', id);
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.is_empty())
|
||||
this._$input.focus();
|
||||
else
|
||||
this._$nb.focus();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static check_nb(nb) {
|
||||
return /^[0-9]+$/.test(nb) && nb > 0 && nb <= 24;
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleAutocomplete {
|
||||
|
||||
constructor(article_manager, $container) {
|
||||
this.manager = article_manager;
|
||||
this._$container = $container;
|
||||
this._$input = $('#article_autocomplete');
|
||||
|
||||
this.showAll();
|
||||
this._init_events();
|
||||
}
|
||||
|
||||
_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)$/;
|
||||
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
that.update(text+e.key, false);
|
||||
|
||||
return false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
update(prefix, backspace) {
|
||||
|
||||
this.resetMatch();
|
||||
var article_list = this.manager.data;
|
||||
var lower = prefix.toLowerCase();
|
||||
var that = this;
|
||||
|
||||
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) {
|
||||
this.manager.unset();
|
||||
this.updateDisplay();
|
||||
if (!backspace)
|
||||
this.updatePrefix();
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
var that = this;
|
||||
|
||||
this.manager.data.traverse('category', function(category) {
|
||||
var is_active = false;
|
||||
for (let article of category.articles) {
|
||||
if (that.matching.indexOf(article) != -1) {
|
||||
is_active = true;
|
||||
that._$container.find('#article-'+article.id).show();
|
||||
} else {
|
||||
that._$container.find('#article-'+article.id).hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_active) {
|
||||
that._$container.find('#category-'+category.id).show();
|
||||
} else {
|
||||
that._$container.find('#category-'+category.id).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updatePrefix() {
|
||||
var lower = this.matching.map(function (article) {
|
||||
return article.name.toLowerCase();
|
||||
});
|
||||
|
||||
lower.sort();
|
||||
var first = lower[0], last = lower[lower.length-1],
|
||||
length = first.length, i = 0;
|
||||
while (i < length && first.charAt(i) === last.charAt(i)) i++;
|
||||
|
||||
this._$input.val(first.substring(0,i));
|
||||
}
|
||||
|
||||
showAll() {
|
||||
var that = this;
|
||||
this.resetMatch();
|
||||
this.manager.data.traverse('article', function(article) {
|
||||
that.matching.push(article);
|
||||
});
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
resetMatch() {
|
||||
this.matching = [];
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
{% load l10n %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||
{% if account.user == request.user %}
|
||||
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
||||
|
@ -85,33 +86,11 @@ $(document).ready(function() {
|
|||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
|
||||
'use strict';
|
||||
|
||||
khistory = new KHistory({
|
||||
display_trigramme: false,
|
||||
});
|
||||
var khistory = new KHistory({'no_trigramme': true});
|
||||
|
||||
function getHistory() {
|
||||
var data = {
|
||||
'accounts': [{{ account.pk }}],
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.history.json' %}",
|
||||
method : "POST",
|
||||
data : data,
|
||||
})
|
||||
.done(function(data) {
|
||||
for (var i=0; i<data['opegroups'].length; i++) {
|
||||
khistory.addOpeGroup(data['opegroups'][i]);
|
||||
}
|
||||
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
|
||||
$('#nb_opes').text(nb_opes);
|
||||
});
|
||||
}
|
||||
|
||||
getHistory();
|
||||
Config.reset(() => khistory.fetch({'accounts': [{{account.pk}}]}));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
||||
<script type="text/javascript" src="{% url 'js_reverse' %}"></script>
|
||||
|
||||
{% include "kfetopen/init.html" %}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
||||
{{ filter_form.media }}
|
||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||
|
@ -32,14 +33,14 @@
|
|||
|
||||
{% block main %}
|
||||
|
||||
<table id="history" class="table">
|
||||
</table>
|
||||
<div id="history">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
|
||||
'use strict';
|
||||
|
||||
khistory = new KHistory();
|
||||
var khistory = new KHistory();
|
||||
|
||||
var $from_date = $('#id_from_date');
|
||||
var $to_date = $('#id_to_date');
|
||||
|
@ -54,7 +55,9 @@ $(document).ready(function() {
|
|||
return selected;
|
||||
}
|
||||
|
||||
function getHistory() {
|
||||
function updateHistory() {
|
||||
|
||||
// Get API options
|
||||
var data = {};
|
||||
if ($from_date.val())
|
||||
data['from'] = moment($from_date.val()).format('YYYY-MM-DD HH:mm:ss');
|
||||
|
@ -64,21 +67,11 @@ $(document).ready(function() {
|
|||
if ($checkouts)
|
||||
data['checkouts'] = checkouts;
|
||||
var accounts = getSelectedMultiple($accounts);
|
||||
data['accounts'] = accounts;
|
||||
if (accounts)
|
||||
data['accounts'] = accounts.map(id => parseInt(id));
|
||||
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.history.json' %}",
|
||||
method : "POST",
|
||||
data : data,
|
||||
})
|
||||
.done(function(data) {
|
||||
for (var i=0; i<data['opegroups'].length; i++) {
|
||||
khistory.addOpeGroup(data['opegroups'][i]);
|
||||
}
|
||||
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
|
||||
$('#nb_opes').text(nb_opes);
|
||||
});
|
||||
// Update history
|
||||
khistory.fetch(data);
|
||||
}
|
||||
|
||||
let defaults_datetimepicker = {
|
||||
|
@ -92,8 +85,9 @@ $(document).ready(function() {
|
|||
$from_date.datetimepicker($.extend({}, defaults_datetimepicker, {
|
||||
defaultDate: moment().subtract(24, 'hours'),
|
||||
}));
|
||||
|
||||
$to_date.datetimepicker($.extend({}, defaults_datetimepicker, {
|
||||
defaultDate: moment(),
|
||||
defaultDate: moment().add(5, 'minutes') // workaround for 'stepping' rounding
|
||||
}));
|
||||
|
||||
$("#from_date").on("dp.change", function (e) {
|
||||
|
@ -111,131 +105,11 @@ $(document).ready(function() {
|
|||
countSelected: "# sur %"
|
||||
});
|
||||
|
||||
$("input").on('dp.change change', function() {
|
||||
khistory.reset();
|
||||
getHistory();
|
||||
$("#update_history").on('click', function() {
|
||||
updateHistory();
|
||||
});
|
||||
|
||||
khistory.$container.selectable({
|
||||
filter: 'div.opegroup, div.ope',
|
||||
selected: function(e, ui) {
|
||||
$(ui.selected).each(function() {
|
||||
if ($(this).hasClass('opegroup')) {
|
||||
var opegroup = $(this).data('opegroup');
|
||||
$(this).siblings('.ope').filter(function() {
|
||||
return $(this).data('opegroup') == opegroup
|
||||
}).addClass('ui-selected');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
$(document).on('keydown', function (e) {
|
||||
if (e.keyCode == 46) {
|
||||
// DEL (Suppr)
|
||||
var opes_to_cancel = [];
|
||||
khistory.$container.find('.ope.ui-selected').each(function () {
|
||||
opes_to_cancel.push($(this).data('ope'));
|
||||
});
|
||||
if (opes_to_cancel.length > 0)
|
||||
confirmCancel(opes_to_cancel);
|
||||
}
|
||||
});
|
||||
|
||||
function confirmCancel(opes_to_cancel) {
|
||||
var nb = opes_to_cancel.length;
|
||||
var content = nb+" opérations vont être annulées";
|
||||
$.confirm({
|
||||
title: 'Confirmation',
|
||||
content: content,
|
||||
backgroundDismiss: true,
|
||||
animation: 'top',
|
||||
closeAnimation: 'bottom',
|
||||
keyboardEnabled: true,
|
||||
confirm: function() {
|
||||
cancelOperations(opes_to_cancel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function requestAuth(data, callback) {
|
||||
var content = getErrorsHtml(data);
|
||||
content += '<input type="password" name="password" autofocus>',
|
||||
$.confirm({
|
||||
title: 'Authentification requise',
|
||||
content: content,
|
||||
backgroundDismiss: true,
|
||||
animation:'top',
|
||||
closeAnimation:'bottom',
|
||||
keyboardEnabled: true,
|
||||
confirm: function() {
|
||||
var password = this.$content.find('input').val();
|
||||
callback(password);
|
||||
},
|
||||
onOpen: function() {
|
||||
var that = this;
|
||||
this.$content.find('input').on('keypress', function(e) {
|
||||
if (e.keyCode == 13)
|
||||
that.$confirmButton.click();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getErrorsHtml(data) {
|
||||
var content = '';
|
||||
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']) {
|
||||
var url_base = "{% url 'kfet.account.update' LIQ}";
|
||||
url_base = base_url(0, url_base.length-8);
|
||||
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">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function cancelOperations(opes_array, password = '') {
|
||||
var data = { 'operations' : opes_array }
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.kpsul.cancel_operations' %}",
|
||||
method : "POST",
|
||||
data : data,
|
||||
beforeSend: function ($xhr) {
|
||||
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
if (password != '')
|
||||
$xhr.setRequestHeader("KFetPassword", password);
|
||||
},
|
||||
|
||||
})
|
||||
.done(function(data) {
|
||||
khistory.$container.find('.ui-selected').removeClass('ui-selected');
|
||||
})
|
||||
.fail(function($xhr) {
|
||||
var data = $xhr.responseJSON;
|
||||
switch ($xhr.status) {
|
||||
case 403:
|
||||
requestAuth(data, function(password) {
|
||||
cancelOperations(opes_array, password);
|
||||
});
|
||||
break;
|
||||
case 400:
|
||||
displayErrors(getErrorsHtml(data));
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getHistory();
|
||||
Config.reset(updateHistory);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +1,22 @@
|
|||
{% extends 'kfet/base_col_2.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Transferts{% endblock %}
|
||||
{% block header-title %}Transferts{% endblock %}
|
||||
|
||||
{% block fixed %}
|
||||
|
||||
<aside>
|
||||
<div class="heading">
|
||||
<div id="nb_opes"></div>
|
||||
<div class="sub">transferts</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary" href="{% url 'kfet.transfers.create' %}">
|
||||
Nouveaux
|
||||
|
@ -16,109 +27,17 @@
|
|||
|
||||
{% block main %}
|
||||
|
||||
<div id="history">
|
||||
{% for transfergroup in transfergroups %}
|
||||
<div class="opegroup transfergroup" data-transfergroup="{{ transfergroup.pk }}">
|
||||
<span>{{ transfergroup.at }}</span>
|
||||
<span>{{ transfergroup.valid_by.trigramme }}</span>
|
||||
<span>{{ transfergroup.comment }}</span>
|
||||
</div>
|
||||
{% for transfer in transfergroup.transfers.all %}
|
||||
<div class="ope transfer{% if transfer.canceled_at %} canceled{% endif %}" data-transfer="{{ transfer.pk }}" data-transfergroup="{{ transfergroup.pk }}">
|
||||
<span class="amount">{{ transfer.amount }} €</span>
|
||||
<span class="from_acc">{{ transfer.from_acc.trigramme }}</span>
|
||||
<span class="glyphicon glyphicon-arrow-right"></span>
|
||||
<span class="to_acc">{{ transfer.to_acc.trigramme }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<div id="history" class="table">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function() {
|
||||
'use strict';
|
||||
|
||||
lock = 0;
|
||||
|
||||
function displayErrors(html) {
|
||||
$.alert({
|
||||
title: 'Erreurs',
|
||||
content: html,
|
||||
backgroundDismiss: true,
|
||||
animation: 'top',
|
||||
closeAnimation: 'bottom',
|
||||
keyboardEnabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function cancelTransfers(transfers_array, password = '') {
|
||||
if (lock == 1)
|
||||
return false
|
||||
lock = 1;
|
||||
var data = { 'transfers' : transfers_array }
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.transfers.cancel' %}",
|
||||
method : "POST",
|
||||
data : data,
|
||||
beforeSend: function ($xhr) {
|
||||
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
if (password != '')
|
||||
$xhr.setRequestHeader("KFetPassword", password);
|
||||
},
|
||||
|
||||
})
|
||||
.done(function(data) {
|
||||
for (var i=0; i<data['canceled'].length; i++) {
|
||||
$('#history').find('.transfer[data-transfer='+data['canceled'][i]+']')
|
||||
.addClass('canceled');
|
||||
}
|
||||
$('#history').find('.ui-selected').removeClass('ui-selected');
|
||||
lock = 0;
|
||||
})
|
||||
.fail(function($xhr) {
|
||||
var data = $xhr.responseJSON;
|
||||
switch ($xhr.status) {
|
||||
case 403:
|
||||
requestAuth(data, function(password) {
|
||||
cancelTransfers(transfers_array, password);
|
||||
});
|
||||
break;
|
||||
case 400:
|
||||
displayErrors(getErrorsHtml(data));
|
||||
break;
|
||||
}
|
||||
lock = 0;
|
||||
});
|
||||
}
|
||||
|
||||
$('#history').selectable({
|
||||
filter: 'div.transfergroup, div.transfer',
|
||||
selected: function(e, ui) {
|
||||
$(ui.selected).each(function() {
|
||||
if ($(this).hasClass('transfergroup')) {
|
||||
var transfergroup = $(this).attr('data-transfergroup');
|
||||
$(this).siblings('.ope').filter(function() {
|
||||
return $(this).attr('data-transfergroup') == transfergroup
|
||||
}).addClass('ui-selected');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
$(document).on('keydown', function (e) {
|
||||
if (e.keyCode == 46) {
|
||||
// DEL (Suppr)
|
||||
var transfers_to_cancel = [];
|
||||
$('#history').find('.transfer.ui-selected').each(function () {
|
||||
transfers_to_cancel.push($(this).attr('data-transfer'));
|
||||
});
|
||||
if (transfers_to_cancel.length > 0)
|
||||
cancelTransfers(transfers_to_cancel);
|
||||
}
|
||||
});
|
||||
var khistory = new KHistory();
|
||||
|
||||
Config.reset(() => khistory.fetch({'transfersonly': true}));
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/transfers_form.css' %}">
|
||||
<script type="text/javascript" src="{% url 'js_reverse' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Nouveaux transferts{% endblock %}
|
||||
|
@ -51,14 +52,12 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
function getAccountData(trigramme, callback = function() {}) {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.account.read.json' %}",
|
||||
method : "POST",
|
||||
data : { trigramme: trigramme },
|
||||
success : callback,
|
||||
});
|
||||
function getAccountData(trigramme, callback) {
|
||||
callback = callback || $.noop;
|
||||
$.getJSON(Urls['kfet.account.read'](trigramme), {
|
||||
'format': 'json',
|
||||
})
|
||||
.done(callback);
|
||||
}
|
||||
|
||||
function updateAccountData(trigramme, $input) {
|
||||
|
|
|
@ -4,6 +4,7 @@ from decimal import Decimal
|
|||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import Client, TestCase
|
||||
from django.utils import timezone
|
||||
|
@ -12,7 +13,7 @@ from ..config import kfet_config
|
|||
from ..models import (
|
||||
Account, Article, ArticleCategory, Checkout, CheckoutStatement, Inventory,
|
||||
InventoryArticle, Operation, OperationGroup, Order, OrderArticle, Supplier,
|
||||
SupplierArticle, Transfer, TransferGroup,
|
||||
SupplierArticle, TransferGroup,
|
||||
)
|
||||
from .testcases import ViewTestCaseMixin
|
||||
from .utils import create_team, create_user, get_perms
|
||||
|
@ -248,6 +249,26 @@ class AccountReadViewTests(ViewTestCaseMixin, TestCase):
|
|||
r = client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_json(self):
|
||||
r = self.client.get(self.url, {'format': 'json'})
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
content = json.loads(r.content.decode('utf-8'))
|
||||
|
||||
expected = {
|
||||
'name': 'first last',
|
||||
'trigramme': '001',
|
||||
'balance': '0.00',
|
||||
}
|
||||
self.assertDictContainsSubset(expected, content)
|
||||
|
||||
self.assertSetEqual(set(content.keys()), set([
|
||||
'id', 'trigramme', 'first_name', 'last_name', 'name', 'email',
|
||||
'is_cof', 'promo', 'balance', 'is_frozen', 'departement',
|
||||
'nickname',
|
||||
]))
|
||||
|
||||
|
||||
class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.account.update'
|
||||
|
@ -762,6 +783,35 @@ class CheckoutReadViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.context['checkout'], self.checkout)
|
||||
|
||||
def test_json(self):
|
||||
r = self.client.get(self.url, {
|
||||
'format': 'json',
|
||||
'last_statement': '1',
|
||||
})
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
content = json.loads(r.content.decode('utf-8'))
|
||||
json_now = json.dumps(self.now, cls=DjangoJSONEncoder).strip('"')
|
||||
json_tomorrow = json.dumps(
|
||||
self.now + timedelta(days=1),
|
||||
cls=DjangoJSONEncoder
|
||||
).strip('"')
|
||||
|
||||
self.assertEqual(content, {
|
||||
'id': self.checkout.pk,
|
||||
'name': 'Checkout',
|
||||
'balance': '10.00',
|
||||
'valid_from': json_now,
|
||||
'valid_to': json_tomorrow,
|
||||
'laststatement': {
|
||||
'id': self.checkout.statements.all()[0].pk,
|
||||
'at': json_now,
|
||||
'balance_new': '10.00',
|
||||
'balance_old': '10.00',
|
||||
'by': str(self.accounts['team']),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class CheckoutUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.checkout.update'
|
||||
|
@ -1411,46 +1461,6 @@ class KPsulViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
class KPsulCheckoutDataViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.kpsul.checkout_data'
|
||||
url_expected = '/k-fet/k-psul/checkout_data'
|
||||
|
||||
http_methods = ['POST']
|
||||
|
||||
auth_user = 'team'
|
||||
auth_forbidden = [None, 'user']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.checkout = Checkout.objects.create(
|
||||
name='Checkout',
|
||||
balance=Decimal('10'),
|
||||
created_by=self.accounts['team'],
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + timedelta(days=5),
|
||||
)
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.post(self.url, {'pk': self.checkout.pk})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
content = json.loads(r.content.decode('utf-8'))
|
||||
|
||||
expected = {
|
||||
'name': 'Checkout',
|
||||
'balance': '10.00',
|
||||
}
|
||||
|
||||
self.assertDictContainsSubset(expected, content)
|
||||
|
||||
self.assertSetEqual(set(content.keys()), set([
|
||||
'balance', 'id', 'name', 'valid_from', 'valid_to',
|
||||
'last_statement_at', 'last_statement_balance',
|
||||
'last_statement_by_first_name', 'last_statement_by_last_name',
|
||||
'last_statement_by_trigramme',
|
||||
]))
|
||||
|
||||
|
||||
class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.kpsul.perform_operations'
|
||||
url_expected = '/k-fet/k-psul/perform_operations'
|
||||
|
@ -1470,11 +1480,82 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
http_methods = ['POST']
|
||||
|
||||
auth_user = 'team'
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user']
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=[
|
||||
'kfet.perform_negative_operations',
|
||||
]),
|
||||
'u1': create_user('user1', '001'),
|
||||
'u2': create_user('user2', '002'),
|
||||
'u3': create_user('user3', '003'),
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
trg1 = TransferGroup.objects.create()
|
||||
self.trg1_1 = trg1.transfers.create(
|
||||
from_acc=self.accounts['u1'],
|
||||
to_acc=self.accounts['u2'],
|
||||
amount='3.5',
|
||||
)
|
||||
self.trg1_2 = trg1.transfers.create(
|
||||
from_acc=self.accounts['u2'],
|
||||
to_acc=self.accounts['u3'],
|
||||
amount='2.4',
|
||||
)
|
||||
|
||||
trg2 = TransferGroup.objects.create(
|
||||
at=(
|
||||
timezone.now() -
|
||||
(kfet_config.cancel_duration + timedelta(seconds=1))
|
||||
),
|
||||
)
|
||||
self.trg2_1 = trg2.transfers.create(
|
||||
from_acc=self.accounts['u1'],
|
||||
to_acc=self.accounts['u2'],
|
||||
amount='5',
|
||||
)
|
||||
|
||||
def test_ok(self):
|
||||
pass
|
||||
data = {
|
||||
'transfers[]': [str(self.trg1_1.pk), str(self.trg1_2.pk)],
|
||||
}
|
||||
|
||||
r = self.client.post(self.url, data)
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
u1 = self.accounts['u1']
|
||||
u1.refresh_from_db()
|
||||
self.assertEqual(u1.balance, Decimal('3.5'))
|
||||
|
||||
u2 = self.accounts['u2']
|
||||
u2.refresh_from_db()
|
||||
self.assertEqual(u2.balance, Decimal('-1.1'))
|
||||
|
||||
u3 = self.accounts['u3']
|
||||
u3.refresh_from_db()
|
||||
self.assertEqual(u3.balance, Decimal('-2.4'))
|
||||
|
||||
def test_error_tooold(self):
|
||||
data = {
|
||||
'transfers[]': [str(self.trg2_1.pk)],
|
||||
}
|
||||
|
||||
r = self.client.post(self.url, data)
|
||||
|
||||
self.assertEqual(r.status_code, 403)
|
||||
self.assertDictEqual(json.loads(r.content.decode('utf-8')), {
|
||||
'canceled': {},
|
||||
'warnings': {},
|
||||
'errors': {
|
||||
'missing_perms': ['Annuler des commandes non récentes'],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class KPsulArticlesData(ViewTestCaseMixin, TestCase):
|
||||
|
@ -1486,16 +1567,10 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
category = ArticleCategory.objects.create(name='Catégorie')
|
||||
self.article1 = Article.objects.create(
|
||||
category=category,
|
||||
name='Article 1',
|
||||
)
|
||||
self.article2 = Article.objects.create(
|
||||
category=category,
|
||||
name='Article 2',
|
||||
price=Decimal('2.5'),
|
||||
)
|
||||
self.ac = ArticleCategory.objects.create(name='Catégorie')
|
||||
|
||||
self.a1 = self.ac.articles.create(name='Article 1')
|
||||
self.a2 = self.ac.articles.create(name='Article 2', price='2.5')
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
|
@ -1503,24 +1578,35 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
|
|||
|
||||
content = json.loads(r.content.decode('utf-8'))
|
||||
|
||||
articles = content['articles']
|
||||
|
||||
expected_list = [{
|
||||
'category__name': 'Catégorie',
|
||||
'name': 'Article 1',
|
||||
'price': '0.00',
|
||||
}, {
|
||||
'category__name': 'Catégorie',
|
||||
'name': 'Article 2',
|
||||
'price': '2.50',
|
||||
}]
|
||||
|
||||
for expected, article in zip(expected_list, articles):
|
||||
self.assertDictContainsSubset(expected, article)
|
||||
self.assertSetEqual(set(article.keys()), set([
|
||||
'id', 'name', 'price', 'stock',
|
||||
'category_id', 'category__name', 'category__has_addcost',
|
||||
]))
|
||||
self.assertEqual(content, {
|
||||
'objects': {
|
||||
'article': [
|
||||
{
|
||||
'id': self.a1.pk,
|
||||
'name': 'Article 1',
|
||||
'price': '0.00',
|
||||
'stock': 0,
|
||||
'category__id': self.a1.category.pk,
|
||||
},
|
||||
{
|
||||
'id': self.a2.pk,
|
||||
'name': 'Article 2',
|
||||
'price': '2.50',
|
||||
'stock': 0,
|
||||
'category__id': self.a2.category.pk,
|
||||
},
|
||||
],
|
||||
},
|
||||
'related': {
|
||||
'category': [
|
||||
{
|
||||
'id': self.ac.pk,
|
||||
'name': 'Catégorie',
|
||||
'has_addcost': True,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class KPsulUpdateAddcost(ViewTestCaseMixin, TestCase):
|
||||
|
@ -1586,34 +1672,6 @@ class HistoryJSONViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.account.read.json'
|
||||
url_expected = '/k-fet/accounts/read.json'
|
||||
|
||||
http_methods = ['POST']
|
||||
|
||||
auth_user = 'team'
|
||||
auth_forbidden = [None, 'user']
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.post(self.url, {'trigramme': '000'})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
content = json.loads(r.content.decode('utf-8'))
|
||||
|
||||
expected = {
|
||||
'name': 'first last',
|
||||
'trigramme': '000',
|
||||
'balance': '0.00',
|
||||
}
|
||||
self.assertDictContainsSubset(expected, content)
|
||||
|
||||
self.assertSetEqual(set(content.keys()), set([
|
||||
'balance', 'departement', 'email', 'id', 'is_cof', 'is_frozen',
|
||||
'name', 'nickname', 'promo', 'trigramme',
|
||||
]))
|
||||
|
||||
|
||||
class SettingsListViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.settings'
|
||||
url_expected = '/k-fet/settings/'
|
||||
|
@ -1766,62 +1824,6 @@ class TransferPerformViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(team1.balance, Decimal('2.4'))
|
||||
|
||||
|
||||
class TransferCancelViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.transfers.cancel'
|
||||
url_expected = '/k-fet/transfers/cancel'
|
||||
|
||||
http_methods = ['POST']
|
||||
|
||||
auth_user = 'team1'
|
||||
auth_forbidden = [None, 'user', 'team']
|
||||
|
||||
def get_users_extra(self):
|
||||
return {
|
||||
'team1': create_team('team1', '101', perms=[
|
||||
# Convenience
|
||||
'kfet.perform_negative_operations',
|
||||
]),
|
||||
}
|
||||
|
||||
@property
|
||||
def post_data(self):
|
||||
return {
|
||||
'transfers[]': [self.transfer1.pk, self.transfer2.pk],
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
group = TransferGroup.objects.create()
|
||||
self.transfer1 = Transfer.objects.create(
|
||||
group=group,
|
||||
from_acc=self.accounts['user'],
|
||||
to_acc=self.accounts['team'],
|
||||
amount='3.5',
|
||||
)
|
||||
self.transfer2 = Transfer.objects.create(
|
||||
group=group,
|
||||
from_acc=self.accounts['team'],
|
||||
to_acc=self.accounts['root'],
|
||||
amount='2.4',
|
||||
)
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.post(self.url, self.post_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
user = self.accounts['user']
|
||||
user.refresh_from_db()
|
||||
self.assertEqual(user.balance, Decimal('3.5'))
|
||||
|
||||
team = self.accounts['team']
|
||||
team.refresh_from_db()
|
||||
self.assertEqual(team.balance, Decimal('-1.1'))
|
||||
|
||||
root = self.accounts['root']
|
||||
root.refresh_from_db()
|
||||
self.assertEqual(root.balance, Decimal('-2.4'))
|
||||
|
||||
|
||||
class InventoryListViewTests(ViewTestCaseMixin, TestCase):
|
||||
url_name = 'kfet.inventory'
|
||||
url_expected = '/k-fet/inventaires/'
|
||||
|
|
|
@ -159,8 +159,6 @@ urlpatterns = [
|
|||
# -----
|
||||
|
||||
url('^k-psul/$', views.kpsul, name='kfet.kpsul'),
|
||||
url('^k-psul/checkout_data$', views.kpsul_checkout_data,
|
||||
name='kfet.kpsul.checkout_data'),
|
||||
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
|
||||
name='kfet.kpsul.perform_operations'),
|
||||
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
|
||||
|
@ -178,9 +176,6 @@ urlpatterns = [
|
|||
|
||||
url(r'^history.json$', views.history_json,
|
||||
name='kfet.history.json'),
|
||||
url(r'^accounts/read.json$', views.account_read_json,
|
||||
name='kfet.account.read.json'),
|
||||
|
||||
|
||||
# -----
|
||||
# Settings urls
|
||||
|
@ -202,8 +197,6 @@ urlpatterns = [
|
|||
name='kfet.transfers.create'),
|
||||
url(r'^transfers/perform$', views.perform_transfers,
|
||||
name='kfet.transfers.perform'),
|
||||
url(r'^transfers/cancel$', views.cancel_transfers,
|
||||
name='kfet.transfers.cancel'),
|
||||
|
||||
# -----
|
||||
# Inventories urls
|
||||
|
|
709
kfet/views.py
709
kfet/views.py
|
@ -15,7 +15,7 @@ from django.contrib.auth.models import User, Permission
|
|||
from django.http import JsonResponse, Http404
|
||||
from django.forms import formset_factory
|
||||
from django.db import transaction
|
||||
from django.db.models import F, Sum, Prefetch, Count
|
||||
from django.db.models import Q, F, Sum, Prefetch, Count
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
|
@ -47,12 +47,36 @@ from decimal import Decimal
|
|||
import heapq
|
||||
import statistics
|
||||
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
|
||||
|
||||
from .auth.views import ( # noqa
|
||||
account_group, login_generic, AccountGroupCreate, AccountGroupUpdate,
|
||||
)
|
||||
|
||||
|
||||
# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/
|
||||
class JSONResponseMixin(object):
|
||||
"""
|
||||
A mixin that can be used to render a JSON response.
|
||||
"""
|
||||
def render_to_json_response(self, context, **response_kwargs):
|
||||
"""
|
||||
Returns a JSON response, transforming 'context' to make the payload.
|
||||
"""
|
||||
return JsonResponse(
|
||||
self.get_data(context),
|
||||
**response_kwargs
|
||||
)
|
||||
|
||||
def get_data(self, context):
|
||||
"""
|
||||
Returns an object that will be serialized as JSON by json.dumps().
|
||||
"""
|
||||
# Note: This is *EXTREMELY* naive; in reality, you'll need
|
||||
# to do much more complex handling to ensure that arbitrary
|
||||
# objects -- such as Django model instances or querysets
|
||||
# -- can be serialized as JSON.
|
||||
return context
|
||||
|
||||
|
||||
def put_cleaned_data_in_dict(dict, form):
|
||||
for field in form.cleaned_data:
|
||||
dict[field] = form.cleaned_data[field]
|
||||
|
@ -323,6 +347,13 @@ def account_read(request, trigramme):
|
|||
request.user != account.user):
|
||||
raise PermissionDenied
|
||||
|
||||
if request.GET.get('format') == 'json':
|
||||
export_keys = ['id', 'trigramme', 'first_name', 'last_name', 'name',
|
||||
'email', 'is_cof', 'promo', 'balance', 'is_frozen',
|
||||
'departement', 'nickname']
|
||||
data = {k: getattr(account, k) for k in export_keys}
|
||||
return JsonResponse(data)
|
||||
|
||||
addcosts = (
|
||||
OperationGroup.objects
|
||||
.filter(opes__addcost_for=account,
|
||||
|
@ -338,6 +369,7 @@ def account_read(request, trigramme):
|
|||
'addcosts': addcosts,
|
||||
})
|
||||
|
||||
|
||||
# Account - Update
|
||||
|
||||
|
||||
|
@ -530,18 +562,38 @@ class CheckoutCreate(SuccessMessageMixin, CreateView):
|
|||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
# Checkout - Read
|
||||
|
||||
class CheckoutRead(DetailView):
|
||||
class CheckoutRead(JSONResponseMixin, DetailView):
|
||||
model = Checkout
|
||||
template_name = 'kfet/checkout_read.html'
|
||||
context_object_name = 'checkout'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['statements'] = context['checkout'].statements.order_by('-at')
|
||||
checkout = self.object
|
||||
if self.request.GET.get('last_statement'):
|
||||
context['laststatement'] = checkout.statements.latest('at')
|
||||
else:
|
||||
context['statements'] = checkout.statements.order_by('-at')
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **kwargs):
|
||||
if self.request.GET.get('format') == 'json':
|
||||
export_keys = ['id', 'name', 'balance', 'valid_from', 'valid_to']
|
||||
data = {k: getattr(self.object, k) for k in export_keys}
|
||||
if 'laststatement' in context:
|
||||
last_st = context['laststatement']
|
||||
export_keys = ['id', 'at', 'balance_new', 'balance_old']
|
||||
last_st_data = {k: getattr(last_st, k) for k in export_keys}
|
||||
last_st_data['by'] = str(last_st.by)
|
||||
data['laststatement'] = last_st_data
|
||||
return self.render_to_json_response(data)
|
||||
else:
|
||||
return super().render_to_response(context, **kwargs)
|
||||
|
||||
|
||||
# Checkout - Update
|
||||
|
||||
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
|
||||
|
@ -625,7 +677,24 @@ class CheckoutStatementCreate(SuccessMessageMixin, CreateView):
|
|||
form.instance.balance_new = getAmountBalance(form.cleaned_data)
|
||||
form.instance.checkout_id = self.kwargs['pk_checkout']
|
||||
form.instance.by = self.request.user.profile.account_kfet
|
||||
return super().form_valid(form)
|
||||
res = super(CheckoutStatementCreate, self).form_valid(form)
|
||||
|
||||
ws_data = {
|
||||
'id': self.object.id,
|
||||
'at': self.object.at,
|
||||
'balance_new': self.object.balance_new,
|
||||
'balance_old': self.object.balance_old,
|
||||
'by': str(self.object.by),
|
||||
}
|
||||
consumers.KPsul.group_send('kfet.kpsul', {
|
||||
'checkouts': [{
|
||||
'id': self.object.checkout.id,
|
||||
'laststatement': ws_data,
|
||||
}],
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class CheckoutStatementUpdate(SuccessMessageMixin, UpdateView):
|
||||
model = CheckoutStatement
|
||||
|
@ -848,50 +917,6 @@ def kpsul_get_settings(request):
|
|||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def account_read_json(request):
|
||||
trigramme = request.POST.get('trigramme', '')
|
||||
account = get_object_or_404(Account, trigramme=trigramme)
|
||||
data = { 'id': account.pk, 'name': account.name, 'email': account.email,
|
||||
'is_cof': account.is_cof, 'promo': account.promo,
|
||||
'balance': account.balance, 'is_frozen': account.is_frozen,
|
||||
'departement': account.departement, 'nickname': account.nickname,
|
||||
'trigramme': account.trigramme }
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_checkout_data(request):
|
||||
pk = request.POST.get('pk', 0)
|
||||
if not pk:
|
||||
pk = 0
|
||||
|
||||
data = (
|
||||
Checkout.objects
|
||||
.annotate(
|
||||
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
|
||||
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
|
||||
last_statement_by_trigramme=F('statements__by__trigramme'),
|
||||
last_statement_balance=F('statements__balance_new'),
|
||||
last_statement_at=F('statements__at'))
|
||||
.select_related(
|
||||
'statements'
|
||||
'statements__by',
|
||||
'statements__by__cofprofile__user')
|
||||
.filter(pk=pk)
|
||||
.order_by('statements__at')
|
||||
.values(
|
||||
'id', 'name', 'balance', 'valid_from', 'valid_to',
|
||||
'last_statement_balance', 'last_statement_at',
|
||||
'last_statement_by_trigramme', 'last_statement_by_last_name',
|
||||
'last_statement_by_first_name')
|
||||
.last()
|
||||
)
|
||||
if data is None:
|
||||
raise Http404
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_update_addcost(request):
|
||||
addcost_form = AddcostForm(request.POST)
|
||||
|
@ -1081,31 +1106,47 @@ def kpsul_perform_operations(request):
|
|||
websocket_data = {}
|
||||
websocket_data['opegroups'] = [{
|
||||
'add': True,
|
||||
'id': operationgroup.pk,
|
||||
'amount': operationgroup.amount,
|
||||
'checkout__name': operationgroup.checkout.name,
|
||||
'at': operationgroup.at,
|
||||
'is_cof': operationgroup.is_cof,
|
||||
'comment': operationgroup.comment,
|
||||
'valid_by__trigramme': (operationgroup.valid_by and
|
||||
operationgroup.valid_by.trigramme or None),
|
||||
'on_acc__trigramme': operationgroup.on_acc.trigramme,
|
||||
'opes': [],
|
||||
'modelname': 'opegroup',
|
||||
'content': {
|
||||
'id': operationgroup.pk,
|
||||
'amount': operationgroup.amount,
|
||||
'at': operationgroup.at,
|
||||
'is_cof': operationgroup.is_cof,
|
||||
'comment': operationgroup.comment,
|
||||
'valid_by': (operationgroup.valid_by and
|
||||
operationgroup.valid_by.trigramme or None),
|
||||
'trigramme': operationgroup.on_acc.trigramme,
|
||||
# Used to filter websocket updates
|
||||
'account_id': operationgroup.on_acc.pk,
|
||||
'checkout_id': operationgroup.checkout.pk,
|
||||
'children': [],
|
||||
},
|
||||
}]
|
||||
for operation in operations:
|
||||
for ope in operations:
|
||||
ope_data = {
|
||||
'id': operation.pk, 'type': operation.type,
|
||||
'amount': operation.amount,
|
||||
'addcost_amount': operation.addcost_amount,
|
||||
'addcost_for__trigramme': (
|
||||
operation.addcost_for and addcost_for.trigramme or None),
|
||||
'article__name': (
|
||||
operation.article and operation.article.name or None),
|
||||
'article_nb': operation.article_nb,
|
||||
'group_id': operationgroup.pk,
|
||||
'canceled_by__trigramme': None, 'canceled_at': None,
|
||||
'content': {
|
||||
'id': ope.id,
|
||||
'amount': ope.amount,
|
||||
'canceled_at': None,
|
||||
'canceled_by': None,
|
||||
},
|
||||
}
|
||||
websocket_data['opegroups'][0]['opes'].append(ope_data)
|
||||
|
||||
if ope.type == Operation.PURCHASE:
|
||||
ope_data['modelname'] = 'purchase'
|
||||
ope_data['content'].update({
|
||||
'article_name': ope.article.name,
|
||||
'article_nb': ope.article_nb,
|
||||
'addcost_amount': ope.addcost_amount,
|
||||
'addcost_for':
|
||||
ope.addcost_for and ope.addcost_for.trigramme or None,
|
||||
})
|
||||
else:
|
||||
ope_data['modelname'] = 'specialope'
|
||||
ope_data['content'].update({
|
||||
'type': ope.type,
|
||||
})
|
||||
websocket_data['opegroups'][0]['content']['children'].append(ope_data)
|
||||
# Need refresh from db cause we used update on queryset
|
||||
operationgroup.checkout.refresh_from_db()
|
||||
websocket_data['checkouts'] = [{
|
||||
|
@ -1128,37 +1169,63 @@ def kpsul_perform_operations(request):
|
|||
@teamkfet_required
|
||||
def kpsul_cancel_operations(request):
|
||||
# Pour la réponse
|
||||
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
|
||||
data = {'canceled': {}, 'warnings': {}, 'errors': {}}
|
||||
|
||||
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
||||
try:
|
||||
# Set pour virer les doublons
|
||||
opes_post = set(map(int, filter(None, request.POST.getlist('operations[]', []))))
|
||||
opes_post = (
|
||||
set(map(int, filter(None, request.POST.getlist('opes[]', []))))
|
||||
)
|
||||
transfers_post = (
|
||||
set(map(int, filter(None, request.POST.getlist('transfers[]', []))))
|
||||
)
|
||||
except ValueError:
|
||||
return JsonResponse(data, status=400)
|
||||
|
||||
opes_all = (
|
||||
Operation.objects
|
||||
.select_related('group', 'group__on_acc', 'group__on_acc__negative')
|
||||
.filter(pk__in=opes_post))
|
||||
opes_pk = [ ope.pk for ope in opes_all ]
|
||||
opes_notexisting = [ ope for ope in opes_post if ope not in opes_pk ]
|
||||
if opes_notexisting:
|
||||
data['errors']['opes_notexisting'] = opes_notexisting
|
||||
opes_pk = [ope.pk for ope in opes_all]
|
||||
opes_notexisting = [ope for ope in opes_post if ope not in opes_pk]
|
||||
|
||||
transfers_all = (
|
||||
Transfer.objects
|
||||
.select_related('group', 'from_acc', 'from_acc__negative',
|
||||
'to_acc', 'to_acc__negative')
|
||||
.filter(pk__in=transfers_post))
|
||||
transfers_pk = [transfer.pk for transfer in transfers_all]
|
||||
transfers_notexisting = [transfer for transfer in transfers_post
|
||||
if transfer not in transfers_pk]
|
||||
|
||||
if transfers_notexisting or opes_notexisting:
|
||||
if transfers_notexisting:
|
||||
data['errors']['transfers_notexisting'] = transfers_notexisting
|
||||
if opes_notexisting:
|
||||
data['errors']['opes_notexisting'] = opes_notexisting
|
||||
return JsonResponse(data, status=400)
|
||||
|
||||
opes_already_canceled = [] # Déjà annulée
|
||||
opes = [] # Pas déjà annulée
|
||||
already_canceled = {} # Opération/Transfert déjà annulé
|
||||
opes = [] # Pas déjà annulée
|
||||
transfers = []
|
||||
required_perms = set()
|
||||
stop_all = False
|
||||
|
||||
stop_all = False
|
||||
cancel_duration = kfet_config.cancel_duration
|
||||
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
|
||||
to_groups_amounts = defaultdict(lambda:0) # ------ sur les montants des groupes d'opé
|
||||
to_checkouts_balances = defaultdict(lambda:0) # ------ sur les balances de caisses
|
||||
to_articles_stocks = defaultdict(lambda:0) # ------ sur les stocks d'articles
|
||||
# Modifs à faire sur les balances des comptes
|
||||
to_accounts_balances = defaultdict(lambda: 0)
|
||||
# ------ sur les montants des groupes d'opé
|
||||
to_groups_amounts = defaultdict(lambda: 0)
|
||||
# ------ sur les balances de caisses
|
||||
to_checkouts_balances = defaultdict(lambda: 0)
|
||||
# ------ sur les stocks d'articles
|
||||
to_articles_stocks = defaultdict(lambda: 0)
|
||||
|
||||
for ope in opes_all:
|
||||
if ope.canceled_at:
|
||||
# Opération déjà annulée, va pour un warning en Response
|
||||
opes_already_canceled.append(ope.pk)
|
||||
already_canceled['opes'].append(ope.pk)
|
||||
else:
|
||||
opes.append(ope.pk)
|
||||
# Si opé il y a plus de CANCEL_DURATION, permission requise
|
||||
|
@ -1185,10 +1252,11 @@ def kpsul_cancel_operations(request):
|
|||
# par `.save()`, amount_error est recalculé automatiquement,
|
||||
# ce qui n'est pas le cas en faisant un update sur queryset
|
||||
# TODO ? : Maj les balance_old de relevés pour modifier l'erreur
|
||||
last_statement = (CheckoutStatement.objects
|
||||
.filter(checkout=ope.group.checkout)
|
||||
.order_by('at')
|
||||
.last())
|
||||
last_statement = \
|
||||
(CheckoutStatement.objects
|
||||
.filter(checkout=ope.group.checkout)
|
||||
.order_by('at')
|
||||
.last())
|
||||
if not last_statement or last_statement.at < ope.group.at:
|
||||
if ope.is_checkout:
|
||||
if ope.group.on_acc.is_cash:
|
||||
|
@ -1204,23 +1272,41 @@ def kpsul_cancel_operations(request):
|
|||
# Note : si InventoryArticle est maj par .save(), stock_error
|
||||
# est recalculé automatiquement
|
||||
if ope.article and ope.article_nb:
|
||||
last_stock = (InventoryArticle.objects
|
||||
last_stock = (
|
||||
InventoryArticle.objects
|
||||
.select_related('inventory')
|
||||
.filter(article=ope.article)
|
||||
.order_by('inventory__at')
|
||||
.last())
|
||||
.last()
|
||||
)
|
||||
if not last_stock or last_stock.inventory.at < ope.group.at:
|
||||
to_articles_stocks[ope.article] += ope.article_nb
|
||||
|
||||
if not opes:
|
||||
data['warnings']['already_canceled'] = opes_already_canceled
|
||||
for transfer in transfers_all:
|
||||
if transfer.canceled_at:
|
||||
# Transfert déjà annulé, va pour un warning en Response
|
||||
already_canceled['transfers'].append(transfer.pk)
|
||||
else:
|
||||
transfers.append(transfer.pk)
|
||||
# Si transfer il y a plus de CANCEL_DURATION, permission requise
|
||||
if transfer.group.at + cancel_duration < timezone.now():
|
||||
required_perms.add('kfet.cancel_old_operations')
|
||||
|
||||
# Calcul de toutes modifs à faire en cas de validation
|
||||
|
||||
# Pour les balances de comptes
|
||||
to_accounts_balances[transfer.from_acc] += transfer.amount
|
||||
to_accounts_balances[transfer.to_acc] += -transfer.amount
|
||||
|
||||
if not opes and not transfers:
|
||||
data['warnings']['already_canceled'] = already_canceled
|
||||
return JsonResponse(data)
|
||||
|
||||
negative_accounts = []
|
||||
# Checking permissions or stop
|
||||
for account in to_accounts_balances:
|
||||
(perms, stop) = account.perms_to_perform_operation(
|
||||
amount = to_accounts_balances[account])
|
||||
amount=to_accounts_balances[account])
|
||||
required_perms |= perms
|
||||
stop_all = stop_all or stop
|
||||
if stop:
|
||||
|
@ -1240,6 +1326,10 @@ def kpsul_cancel_operations(request):
|
|||
with transaction.atomic():
|
||||
(Operation.objects.filter(pk__in=opes)
|
||||
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
||||
|
||||
(Transfer.objects.filter(pk__in=transfers)
|
||||
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
||||
|
||||
for account in to_accounts_balances:
|
||||
(
|
||||
Account.objects
|
||||
|
@ -1252,20 +1342,22 @@ def kpsul_cancel_operations(request):
|
|||
account.update_negative()
|
||||
for checkout in to_checkouts_balances:
|
||||
Checkout.objects.filter(pk=checkout.pk).update(
|
||||
balance = F('balance') + to_checkouts_balances[checkout])
|
||||
balance=F('balance') + to_checkouts_balances[checkout])
|
||||
for group in to_groups_amounts:
|
||||
OperationGroup.objects.filter(pk=group.pk).update(
|
||||
amount = F('amount') + to_groups_amounts[group])
|
||||
amount=F('amount') + to_groups_amounts[group])
|
||||
for article in to_articles_stocks:
|
||||
Article.objects.filter(pk=article.pk).update(
|
||||
stock = F('stock') + to_articles_stocks[article])
|
||||
stock=F('stock') + to_articles_stocks[article])
|
||||
|
||||
# Websocket data
|
||||
websocket_data = { 'opegroups': [], 'opes': [], 'checkouts': [], 'articles': [] }
|
||||
websocket_data = {'opegroups': [], 'opes': [],
|
||||
'checkouts': [], 'articles': []}
|
||||
# Need refresh from db cause we used update on querysets
|
||||
opegroups_pk = [ opegroup.pk for opegroup in to_groups_amounts ]
|
||||
opegroups_pk = [opegroup.pk for opegroup in to_groups_amounts]
|
||||
opegroups = (OperationGroup.objects
|
||||
.values('id','amount','is_cof').filter(pk__in=opegroups_pk))
|
||||
.values('id', 'amount', 'is_cof')
|
||||
.filter(pk__in=opegroups_pk))
|
||||
for opegroup in opegroups:
|
||||
websocket_data['opegroups'].append({
|
||||
'cancellation': True,
|
||||
|
@ -1273,24 +1365,35 @@ def kpsul_cancel_operations(request):
|
|||
'amount': opegroup['amount'],
|
||||
'is_cof': opegroup['is_cof'],
|
||||
})
|
||||
canceled_by__trigramme = canceled_by and canceled_by.trigramme or None
|
||||
canceled_by = canceled_by and canceled_by.trigramme or None
|
||||
for ope in opes:
|
||||
websocket_data['opes'].append({
|
||||
'cancellation': True,
|
||||
'modelname': 'ope',
|
||||
'id': ope,
|
||||
'canceled_by__trigramme': canceled_by__trigramme,
|
||||
'canceled_by': canceled_by,
|
||||
'canceled_at': canceled_at,
|
||||
})
|
||||
for ope in transfers:
|
||||
websocket_data['opes'].append({
|
||||
'cancellation': True,
|
||||
'modelname': 'transfer',
|
||||
'id': ope,
|
||||
'canceled_by': canceled_by,
|
||||
'canceled_at': canceled_at,
|
||||
})
|
||||
|
||||
# Need refresh from db cause we used update on querysets
|
||||
checkouts_pk = [ checkout.pk for checkout in to_checkouts_balances]
|
||||
checkouts_pk = [checkout.pk for checkout in to_checkouts_balances]
|
||||
checkouts = (Checkout.objects
|
||||
.values('id', 'balance').filter(pk__in=checkouts_pk))
|
||||
.values('id', 'balance')
|
||||
.filter(pk__in=checkouts_pk))
|
||||
for checkout in checkouts:
|
||||
websocket_data['checkouts'].append({
|
||||
'id': checkout['id'],
|
||||
'balance': checkout['balance']})
|
||||
# Need refresh from db cause we used update on querysets
|
||||
articles_pk = [ article.pk for articles in to_articles_stocks]
|
||||
articles_pk = [article.pk for articles in to_articles_stocks]
|
||||
articles = Article.objects.values('id', 'stock').filter(pk__in=articles_pk)
|
||||
for article in articles:
|
||||
websocket_data['articles'].append({
|
||||
|
@ -1298,92 +1401,195 @@ def kpsul_cancel_operations(request):
|
|||
'stock': article['stock']})
|
||||
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
|
||||
|
||||
data['canceled'] = opes
|
||||
if opes_already_canceled:
|
||||
data['warnings']['already_canceled'] = opes_already_canceled
|
||||
data['canceled']['opes'] = opes
|
||||
data['canceled']['transfers'] = transfers
|
||||
if already_canceled:
|
||||
data['warnings']['already_canceled'] = already_canceled
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@login_required
|
||||
def history_json(request):
|
||||
# Récupération des paramètres
|
||||
from_date = request.POST.get('from', None)
|
||||
to_date = request.POST.get('to', None)
|
||||
limit = request.POST.get('limit', None);
|
||||
checkouts = request.POST.getlist('checkouts[]', None)
|
||||
accounts = request.POST.getlist('accounts[]', None)
|
||||
from_date = request.GET.get('from', None)
|
||||
to_date = request.GET.get('to', None)
|
||||
checkouts = request.GET.getlist('checkouts[]', None)
|
||||
accounts = request.GET.getlist('accounts[]', None)
|
||||
transfers_only = request.GET.get('transfersonly', None)
|
||||
opes_only = request.GET.get('opesonly', None)
|
||||
|
||||
# Un non-membre de l'équipe n'a que accès à son historique
|
||||
if not request.user.has_perm('kfet.is_team'):
|
||||
accounts = [request.user.profile.account_kfet]
|
||||
|
||||
# Construction de la requête (sur les opérations) pour le prefetch
|
||||
queryset_prefetch = Operation.objects.select_related(
|
||||
'article', 'canceled_by', 'addcost_for')
|
||||
ope_queryset_prefetch = Operation.objects.select_related(
|
||||
'canceled_by', 'addcost_for', 'article')
|
||||
ope_prefetch = Prefetch('opes',
|
||||
queryset=ope_queryset_prefetch)
|
||||
|
||||
transfer_queryset_prefetch = Transfer.objects.select_related(
|
||||
'from_acc', 'to_acc', 'canceled_by')
|
||||
|
||||
if accounts:
|
||||
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
|
||||
Q(from_acc__in=accounts) |
|
||||
Q(to_acc__in=accounts))
|
||||
|
||||
if not request.user.has_perm('kfet.is_team'):
|
||||
acc = request.user.profile.account_kfet
|
||||
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
|
||||
Q(from_acc=acc) | Q(to_acc=acc))
|
||||
|
||||
transfer_prefetch = Prefetch('transfers',
|
||||
queryset=transfer_queryset_prefetch,
|
||||
to_attr='filtered_transfers')
|
||||
|
||||
# Construction de la requête principale
|
||||
opegroups = (
|
||||
OperationGroup.objects
|
||||
.prefetch_related(Prefetch('opes', queryset=queryset_prefetch))
|
||||
.select_related('on_acc', 'valid_by')
|
||||
.order_by('at')
|
||||
.prefetch_related(ope_prefetch)
|
||||
.select_related('on_acc',
|
||||
'valid_by')
|
||||
.order_by('at')
|
||||
)
|
||||
|
||||
transfergroups = (
|
||||
TransferGroup.objects
|
||||
.prefetch_related(transfer_prefetch)
|
||||
.select_related('valid_by')
|
||||
.order_by('at')
|
||||
)
|
||||
|
||||
# Application des filtres
|
||||
if from_date:
|
||||
opegroups = opegroups.filter(at__gte=from_date)
|
||||
transfergroups = transfergroups.filter(at__gte=from_date)
|
||||
if to_date:
|
||||
opegroups = opegroups.filter(at__lt=to_date)
|
||||
transfergroups = transfergroups.filter(at__lt=to_date)
|
||||
if checkouts:
|
||||
opegroups = opegroups.filter(checkout_id__in=checkouts)
|
||||
transfergroups = TransferGroup.objects.none()
|
||||
if transfers_only:
|
||||
opegroups = OperationGroup.objects.none()
|
||||
if opes_only:
|
||||
transfergroups = TransferGroup.objects.none()
|
||||
if accounts:
|
||||
opegroups = opegroups.filter(on_acc_id__in=accounts)
|
||||
# Un non-membre de l'équipe n'a que accès à son historique
|
||||
if not request.user.has_perm('kfet.is_team'):
|
||||
opegroups = opegroups.filter(on_acc=request.user.profile.account_kfet)
|
||||
if limit:
|
||||
opegroups = opegroups[:limit]
|
||||
|
||||
opegroups = opegroups.filter(on_acc__in=accounts)
|
||||
|
||||
# Construction de la réponse
|
||||
opegroups_list = []
|
||||
related_data = defaultdict(list)
|
||||
objects_data = defaultdict(list)
|
||||
for opegroup in opegroups:
|
||||
opegroup_dict = {
|
||||
'id' : opegroup.id,
|
||||
'amount' : opegroup.amount,
|
||||
'at' : opegroup.at,
|
||||
'checkout_id': opegroup.checkout_id,
|
||||
'is_cof' : opegroup.is_cof,
|
||||
'comment' : opegroup.comment,
|
||||
'opes' : [],
|
||||
'on_acc__trigramme':
|
||||
opegroup.on_acc and opegroup.on_acc.trigramme or None,
|
||||
'id': opegroup.id,
|
||||
'amount': opegroup.amount,
|
||||
'at': opegroup.at,
|
||||
'is_cof': opegroup.is_cof,
|
||||
'comment': opegroup.comment,
|
||||
'trigramme':
|
||||
opegroup.on_acc and opegroup.on_acc.trigramme or None,
|
||||
}
|
||||
if request.user.has_perm('kfet.is_team'):
|
||||
opegroup_dict['valid_by__trigramme'] = (
|
||||
opegroup_dict['valid_by'] = (
|
||||
opegroup.valid_by and opegroup.valid_by.trigramme or None)
|
||||
|
||||
for ope in opegroup.opes.all():
|
||||
ope_dict = {
|
||||
'id' : ope.id,
|
||||
'type' : ope.type,
|
||||
'amount' : ope.amount,
|
||||
'article_nb' : ope.article_nb,
|
||||
'addcost_amount': ope.addcost_amount,
|
||||
'canceled_at' : ope.canceled_at,
|
||||
'article__name':
|
||||
ope.article and ope.article.name or None,
|
||||
'addcost_for__trigramme':
|
||||
ope.addcost_for and ope.addcost_for.trigramme or None,
|
||||
'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,
|
||||
}
|
||||
if request.user.has_perm('kfet.is_team'):
|
||||
ope_dict['canceled_by__trigramme'] = (
|
||||
ope_dict['canceled_by'] = (
|
||||
ope.canceled_by and ope.canceled_by.trigramme or None)
|
||||
opegroup_dict['opes'].append(ope_dict)
|
||||
opegroups_list.append(opegroup_dict)
|
||||
return JsonResponse({ 'opegroups': opegroups_list })
|
||||
|
||||
if ope.type == Operation.PURCHASE:
|
||||
ope_dict.update({
|
||||
'article_name': ope.article.name,
|
||||
'article_nb': ope.article_nb,
|
||||
'addcost_amount': ope.addcost_amount,
|
||||
'addcost_for':
|
||||
ope.addcost_for and ope.addcost_for.trigramme or None,
|
||||
})
|
||||
objects_data['purchase'].append(ope_dict)
|
||||
else:
|
||||
ope_dict.update({
|
||||
'type': ope.type,
|
||||
})
|
||||
objects_data['specialope'].append(ope_dict)
|
||||
|
||||
related_data['opegroup'].append(opegroup_dict)
|
||||
|
||||
for transfergroup in transfergroups:
|
||||
if transfergroup.filtered_transfers:
|
||||
transfergroup_dict = {
|
||||
'id': transfergroup.id,
|
||||
'at': transfergroup.at,
|
||||
'comment': transfergroup.comment,
|
||||
}
|
||||
|
||||
if request.user.has_perm('kfet.is_team'):
|
||||
transfergroup_dict['valid_by'] = (
|
||||
transfergroup.valid_by and
|
||||
transfergroup.valid_by.trigramme or
|
||||
None)
|
||||
|
||||
for transfer in transfergroup.filtered_transfers:
|
||||
transfer_dict = {
|
||||
'id': transfer.id,
|
||||
'amount': transfer.amount,
|
||||
'canceled_at': transfer.canceled_at,
|
||||
'from_acc': transfer.from_acc.trigramme,
|
||||
'to_acc': transfer.to_acc.trigramme,
|
||||
'transfergroup__id': transfergroup.id,
|
||||
}
|
||||
if request.user.has_perm('kfet.is_team'):
|
||||
transfer_dict['canceled_by'] = (
|
||||
transfer.canceled_by and
|
||||
transfer.canceled_by.trigramme or
|
||||
None)
|
||||
objects_data['transfer'].append(transfer_dict)
|
||||
related_data['transfergroup'].append(transfergroup_dict)
|
||||
|
||||
data = {
|
||||
'objects': objects_data,
|
||||
'related': related_data,
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_articles_data(request):
|
||||
articles = (
|
||||
Article.objects
|
||||
.values('id', 'name', 'price', 'stock', 'category_id',
|
||||
'category__name', 'category__has_addcost')
|
||||
.filter(is_sold=True))
|
||||
return JsonResponse({ 'articles': list(articles) })
|
||||
data = {'objects': {}, 'related': {}}
|
||||
|
||||
data['objects']['article'] = [
|
||||
{
|
||||
'id': article.id,
|
||||
'name': article.name,
|
||||
'price': article.price,
|
||||
'stock': article.stock,
|
||||
'category__id': article.category_id,
|
||||
}
|
||||
for article in Article.objects.filter(is_sold=True)
|
||||
]
|
||||
|
||||
data['related']['category'] = [
|
||||
{
|
||||
'id': category.id,
|
||||
'name': category.name,
|
||||
'has_addcost': category.has_addcost,
|
||||
}
|
||||
for category in ArticleCategory.objects.all()
|
||||
]
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
|
@ -1430,25 +1636,10 @@ config_update = (
|
|||
# Transfer views
|
||||
# -----
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def transfers(request):
|
||||
transfers_pre = Prefetch(
|
||||
'transfers',
|
||||
queryset=(
|
||||
Transfer.objects
|
||||
.select_related('from_acc', 'to_acc')
|
||||
),
|
||||
)
|
||||
|
||||
transfergroups = (
|
||||
TransferGroup.objects
|
||||
.select_related('valid_by')
|
||||
.prefetch_related(transfers_pre)
|
||||
.order_by('-at')
|
||||
)
|
||||
return render(request, 'kfet/transfers.html', {
|
||||
'transfergroups': transfergroups,
|
||||
})
|
||||
return render(request, 'kfet/transfers.html')
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
|
@ -1457,20 +1648,24 @@ def transfers_create(request):
|
|||
return render(request, 'kfet/transfers_create.html',
|
||||
{ 'transfer_formset': transfer_formset })
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def perform_transfers(request):
|
||||
data = { 'errors': {}, 'transfers': [], 'transfergroup': 0 }
|
||||
data = {'errors': {}, 'transfers': [], 'transfergroup': 0}
|
||||
|
||||
# Checking transfer_formset
|
||||
transfer_formset = TransferFormSet(request.POST)
|
||||
if not transfer_formset.is_valid():
|
||||
return JsonResponse({ 'errors': list(transfer_formset.errors)}, status=400)
|
||||
return JsonResponse({'errors': list(transfer_formset.errors)},
|
||||
status=400)
|
||||
|
||||
transfers = transfer_formset.save(commit = False)
|
||||
transfers = transfer_formset.save(commit=False)
|
||||
|
||||
# Initializing vars
|
||||
required_perms = set(['kfet.add_transfer']) # Required perms to perform all transfers
|
||||
to_accounts_balances = defaultdict(lambda:0) # For balances of accounts
|
||||
# Required perms to perform all transfers
|
||||
required_perms = set(['kfet.add_transfer'])
|
||||
# For balances of accounts
|
||||
to_accounts_balances = defaultdict(lambda: 0)
|
||||
|
||||
for transfer in transfers:
|
||||
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
||||
|
@ -1482,7 +1677,7 @@ def perform_transfers(request):
|
|||
# Checking if ok on all accounts
|
||||
for account in to_accounts_balances:
|
||||
(perms, stop) = account.perms_to_perform_operation(
|
||||
amount = to_accounts_balances[account])
|
||||
amount=to_accounts_balances[account])
|
||||
required_perms |= perms
|
||||
stop_all = stop_all or stop
|
||||
if stop:
|
||||
|
@ -1508,7 +1703,7 @@ def perform_transfers(request):
|
|||
# Updating balances accounts
|
||||
for account in to_accounts_balances:
|
||||
Account.objects.filter(pk=account.pk).update(
|
||||
balance = F('balance') + to_accounts_balances[account])
|
||||
balance=F('balance') + to_accounts_balances[account])
|
||||
account.refresh_from_db()
|
||||
if account.balance < 0:
|
||||
if hasattr(account, 'negative'):
|
||||
|
@ -1517,10 +1712,10 @@ def perform_transfers(request):
|
|||
account.negative.save()
|
||||
else:
|
||||
negative = AccountNegative(
|
||||
account = account, start = timezone.now())
|
||||
account=account, start=timezone.now())
|
||||
negative.save()
|
||||
elif (hasattr(account, 'negative')
|
||||
and not account.negative.balance_offset):
|
||||
elif (hasattr(account, 'negative') and
|
||||
not account.negative.balance_offset):
|
||||
account.negative.delete()
|
||||
|
||||
# Saving transfer group
|
||||
|
@ -1533,103 +1728,38 @@ def perform_transfers(request):
|
|||
transfer.save()
|
||||
data['transfers'].append(transfer.pk)
|
||||
|
||||
# Websocket data
|
||||
websocket_data = {}
|
||||
websocket_data['opegroups'] = [{
|
||||
'add': True,
|
||||
'modelname': 'transfergroup',
|
||||
'content': {
|
||||
'id': transfergroup.pk,
|
||||
'at': transfergroup.at,
|
||||
'comment': transfergroup.comment,
|
||||
'valid_by__trigramme': (transfergroup.valid_by and
|
||||
transfergroup.valid_by.trigramme or None),
|
||||
'children': []
|
||||
},
|
||||
}]
|
||||
for transfer in transfers:
|
||||
ope_data = {
|
||||
'modelname': 'transfer',
|
||||
'content': {
|
||||
'id': transfer.pk,
|
||||
'amount': transfer.amount,
|
||||
'from_acc': transfer.from_acc.trigramme,
|
||||
'to_acc': transfer.to_acc.trigramme,
|
||||
'canceled_by__trigramme': None, 'canceled_at': None,
|
||||
'from_acc_id': transfer.from_acc.id,
|
||||
'to_acc_id': transfer.to_acc.id,
|
||||
},
|
||||
}
|
||||
websocket_data['opegroups'][0]['content']['children'].append(ope_data)
|
||||
|
||||
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
|
||||
return JsonResponse(data)
|
||||
|
||||
@teamkfet_required
|
||||
def cancel_transfers(request):
|
||||
# Pour la réponse
|
||||
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
|
||||
|
||||
# Checking if BAD REQUEST (transfers_pk not int or not existing)
|
||||
try:
|
||||
# Set pour virer les doublons
|
||||
transfers_post = set(map(int, filter(None, request.POST.getlist('transfers[]', []))))
|
||||
except ValueError:
|
||||
return JsonResponse(data, status=400)
|
||||
transfers_all = (
|
||||
Transfer.objects
|
||||
.select_related('group', 'from_acc', 'from_acc__negative',
|
||||
'to_acc', 'to_acc__negative')
|
||||
.filter(pk__in=transfers_post))
|
||||
transfers_pk = [ transfer.pk for transfer in transfers_all ]
|
||||
transfers_notexisting = [ transfer for transfer in transfers_post
|
||||
if transfer not in transfers_pk ]
|
||||
if transfers_notexisting:
|
||||
data['errors']['transfers_notexisting'] = transfers_notexisting
|
||||
return JsonResponse(data, status=400)
|
||||
|
||||
transfers_already_canceled = [] # Déjà annulée
|
||||
transfers = [] # Pas déjà annulée
|
||||
required_perms = set()
|
||||
stop_all = False
|
||||
cancel_duration = kfet_config.cancel_duration
|
||||
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
|
||||
for transfer in transfers_all:
|
||||
if transfer.canceled_at:
|
||||
# Transfert déjà annulé, va pour un warning en Response
|
||||
transfers_already_canceled.append(transfer.pk)
|
||||
else:
|
||||
transfers.append(transfer.pk)
|
||||
# Si transfer il y a plus de CANCEL_DURATION, permission requise
|
||||
if transfer.group.at + cancel_duration < timezone.now():
|
||||
required_perms.add('kfet.cancel_old_operations')
|
||||
|
||||
# Calcul de toutes modifs à faire en cas de validation
|
||||
|
||||
# Pour les balances de comptes
|
||||
to_accounts_balances[transfer.from_acc] += transfer.amount
|
||||
to_accounts_balances[transfer.to_acc] += -transfer.amount
|
||||
|
||||
if not transfers:
|
||||
data['warnings']['already_canceled'] = transfers_already_canceled
|
||||
return JsonResponse(data)
|
||||
|
||||
negative_accounts = []
|
||||
# Checking permissions or stop
|
||||
for account in to_accounts_balances:
|
||||
(perms, stop) = account.perms_to_perform_operation(
|
||||
amount = to_accounts_balances[account])
|
||||
required_perms |= perms
|
||||
stop_all = stop_all or stop
|
||||
if stop:
|
||||
negative_accounts.append(account.trigramme)
|
||||
|
||||
if stop_all or not request.user.has_perms(required_perms):
|
||||
missing_perms = get_missing_perms(required_perms, request.user)
|
||||
if missing_perms:
|
||||
data['errors']['missing_perms'] = missing_perms
|
||||
if stop_all:
|
||||
data['errors']['negative'] = negative_accounts
|
||||
return JsonResponse(data, status=403)
|
||||
|
||||
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||
canceled_at = timezone.now()
|
||||
|
||||
with transaction.atomic():
|
||||
(Transfer.objects.filter(pk__in=transfers)
|
||||
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
||||
|
||||
for account in to_accounts_balances:
|
||||
Account.objects.filter(pk=account.pk).update(
|
||||
balance = F('balance') + to_accounts_balances[account])
|
||||
account.refresh_from_db()
|
||||
if account.balance < 0:
|
||||
if hasattr(account, 'negative'):
|
||||
if not account.negative.start:
|
||||
account.negative.start = timezone.now()
|
||||
account.negative.save()
|
||||
else:
|
||||
negative = AccountNegative(
|
||||
account = account, start = timezone.now())
|
||||
negative.save()
|
||||
elif (hasattr(account, 'negative')
|
||||
and not account.negative.balance_offset):
|
||||
account.negative.delete()
|
||||
|
||||
data['canceled'] = transfers
|
||||
if transfers_already_canceled:
|
||||
data['warnings']['already_canceled'] = transfers_already_canceled
|
||||
return JsonResponse(data)
|
||||
|
||||
class InventoryList(ListView):
|
||||
queryset = (Inventory.objects
|
||||
|
@ -2020,29 +2150,6 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView):
|
|||
# ---------------
|
||||
# Vues génériques
|
||||
# ---------------
|
||||
# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/
|
||||
class JSONResponseMixin(object):
|
||||
"""
|
||||
A mixin that can be used to render a JSON response.
|
||||
"""
|
||||
def render_to_json_response(self, context, **response_kwargs):
|
||||
"""
|
||||
Returns a JSON response, transforming 'context' to make the payload.
|
||||
"""
|
||||
return JsonResponse(
|
||||
self.get_data(context),
|
||||
**response_kwargs
|
||||
)
|
||||
|
||||
def get_data(self, context):
|
||||
"""
|
||||
Returns an object that will be serialized as JSON by json.dumps().
|
||||
"""
|
||||
# Note: This is *EXTREMELY* naive; in reality, you'll need
|
||||
# to do much more complex handling to ensure that arbitrary
|
||||
# objects -- such as Django model instances or querysets
|
||||
# -- can be serialized as JSON.
|
||||
return context
|
||||
|
||||
|
||||
class JSONDetailView(JSONResponseMixin, BaseDetailView):
|
||||
|
|
|
@ -18,6 +18,7 @@ statistics==1.0.3.5
|
|||
django-widget-tweaks==1.4.1
|
||||
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
||||
ldap3
|
||||
django-js-reverse==0.7.3
|
||||
channels==1.1.5
|
||||
python-dateutil
|
||||
wagtail==1.10.*
|
||||
|
|
Loading…
Reference in a new issue