Newer
Older
/**
* @module package/quiqqer/erp/bin/backend/controls/articles/ArticleList
* @author www.pcsg.de (Henning Leutz)
*
* Article list (Produkte Positionen)
*
* @event onCalc [self, {Object} calculation]
* @event onArticleSelect [self, {Object} Article]
* @event onArticleUnSelect [self, {Object} Article]
* @event onArticleReplaceClick [self, {Object} Article]
*/
define('package/quiqqer/erp/bin/backend/controls/articles/ArticleList', [
'qui/QUI',
'qui/controls/Control',
'package/quiqqer/erp/bin/backend/controls/articles/product/AddProductWindow',
'package/quiqqer/erp/bin/backend/controls/articles/Article',
'package/quiqqer/erp/bin/backend/classes/Sortable',
'text!package/quiqqer/erp/bin/backend/controls/articles/ArticleList.html',
'text!package/quiqqer/erp/bin/backend/controls/articles/ArticleList.sortablePlaceholder.html',
'css!package/quiqqer/erp/bin/backend/controls/articles/ArticleList.css'
], function (QUI, QUIControl, QUISwitch, Mustache,
QUIAjax, QUILocale, AddProductWindow, Article, Sortables, template, templateSortablePlaceholder) {
const lg = 'quiqqer/erp';
return new Class({
Extends: QUIControl,
Type : 'package/quiqqer/erp/bin/backend/controls/articles/ArticleList',
Binds: [
'$onArticleDelete',
'$onArticleSelect',
'$onArticleUnSelect',
'$onArticleReplace',
'$onArticleCalc',

Patrick Müller
committed
'$refreshNettoBruttoDisplay',
'$getArticleDataForCalculation',
'$onArticleEditCustomFields'
currency : false, // bool || string -> EUR, USD ...
nettoinput: true
},
initialize: function (options) {
this.parent(options);
this.$articles = [];
this.$isIncalculationFrame = false;
this.$calculations = {
currencyData: {},
isEuVat : 0,
isNetto : true,
nettoSubSum : 0,
nettoSum : 0,
subSum : 0,
sum : 0,
vatArray : [],
vatText : []
};
this.$Container = null;
this.$Sortables = null;
this.addEvents({
onInject: this.$onInject
});
},
/**
* Create the DOMNode element
*
* @returns {HTMLDivElement}
*/
create: function () {
this.$Elm.addClass('quiqqer-erp-backend-erpItems');
this.$Elm.set('data-qui', 'package/quiqqer/erp/bin/backend/controls/articles/ArticleList');
this.$Elm.set({
html: Mustache.render(template, {
titleArticleNo : QUILocale.get(lg, 'products.articleNo'),
titleDescription : QUILocale.get(lg, 'products.description'),
titleQuantity : QUILocale.get(lg, 'products.quantity'),
titleQuantityUnit: QUILocale.get(lg, 'products.quantityUnit'),
titleUnitPrice : QUILocale.get(lg, 'products.unitPrice'),
titlePrice : QUILocale.get(lg, 'products.price'),
titleVAT : QUILocale.get(lg, 'products.table.vat'),
titleDiscount : QUILocale.get(lg, 'products.discount'),
titleSum : QUILocale.get(lg, 'products.sum')
})
});
if (this.getAttribute('styles')) {
this.setStyles(this.getAttribute('styles'));
}
this.$Container = this.$Elm.getElement('.quiqqer-erp-backend-erpItems-items');
const SwitchDesc = this.$Elm.getElement('.quiqqer-erp-backend-erpItems-container-switch-desc');
this.$Switch = new QUISwitch({
switchTextOn : 'netto',
switchTextOnIcon : false,
switchTextOff : 'brutto',
switchTextOffIcon: false,
events : {
onChange: function () {
self.setAttribute('nettoinput', !!self.$Switch.getStatus());
self.$refreshNettoBruttoDisplay();
}
}
}).inject(
this.$Elm.getElement('.quiqqer-erp-backend-erpItems-container-switch-btn')
);
SwitchDesc.addEvent('click', function () {
self.$Switch.toggle();
});
this.$refreshNettoBruttoDisplay();
return this.$Elm;
},
/**
* event: on inject
*/
$onInject: function () {
(function () {
if (this.$articles.length) {
this.$articles[0].select();
}
}).delay(500, this);
},
/**
* Serialize the list
*
* @returns {Object}
*/
serialize: function () {
const articles = this.$getArticles().map(function (Article) {
const attr = Article.getAttributes();
attr.control = typeOf(Article);
return attr;
});
return {
articles : articles,
priceFactors: this.$priceFactors
};
},

Patrick Müller
committed
/**
* Get internal article list
*
* @return {[]}
*/
$getArticles: function () {
return this.$articles;
},
/**
* Return the articles count
*
* @returns {number}
*/
count: function () {
return this.$articles.length;
},
/**
* Unserialize the list
*
* load the serialized list into
* current articles would be deleted
*
* @param {Object|String} list
* @return {Promise}
*/
unserialize: function (list) {
const self = this;
let data = {};
if (typeOf(list) === 'string') {
try {
data = JSON.stringify(list);
} catch (e) {
}
} else {
data = list;
}
if ("priceFactors" in data) {
this.$priceFactors = data.priceFactors;
}
if (!("articles" in data)) {
return Promise.resolve();
}
this.$articles = [];
const controls = data.articles.map(function (Article) {
if (typeof Article.control !== 'undefined' && Article.control !== '') {
return Article.control;
}
return 'package/quiqqer/erp/bin/backend/controls/articles/Article';
}).unique();
require(controls, function () {
let i, len, article, index;
for (i = 0, len = data.articles.length; i < len; i++) {
article = data.articles[i];
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
if (index === -1) {
self.addArticle(new Article(article));
continue;
}
try {
self.addArticle(new arguments[index](article));
} catch (e) {
console.log(e);
}
}
});
},
/**
* Set user details to the list
*
* @param {Object} user
*/
setUser: function (user) {
this.$user = user;
this.$articles.each(function (Article) {
Article.setUser(this.$user);
}.bind(this));
},
/**
* Return the user details
*
* @return {Object|*|{}}
*/
getUser: function () {
return this.$user;
},
/**
* Add a product to the list
* The product must be an instance of Article
*
* @param {Object} Child
*/
addArticle: function (Child) {
if (typeof Child !== 'object') {
return;
}
if (!(Child instanceof Article)) {
return;
}
if (this.getAttribute('currency')) {
Child.setCurrency(this.getAttribute('currency'));
}
this.$articles.push(Child);
Child.setUser(this.$user);
Child.setPosition(this.$articles.length);
Child.setAttribute('List', this);
onDelete : this.$onArticleDelete,
onSelect : this.$onArticleSelect,
onUnSelect : this.$onArticleUnSelect,
onReplace : this.$onArticleReplace,
onEditCustomFields: this.$onArticleEditCustomFields,
onCalc : this.$executeCalculation
});
if (this.$Container) {
Child.inject(this.$Container);
}
Child.getElm().addClass('article');
},
/**
* Replace an article with another
*
* @param {Object} NewArticle
* @param {Number} position
*/
replaceArticle: function (NewArticle, position) {
if (typeof NewArticle !== 'object') {
return;
}
if (!(NewArticle instanceof Article)) {
return;
}
const Wanted = this.$articles.find(function (Article) {
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
return Article.getAttribute('position') === position;
});
this.addArticle(NewArticle);
if (Wanted) {
NewArticle.getElm().inject(Wanted.getElm(), 'after');
Wanted.remove();
}
NewArticle.setPosition(position);
this.$recalculatePositions();
return this.$calc();
},
/**
* Insert a new empty product
*/
insertNewProduct: function () {
this.addArticle(new Article());
},
/**
* Return the articles as an array
*
* @return {Array}
*/
save: function () {
return this.$articles.map(function (Article) {
return Object.merge(Article.getAttributes(), {
control: Article.getType()
});
});
},
/**
* Calculate the list
*/
$calc: function () {
if (this.$calculationTimer) {
clearTimeout(this.$calculationTimer);
this.$calculationTimer = null;
}
this.$calculationTimer = (function () {
self.$executeCalculation();
}).delay(500);
},
/**
* Calc
*/
/**
* Execute a new calculation
*
* @returns {Promise}
*/
$executeCalculation: function () {
if (this.$isIncalculationFrame) {
this.fireEvent('calc', [
this,
this.$calculations
}
if (this.$calculationRunning) {
return new Promise((resolve) => {
const trigger = () => {
resolve(this.$calculations);
this.removeEvent('onCalc', trigger);
});
}
this.$calculationRunning = true;
return new Promise((resolve, reject) => {
const articles = this.$getArticleDataForCalculation();
QUIAjax.get('package_quiqqer_erp_ajax_products_calc', (result) => {
this.$calculations = result;
this.$isIncalculationFrame = true;
this.$calculationRunning = false;
// performance double request -> quiqqer/invoice#104
self.$isIncalculationFrame = false;
}, 100);
'package' : 'quiqqer/erp',
articles : JSON.encode({articles: articles}),
priceFactors: JSON.encode(this.getPriceFactors()),
user : JSON.encode(this.$user),
currency : this.getAttribute('currency'),
onError : function (err) {
console.error(err);
reject();
}

Patrick Müller
committed
/**
* Get article data used for calculation
*
* @return {Array}
*/
$getArticleDataForCalculation: function () {
return this.$articles.map(function (Article) {
return Article.getAttributes();
});
},
/**
* Return the current calculations
*
* @returns {{currencyData: {}, isEuVat: number, isNetto: boolean, nettoSubSum: number, nettoSum: number, subSum: number, sum: number, vatArray: Array, vatText: Array}|*}
*/
getCalculation: function () {
return this.$calculations;
},
/**
* Return price factors
*
* @return {[]}
*/
getPriceFactors: function () {
return this.$priceFactors;
},
/**
* Remove a price factor
*
* @param no
*/
removePriceFactor: function (no) {
for (let i = 0, len = this.$priceFactors.length; i < len; i++) {
if (i !== parseInt(no)) {
newList.push(this.$priceFactors[i]);
}
}
this.$priceFactors = newList;
},
/**
* add a price factor
*
* {
* calculation : 2,
* calculation_basis: 2,
* description : Form.elements.title.value,
* identifier : "",
* index : priority,
* nettoSum : data.nettoSum,
* nettoSumFormatted: data.nettoSumFormatted,
* sum : data.sum,
* sumFormatted : data.sumFormatted,
* title : Form.elements.title.value,
* value : data.sum,
* valueText : data.sumFormatted,
* vat : Form.elements.vat.value,
* visible : 1
* }
* @param priceFactor
*/
addPriceFactor: function (priceFactor) {
const prio = priceFactor.index;
if (prio === this.$priceFactors.length) {
this.$priceFactors.push(priceFactor);
return;
}
this.$priceFactors.splice(prio, 0, priceFactor);
},
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
/**
* edit a price factor
*
* {
* calculation : 2,
* calculation_basis: 2,
* description : Form.elements.title.value,
* identifier : "",
* index : priority,
* nettoSum : data.nettoSum,
* nettoSumFormatted: data.nettoSumFormatted,
* sum : data.sum,
* sumFormatted : data.sumFormatted,
* title : Form.elements.title.value,
* value : data.sum,
* valueText : data.sumFormatted,
* vat : Form.elements.vat.value,
* visible : 1
* }
*
* @param index
* @param priceFactor
*/
editPriceFactor: function (index, priceFactor) {
for (let k in priceFactor) {
this.$priceFactors[index][k] = priceFactor[k];
}
},
/**
* Return the articles count
*
* @returns {number}
*/
countPriceFactors: function () {
return this.$priceFactors.length;
/**
* Sorting
*/
/**
* Toggles the sorting
*/
toggleSorting: function () {
if (this.$sorting) {
this.disableSorting();
return;
}
this.enableSorting();
},
/**
* Enables the sorting
* Articles can be sorted by drag and drop
*/
enableSorting: function () {
const Elm = this.getElm(),
elements = Elm.getElements('.article');
const Article = QUI.Controls.getById(Node.get('data-quiid'));
const attributes = Article.getAttributes();
Article.addEvents({
onSetPosition: self.$onArticleSetPosition
});
new Element('div', {
'class': 'quiqqer-erp-sortableClone-placeholder',
html : Mustache.render(templateSortablePlaceholder, attributes)
}).inject(Node);
});
this.$Sortables = new Sortables(this.$Container, {
revert: {
duration : 500,
transition: 'elastic:out'
},
clone: function (event) {
if (!Target.hasClass('article')) {
Target = Target.getParent('.article');
}
const size = Target.getSize(),
pos = Target.getPosition(self.$Container);
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
return new Element('div', {
styles: {
background: 'rgba(0,0,0,0.5)',
height : size.y,
position : 'absolute',
top : pos.y,
width : size.x,
zIndex : 1000
}
});
},
onStart: function (element) {
element.addClass('quiqqer-erp-sortableClone');
self.$Container.setStyles({
height : self.$Container.getSize().y,
overflow: 'hidden',
width : self.$Container.getSize().x
});
},
onComplete: function (element) {
element.removeClass('quiqqer-erp-sortableClone');
self.$Container.setStyles({
height : null,
overflow: null,
width : null
});
self.$recalculatePositions();
}
});
this.$sorting = true;
},
/**
* Disables the sorting
* Articles can not be sorted
*/
disableSorting: function () {
this.$sorting = false;
const self = this,
Elm = this.getElm(),
elements = Elm.getElements('.article');
Elm.getElements('.quiqqer-erp-sortableClone-placeholder').destroy();
elements.each(function (Node) {
const Article = QUI.Controls.getById(Node.get('data-quiid'));
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
Article.removeEvents({
onSetPosition: self.$onArticleSetPosition
});
});
this.$Sortables.detach();
this.$Sortables = null;
this.$articles.sort(function (A, B) {
return A.getAttribute('position') - B.getAttribute('position');
});
},
/**
* Is the sorting enabled?
*
* @return {boolean}
*/
isSortingEnabled: function () {
return this.$sorting;
},
/**
* event: on set position at article
*
* @param Article
*/
$onArticleSetPosition: function (Article) {
Article.getElm()
.getElement('.quiqqer-erp-backend-erpArticlePlaceholder-pos')
.set('html', Article.getAttribute('position'));
},
/**
* Recalculate the Position of all Articles
*/
$recalculatePositions: function () {
const Elm = this.getElm(),
elements = Elm.getElements('.article');
for (i = 0, len = elements.length; i < len; i++) {
Article = QUI.Controls.getById(elements[i].get('data-quiid'));
Article.setPosition(i + 1);
}
},
/**
* Events
*/
/**
* event : on article delete
*
* @param {Object} Article
*/
$onArticleDelete: function (Article) {
if (this.$selectedArticle) {
this.$selectedArticle.unselect();
}
articles = [],
position = 1;
for (i = 0, len = this.$articles.length; i < len; i++) {
if (this.$articles[i].getAttribute('position') === Article.getAttribute('position')) {
continue;
}
Current = this.$articles[i];
Current.setPosition(position);
articles.push(Current);
position++;
}
this.$articles = articles;
this.$executeCalculation().then(function () {
if (self.$articles.length) {
self.$articles[0].select();
}
});
},
/**
* event : on article delete
*
* @param {Object} Article
*/
$onArticleSelect: function (Article) {
if (this.$selectedArticle &&
this.$selectedArticle !== Article) {
this.$selectedArticle.unselect();
}
this.$selectedArticle = Article;
this.fireEvent('articleSelect', [
this,
this.$selectedArticle
]);
},
/**
* event : on article delete
*
* @param Article
*/
$onArticleUnSelect: function (Article) {
if (this.$selectedArticle === Article) {
this.$selectedArticle = null;
this.fireEvent('articleUnSelect', [
this,
this.$selectedArticle
]);
}
},
/**
* event : on article replace click
*
* @param Article
*/
$onArticleReplace: function (Article) {
this.fireEvent('articleReplaceClick', [
this,
Article
]);
/**
* event: on article edit custom fields clikc
*
* @param {Object} EditArticle - package/quiqqer/erp/bin/backend/controls/articles/Article
*/
$onArticleEditCustomFields: function (EditArticle) {
const ArticleCustomFields = EditArticle.getAttribute('customFields');
const FieldValues = {};
for (const [fieldId, FieldData] of Object.entries(ArticleCustomFields)) {
FieldValues[fieldId] = FieldData.value;
}
const AddProductControl = new AddProductWindow({
fieldValues: FieldValues,
editAmount : false
});
const productId = EditArticle.getAttribute('id');
AddProductControl.openProductSettings(productId).then((ArticleData) => {
if (!ArticleData) {
return false;
}
return AddProductControl.$parseProductToArticle(productId, ArticleData);
}).then((NewArticleData) => {
if (!NewArticleData) {
return;
}
const NewArticleCustomFields = NewArticleData.customFields;
for (const [fieldId, FieldData] of Object.entries(ArticleCustomFields)) {
if (!(fieldId in NewArticleCustomFields)) {
NewArticleCustomFields[fieldId] = FieldData;
}
}
EditArticle.setAttribute('customFields', NewArticleCustomFields);
const NewArticle = new Article(EditArticle.getAttributes());
this.replaceArticle(NewArticle, EditArticle.getAttribute('position'));
});
},
/**
* Return the current selected Article
*
* @returns {null|Object}
*/
getSelectedArticle: function () {
return this.$selectedArticle;
/**
* refresh the brutto / netto switch display
*/
const SwitchDesc = this.$Elm.getElement('.quiqqer-erp-backend-erpItems-container-switch-desc');
if (this.getAttribute('nettoinput')) {
SwitchDesc.set('html', QUILocale.get(lg, 'control.articleList.netto.message'));
this.$Elm.addClass('netto-view');
this.$Elm.removeClass('brutto-view');
} else {
SwitchDesc.set('html', QUILocale.get(lg, 'control.articleList.brutto.message'));
this.$Elm.addClass('brutto-view');
this.$Elm.removeClass('netto-view');