WIP: Aureplop/kpsul js refactor #501
19 changed files with 3898 additions and 1843 deletions
|
@ -81,6 +81,7 @@ INSTALLED_APPS = [
|
||||||
'kfet.open',
|
'kfet.open',
|
||||||
'channels',
|
'channels',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
|
'django_js_reverse',
|
||||||
'custommail',
|
'custommail',
|
||||||
'djconfig',
|
'djconfig',
|
||||||
'wagtail.wagtailforms',
|
'wagtail.wagtailforms',
|
||||||
|
|
|
@ -6,9 +6,11 @@ from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.contrib.auth import views as django_views
|
from django.contrib.auth import views as django_views
|
||||||
from django_cas_ng import views as django_cas_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.wagtailadmin import urls as wagtailadmin_urls
|
||||||
from wagtail.wagtailcore import urls as wagtail_urls
|
from wagtail.wagtailcore import urls as wagtail_urls
|
||||||
|
@ -91,6 +93,7 @@ urlpatterns = [
|
||||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente,
|
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente,
|
||||||
name="ml_bda_revente"),
|
name="ml_bda_revente"),
|
||||||
url(r'^k-fet/', include('kfet.urls')),
|
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'^cms/', include(wagtailadmin_urls)),
|
||||||
url(r'^documents/', include(wagtaildocs_urls)),
|
url(r'^documents/', include(wagtaildocs_urls)),
|
||||||
# djconfig
|
# djconfig
|
||||||
|
|
|
@ -44,6 +44,11 @@
|
||||||
width:90px;
|
width:90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#history .opegroup .infos {
|
||||||
|
text-align:center;
|
||||||
|
width:145px;
|
||||||
|
}
|
||||||
|
|
||||||
#history .opegroup .valid_by {
|
#history .opegroup .valid_by {
|
||||||
padding-left:20px
|
padding-left:20px
|
||||||
}
|
}
|
||||||
|
@ -71,6 +76,10 @@
|
||||||
text-align:right;
|
text-align:right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#history .ope .glyphicon {
|
||||||
|
padding-left:15px;
|
||||||
|
}
|
||||||
|
|
||||||
#history .ope .infos2 {
|
#history .ope .infos2 {
|
||||||
padding-left:15px;
|
padding-left:15px;
|
||||||
}
|
}
|
||||||
|
@ -88,11 +97,11 @@
|
||||||
color:#FFF;
|
color:#FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#history .ope.canceled, #history .transfer.canceled {
|
#history [canceled="true"] {
|
||||||
color:#444;
|
color:#444;
|
||||||
}
|
}
|
||||||
|
|
||||||
#history .ope.canceled::before, #history.transfer.canceled::before {
|
#history [canceled="true"]::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
width:100%;
|
width:100%;
|
||||||
|
|
|
@ -49,10 +49,10 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
height:120px;
|
height:120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#account[data-balance="ok"] #account_form input { background:#009011; 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="low"] #account_form input { background:#EC6400; color:#FFF; }
|
||||||
#account[data-balance="neg"] #account_form input { background:#C8102E; 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="frozen"] #account_form input { background:#000FBA; color:#FFF; }
|
||||||
|
|
||||||
#account_form {
|
#account_form {
|
||||||
padding:0;
|
padding:0;
|
||||||
|
@ -90,7 +90,7 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
font-size:12px;
|
font-size:12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#account_data #account-balance {
|
#account_data #account-balance_ukf {
|
||||||
height:40px;
|
height:40px;
|
||||||
line-height:40px;
|
line-height:40px;
|
||||||
|
|
||||||
|
@ -102,6 +102,10 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#account-is_cof {
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
#account .buttons {
|
#account .buttons {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
bottom:0;
|
bottom:0;
|
||||||
|
@ -119,7 +123,7 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
line-height:24px;
|
line-height:24px;
|
||||||
}
|
}
|
||||||
#account_data #account-balance {
|
#account_data #account-balance_ukf {
|
||||||
font-size:50px;
|
font-size:50px;
|
||||||
line-height:60px;
|
line-height:60px;
|
||||||
height:60px;
|
height:60px;
|
||||||
|
@ -306,28 +310,40 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
#articles_data {
|
#articles_data {
|
||||||
overflow:auto;
|
overflow:auto;
|
||||||
max-height:500px;
|
max-height:500px;
|
||||||
}
|
|
||||||
|
|
||||||
#articles_data table {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#articles_data table tr.article {
|
#articles_data div.article {
|
||||||
height:25px;
|
height:25px;
|
||||||
font-size:14px;
|
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;
|
padding-left:10px;
|
||||||
|
width:78%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#articles_data table tr.article td + td {
|
#articles_data span.price {
|
||||||
padding-right:10px;
|
width:8%;
|
||||||
text-align:right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#articles_data table tr.category {
|
#articles_data span.stock {
|
||||||
|
width:14%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#articles_data div.category {
|
||||||
height:35px;
|
height:35px;
|
||||||
|
line-height:35px;
|
||||||
background-color:#c8102e;
|
background-color:#c8102e;
|
||||||
font-family:"Roboto Slab";
|
font-family:"Roboto Slab";
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
|
@ -335,7 +351,7 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
color:#FFF;
|
color:#FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#articles_data table tr.category>td:first-child {
|
#articles_data div.category>span:first-child {
|
||||||
padding-left:20px;
|
padding-left:20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,14 +73,19 @@
|
||||||
|
|
||||||
.jconfirm .capslock .glyphicon {
|
.jconfirm .capslock .glyphicon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display:none;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
display: none ;
|
|
||||||
margin-left: 60px !important;
|
margin-left: 60px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.capslock_on .capslock .glyphicon{
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.jconfirm .capslock input {
|
.jconfirm .capslock input {
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
|
|
|
@ -1,148 +1,235 @@
|
||||||
function KHistory(options={}) {
|
var cancelHistory = new Event("cancel_done");
|
||||||
$.extend(this, KHistory.default_options, options);
|
|
||||||
|
|
||||||
this.$container = $(this.container);
|
class KHistory {
|
||||||
|
|
||||||
this.reset = function() {
|
static get default_options() {
|
||||||
this.$container.html('');
|
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>',
|
||||||
|
},
|
||||||
|
|
||||||
|
'api_options': {
|
||||||
|
from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options) {
|
||||||
|
var all_options = $.extend(true, {}, this.constructor.default_options, options);
|
||||||
|
this.api_options = all_options.api_options;
|
||||||
|
|
||||||
|
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");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addOpeGroup = function(opegroup) {
|
api_with_auth({
|
||||||
var $day = this._getOrCreateDay(opegroup['at']);
|
url: Urls['kfet.kpsul.cancel_operations'](),
|
||||||
var $opegroup = this._opeGroupHtml(opegroup);
|
data: to_cancel,
|
||||||
|
on_success: on_success,
|
||||||
$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 = '';
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$ope_html
|
|
||||||
.data('ope', ope['id'])
|
|
||||||
.find('.amount').text(amount).end()
|
|
||||||
.find('.infos1').text(infos1).end()
|
|
||||||
.find('.infos2').text(infos2).end();
|
|
||||||
|
|
||||||
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+')');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ope['canceled_at'])
|
|
||||||
this.cancelOpe(ope, $ope_html);
|
|
||||||
|
|
||||||
return $ope_html;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
add_node(data) {
|
||||||
return this.$container.find('.ope').filter(function() {
|
var node = this.data.get_or_create(data.modelname, data.content, 0);
|
||||||
return $(this).data('ope') == id
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let opegroup of opegroups) {
|
||||||
|
if (opegroup['cancellation']) {
|
||||||
|
let update_data = { 'amount': opegroup.amount };
|
||||||
|
this.update_node('opegroup', opegroup.id, update_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cancelOpeGroup = function(opegroup) {
|
get_selected() {
|
||||||
var $opegroup = this.findOpeGroup(opegroup['id']);
|
var selected = {'transfers': [], 'opes': [],};
|
||||||
var trigramme = $opegroup.find('.trigramme').text();
|
this._$container.find('.ope.ui-selected').each(function() {
|
||||||
var amount = amountDisplay(
|
var [type, id] = $(this).parent().attr('id').split('-');
|
||||||
parseFloat(opegroup['amount'], opegroup['is_cof'], trigramme));
|
|
||||||
$opegroup.find('.amount').text(amount);
|
if (type === 'transfer')
|
||||||
|
selected['transfers'].push(id);
|
||||||
|
else
|
||||||
|
selected['opes'].push(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this._$container.find('.ui-selected')
|
||||||
|
.removeClass('.ui-selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
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
|
* CSRF Token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -19,6 +154,7 @@ $.ajaxSetup({
|
||||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function add_csrf_form($form) {
|
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
|
* 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) {
|
function amountToUKF(amount, is_cof=false, account=false) {
|
||||||
var rounding = account ? Math.floor : Math.round ;
|
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);
|
return rounding(amount * coef_cof * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +260,58 @@ function isValidTrigramme(trigramme) {
|
||||||
return trigramme.match(pattern);
|
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) {
|
function getErrorsHtml(data) {
|
||||||
var content = '';
|
var content = '';
|
||||||
if (!data)
|
if (!data)
|
||||||
|
@ -148,59 +359,77 @@ function getErrorsHtml(data) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestAuth(data, callback, focus_next = null) {
|
function displayErrors(html) {
|
||||||
var content = getErrorsHtml(data);
|
$.alert({
|
||||||
content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
|
title: 'Erreurs',
|
||||||
$.confirm({
|
content: html,
|
||||||
title: 'Authentification requise',
|
|
||||||
content: content,
|
|
||||||
backgroundDismiss: true,
|
backgroundDismiss: true,
|
||||||
animation: 'top',
|
animation: 'top',
|
||||||
closeAnimation: 'bottom',
|
closeAnimation: 'bottom',
|
||||||
keyboardEnabled: true,
|
keyboardEnabled: true,
|
||||||
confirm: function() {
|
});
|
||||||
var password = this.$content.find('input').val();
|
}
|
||||||
callback(password);
|
|
||||||
|
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);
|
||||||
},
|
},
|
||||||
onOpen: function() {
|
})
|
||||||
var that = this;
|
.done(function(data) {
|
||||||
var capslock = -1 ; // 1 -> caps on ; 0 -> caps off ; -1 or 2 -> unknown
|
on_success(data);
|
||||||
this.$content.find('input').on('keypress', function(e) {
|
})
|
||||||
if (e.keyCode == 13)
|
.fail(function($xhr) {
|
||||||
that.$confirmButton.click();
|
var response = $xhr.responseJSON;
|
||||||
|
switch ($xhr.status) {
|
||||||
var s = String.fromCharCode(e.which);
|
case 403:
|
||||||
if ((s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey)|| //caps on, shift off
|
authDialog.open({
|
||||||
(s.toUpperCase() !== s && s.toLowerCase() === s && e.shiftKey)) { //caps on, shift on
|
callback: function(password) {
|
||||||
capslock = 1 ;
|
api_with_auth(settings, password)
|
||||||
} 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() {
|
pre_content: getErrorsHtml(response),
|
||||||
if (focus_next)
|
next_focus: settings.next_focus,
|
||||||
this._lastFocused = focus_next;
|
});
|
||||||
|
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
|
* 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 %}
|
{% load l10n %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% 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>
|
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||||
{% if account.user == request.user %}
|
{% if account.user == request.user %}
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
||||||
|
@ -85,33 +86,11 @@ $(document).ready(function() {
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
|
'use strict';
|
||||||
|
|
||||||
khistory = new KHistory({
|
var khistory = new KHistory({'no_trigramme': true});
|
||||||
display_trigramme: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function getHistory() {
|
Config.reset(() => khistory.fetch({'accounts': [{{account.pk}}]}));
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
</script>
|
</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-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/moment-timezone-with-data-2010-2020.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.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" %}
|
{% include "kfetopen/init.html" %}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
|
<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>
|
<script type="text/javascript" src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
||||||
{{ filter_form.media }}
|
{{ filter_form.media }}
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||||
|
@ -32,14 +33,14 @@
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<table id="history" class="table">
|
<div id="history">
|
||||||
</table>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(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 $from_date = $('#id_from_date');
|
||||||
var $to_date = $('#id_to_date');
|
var $to_date = $('#id_to_date');
|
||||||
|
@ -54,7 +55,9 @@ $(document).ready(function() {
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHistory() {
|
function updateHistory() {
|
||||||
|
|
||||||
|
// Get API options
|
||||||
var data = {};
|
var data = {};
|
||||||
if ($from_date.val())
|
if ($from_date.val())
|
||||||
data['from'] = moment($from_date.val()).format('YYYY-MM-DD HH:mm:ss');
|
data['from'] = moment($from_date.val()).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
@ -64,21 +67,11 @@ $(document).ready(function() {
|
||||||
if ($checkouts)
|
if ($checkouts)
|
||||||
data['checkouts'] = checkouts;
|
data['checkouts'] = checkouts;
|
||||||
var accounts = getSelectedMultiple($accounts);
|
var accounts = getSelectedMultiple($accounts);
|
||||||
data['accounts'] = accounts;
|
if (accounts)
|
||||||
|
data['accounts'] = accounts.map(id => parseInt(id));
|
||||||
|
|
||||||
$.ajax({
|
// Update history
|
||||||
dataType: "json",
|
khistory.fetch(data);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaults_datetimepicker = {
|
let defaults_datetimepicker = {
|
||||||
|
@ -92,8 +85,9 @@ $(document).ready(function() {
|
||||||
$from_date.datetimepicker($.extend({}, defaults_datetimepicker, {
|
$from_date.datetimepicker($.extend({}, defaults_datetimepicker, {
|
||||||
defaultDate: moment().subtract(24, 'hours'),
|
defaultDate: moment().subtract(24, 'hours'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$to_date.datetimepicker($.extend({}, defaults_datetimepicker, {
|
$to_date.datetimepicker($.extend({}, defaults_datetimepicker, {
|
||||||
defaultDate: moment(),
|
defaultDate: moment().add(5, 'minutes') // workaround for 'stepping' rounding
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$("#from_date").on("dp.change", function (e) {
|
$("#from_date").on("dp.change", function (e) {
|
||||||
|
@ -111,131 +105,11 @@ $(document).ready(function() {
|
||||||
countSelected: "# sur %"
|
countSelected: "# sur %"
|
||||||
});
|
});
|
||||||
|
|
||||||
$("input").on('dp.change change', function() {
|
$("#update_history").on('click', function() {
|
||||||
khistory.reset();
|
updateHistory();
|
||||||
getHistory();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
khistory.$container.selectable({
|
Config.reset(updateHistory);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +1,22 @@
|
||||||
{% extends 'kfet/base_col_2.html' %}
|
{% extends 'kfet/base_col_2.html' %}
|
||||||
{% load staticfiles %}
|
{% 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 title %}Transferts{% endblock %}
|
||||||
{% block header-title %}Transferts{% endblock %}
|
{% block header-title %}Transferts{% endblock %}
|
||||||
|
|
||||||
{% block fixed %}
|
{% block fixed %}
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<div class="heading">
|
||||||
|
<div id="nb_opes"></div>
|
||||||
|
<div class="sub">transferts</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="btn btn-primary" href="{% url 'kfet.transfers.create' %}">
|
<a class="btn btn-primary" href="{% url 'kfet.transfers.create' %}">
|
||||||
Nouveaux
|
Nouveaux
|
||||||
|
@ -16,109 +27,17 @@
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<div id="history">
|
<div id="history" class="table">
|
||||||
{% 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>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
lock = 0;
|
var khistory = new KHistory();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
Config.reset(() => khistory.fetch({'transfersonly': true}));
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/transfers_form.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/transfers_form.css' %}">
|
||||||
|
<script type="text/javascript" src="{% url 'js_reverse' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Nouveaux transferts{% endblock %}
|
{% block title %}Nouveaux transferts{% endblock %}
|
||||||
|
@ -51,14 +52,12 @@
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
function getAccountData(trigramme, callback = function() {}) {
|
function getAccountData(trigramme, callback) {
|
||||||
$.ajax({
|
callback = callback || $.noop;
|
||||||
dataType: "json",
|
$.getJSON(Urls['kfet.account.read'](trigramme), {
|
||||||
url : "{% url 'kfet.account.read.json' %}",
|
'format': 'json',
|
||||||
method : "POST",
|
})
|
||||||
data : { trigramme: trigramme },
|
.done(callback);
|
||||||
success : callback,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAccountData(trigramme, $input) {
|
function updateAccountData(trigramme, $input) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ from decimal import Decimal
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -12,7 +13,7 @@ from ..config import kfet_config
|
||||||
from ..models import (
|
from ..models import (
|
||||||
Account, Article, ArticleCategory, Checkout, CheckoutStatement, Inventory,
|
Account, Article, ArticleCategory, Checkout, CheckoutStatement, Inventory,
|
||||||
InventoryArticle, Operation, OperationGroup, Order, OrderArticle, Supplier,
|
InventoryArticle, Operation, OperationGroup, Order, OrderArticle, Supplier,
|
||||||
SupplierArticle, Transfer, TransferGroup,
|
SupplierArticle, TransferGroup,
|
||||||
)
|
)
|
||||||
from .testcases import ViewTestCaseMixin
|
from .testcases import ViewTestCaseMixin
|
||||||
from .utils import create_team, create_user, get_perms
|
from .utils import create_team, create_user, get_perms
|
||||||
|
@ -248,6 +249,26 @@ class AccountReadViewTests(ViewTestCaseMixin, TestCase):
|
||||||
r = client.get(self.url)
|
r = client.get(self.url)
|
||||||
self.assertEqual(r.status_code, 200)
|
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):
|
class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||||
url_name = 'kfet.account.update'
|
url_name = 'kfet.account.update'
|
||||||
|
@ -762,6 +783,35 @@ class CheckoutReadViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertEqual(r.context['checkout'], self.checkout)
|
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):
|
class CheckoutUpdateViewTests(ViewTestCaseMixin, TestCase):
|
||||||
url_name = 'kfet.checkout.update'
|
url_name = 'kfet.checkout.update'
|
||||||
|
@ -1411,46 +1461,6 @@ class KPsulViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
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):
|
class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
url_name = 'kfet.kpsul.perform_operations'
|
url_name = 'kfet.kpsul.perform_operations'
|
||||||
url_expected = '/k-fet/k-psul/perform_operations'
|
url_expected = '/k-fet/k-psul/perform_operations'
|
||||||
|
@ -1470,11 +1480,82 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
http_methods = ['POST']
|
http_methods = ['POST']
|
||||||
|
|
||||||
auth_user = 'team'
|
auth_user = 'team1'
|
||||||
auth_forbidden = [None, 'user']
|
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):
|
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):
|
class KPsulArticlesData(ViewTestCaseMixin, TestCase):
|
||||||
|
@ -1486,16 +1567,10 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
category = ArticleCategory.objects.create(name='Catégorie')
|
self.ac = ArticleCategory.objects.create(name='Catégorie')
|
||||||
self.article1 = Article.objects.create(
|
|
||||||
category=category,
|
self.a1 = self.ac.articles.create(name='Article 1')
|
||||||
name='Article 1',
|
self.a2 = self.ac.articles.create(name='Article 2', price='2.5')
|
||||||
)
|
|
||||||
self.article2 = Article.objects.create(
|
|
||||||
category=category,
|
|
||||||
name='Article 2',
|
|
||||||
price=Decimal('2.5'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ok(self):
|
def test_ok(self):
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
|
@ -1503,24 +1578,35 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
content = json.loads(r.content.decode('utf-8'))
|
content = json.loads(r.content.decode('utf-8'))
|
||||||
|
|
||||||
articles = content['articles']
|
self.assertEqual(content, {
|
||||||
|
'objects': {
|
||||||
expected_list = [{
|
'article': [
|
||||||
'category__name': 'Catégorie',
|
{
|
||||||
|
'id': self.a1.pk,
|
||||||
'name': 'Article 1',
|
'name': 'Article 1',
|
||||||
'price': '0.00',
|
'price': '0.00',
|
||||||
}, {
|
'stock': 0,
|
||||||
'category__name': 'Catégorie',
|
'category__id': self.a1.category.pk,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': self.a2.pk,
|
||||||
'name': 'Article 2',
|
'name': 'Article 2',
|
||||||
'price': '2.50',
|
'price': '2.50',
|
||||||
}]
|
'stock': 0,
|
||||||
|
'category__id': self.a2.category.pk,
|
||||||
for expected, article in zip(expected_list, articles):
|
},
|
||||||
self.assertDictContainsSubset(expected, article)
|
],
|
||||||
self.assertSetEqual(set(article.keys()), set([
|
},
|
||||||
'id', 'name', 'price', 'stock',
|
'related': {
|
||||||
'category_id', 'category__name', 'category__has_addcost',
|
'category': [
|
||||||
]))
|
{
|
||||||
|
'id': self.ac.pk,
|
||||||
|
'name': 'Catégorie',
|
||||||
|
'has_addcost': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class KPsulUpdateAddcost(ViewTestCaseMixin, TestCase):
|
class KPsulUpdateAddcost(ViewTestCaseMixin, TestCase):
|
||||||
|
@ -1586,34 +1672,6 @@ class HistoryJSONViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
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):
|
class SettingsListViewTests(ViewTestCaseMixin, TestCase):
|
||||||
url_name = 'kfet.settings'
|
url_name = 'kfet.settings'
|
||||||
url_expected = '/k-fet/settings/'
|
url_expected = '/k-fet/settings/'
|
||||||
|
@ -1766,62 +1824,6 @@ class TransferPerformViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(team1.balance, Decimal('2.4'))
|
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):
|
class InventoryListViewTests(ViewTestCaseMixin, TestCase):
|
||||||
url_name = 'kfet.inventory'
|
url_name = 'kfet.inventory'
|
||||||
url_expected = '/k-fet/inventaires/'
|
url_expected = '/k-fet/inventaires/'
|
||||||
|
|
|
@ -159,8 +159,6 @@ urlpatterns = [
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
url('^k-psul/$', views.kpsul, name='kfet.kpsul'),
|
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,
|
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
|
||||||
name='kfet.kpsul.perform_operations'),
|
name='kfet.kpsul.perform_operations'),
|
||||||
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
|
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
|
||||||
|
@ -178,9 +176,6 @@ urlpatterns = [
|
||||||
|
|
||||||
url(r'^history.json$', views.history_json,
|
url(r'^history.json$', views.history_json,
|
||||||
name='kfet.history.json'),
|
name='kfet.history.json'),
|
||||||
url(r'^accounts/read.json$', views.account_read_json,
|
|
||||||
name='kfet.account.read.json'),
|
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Settings urls
|
# Settings urls
|
||||||
|
@ -202,8 +197,6 @@ urlpatterns = [
|
||||||
name='kfet.transfers.create'),
|
name='kfet.transfers.create'),
|
||||||
url(r'^transfers/perform$', views.perform_transfers,
|
url(r'^transfers/perform$', views.perform_transfers,
|
||||||
name='kfet.transfers.perform'),
|
name='kfet.transfers.perform'),
|
||||||
url(r'^transfers/cancel$', views.cancel_transfers,
|
|
||||||
name='kfet.transfers.cancel'),
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Inventories urls
|
# Inventories urls
|
||||||
|
|
635
kfet/views.py
635
kfet/views.py
|
@ -15,7 +15,7 @@ from django.contrib.auth.models import User, Permission
|
||||||
from django.http import JsonResponse, Http404
|
from django.http import JsonResponse, Http404
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
from django.db import transaction
|
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.db.models.functions import Coalesce
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -47,12 +47,36 @@ from decimal import Decimal
|
||||||
import heapq
|
import heapq
|
||||||
import statistics
|
import statistics
|
||||||
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
|
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
|
||||||
|
|
||||||
from .auth.views import ( # noqa
|
from .auth.views import ( # noqa
|
||||||
account_group, login_generic, AccountGroupCreate, AccountGroupUpdate,
|
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):
|
def put_cleaned_data_in_dict(dict, form):
|
||||||
for field in form.cleaned_data:
|
for field in form.cleaned_data:
|
||||||
dict[field] = form.cleaned_data[field]
|
dict[field] = form.cleaned_data[field]
|
||||||
|
@ -323,6 +347,13 @@ def account_read(request, trigramme):
|
||||||
request.user != account.user):
|
request.user != account.user):
|
||||||
raise PermissionDenied
|
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 = (
|
addcosts = (
|
||||||
OperationGroup.objects
|
OperationGroup.objects
|
||||||
.filter(opes__addcost_for=account,
|
.filter(opes__addcost_for=account,
|
||||||
|
@ -338,6 +369,7 @@ def account_read(request, trigramme):
|
||||||
'addcosts': addcosts,
|
'addcosts': addcosts,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# Account - Update
|
# Account - Update
|
||||||
|
|
||||||
|
|
||||||
|
@ -530,18 +562,38 @@ class CheckoutCreate(SuccessMessageMixin, CreateView):
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
# Checkout - Read
|
# Checkout - Read
|
||||||
|
|
||||||
class CheckoutRead(DetailView):
|
class CheckoutRead(JSONResponseMixin, DetailView):
|
||||||
model = Checkout
|
model = Checkout
|
||||||
template_name = 'kfet/checkout_read.html'
|
template_name = 'kfet/checkout_read.html'
|
||||||
context_object_name = 'checkout'
|
context_object_name = 'checkout'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**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
|
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
|
# Checkout - Update
|
||||||
|
|
||||||
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
|
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
|
||||||
|
@ -625,7 +677,24 @@ class CheckoutStatementCreate(SuccessMessageMixin, CreateView):
|
||||||
form.instance.balance_new = getAmountBalance(form.cleaned_data)
|
form.instance.balance_new = getAmountBalance(form.cleaned_data)
|
||||||
form.instance.checkout_id = self.kwargs['pk_checkout']
|
form.instance.checkout_id = self.kwargs['pk_checkout']
|
||||||
form.instance.by = self.request.user.profile.account_kfet
|
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):
|
class CheckoutStatementUpdate(SuccessMessageMixin, UpdateView):
|
||||||
model = CheckoutStatement
|
model = CheckoutStatement
|
||||||
|
@ -848,50 +917,6 @@ def kpsul_get_settings(request):
|
||||||
return JsonResponse(data)
|
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
|
@teamkfet_required
|
||||||
def kpsul_update_addcost(request):
|
def kpsul_update_addcost(request):
|
||||||
addcost_form = AddcostForm(request.POST)
|
addcost_form = AddcostForm(request.POST)
|
||||||
|
@ -1081,31 +1106,47 @@ def kpsul_perform_operations(request):
|
||||||
websocket_data = {}
|
websocket_data = {}
|
||||||
websocket_data['opegroups'] = [{
|
websocket_data['opegroups'] = [{
|
||||||
'add': True,
|
'add': True,
|
||||||
|
'modelname': 'opegroup',
|
||||||
|
'content': {
|
||||||
'id': operationgroup.pk,
|
'id': operationgroup.pk,
|
||||||
'amount': operationgroup.amount,
|
'amount': operationgroup.amount,
|
||||||
'checkout__name': operationgroup.checkout.name,
|
|
||||||
'at': operationgroup.at,
|
'at': operationgroup.at,
|
||||||
'is_cof': operationgroup.is_cof,
|
'is_cof': operationgroup.is_cof,
|
||||||
'comment': operationgroup.comment,
|
'comment': operationgroup.comment,
|
||||||
'valid_by__trigramme': (operationgroup.valid_by and
|
'valid_by': (operationgroup.valid_by and
|
||||||
operationgroup.valid_by.trigramme or None),
|
operationgroup.valid_by.trigramme or None),
|
||||||
'on_acc__trigramme': operationgroup.on_acc.trigramme,
|
'trigramme': operationgroup.on_acc.trigramme,
|
||||||
'opes': [],
|
# 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 = {
|
ope_data = {
|
||||||
'id': operation.pk, 'type': operation.type,
|
'content': {
|
||||||
'amount': operation.amount,
|
'id': ope.id,
|
||||||
'addcost_amount': operation.addcost_amount,
|
'amount': ope.amount,
|
||||||
'addcost_for__trigramme': (
|
'canceled_at': None,
|
||||||
operation.addcost_for and addcost_for.trigramme or None),
|
'canceled_by': 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,
|
|
||||||
}
|
}
|
||||||
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
|
# Need refresh from db cause we used update on queryset
|
||||||
operationgroup.checkout.refresh_from_db()
|
operationgroup.checkout.refresh_from_db()
|
||||||
websocket_data['checkouts'] = [{
|
websocket_data['checkouts'] = [{
|
||||||
|
@ -1128,37 +1169,63 @@ def kpsul_perform_operations(request):
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
def kpsul_cancel_operations(request):
|
def kpsul_cancel_operations(request):
|
||||||
# Pour la réponse
|
# Pour la réponse
|
||||||
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
|
data = {'canceled': {}, 'warnings': {}, 'errors': {}}
|
||||||
|
|
||||||
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
||||||
try:
|
try:
|
||||||
# Set pour virer les doublons
|
# 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:
|
except ValueError:
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
opes_all = (
|
opes_all = (
|
||||||
Operation.objects
|
Operation.objects
|
||||||
.select_related('group', 'group__on_acc', 'group__on_acc__negative')
|
.select_related('group', 'group__on_acc', 'group__on_acc__negative')
|
||||||
.filter(pk__in=opes_post))
|
.filter(pk__in=opes_post))
|
||||||
opes_pk = [ope.pk for ope in opes_all]
|
opes_pk = [ope.pk for ope in opes_all]
|
||||||
opes_notexisting = [ope for ope in opes_post if ope not in opes_pk]
|
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:
|
if opes_notexisting:
|
||||||
data['errors']['opes_notexisting'] = opes_notexisting
|
data['errors']['opes_notexisting'] = opes_notexisting
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
opes_already_canceled = [] # Déjà annulée
|
already_canceled = {} # Opération/Transfert déjà annulé
|
||||||
opes = [] # Pas déjà annulée
|
opes = [] # Pas déjà annulée
|
||||||
|
transfers = []
|
||||||
required_perms = set()
|
required_perms = set()
|
||||||
|
|
||||||
stop_all = False
|
stop_all = False
|
||||||
cancel_duration = kfet_config.cancel_duration
|
cancel_duration = kfet_config.cancel_duration
|
||||||
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
|
# Modifs à faire sur les balances des comptes
|
||||||
to_groups_amounts = defaultdict(lambda:0) # ------ sur les montants des groupes d'opé
|
to_accounts_balances = defaultdict(lambda: 0)
|
||||||
to_checkouts_balances = defaultdict(lambda:0) # ------ sur les balances de caisses
|
# ------ sur les montants des groupes d'opé
|
||||||
to_articles_stocks = defaultdict(lambda:0) # ------ sur les stocks d'articles
|
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:
|
for ope in opes_all:
|
||||||
if ope.canceled_at:
|
if ope.canceled_at:
|
||||||
# Opération déjà annulée, va pour un warning en Response
|
# 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:
|
else:
|
||||||
opes.append(ope.pk)
|
opes.append(ope.pk)
|
||||||
# Si opé il y a plus de CANCEL_DURATION, permission requise
|
# Si opé il y a plus de CANCEL_DURATION, permission requise
|
||||||
|
@ -1185,7 +1252,8 @@ def kpsul_cancel_operations(request):
|
||||||
# par `.save()`, amount_error est recalculé automatiquement,
|
# par `.save()`, amount_error est recalculé automatiquement,
|
||||||
# ce qui n'est pas le cas en faisant un update sur queryset
|
# 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
|
# TODO ? : Maj les balance_old de relevés pour modifier l'erreur
|
||||||
last_statement = (CheckoutStatement.objects
|
last_statement = \
|
||||||
|
(CheckoutStatement.objects
|
||||||
.filter(checkout=ope.group.checkout)
|
.filter(checkout=ope.group.checkout)
|
||||||
.order_by('at')
|
.order_by('at')
|
||||||
.last())
|
.last())
|
||||||
|
@ -1204,16 +1272,34 @@ def kpsul_cancel_operations(request):
|
||||||
# Note : si InventoryArticle est maj par .save(), stock_error
|
# Note : si InventoryArticle est maj par .save(), stock_error
|
||||||
# est recalculé automatiquement
|
# est recalculé automatiquement
|
||||||
if ope.article and ope.article_nb:
|
if ope.article and ope.article_nb:
|
||||||
last_stock = (InventoryArticle.objects
|
last_stock = (
|
||||||
|
InventoryArticle.objects
|
||||||
.select_related('inventory')
|
.select_related('inventory')
|
||||||
.filter(article=ope.article)
|
.filter(article=ope.article)
|
||||||
.order_by('inventory__at')
|
.order_by('inventory__at')
|
||||||
.last())
|
.last()
|
||||||
|
)
|
||||||
if not last_stock or last_stock.inventory.at < ope.group.at:
|
if not last_stock or last_stock.inventory.at < ope.group.at:
|
||||||
to_articles_stocks[ope.article] += ope.article_nb
|
to_articles_stocks[ope.article] += ope.article_nb
|
||||||
|
|
||||||
if not opes:
|
for transfer in transfers_all:
|
||||||
data['warnings']['already_canceled'] = opes_already_canceled
|
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)
|
return JsonResponse(data)
|
||||||
|
|
||||||
negative_accounts = []
|
negative_accounts = []
|
||||||
|
@ -1240,6 +1326,10 @@ def kpsul_cancel_operations(request):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
(Operation.objects.filter(pk__in=opes)
|
(Operation.objects.filter(pk__in=opes)
|
||||||
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
.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:
|
for account in to_accounts_balances:
|
||||||
(
|
(
|
||||||
Account.objects
|
Account.objects
|
||||||
|
@ -1261,11 +1351,13 @@ def kpsul_cancel_operations(request):
|
||||||
stock=F('stock') + to_articles_stocks[article])
|
stock=F('stock') + to_articles_stocks[article])
|
||||||
|
|
||||||
# Websocket data
|
# Websocket data
|
||||||
websocket_data = { 'opegroups': [], 'opes': [], 'checkouts': [], 'articles': [] }
|
websocket_data = {'opegroups': [], 'opes': [],
|
||||||
|
'checkouts': [], 'articles': []}
|
||||||
# Need refresh from db cause we used update on querysets
|
# 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
|
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:
|
for opegroup in opegroups:
|
||||||
websocket_data['opegroups'].append({
|
websocket_data['opegroups'].append({
|
||||||
'cancellation': True,
|
'cancellation': True,
|
||||||
|
@ -1273,18 +1365,29 @@ def kpsul_cancel_operations(request):
|
||||||
'amount': opegroup['amount'],
|
'amount': opegroup['amount'],
|
||||||
'is_cof': opegroup['is_cof'],
|
'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:
|
for ope in opes:
|
||||||
websocket_data['opes'].append({
|
websocket_data['opes'].append({
|
||||||
'cancellation': True,
|
'cancellation': True,
|
||||||
|
'modelname': 'ope',
|
||||||
'id': ope,
|
'id': ope,
|
||||||
'canceled_by__trigramme': canceled_by__trigramme,
|
'canceled_by': canceled_by,
|
||||||
'canceled_at': canceled_at,
|
'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
|
# 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
|
checkouts = (Checkout.objects
|
||||||
.values('id', 'balance').filter(pk__in=checkouts_pk))
|
.values('id', 'balance')
|
||||||
|
.filter(pk__in=checkouts_pk))
|
||||||
for checkout in checkouts:
|
for checkout in checkouts:
|
||||||
websocket_data['checkouts'].append({
|
websocket_data['checkouts'].append({
|
||||||
'id': checkout['id'],
|
'id': checkout['id'],
|
||||||
|
@ -1298,92 +1401,195 @@ def kpsul_cancel_operations(request):
|
||||||
'stock': article['stock']})
|
'stock': article['stock']})
|
||||||
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
|
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
|
||||||
|
|
||||||
data['canceled'] = opes
|
data['canceled']['opes'] = opes
|
||||||
if opes_already_canceled:
|
data['canceled']['transfers'] = transfers
|
||||||
data['warnings']['already_canceled'] = opes_already_canceled
|
if already_canceled:
|
||||||
|
data['warnings']['already_canceled'] = already_canceled
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def history_json(request):
|
def history_json(request):
|
||||||
# Récupération des paramètres
|
# Récupération des paramètres
|
||||||
from_date = request.POST.get('from', None)
|
from_date = request.GET.get('from', None)
|
||||||
to_date = request.POST.get('to', None)
|
to_date = request.GET.get('to', None)
|
||||||
limit = request.POST.get('limit', None);
|
checkouts = request.GET.getlist('checkouts[]', None)
|
||||||
checkouts = request.POST.getlist('checkouts[]', None)
|
accounts = request.GET.getlist('accounts[]', None)
|
||||||
accounts = request.POST.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
|
# Construction de la requête (sur les opérations) pour le prefetch
|
||||||
queryset_prefetch = Operation.objects.select_related(
|
ope_queryset_prefetch = Operation.objects.select_related(
|
||||||
'article', 'canceled_by', 'addcost_for')
|
'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
|
# Construction de la requête principale
|
||||||
opegroups = (
|
opegroups = (
|
||||||
OperationGroup.objects
|
OperationGroup.objects
|
||||||
.prefetch_related(Prefetch('opes', queryset=queryset_prefetch))
|
.prefetch_related(ope_prefetch)
|
||||||
.select_related('on_acc', 'valid_by')
|
.select_related('on_acc',
|
||||||
|
'valid_by')
|
||||||
.order_by('at')
|
.order_by('at')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
transfergroups = (
|
||||||
|
TransferGroup.objects
|
||||||
|
.prefetch_related(transfer_prefetch)
|
||||||
|
.select_related('valid_by')
|
||||||
|
.order_by('at')
|
||||||
|
)
|
||||||
|
|
||||||
# Application des filtres
|
# Application des filtres
|
||||||
if from_date:
|
if from_date:
|
||||||
opegroups = opegroups.filter(at__gte=from_date)
|
opegroups = opegroups.filter(at__gte=from_date)
|
||||||
|
transfergroups = transfergroups.filter(at__gte=from_date)
|
||||||
if to_date:
|
if to_date:
|
||||||
opegroups = opegroups.filter(at__lt=to_date)
|
opegroups = opegroups.filter(at__lt=to_date)
|
||||||
|
transfergroups = transfergroups.filter(at__lt=to_date)
|
||||||
if checkouts:
|
if checkouts:
|
||||||
opegroups = opegroups.filter(checkout_id__in=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:
|
if accounts:
|
||||||
opegroups = opegroups.filter(on_acc_id__in=accounts)
|
opegroups = opegroups.filter(on_acc__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]
|
|
||||||
|
|
||||||
|
|
||||||
# Construction de la réponse
|
# Construction de la réponse
|
||||||
opegroups_list = []
|
related_data = defaultdict(list)
|
||||||
|
objects_data = defaultdict(list)
|
||||||
for opegroup in opegroups:
|
for opegroup in opegroups:
|
||||||
opegroup_dict = {
|
opegroup_dict = {
|
||||||
'id': opegroup.id,
|
'id': opegroup.id,
|
||||||
'amount': opegroup.amount,
|
'amount': opegroup.amount,
|
||||||
'at': opegroup.at,
|
'at': opegroup.at,
|
||||||
'checkout_id': opegroup.checkout_id,
|
|
||||||
'is_cof': opegroup.is_cof,
|
'is_cof': opegroup.is_cof,
|
||||||
'comment': opegroup.comment,
|
'comment': opegroup.comment,
|
||||||
'opes' : [],
|
'trigramme':
|
||||||
'on_acc__trigramme':
|
|
||||||
opegroup.on_acc and opegroup.on_acc.trigramme or None,
|
opegroup.on_acc and opegroup.on_acc.trigramme or None,
|
||||||
}
|
}
|
||||||
if request.user.has_perm('kfet.is_team'):
|
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)
|
opegroup.valid_by and opegroup.valid_by.trigramme or None)
|
||||||
|
|
||||||
for ope in opegroup.opes.all():
|
for ope in opegroup.opes.all():
|
||||||
ope_dict = {
|
ope_dict = {
|
||||||
'id': ope.id,
|
'id': ope.id,
|
||||||
'type' : ope.type,
|
|
||||||
'amount': ope.amount,
|
'amount': ope.amount,
|
||||||
'article_nb' : ope.article_nb,
|
|
||||||
'addcost_amount': ope.addcost_amount,
|
|
||||||
'canceled_at': ope.canceled_at,
|
'canceled_at': ope.canceled_at,
|
||||||
'article__name':
|
'is_cof': opegroup.is_cof,
|
||||||
ope.article and ope.article.name or None,
|
'trigramme':
|
||||||
'addcost_for__trigramme':
|
opegroup.on_acc and opegroup.on_acc.trigramme or None,
|
||||||
ope.addcost_for and ope.addcost_for.trigramme or None,
|
'opegroup__id': opegroup.id,
|
||||||
}
|
}
|
||||||
if request.user.has_perm('kfet.is_team'):
|
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)
|
ope.canceled_by and ope.canceled_by.trigramme or None)
|
||||||
opegroup_dict['opes'].append(ope_dict)
|
|
||||||
opegroups_list.append(opegroup_dict)
|
if ope.type == Operation.PURCHASE:
|
||||||
return JsonResponse({ 'opegroups': opegroups_list })
|
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
|
@teamkfet_required
|
||||||
def kpsul_articles_data(request):
|
def kpsul_articles_data(request):
|
||||||
articles = (
|
data = {'objects': {}, 'related': {}}
|
||||||
Article.objects
|
|
||||||
.values('id', 'name', 'price', 'stock', 'category_id',
|
data['objects']['article'] = [
|
||||||
'category__name', 'category__has_addcost')
|
{
|
||||||
.filter(is_sold=True))
|
'id': article.id,
|
||||||
return JsonResponse({ 'articles': list(articles) })
|
'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
|
@teamkfet_required
|
||||||
|
@ -1430,25 +1636,10 @@ config_update = (
|
||||||
# Transfer views
|
# Transfer views
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
def transfers(request):
|
def transfers(request):
|
||||||
transfers_pre = Prefetch(
|
return render(request, 'kfet/transfers.html')
|
||||||
'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,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
|
@ -1457,6 +1648,7 @@ def transfers_create(request):
|
||||||
return render(request, 'kfet/transfers_create.html',
|
return render(request, 'kfet/transfers_create.html',
|
||||||
{ 'transfer_formset': transfer_formset })
|
{ 'transfer_formset': transfer_formset })
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
def perform_transfers(request):
|
def perform_transfers(request):
|
||||||
data = {'errors': {}, 'transfers': [], 'transfergroup': 0}
|
data = {'errors': {}, 'transfers': [], 'transfergroup': 0}
|
||||||
|
@ -1464,13 +1656,16 @@ def perform_transfers(request):
|
||||||
# Checking transfer_formset
|
# Checking transfer_formset
|
||||||
transfer_formset = TransferFormSet(request.POST)
|
transfer_formset = TransferFormSet(request.POST)
|
||||||
if not transfer_formset.is_valid():
|
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
|
# Initializing vars
|
||||||
required_perms = set(['kfet.add_transfer']) # Required perms to perform all transfers
|
# Required perms to perform all transfers
|
||||||
to_accounts_balances = defaultdict(lambda:0) # For balances of accounts
|
required_perms = set(['kfet.add_transfer'])
|
||||||
|
# For balances of accounts
|
||||||
|
to_accounts_balances = defaultdict(lambda: 0)
|
||||||
|
|
||||||
for transfer in transfers:
|
for transfer in transfers:
|
||||||
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
||||||
|
@ -1519,8 +1714,8 @@ def perform_transfers(request):
|
||||||
negative = AccountNegative(
|
negative = AccountNegative(
|
||||||
account=account, start=timezone.now())
|
account=account, start=timezone.now())
|
||||||
negative.save()
|
negative.save()
|
||||||
elif (hasattr(account, 'negative')
|
elif (hasattr(account, 'negative') and
|
||||||
and not account.negative.balance_offset):
|
not account.negative.balance_offset):
|
||||||
account.negative.delete()
|
account.negative.delete()
|
||||||
|
|
||||||
# Saving transfer group
|
# Saving transfer group
|
||||||
|
@ -1533,103 +1728,38 @@ def perform_transfers(request):
|
||||||
transfer.save()
|
transfer.save()
|
||||||
data['transfers'].append(transfer.pk)
|
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)
|
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):
|
class InventoryList(ListView):
|
||||||
queryset = (Inventory.objects
|
queryset = (Inventory.objects
|
||||||
|
@ -2020,29 +2150,6 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView):
|
||||||
# ---------------
|
# ---------------
|
||||||
# Vues génériques
|
# 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):
|
class JSONDetailView(JSONResponseMixin, BaseDetailView):
|
||||||
|
|
|
@ -18,6 +18,7 @@ statistics==1.0.3.5
|
||||||
django-widget-tweaks==1.4.1
|
django-widget-tweaks==1.4.1
|
||||||
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
||||||
ldap3
|
ldap3
|
||||||
|
django-js-reverse==0.7.3
|
||||||
channels==1.1.5
|
channels==1.1.5
|
||||||
python-dateutil
|
python-dateutil
|
||||||
wagtail==1.10.*
|
wagtail==1.10.*
|
||||||
|
|
Loading…
Reference in a new issue