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