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, QUILoader, 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 ...
},
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')
this.$Loader = new QUILoader().inject(this.$Elm);
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');
switchTextOn: 'netto',
switchTextOnIcon: false,
switchTextOff: 'brutto',
self.setAttribute('nettoinput', !!self.$Switch.getStatus());
self.$refreshNettoBruttoDisplay();
self.$calc().then(() => {
self.$Loader.hide();
});
}
}
}).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 {
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);
}
}
self.fireEvent('calc', [
self,
self.$calculations
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
312
313
314
315
316
317
318
});
},
/**
* 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) {
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
392
393
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 () {
return new Promise((resolve) => {
if (this.$calculationTimer) {
clearTimeout(this.$calculationTimer);
this.$calculationTimer = null;
}
this.$calculationTimer = (function () {
self.$executeCalculation().then(resolve);
}).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}),
user: JSON.encode(this.$user),
currency: this.getAttribute('currency'),
nettoInput: this.getAttribute('nettoinput') ? 1 : 0,
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);
},
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
/**
* 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: {
transition: 'elastic:out'
},
clone: function (event) {
if (!Target.hasClass('article')) {
Target = Target.getParent('.article');
}
pos = Target.getPosition(self.$Container);
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,
width: self.$Container.getSize().x
});
},
onComplete: function (element) {
element.removeClass('quiqqer-erp-sortableClone');
self.$Container.setStyles({
});
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'));
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
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({
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');