Skip to content
Code-Schnipsel Gruppen Projekte
ArticleList.js 26,5 KiB
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',
Henning Leutz's avatar
Henning Leutz committed
    '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'

Henning Leutz's avatar
Henning Leutz committed
], 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',
Henning Leutz's avatar
Henning Leutz committed
            '$executeCalculation',
            '$getArticleDataForCalculation',
            '$onArticleEditCustomFields'
        ],

        options: {
Henning Leutz's avatar
Henning Leutz committed
            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;
Henning Leutz's avatar
Henning Leutz committed
            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');
Henning Leutz's avatar
Henning Leutz committed

            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 () {
                let i, len, article, index;

                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
                ]);
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;
Henning Leutz's avatar
Henning Leutz committed
        /**
         * refresh the brutto / netto switch display
         */
Henning Leutz's avatar
Henning Leutz committed
        $refreshNettoBruttoDisplay: function () {
            const SwitchDesc = this.$Elm.getElement('.quiqqer-erp-backend-erpItems-container-switch-desc');
Henning Leutz's avatar
Henning Leutz committed

            if (this.getAttribute('nettoinput')) {
                SwitchDesc.set('html', QUILocale.get(lg, 'control.articleList.netto.message'));
Henning Leutz's avatar
Henning Leutz committed
                this.$Switch.setSilentOn();
Henning Leutz's avatar
Henning Leutz committed
                this.$Elm.addClass('netto-view');
                this.$Elm.removeClass('brutto-view');
Henning Leutz's avatar
Henning Leutz committed
            } else {
                SwitchDesc.set('html', QUILocale.get(lg, 'control.articleList.brutto.message'));
Henning Leutz's avatar
Henning Leutz committed
                this.$Switch.setSilentOff();
Henning Leutz's avatar
Henning Leutz committed
                this.$Elm.addClass('brutto-view');
                this.$Elm.removeClass('netto-view');