From a9cb50b38dfc4c711777d1774257670e2cc728b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 23 Feb 2017 22:07:38 +0100 Subject: [PATCH] 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 --- cof/settings_dev.py | 1 + cof/urls.py | 3 + kfet/static/kfet/css/kpsul.css | 12 +- kfet/static/kfet/js/kfet.api.js | 685 ++++++++++++++++++++++++++++++++ kfet/static/kfet/js/kfet.js | 29 +- kfet/static/kfet/js/kpsul.js | 369 +++++++++++++++++ kfet/templates/kfet/kpsul.html | 548 ++----------------------- kfet/urls.py | 7 +- kfet/views.py | 87 ++-- requirements.txt | 1 + 10 files changed, 1168 insertions(+), 574 deletions(-) create mode 100644 kfet/static/kfet/js/kfet.api.js create mode 100644 kfet/static/kfet/js/kpsul.js diff --git a/cof/settings_dev.py b/cof/settings_dev.py index f6521222..7bffc0e7 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -50,6 +50,7 @@ INSTALLED_APPS = ( 'kfet', 'channels', 'widget_tweaks', + 'django_js_reverse', ) MIDDLEWARE_CLASSES = ( diff --git a/cof/urls.py b/cof/urls.py index b4c4da3c..068791e0 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -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: diff --git a/kfet/static/kfet/css/kpsul.css b/kfet/static/kfet/css/kpsul.css index 7f9c55e6..3d8c63f4 100644 --- a/kfet/static/kfet/css/kpsul.css +++ b/kfet/static/kfet/css/kpsul.css @@ -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; diff --git a/kfet/static/kfet/js/kfet.api.js b/kfet/static/kfet/js/kfet.api.js new file mode 100644 index 00000000..7836fcb9 --- /dev/null +++ b/kfet/static/kfet/js/kfet.api.js @@ -0,0 +1,685 @@ +/** + * @file Interact with k-fet API. + * @copyright 2017 cof-geek + * @license MIT + */ + +/** + * Get and store K-Psul config from API. + *

+ * + * 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. + *

+ * + * A model subclasses {@link Models.ModelObject}.
+ * A model whose instances can be got from API subclasses + * {@link Models.APIModelObject}.
+ * These two classes should not be used directly. + *

+ * + * Models with API support: + * {@link Models.Account} (partial), + * {@link Models.Checkout} (partial). + *
+ * 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 props static member. + * @abstract + * @type {Object.} + */ + 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 this.id + */ + 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 empty_props and empty_attrs options to + * render 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 ['id', 'trigramme', 'name', 'nickname', 'email', 'is_cof', + * 'promo', 'balance', 'is_frozen', 'departement'] + * @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 { 'id': 0, 'trigramme': '', 'name': '', 'nickname': '', + * 'email': '', ''is_cof': false, 'promo': '', 'balance': 0, + * 'is_frozen': false, 'departement': '' } + * @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 django-js-reverse('kfet.account') + * @see {@link Models.APIModelObject.url_model|APIModelObject.url_model} + */ + static get url_model() { return Urls['kfet.account'](); } + + /** + * @default django-js-reverse('kfet.account.read')(trigramme) + * @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 this.trigramme + */ + 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 ['id', 'name', 'balance', 'valid_from', 'valid_to'] + * @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 { 'id': 0, 'name': '', 'balance': 0, 'valid_from': '', + * 'valid_to': '' } + * @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 django-js-reverse('kfet.kpsul.checkout_data.read')(pk) + * @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 ['id', 'at', 'balance_old', 'balance_new', 'by'] + * @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 { 'id': 0, 'at': '', 'balance_old': 0, 'balance_new': 0, + * 'by': '' } + * @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. + *

+ * + * A formatter subclasses {@link Formatters.Formatter}.
+ * 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] + * options['props'] should be an array of + * properties (default: class.props).
+ * options['prefix_prop'] is added in front of + * properties names in selector (default: '.').
+ * options['prefix_attr'] is added in front of + * attributes names for container (default: ''). + */ + 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} + ['balance_ukf'] + */ + static get props() { + return Account.props.concat(['balance_ukf']); + } + + /** + * Added attributes to $container element. + * @default ['data-balance'] + */ + static get attrs() { + return ['data_balance']; + } + + static get _data_balance() { + return { + 'default': '', 'frozen': 'frozen', + 'ok': 'ok', 'low': 'low', 'neg': 'neg', + }; + } + + /** + * a.balance with two decimals. + */ + static prop_balance(a) { + return a.balance.toFixed(2); + } + + /** + * '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 a. + */ + 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 '' + */ + static get_prop() { + return ''; + } + + /** + * Attribute data_balance is always ok. + */ + 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; + } + + /** + * c.balance 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; + } + + /** + * s.balance_old with two decimals. + */ + static prop_balance_old(s) { + return s.balance_old.toFixed(2); + } + + /** + * s.balance_new with two decimals. + */ + static prop_balance_new(s) { + return s.balance_new.toFixed(2); + } + + /** + * s.at formatted as DD/MM/YY à HH:MM. + */ + static prop_at(s) { + return moment.isMoment(s.at) ? s.at.tz('Europe/Paris').format('DD/MM/YY à HH:mm') : s.at; + } + +} diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index f09bf347..455d2d95 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -1,3 +1,30 @@ +/** + * @file Miscellaneous JS definitions for k-fet 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); } diff --git a/kfet/static/kfet/js/kpsul.js b/kfet/static/kfet/js/kpsul.js new file mode 100644 index 00000000..9dd6ae19 --- /dev/null +++ b/kfet/static/kfet/js/kpsul.js @@ -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 += ''; + } else { /* trigramme input is empty or invalid */ + buttons += ''; + } + } else { /* an account is loaded */ + var url = Urls['kfet.account.update'](encodeURIComponent(this.account.trigramme)); + buttons += ''; + } + + 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 = '
' ; + 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 += ''; + buttons += ''; + } + 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); + } +} + diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 11dfb0f2..7fc56605 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -12,8 +12,11 @@ + + + {% endblock %} {% block title %}K-Psul{% endblock %} @@ -80,7 +83,7 @@ {{ trigramme_form.trigramme }}
-
+
@@ -102,7 +105,9 @@
En caisse:
-
+
@@ -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 += ''; - } else { /* trigramme input is empty or invalid */ - buttons += ''; - } - } 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 += ''; - } - - 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 = '
' ; - 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 = 'Dernier relevé:
€ le par '; - - - // 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 += ''; - buttons += ''; - } - 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 += '
Total: '+amount.toFixed(2)+' €
'; @@ -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 += '
Total: '+amountUKF+'
'; basketrel_html += '
Nouveau solde: '+newBalanceUKF+'
'; @@ -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 += '
Trigramme : '+trigramme+'
'; previousop_html += basketrel_container.html(); previousop_container.html(previousop_html); @@ -1357,14 +870,13 @@ $(document).ready(function() { var addcost_default_html = '
Majoration vers de
'; 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(); diff --git a/kfet/urls.py b/kfet/urls.py index 271ed917..e5536509 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -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\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 diff --git a/kfet/views.py b/kfet/views.py index c228500f..26ad88b4 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -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,19 +333,32 @@ def account_read(request, trigramme): and request.user != account.user: raise PermissionDenied - 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')) + 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') + ) return render(request, "kfet/account_read.html", { - 'account' : account, + 'account': account, 'addcosts': addcosts, - 'settings': { 'subvention_cof': Settings.SUBVENTION_COF() }, + '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) diff --git a/requirements.txt b/requirements.txt index ef2b3669..53132e50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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