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 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);
},
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
/**
* 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);
654
655
656
657
658
659
660
661
662
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
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'));
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
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');