Merge branch 'Aufinal/refactor_articles' into 'aureplop/kpsul_js_refactor'
Aufinal/refactor articles See merge request !173
This commit is contained in:
commit
6a8f41849b
5 changed files with 809 additions and 307 deletions
|
@ -306,30 +306,48 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
#articles_data {
|
||||
overflow:auto;
|
||||
max-height:500px;
|
||||
}
|
||||
|
||||
#articles_data table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#articles_data table tr.article {
|
||||
#articles_data div.article {
|
||||
height:25px;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
#articles_data table tr.article>td:first-child {
|
||||
padding-left:10px;
|
||||
#articles_data .article[data_stock="low"] {
|
||||
background:rgba(236,100,0,0.3);
|
||||
}
|
||||
|
||||
#articles_data table tr.category {
|
||||
#articles_data span {
|
||||
height:25px;
|
||||
line-height:25px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#articles_data span.name {
|
||||
padding-left:10px;
|
||||
width:78%;
|
||||
}
|
||||
|
||||
#articles_data span.price {
|
||||
width:8%;
|
||||
}
|
||||
|
||||
#articles_data span.stock {
|
||||
width:14%;
|
||||
}
|
||||
|
||||
|
||||
#articles_data div.category {
|
||||
height:35px;
|
||||
line-height:35px;
|
||||
background-color:#c8102e;
|
||||
font-size:16px;
|
||||
color:#FFF;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#articles_data table tr.category>td:first-child {
|
||||
#articles_data div.category>span:first-child {
|
||||
padding-left:20px;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@
|
|||
* 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.
|
||||
* A model to manage ModelObject forests
|
||||
* {@link Models.ModelForest}.<br>
|
||||
* A ModelObject that can be fetched through API
|
||||
* {@link Models.APIModelForest}.<br>
|
||||
* These classes should not be used directly.
|
||||
* <br><br>
|
||||
*
|
||||
* Models with API support:
|
||||
|
@ -20,7 +24,9 @@
|
|||
* {@link Models.Checkout} (partial).
|
||||
* <br>
|
||||
* Models without API support:
|
||||
* {@link Models.Statement}.
|
||||
* {@link Models.Statement},
|
||||
* {@link Models.ArticleCategory},
|
||||
* {@link Models.Article}.
|
||||
*
|
||||
* @namespace Models
|
||||
*/
|
||||
|
@ -113,6 +119,13 @@ class ModelObject {
|
|||
return $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string value for the model, to use in comparisons
|
||||
* @see {@link Models.TreeNode.compare|TreeNode.compare}
|
||||
*/
|
||||
comparevalue() {
|
||||
return this.id.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -428,6 +441,401 @@ class Statement extends ModelObject {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* ArticleCategory model. Cannot be accessed through API.
|
||||
* @extends Models.ModelObject
|
||||
* @memberof Models
|
||||
*/
|
||||
class ArticleCategory extends ModelObject {
|
||||
|
||||
/**
|
||||
* Properties associated to a category
|
||||
* @default <tt>['id', 'name']</tt>
|
||||
* @see {@link Models.ModelObject.props|ModelObject.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ['id', 'name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for ArticleCategory model instances.
|
||||
* @default <tt>{ 'id': 0, 'name': '' }</tt>
|
||||
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
|
||||
*/
|
||||
static get default_data() {
|
||||
return {'id': 0, 'name': ''};
|
||||
}
|
||||
|
||||
/**
|
||||
* @default {@link Formatters.ArticleCategoryFormatter}
|
||||
*/
|
||||
formatter() {
|
||||
return ArticleCategoryFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function between ArticleCategory model instances.
|
||||
* @see {@link Models.ModelObject.compare|ModelObject.compare}
|
||||
*/
|
||||
static compare(a, b) {
|
||||
return a.name.localeCompare(b.name) ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Article model. Cannot be accessed through API.
|
||||
* @extends Models.ModelObject
|
||||
* @memberof Models
|
||||
*/
|
||||
class Article extends ModelObject {
|
||||
/**
|
||||
* Properties associated to an article
|
||||
* @default <tt>['id', 'name']</tt>
|
||||
* @see {@link Models.ModelObject.props|ModelObject.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ['id', 'name', 'price', 'stock', 'category'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for Article model instances.
|
||||
* @default <tt>{ 'id': 0, 'name': '', 'price': 0, 'stock': 0,
|
||||
* 'category': new ArticleCategory() }</tt>
|
||||
* @see {@link Models.ModelObject.default_data|ModelObject.default_data}
|
||||
*/
|
||||
static get default_data() {
|
||||
return {
|
||||
'id': 0, 'name': '', 'price': 0, 'stock': 0,
|
||||
'category': new ArticleCategory(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @default {@link Formatters.ArticleFormatter}
|
||||
*/
|
||||
formatter() {
|
||||
return ArticleFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function between Article model instances.
|
||||
* @see {@link Models.ModelObject.compare|ModelObject.compare}
|
||||
*/
|
||||
static compare(a, b) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
// Take care of 'price' type
|
||||
// API currently returns a string object (serialization of Decimal type within Django)
|
||||
get price() { return this._price; }
|
||||
set price(v) { this._price = floatCheck(v); }
|
||||
|
||||
is_low_stock(nb) {
|
||||
return (-5 <= this.stock - nb && this.stock - nb <= 5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Node for ModelForest object
|
||||
* @memberof Models
|
||||
*/
|
||||
class TreeNode {
|
||||
|
||||
constructor(type, content) {
|
||||
this.modelname = type;
|
||||
this.content = content;
|
||||
this.parent = null;
|
||||
this.children = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple {@link Models.ModelObject} forest.
|
||||
* @memberof Models
|
||||
*/
|
||||
class ModelForest {
|
||||
|
||||
/**
|
||||
* Dictionary associating types to classes
|
||||
* @abstract
|
||||
* @type {Object}
|
||||
*/
|
||||
static get models() { return {}; }
|
||||
|
||||
/**
|
||||
* Comparison function for nodes
|
||||
* @abstract
|
||||
* @param {class} model Model to use for comparison
|
||||
* @param {Models.TreeNode} a
|
||||
* @param {Models.TreeNode} b
|
||||
* @see {@link Models.ModelObject.compare|ModelObject.compare}
|
||||
*/
|
||||
static compare(model, a, b) {
|
||||
return model.compare(a.content, b.content);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates empty instance and populates it with data if given
|
||||
* @param {Object[]} [datalist=[]]
|
||||
*/
|
||||
constructor(datalist) {
|
||||
this.from(datalist || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an object from the instance data, or creates it if
|
||||
* it does not exist yet.<br>
|
||||
* If direction >= 0, parent objects are created recursively.
|
||||
* If direction <= 0, child objects are created recursively.
|
||||
* @param {Object} data
|
||||
* @param {number} direction
|
||||
*/
|
||||
get_or_create(data, direction) {
|
||||
var model = this.constructor.models[data.modelname];
|
||||
|
||||
var existing = this.find_node(data.modelname, data.content.id);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
var content = new this.constructor.models[data.modelname](data.content);
|
||||
var node = new TreeNode(data.modelname, content);
|
||||
|
||||
if (data.child_sort)
|
||||
node.child_sort = data.child_sort
|
||||
|
||||
if (direction <= 0) {
|
||||
if (data.parent) {
|
||||
var parent = this.get_or_create(data.parent, -1);
|
||||
node.parent = parent;
|
||||
parent.children.push(node);
|
||||
} else {
|
||||
this.roots.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (direction >= 0 && data.children) {
|
||||
for (let child_data of data.children) {
|
||||
var child = this.get_or_create(child_data, 1);
|
||||
child.parent = node;
|
||||
node.children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return node ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets then populates the instance with the given data.
|
||||
* @param {Object[]} datalist
|
||||
*/
|
||||
from(datalist) {
|
||||
this.roots = [];
|
||||
for (let data of datalist) {
|
||||
this.get_or_create(data, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all Models.TreeNode from the tree.
|
||||
*/
|
||||
clear() {
|
||||
this.from([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a node (and all its offspring) and returns the
|
||||
* corresponding jQuery object.
|
||||
* @param {Models.TreeNode} node
|
||||
* @param {Object} templates Templates to render each model
|
||||
* @param {Object} [options] Options for element render method
|
||||
*/
|
||||
render_element(node, templates, options) {
|
||||
var template = templates[node.modelname];
|
||||
var options = options || {} ;
|
||||
|
||||
var $container = $('<div></div>');
|
||||
$container.attr('id', node.modelname+'-'+node.content.id);
|
||||
|
||||
var $rendered = node.content.display($(template), options);
|
||||
$container.append($rendered);
|
||||
|
||||
//dirty
|
||||
node.children.sort(this.constructor.compare.bind(null, this.constructor.models[node.child_sort]));
|
||||
|
||||
for (let child of node.children) {
|
||||
var $child = this.render_element(child, templates, options);
|
||||
$container.append($child);
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
add_to_container($container, node, templates, options) {
|
||||
var existing = node.parent ;
|
||||
var first_missing = node;
|
||||
|
||||
while (existing && !($container.find('#'+existing.modelname+'-'+existing.id))) {
|
||||
first_missing = existing ;
|
||||
existing = existing.parent;
|
||||
}
|
||||
|
||||
var $to_insert = render_element(first_missing, templates, options);
|
||||
var $insert_in = existing ? $container.find('#'+existing.modelname+'-'+existing.id)
|
||||
: $container ;
|
||||
$insert_in.prepend($to_insert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display stored data in container.
|
||||
* @param {jQuery} $container
|
||||
* @param {Object} templates Templates to render each model
|
||||
* @param {Object} [options] Options for element render method
|
||||
*/
|
||||
display($container, templates, options) {
|
||||
this.roots.sort(this.constructor.compare.bind(null, this.root_sort));
|
||||
for (let root of this.roots) {
|
||||
$container.append(this.render_element(root, templates, options));
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find if node already exists in given tree
|
||||
* @param {Models.TreeNode}
|
||||
*/
|
||||
find_node(modelname, id) {
|
||||
var result = null;
|
||||
|
||||
function recurse(node) {
|
||||
if (node.modelname === modelname && node.content.id === id)
|
||||
result = node;
|
||||
|
||||
for (let child of node.children)
|
||||
recurse(child);
|
||||
}
|
||||
|
||||
for (let root of this.roots)
|
||||
recurse(root);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs for each node (in a DFS order) the callback function
|
||||
* on node.content and node.parent.content, if node has given modelname.
|
||||
* @param {string} modelname
|
||||
* @param {function} callback
|
||||
*/
|
||||
traverse(modelname, callback) {
|
||||
function recurse(node) {
|
||||
if (node.modelname === modelname) {
|
||||
var parent = node.parent && node.parent.content || null;
|
||||
var children = node.children.map( (child) => child.content);
|
||||
callback(node.content, children, parent);
|
||||
}
|
||||
|
||||
for (let child of node.children)
|
||||
recurse(child);
|
||||
}
|
||||
|
||||
for (let root of this.roots)
|
||||
recurse(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find instance in tree with given type and id
|
||||
* @param {string} modelname
|
||||
* @param {number} id
|
||||
*/
|
||||
find(modelname, id) {
|
||||
var result = null;
|
||||
|
||||
function callback(content) {
|
||||
if (content.id == id)
|
||||
result = content ;
|
||||
}
|
||||
|
||||
this.traverse(modelname, callback);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Describes a model list that can be filled through API.
|
||||
* @extends Models.ModelForest
|
||||
* @memberof Models
|
||||
*/
|
||||
class APIModelForest extends ModelForest {
|
||||
|
||||
/**
|
||||
* Request url to fill the model.
|
||||
* @abstract
|
||||
* @type {string}
|
||||
*/
|
||||
static get url_model() {}
|
||||
|
||||
/**
|
||||
* Fills the instance with distant data. It sends a GET HTTP request to
|
||||
* {@link Models.APIModelForest#url_model}.
|
||||
* @param {object} [api_options] Additional data appended to the request.
|
||||
*/
|
||||
fromAPI(api_options) {
|
||||
api_options = api_options || {};
|
||||
|
||||
api_options['format'] = 'json';
|
||||
|
||||
return $.getJSON(this.constructor.url_model, api_options)
|
||||
.done( (json) => this.from(json) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ArticleList model. Can be accessed through API.
|
||||
* @extends Models.APIModelForest
|
||||
* @memberof Models
|
||||
*/
|
||||
class ArticleList extends APIModelForest {
|
||||
|
||||
/**
|
||||
* Default structure for ArticleList instances
|
||||
* @abstract
|
||||
* @default <tt>{'article': Article,
|
||||
'category': ArticleCategory}</tt>
|
||||
*/
|
||||
static get models() {
|
||||
return {'article': Article,
|
||||
'category': ArticleCategory};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default url to get ArticlList data
|
||||
* @abstract
|
||||
* @default <tt>django-js-reverse('kfet.kpsul.articles_data')</tt>
|
||||
* @see {@link Models.APIModelForest.url_model|APIModelForest.url_model}
|
||||
*/
|
||||
static get url_model() {
|
||||
return Urls['kfet.kpsul.articles_data']();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides model to sort root objects
|
||||
* {@see Models.ModelForest.constructor|ModelForest.constructor}
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.root_sort = ArticleCategory;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ---------- ---------- */
|
||||
|
||||
|
@ -662,3 +1070,58 @@ class StatementFormatter extends Formatter {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
* @extends Formatters.Formatter
|
||||
*/
|
||||
|
||||
class ArticleCategoryFormatter extends Formatter {
|
||||
|
||||
/**
|
||||
* Properties renderable to html.
|
||||
* @default {@link Models.ArticleCategory.props}
|
||||
*/
|
||||
static get props() {
|
||||
return ArticleCategory.props;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @memberof Formatters
|
||||
* @extends Formatters.Formatter
|
||||
*/
|
||||
|
||||
class ArticleFormatter extends Formatter {
|
||||
|
||||
/**
|
||||
* Properties renderable to html.
|
||||
* @default {@link Models.Article.props}
|
||||
*/
|
||||
static get props() {
|
||||
return Article.props;
|
||||
}
|
||||
|
||||
static get attrs() {
|
||||
return ['data_stock'];
|
||||
}
|
||||
|
||||
static prop_price(s) {
|
||||
return amountToUKF(s.price, true);
|
||||
}
|
||||
|
||||
static get _data_stock() {
|
||||
return {
|
||||
'default': '', 'low': 'low',
|
||||
'ok': 'ok', 'neg': 'neg',
|
||||
};
|
||||
}
|
||||
|
||||
static attr_data_stock(a) {
|
||||
if (a.stock > 5) { return this._data_stock.ok; }
|
||||
else if (a.stock >= -5) { return this._data_stock.low; }
|
||||
else /* a.stock < -5 */ { return this._data_stock.neg; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,18 +11,32 @@ class KPsulManager {
|
|||
this._env = env;
|
||||
this.account_manager = new AccountManager(this);
|
||||
this.checkout_manager = new CheckoutManager(this);
|
||||
this.article_manager = new ArticleManager(this);
|
||||
}
|
||||
|
||||
reset(soft) {
|
||||
soft = soft || false;
|
||||
|
||||
this.account_manager.reset();
|
||||
this.article_manager.reset();
|
||||
|
||||
if (!soft) {
|
||||
this.checkout_manager.reset();
|
||||
this.article_manager.reset_data();
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.checkout_manager.is_empty())
|
||||
this.checkout_manager.focus();
|
||||
else if (this.account_manager.is_empty())
|
||||
this.account_manager.focus();
|
||||
else
|
||||
this.article_manager.focus();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -96,7 +110,7 @@ class AccountManager {
|
|||
$('#id_on_acc').val(this.account.id);
|
||||
this.display();
|
||||
|
||||
kpsul._env.articleSelect.focus();
|
||||
kpsul.focus();
|
||||
kpsul._env.updateBasketAmount();
|
||||
kpsul._env.updateBasketRel();
|
||||
}
|
||||
|
@ -282,11 +296,7 @@ class CheckoutManager {
|
|||
.done( (data) => this._update_on_success(data) )
|
||||
.fail( () => this.reset_data() );
|
||||
|
||||
if (kpsul.account_manager.is_empty()) {
|
||||
kpsul.account_manager.focus();
|
||||
} else {
|
||||
kpsul._env.articleSelect.focus().select();
|
||||
}
|
||||
kpsul.focus();
|
||||
}
|
||||
|
||||
_update_on_success(data) {
|
||||
|
@ -351,6 +361,11 @@ class CheckoutManager {
|
|||
|
||||
this.display();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.selection.focus();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -383,5 +398,249 @@ class CheckoutSelection {
|
|||
reset() {
|
||||
this._$input.find('option:first').prop('selected', true);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._$input.focus();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleManager {
|
||||
|
||||
constructor(env) {
|
||||
this._env = env; // Global K-Psul Manager
|
||||
|
||||
this._$container = $('#articles_data');
|
||||
this._$input = $('#article_autocomplete');
|
||||
this._$nb = $('#article_number');
|
||||
this._$stock = $('#article_stock');
|
||||
this.templates = {'category': '<div class="category"><span class="name"></span></div>',
|
||||
'article' : '<div class="article"><span class="name"></span><span class="price"></span><span class="stock"></span></div>'}
|
||||
|
||||
this.selected = new Article() ;
|
||||
this.list = new ArticleList() ;
|
||||
this.autocomplete = new ArticleAutocomplete(this);
|
||||
|
||||
this._init_events();
|
||||
}
|
||||
|
||||
get nb() {
|
||||
return this._$nb.val() ;
|
||||
}
|
||||
|
||||
display_list() {
|
||||
this.list.display(this._$container, this.templates) ;
|
||||
}
|
||||
|
||||
validate(article) {
|
||||
this.selected.from(article) ;
|
||||
this._$input.val(article.name);
|
||||
this._$nb.val('1');
|
||||
this._$stock.text('/'+article.stock);
|
||||
this._$nb.focus().select();
|
||||
}
|
||||
|
||||
unset() {
|
||||
this.selected.clear();
|
||||
}
|
||||
|
||||
is_empty() {
|
||||
return this.selected.is_empty();
|
||||
}
|
||||
|
||||
reset_data() {
|
||||
this._$container.html('');
|
||||
this.list.clear();
|
||||
this.list.fromAPI()
|
||||
.done( () => this.display_list() );
|
||||
}
|
||||
|
||||
get_article(id) {
|
||||
return this.list.find('article', id);
|
||||
}
|
||||
|
||||
update_data(data) {
|
||||
for (let article_dict of data.articles) {
|
||||
var article = this.get_article(article_dict.id);
|
||||
|
||||
// For now, article additions are disregarded
|
||||
if (article) {
|
||||
article.stock = article_dict.stock;
|
||||
this._$container.find('#article-'+article.id+' .stock')
|
||||
.text(article.stock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.unset() ;
|
||||
this._$stock.text('');
|
||||
this._$nb.val('');
|
||||
this._$input.val('');
|
||||
this.autocomplete.showAll() ;
|
||||
}
|
||||
|
||||
_init_events() {
|
||||
var that = this;
|
||||
|
||||
// 8:Backspace|9:Tab|13:Enter|46:DEL|112-117:F1-6|119-123:F8-F12
|
||||
var normalKeys = /^(8|9|13|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
var arrowKeys = /^(37|38|39|40)$/;
|
||||
|
||||
//Global input event (to merge ?)
|
||||
this._$input.on('keydown', function(e) {
|
||||
if (e.keyCode == 13 && that._$input.val() == '') {
|
||||
kpsul._env.performOperations();
|
||||
}
|
||||
});
|
||||
|
||||
this._$container.on('click', '.article', function() {
|
||||
var id = $(this).parent().attr('id').split('-')[1];
|
||||
var article = that.list.find('article', id);
|
||||
if (article)
|
||||
that.validate(article);
|
||||
});
|
||||
|
||||
this._$nb.on('keydown', function(e) {
|
||||
if (e.keyCode == 13 && that.constructor.check_nb(that.nb) && !that.is_empty()) {
|
||||
kpsul._env.addPurchase(that.selected, that.nb);
|
||||
that.reset();
|
||||
that.focus();
|
||||
}
|
||||
if (normalKeys.test(e.keyCode) || arrowKeys.test(e.KeyCode) || e.ctrlKey) {
|
||||
if (e.ctrlKey && e.charCode == 65)
|
||||
that._$nb.val('');
|
||||
return true ;
|
||||
}
|
||||
if (that.constructor.check_nb(that.nb+e.key))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.is_empty())
|
||||
this._$input.focus();
|
||||
else
|
||||
this._$nb.focus();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static check_nb(nb) {
|
||||
return /^[0-9]+$/.test(nb) && nb > 0 && nb <= 24 ;
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleAutocomplete {
|
||||
|
||||
constructor(article_manager) {
|
||||
this.manager = article_manager;
|
||||
this._$container = article_manager._$container ;
|
||||
this._$input = $('#article_autocomplete');
|
||||
|
||||
this.showAll() ;
|
||||
this._init_events();
|
||||
}
|
||||
|
||||
_init_events() {
|
||||
var that = this ;
|
||||
|
||||
// 8:Backspace|9:Tab|13:Enter|46:DEL|112-117:F1-6|119-123:F8-F12
|
||||
var normalKeys = /^(8|9|13|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
|
||||
this._$input
|
||||
.on('keydown', function(e) {
|
||||
var text = that._$input.val() ;
|
||||
if (normalKeys.test(e.keyCode) || e.ctrlKey) {
|
||||
// For the backspace key, we suppose the cursor is at the very end
|
||||
if(e.keyCode == 8) {
|
||||
that.update(text.substring(0, text.length-1), true);
|
||||
}
|
||||
return true ;
|
||||
}
|
||||
that.update(text+e.key, false);
|
||||
|
||||
return false ;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
update(prefix, backspace) {
|
||||
|
||||
this.resetMatch();
|
||||
var article_list = this.manager.list ;
|
||||
var lower = prefix.toLowerCase() ;
|
||||
var that = this ;
|
||||
|
||||
article_list.traverse('article', function(article) {
|
||||
if (article.name.toLowerCase().startsWith(lower))
|
||||
that.matching.push(article);
|
||||
});
|
||||
|
||||
if (this.matching.length == 1) {
|
||||
if (!backspace) {
|
||||
this.manager.validate(this.matching[0]) ;
|
||||
this.showAll() ;
|
||||
} else {
|
||||
this.manager.unset();
|
||||
this.updateDisplay();
|
||||
}
|
||||
} else if (this.matching.length > 1) {
|
||||
this.manager.unset();
|
||||
this.updateDisplay() ;
|
||||
if (!backspace)
|
||||
this.updatePrefix();
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
var that = this;
|
||||
|
||||
this.manager.list.traverse('category', function(category, articles) {
|
||||
var is_active = false;
|
||||
for (let article of articles) {
|
||||
if (that.matching.indexOf(article) != -1) {
|
||||
is_active = true;
|
||||
that._$container.find('#article-'+article.id).show();
|
||||
} else {
|
||||
that._$container.find('#article-'+article.id).hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_active) {
|
||||
that._$container.find('#category-'+category.id).show();
|
||||
} else {
|
||||
that._$container.find('#category-'+category.id).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updatePrefix() {
|
||||
var lower = this.matching.map(function (article) {
|
||||
return article.name.toLowerCase() ;
|
||||
});
|
||||
|
||||
lower.sort() ;
|
||||
var first = lower[0], last = lower[lower.length-1],
|
||||
length = first.length, i = 0;
|
||||
while (i < length && first.charAt(i) === last.charAt(i)) i++;
|
||||
|
||||
this._$input.val(first.substring(0,i)) ;
|
||||
}
|
||||
|
||||
showAll() {
|
||||
var that = this;
|
||||
this.resetMatch();
|
||||
this.manager.list.traverse('article', function(article) {
|
||||
that.matching.push(article);
|
||||
});
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
resetMatch() {
|
||||
this.matching = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,13 +128,8 @@
|
|||
<input type="text" id="article_autocomplete" autocomplete="off">
|
||||
<input type="number" id="article_number" step="1" min="1">
|
||||
<span type="stock" id="article_stock"></span>
|
||||
<input type="hidden" id="article_id" value="">
|
||||
</div>
|
||||
<div id="articles_data">
|
||||
<table>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row kpsul_middle_left_bottom">
|
||||
|
@ -196,7 +191,7 @@ $(document).ready(function() {
|
|||
|
||||
commentDialog.open({
|
||||
callback: confirm_callback,
|
||||
next_focus: articleSelect
|
||||
next_focus: kpsul,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -242,7 +237,7 @@ $(document).ready(function() {
|
|||
else
|
||||
displayErrors(getErrorsHtml(response));
|
||||
},
|
||||
next_focus: articleSelect,
|
||||
next_focus: kpsul,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -278,221 +273,6 @@ $(document).ready(function() {
|
|||
cancelOperations();
|
||||
});
|
||||
|
||||
// -----
|
||||
// Articles data
|
||||
// -----
|
||||
|
||||
var articles_container = $('#articles_data tbody');
|
||||
var article_category_default_html = '<tr class="category"><td colspan="3"></td></tr>';
|
||||
var article_default_html = '<tr class="article"><td class="name"></td><td class="price"></td><td class="stock"></td></tr>';
|
||||
|
||||
function addArticle(article) {
|
||||
var article_html = $(article_default_html);
|
||||
article_html.attr('id', 'data-article-'+article['id']);
|
||||
article_html.addClass('data-category-'+article['category_id']);
|
||||
for (var elem in article) {
|
||||
article_html.find('.'+elem).text(article[elem])
|
||||
}
|
||||
if (-5 <= article['stock'] && article['stock'] <= 5) {
|
||||
article_html.addClass('low-stock');
|
||||
}
|
||||
article_html.find('.price').text(amountToUKF(article['price'], false, false)+' UKF');
|
||||
var category_html = articles_container
|
||||
.find('#data-category-'+article['category_id']);
|
||||
if (category_html.length == 0) {
|
||||
category_html = $(article_category_default_html);
|
||||
category_html.attr('id', 'data-category-'+article['category_id']);
|
||||
category_html.find('td').text(article['category__name']);
|
||||
var added = false;
|
||||
articles_container.find('.category').each(function() {
|
||||
if (article['category__name'].toLowerCase() < $(this).text().toLowerCase()) {
|
||||
$(this).before(category_html);
|
||||
added = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!added) articles_container.append(category_html);
|
||||
}
|
||||
var $after = articles_container.find('#data-category-'+article['category_id']);
|
||||
articles_container
|
||||
.find('.article.data-category-'+article['category_id']).each(function() {
|
||||
if (article['name'].toLowerCase < $('.name', this).text().toLowerCase())
|
||||
return false;
|
||||
$after = $(this);
|
||||
});
|
||||
$after.after(article_html);
|
||||
// Pour l'autocomplétion
|
||||
articlesList.push([article['name'],article['id'],article['category_id'],article['price'], article['stock']]);
|
||||
}
|
||||
|
||||
function getArticles() {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.kpsul.articles_data' %}",
|
||||
method : "GET",
|
||||
})
|
||||
.done(function(data) {
|
||||
for (var i=0; i<data['articles'].length; i++) {
|
||||
addArticle(data['articles'][i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetArticles() {
|
||||
articles_container.find('tr').remove();
|
||||
articlesList = [];
|
||||
}
|
||||
|
||||
// -----
|
||||
// Article selection
|
||||
// -----
|
||||
|
||||
var articleSelect = $('#article_autocomplete');
|
||||
var articleId = $('#article_id');
|
||||
var articleNb = $('#article_number');
|
||||
var articleStock = $('#article_stock');
|
||||
// 8:Backspace|9:Tab|13:Enter|38-40:Arrows|46:DEL|112-117:F1-6|119-123:F8-F12
|
||||
var normalKeys = /^(8|9|13|37|38|39|40|46|112|113|114|115|116|117|119|120|121|122|123)$/;
|
||||
var articlesList = [];
|
||||
|
||||
function deleteNonMatching(array, str) {
|
||||
var dup = [];
|
||||
var lower_str = str.toLowerCase();
|
||||
for (var i=0; i<array.length; i++) {
|
||||
if (((array[i][0]).toLowerCase()).indexOf(lower_str) === 0)
|
||||
dup.push(array[i])
|
||||
}
|
||||
return dup;
|
||||
}
|
||||
|
||||
function callbackForPrefix(elt) {
|
||||
return elt[0].toLowerCase();
|
||||
}
|
||||
|
||||
function sharedPrefix(array) {
|
||||
var dup = array.map(callbackForPrefix);
|
||||
dup.sort(); // On trie l'array
|
||||
// On récupère le préfixe du premier et du dernier élément
|
||||
var first = dup[0], last = dup[array.length-1],
|
||||
length = first.length, i = 0;
|
||||
while (i < length && first.charAt(i) === last.charAt(i)) i++;
|
||||
return first.substring(0, i);
|
||||
}
|
||||
|
||||
function displayMatchedArticles(array) {
|
||||
var categories_to_display = [];
|
||||
for (var i=0; i<articlesList.length; i++) {
|
||||
if (array.indexOf(articlesList[i]) > -1) {
|
||||
articles_container.find('#data-article-'+articlesList[i][1]).show();
|
||||
if (categories_to_display.indexOf(articlesList[i][2]) == -1)
|
||||
categories_to_display.push(articlesList[i][2]);
|
||||
} else {
|
||||
articles_container.find('#data-article-'+articlesList[i][1]).hide();
|
||||
}
|
||||
}
|
||||
articles_container.find('.category').hide();
|
||||
for (var i=0; i<categories_to_display.length; i++) {
|
||||
articles_container
|
||||
.find('#data-category-'+categories_to_display[i])
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
function updateMatchedArticles(str, commit = true) {
|
||||
var lower_str = str.toLowerCase();
|
||||
var articlesMatch = deleteNonMatching(articlesList, lower_str);
|
||||
|
||||
if (articlesMatch.length == 1) {
|
||||
articleId.val(0);
|
||||
// 1 seul résultat, victoire
|
||||
if (commit) {
|
||||
articleId.val(articlesMatch[0][1]);
|
||||
articleSelect.val(articlesMatch[0][0]);
|
||||
articleStock.text('/'+articlesMatch[0][4]);
|
||||
displayMatchedArticles(articlesList);
|
||||
return true;
|
||||
}
|
||||
displayMatchedArticles(articlesMatch);
|
||||
} else if (articlesMatch.length > 1) {
|
||||
articleId.val(0);
|
||||
if (commit)
|
||||
articleSelect.val(sharedPrefix(articlesMatch));
|
||||
displayMatchedArticles(articlesMatch);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// A utiliser après la sélection d'un article
|
||||
function goToArticleNb() {
|
||||
articleNb.val('1');
|
||||
articleNb.focus().select();
|
||||
}
|
||||
|
||||
articleSelect.on('keydown', function(e) {
|
||||
var text = articleSelect.val();
|
||||
// Comportement normal pour ces touches
|
||||
if (normalKeys.test(e.keyCode) || e.ctrlKey) {
|
||||
if (text == '' && e.keyCode == 13)
|
||||
performOperations();
|
||||
if (e.keyCode == 8)
|
||||
updateMatchedArticles(text.substring(0,text.length-1), false);
|
||||
if (e.charCode == 65 && e.ctrlKey) {
|
||||
articleId.val(0);
|
||||
articleSelect.val('');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (updateMatchedArticles(text+e.key))
|
||||
goToArticleNb();
|
||||
return false;
|
||||
});
|
||||
|
||||
function getArticleId($article) {
|
||||
return $article.attr('id').split('-')[2];
|
||||
}
|
||||
|
||||
function getArticleName($article) {
|
||||
return $article.find('.name').text();
|
||||
}
|
||||
|
||||
function getArticleStock($article) {
|
||||
return $article.find('.stock').text();
|
||||
}
|
||||
|
||||
// Sélection des articles à la souris/tactile
|
||||
articles_container.on('click', '.article', function() {
|
||||
articleId.val(getArticleId($(this)));
|
||||
articleSelect.val(getArticleName($(this)));
|
||||
articleStock.text('/'+getArticleStock($(this)));
|
||||
displayMatchedArticles(articlesList);
|
||||
goToArticleNb();
|
||||
});
|
||||
|
||||
function is_nb_ok(nb) {
|
||||
return /^[0-9]+$/.test(nb) && nb > 0 && nb <= 24;
|
||||
}
|
||||
|
||||
articleNb.on('keydown', function(e) {
|
||||
if (e.keyCode == 13 && is_nb_ok(articleNb.val()) && articleId.val() > 0) {
|
||||
addPurchase(articleId.val(), articleNb.val());
|
||||
articleSelect.val('');
|
||||
articleNb.val('');
|
||||
articleStock.text('');
|
||||
articleSelect.focus();
|
||||
displayMatchedArticles(articlesList);
|
||||
return false;
|
||||
}
|
||||
if (normalKeys.test(e.keyCode) || arrowKeys.test(e.keyCode) || e.keyCode == 8 || e.ctrlKey) {
|
||||
if (e.ctrlKey && e.charCode == 65)
|
||||
articleNb.val('');
|
||||
return true;
|
||||
}
|
||||
var nb = articleNb.val()+e.key;
|
||||
if (is_nb_ok(nb))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// -----
|
||||
// Basket
|
||||
// -----
|
||||
|
@ -501,11 +281,8 @@ $(document).ready(function() {
|
|||
var basket_container = $('#basket table');
|
||||
var arrowKeys = /^(37|38|39|40)$/;
|
||||
|
||||
function amountEuroPurchase(id, nb) {
|
||||
var i = 0;
|
||||
while (i<articlesList.length && id != articlesList[i][1]) i++;
|
||||
var article_data = articlesList[i];
|
||||
var amount_euro = - article_data[3] * nb ;
|
||||
function amountEuroPurchase(article, nb) {
|
||||
var amount_euro = - article.price * nb ;
|
||||
if (Config.get('addcost_for') && Config.get('addcost_amount') && kpsul.account_manager.account.trigramme != Config.get('addcost_for'))
|
||||
amount_euro -= Config.get('addcost_amount') * nb;
|
||||
var reduc_divisor = 1;
|
||||
|
@ -514,45 +291,34 @@ $(document).ready(function() {
|
|||
return amount_euro / reduc_divisor;
|
||||
}
|
||||
|
||||
function addPurchase(id, nb) {
|
||||
var i = 0;
|
||||
while (i<articlesList.length && id != articlesList[i][1]) i++;
|
||||
var article_data = articlesList[i];
|
||||
function addPurchase(article, nb) {
|
||||
|
||||
var existing = false;
|
||||
formset_container.find('[data-opeindex]').each(function () {
|
||||
var opeindex = $(this).attr('data-opeindex');
|
||||
var article_id = $(this).find('#id_form-'+opeindex+'-article').val();
|
||||
if (article_id == id) {
|
||||
if (article_id == article.id) {
|
||||
existing = true ;
|
||||
addExistingPurchase(opeindex, nb);
|
||||
}
|
||||
});
|
||||
if (!existing) {
|
||||
var amount_euro = amountEuroPurchase(id, nb).toFixed(2);
|
||||
var index = addPurchaseToFormset(article_data[1], nb, amount_euro);
|
||||
var amount_euro = amountEuroPurchase(article, nb).toFixed(2);
|
||||
var index = addPurchaseToFormset(article.id, nb, amount_euro);
|
||||
var article_basket_html = $(item_basket_default_html);
|
||||
article_basket_html
|
||||
.attr('data-opeindex', index)
|
||||
.find('.number').text('('+nb+'/'+article_data[4]+')').end()
|
||||
.find('.name').text(article_data[0]).end()
|
||||
.find('.amount').text(amountToUKF(amount_euro, kpsul.account_manager.account.is_cof, false));
|
||||
.find('.number').text(nb).end()
|
||||
.find('.name').text(article.name).end()
|
||||
.find('.amount').text(amountToUKF(amount_euro, kpsul.account_manager.account.is_cof));
|
||||
basket_container.prepend(article_basket_html);
|
||||
if (is_low_stock(id, nb))
|
||||
if (article.is_low_stock(nb))
|
||||
article_basket_html.find('.lowstock')
|
||||
.show();
|
||||
updateBasketRel();
|
||||
}
|
||||
}
|
||||
|
||||
function is_low_stock(id, nb) {
|
||||
var i = 0 ;
|
||||
while (i<articlesList.length && id != articlesList[i][1]) i++;
|
||||
var article_data = articlesList[i];
|
||||
|
||||
var stock = article_data[4] ;
|
||||
return (-5 <= stock - nb && stock - nb <= 5);
|
||||
}
|
||||
|
||||
function addDeposit(amount) {
|
||||
var deposit_basket_html = $(item_basket_default_html);
|
||||
var amount = parseFloat(amount).toFixed(2);
|
||||
|
@ -685,15 +451,12 @@ $(document).ready(function() {
|
|||
function addExistingPurchase(opeindex, nb) {
|
||||
var type = formset_container.find("#id_form-"+opeindex+"-type").val();
|
||||
var id = formset_container.find("#id_form-"+opeindex+"-article").val();
|
||||
var article = kpsul.article_manager.get_article(parseInt(id));
|
||||
var nb_before = formset_container.find("#id_form-"+opeindex+"-article_nb").val();
|
||||
var nb_after = parseInt(nb_before) + parseInt(nb);
|
||||
var amountEuro_after = amountEuroPurchase(id, nb_after);
|
||||
var amountEuro_after = amountEuroPurchase(article, nb_after);
|
||||
var amountUKF_after = amountToUKF(amountEuro_after, kpsul.account_manager.account.is_cof, false);
|
||||
|
||||
var i = 0;
|
||||
while (i<articlesList.length && id != articlesList[i][1]) i++;
|
||||
var article_data = articlesList[i];
|
||||
|
||||
if (type == 'purchase') {
|
||||
if (nb_after == 0) {
|
||||
deleteFromBasket(opeindex);
|
||||
|
@ -701,20 +464,20 @@ $(document).ready(function() {
|
|||
if (nb_before > 0) {
|
||||
var article_html = basket_container.find('[data-opeindex='+opeindex+']');
|
||||
article_html.find('.amount').text(amountUKF_after).end()
|
||||
.find('.number').text('('+nb_after+'/'+article_data[4]+')').end() ;
|
||||
.find('.number').text('('+nb_after+'/'+article.stock+')').end() ;
|
||||
|
||||
} else {
|
||||
article_html = $(item_basket_default_html);
|
||||
article_html
|
||||
.attr('data-opeindex', opeindex)
|
||||
.find('.number').text('('+nb_after+'/'+article_data[4]+')').end()
|
||||
.find('.name').text(article_data[0]).end()
|
||||
.find('.number').text('('+nb_after+'/'+article.stock+')').end()
|
||||
.find('.name').text(article.name).end()
|
||||
.find('.amount').text(amountUKF_after);
|
||||
basket_container.prepend(article_basket_html);
|
||||
|
||||
}
|
||||
|
||||
if (is_low_stock(id, nb_after))
|
||||
if (article.is_low_stock(nb_after))
|
||||
article_html.find('.lowstock')
|
||||
.show();
|
||||
else
|
||||
|
@ -732,11 +495,7 @@ $(document).ready(function() {
|
|||
mngmt_total_forms_input.val(1);
|
||||
formset_container.find('div').remove();
|
||||
updateBasketRel();
|
||||
articleId.val(0);
|
||||
articleSelect.val('');
|
||||
articleNb.val('');
|
||||
articleStock.text('');
|
||||
displayMatchedArticles(articlesList);
|
||||
kpsul.article_manager.reset();
|
||||
}
|
||||
|
||||
// -----
|
||||
|
@ -755,11 +514,9 @@ $(document).ready(function() {
|
|||
addDeposit(amount);
|
||||
}
|
||||
|
||||
var next_focus = articleSelect.val() ? articleNb : articleSelect ;
|
||||
|
||||
depositDialog.open({
|
||||
callback: callback,
|
||||
next_focus: next_focus,
|
||||
next_focus: kpsul.article_manager,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -775,11 +532,9 @@ $(document).ready(function() {
|
|||
addEdit(amount);
|
||||
}
|
||||
|
||||
var next_focus = articleSelect.val() ? articleNb : articleSelect ;
|
||||
|
||||
editDialog.open({
|
||||
callback: callback,
|
||||
next_focus: next_focus,
|
||||
next_focus: kpsul.article_manager,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -795,11 +550,9 @@ $(document).ready(function() {
|
|||
addWithdraw(amount);
|
||||
}
|
||||
|
||||
var next_focus = articleSelect.val() ? articleNb : articleSelect ;
|
||||
|
||||
withdrawDialog.open({
|
||||
callback: callback,
|
||||
next_focus: next_focus,
|
||||
next_focus: kpsul.article_manager,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1013,21 +766,9 @@ $(document).ready(function() {
|
|||
displayCheckoutData();
|
||||
}
|
||||
}
|
||||
for (var i=0; i<data['articles'].length; i++) {
|
||||
var article = data['articles'][i];
|
||||
var article_line = articles_container.find('#data-article-'+article.id);
|
||||
if (article.stock <= 5 && article.stock >= -5) {
|
||||
article_line.addClass('low-stock');
|
||||
} else {
|
||||
article_line.removeClass('low-stock');
|
||||
}
|
||||
article_line.find('.stock')
|
||||
.text(article['stock']);
|
||||
|
||||
var i = 0;
|
||||
while (i < articlesList.length && articlesList[i][1] != article.id) i++ ;
|
||||
articlesList[i][4] = article.stock ;
|
||||
}
|
||||
kpsul.article_manager.update_data(data);
|
||||
|
||||
if (data['addcost']) {
|
||||
Config.set('addcost_for', data['addcost']['for']);
|
||||
Config.set('addcost_amount', data['addcost']['amount']);
|
||||
|
@ -1053,12 +794,11 @@ $(document).ready(function() {
|
|||
function hardReset(give_tri_focus=true) {
|
||||
coolReset(give_tri_focus);
|
||||
kpsul.checkout_manager.reset();
|
||||
resetArticles();
|
||||
resetPreviousOp();
|
||||
khistory.reset();
|
||||
Config.reset(function() {
|
||||
kpsul.article_manager.reset_data();
|
||||
displayAddcost();
|
||||
getArticles();
|
||||
getHistory();
|
||||
});
|
||||
}
|
||||
|
@ -1103,7 +843,7 @@ $(document).ready(function() {
|
|||
} else {
|
||||
// F2 - Basket reset
|
||||
resetBasket();
|
||||
articleSelect.focus();
|
||||
kpsul.article_manager.focus();
|
||||
}
|
||||
return false;
|
||||
case 114:
|
||||
|
@ -1135,9 +875,10 @@ $(document).ready(function() {
|
|||
// -----
|
||||
|
||||
var env = {
|
||||
articleSelect: articleSelect,
|
||||
addPurchase: addPurchase,
|
||||
updateBasketAmount: updateBasketAmount,
|
||||
updateBasketRel: updateBasketRel,
|
||||
performOperations: performOperations,
|
||||
};
|
||||
|
||||
window.kpsul = new KPsulManager(env);
|
||||
|
|
|
@ -1419,13 +1419,34 @@ def history_json(request):
|
|||
opegroups_list.append(opegroup_dict)
|
||||
return JsonResponse({ 'opegroups': opegroups_list })
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_articles_data(request):
|
||||
articles = (
|
||||
Article.objects
|
||||
.values('id', 'name', 'price', 'stock', 'category_id', 'category__name')
|
||||
.filter(is_sold=True))
|
||||
return JsonResponse({ 'articles': list(articles) })
|
||||
.filter(is_sold=True)
|
||||
.select_related('category'))
|
||||
articlelist = []
|
||||
|
||||
for article in articles:
|
||||
articlelist.append({
|
||||
'modelname': 'article',
|
||||
'content': {
|
||||
'id': article.id,
|
||||
'name': article.name,
|
||||
'price': article.price,
|
||||
'stock': article.stock,
|
||||
},
|
||||
'parent': {
|
||||
'modelname': 'category',
|
||||
'content': {
|
||||
'id': article.category.id,
|
||||
'name': article.category.name,
|
||||
},
|
||||
'child_sort': 'article',
|
||||
}
|
||||
})
|
||||
return JsonResponse(articlelist, safe=False)
|
||||
|
||||
@teamkfet_required
|
||||
def history(request):
|
||||
|
|
Loading…
Add table
Reference in a new issue