diff --git a/ajax/dashboard/globalProcess/availablePlugins.php b/ajax/dashboard/globalProcess/availablePlugins.php new file mode 100644 index 0000000000000000000000000000000000000000..725f422b65714a96fad9de870e2b3bf81e9d3005 --- /dev/null +++ b/ajax/dashboard/globalProcess/availablePlugins.php @@ -0,0 +1,26 @@ +<?php + +/** + * This file contains package_quiqqer_erp_ajax_dashboard_globalProcess_availablePlugins + */ + +use QUI\ERP\Processes; + +QUI::$Ajax->registerFunction( + 'package_quiqqer_erp_ajax_dashboard_globalProcess_availablePlugins', + function () { + $PackageManager = QUI::getPackageManager(); + $Processes = new Processes(); + $plugins = []; + + foreach ($Processes->getWantedPluginList() as $plugin) { + if ($PackageManager->isInstalled($plugin)) { + $plugins[] = $plugin; + } + } + + return $plugins; + }, + [], + ['Permission::checkAdminUser'] +); diff --git a/ajax/dashboard/globalProcess/getList.php b/ajax/dashboard/globalProcess/getList.php new file mode 100644 index 0000000000000000000000000000000000000000..410cc1f921a3eda85bfe7719e4bd8612517c3674 --- /dev/null +++ b/ajax/dashboard/globalProcess/getList.php @@ -0,0 +1,16 @@ +<?php + +/** + * This file contains package_quiqqer_erp_ajax_dashboard_globalProcess_getList + */ + +use QUI\ERP\Processes; + +QUI::$Ajax->registerFunction( + 'package_quiqqer_erp_ajax_dashboard_globalProcess_getList', + function () { + return (new Processes())->getList(); + }, + [], + ['Permission::checkAdminUser'] +); diff --git a/ajax/dashboard/globalProcess/getProcess.php b/ajax/dashboard/globalProcess/getProcess.php new file mode 100644 index 0000000000000000000000000000000000000000..b366dcdff4ffa649329d7a36cc47b80523c56160 --- /dev/null +++ b/ajax/dashboard/globalProcess/getProcess.php @@ -0,0 +1,20 @@ +<?php + +/** + * This file contains package_quiqqer_erp_ajax_dashboard_globalProcess_getProcess + */ + +use QUI\ERP\Process; + +QUI::$Ajax->registerFunction( + 'package_quiqqer_erp_ajax_dashboard_globalProcess_getProcess', + function ($globalProcessId) { + $Process = new Process($globalProcessId); + + return [ + 'history' => $Process->getCompleteHistory()->toArray() + ]; + }, + ['globalProcessId'], + ['Permission::checkAdminUser'] +); diff --git a/bin/backend/controls/Comments.css b/bin/backend/controls/Comments.css index 842f08f5c648234fb91d2c431a3bcbdc77304d27..2456669f86cb6a854f4bcb013321efc130af5e8e 100644 --- a/bin/backend/controls/Comments.css +++ b/bin/backend/controls/Comments.css @@ -11,6 +11,16 @@ width: 100%; } +.quiqqer-erp-comments-comment--clickable:hover { + background: #2F8FC6; + color: #FFFFFF; + cursor: pointer; +} + +.quiqqer-erp-comments-comment--clickable:hover .quiqqer-erp-comments-comment-type { + color: #FFFFFF; +} + /** title ================================================== */ diff --git a/bin/backend/controls/Comments.html b/bin/backend/controls/Comments.html index eac3944f613c0b15da07e97f29594ce8bdb0c08c..53026d4b6a1d4c1e3ff58f77418f23098911359c 100644 --- a/bin/backend/controls/Comments.html +++ b/bin/backend/controls/Comments.html @@ -21,6 +21,7 @@ <div class="quiqqer-erp-comments-comment" data-id="{{id}}" data-source="{{source}}" + data-object-hash="{{objectHash}}" > <span class="quiqqer-erp-comments-comment-type" title="{{title}}" {{#editable}}data-editable="1"{{/editable}} diff --git a/bin/backend/controls/Comments.js b/bin/backend/controls/Comments.js index 6362a3777682b77e492579bdfb0e7e96fd667d9c..65f31bbb396f646f96eb1f4f78eb65f61eb8d530 100644 --- a/bin/backend/controls/Comments.js +++ b/bin/backend/controls/Comments.js @@ -10,31 +10,33 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ 'qui/QUI', 'qui/controls/Control', + 'utils/Panels', 'Mustache', 'Locale', 'text!package/quiqqer/erp/bin/backend/controls/Comments.html', 'css!package/quiqqer/erp/bin/backend/controls/Comments.css' -], function (QUI, QUIControl, Mustache, QUILocale, template) { - "use strict"; +], function(QUI, QUIControl, PanelUtils, Mustache, QUILocale, template) { + 'use strict'; const lg = 'quiqqer/erp'; return new Class({ Extends: QUIControl, - Type : 'package/quiqqer/erp/bin/backend/controls/Comments', + Type: 'package/quiqqer/erp/bin/backend/controls/Comments', Binds: [ - '$onCreate' + '$onCreate', + '$onEntryClick' ], options: { comments: false }, - initialize: function (options) { + initialize: function(options) { this.parent(options); this.$filter = false; @@ -50,7 +52,7 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ * * @returns {HTMLDivElement} */ - create: function () { + create: function() { this.$Elm = this.parent(); this.$Elm.addClass('quiqqer-erp-comments'); @@ -59,12 +61,20 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ return this.$Elm; }, + /** + * empties the comment list + */ + clear: function() { + this.$comments = []; + this.$Elm.set('html', ''); + }, + /** * insert / set comments * * @param {String|Object} comments */ - unserialize: function (comments) { + unserialize: function(comments) { if (typeOf(comments) === 'string') { try { comments = JSON.decode(comments); @@ -78,7 +88,7 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ const Formatter = this.$getFormatter(); - comments = comments.map(function (entry) { + comments = comments.map(function(entry) { let date = new Date(entry.time * 1000), type = 'fa fa-comment'; @@ -106,19 +116,24 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ entry.id = ''; } + if (typeof entry.objectHash === 'undefined') { + entry.objectHash = ''; + } + if (typeof entry.editable === 'undefined') { entry.editable = false; } return { - date : date, - time : Formatter.format(date), - message : entry.message, - type : type, + date: date, + time: Formatter.format(date), + message: entry.message, + type: type, timestamp: entry.time, - id : entry.id, - source : entry.source, - editable : entry.editable + id: entry.id, + source: entry.source, + editable: entry.editable, + objectHash: entry.objectHash }; }); @@ -135,7 +150,7 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ if (typeof group[day] === 'undefined') { group[day] = { - day : day, + day: day, data: [] }; } @@ -151,14 +166,15 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ } group[day].data.push({ - time : entry.time, - message : entry.message, - type : entry.type, + time: entry.time, + message: entry.message, + type: entry.type, timestamp: entry.timestamp, - id : entry.id, - source : entry.source, - title : entry.source !== '' ? title : '', - editable : entry.editable + id: entry.id, + source: entry.source, + title: entry.source !== '' ? title : '', + editable: entry.editable, + objectHash: entry.objectHash }); } @@ -169,18 +185,18 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ /** * refresh the display */ - refresh: function () { + refresh: function() { let i, data, realData, commentEntries; const self = this; const comments = []; - const sortComments = function (a, b) { + const sortComments = function(a, b) { return a.timestamp - b.timestamp; }; const commentClone = Object.clone(this.$comments); - const filterComments = function (entry) { + const filterComments = function(entry) { const message = entry.message.toLowerCase(); const type = entry.type.toLowerCase(); const id = entry.id.toLowerCase(); @@ -223,12 +239,25 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ this.$Elm.set({ html: Mustache.render(template, { - comments : comments, + comments: comments, textNoComments: QUILocale.get(lg, 'comments.message.no.comments') }) }); - this.$Elm.getElements('[data-editable]').addEvent('click', function (event) { + this.$Elm.querySelectorAll('.quiqqer-erp-comments-comment').forEach((Comment) => { + if (!Comment.get('data-object-hash')) { + return; + } + + if (typeof QUIQQER_FRONTEND !== 'undefined') { + return; + } + + Comment.addClass('quiqqer-erp-comments-comment--clickable'); + Comment.addEventListener('click', this.$onEntryClick); + }); + + this.$Elm.getElements('[data-editable]').addEvent('click', function(event) { let Parent = event.target; if (!Parent.hasClass('quiqqer-erp-comments-comment')) { @@ -236,8 +265,9 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ } const data = { - id : Parent.get('data-id'), - source: Parent.get('data-source') + id: Parent.get('data-id'), + source: Parent.get('data-source'), + objectHash: Parent.get('data-object-hash') }; self.fireEvent('edit', [ @@ -253,14 +283,14 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ * * @return {window.Intl.DateTimeFormat} */ - $getFormatter: function () { + $getFormatter: function() { let locale = QUILocale.getCurrent(); const options = { // year : 'numeric', // month : '2-digit', // day : '2-digit', - hour : '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit' }; @@ -283,13 +313,13 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ * * @return {window.Intl.DateTimeFormat} */ - $getDayFormatter: function () { + $getDayFormatter: function() { let locale = QUILocale.getCurrent(); const options = { - year : 'numeric', + year: 'numeric', month: '2-digit', - day : '2-digit' + day: '2-digit' }; if (!locale.match('_')) { @@ -305,6 +335,95 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ } }, + /** + * event: comment click + * + * @param event + */ + $onEntryClick: function(event) { + let Target = event.target; + + if (!Target.hasClass('quiqqer-erp-comments-comment')) { + Target = Target.getParent('.quiqqer-erp-comments-comment'); + } + + switch (Target.get('data-source')) { + case 'quiqqer/order': + require([ + 'package/quiqqer/order/bin/backend/controls/panels/Order' + ], (Order) => { + PanelUtils.openPanelInTasks( + new Order({ + orderId: Target.get('data-object-hash') + }) + ); + }); + return; + + case 'quiqqer/invoice': + require([ + 'package/quiqqer/invoice/bin/backend/controls/panels/Invoice' + ], (Invoice) => { + PanelUtils.openPanelInTasks( + new Invoice({ + invoiceId: Target.get('data-object-hash') + }) + ); + }); + return; + + case 'quiqqer/offer': + require([ + 'package/quiqqer/offers/bin/js/backend/controls/panels/Offer' + ], (Offer) => { + PanelUtils.openPanelInTasks( + new Offer({ + offerId: Target.get('data-object-hash') + }) + ); + }); + return; + + case 'quiqqer/purchasing': + require([ + 'package/quiqqer/purchasing/bin/js/backend/controls/panels/processes/Process' + ], (Process) => { + PanelUtils.openPanelInTasks( + new Process({ + processId: Target.get('data-object-hash') + }) + ); + }); + return; + + case 'quiqqer/salesorders': + require([ + 'package/quiqqer/salesorders/bin/js/backend/controls/panels/SalesOrder' + ], (SalesOrder) => { + PanelUtils.openPanelInTasks( + new SalesOrder({ + salesOrderHash: Target.get('data-object-hash') + }) + ); + }); + return; + + case 'quiqqer/payment-transaction': + require([ + 'package/quiqqer/payment-transactions/bin/backend/controls/windows/Transaction' + ], (TransactionWindow) => { + console.log(Target.get('data-object-hash')); + + new TransactionWindow({ + txid: Target.get('data-object-hash') + }).open(); + }); + return; + } + + QUI.fireEvent('onQuiqqerErpCommentsClick', [this, Target]); + }, + //region filter /** @@ -312,7 +431,7 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ * * @param {String} value */ - filter: function (value) { + filter: function(value) { this.$filter = value.toString().toLowerCase(); this.refresh(); }, @@ -320,7 +439,7 @@ define('package/quiqqer/erp/bin/backend/controls/Comments', [ /** * Clears the filter */ - clearFilter: function () { + clearFilter: function() { this.$filter = false; this.refresh(); } diff --git a/bin/backend/controls/articles/Article.js b/bin/backend/controls/articles/Article.js index 8fe18bcbb4cc7bb72e81a520bfe995fdc0d26933..97e8223c4d707c79ed28f0dc94256137d8685722 100644 --- a/bin/backend/controls/articles/Article.js +++ b/bin/backend/controls/articles/Article.js @@ -102,6 +102,8 @@ define('package/quiqqer/erp/bin/backend/controls/articles/Article', [ 'class': 'QUI\\ERP\\Accounting\\Article', params: false, // mixed value for API Articles currency: false, + productSetParentUuid: null, + uuid: null, showSelectCheckbox: false, // select this article via checkbox instead of click @@ -133,6 +135,14 @@ define('package/quiqqer/erp/bin/backend/controls/articles/Article', [ this.$calculations = {}; this.$bruttoCalc = {}; + if (typeof options !== 'undefined' && typeof options.calculated !== 'undefined') { + this.$calculations = options.calculated; + } + + if (typeof this.$calculations.nettoPriceNotRounded !== 'undefined' && this.$calculations.nettoPriceNotRounded) { + this.setAttribute('unitPrice', this.$calculations.nettoPriceNotRounded); + } + this.$SelectCheckbox = null; this.$Position = null; this.$Quantity = null; diff --git a/bin/backend/controls/articles/ArticleList.js b/bin/backend/controls/articles/ArticleList.js index c4638e9aab36bfaedfcc8c538c54528224bcfbd0..f52bef81ad3e7e81625cbaf6146ff9102713fa24 100644 --- a/bin/backend/controls/articles/ArticleList.js +++ b/bin/backend/controls/articles/ArticleList.js @@ -242,7 +242,7 @@ define('package/quiqqer/erp/bin/backend/controls/articles/ArticleList', [ this.$articles = []; let selectedPosition = null; - + console.log(data.articles); if (this.$Container) { if (this.$selectedArticle) { selectedPosition = this.$selectedArticle.getAttribute('position'); diff --git a/bin/backend/controls/dashboard/cards/GlobalProcessIdList.js b/bin/backend/controls/dashboard/cards/GlobalProcessIdList.js new file mode 100644 index 0000000000000000000000000000000000000000..3d7b28c1ee442eb9874f81b7914062d31d4af703 --- /dev/null +++ b/bin/backend/controls/dashboard/cards/GlobalProcessIdList.js @@ -0,0 +1,253 @@ +/** + * @module package/quiqqer/erp/bin/backend/controls/dashboard/cards/GlobalProcessIdList + * @author www.pcsg.de (Henning Leutz) + */ +define('package/quiqqer/erp/bin/backend/controls/dashboard/cards/GlobalProcessIdList', [ + + 'qui/QUI', + 'package/quiqqer/dashboard/bin/backend/controls/Card', + 'controls/grid/Grid', + 'Locale', + 'Ajax' + +], function(QUI, Card, Grid, QUILocale, QUIAjax) { + 'use strict'; + + const lg = 'quiqqer/erp'; + + return new Class({ + + Extends: Card, + Type: 'package/quiqqer/erp/bin/backend/controls/dashboard/cards/GlobalProcessIdList', + + Binds: [ + 'refresh', + '$onCreate', + '$onDblClick', + '$getPlugins' + ], + + initialize: function(options) { + this.parent(options); + + this.setAttribute({ + content: '', + priority: 1 + }); + + this.$Grid = null; + + this.addEvents({ + onCreate: this.$onCreate + }); + }, + + $onCreate: function() { + this.$Content.addClass('card-table'); + this.$Content.removeClass('card-body'); + + this.getElm().classList.add('col-sg-12'); + this.getElm().classList.add('col-sm-12'); + + this.setTitle(QUILocale.get(lg, 'dashboard.erp.grid.title')); + this.setContent(''); + + const Container = new Element('div', { + styles: { + height: 600, + width: '100%' + } + }).inject(this.getContent()); + + this.$getPlugins().then((plugins) => { + const columnModel = []; + + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.date'), + dataIndex: 'date', + dataType: 'string', + width: 140 + }); + + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.processId'), + dataIndex: 'globalProcessId', + dataType: 'string', + width: 240 + }); + + if (plugins.indexOf('quiqqer/invoice') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.invoice'), + dataIndex: 'invoice', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/order') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.order'), + dataIndex: 'order', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/offers') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.offer'), + dataIndex: 'offer', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/salesorders') !== -1) { + console.log(1, QUILocale.get(lg, 'dashboard.erp.salesOrder')); + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.salesOrder'), + dataIndex: 'salesorders', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/purchasing') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.purchasing'), + dataIndex: 'purchasing', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/booking') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.booking'), + dataIndex: 'booking', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/contract') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.contract'), + dataIndex: 'contract', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/delivery-notes') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.deliveryNotes'), + dataIndex: 'deliveryNotes', + dataType: 'string', + width: 240 + }); + } + + if (plugins.indexOf('quiqqer/payment-transactions') !== -1) { + columnModel.push({ + header: QUILocale.get(lg, 'dashboard.erp.transactions'), + dataIndex: 'transactions', + dataType: 'string', + width: 240 + }); + } + + this.$Grid = new Grid(Container, { + buttons: [ + { + name: 'add', + textimage: 'fa fa-plus', + text: 'Vorgang hinzufügen', + styles: { + 'float': 'right' + } + } + ], + columnModel: columnModel, + pagination: true, + exportData: true + }); + + this.$Grid.addEvents({ + onRefresh: this.refresh, + onDblClick: this.$onDblClick + }); + + this.$Content.setStyle('padding', 10); + this.$Content.setStyle('display', null); + + this.$Grid.setHeight(600); + this.refresh(); + }); + }, + + refresh: function() { + if (!this.$Grid) { + return Promise.resolve(); + } + + this.$Grid.showLoader(); + + return new Promise((resolve) => { + QUIAjax.get('package_quiqqer_erp_ajax_dashboard_globalProcess_getList', (result) => { + let entry; + const data = []; + const DateFormatter = QUILocale.getDateTimeFormatter({ + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + + for (let hash in result) { + entry = result[hash]; + entry.globalProcessId = hash; + entry.date = DateFormatter.format(new Date(entry.date)); + data.push(entry); + } + + this.$Grid.setData({ + data: data + }); + + this.$Grid.hideLoader(); + resolve(result); + }, { + 'package': 'quiqqer/erp' + }); + }); + }, + + $getPlugins: function() { + return new Promise((resolve) => { + QUIAjax.get('package_quiqqer_erp_ajax_dashboard_globalProcess_availablePlugins', resolve, { + 'package': 'quiqqer/erp' + }); + }); + }, + + $onDblClick: function() { + const selected = this.$Grid.getSelectedData(); + const globalProcessId = selected[0].globalProcessId; + + window.parent.require([ + 'utils/Panels', + 'package/quiqqer/erp/bin/backend/controls/process/ProcessPanel' + ], (PanelUtils, ProcessPanel) => { + PanelUtils.openPanelInTasks( + new ProcessPanel({ + globalProcessId: globalProcessId + }) + ); + }); + } + }); +}); diff --git a/bin/backend/controls/elements/PriceCalcInput.js b/bin/backend/controls/elements/PriceCalcInput.js index 77a6b34f8b1a17829697b18f33d4ae1630aca677..83e4901a47c2247edb0e46cdac4626ddc967d05f 100644 --- a/bin/backend/controls/elements/PriceCalcInput.js +++ b/bin/backend/controls/elements/PriceCalcInput.js @@ -1,6 +1,8 @@ /** * @module package/quiqqer/erp/bin/backend/controls/elements/PriceCalcInput * @author www.pcsg.de (Henning Leutz) + * + * @event onSetValue [value, this] - Fires if a net/gross price value is set/calculated */ define('package/quiqqer/erp/bin/backend/controls/elements/PriceCalcInput', [ @@ -464,11 +466,13 @@ define('package/quiqqer/erp/bin/backend/controls/elements/PriceCalcInput', [ if ((foundGroupSeparator || foundDecimalSeparator) && !(foundGroupSeparator && !foundDecimalSeparator)) { Node.value = value; + this.fireEvent('setValue', [value, this]); return Promise.resolve(); } return this.getFormatter().then((Formatter) => { Node.value = Formatter.format(parseFloat(value)); + this.fireEvent('setValue', [parseFloat(value), this]); }); }, diff --git a/bin/backend/controls/process/ProcessPanel.css b/bin/backend/controls/process/ProcessPanel.css new file mode 100644 index 0000000000000000000000000000000000000000..d14c0e78b64818021745fc46f59569dbf989e488 --- /dev/null +++ b/bin/backend/controls/process/ProcessPanel.css @@ -0,0 +1,6 @@ +.quiqqer-erp-process-comments-header { + background: #f7f7f7; + display: inline-block; + padding: 20px; + width: 100%; +} \ No newline at end of file diff --git a/bin/backend/controls/process/ProcessPanel.js b/bin/backend/controls/process/ProcessPanel.js new file mode 100644 index 0000000000000000000000000000000000000000..2a8c1df82107b275972242c5e2325fa4f1503843 --- /dev/null +++ b/bin/backend/controls/process/ProcessPanel.js @@ -0,0 +1,115 @@ +/** + * @module package/quiqqer/erp/bin/backend/controls/process/ProcessPanel + * @author www.pcsg.de (Henning Leutz) + */ +define('package/quiqqer/erp/bin/backend/controls/process/ProcessPanel', [ + + 'qui/QUI', + 'qui/controls/desktop/Panel', + 'Locale', + 'Ajax', + + 'css!package/quiqqer/erp/bin/backend/controls/process/ProcessPanel.css' + +], function(QUI, QUIPanel, QUILocale, QUIAjax) { + 'use strict'; + + const lg = 'quiqqer/erp'; + + return new Class({ + + Extends: QUIPanel, + Type: 'package/quiqqer/erp/bin/backend/controls/process/ProcessPanel', + + Binds: [ + '$onCreate', + '$onShow' + ], + + options: { + globalProcessId: false + }, + + initialize: function(options) { + this.parent(options); + + if (typeof options.globalProcessId !== 'undefined') { + this.setAttribute('#id', 'process--' + options.globalProcessId); + } + + this.$Comments = null; + + this.addEvents({ + onCreate: this.$onCreate, + onShow: this.$onShow + }); + }, + + $onCreate: function() { + if (!this.getAttribute('globalProcessId')) { + this.close(); + return; + } + + this.setAttributes({ + icon: 'fa fa-sitemap', + title: QUILocale.get(lg, 'panel.globalProcess.title', { + globalProcessId: this.getAttribute('globalProcessId') + }) + }); + + this.Loader.show(); + this.refresh(); + + this.getBody().setStyles({ + padding: 0 + }); + + new Element('div', { + 'class': 'quiqqer-erp-process-comments-header', + html: '<div class="quiqqer-customer-comments-header-filter">' + + ' <input type="text" name="filter" placeholder="Filter (Nachricht, Type, ID) ...">' + + ' </div>' + }).inject(this.getBody()); + + require(['package/quiqqer/erp/bin/backend/controls/Comments'], (Comments) => { + const CommentContainer = new Element('div', { + styles: { + padding: 20 + } + }).inject(this.getBody()); + + const Filter = this.getBody().getElement('[name="filter"]'); + this.$Comments = new Comments().inject(CommentContainer); + + Filter.addEvent('keyup', () => { + this.$Comments.filter(Filter.value); + }); + + this.$onShow(); + }); + }, + + $onShow: function() { + if (!this.getAttribute('globalProcessId')) { + this.close(); + return; + } + + if (!this.$Comments) { + return; + } + + this.Loader.show(); + + QUIAjax.get('package_quiqqer_erp_ajax_dashboard_globalProcess_getProcess', (result) => { + this.$Comments.clear(); + this.$Comments.unserialize(result.history); + this.Loader.hide(); + }, { + 'package': 'quiqqer/erp', + globalProcessId: this.getAttribute('globalProcessId') + }); + } + }); +}); diff --git a/locale.xml b/locale.xml index cd237ece29ebbc503a81312dd6626bbcdcb6166d..2d5fbd4aaa0f6a41c43d297101fc721b51624826 100644 --- a/locale.xml +++ b/locale.xml @@ -275,7 +275,6 @@ </groups> <groups name="quiqqer/erp" datatype="php"> - <locale name="bankAccounts.patch.bankAccount_created"> <de> <![CDATA[Es wurde automatisch ein Standard-Bankkonto auf Basis der Firmen-Bankdaten in der ecoyn-Konfiguration angelegt.]]></de> @@ -670,11 +669,52 @@ <de><![CDATA[ecoyn Dashboard]]></de> <en><![CDATA[ecoyn dashboard]]></en> </locale> + <locale name="dashboard.erp.title"> + <de><![CDATA[ERP Dashboard]]></de> + <en><![CDATA[ERP Dashboard]]></en> + </locale> <locale name="exchangerate.text"> <de><![CDATA[[startCurrency] = [rate]]]></de> <en><![CDATA[[startCurrency] = [rate]]]></en> </locale> + <locale name="created.invoice"> + <de><![CDATA[Rechnung #[invoiceId] erstellt]]></de> + <en><![CDATA[Created invoice #[invoiceId]]]></en> + </locale> + <locale name="created.order"> + <de><![CDATA[Bestellung #[orderId] erstellt]]></de> + <en><![CDATA[Created order #[orderId]]]></en> + </locale> + + <locale name="process.history.invoice.created"> + <de><![CDATA[Rechnung #[hash] erstellt]]></de> + <en><![CDATA[Invoice #[hash] created]]></en> + </locale> + <locale name="process.history.order.created"> + <de><![CDATA[Bestellung #[hash] erstellt]]></de> + <en><![CDATA[Order #[hash] created]]></en> + </locale> + <locale name="process.history.offer.created"> + <de><![CDATA[Angebot #[hash] erstellt]]></de> + <en><![CDATA[Offer #[hash] created]]></en> + </locale> + <locale name="process.history.purchasing.created"> + <de><![CDATA[Einkauf #[hash] erstellt]]></de> + <en><![CDATA[Purchase #[hash] created]]></en> + </locale> + <locale name="process.history.salesorders.created"> + <de><![CDATA[Auftrag #[hash] erstellt]]></de> + <en><![CDATA[Sales order #[hash] created]]></en> + </locale> + <locale name="process.history.booking.created"> + <de><![CDATA[Buchung #[hash] erstellt]]></de> + <en><![CDATA[Booking #[hash] created]]></en> + </locale> + <locale name="process.history.transaction.created"> + <de><![CDATA[Zahlung von [amount] eingegangen / gebucht. (#[hash])]]></de> + <en><![CDATA[Payment from [amount] received / posted. (#[hash])]]></en> + </locale> </groups> <groups name="quiqqer/erp" datatype="js"> @@ -1398,5 +1438,58 @@ Allowed characters: Letters, numbers and _ ä ö ü ß]]></en> <en><![CDATA[Gross input]]></en> </locale> + <locale name="dashboard.erp.processId"> + <de><![CDATA[Vorgangsnummer]]></de> + <en><![CDATA[Process number]]></en> + </locale> + <locale name="dashboard.erp.grid.title"> + <de><![CDATA[Die letzten Vorgänge]]></de> + <en><![CDATA[The latest processes]]></en> + </locale> + <locale name="dashboard.erp.date"> + <de><![CDATA[Datum]]></de> + <en><![CDATA[Date]]></en> + </locale> + <locale name="dashboard.erp.invoice"> + <de><![CDATA[Rechnung]]></de> + <en><![CDATA[Invoice]]></en> + </locale> + <locale name="dashboard.erp.order"> + <de><![CDATA[Bestellung]]></de> + <en><![CDATA[Order]]></en> + </locale> + <locale name="dashboard.erp.offer"> + <de><![CDATA[Angebot]]></de> + <en><![CDATA[Offer]]></en> + </locale> + <locale name="dashboard.erp.salesOrder"> + <de><![CDATA[Auftrag]]></de> + <en><![CDATA[Sales Order]]></en> + </locale> + <locale name="dashboard.erp.purchasing"> + <de><![CDATA[Einkauf]]></de> + <en><![CDATA[Purchase]]></en> + </locale> + <locale name="dashboard.erp.booking"> + <de><![CDATA[Buchung]]></de> + <en><![CDATA[Booking]]></en> + </locale> + <locale name="dashboard.erp.contract"> + <de><![CDATA[Vertrag]]></de> + <en><![CDATA[Contract]]></en> + </locale> + <locale name="dashboard.erp.transactions"> + <de><![CDATA[Zahlungen / Transaktionen]]></de> + <en><![CDATA[Payments / Transactions]]></en> + </locale> + <locale name="dashboard.erp.deliveryNotes"> + <de><![CDATA[Lieferschein]]></de> + <en><![CDATA[Delivery Notes]]></en> + </locale> + <locale name="panel.globalProcess.title"> + <de><![CDATA[Prozess: [globalProcessId]]]></de> + <en><![CDATA[Process: [globalProcessId]]]></en> + </locale> + </groups> </locales> diff --git a/package.xml b/package.xml index 8c647872e40cb8aaab633945fe71e2b7fb53d252..3ad5c2677709a4eee742db49b94e66e6a25d344f 100644 --- a/package.xml +++ b/package.xml @@ -36,7 +36,7 @@ <provider> <erp src="\QUI\ERP\Provider\Erp"/> <requirements src="\QUI\ERP\Provider\Requirements"/> - <!-- <dashboard src="\QUI\ERP\Dashboard\DashboardProvider"/>--> + <dashboard src="\QUI\ERP\Provider\DashboardProvider"/> </provider> </package> </quiqqer> diff --git a/src/QUI/ERP/Accounting/Article.php b/src/QUI/ERP/Accounting/Article.php index 5a35b3f651b6997eb7bb4d3d5801fbada50660f6..da134f06cd7b3c8082adf999bad6d6fa9aeb5259 100644 --- a/src/QUI/ERP/Accounting/Article.php +++ b/src/QUI/ERP/Accounting/Article.php @@ -121,6 +121,8 @@ class Article implements ArticleInterface */ protected $isNetto; + protected float $position = 0; + /** * @var ArticleDiscount|null */ @@ -169,6 +171,9 @@ public function __construct($attributes = []) } else { $this->attributes['vat'] = ''; } + if (isset($attributes['position'])) { + $this->position = (float)$attributes['position']; + } if (isset($attributes['discount'])) { $this->Discount = ArticleDiscount::unserialize($attributes['discount']); @@ -738,6 +743,7 @@ public function toArray(): array 'class' => $class, 'customFields' => $this->customFields, 'customData' => $this->customData, + 'position' => $this->position, // calculated data 'calculated' => [ diff --git a/src/QUI/ERP/Accounting/ArticleListUnique.php b/src/QUI/ERP/Accounting/ArticleListUnique.php index b767b865d312cda0ae42ec379563b7956b651e51..2cf4db9e56e38ab0fcf16f910b9a407b0b1be79c 100644 --- a/src/QUI/ERP/Accounting/ArticleListUnique.php +++ b/src/QUI/ERP/Accounting/ArticleListUnique.php @@ -111,6 +111,10 @@ public function __construct(array $attributes = [], $User = null) $currency = $attributes['calculations']['currencyData']['code']; } + // sorting + $articles = $this->sortArticlesWithParents($articles); + + // adding foreach ($articles as $article) { if (!isset($article['currency'])) { $article['currency'] = $currency; @@ -166,6 +170,63 @@ public function __construct(array $attributes = [], $User = null) } } + /** + * Sorts items within the list by parent-child relationship. + * + * Items without `productSetParentUuid` are considered parents and positioned before their children, + * with each child directly assigned to its parent via `productSetParentUuid`. + * + * Children follow immediately after their parents in the sorted list. + * Each item is assigned a consecutive position, which reflects its order in the sorted list. + * + * @param array $articles - The input list of items, articles + * @return array The sorted list of items with added 'position' keys, starting with 1. + */ + protected function sortArticlesWithParents(array $articles = []): array + { + if (empty($articles)) { + return []; + } + + $sortedArticles = []; + $children = []; + + foreach ($articles as $article) { + if (!empty($article['productSetParentUuid'])) { + $children[$article['productSetParentUuid']][] = $article; + } + } + + $positionCounter = 1; + + foreach ($articles as $article) { + if (!empty($article['productSetParentUuid'])) { + continue; + } + + if (empty($article['uuid'])) { + continue; + } + + $article['position'] = $positionCounter; + $sortedArticles[] = $article; + $uuid = $article['uuid']; + + if (isset($children[$uuid])) { + $subPosition = 0.1; + foreach ($children[$uuid] as $child) { + $child['position'] = $positionCounter + $subPosition; + $sortedArticles[] = $child; + $subPosition += 0.1; + } + } + + $positionCounter++; + } + + return $sortedArticles; + } + /** * placeholder. unique list cant be recalculate * recalculate makes the unique article list compatible to the article list @@ -368,17 +429,21 @@ public function toHTML($template = false): string $this->calculations['nettoSum'] = $Currency->format($this->calculations['nettoSum']); $this->calculations['nettoSubSum'] = $Currency->format($this->calculations['nettoSubSum']); - $pos = 1; + $articles = []; - $articles = array_map(function ($Article) use ($Currency, &$pos) { + foreach ($this->articles as $Article) { $View = $Article->getView(); $View->setCurrency($Currency); - $View->setPosition($pos); + $position = $View->getPosition(); - $pos++; + if (floor($position) % 2) { + $View->setAttribute('odd', true); + } else { + $View->setAttribute('even', true); + } - return $View; - }, $this->articles); + $articles[] = $View; + } $ExchangeCurrency = $this->ExchangeCurrency; $showExchangeRate = $this->showExchangeRate; @@ -392,8 +457,13 @@ public function toHTML($template = false): string $Currency->setExchangeRate($this->exchangeRate); } - $exchangeRate = $Currency->getExchangeRate($ExchangeCurrency); - $exchangeRate = $ExchangeCurrency->format($exchangeRate); + if ($Currency instanceof QUI\ERP\CryptoCurrency\Currency) { + $ExchangeCurrency->setExchangeRate($this->exchangeRate); + $exchangeRate = $Currency->convertFormat(1, $ExchangeCurrency); + } else { + $exchangeRate = $Currency->getExchangeRate($ExchangeCurrency); + $exchangeRate = $ExchangeCurrency->format($exchangeRate); + } $exchangeRateText = $this->Locale->get('quiqqer/erp', 'exchangerate.text', [ 'startCurrency' => $Currency->format(1), @@ -404,12 +474,17 @@ public function toHTML($template = false): string // if currency of list is other currency like the default one // currency = BTC, Default = EUR // exchange rate must be displayed - if ($ExchangeCurrency && $ExchangeCurrency->getCode() !== QUI\ERP\Defaults::getCurrency()->getCode()) { + if ($Currency->getCode() !== QUI\ERP\Defaults::getCurrency()->getCode()) { $showExchangeRate = true; $DefaultCurrency = QUI\ERP\Defaults::getCurrency(); - $exchangeRate = $ExchangeCurrency->getExchangeRate($DefaultCurrency); - $exchangeRate = $ExchangeCurrency->format($exchangeRate); + if ($Currency instanceof QUI\ERP\CryptoCurrency\Currency) { + $DefaultCurrency->setExchangeRate($this->exchangeRate); + $exchangeRate = $Currency->convertFormat(1, $DefaultCurrency); + } else { + $exchangeRate = $Currency->getExchangeRate($DefaultCurrency); + $exchangeRate = $DefaultCurrency->format($exchangeRate); + } $exchangeRateText = $this->Locale->get('quiqqer/erp', 'exchangerate.text', [ 'startCurrency' => $DefaultCurrency->format(1), diff --git a/src/QUI/ERP/Accounting/ArticleView.html b/src/QUI/ERP/Accounting/ArticleView.html index 689d4391c5a4023693bbbcc8ce698eb5858b3b39..01ca1f5d498cda97348d096aa14aa4b0d8d696c1 100644 --- a/src/QUI/ERP/Accounting/ArticleView.html +++ b/src/QUI/ERP/Accounting/ArticleView.html @@ -1,4 +1,4 @@ -<tr> +<tr class="{$cssClasses}"> <td class="articles-article-pos" data-label="{locale group='quiqqer/erp' var='article.list.articles.header.pos'}" > @@ -36,11 +36,11 @@ </ul> </td> {if $hasAppliedVat} - <td class="articles-article-vat" - data-label="{locale group='quiqqer/erp' var='article.list.articles.header.vat'}" - > - {$this->getAttribute('vat')}% - </td> + <td class="articles-article-vat" + data-label="{locale group='quiqqer/erp' var='article.list.articles.header.vat'}" + > + {$this->getAttribute('vat')}% + </td> {/if} {if $this->getAttribute('quantity') !== false} diff --git a/src/QUI/ERP/Accounting/ArticleView.php b/src/QUI/ERP/Accounting/ArticleView.php index a54e40bf111d2368e8fadf47bc60f4440ce42d30..a05a842fa85621b43f3ce109907074659a28143d 100644 --- a/src/QUI/ERP/Accounting/ArticleView.php +++ b/src/QUI/ERP/Accounting/ArticleView.php @@ -9,6 +9,8 @@ use QUI; use QUI\ERP\Accounting\Calc as ErpCalc; +use function implode; + /** * Class ArticleView * @@ -17,9 +19,9 @@ class ArticleView extends QUI\QDOM { /** - * @var int + * @var float */ - protected int $position; + protected float $position; /** * @var Article @@ -39,6 +41,10 @@ public function __construct(Article $Article) { $this->Article = $Article; $this->setAttributes($this->Article->toArray()); + + if ($this->getAttribute('position')) { + $this->position = (float)$this->getAttribute('position'); + } } /** @@ -54,7 +60,7 @@ public function getQuantityUnit(): string * * @param QUI\ERP\Currency\Currency $Currency */ - public function setCurrency(QUI\ERP\Currency\Currency $Currency) + public function setCurrency(QUI\ERP\Currency\Currency $Currency): void { $this->Currency = $Currency; } @@ -64,9 +70,17 @@ public function setCurrency(QUI\ERP\Currency\Currency $Currency) * * @param $position */ - public function setPosition($position) + public function setPosition($position): void + { + $this->position = (float)$position; + } + + /** + * @return float + */ + public function getPosition(): float { - $this->position = (int)$position; + return $this->position; } /** @@ -191,9 +205,27 @@ public function toHTML(): string } $articleData = $this->Article->toArray(); + $cssClasses = ['articles-article-entry']; + + if (!empty($this->Article->getProductSetParentUuid())) { + $cssClasses[] = 'articles-article--additional'; + } else { + $cssClasses[] = 'articles-article--real'; + } + + if ($this->getAttribute('odd')) { + $cssClasses[] = 'articles-article--odd'; + } + + if ($this->getAttribute('even')) { + $cssClasses[] = 'articles-article--even'; + } $Engine->assign([ 'this' => $this, + 'uuid' => $articleData['uuid'], + 'cssClasses' => implode(' ', $cssClasses), + 'productSetParentUuid' => $articleData['productSetParentUuid'], 'position' => $this->position, 'unitPrice' => $Currency->format($article['unitPrice']), 'sum' => $Currency->format($article['sum']), diff --git a/src/QUI/ERP/Comments.php b/src/QUI/ERP/Comments.php index 09f499e029f7bc96d6c404647b8a5d38a8945b9e..e7cd8a974ac288d009f35a950d5d018d42ee892d 100644 --- a/src/QUI/ERP/Comments.php +++ b/src/QUI/ERP/Comments.php @@ -119,17 +119,18 @@ public function toArray(): array * Add a comment * * @param string $message - * @param int|false $time - optional, unix timestamp + * @param bool|int $time - optional, unix timestamp * @param string $source - optional, name of the package * @param string $sourceIcon - optional, source icon - * @param string|false $id - optional, comment id, if needed, it will set one + * @param bool|string $id - optional, comment id, if needed, it will set one */ public function addComment( string $message, - $time = false, + bool|int $time = false, string $source = '', string $sourceIcon = '', - $id = false + bool|string $id = false, + bool|string $objectHash = false ) { if ($time === false) { $time = time(); @@ -151,14 +152,15 @@ public function addComment( 'time' => (int)$time, 'source' => $source, 'sourceIcon' => $sourceIcon, - 'id' => $id + 'id' => $id, + 'objectHash' => $objectHash ]; } /** * Clear all comments */ - public function clear() + public function clear(): void { $this->comments = []; } @@ -166,7 +168,7 @@ public function clear() /** * Sort all comments via its time */ - public function sort() + public function sort(): void { usort($this->comments, function ($commentA, $commentB) { if ($commentA['time'] == $commentB['time']) { @@ -182,7 +184,7 @@ public function sort() * * @param Comments $Comments */ - public function import(Comments $Comments) + public function import(Comments $Comments): void { $comments = $Comments->toArray(); @@ -204,7 +206,8 @@ public function import(Comments $Comments) $comment['time'], $comment['source'], $comment['sourceIcon'], - $comment['id'] + $comment['id'], + $comment['objectHash'] ?? '', ); } @@ -228,7 +231,7 @@ public static function getCommentsByUser(QUI\Users\User $User): ?Comments if ($User->getAttribute('comments')) { $isEditable = QUI\Permissions\Permission::hasPermission('quiqqer.customer.editComments'); - $json = json_decode($User->getAttribute('comments'), true); + $json = json_decode($User->getAttribute('comments'), true); if (!is_array($json)) { $json = []; diff --git a/src/QUI/ERP/Dashboard/Dashboard.php b/src/QUI/ERP/Dashboard/ErpDashboard.php similarity index 69% rename from src/QUI/ERP/Dashboard/Dashboard.php rename to src/QUI/ERP/Dashboard/ErpDashboard.php index 734f5350d5d606d84d54c39985f1e3eae1fb49f4..053478d8c9f6c2ab81ebeeed4c6c67416414c0a2 100644 --- a/src/QUI/ERP/Dashboard/Dashboard.php +++ b/src/QUI/ERP/Dashboard/ErpDashboard.php @@ -10,7 +10,7 @@ * * @package QUI\LoginLogger */ -class Dashboard implements DashboardInterface +class ErpDashboard implements DashboardInterface { /** * @param null $Locale @@ -22,7 +22,7 @@ public function getTitle($Locale = null): string $Locale = QUI::getLocale(); } - return $Locale->get('quiqqer/erp', 'dashboard.title'); + return $Locale->get('quiqqer/erp', 'dashboard.erp.title'); } /** @@ -30,7 +30,9 @@ public function getTitle($Locale = null): string */ public function getCards(): array { - return []; + return [ + 'package/quiqqer/erp/bin/backend/controls/dashboard/cards/GlobalProcessIdList' + ]; } public function getJavaScriptControl(): string diff --git a/src/QUI/ERP/Process.php b/src/QUI/ERP/Process.php index bb5161809bedab46a153b009e78fef5b38c1b8e7..791361ba63f90d8a2f7b609227e270ca707f9b15 100644 --- a/src/QUI/ERP/Process.php +++ b/src/QUI/ERP/Process.php @@ -7,12 +7,18 @@ namespace QUI\ERP; use QUI; +use QUI\ERP\Accounting\Offers\Handler as OfferHandler; +use QUI\ERP\Booking\Table as BookingTable; +use QUI\ERP\Purchasing\Processes\Handler as PurchasingHandler; +use QUI\ERP\SalesOrders\Handler as SalesOrdersHandler; use function count; +use function strtotime; /** * Class Process * - represents a complete erp process + * - Vorgangsnummer * * @package QUI\ERP */ @@ -59,9 +65,9 @@ protected function table(): string * Add a comment to the history for the complete process * * @param string $message - * @param int|bool $time - optional, unix timestamp + * @param bool|int $time - optional, unix timestamp */ - public function addHistory(string $message, $time = false) + public function addHistory(string $message, bool|int $time = false): void { $this->getHistory()->addComment($message, $time); @@ -91,7 +97,7 @@ public function getHistory(): Comments try { $result = QUI::getDataBase()->fetch([ - 'from' => $this->table(), + 'from' => $this->table(), 'where' => [ 'id' => $this->processId ], @@ -125,15 +131,24 @@ public function getCompleteHistory(): Comments { $History = $this->getHistory(); - $invoices = $this->getInvoices(); - $orders = $this->getOrders(); + $this->parseBookings($History); + $this->parseInvoices($History); + $this->parseOffers($History); + $this->parseOrders($History); + $this->parsePurchasing($History); + $this->parseSalesOrders($History); + $this->parseTransactions($History); - foreach ($invoices as $Invoice) { - $History->import($Invoice->getHistory()); + try { + QUI::getEvents()->fireEvent('quiqqerErpGetCompleteHistory', [$this, $this->processId]); + } catch (\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); } - foreach ($orders as $Order) { - $History->import($Order->getHistory()); + try { + QUI::getEvents()->fireEvent('quiqqerErpProcessHistory', [$this, $this->processId]); + } catch (\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); } return $History; @@ -143,6 +158,45 @@ public function getCompleteHistory(): Comments //region invoice + protected function parseInvoices(Comments $History): void + { + $invoices = $this->getInvoices(); + + foreach ($invoices as $Invoice) { + $History->addComment( + QUI::getLocale()->get('quiqqer/erp', 'process.history.invoice.created', [ + 'hash' => $Invoice->getHash() + ]), + strtotime($Invoice->getAttribute('date')), + 'quiqqer/invoice', + 'fa fa-file-text-o', + false, + $Invoice->getHash() + ); + + $history = $Invoice->getHistory()->toArray(); + + foreach ($history as $entry) { + if (empty($entry['source'])) { + $entry['source'] = 'quiqqer/invoice'; + } + + if (empty($entry['sourceIcon'])) { + $entry['sourceIcon'] = 'fa fa-file-text-o'; + } + + $History->addComment( + $entry['message'], + $entry['time'], + $entry['source'], + $entry['sourceIcon'], + $entry['id'], + $Invoice->getHash() + ); + } + } + } + /** * Return if the process has invoices or not * @@ -184,6 +238,10 @@ public function hasTemporaryInvoice(): bool */ public function getInvoices(): array { + if (!QUI::getPackageManager()->isInstalled('quiqqer/invoice')) { + return []; + } + try { return QUI\ERP\Accounting\Invoice\Handler::getInstance()->getInvoicesByGlobalProcessId($this->processId); } catch (\QUI\Exception $Exception) { @@ -195,6 +253,55 @@ public function getInvoices(): array //region order + protected function parseOrders(Comments $History): void + { + // orders + $orders = $this->getOrders(); + + foreach ($orders as $Order) { + $history = $Order->getHistory()->toArray(); + $hasCreateMessage = false; + $createMessage = QUI::getLocale()->get('quiqqer/erp', 'process.history.order.created', [ + 'hash' => $Order->getHash() + ]); + + foreach ($history as $entry) { + if ($entry['message'] === $createMessage) { + $hasCreateMessage = true; + break; + } + } + + if ($hasCreateMessage === false) { + $History->addComment( + $createMessage, + strtotime($Order->getCreateDate()), + 'quiqqer/order', + 'fa fa-shopping-basket' + ); + } + + foreach ($history as $entry) { + if (empty($entry['source'])) { + $entry['source'] = 'quiqqer/order'; + } + + if (empty($entry['sourceIcon'])) { + $entry['sourceIcon'] = 'fa fa-shopping-basket'; + } + + $History->addComment( + $entry['message'], + $entry['time'], + $entry['source'], + $entry['sourceIcon'], + $entry['id'], + $Order->getHash() + ); + } + } + } + /** * @return bool */ @@ -208,11 +315,9 @@ public function hasOrder(): bool * * @return null|Order\Order|Order\OrderInProcess */ - public function getOrder() + public function getOrder(): Order\OrderInProcess|Order\Order|null { - try { - QUI::getPackage('quiqqer/order'); - } catch (QUI\Exception $Exception) { + if (!QUI::getPackageManager()->isInstalled('quiqqer/order')) { return null; } @@ -236,13 +341,15 @@ public function getOrder() /** * Return all orders from the process * - * @return array|Order\Order|Order\Order[]|Order\OrderInProcess + * @return array|Order\Order|Order\Order[]|Order\OrderInProcess[] */ - public function getOrders() + public function getOrders(): array { - try { - QUI::getPackage('quiqqer/order'); + if (!QUI::getPackageManager()->isInstalled('quiqqer/order')) { + return []; + } + try { return QUI\ERP\Order\Handler::getInstance()->getOrdersByGlobalProcessId($this->processId); } catch (QUI\Exception $Exception) { return []; @@ -251,7 +358,340 @@ public function getOrders() //endregion + //region offers + protected function parseOffers(Comments $History): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/offers')) { + return; + } + + // orders + $offers = $this->getOffers(); + + foreach ($offers as $Offer) { + $History->addComment( + QUI::getLocale()->get('quiqqer/erp', 'process.history.offer.created', [ + 'hash' => $Offer->getHash() + ]), + strtotime($Offer->getAttribute('date')), + 'quiqqer/offer', + 'fa fa-file-text-o', + false, + $Offer->getHash() + ); + + $history = $Offer->getHistory()->toArray(); + + foreach ($history as $entry) { + if (empty($entry['source'])) { + $entry['source'] = 'quiqqer/offer'; + } + + if (empty($entry['sourceIcon'])) { + $entry['sourceIcon'] = 'fa fa-file-text-o'; + } + + $History->addComment( + $entry['message'], + $entry['time'], + $entry['source'], + $entry['sourceIcon'], + $entry['id'], + $Offer->getHash() + ); + } + } + } + + /** + * @return QUI\ERP\Accounting\Offers\Offer[] + */ + public function getOffers(): array + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/offers')) { + return []; + } + + try { + $offers = QUI::getDatabase()->fetch([ + 'select' => 'id,hash,global_process_id,date', + 'from' => OfferHandler::getInstance()->offersTable(), + 'where_or' => [ + 'global_process_id' => $this->processId, + 'hash' => $this->processId + ] + ]); + } catch (\Exception) { + return []; + } + + $result = []; + $Offers = OfferHandler::getInstance(); + + foreach ($offers as $offer) { + try { + $result[] = $Offers->getOffer($offer['id']); + } catch (\Exception) { + } + } + + return $result; + } + + //endregion + + //region booking + protected function parseBookings(Comments $History): void + { + // orders + $bookings = $this->getBookings(); + + foreach ($bookings as $Booking) { + $History->addComment( + QUI::getLocale()->get('quiqqer/erp', 'process.history.booking.created', [ + 'hash' => $Booking->getUuid() + ]), + $Booking->getCreateDate()->getTimestamp(), + 'quiqqer/booking', + 'fa fa-ticket', + false, + $Booking->getUuid() + ); + + $history = $Booking->getHistory()->toArray(); + + foreach ($history as $entry) { + if (empty($entry['source'])) { + $entry['source'] = 'quiqqer/booking'; + } + + if (empty($entry['sourceIcon'])) { + $entry['sourceIcon'] = 'fa fa-ticket'; + } + + $History->addComment( + $entry['message'], + $entry['time'], + $entry['source'], + $entry['sourceIcon'], + $entry['id'], + $Booking->getUuid() + ); + } + } + } + + /** + * @return array + */ + public function getBookings(): array + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/booking')) { + return []; + } + + try { + $bookings = QUI::getDatabase()->fetch([ + 'select' => 'uuid,globalProcessId,createDate', + 'from' => BookingTable::BOOKINGS->tableName(), + 'where_or' => [ + 'globalProcessId' => $this->processId, + 'uuid' => $this->processId + ] + ]); + } catch (\Exception) { + return []; + } + + $result = []; + $BookingRepository = new QUI\ERP\Booking\Repository\BookingRepository(); + + foreach ($bookings as $booking) { + try { + $result[] = $BookingRepository->getByUuid($booking['uuid']); + } catch (\Exception) { + } + } + + return $result; + } + + //endregion + + //region purchase / Einkauf + protected function parsePurchasing(Comments $History): void + { + // orders + $purchasing = $this->getPurchasing(); + + foreach ($purchasing as $Purchasing) { + $History->addComment( + QUI::getLocale()->get('quiqqer/erp', 'process.history.purchasing.created', [ + 'hash' => $Purchasing->getHash() + ]), + strtotime($Purchasing->getAttribute('c_date')), + 'quiqqer/purchasing', + 'fa fa-cart-arrow-down', + false, + $Purchasing->getHash() + ); + + $history = $Purchasing->getHistory()->toArray(); + + foreach ($history as $entry) { + if (empty($entry['source'])) { + $entry['source'] = 'quiqqer/purchasing'; + } + + if (empty($entry['sourceIcon'])) { + $entry['sourceIcon'] = 'fa fa-cart-arrow-down'; + } + + $History->addComment( + $entry['message'], + $entry['time'], + $entry['source'], + $entry['sourceIcon'], + $entry['id'], + $Purchasing->getHash() + ); + } + } + } + + /** + * @return QUI\ERP\Purchasing\Processes\PurchasingProcess[] + */ + public function getPurchasing(): array + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/purchasing')) { + return []; + } + + try { + $purchasing = QUI::getDatabase()->fetch([ + 'select' => 'id,hash,global_process_id,date', + 'from' => PurchasingHandler::getTablePurchasingProcesses(), + 'where_or' => [ + 'global_process_id' => $this->processId, + 'hash' => $this->processId + ] + ]); + } catch (\Exception) { + return []; + } + + $result = []; + + foreach ($purchasing as $process) { + try { + $result[] = PurchasingHandler::getPurchasingProcess($process['id']); + } catch (\Exception) { + } + } + + return $result; + } + + //endregion + + //region sales orders / Aufträge + protected function parseSalesOrders(Comments $History): void + { + // orders + $salesOrders = $this->getSalesOrders(); + + foreach ($salesOrders as $SalesOrder) { + $History->addComment( + QUI::getLocale()->get('quiqqer/erp', 'process.history.salesorders.created', [ + 'hash' => $SalesOrder->getHash() + ]), + strtotime($SalesOrder->getAttribute('c_date')), + 'quiqqer/salesorders', + 'fa fa-suitcase', + false, + $SalesOrder->getHash() + ); + + $history = $SalesOrder->getHistory()->toArray(); + + foreach ($history as $entry) { + if (empty($entry['source'])) { + $entry['source'] = 'quiqqer/salesorders'; + } + + if (empty($entry['sourceIcon'])) { + $entry['sourceIcon'] = 'fa fa-suitcase'; + } + + $History->addComment( + $entry['message'], + $entry['time'], + $entry['source'], + $entry['sourceIcon'], + $entry['id'], + $SalesOrder->getHash() + ); + } + } + } + + /** + * @return QUI\ERP\Purchasing\Processes\PurchasingProcess[] + */ + public function getSalesOrders(): array + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/salesorders')) { + return []; + } + + try { + $salesOrders = QUI::getDatabase()->fetch([ + 'select' => 'id,hash,global_process_id,date', + 'from' => SalesOrdersHandler::getTableSalesOrders(), + 'where_or' => [ + 'global_process_id' => $this->processId, + 'hash' => $this->processId + ] + ]); + } catch (\Exception) { + return []; + } + + $result = []; + + foreach ($salesOrders as $salesOrder) { + try { + $result[] = SalesOrdersHandler::getSalesOrder($salesOrder['id']); + } catch (\Exception) { + } + } + + return $result; + } + + //endregion + //region transactions + protected function parseTransactions(Comments $History): void + { + // orders + $transactions = $this->getTransactions(); + + foreach ($transactions as $Transaction) { + $History->addComment( + QUI::getLocale()->get('quiqqer/erp', 'process.history.transaction.created', [ + 'hash' => $Transaction->getHash(), + 'amount' => $Transaction->getAmountFormatted() + ]), + strtotime($Transaction->getDate()), + 'quiqqer/payment-transaction', + 'fa fa-money', + false, + $Transaction->getHash() + ); + } + } /** * @return bool @@ -268,11 +708,9 @@ public function hasTransactions(): bool * * @return QUI\ERP\Accounting\Payments\Transactions\Transaction[]; */ - public function getTransactions(): ?array + public function getTransactions(): array { - try { - QUI::getPackage('quiqqer/payment-transactions'); - } catch (QUI\Exception $Exception) { + if (!QUI::getPackageManager()->isInstalled('quiqqer/payment-transactions')) { return []; } @@ -280,7 +718,7 @@ public function getTransactions(): ?array return $this->transactions; } - $Transactions = QUI\ERP\Accounting\Payments\Transactions\Handler::getInstance(); + $Transactions = QUI\ERP\Accounting\Payments\Transactions\Handler::getInstance(); $this->transactions = $Transactions->getTransactionsByProcessId($this->processId); return $this->transactions; diff --git a/src/QUI/ERP/Processes.php b/src/QUI/ERP/Processes.php new file mode 100644 index 0000000000000000000000000000000000000000..0047273ac2171fccb64daec9958b0490aadc856f --- /dev/null +++ b/src/QUI/ERP/Processes.php @@ -0,0 +1,393 @@ +<?php + +/** + * This file contains QUI\ERP\Processes + */ + +namespace QUI\ERP; + +use QUI; +use QUI\ERP\Accounting\Invoice\Handler as InvoiceHandler; +use QUI\ERP\Accounting\Offers\Handler as OfferHandler; +use QUI\ERP\Accounting\Payments\Transactions\Factory as TransactionFactory; +use QUI\ERP\Booking\Table as BookingTable; +use QUI\ERP\Order\Handler as OrderHandler; +use QUI\ERP\Purchasing\Processes\Handler as PurchasingHandler; +use QUI\ERP\SalesOrders\Handler as SalesOrdersHandler; + +/** + * + * Reads following modules for global process id entities + * - quiqqer/invoice + * - quiqqer/order + * - quiqqer/offer + * - quiqqer/salesorders + * - quiqqer/purchasing + * - quiqqer/booking + * - quiqqer/contracts + * - quiqqer/payments + * - quiqqer/payment-transactions + * - quiqqer/delivery-notes + */ +class Processes +{ + protected array $list = []; + + /** + * @return array + */ + public function getList(): array + { + try { + $this->readBooking(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + try { + $this->readInvoices(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + try { + $this->readOffers(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + try { + $this->readOrders(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + try { + $this->readPurchasing(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + try { + $this->readSalesOrders(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + try { + $this->readTransactions(); + } catch (QUI\Database\Exception $exception) { + QUI\System\Log::addError($exception->getMessage()); + } + + uasort($this->list, function ($a, $b) { + return strtotime($b['date']) - strtotime($a['date']); + }); + + return $this->list; + } + + /** + * Returns the plugin list with which plugins the processes can handle + * + * @return string[] + */ + public function getWantedPluginList(): array + { + return [ + 'quiqqer/booking', + 'quiqqer/contracts', + 'quiqqer/delivery-notes', + 'quiqqer/invoice', + 'quiqqer/offers', + 'quiqqer/order', + 'quiqqer/payments', + 'quiqqer/payment-transactions', + 'quiqqer/purchasing', + 'quiqqer/salesorders' + ]; + } + + //region read databases + + /** + * Rads all invoices + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readInvoices(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/invoice')) { + return; + } + + $invoices = QUI::getDatabase()->fetch([ + 'select' => 'hash,global_process_id,date', + 'from' => InvoiceHandler::getInstance()->invoiceTable() + ]); + + foreach ($invoices as $invoice) { + $gpi = $invoice['global_process_id']; + + if (empty($gpi)) { + $gpi = $invoice['hash']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $invoice['date'] + ); + + $this->list[$gpi]['invoice'] = $invoice['hash']; + } + } + + /** + * Rads all invoices + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readOrders(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/order')) { + return; + } + + $orders = QUI::getDatabase()->fetch([ + 'select' => 'hash,global_process_id,c_date', + 'from' => OrderHandler::getInstance()->table() + ]); + + foreach ($orders as $order) { + $gpi = $order['global_process_id']; + + if (empty($gpi)) { + $gpi = $order['hash']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $order['c_date'] + ); + + $this->list[$gpi]['order'] = $order['hash']; + } + } + + /** + * Read all offers + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readOffers(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/offers')) { + return; + } + + $offers = QUI::getDatabase()->fetch([ + 'select' => 'hash,global_process_id,date', + 'from' => OfferHandler::getInstance()->offersTable() + ]); + + foreach ($offers as $offer) { + $gpi = $offer['global_process_id']; + + if (empty($gpi)) { + $gpi = $offer['hash']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $offer['date'] + ); + + $this->list[$gpi]['offer'] = $offer['hash']; + } + } + + /** + * Read all sales orders + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readSalesOrders(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/salesorders')) { + return; + } + + $salesOrders = QUI::getDatabase()->fetch([ + 'select' => 'hash,global_process_id,date', + 'from' => SalesOrdersHandler::getTableSalesOrders() + ]); + + foreach ($salesOrders as $salesOrder) { + $gpi = $salesOrder['global_process_id']; + + if (empty($gpi)) { + $gpi = $salesOrder['hash']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $salesOrder['date'] + ); + + $this->list[$gpi]['salesorders'] = $salesOrder['hash']; + } + } + + /** + * Read all sales orders + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readTransactions(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/payment-transactions')) { + return; + } + + $transactions = QUI::getDatabase()->fetch([ + 'select' => 'hash,global_process_id,date', + 'from' => TransactionFactory::table() + ]); + + foreach ($transactions as $transaction) { + $gpi = $transaction['global_process_id']; + + if (empty($gpi)) { + $gpi = $transaction['hash']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $transaction['date'] + ); + + $this->list[$gpi]['transactions'] = $transaction['hash']; + } + } + + /** + * Read all purchases + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readPurchasing(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/purchasing')) { + return; + } + + $purchasing = QUI::getDatabase()->fetch([ + 'select' => 'hash,global_process_id,date', + 'from' => PurchasingHandler::getTablePurchasingProcesses() + ]); + + foreach ($purchasing as $entry) { + $gpi = $entry['global_process_id']; + + if (empty($gpi)) { + $gpi = $entry['hash']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $entry['date'] + ); + + $this->list[$gpi]['purchasing'] = $entry['hash']; + } + } + + /** + * Read all purchases + * + * @return void + * @throws QUI\Database\Exception + */ + protected function readBooking(): void + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/booking')) { + return; + } + + $bookings = QUI::getDatabase()->fetch([ + 'select' => 'uuid,globalProcessId,createDate', + 'from' => BookingTable::BOOKINGS->tableName(), + ]); + + foreach ($bookings as $booking) { + $gpi = $booking['globalProcessId']; + + if (empty($gpi)) { + $gpi = $booking['uuid']; + } + + if (!isset($this->list[$gpi])) { + $this->list[$gpi] = []; + } + + $this->list[$gpi]['date'] = $this->getEarlierDate( + $this->list[$gpi]['date'] ?? null, + $booking['createDate'] + ); + + $this->list[$gpi]['booking'] = $booking['uuid']; + } + } + + //endregion + + //region utils + + protected function getEarlierDate($date1, $date2) + { + $timestamp1 = strtotime($date1); + $timestamp2 = strtotime($date2); + + if ($date1 === null && $date2) { + return $date2; + } + + if ($date1 && $date2 === null) { + return $date1; + } + + return ($timestamp1 < $timestamp2) ? $date1 : $date2; + } + + //endregion +} diff --git a/src/QUI/ERP/Dashboard/DashboardProvider.php b/src/QUI/ERP/Provider/DashboardProvider.php similarity index 53% rename from src/QUI/ERP/Dashboard/DashboardProvider.php rename to src/QUI/ERP/Provider/DashboardProvider.php index eddc64e2c120d34f30a61ac3192def07f6457238..71ae31a1ec02fe08d118fa46acfaf1fc9b631e25 100644 --- a/src/QUI/ERP/Dashboard/DashboardProvider.php +++ b/src/QUI/ERP/Provider/DashboardProvider.php @@ -1,24 +1,19 @@ <?php -namespace QUI\ERP\Dashboard; +namespace QUI\ERP\Provider; +use QUI; use QUI\Dashboard\DashboardProviderInterface; -/** - * Class DashboardProvider - * - * @package QUI\LoginLogger - */ class DashboardProvider implements DashboardProviderInterface { - /** - * @inheritDoc - * - * @return array - */ - public static function getCards(): array + public function getTitle($Locale = null): string { - return []; + if ($Locale === null) { + $Locale = QUI::getLocale(); + } + + return $Locale->get('quiqqer/erp', 'dashboard.provider.title'); } /** @@ -27,7 +22,15 @@ public static function getCards(): array public static function getBoards(): array { return [ - new Dashboard() + new QUI\ERP\Dashboard\ErpDashboard() ]; } + + /** + * @return array + */ + public static function getCards(): array + { + return []; + } } diff --git a/src/QUI/ERP/Utils/Process.php b/src/QUI/ERP/Utils/Process.php index dacf1e3f13e1159fda0c6db1cc18730d74c90465..77c82775fa8974f13e8894ad5546187e60f4d8df 100644 --- a/src/QUI/ERP/Utils/Process.php +++ b/src/QUI/ERP/Utils/Process.php @@ -22,6 +22,8 @@ class Process * * @param string $hash - process hash * @return array + * + * @deprecated */ public static function getProcessInformation($hash) { @@ -31,7 +33,7 @@ public static function getProcessInformation($hash) try { QUI::getPackage('quiqqer/order'); - $Order = QUI\ERP\Order\Handler::getInstance()->getOrderByHash($hash); + $Order = QUI\ERP\Order\Handler::getInstance()->getOrderByHash($hash); $result['order'] = $Order->getAttributes(); } catch (QUI\Exception $Exception) { QUI\System\Log::writeDebugException($Exception); @@ -41,7 +43,7 @@ public static function getProcessInformation($hash) try { QUI::getPackage('quiqqer/invoice'); - $Invoice = QUI\ERP\Accounting\Invoice\Utils\Invoice::getInvoiceByString($hash); + $Invoice = QUI\ERP\Accounting\Invoice\Utils\Invoice::getInvoiceByString($hash); $result['invoice'] = $Invoice->getAttributes(); } catch (QUI\Exception $Exception) { QUI\System\Log::writeDebugException($Exception);