Better k-fet js and more
JavaScript ---------- - Basic classes that can be inherited to define a new class for a django model in javascript. - Formatters classes are used to render properties and attributes of the instances of models classes. - New classes to handle Account, Checkout, Statement models. - Refactor K-Psul JS (part n/m). - Better file organization. Views ----- - 'kpsul.checkout_data' is cleaner. Last statement is added to the JSON response with GET paramater 'last_statement'. - 'account.read.json' is merged in account.read. JSON response is sent if GET parametter 'format' is set to 'json'. - Fix PEP8 of concerned views. New requirement: django-js-reverse ---------------------------------- Used to resolve the URLs defined in the project in JavaScript. See https://github.com/ierror/django-js-reverse
This commit is contained in:
parent
ee848d210e
commit
a9cb50b38d
10 changed files with 1168 additions and 574 deletions
|
@ -50,6 +50,7 @@ INSTALLED_APPS = (
|
|||
'kfet',
|
||||
'channels',
|
||||
'widget_tweaks',
|
||||
'django_js_reverse',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
|
|
|
@ -14,9 +14,11 @@ from django.conf import settings
|
|||
from django.conf.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.contrib.auth import views as django_views
|
||||
from django_cas_ng import views as django_cas_views
|
||||
from django_js_reverse.views import urls_js
|
||||
|
||||
from gestioncof import views as gestioncof_views, csv_views
|
||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
||||
|
@ -85,6 +87,7 @@ urlpatterns = [
|
|||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
||||
url(r'^k-fet/', include('kfet.urls')),
|
||||
url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
|
@ -38,10 +38,10 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
height:120px;
|
||||
}
|
||||
|
||||
#account[data-balance="ok"] #account_form input { background:#009011; color:#FFF;}
|
||||
#account[data-balance="low"] #account_form input { background:#EC6400; color:#FFF; }
|
||||
#account[data-balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
|
||||
#account[data-balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
|
||||
#account[data_balance="ok"] #account_form input { background:#009011; color:#FFF;}
|
||||
#account[data_balance="low"] #account_form input { background:#EC6400; color:#FFF; }
|
||||
#account[data_balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
|
||||
#account[data_balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
|
||||
|
||||
#account_form {
|
||||
padding:0;
|
||||
|
@ -79,7 +79,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
font-size:12px;
|
||||
}
|
||||
|
||||
#account_data #account-balance {
|
||||
#account_data #account-balance_ukf {
|
||||
height:40px;
|
||||
line-height:40px;
|
||||
|
||||
|
@ -112,7 +112,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
font-size:14px;
|
||||
line-height:24px;
|
||||
}
|
||||
#account_data #account-balance {
|
||||
#account_data #account-balance_ukf {
|
||||
font-size:50px;
|
||||
line-height:60px;
|
||||
height:60px;
|
||||
|
|
685
kfet/static/kfet/js/kfet.api.js
Normal file
685
kfet/static/kfet/js/kfet.api.js
Normal file
|
@ -0,0 +1,685 @@
|
|||
/**
|
||||
* @file Interact with k-fet API.
|
||||
* @copyright 2017 cof-geek
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ---------- ---------- */
|
||||
|
||||
|
||||
/**
|
||||
* Virtual namespace for models.
|
||||
* <br><br>
|
||||
*
|
||||
* A model subclasses {@link Models.ModelObject}.<br>
|
||||
* A model whose instances can be got from API subclasses
|
||||
* {@link Models.APIModelObject}.<br>
|
||||
* These two classes should not be used directly.
|
||||
* <br><br>
|
||||
*
|
||||
* Models with API support:
|
||||
* {@link Models.Account} (partial),
|
||||
* {@link Models.Checkout} (partial).
|
||||
* <br>
|
||||
* Models without API support:
|
||||
* {@link Models.Statement}.
|
||||
*
|
||||
* @namespace Models
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Extended object
|
||||
* @memberof Models
|
||||
*/
|
||||
class ModelObject {
|
||||
|
||||
/**
|
||||
* These properties always exist on instances of this class.
|
||||
* @abstract
|
||||
* @type {string[]}
|
||||
*/
|
||||
static get props() { return []; }
|
||||
|
||||
/**
|
||||
* Default values for properties given by <tt>props</tt> static member.
|
||||
* @abstract
|
||||
* @type {Object.<string, *>}
|
||||
*/
|
||||
static get default_data() { return {}; }
|
||||
|
||||
/**
|
||||
* Create new instance from data or default values.
|
||||
* @param {Object} [data={}] - data to store in instance
|
||||
*/
|
||||
constructor(data) {
|
||||
this.from(data || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an instance is empty.
|
||||
* @return {boolean}
|
||||
*/
|
||||
is_empty() {
|
||||
return ( this.id === undefined ) ? false : this.id == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Formatters|Formatter} used for rendering.
|
||||
* @default {@link Formatters.Formatter}
|
||||
* @type {Formatter}
|
||||
*/
|
||||
formatter() { return Formatter; }
|
||||
|
||||
/**
|
||||
* Set properties of this instance from data object ones. If a property is
|
||||
* not present in data object, its value fallback to
|
||||
* {@link Models.ModelObject.default_data} one.
|
||||
* @param {Object} data
|
||||
* @todo Restrict to props
|
||||
*/
|
||||
from(data) {
|
||||
// TODO: add restrict
|
||||
$.extend(this, this.constructor.default_data, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear properties to {@link Models.ModelObject.default_data|default_data}.
|
||||
*/
|
||||
clear() {
|
||||
this.from({});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display stored data in container.
|
||||
* @param {jQuery} $container
|
||||
* @param {Object} [options] Options for formatter render method.
|
||||
* @param {Formatters.Formatter}
|
||||
* [formatter={@link Models.ModelObject#formatter|this.formatter()}]
|
||||
* Formatter class to use.
|
||||
*/
|
||||
display($container, options, formatter) {
|
||||
formatter = formatter || this.formatter();
|
||||
formatter.render(this, $container, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Describes a model whose instances can be obtained through API.
|
||||
* @extends Models.ModelObject
|
||||
* @memberof Models
|
||||
*/
|
||||
class APIModelObject extends ModelObject {
|
||||
|
||||
/**
|
||||
* Request url to get array of model instances data.
|
||||
* @abstract
|
||||
* @type {string}
|
||||
*/
|
||||
static get url_model() {}
|
||||
|
||||
/**
|
||||
* Request url to get a single model instance data.
|
||||
* @abstract
|
||||
* @param {*} api_pk - Identifier of a model instance in api requests.
|
||||
* @return {string}
|
||||
*/
|
||||
static url_object_for(api_pk) {}
|
||||
|
||||
/**
|
||||
* See {@link Models.ModelObject|new ModelObject(data)}.
|
||||
* @param {Object} [data={}] - data to store in instance
|
||||
*/
|
||||
constructor(data) {
|
||||
super(data);
|
||||
|
||||
if (this.id === undefined)
|
||||
this.id = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier of the model instance in api requests.
|
||||
* @default <tt>this.id</tt>
|
||||
*/
|
||||
get api_pk() { return this.id; }
|
||||
|
||||
/**
|
||||
* Request url used to get current instance data.
|
||||
*/
|
||||
get url_object() {
|
||||
if (this._url_object === undefined)
|
||||
return this.is_empty() ? '' : this.constructor.url_object_for(this.api_pk);
|
||||
return this._url_object;
|
||||
}
|
||||
|
||||
set url_object(v) { this._url_object = v; }
|
||||
|
||||
/**
|
||||
* Get data of a distant model instance. It sends a GET HTTP request to
|
||||
* {@link Models.APIModelObject#url_object}.
|
||||
* @param {object} [api_options] Additional data appended to the request.
|
||||
* @param {jQueryAjaxSuccess} [on_success] A function to be called if the request succeeds.
|
||||
* @param {jQueryAjaxError} [on_error] A function to be called if the request fails.
|
||||
*/
|
||||
fromAPI(api_options, on_success, on_error) {
|
||||
var that = this;
|
||||
|
||||
api_options = api_options || {};
|
||||
on_success = on_success || $.noop;
|
||||
on_error = on_error || $.noop;
|
||||
|
||||
api_options['format'] = 'json';
|
||||
|
||||
$.getJSON(this.url_object, api_options)
|
||||
.done(function (json, textStatus, jqXHR) {
|
||||
that.from(json)
|
||||
on_success(json, textStatus, jqXHR);
|
||||
})
|
||||
.fail(on_error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data of a distant model instance via its id.
|
||||
* @param {*} api_pk
|
||||
* @param {object} [api_options]
|
||||
* @param {jQueryAjaxSuccess} [on_success]
|
||||
* @param {jQueryAjaxError} [on_error]
|
||||
* @see {@link Models.APIModelObject#fromAPI|fromAPI}
|
||||
*/
|
||||
get_by_apipk(api_pk, api_options, on_success, on_error) {
|
||||
this.url_object = this.constructor.url_object_for(api_pk);
|
||||
this.fromAPI(api_options, on_success, on_error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <tt>empty_props</tt> and <tt>empty_attrs</tt> options to
|
||||
* <tt>render</tt> call in {@link Models.ModelObject#display} if instance
|
||||
* is empty.
|
||||
* @see {@link Models.ModelObject#display}
|
||||
* @see {@link Models.APIModelObject#is_empty}
|
||||
*/
|
||||
display($container, options, formatter) {
|
||||
if (this.is_empty()) {
|
||||
options.empty_props = true;
|
||||
options.empty_attrs = true;
|
||||
}
|
||||
return ModelObject.prototype.display.call(this, $container, options, formatter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Account model. Can be accessed through API.
|
||||
* @extends Models.APIModelObject
|
||||
* @memberof Models
|
||||
*/
|
||||
class Account extends APIModelObject {
|
||||
|
||||
/**
|
||||
* Properties retrieved through API.
|
||||
* @default <tt>['id', 'trigramme', 'name', 'nickname', 'email', 'is_cof',
|
||||
* 'promo', 'balance', 'is_frozen', 'departement']</tt>
|
||||
* @see {@link Models.ModelObject.props|ModelObject.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ['id', 'trigramme', 'name', 'nickname', 'email', 'is_cof',
|
||||
'promo', 'balance', 'is_frozen', 'departement'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for Account model instances.
|
||||
* @default <tt>{ 'id': 0, 'trigramme': '', 'name': '', 'nickname': '',
|
||||
* 'email': '', ''is_cof': false, 'promo': '', 'balance': 0,
|
||||
* 'is_frozen': false, 'departement': '' }</tt>
|
||||
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
|
||||
*/
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': 0, 'trigramme': '', 'name': '', 'nickname': '', 'email': '',
|
||||
'is_cof' : false, 'promo': '', 'balance': 0, 'is_frozen': false,
|
||||
'departement': '',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @default <tt>django-js-reverse('kfet.account')</tt>
|
||||
* @see {@link Models.APIModelObject.url_model|APIModelObject.url_model}
|
||||
*/
|
||||
static get url_model() { return Urls['kfet.account'](); }
|
||||
|
||||
/**
|
||||
* @default <tt>django-js-reverse('kfet.account.read')(trigramme)</tt>
|
||||
* @param {string} trigramme
|
||||
* @see {@link Models.APIModelObject.url_object_for|APIModelObject.url_object_for}
|
||||
*/
|
||||
static url_object_for(trigramme) {
|
||||
var trigramme_url = encodeURIComponent(trigramme);
|
||||
return Urls['kfet.account.read'](trigramme_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @default <tt>this.trigramme</tt>
|
||||
*/
|
||||
get api_pk() { return this.trigramme; }
|
||||
|
||||
/**
|
||||
* @default {@link Formatters.AccountFormatter}
|
||||
*/
|
||||
formatter() {
|
||||
return (this.trigramme == 'LIQ') ? LIQFormatter : AccountFormatter;
|
||||
}
|
||||
|
||||
// take care of "balance" type
|
||||
// API currently returns a string object (serialization of Decimal type within Django)
|
||||
get balance() { return this._balance; }
|
||||
set balance(v) { return this._balance = floatCheck(v); }
|
||||
|
||||
/**
|
||||
* Balance converted to UKF according to cof status.
|
||||
*/
|
||||
get balance_ukf() { return amountToUKF(this.balance, this.is_cof); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checkout model. Can be accessed through API.
|
||||
* @extends Models.APIModelObject
|
||||
* @memberof Models
|
||||
*/
|
||||
class Checkout extends APIModelObject {
|
||||
|
||||
/**
|
||||
* Properties retrieved through API.
|
||||
* @default <tt>['id', 'name', 'balance', 'valid_from', 'valid_to']</tt>
|
||||
* @see {@link Models.ModelObject.props|ModelObject.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ['id', 'name', 'balance', 'valid_from', 'valid_to'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for Account model instances.
|
||||
* @default <tt>{ 'id': 0, 'name': '', 'balance': 0, 'valid_from': '',
|
||||
* 'valid_to': '' }</tt>
|
||||
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
|
||||
*/
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': 0, 'name': '', 'balance': 0, 'valid_from': '', 'valid_to': '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by API yet.
|
||||
* @see {@link Models.APIModelObject.url_model|APIModelObject.url_model}
|
||||
*/
|
||||
static get url_model() { return ''; }
|
||||
|
||||
/**
|
||||
* @default <tt>django-js-reverse('kfet.kpsul.checkout_data.read')(pk)</tt>
|
||||
* @param {string} api_pk - a checkout id
|
||||
* @see {@link Models.APIModelObject.url_object_for|APIModelObject.url_object_for}
|
||||
*/
|
||||
static url_object_for(api_pk) {
|
||||
return Urls['kfet.kpsul.checkout_data.read'](api_pk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @default {@link Formatters.CheckoutFormatter}
|
||||
*/
|
||||
formatter() {
|
||||
return CheckoutFormatter;
|
||||
}
|
||||
|
||||
// take care of "balance" type
|
||||
// API currently returns a string object (serialization of Decimal type within Django)
|
||||
get balance() { return this._balance; }
|
||||
set balance(v) { this._balance = floatCheck(v); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Statement model. Cannot be accessed through API.
|
||||
* @extends Models.ModelObject
|
||||
* @memberof Models
|
||||
*/
|
||||
class Statement extends ModelObject {
|
||||
|
||||
/**
|
||||
* Properties associated to a statement.
|
||||
* @default <tt>['id', 'at', 'balance_old', 'balance_new', 'by']</tt>
|
||||
* @see {@link Models.ModelObject.props|ModelObject.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ['id', 'at', 'balance_old', 'balance_new', 'by'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for Statement model instances.
|
||||
* @default <tt>{ 'id': 0, 'at': '', 'balance_old': 0, 'balance_new': 0,
|
||||
* 'by': '' }</tt>
|
||||
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
|
||||
*/
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': 0, 'at': '', 'balance_old': 0, 'balance_new': 0, 'by': '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @default {@link Formatters.StatementFormatter}
|
||||
*/
|
||||
formatter() {
|
||||
return StatementFormatter;
|
||||
}
|
||||
|
||||
// take care of "balance" type
|
||||
// API currently returns a string object (serialization of Decimal type within Django)
|
||||
get balance_old() { return this._balance_old; }
|
||||
set balance_old(v) { this._balance_old = floatCheck(v); }
|
||||
|
||||
get balance_new() { return this._balance_new; }
|
||||
set balance_new(v) { this._balance_new = floatCheck(v); }
|
||||
|
||||
get at() { return this._at; }
|
||||
set at(v) { this._at = moment.isMoment(v) ? v : moment.tz(v, 'UTC'); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ---------- ---------- */
|
||||
|
||||
/**
|
||||
* Virtual namespace for formatters.
|
||||
* <br><br>
|
||||
*
|
||||
* A formatter subclasses {@link Formatters.Formatter}.<br>
|
||||
* Formatters should be accessed statically only.
|
||||
*
|
||||
* @namespace Formatters
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
*/
|
||||
class Formatter {
|
||||
|
||||
static get props() { return []; }
|
||||
static get attrs() { return []; }
|
||||
|
||||
/**
|
||||
* Get value of a property through current formatter if defined, object itself otherwise.
|
||||
*
|
||||
* @param {*} object - Object used for formatting
|
||||
* @param {string} name - Name of property to render
|
||||
* @param {string} [prefix=''] - Prefix used for formatter method search
|
||||
* @return {*} Formatted property value
|
||||
*/
|
||||
static get(object, name, prefix) {
|
||||
prefix = prefix || '';
|
||||
var method = prefix + name;
|
||||
if (this[method] !== undefined)
|
||||
return this[method](object);
|
||||
return object[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted value of a property using prefix 'prop_'.
|
||||
* @see Formatter.get
|
||||
*/
|
||||
static get_prop(object, name) {
|
||||
return this.get(object, name, 'prop_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted value of a property using prefix 'attr_'.
|
||||
* @see Formatter.get
|
||||
*/
|
||||
static get_attr(obj, attr) {
|
||||
return this.get(obj, attr, 'attr_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render container.
|
||||
* @param {*} object - Object used for formatting
|
||||
* @param {jQueryNode} $container
|
||||
* @param {object} [options]
|
||||
* <tt>options['props']</tt> should be an array of
|
||||
* properties (default: <tt>class.props</tt>).<br>
|
||||
* <tt>options['prefix_prop']</tt> is added in front of
|
||||
* properties names in selector (default: <tt>'.'</tt>).<br>
|
||||
* <tt>options['prefix_attr']</tt> is added in front of
|
||||
* attributes names for container (default: <tt>''</tt>).
|
||||
*/
|
||||
static render(object, $container, options) {
|
||||
options.props = options.props || [];
|
||||
options.attrs = options.attrs || [];
|
||||
|
||||
var props = options.override_props ? options.props : this.props.concat(options.props);
|
||||
var attrs = options.override_attrs ? options.attrs : this.attrs.concat(options.attrs);
|
||||
|
||||
var prefix_prop = options.prefix_prop !== undefined ? options.prefix_prop : '.';
|
||||
var prefix_attr = options.prefix_attr !== undefined ? options.prefix_attr : '';
|
||||
|
||||
for (var i in props) {
|
||||
var selector = prefix_prop + props[i];
|
||||
var html = options.empty_props ? '' : this.get_prop(object, props[i]);
|
||||
$container.find( selector ).html( html );
|
||||
}
|
||||
|
||||
for (var i in attrs) {
|
||||
var name = prefix_attr + attrs[i];
|
||||
var value = options.empty_attrs ? '' : this.get_attr(object, attrs[i]);
|
||||
$container.attr( name, value );
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
* @extends Formatters.Formatter
|
||||
*/
|
||||
class AccountFormatter extends Formatter {
|
||||
|
||||
/**
|
||||
* Properties renderable in html.
|
||||
* @default {@link Models.Account.props}<tt> + ['balance_ukf']</tt>
|
||||
*/
|
||||
static get props() {
|
||||
return Account.props.concat(['balance_ukf']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Added attributes to $container element.
|
||||
* @default <tt>['data-balance']</tt>
|
||||
*/
|
||||
static get attrs() {
|
||||
return ['data_balance'];
|
||||
}
|
||||
|
||||
static get _data_balance() {
|
||||
return {
|
||||
'default': '', 'frozen': 'frozen',
|
||||
'ok': 'ok', 'low': 'low', 'neg': 'neg',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* <tt>a.balance</tt> with two decimals.
|
||||
*/
|
||||
static prop_balance(a) {
|
||||
return a.balance.toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <tt>'COF' if account is cof or 'Non-COF'
|
||||
*/
|
||||
static prop_is_cof(a) {
|
||||
return a.is_cof ? 'COF' : 'Non-COF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Value of data_attribute according to is_frozen status and balance of
|
||||
* account <tt>a</tt>.
|
||||
*/
|
||||
static attr_data_balance(a) {
|
||||
if (a.is_frozen) { return this._data_balance.frozen; }
|
||||
else if (a.balance >= 5) { return this._data_balance.ok; }
|
||||
else if (a.balance >= 0) { return this._data_balance.low; }
|
||||
else /* a.balance < 0 */ { return this._data_balance.neg; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
* @extends Formatters.AccountFormatter
|
||||
*/
|
||||
class LIQFormatter extends AccountFormatter {
|
||||
|
||||
/**
|
||||
* Rendering a property always returns the empty string
|
||||
* @default <tt>''</tt>
|
||||
*/
|
||||
static get_prop() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute <tt>data_balance</tt> is always <tt>ok</tt>.
|
||||
*/
|
||||
static attr_data_balance(a) { return this._data_balance.ok; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
* @extends Formatters.Formatter
|
||||
*/
|
||||
class CheckoutFormatter extends Formatter {
|
||||
|
||||
/**
|
||||
* Properties renderable to html.
|
||||
* @default {@link Models.Checkout.props}
|
||||
*/
|
||||
static get props() {
|
||||
return Checkout.props;
|
||||
}
|
||||
|
||||
/**
|
||||
* <tt>c.balance</tt> with two decimals.
|
||||
*/
|
||||
static prop_balance(c) {
|
||||
return c.balance.toFixed(2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
* @extends Formatters.Formatter
|
||||
*/
|
||||
|
||||
class StatementFormatter extends Formatter {
|
||||
|
||||
/**
|
||||
* Properties renderable to html.
|
||||
* @default {@link Models.Statement.props}
|
||||
*/
|
||||
static get props() {
|
||||
return Statement.props;
|
||||
}
|
||||
|
||||
/**
|
||||
* <tt>s.balance_old</tt> with two decimals.
|
||||
*/
|
||||
static prop_balance_old(s) {
|
||||
return s.balance_old.toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <tt>s.balance_new</tt> with two decimals.
|
||||
*/
|
||||
static prop_balance_new(s) {
|
||||
return s.balance_new.toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <tt>s.at</tt> formatted as <tt>DD/MM/YY à HH:MM</tt>.
|
||||
*/
|
||||
static prop_at(s) {
|
||||
return moment.isMoment(s.at) ? s.at.tz('Europe/Paris').format('DD/MM/YY à HH:mm') : s.at;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,30 @@
|
|||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
$(window).scroll(function() {
|
||||
if ($(window).width() >= 768 && $(this).scrollTop() > 72.6) {
|
||||
|
@ -38,7 +65,7 @@ function amountDisplay(amount, is_cof=false, tri='') {
|
|||
}
|
||||
|
||||
function amountToUKF(amount, is_cof=false) {
|
||||
var coef_cof = is_cof ? 1 + Config.getAll().subvention_cof / 100 : 1;
|
||||
var coef_cof = is_cof ? 1 + Config.get('subvention_cof') / 100 : 1;
|
||||
return Math.round(amount * coef_cof * 10);
|
||||
}
|
||||
|
||||
|
|
369
kfet/static/kfet/js/kpsul.js
Normal file
369
kfet/static/kfet/js/kpsul.js
Normal file
|
@ -0,0 +1,369 @@
|
|||
class KPsulManager {
|
||||
|
||||
constructor(env) {
|
||||
this._env = env;
|
||||
this.account_manager = new AccountManager(this);
|
||||
this.checkout_manager = new CheckoutManager(this);
|
||||
}
|
||||
|
||||
reset(soft) {
|
||||
soft = soft || false;
|
||||
|
||||
this.account_manager.reset();
|
||||
|
||||
if (!soft) {
|
||||
this.checkout_manager.reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AccountManager {
|
||||
|
||||
constructor() {
|
||||
this._$container = $('#account');
|
||||
|
||||
this.account = new Account();
|
||||
this.selection = new AccountSelection(this);
|
||||
this.search = new AccountSearch(this);
|
||||
}
|
||||
|
||||
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() {
|
||||
// dirty
|
||||
|
||||
var buttons = '';
|
||||
|
||||
if (this.is_empty()) {
|
||||
var trigramme = this.selection.get();
|
||||
if (trigramme.isValidTri()) {
|
||||
var url_base = "{% url 'kfet.account.create' %}";
|
||||
var url = url_base + '?trigramme=' + encodeURIComponent(trigramme);
|
||||
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Créer ce compte"><span class="glyphicon glyphicon-plus"></span></a>';
|
||||
} else { /* trigramme input is empty or invalid */
|
||||
buttons += '<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>';
|
||||
}
|
||||
} else { /* an account is loaded */
|
||||
var url = Urls['kfet.account.update'](encodeURIComponent(this.account.trigramme));
|
||||
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Modifier ce compte"><span class="glyphicon glyphicon-cog"></span></a>';
|
||||
}
|
||||
|
||||
this._$container.find('.buttons').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, {},
|
||||
() => this._update_on_success(),
|
||||
() => this.reset_data()
|
||||
);
|
||||
} else {
|
||||
this.reset_data();
|
||||
}
|
||||
}
|
||||
|
||||
_update_on_success() {
|
||||
$('#id_on_acc').val(this.account.id);
|
||||
this.display();
|
||||
|
||||
kpsul._env.articleSelect.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: '{% url "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,
|
||||
});
|
||||
|
||||
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_';
|
||||
}
|
||||
|
||||
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,
|
||||
(data) => this._update_on_success(data),
|
||||
() => this.reset_data());
|
||||
|
||||
if (kpsul.account_manager.is_empty()) {
|
||||
kpsul.account_manager.focus();
|
||||
} else {
|
||||
kpsul._env.articleSelect.focus().select();
|
||||
}
|
||||
}
|
||||
|
||||
_update_on_success(data) {
|
||||
if (data['laststatement'] !== undefined)
|
||||
this.laststatement.from(data['laststatement']);
|
||||
|
||||
$('#id_checkout').val(this.checkout.id);
|
||||
this.display();
|
||||
|
||||
}
|
||||
|
||||
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 id = this.checkout.id;
|
||||
var url_details = Urls['kfet.checkout.read'](id);
|
||||
var url_newcheckout = Urls['kfet.checkoutstatement.create'](id);
|
||||
buttons += '<a href="' + url_newcheckout + '" title="Effectuer un relevé" class="btn btn-primary" target="_blank"><span class="glyphicon glyphicon-euro"></span></a>';
|
||||
buttons += '<a class="btn btn-primary" href="' + url_details + '" title="Modifier" target="_blank"><span class="glyphicon glyphicon-cog"></span></a>';
|
||||
}
|
||||
this._$container.find('.buttons').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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CheckoutSelection {
|
||||
|
||||
constructor(manager) {
|
||||
this.manager = manager;
|
||||
this._$input = $('#id_checkout_select');
|
||||
|
||||
this._init_events();
|
||||
|
||||
this.choices = [
|
||||
for (option of this._$input.find('option'))
|
||||
if ($(option).attr('value') != '')
|
||||
parseInt($(option).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);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,11 @@
|
|||
<script type="text/javascript" src="{% static 'kfet/js/moment.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="{% url 'js_reverse' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kpsul.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}K-Psul{% endblock %}
|
||||
|
@ -80,7 +83,7 @@
|
|||
{{ trigramme_form.trigramme }}
|
||||
</div>
|
||||
<div class="col-lg-9 col-xs-8" id="account_data">
|
||||
<div id="account-balance" class="data_line"></div>
|
||||
<div id="account-balance_ukf" class="data_line"></div>
|
||||
<div id="account-name" class="data_line"></div>
|
||||
<div id="account-nickname" class="data_line"></div>
|
||||
<div class="data_line">
|
||||
|
@ -102,7 +105,9 @@
|
|||
<div>
|
||||
<b>En caisse:</b> <span id="checkout-balance"></span> €
|
||||
</div>
|
||||
<div id="last_statement">
|
||||
<div id="last_statement" style="display:none">
|
||||
<b>Dernier relevé:</b><br>
|
||||
<span id="checkout-last_statement_balance_new"></span>€ le <span id="checkout-last_statement_at"></span> par <span id="checkout-last_statement_by"></span>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
</div>
|
||||
|
@ -184,413 +189,6 @@ function booleanCheck(v) {
|
|||
return v == true;
|
||||
}
|
||||
|
||||
function functionCheck(v) {
|
||||
if (typeof v === 'function')
|
||||
return v;
|
||||
return function(){};
|
||||
}
|
||||
|
||||
class Config {
|
||||
|
||||
static getAll() {
|
||||
if (typeof window.config === 'undefined')
|
||||
window.config = {};
|
||||
return window.config;
|
||||
}
|
||||
|
||||
static reset(callback) {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.kpsul.get_settings' %}",
|
||||
method : "POST",
|
||||
})
|
||||
.done(function(data) {
|
||||
Config.addcost_for = data['addcost_for'];
|
||||
Config.addcost_amount = data['addcost_amount'];
|
||||
Config.subvention_cof = data['subvention_cof'];
|
||||
})
|
||||
.always(callback);
|
||||
}
|
||||
|
||||
static set addcost_for(v) {
|
||||
Config.getAll().addcost_for = v;
|
||||
}
|
||||
|
||||
static set addcost_amount(v) {
|
||||
Config.getAll().addcost_amount = floatCheck(v);
|
||||
}
|
||||
|
||||
static set subvention_cof(v) {
|
||||
Config.getAll().subvention_cof = floatCheck(v);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
String.prototype.formatTri = function() {
|
||||
return this.toUpperCase();
|
||||
}
|
||||
|
||||
String.prototype.isValidTri = function() {
|
||||
var pattern = /^[^a-z]{3}$/;
|
||||
return pattern.test(this);
|
||||
}
|
||||
|
||||
String.prototype.urlify = function() {
|
||||
return encodeURIComponent(this);
|
||||
}
|
||||
|
||||
|
||||
class Account {
|
||||
|
||||
constructor() {
|
||||
$.extend(this, this.constructor.default_data);
|
||||
}
|
||||
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': 0, 'trigramme': '', 'name': '', 'nickname': '', 'email': '',
|
||||
'is_cof' : false, 'promo': '', 'balance': 0, 'is_frozen': false,
|
||||
'departement': '',
|
||||
};
|
||||
};
|
||||
|
||||
get id() { return this._id; }
|
||||
get balance() { return this._balance; }
|
||||
get balance_ukf() { return amountToUKF(this.balance, this.is_cof); }
|
||||
get is_cof() { return this._is_cof; }
|
||||
get is_frozen() { return this._is_frozen; }
|
||||
|
||||
set id(v) { this._id = intCheck(v); }
|
||||
set balance(v) { this._balance = floatCheck(v); }
|
||||
set is_cof(v) { this._is_cof = booleanCheck(v); }
|
||||
set is_frozen(v) { this._is_frozen = booleanCheck(v); }
|
||||
|
||||
from(data, callback) {
|
||||
callback = functionCheck(callback);
|
||||
$.extend(this, Account.default_data, data);
|
||||
callback();
|
||||
}
|
||||
|
||||
fromAPI(trigramme, on_success, on_error) {
|
||||
on_error = functionCheck(on_error);
|
||||
var that = this;
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.account.read.json' %}",
|
||||
method : "POST",
|
||||
data : { trigramme: trigramme },
|
||||
})
|
||||
.done((data) => this.from(data, on_success))
|
||||
.fail(on_error);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.from({});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AccountFormatter {
|
||||
|
||||
constructor(account) {
|
||||
this._account = account;
|
||||
}
|
||||
|
||||
static get _data_balance_default() { return ''; }
|
||||
static get _data_balance_frozen() { return 'frozen'; }
|
||||
static get _data_balance_ok() { return 'ok'; }
|
||||
static get _data_balance_low() { return 'low'; }
|
||||
static get _data_balance_neg() { return 'neg'; }
|
||||
|
||||
get str_balance_ukf() { return ''; }
|
||||
get str_is_cof() { return ''; }
|
||||
get data_balance() { return this.constructor._data_balance_default; }
|
||||
|
||||
display($container) {
|
||||
var a = this._account;
|
||||
$container
|
||||
.find('#account-balance')
|
||||
.text(this.str_balance_ukf)
|
||||
.end()
|
||||
.find('#account-is_cof')
|
||||
.text(this.str_is_cof)
|
||||
.end()
|
||||
.find('#account-name')
|
||||
.text(a.name)
|
||||
.end()
|
||||
.find('#account-nickname')
|
||||
.text(a.nickname)
|
||||
.end()
|
||||
.find('#account-departement')
|
||||
.text(a.departement)
|
||||
.end()
|
||||
.find('#account-promo')
|
||||
.text(a.promo)
|
||||
.end()
|
||||
.find('#account-email')
|
||||
.text(a.email)
|
||||
.end();
|
||||
$container.attr('data-balance', this.data_balance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StandardAccountFormatter extends AccountFormatter {
|
||||
|
||||
get str_balance_ukf() {
|
||||
return this._account.balance_ukf;
|
||||
}
|
||||
|
||||
get str_is_cof() {
|
||||
return this._account.is_cof ? 'COF' : 'Non-COF';
|
||||
}
|
||||
|
||||
get data_balance() {
|
||||
var is_frozen = this._account.is_frozen;
|
||||
var balance = this._account.balance;
|
||||
if (is_frozen) { return this.constructor._data_balance_frozen; }
|
||||
else if (balance >= 5) { return this.constructor._data_balance_ok; }
|
||||
else if (balance >= 0) { return this.constructor._data_balance_low; }
|
||||
else /* balance < 0 */ { return this.constructor._data_balance_neg; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class LIQAccountFormatter extends AccountFormatter {
|
||||
|
||||
get str_balance_ukf() { return ''; }
|
||||
get str_is_cof() { return ''; }
|
||||
get data_balance() { return this.constructor.data_balance_ok; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AccountManager {
|
||||
|
||||
constructor(env) {
|
||||
this._env = env; // temporary, should be a link to "kpsul_manager" or something like this
|
||||
|
||||
this._$container = $('#account');
|
||||
|
||||
this.account = new Account();
|
||||
this.selection = new AccountSelection(this);
|
||||
this.search = new AccountSearch(this);
|
||||
}
|
||||
|
||||
get is_empty() {
|
||||
return this.account.id == 0;
|
||||
}
|
||||
|
||||
get formatter() {
|
||||
if (this.account.trigramme == 'LIQ') { return LIQAccountFormatter; }
|
||||
else if (!this.is_empty) { return StandardAccountFormatter; }
|
||||
else /* account is empty */ { return AccountFormatter; }
|
||||
}
|
||||
|
||||
display() {
|
||||
this._display_data();
|
||||
this._display_buttons();
|
||||
}
|
||||
|
||||
_display_data() {
|
||||
(new (this.formatter)(this.account)).display(this._$container);
|
||||
}
|
||||
|
||||
_display_buttons() {
|
||||
// dirty
|
||||
|
||||
var buttons = '';
|
||||
|
||||
if (this.is_empty) {
|
||||
var trigramme = this.selection.get();
|
||||
if (trigramme.isValidTri()) {
|
||||
var url_base = "{% url 'kfet.account.create' %}";
|
||||
var url = url_base + '?trigramme=' + trigramme.urlify();
|
||||
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Créer ce compte"><span class="glyphicon glyphicon-plus"></span></a>';
|
||||
} else { /* trigramme input is empty or invalid */
|
||||
buttons += '<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>';
|
||||
}
|
||||
} else { /* an account is loaded */
|
||||
var url_pattern = "{% url 'kfet.account.read' 'LIQ' %}";
|
||||
var url_base = url_pattern.substr(0, url_pattern.length - 3); /* dirty */
|
||||
var url = url_base + this.account.trigramme.urlify();
|
||||
buttons += '<a href="'+url+'" class="btn btn-primary" target="_blank" title="Modifier ce compte"><span class="glyphicon glyphicon-cog"></span></a>';
|
||||
}
|
||||
|
||||
this._$container.find('.buttons').html(buttons);
|
||||
}
|
||||
|
||||
update(trigramme) {
|
||||
if (typeof trigramme !== 'undefined')
|
||||
this.selection.set(trigramme);
|
||||
|
||||
trigramme = trigramme || this.selection.get();
|
||||
|
||||
if (trigramme.isValidTri()) {
|
||||
this.account.fromAPI(trigramme,
|
||||
() => this._update_on_success(),
|
||||
() => this.reset_data()
|
||||
);
|
||||
} else {
|
||||
this.reset_data();
|
||||
}
|
||||
}
|
||||
|
||||
_update_on_success() {
|
||||
$('#id_on_acc').val(this.account.id);
|
||||
this.display();
|
||||
|
||||
this._env.articleSelect.focus();
|
||||
this._env.updateBasketAmount();
|
||||
this._env.updateBasketRel();
|
||||
}
|
||||
|
||||
reset() {
|
||||
$('#id_on_acc').val(0);
|
||||
this.selection.reset();
|
||||
this.reset_data();
|
||||
}
|
||||
|
||||
reset_data() {
|
||||
this.account.reset();
|
||||
this.display();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.selection.focus();
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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() {
|
||||
console.log('plop');
|
||||
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: '{% url "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,
|
||||
});
|
||||
|
||||
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.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
this._$dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
'use strict';
|
||||
|
@ -601,91 +199,6 @@ $(document).ready(function() {
|
|||
// Lock to avoid multiple requests
|
||||
var lock = 0;
|
||||
|
||||
// -----
|
||||
// Checkout data management
|
||||
// -----
|
||||
|
||||
// Initializing
|
||||
var checkout_container = $('#checkout');
|
||||
var checkoutInput = $('#id_checkout_select');
|
||||
var checkout_data = {}
|
||||
var checkout_data_default = {
|
||||
'id' : 0,
|
||||
'name': '',
|
||||
'balance' : '',
|
||||
'valid_from': '',
|
||||
'valid_to' : '',
|
||||
'last_statement_balance': '',
|
||||
'last_statement_at' : '',
|
||||
'last_statement_by_trigramme' : '',
|
||||
'last_statement_by_first_name': '',
|
||||
'last_statement_by_last_name' : '' ,
|
||||
}
|
||||
var last_statement_container = $('#last_statement');
|
||||
var last_statement_html_default = '<b>Dernier relevé: </b><br><span id="checkout-last_statement_balance"></span>€ le <span id="checkout-last_statement_at"></span> par <span id="checkout-last_statement_by_trigramme"></span>';
|
||||
|
||||
|
||||
// Display data
|
||||
function displayCheckoutData() {
|
||||
var at_formated = '';
|
||||
if (checkout_data['last_statement_at']) {
|
||||
last_statement_container.html(last_statement_html_default);
|
||||
var at = moment.tz(checkout_data['last_statement_at'], 'UTC');
|
||||
at_formated = at.tz('Europe/Paris').format('DD/MM/YY à HH:mm');
|
||||
} else {
|
||||
last_statement_container.html('');
|
||||
}
|
||||
for (var elem in checkout_data) {
|
||||
$('#checkout-'+elem).text(checkout_data[elem]);
|
||||
}
|
||||
$('#checkout-last_statement_at').text(at_formated);
|
||||
var buttons = '';
|
||||
if (checkout_data['id'] !== 0) {
|
||||
var url_base = "{% url 'kfet.checkoutstatement.create' 1 %}";
|
||||
url_base = url_base.substr(0,url_base.length - 16);
|
||||
buttons += '<a class="btn btn-primary" href="'+url_base+checkout_data['id']+'/statements/add" title="Effectuer un relevé" target="_blank"><span class="glyphicon glyphicon-euro"></span></a>';
|
||||
buttons += '<a class="btn btn-primary" href="'+url_base+checkout_data['id']+'" title="Modifier" target="_blank"><span class="glyphicon glyphicon-cog"></span></a>';
|
||||
}
|
||||
checkout_container.find('.buttons').html(buttons);
|
||||
}
|
||||
|
||||
// Clear data
|
||||
function resetCheckout() {
|
||||
checkout_data = checkout_data_default;
|
||||
$('#id_checkout').val(0);
|
||||
checkoutInput.find('option:first').prop('selected', true);
|
||||
displayCheckoutData();
|
||||
}
|
||||
|
||||
// Store data
|
||||
function storeCheckoutData(data) {
|
||||
checkout_data = $.extend({}, checkout_data_default, data);
|
||||
$('#id_checkout').val(checkout_data['id']);
|
||||
displayCheckoutData();
|
||||
}
|
||||
|
||||
// Retrieve data
|
||||
function retrieveCheckoutData(id) {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.kpsul.checkout_data' %}",
|
||||
method : "POST",
|
||||
data : { 'pk': id },
|
||||
})
|
||||
.done(function(data) { storeCheckoutData(data) })
|
||||
.fail(function() { resetCheckout() });
|
||||
}
|
||||
|
||||
// Event listener
|
||||
checkoutInput.on('change', function() {
|
||||
retrieveCheckoutData(checkoutInput.val());
|
||||
if (!account_manager.is_empty()) {
|
||||
articleSelect.focus().select();
|
||||
} else {
|
||||
account_manager.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// -----
|
||||
// Auth
|
||||
// -----
|
||||
|
@ -1047,13 +560,12 @@ $(document).ready(function() {
|
|||
var arrowKeys = /^(37|38|39|40)$/;
|
||||
|
||||
function amountEuroPurchase(article_data, nb) {
|
||||
var cfg = Config.getAll();
|
||||
var amount_euro = - article_data[3] * nb ;
|
||||
if (cfg.addcost_for && cfg.addcost_amount && account_manager.account.trigramme != cfg.addcost_for)
|
||||
amount_euro -= cfg.addcost_amount * nb;
|
||||
if (Config.get('addcost_for') && Config.get('addcost_amount') && account_manager.account.trigramme != Config.get('addcost_for'))
|
||||
amount_euro -= Config.get('addcost_amount') * nb;
|
||||
var reduc_divisor = 1;
|
||||
if (account_manager.account.is_cof)
|
||||
reduc_divisor = 1 + cfg.subvention_cof / 100;
|
||||
if (kpsul.account_manager.account.is_cof)
|
||||
reduc_divisor = 1 + Config.get('subvention_cof') / 100;
|
||||
return amount_euro / reduc_divisor;
|
||||
}
|
||||
|
||||
|
@ -1070,7 +582,7 @@ $(document).ready(function() {
|
|||
.attr('data-opeindex', index)
|
||||
.find('.number').text(nb).end()
|
||||
.find('.name').text(article_data[0]).end()
|
||||
.find('.amount').text(amountToUKF(amount_euro, account_manager.account.is_cof));
|
||||
.find('.amount').text(amountToUKF(amount_euro, kpsul.account_manager.account.is_cof));
|
||||
basket_container.prepend(article_basket_html);
|
||||
updateBasketRel();
|
||||
}
|
||||
|
@ -1084,7 +596,7 @@ $(document).ready(function() {
|
|||
.attr('data-opeindex', index)
|
||||
.find('.number').text(amount+"€").end()
|
||||
.find('.name').text(text).end()
|
||||
.find('.amount').text(amountToUKF(amount, account_manager.account.is_cof));
|
||||
.find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof));
|
||||
basket_container.prepend(deposit_basket_html);
|
||||
updateBasketRel();
|
||||
}
|
||||
|
@ -1097,7 +609,7 @@ $(document).ready(function() {
|
|||
.attr('data-opeindex', index)
|
||||
.find('.number').text(amount+"€").end()
|
||||
.find('.name').text('Retrait').end()
|
||||
.find('.amount').text(amountToUKF(amount, account_manager.account.is_cof));
|
||||
.find('.amount').text(amountToUKF(amount, kpsul.account_manager.account.is_cof));
|
||||
basket_container.prepend(withdraw_basket_html);
|
||||
updateBasketRel();
|
||||
}
|
||||
|
@ -1139,7 +651,7 @@ $(document).ready(function() {
|
|||
var amount = $(this).find('#id_form-'+opeindex+'-amount');
|
||||
if (!deleted && type == "purchase")
|
||||
amount.val(amountEuroPurchase(article_id, article_nb));
|
||||
basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), account_manager.account.is_cof));
|
||||
basket_container.find('[data-opeindex='+opeindex+'] .amount').text(amountToUKF(amount.val(), kpsul.account_manager.account.is_cof));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1147,8 +659,9 @@ $(document).ready(function() {
|
|||
|
||||
function updateBasketRel() {
|
||||
var basketrel_html = '';
|
||||
var trigramme = account_manager.account.trigramme;
|
||||
var is_cof = account_manager.account.is_cof;
|
||||
var account = kpsul.account_manager.account;
|
||||
var trigramme = account.trigramme;
|
||||
var is_cof = account.is_cof;
|
||||
if (trigramme == 'LIQ' && !isBasketEmpty()) {
|
||||
var amount = - getAmountBasket();
|
||||
basketrel_html += '<div>Total: '+amount.toFixed(2)+' €</div>';
|
||||
|
@ -1161,7 +674,7 @@ $(document).ready(function() {
|
|||
} else if (trigramme != '' && !isBasketEmpty()) {
|
||||
var amount = getAmountBasket();
|
||||
var amountUKF = amountToUKF(amount, is_cof);
|
||||
var newBalance = account_manager.account.balance + amount;
|
||||
var newBalance = account.balance + amount;
|
||||
var newBalanceUKF = amountToUKF(newBalance, is_cof);
|
||||
basketrel_html += '<div>Total: '+amountUKF+'</div>';
|
||||
basketrel_html += '<div>Nouveau solde: '+newBalanceUKF+'</div>';
|
||||
|
@ -1340,7 +853,7 @@ $(document).ready(function() {
|
|||
|
||||
function updatePreviousOp() {
|
||||
var previousop_html = '';
|
||||
var trigramme = account_manager.account.trigramme;
|
||||
var trigramme = kpsul.account_manager.account.trigramme;
|
||||
previousop_html += '<div class="trigramme">Trigramme : '+trigramme+'</div>';
|
||||
previousop_html += basketrel_container.html();
|
||||
previousop_container.html(previousop_html);
|
||||
|
@ -1357,14 +870,13 @@ $(document).ready(function() {
|
|||
var addcost_default_html = '<div id="addcost">Majoration vers <span class="addcost_for"></span> de <span class="addcost_amount"></span>€</div>';
|
||||
|
||||
function displayAddcost() {
|
||||
var cfg = Config.getAll();
|
||||
checkout_container.find('#addcost').remove();
|
||||
$('#addcost').remove();
|
||||
$('body').css('animation', 'none');
|
||||
if (cfg.addcost_for && cfg.addcost_amount) {
|
||||
if (Config.get('addcost_for') && Config.get('addcost_amount')) {
|
||||
var addcost_html = $(addcost_default_html);
|
||||
addcost_html
|
||||
.find('.addcost_for').text(cfg.addcost_for).end()
|
||||
.find('.addcost_amount').text(cfg.addcost_amount.toFixed(2)).end();
|
||||
.find('.addcost_for').text(Config.get('addcost_for')).end()
|
||||
.find('.addcost_amount').text(Config.get('addcost_amount').toFixed(2)).end();
|
||||
checkout_container.prepend(addcost_html);
|
||||
$('body').css('animation', 'colorchange 3s infinite');
|
||||
}
|
||||
|
@ -1494,8 +1006,8 @@ $(document).ready(function() {
|
|||
.text(article['stock']);
|
||||
}
|
||||
if (data['addcost']) {
|
||||
Config.addcost_for = data['addcost']['for'];
|
||||
Config.addcost_amount = data['addcost']['amount'];
|
||||
Config.set('addcost_for', data['addcost']['for']);
|
||||
Config.set('addcost_amount', data['addcost']['amount']);
|
||||
displayAddcost();
|
||||
}
|
||||
}
|
||||
|
@ -1507,17 +1019,17 @@ $(document).ready(function() {
|
|||
// Reset functions
|
||||
|
||||
function coolReset(give_tri_focus=true) {
|
||||
account_manager.reset();
|
||||
kpsul.account_manager.reset();
|
||||
resetBasket();
|
||||
resetComment();
|
||||
resetSelectable();
|
||||
if (give_tri_focus)
|
||||
account_manager.focus();
|
||||
kpsul.account_manager.focus();
|
||||
}
|
||||
|
||||
function hardReset(give_tri_focus=true) {
|
||||
coolReset(give_tri_focus);
|
||||
checkoutInput.trigger('change');
|
||||
kpsul.checkout_manager.reset();
|
||||
resetArticles();
|
||||
resetPreviousOp();
|
||||
khistory.reset();
|
||||
|
@ -1605,7 +1117,7 @@ $(document).ready(function() {
|
|||
updateBasketRel: updateBasketRel,
|
||||
};
|
||||
|
||||
var account_manager = new AccountManager(env);
|
||||
window.kpsul = new KPsulManager(env);
|
||||
|
||||
hardReset();
|
||||
|
||||
|
|
|
@ -132,8 +132,8 @@ urlpatterns = [
|
|||
# -----
|
||||
|
||||
url('^k-psul/$', views.kpsul, name='kfet.kpsul'),
|
||||
url('^k-psul/checkout_data$', views.kpsul_checkout_data,
|
||||
name='kfet.kpsul.checkout_data'),
|
||||
url(r'^k-psul/checkout_data/(?P<pk>\d+)$', views.kpsul_checkout_data,
|
||||
name='kfet.kpsul.checkout_data.read'),
|
||||
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
|
||||
name='kfet.kpsul.perform_operations'),
|
||||
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
|
||||
|
@ -151,9 +151,6 @@ urlpatterns = [
|
|||
|
||||
url(r'^history.json$', views.history_json,
|
||||
name='kfet.history.json'),
|
||||
url(r'^accounts/read.json$', views.account_read_json,
|
||||
name='kfet.account.read.json'),
|
||||
|
||||
|
||||
# -----
|
||||
# Settings urls
|
||||
|
|
|
@ -17,6 +17,7 @@ from django.contrib.auth.decorators import login_required, permission_required
|
|||
from django.contrib.auth.models import User, Permission, Group
|
||||
from django.http import HttpResponse, JsonResponse, Http404
|
||||
from django.forms import modelformset_factory, formset_factory
|
||||
from django.forms.models import model_to_dict
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import F, Sum, Prefetch, Count, Func
|
||||
from django.db.models.functions import Coalesce
|
||||
|
@ -316,12 +317,14 @@ def account_create_ajax(request, username=None, login_clipper=None):
|
|||
'user_form' : forms['user_form'],
|
||||
})
|
||||
|
||||
|
||||
# Account - Read
|
||||
|
||||
@login_required
|
||||
def account_read(request, trigramme):
|
||||
try:
|
||||
account = Account.objects.select_related('negative').get(trigramme=trigramme)
|
||||
account = Account.objects.select_related('negative')\
|
||||
.get(trigramme=trigramme)
|
||||
except Account.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
|
@ -330,12 +333,24 @@ def account_read(request, trigramme):
|
|||
and request.user != account.user:
|
||||
raise PermissionDenied
|
||||
|
||||
addcosts = (OperationGroup.objects
|
||||
if request.GET.get('format') == 'json':
|
||||
data = model_to_dict(
|
||||
account,
|
||||
fields=['id', 'trigramme', 'firstname', 'lastname', 'email',
|
||||
'is_cof', 'promo', 'balance', 'is_frozen', 'departement',
|
||||
'nickname', 'trigramme']
|
||||
)
|
||||
data['name'] = account.name
|
||||
return JsonResponse(data)
|
||||
|
||||
addcosts = (
|
||||
OperationGroup.objects
|
||||
.filter(opes__addcost_for=account, opes__canceled_at=None)
|
||||
.extra({'date': "date(at)"})
|
||||
.values('date')
|
||||
.annotate(sum_addcosts=Sum('opes__addcost_amount'))
|
||||
.order_by('-date'))
|
||||
.order_by('-date')
|
||||
)
|
||||
|
||||
return render(request, "kfet/account_read.html", {
|
||||
'account': account,
|
||||
|
@ -343,6 +358,7 @@ def account_read(request, trigramme):
|
|||
'settings': {'subvention_cof': Settings.SUBVENTION_COF()},
|
||||
})
|
||||
|
||||
|
||||
# Account - Update
|
||||
|
||||
@login_required
|
||||
|
@ -827,45 +843,28 @@ def kpsul_get_settings(request):
|
|||
}
|
||||
return JsonResponse(data)
|
||||
|
||||
@teamkfet_required
|
||||
def account_read_json(request):
|
||||
trigramme = request.POST.get('trigramme', '')
|
||||
account = get_object_or_404(Account, trigramme=trigramme)
|
||||
data = { 'id': account.pk, 'name': account.name, 'email': account.email,
|
||||
'is_cof': account.is_cof, 'promo': account.promo,
|
||||
'balance': account.balance, 'is_frozen': account.is_frozen,
|
||||
'departement': account.departement, 'nickname': account.nickname,
|
||||
'trigramme': account.trigramme }
|
||||
return JsonResponse(data)
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_checkout_data(request):
|
||||
pk = request.POST.get('pk', 0)
|
||||
if not pk:
|
||||
pk = 0
|
||||
data = (Checkout.objects
|
||||
.annotate(
|
||||
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
|
||||
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
|
||||
last_statement_by_trigramme=F('statements__by__trigramme'),
|
||||
last_statement_balance=F('statements__balance_new'),
|
||||
last_statement_at=F('statements__at'))
|
||||
.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')
|
||||
.select_related(
|
||||
'statements'
|
||||
'statements__by',
|
||||
'statements__by__cofprofile__user')
|
||||
.filter(pk=pk)
|
||||
.order_by('statements__at')
|
||||
.last())
|
||||
if data is None:
|
||||
raise Http404
|
||||
def kpsul_checkout_data(request, pk):
|
||||
checkout = get_object_or_404(Checkout, pk=pk)
|
||||
data = model_to_dict(
|
||||
checkout,
|
||||
fields=['id', 'name', 'balance', 'valid_from', 'valid_to']
|
||||
)
|
||||
|
||||
if request.GET.get('last_statement'):
|
||||
last_statement = checkout.statements.latest('at')
|
||||
last_statement_data = model_to_dict(
|
||||
last_statement,
|
||||
fields=['id', 'at', 'balance_new', 'balance_old', 'by']
|
||||
)
|
||||
# ``at`` is not editable, so skipped by ``model_to_dict``
|
||||
last_statement_data['at'] = last_statement.at
|
||||
data['laststatement'] = last_statement_data
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_update_addcost(request):
|
||||
addcost_form = AddcostForm(request.POST)
|
||||
|
|
|
@ -18,3 +18,4 @@ statistics==1.0.3.5
|
|||
future==0.15.2
|
||||
django-widget-tweaks==1.4.1
|
||||
git+https://github.com/Aureplop/channels.git#egg=channels
|
||||
django-js-reverse==0.7.3
|
||||
|
|
Loading…
Reference in a new issue