Skip to content
Code-Schnipsel Gruppen Projekte
ArticleList.js 26,5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * @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',
    
        'qui/controls/buttons/Switch',
    
        'Mustache',
        'Ajax',
        'Locale',
    
    
        '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) {
    
        "use strict";
    
    
    
        return new Class({
    
            Extends: QUIControl,
            Type   : 'package/quiqqer/erp/bin/backend/controls/articles/ArticleList',
    
            Binds: [
                '$onArticleDelete',
                '$onArticleSelect',
                '$onArticleUnSelect',
                '$onArticleReplace',
    
                '$calc',
    
                '$onInject',
    
                '$executeCalculation',
    
                '$getArticleDataForCalculation',
                '$onArticleEditCustomFields'
    
            ],
    
            options: {
    
                currency  : false, // bool || string -> EUR, USD ...
                nettoinput: true
    
            },
    
            initialize: function (options) {
                this.parent(options);
    
                this.$articles = [];
    
    Henning Leutz's avatar
    Henning Leutz committed
                this.$user = {};
                this.$sorting = false;
    
    Henning Leutz's avatar
    Henning Leutz committed
                this.$calculationTimer = null;
    
                this.$isIncalculationFrame = false;
    
    
                this.$calculations = {
                    currencyData: {},
                    isEuVat     : 0,
                    isNetto     : true,
                    nettoSubSum : 0,
                    nettoSum    : 0,
                    subSum      : 0,
                    sum         : 0,
                    vatArray    : [],
                    vatText     : []
                };
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                this.$Container = null;
                this.$Sortables = null;
    
                this.$priceFactors = [];
    
    Henning Leutz's avatar
    Henning Leutz committed
                this.$Switch = null;
    
    
                this.addEvents({
                    onInject: this.$onInject
                });
            },
    
            /**
             * Create the DOMNode element
             *
             * @returns {HTMLDivElement}
             */
            create: function () {
    
    Henning Leutz's avatar
    Henning Leutz committed
                const self = this;
    
                this.$Elm = this.parent();
    
                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, {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        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
                };
            },
    
    
            /**
             * 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 () {
    
    
                    for (i = 0, len = data.articles.length; i < len; i++) {
                        article = data.articles[i];
    
    Henning Leutz's avatar
    Henning Leutz committed
                        index = controls.indexOf(article.control);
    
    
                        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);
    
    
                Child.addEvents({
    
                    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) {
    
                    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;
                }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                const self = this;
    
    
                this.$calculationTimer = (function () {
                    self.$executeCalculation();
                }).delay(500);
            },
    
            /**
             * Calc
             */
    
            /**
             * Execute a new calculation
             *
             * @returns {Promise}
             */
            $executeCalculation: function () {
    
    Henning Leutz's avatar
    Henning Leutz committed
                const self = this;
    
                if (this.$isIncalculationFrame) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    this.fireEvent('calc', [
                        this,
                        this.$calculations
    
    Henning Leutz's avatar
    Henning Leutz committed
                    return Promise.resolve(this.$calculations);
    
                if (this.$calculationRunning) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    return new Promise((resolve) => {
                        const trigger = () => {
                            resolve(this.$calculations);
                            this.removeEvent('onCalc', trigger);
    
    Henning Leutz's avatar
    Henning Leutz committed
                        this.addEvent('onCalc', trigger);
    
    Henning Leutz's avatar
    Henning Leutz committed
                return new Promise((resolve, reject) => {
                    const articles = this.$getArticleDataForCalculation();
    
    Henning Leutz's avatar
    Henning Leutz committed
                    QUIAjax.get('package_quiqqer_erp_ajax_products_calc', (result) => {
                        this.$calculations = result;
                        this.$isIncalculationFrame = true;
                        this.$calculationRunning = false;
    
    
                        // performance double request -> quiqqer/invoice#104
    
    Henning Leutz's avatar
    Henning Leutz committed
                        setTimeout(() => {
    
                            self.$isIncalculationFrame = false;
                        }, 100);
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                        this.fireEvent('calc', [
                            this,
    
    Henning Leutz's avatar
    Henning Leutz committed
                            result
                        ]);
    
                        resolve(result);
                    }, {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        '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();
                        }
    
            /**
             * 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) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                let newList = [];
    
    Henning Leutz's avatar
    Henning Leutz committed
                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);
            },
    
    
    Henning Leutz's avatar
    Henning Leutz committed
    
            /**
             * 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 () {
    
    Henning Leutz's avatar
    Henning Leutz committed
                const self = this;
    
    Henning Leutz's avatar
    Henning Leutz committed
                const Elm      = this.getElm(),
                      elements = Elm.getElements('.article');
    
    
                elements.each(function (Node) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    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) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        let Target = event.target;
    
    
                        if (!Target.hasClass('article')) {
                            Target = Target.getParent('.article');
                        }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                        const size = Target.getSize(),
                              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,
                            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;
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                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'));
    
    
                    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()
    
    Henning Leutz's avatar
    Henning Leutz committed
                       .getElement('.quiqqer-erp-backend-erpArticlePlaceholder-pos')
                       .set('html', Article.getAttribute('position'));
    
            },
    
            /**
             * Recalculate the Position of all Articles
             */
            $recalculatePositions: function () {
    
    Henning Leutz's avatar
    Henning Leutz committed
                let i, len, Article;
    
    Henning Leutz's avatar
    Henning Leutz committed
                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();
                }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                let i, len, Current;
    
    Henning Leutz's avatar
    Henning Leutz committed
                let self     = this,
    
                    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;
    
    Henning Leutz's avatar
    Henning Leutz committed
                this.fireEvent('articleSelect', [
                    this,
                    this.$selectedArticle
                ]);
    
            },
    
            /**
             * event : on article delete
             *
             * @param Article
             */
            $onArticleUnSelect: function (Article) {
                if (this.$selectedArticle === Article) {
                    this.$selectedArticle = null;
    
    Henning Leutz's avatar
    Henning Leutz committed
                    this.fireEvent('articleUnSelect', [
                        this,
                        this.$selectedArticle
                    ]);
    
                }
            },
    
            /**
             * event : on article replace click
             *
             * @param Article
             */
            $onArticleReplace: function (Article) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                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
             */
    
            $refreshNettoBruttoDisplay: function () {
    
                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.$Switch.setSilentOn();
    
                    this.$Elm.addClass('netto-view');
                    this.$Elm.removeClass('brutto-view');
    
                } else {
                    SwitchDesc.set('html', QUILocale.get(lg, 'control.articleList.brutto.message'));
    
                    this.$Switch.setSilentOff();
    
                    this.$Elm.addClass('brutto-view');
                    this.$Elm.removeClass('netto-view');