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;
},
* load the serialized list into this list
* 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 ("calculations" in data) {
this.$calculations.calculations = data.calculations;
}
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();
let i, len, article, index;
for (i = 0, len = data.articles.length; i < len; i++) {
article = data.articles[i];
if (index === -1) {
self.addArticle(new Article(article));
continue;
}
try {
self.addArticle(new arguments[index](article));
} catch (e) {
console.log(e);
}
}
this.fireEvent('calc', [
this,
this.$calculations
]);
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
303
304
305
306
307
308
309
310
311
});
},
/**
* 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) {
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
383
384
385
386
387
388
389
390
391
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 the first / main vat of the list
*
* @returns {number}
*/
getVat: function () {
const calculations = this.getCalculation();
const articles = calculations.articles;
const calc = calculations.calculations;
let vat = 0;
if (typeof calc !== 'undefined' && typeof calc.vatArray === 'object') {
let vats = Object.keys(calc.vatArray);
return parseFloat(vats[0]);
}
if (typeof articles !== 'undefined' && typeof articles.length === 'number' && articles.length) {
return articles[0].vat;
}
return vat;
},
/**
* 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);
},
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
/**
* 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);
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
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
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'));
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
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');