From c1e7e559ba54aaf2cea0b8a155f10d455ec4553b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de> Date: Thu, 21 Jan 2021 16:26:14 +0100 Subject: [PATCH] feat: open items management (temp commit) --- .../OpenItemsList/getUserOpenItems.php | 158 +++- ajax/backend/OpenItemsList/search.php | 14 +- .../controls/OpenItems/OpenItems.Total.html | 64 +- .../OpenItems/OpenItems.UserRecords.html | 5 + bin/backend/controls/OpenItems/OpenItems.css | 33 +- bin/backend/controls/OpenItems/OpenItems.js | 789 ++++++++++++------ events.xml | 6 + locale.xml | 95 +++ settings.xml | 23 + src/QUI/ERP/Customer/OpenItemsList/Events.php | 118 +++ .../ERP/Customer/OpenItemsList/Handler.php | 199 ++++- src/QUI/ERP/Customer/OpenItemsList/Item.php | 12 + 12 files changed, 1196 insertions(+), 320 deletions(-) create mode 100644 bin/backend/controls/OpenItems/OpenItems.UserRecords.html create mode 100644 src/QUI/ERP/Customer/OpenItemsList/Events.php diff --git a/ajax/backend/OpenItemsList/getUserOpenItems.php b/ajax/backend/OpenItemsList/getUserOpenItems.php index 71bb7aa..7c2a26b 100644 --- a/ajax/backend/OpenItemsList/getUserOpenItems.php +++ b/ajax/backend/OpenItemsList/getUserOpenItems.php @@ -1,8 +1,8 @@ <?php -use QUI\Utils\Grid; use QUI\Utils\Security\Orthos; use QUI\ERP\Customer\OpenItemsList\Handler; +use QUI\Cache\Manager as QUICacheManager; /** * Search open items list @@ -12,12 +12,151 @@ */ QUI::$Ajax->registerFunction( 'package_quiqqer_customer_ajax_backend_OpenItemsList_getUserOpenItems', - function ($userId) { + function ($userId, $searchParams, $forceRefresh) { try { - $OpenItemsList = Handler::getOpenItemsList(QUI::getUsers()->get((int)$userId)); - $items = []; + $userId = (int)$userId; + $cacheName = 'quiqqer/customer/openitems/'.$userId; + $refresh = true; - foreach ($OpenItemsList->getItems() as $Item) { + if (empty($forceRefresh)) { + try { + $openItems = QUICacheManager::get($cacheName); + $refresh = false; + } catch (\Exception $Exception) { + // nothing - refresh cache + } + } + + if ($refresh) { + $OpenItemsList = Handler::getOpenItemsList(QUI::getUsers()->get($userId)); + $openItems = $OpenItemsList->getItems(); + + QUICacheManager::set($cacheName, $openItems); + } + + $searchParams = Orthos::clearArray(\json_decode($searchParams, true)); + + // Filter + if (!empty($searchParams['search'])) { + $search = \trim($searchParams['search']); + + $openItems = \array_filter($openItems, function ($Item) use ($search) { + return \mb_strpos($Item->getDocumentNo(), $search) !== false; + }); + } + + // Sort + if (!empty($searchParams['sortOn'])) { + $sortOn = $searchParams['sortOn']; + } else { + $sortOn = 'date'; + } + + $sortBy = 'DESC'; + + if (!empty($searchParams['sortBy'])) { + switch (\mb_strtoupper($searchParams['sortBy'])) { + case 'ASC': + case 'DESC': + $sortBy = $searchParams['sortBy']; + break; + } + } + + \usort($openItems, function ($ItemA, $ItemB) use ($sortOn, $sortBy) { + /** + * @var \QUI\ERP\Customer\OpenItemsList\Item $ItemA + * @var \QUI\ERP\Customer\OpenItemsList\Item $ItemB + */ + switch ($sortOn) { + case 'documentNo': + $valA = (int)\preg_replace('#[^\d]#i', '', $ItemA->getDocumentNo()); + $valB = (int)\preg_replace('#[^\d]#i', '', $ItemB->getDocumentNo()); + break; + + case 'documentType': + $valA = $ItemA->getDocumentType(); + $valB = $ItemB->getDocumentType(); + break; + + case 'dueDate': + $valA = $ItemA->getDueDate(); + $valB = $ItemB->getDueDate(); + break; + + case 'net': + $valA = $ItemA->getAmountTotalNet(); + $valB = $ItemB->getAmountTotalNet(); + break; + + case 'vat': + $valA = $ItemA->getAmountTotalVat(); + $valB = $ItemB->getAmountTotalVat(); + break; + + case 'gross': + $valA = $ItemA->getAmountTotalSum(); + $valB = $ItemB->getAmountTotalSum(); + break; + + case 'paid': + $valA = $ItemA->getAmountPaid(); + $valB = $ItemB->getAmountPaid(); + break; + + case 'open': + $valA = $ItemA->getAmountOpen(); + $valB = $ItemB->getAmountOpen(); + break; + + case 'dunningLevel': + $valA = $ItemA->getDunningLevel(); + $valB = $ItemB->getDunningLevel(); + break; + + case 'daysDue': + $valA = $ItemA->getDaysDue(); + $valB = $ItemB->getDaysDue(); + break; + + default: + $valA = $ItemA->getDate(); + $valB = $ItemB->getDate(); + } + + if ($valA === $valB) { + return 0; + } + + if ($sortBy === 'ASC') { + return $valA < $valB ? -1 : 1; + } else { + return $valA < $valB ? 1 : -1; + } + }); + + // Pagination + $page = 1; + + if (!empty($searchParams['page'])) { + $page = (int)$searchParams['page']; + } + + $perPage = 10; + + if (!empty($searchParams['perPage'])) { + $perPage = (int)$searchParams['perPage']; + } + + $offset = ($page - 1) * $perPage; + $totalCount = \count($openItems); + $openItems = \array_splice($openItems, $offset, $perPage); + + // Parse data for GRID display + $items = []; + + /** @var \QUI\ERP\Customer\OpenItemsList\Item $Item */ + foreach ($openItems as $Item) { $documentType = $Item->getDocumentType(); $documentTypeTitle = QUI::getLocale()->get( 'quiqqer/customer', @@ -35,14 +174,15 @@ function ($userId) { 'gross' => $Item->getAmountTotalSumFormatted(), 'paid' => $Item->getAmountPaidFormatted(), 'open' => $Item->getAmountOpenFormatted(), - 'dunningLevel' => $Item->getDunningLevel() + 'dunningLevel' => $Item->getDunningLevel() ?: '-', + 'daysDue' => $Item->getDaysDue() ]; } return [ 'data' => $items, - 'page' => 1, - 'total' => \count($items) + 'page' => $page, + 'total' => $totalCount ]; } catch (\Exception $Exception) { QUI\System\Log::writeException($Exception); @@ -54,6 +194,6 @@ function ($userId) { ]; } }, - ['userId'], + ['userId', 'searchParams', 'forceRefresh'], ['Permission::checkAdminUser', Handler::PERMISSION_OPENITEMS_VIEW] ); diff --git a/ajax/backend/OpenItemsList/search.php b/ajax/backend/OpenItemsList/search.php index 43b1f2e..8f91d1f 100644 --- a/ajax/backend/OpenItemsList/search.php +++ b/ajax/backend/OpenItemsList/search.php @@ -23,16 +23,26 @@ function ($searchParams) { $Grid = new Grid($searchParams); + if (!empty($searchParams['currency'])) { + $Currency = new \QUI\ERP\Currency\Currency($searchParams['currency']); + } else { + $Currency = \QUI\ERP\Currency\Handler::getDefaultCurrency(); + } + return [ 'grid' => $Grid->parseResult($result, $count), - 'totals' => [] // @todo + 'totals' => Handler::getTotals($result, $Currency) ]; } catch (\Exception $Exception) { QUI\System\Log::writeException($Exception); return [ 'grid' => [], - 'totals' => [] + 'totals' => [ + 'display_gross_toPay' => '', + 'display_gross_paid' => '', + 'display_gross_total' => '' + ] ]; } }, diff --git a/bin/backend/controls/OpenItems/OpenItems.Total.html b/bin/backend/controls/OpenItems/OpenItems.Total.html index aa376c4..3f80748 100644 --- a/bin/backend/controls/OpenItems/OpenItems.Total.html +++ b/bin/backend/controls/OpenItems/OpenItems.Total.html @@ -1,24 +1,40 @@ -<header> - <div> </div> - <div>Offen</div> - <div>Bezahlt</div> - <div>Gesamt</div> -</header> -<div class="quiqqer-customer-openitems-total-row"> - <div class="quiqqer-customer-openitems-total-title">Netto</div> - <div class="quiqqer-customer-openitems-total-netto-open">{{display_netto_toPay}}</div> - <div class="quiqqer-customer-openitems-total-netto-paid">{{display_netto_paid}}</div> - <div class="quiqqer-customer-openitems-total-netto-total">{{display_netto_total}}</div> -</div> -<div class="quiqqer-customer-openitems-total-row"> - <div class="quiqqer-customer-openitems-total-title">MwSt</div> - <div class="quiqqer-customer-openitems-total-open">{{display_vat_toPay}}</div> - <div class="quiqqer-customer-openitems-total-paid">{{display_vat_paid}}</div> - <div class="quiqqer-customer-openitems-total-total">{{display_vat_total}}</div> -</div> -<div class="quiqqer-customer-openitems-total-row"> - <div class="quiqqer-customer-openitems-total-brutto-title">Brutto</div> - <div class="quiqqer-customer-openitems-total-brutto-open">{{display_brutto_toPay}}</div> - <div class="quiqqer-customer-openitems-total-brutto-paid">{{display_brutto_paid}}</div> - <div class="quiqqer-customer-openitems-total-brutto-total">{{display_brutto_total}}</div> -</div> +<table class="quiqqer-customer-openitems-totals-tbl"> + <thead> + <tr> + <th> + {{headerNet}} + </th> + <th> + {{headerVat}} + </th> + <th> + {{headerGross}} + </th> + <th> + {{headerPaid}} + </th> + <th> + {{headerOpen}} + </th> + </tr> + </thead> + <tbody> + <tr> + <td> + {{display_net}} + </td> + <td> + {{display_vat}} + </td> + <td> + {{display_gross}} + </td> + <td> + {{display_paid}} + </td> + <td> + {{display_open}} + </td> + </tr> + </tbody> +</table> diff --git a/bin/backend/controls/OpenItems/OpenItems.UserRecords.html b/bin/backend/controls/OpenItems/OpenItems.UserRecords.html new file mode 100644 index 0000000..5d447bd --- /dev/null +++ b/bin/backend/controls/OpenItems/OpenItems.UserRecords.html @@ -0,0 +1,5 @@ +<div class="quiqqer-customer-openitems-userrecords-search"> + <input type="search" placeholder="{{placeholderSearch}}"/> +</div> +<div class="quiqqer-customer-openitems-userrecords-list"> +</div> \ No newline at end of file diff --git a/bin/backend/controls/OpenItems/OpenItems.css b/bin/backend/controls/OpenItems/OpenItems.css index a57f5a9..0f302dc 100644 --- a/bin/backend/controls/OpenItems/OpenItems.css +++ b/bin/backend/controls/OpenItems/OpenItems.css @@ -175,6 +175,23 @@ background: #eaeff4; } +.openItems-total { + background-color: #efefef; + height: 80px; + padding: 20px; + text-align: right; + width: 100%; +} + +.openItems-total th { + text-align: right; + width: 20%; +} + +.quiqqer-customer-openitems-totals-tbl { + width: 100%; +} + .payment-status-amountCell { text-align: right !important; padding-right: 5px; @@ -183,14 +200,22 @@ /** Open items grid ================================================= */ -.quiqqer-customer-openitems-details { - height: 500px; +.quiqqer-customer-openitems-userrecords-list { + height: 400px; } -.quiqqer-customer-openitems-details div.bDiv > ul { +.quiqqer-customer-openitems-userrecords-list div.bDiv > ul { padding: 0 !important; } -.quiqqer-customer-openitems-details div.bDiv > ul > li { +.quiqqer-customer-openitems-userrecords-list div.bDiv > ul > li { overflow: hidden !important; +} + +.quiqqer-customer-openitems-userrecords-search { + background-color: #efefef; + height: 50px; + padding: 10px; + text-align: right; + width: 100%; } \ No newline at end of file diff --git a/bin/backend/controls/OpenItems/OpenItems.js b/bin/backend/controls/OpenItems/OpenItems.js index fc93994..a3e6976 100644 --- a/bin/backend/controls/OpenItems/OpenItems.js +++ b/bin/backend/controls/OpenItems/OpenItems.js @@ -11,20 +11,22 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ 'qui/controls/desktop/Panel', 'qui/controls/buttons/Button', 'qui/controls/buttons/Separator', - 'qui/controls/buttons/Select', 'qui/controls/contextmenu/Item', 'controls/grid/Grid', + 'package/quiqqer/payment-transactions/bin/backend/controls/IncomingPayments/AddPaymentWindow', + 'Locale', 'Ajax', 'Mustache', 'text!package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems.Total.html', + 'text!package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems.UserRecords.html', 'css!package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems.css', 'css!package/quiqqer/erp/bin/backend/payment-status.css' -], function (QUI, QUIPanel, QUIButton, QUISeparator, QUISelect, QUIContextMenuItem, Grid, - QUILocale, QUIAjax, Mustache, templateTotal) { +], function (QUI, QUIPanel, QUIButton, QUISeparator, QUIContextMenuItem, Grid, AddPaymentWindow, + QUILocale, QUIAjax, Mustache, templateTotal, templateUserRecords) { "use strict"; var lg = 'quiqqer/customer'; @@ -45,11 +47,16 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ '$onInject', '$onProcessChange', '$refreshButtonStatus', - '$onPDFExportButtonClick', + '$onClickShowOpenItemsList', '$onClickCopyProcess', - '$onClickOpenItemsDetails', + '$onClickOpenUserRecords', '$onClickOpenProcess', - '$onSearchKeyUp' + '$onSearchKeyUp', + '$refreshUserRecords', + '$refreshUserRecordsButtons', + '$onClickAddTransaction', + '$onClickOpenDocument', + '$onUserRecordsSearchKeyUp' ], initialize: function (options) { @@ -70,7 +77,10 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ this.$periodFilter = null; this.$loaded = false; - this.$GridDetails = null; + this.$GridDetails = null; + this.$currentRecordsUserId = false; + this.$UserRecordsSearch = null; + this.$currentUserRecordsSearch = ''; this.addEvents({ onCreate: this.$onCreate, @@ -92,41 +102,19 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ return; } - var status = ''; - var from = '', - to = ''; - this.$currentSearch = this.$Search.value; - if (this.$currentSearch !== '') { - this.disableFilter(); - } else { - this.enableFilter(); - } - var sortOn = this.$Grid.options.sortOn; - switch (sortOn) { - case 'supplier_name': - case 'display_vatsum': - case 'display_paid': - case 'display_toPay': - sortOn = false; - break; - } + this.showTotal(); this.$search({ - perPage: this.$Grid.options.perPage, - page : this.$Grid.options.page, - sortBy : this.$Grid.options.sortBy, - sortOn : sortOn, - search : this.$currentSearch, - filters: { - from : from, - to : to, - status : status, - currency: this.$Currency.getAttribute('value') - } + perPage : this.$Grid.options.perPage, + page : this.$Grid.options.page, + sortBy : this.$Grid.options.sortBy, + sortOn : sortOn, + search : this.$currentSearch, + currency: this.$Currency.getAttribute('value') }).then(function (result) { result.grid.data = result.grid.data.map(function (entry) { return self.$parseGridRow(entry); @@ -137,9 +125,47 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ this.$Total.set( 'html', - Mustache.render(templateTotal, result.total) + Mustache.render(templateTotal, Object.merge({}, result.totals, { + headerNet : QUILocale.get(lg, 'panel.OpenItems.grid.net'), + headerVat : QUILocale.get(lg, 'panel.OpenItems.grid.vat'), + headerGross: QUILocale.get(lg, 'panel.OpenItems.grid.gross'), + headerPaid : QUILocale.get(lg, 'panel.OpenItems.grid.paid'), + headerOpen : QUILocale.get(lg, 'panel.OpenItems.grid.open') + })) ); + this.$currentRecordsUserId = null; + + this.Loader.hide(); + }.bind(this)).catch(function (err) { + console.error(err); + this.Loader.hide(); + }.bind(this)); + }, + + /** + * Refresh grid entry for a specific user + * + * @param {Number} userId + * @return {Promise} + */ + $refreshUserEntry: function (userId) { + var self = this; + + return this.$search({ + userId: userId + }).then(function (result) { + var entries = self.$Grid.getData(); + + for (var i = 0, len = entries.length; i < len; i++) { + var Entry = entries[i]; + + if (Entry.userId === userId) { + self.$Grid.setDataByRow(i, self.$parseGridRow(result.grid.data[0])); + break; + } + } + this.Loader.hide(); }.bind(this)).catch(function (err) { console.error(err); @@ -160,7 +186,7 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ buttons = this.$Grid.getButtons(); var PDF = buttons.filter(function (Button) { - return Button.getAttribute('name') === 'printPdf'; + return Button.getAttribute('name') === 'showOpenItemsList'; })[0]; if (selected.length) { @@ -242,30 +268,22 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ pagination : true, serverSort : true, accordion : true, - autoSectionToggle : false, + autoSectionToggle : true, openAccordionOnClick : false, - toggleiconTitle : '', - accordionLiveRenderer: this.$onClickOpenItemsDetails, + toggleiconTitle : QUILocale.get(lg, 'panels.OpenItems.grid.toggle_title'), + accordionLiveRenderer: this.$onClickOpenUserRecords, exportData : true, exportTypes : { csv : 'CSV', json: 'JSON' }, buttons : [{ - name : 'addTransaction', - text : QUILocale.get(lg, 'panels.OpenItems.btn.addTransaction'), - textimage: 'fa fa-file-o', - disabled : true, - events : { - onClick: this.$onClickOpenProcess - } - }, { - name : 'printPdf', - text : QUILocale.get(lg, 'panels.OpenItems.btn.pdf'), + name : 'showOpenItemsList', + text : QUILocale.get(lg, 'panels.OpenItems.btn.showOpenItemsList'), textimage: 'fa fa-print', disabled : true, events : { - onClick: this.$onPDFExportButtonClick + onClick: this.$onClickShowOpenItemsList } }], columnModel : [{ @@ -286,45 +304,53 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ dataType : 'integer', width : 200 }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.netSum'), + header : QUILocale.get(lg, 'panel.OpenItems.grid.openItemsCount'), + dataIndex: 'open_items_count', + dataType : 'integer', + width : 100 + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.net'), dataIndex: 'display_net_sum', dataType : 'string', width : 100, className: 'payment-status-amountCell' }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.vatSum'), + header : QUILocale.get(lg, 'panel.OpenItems.grid.vat'), dataIndex: 'display_vat_sum', dataType : 'string', width : 100, className: 'payment-status-amountCell' }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.totalSum'), + header : QUILocale.get(lg, 'panel.OpenItems.grid.gross'), dataIndex: 'display_total_sum', dataType : 'string', width : 100, className: 'payment-status-amountCell' }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.paidSum'), + header : QUILocale.get(lg, 'panel.OpenItems.grid.paid'), dataIndex: 'display_paid_sum', dataType : 'string', width : 100, className: 'payment-status-amountCell' }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.openSum'), + header : QUILocale.get(lg, 'panel.OpenItems.grid.open'), dataIndex: 'display_open_sum', dataType : 'string', width : 100, className: 'payment-status-amountCell' + }, { + dataIndex: 'userId', + dataType : 'integer', + hidden : true }] }); this.$Grid.addEvents({ onRefresh : this.refresh, onClick : this.$refreshButtonStatus, - onDblClick: this.$onClickOpenProcess + onDblClick: this.$onClickShowOpenItemsList }); - this.$Total = new Element('div', { 'class': 'openItems-total' }).inject(this.getContent()); @@ -346,7 +372,7 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ var size = Body.getSize(); - this.$Grid.setHeight(size.y - 20); + this.$Grid.setHeight(size.y - 110); this.$Grid.setWidth(size.x - 20); }, @@ -387,6 +413,8 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ self.$Currency.enable(); self.$Currency.setAttribute('value', currency.code); self.$Currency.setAttribute('text', currency.code); + + self.refresh(); }, { 'package': 'quiqqer/currency' }); @@ -396,218 +424,33 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ }); }, - /** - * event: on panel destroy - */ - $onDestroy: function () { - Processes.removeEvents({ - onPostProcess: this.$onProcessChange - }); - }, - - /** - * event: A change was made to a purchasing process - * - * Update single processes in table without refreshing everything - * - * @param {Object} ProcessesHandler - * @param {Number|Array} processIds - */ - $onProcessChange: function (ProcessesHandler, processIds) { - if (!this.$Grid) { - return; - } - - var self = this, - rows = this.$Grid.getData(), - IdToRow = {}; - - var i, j, len, jlen, processId; - - if (typeof processIds === 'string' || typeof processIds === 'number') { - processIds = [processIds]; - } - - for (i = 0, len = processIds.length; i < len; i++) { - processId = processIds[i]; - - for (j = 0, jlen = rows.length; j < jlen; j++) { - if (rows[j].id == processId) { - IdToRow[processId] = j; - break; - } - } - } - - if (!Object.getLength(IdToRow)) { - return; - } - - return ProcessesHandler.getProcessList({ - ids: processIds, - }).then(function (result) { - for (i = 0, len = result.grid.data.length; i < len; i++) { - var processId = result.grid.data[i].id; - - if (processId in IdToRow) { - self.$Grid.setDataByRow(IdToRow[processId], self.$parseGridRow(result.grid.data[i])); - } - } - }); - }, - //region Buttons events /** * event : on PDF Export button click */ - $onPDFExportButtonClick: function (Button) { + $onClickShowOpenItemsList: function (Button) { + var self = this; var selectedData = this.$Grid.getSelectedData(); if (!selectedData.length) { return; } - Button.setAttribute('textimage', 'fa fa-spinner fa-spin'); + this.Loader.show(); - return new Promise(function (resolve) { - require([ - 'package/quiqqer/erp/bin/backend/controls/OutputDialog' - ], function (OutputDialog) { - new OutputDialog({ - entityId : selectedData[0].id, - entityType: 'PurchasingProcess', - events : { - onOpen: function (SubmitData) { - Button.setAttribute('textimage', 'fa fa-print'); - resolve(); - } + require([ + 'package/quiqqer/erp/bin/backend/controls/OutputDialog' + ], function (OutputDialog) { + new OutputDialog({ + entityId : selectedData[0].userId, + entityType: 'OpenItemsList', + events : { + onOpen: function () { + self.Loader.hide(); } - }).open(); - }); - }); - }, - - /** - * Create a copy of a process as a draft - */ - $onClickCopyProcess: function () { - var self = this, - selected = this.$Grid.getSelectedData(); - - if (!selected.length) { - return Promise.resolve(false); - } - - DialogUtils.openCopyDialog(selected[0].id_str).then(function (newId) { - ProcessPanels.openProcessDraft(newId); - }); - }, - - /** - * Open the accordion details of the open items - * - * @param {Object} data - */ - $onClickOpenItemsDetails: function (data) { - var self = this, - Row = self.$Grid.getDataByRow(data.row), - ParentNode = data.parent; - - ParentNode.setStyle('padding', 10); - //ParentNode.set('html', '<div class="fa fa-spinner fa-spin"></div>'); - - ParentNode.addClass('quiqqer-customer-openitems-details'); - - this.$GridDetails = new Grid(ParentNode, { - pagination : true, - serverSort : false, - accordion : false, - autoSectionToggle : false, - openAccordionOnClick: false, - toggleiconTitle : '', - // @todo Export aktivieren? - //exportData : true, - //exportTypes : { - // csv : 'CSV', - // json: 'JSON' - //}, - buttons : [{ - name : 'addTransaction', - text : QUILocale.get(lg, 'panels.OpenItems.btn.addTransaction'), - textimage: 'fa fa-file-o', - disabled : true, - events : { - onClick: this.$onClickAddTransaction } - }], - columnModel: [{ - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.userId'), - dataIndex: 'date', - dataType : 'string', - width : 150 - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.customerName'), - dataIndex: 'documentTypeTitle', - dataType : 'string', - width : 200 - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.customerName'), - dataIndex: 'documentNo', - dataType : 'string', - width : 200 - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.netSum'), - dataIndex: 'net', - dataType : 'string', - width : 100, - className: 'payment-status-amountCell' - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.vatSum'), - dataIndex: 'vat', - dataType : 'string', - width : 100, - className: 'payment-status-amountCell' - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.totalSum'), - dataIndex: 'gross', - dataType : 'string', - width : 100, - className: 'payment-status-amountCell' - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.paidSum'), - dataIndex: 'paid', - dataType : 'string', - width : 100, - className: 'payment-status-amountCell' - }, { - header : QUILocale.get(lg, 'panel.OpenItems.grid.details.openSum'), - dataIndex: 'open', - dataType : 'string', - width : 100, - className: 'payment-status-amountCell' - }] - }); - - this.$GridDetails.addEvents({ - //onRefresh : this.refresh, - //onClick : this.$refreshButtonStatus, - //onDblClick: this.$onClickOpenProcess - }); - - this.Loader.show(); - - this.$getUserOpenItems(Row.userId).then(function (result) { - - console.log(result); - - self.$GridDetails.setData(result); - self.Loader.hide(); - - var size = ParentNode.getSize(); - - self.$GridDetails.setHeight(size.y - 120); - self.$GridDetails.setWidth(size.x - 20); + }).open(); }); }, @@ -760,18 +603,6 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ }); }, - /** - * Disable the filter - */ - disableFilter: function () { - }, - - /** - * Enable the filter - */ - enableFilter: function () { - }, - /** * key up event at the search input * @@ -853,20 +684,430 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [ }); }, + // region User records + + /** + * Open the accordion details of the open items + * + * @param {Object} data + */ + $onClickOpenUserRecords: function (data) { + var self = this, + Row = self.$Grid.getDataByRow(data.row), + ParentNode = data.parent; + + if (Row.userId === this.$currentRecordsUserId) { + return; + } + + this.$currentRecordsUserId = Row.userId; + + ParentNode.setStyle('padding', 10); + //ParentNode.set('html', '<div class="fa fa-spinner fa-spin"></div>'); + + ParentNode.addClass('quiqqer-customer-openitems-userrecords'); + ParentNode.set('html', Mustache.render(templateUserRecords, { + placeholderSearch: QUILocale.get(lg, 'panels.OpenItems.details.tpl.placeholderSearch') + })); + + this.$UserRecordsSearch = ParentNode.getElement('.quiqqer-customer-openitems-userrecords-search input'); + + this.$UserRecordsSearch.addEvents({ + keyup : this.$onUserRecordsSearchKeyUp, + search: this.$onUserRecordsSearchKeyUp, + click : this.$onUserRecordsSearchKeyUp + }); + + new QUIButton({ + name : 'searchUserRecords', + icon : 'fa fa-search', + styles: { + float : 'right', + margin: 0 + }, + events: { + onClick: function () { + self.$refreshUserRecords(self.$GridDetails); + } + } + }).inject( + ParentNode.getElement('.quiqqer-customer-openitems-userrecords-search'), + 'bottom' + ); + + var GridParent = ParentNode.getElement('.quiqqer-customer-openitems-userrecords-list'); + + if (this.$GridDetails) { + this.$GridDetails.destroy(); + } + + this.$GridDetails = new Grid(GridParent, { + pagination : true, + serverSort : true, + accordion : false, + autoSectionToggle : false, + openAccordionOnClick: false, + toggleiconTitle : '', + // @todo Export aktivieren? + //exportData : true, + //exportTypes : { + // csv : 'CSV', + // json: 'JSON' + //}, + buttons : [{ + name : 'open', + text : QUILocale.get(lg, 'panels.OpenItems.details.btn.open'), + textimage: 'fa fa-file-o', + disabled : true, + events : { + onClick: this.$onClickOpenDocument + } + }, { + name : 'addTransaction', + text : QUILocale.get(lg, 'panels.OpenItems.details.btn.addTransaction'), + textimage: 'fa fa-money', + disabled : true, + events : { + onClick: this.$onClickAddTransaction + } + }], + columnModel: [{ + header : QUILocale.get(lg, 'panel.OpenItems.grid.date'), + dataIndex: 'date', + dataType : 'string', + width : 100 + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.details.documentNo'), + dataIndex: 'documentNo', + dataType : 'string', + width : 125 + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.details.documentType'), + dataIndex: 'documentTypeTitle', + dataType : 'string', + width : 85 + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.net'), + dataIndex: 'net', + dataType : 'string', + width : 100, + className: 'payment-status-amountCell' + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.vat'), + dataIndex: 'vat', + dataType : 'string', + width : 100, + className: 'payment-status-amountCell' + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.gross'), + dataIndex: 'gross', + dataType : 'string', + width : 100, + className: 'payment-status-amountCell' + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.paid'), + dataIndex: 'paid', + dataType : 'string', + width : 100, + className: 'payment-status-amountCell' + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.open'), + dataIndex: 'open', + dataType : 'string', + width : 100, + className: 'payment-status-amountCell' + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.details.daysDue'), + dataIndex: 'daysDue', + dataType : 'integer', + width : 75 + }, { + header : QUILocale.get(lg, 'panel.OpenItems.grid.details.dunningLevel'), + dataIndex: 'dunningLevel', + dataType : 'integer', + width : 75 + }, { + dataIndex: 'documentType', + dataType : 'string', + hidden : true + }] + }); + + this.$GridDetails.addEvents({ + onRefresh : this.$refreshUserRecords, + onClick : this.$refreshUserRecordsButtons, + onDblClick: this.$onClickAddTransaction + }); + + this.$refreshUserRecords(this.$GridDetails, true); + + var size = ParentNode.getSize(); + + self.$GridDetails.setHeight(size.y - 120); + }, + + /** + * Refresh grid button status of user open items records details + */ + $refreshUserRecordsButtons: function () { + if (!this.$GridDetails) { + return; + } + + var selected = this.$GridDetails.getSelectedData(), + buttons = this.$GridDetails.getButtons(); + + var OpenDocument = buttons.filter(function (Button) { + return Button.getAttribute('name') === 'open'; + })[0]; + + var AddTransaction = buttons.filter(function (Button) { + return Button.getAttribute('name') === 'addTransaction'; + })[0]; + + if (selected.length) { + OpenDocument.enable(); + AddTransaction.enable(); + return; + } + + OpenDocument.disable(); + AddTransaction.disable(); + }, + + /** + * If the user clicks the "add transaction to open item record" button + */ + $onClickAddTransaction: function () { + var self = this, + selected = this.$GridDetails.getSelectedData(); + + if (!selected.length) { + return; + } + + var Row = selected[0]; + var erpEntity; + + switch (Row.documentType) { + case 'invoice': + erpEntity = 'Invoice'; + break; + + case 'order': + erpEntity = 'Order'; + break; + + default: + return; + } + + var submitTransaction = function (Win, Data) { + Win.Loader.show(); + + switch (erpEntity) { + case 'Invoice': + require(['package/quiqqer/invoice/bin/Invoices'], function (Invoices) { + Invoices.addPaymentToInvoice( + Row.documentNo, + Data.amount, + Data.payment_method + ).then(function () { + Win.close(); + + self.$refreshUserEntry(self.$currentRecordsUserId).then(function () { + self.$refreshUserRecords(self.$GridDetails, true); + }); + }).catch(function (err) { + Win.Loader.hide(); + }); + }); + break; + + case 'Order': + // @todo + break; + } + }; + + new AddPaymentWindow({ + entityId : Row.documentNo, + entityType: erpEntity, + events : { + onSubmit: submitTransaction + } + }).open(); + }, + + /** + * If the user clicks the "open open item record document" button + */ + $onClickOpenDocument: function () { + var self = this, + selected = this.$GridDetails.getSelectedData(); + + if (!selected.length) { + return; + } + + var Row = selected[0]; + + switch (Row.documentType) { + case 'invoice': + this.Loader.show(); + + require(['package/quiqqer/invoice/bin/backend/utils/Panels'], function (InvoicePanels) { + InvoicePanels.openInvoice(Row.documentNo).then(function () { + self.Loader.hide(); + }); + }); + break; + + case 'order': + // @todo + break; + + default: + return; + } + }, + + /** + * key up event at the user records search input + * + * @param {DOMEvent} event + */ + $onUserRecordsSearchKeyUp: function (event) { + if (event.key === 'up' || + event.key === 'down' || + event.key === 'left' || + event.key === 'right') { + return; + } + + var SearchInput = event.target; + + if (this.$searchDelay) { + clearTimeout(this.$searchDelay); + } + + if (event.type === 'click') { + // workaround, cancel needs time to clear + (function () { + if (this.$UserRecordsSearch.value !== this.$currentUserRecordsSearch) { + this.$searchDelay = (function () { + this.$refreshUserRecords(this.$GridDetails); + }).delay(250, this); + } + }).delay(100, this); + } + + if (this.$UserRecordsSearch.value === this.$currentUserRecordsSearch) { + return; + } + + if (event.key === 'enter') { + this.$searchDelay = (function () { + this.$refreshUserRecords(this.$GridDetails); + }).delay(250, this); + return; + } + }, + + /** + * Refresh GRID with user open items records + * + * @param {Object} Grid + * @param {Boolean} [forceRefresh] - Force refresh of user open items records; otherwise try + * to fetch from cache + */ + $refreshUserRecords: function (Grid, forceRefresh) { + if (!this.$GridDetails) { + return; + } + + forceRefresh = forceRefresh || false; + + var self = this; + var sortOn = this.$GridDetails.options.sortOn; + + switch (sortOn) { + case 'supplier_name': + case 'display_vatsum': + case 'display_paid': + case 'display_toPay': + sortOn = false; + break; + } + + this.Loader.show(); + + this.$currentUserRecordsSearch = this.$UserRecordsSearch.value; + + this.$getUserOpenItems( + this.$currentRecordsUserId, + { + perPage: this.$GridDetails.options.perPage, + page : this.$GridDetails.options.page, + sortBy : this.$GridDetails.options.sortBy, + sortOn : sortOn, + search : this.$currentUserRecordsSearch + }, + forceRefresh + ).then(function (result) { + self.$GridDetails.setData(result); + self.Loader.hide(); + + self.$refreshUserRecordsButtons(); + }); + }, + /** * Get list of open items by user * * @param {Number} userId + * @param {Object} SearchParams + * @param {Boolean} forceRefresh * @return {Promise} */ - $getUserOpenItems: function (userId) { + $getUserOpenItems: function (userId, SearchParams, forceRefresh) { return new Promise(function (resolve, reject) { QUIAjax.get('package_quiqqer_customer_ajax_backend_OpenItemsList_getUserOpenItems', resolve, { - 'package': 'quiqqer/customer', - userId : userId, - onError : reject + 'package' : 'quiqqer/customer', + userId : userId, + searchParams: JSON.encode(SearchParams), + forceRefresh: forceRefresh ? 1 : 0, + onError : reject }); }); + }, + + // endregion + + // region Totals + + /** + * Show the total display + */ + showTotal: function () { + this.getContent().setStyle('overflow', 'hidden'); + + return new Promise(function (resolve) { + this.$Total.setStyles({ + display: 'inline-block', + opacity: 0 + }); + + moofx(this.$Total).animate({ + bottom : 1, + opacity: 1 + }, { + duration: 200, + callback: resolve + }); + }.bind(this)); } + + // endregion }); }); diff --git a/events.xml b/events.xml index e4ac932..bf0d2f5 100644 --- a/events.xml +++ b/events.xml @@ -7,4 +7,10 @@ <event on="onQuiqqerOrderCustomerDataSaveEnd" fire="\QUI\ERP\Customer\EventHandler::onQuiqqerOrderCustomerDataSaveEnd" /> + + <!-- Open items --> + <event on="onTransactionCreate" fire="\QUI\ERP\Customer\OpenItemsList\Events::onTransactionCreate"/> + <event on="onQuiqqerInvoiceTemporaryInvoicePostEnd" fire="\QUI\ERP\Customer\OpenItemsList\Events::onQuiqqerInvoiceTemporaryInvoicePostEnd"/> + <event on="onQuiqqerOrderCreated" fire="\QUI\ERP\Customer\OpenItemsList\Events::onQuiqqerOrderCreated"/> + </events> diff --git a/locale.xml b/locale.xml index 2306128..fed442a 100644 --- a/locale.xml +++ b/locale.xml @@ -33,6 +33,18 @@ <de><![CDATA[Kunden]]></de> <en><![CDATA[Customer]]></en> </locale> + <locale name="openItems.settings.title"> + <de><![CDATA[Offene Posten]]></de> + <en><![CDATA[Open Items]]></en> + </locale> + <locale name="customer.settings.considerOrders"> + <de><![CDATA[Bestellungen berücksichtigen]]></de> + <en><![CDATA[Consider order]]></en> + </locale> + <locale name="customer.settings.considerOrders.description"> + <de><![CDATA[Bei der Erfassung von offenen Posten werden auch Bestellungen einbezogen, sofern sie keine zugeordnete Rechnung haben]]></de> + <en><![CDATA[When capturing open items, purchase orders are also included if they do not have an assigned invoice]]></en> + </locale> <locale name="customer.settings.groupId"> <de><![CDATA[Kundengruppe]]></de> <en><![CDATA[Customer group]]></en> @@ -237,6 +249,89 @@ Best regards </groups> <groups name="quiqqer/customer" datatype="js"> + + <!-- OpenItems --> + <locale name="panels.OpenItems.grid.toggle_title"> + <de><![CDATA[Zeige alle offenen Posten dieses Kunden]]></de> + <en><![CDATA[Show all open items of this customer]]></en> + </locale> + <locale name="panels.OpenItems.title"> + <de><![CDATA[Offene Posten]]></de> + <en><![CDATA[Open Items]]></en> + </locale> + <locale name="panel.OpenItems.grid.date"> + <de><![CDATA[Datum]]></de> + <en><![CDATA[Date]]></en> + </locale> + <locale name="panel.OpenItems.grid.details.documentType"> + <de><![CDATA[Beleg-Typ]]></de> + <en><![CDATA[Document type]]></en> + </locale> + <locale name="panel.OpenItems.grid.details.documentNo"> + <de><![CDATA[Beleg-Nr.]]></de> + <en><![CDATA[Document no.]]></en> + </locale> + <locale name="panel.OpenItems.grid.net"> + <de><![CDATA[Netto]]></de> + <en><![CDATA[Net]]></en> + </locale> + <locale name="panel.OpenItems.grid.vat"> + <de><![CDATA[MwSt.]]></de> + <en><![CDATA[VAT]]></en> + </locale> + <locale name="panel.OpenItems.grid.gross"> + <de><![CDATA[Brutto]]></de> + <en><![CDATA[Gross]]></en> + </locale> + <locale name="panel.OpenItems.grid.paid"> + <de><![CDATA[beglichen]]></de> + <en><![CDATA[paid]]></en> + </locale> + <locale name="panel.OpenItems.grid.open"> + <de><![CDATA[offen]]></de> + <en><![CDATA[open]]></en> + </locale> + <locale name="panel.OpenItems.grid.userId"> + <de><![CDATA[Kunden-Nr.]]></de> + <en><![CDATA[Customer no.]]></en> + </locale> + <locale name="panel.OpenItems.grid.customerName"> + <de><![CDATA[Kunde]]></de> + <en><![CDATA[Customer]]></en> + </locale> + <locale name="panels.OpenItems.details.btn.open"> + <de><![CDATA[Beleg öffnen]]></de> + <en><![CDATA[Open document]]></en> + </locale> + <locale name="panels.OpenItems.details.btn.addTransaction"> + <de><![CDATA[Zahlung buchen]]></de> + <en><![CDATA[Book payment]]></en> + </locale> + <locale name="panel.OpenItems.grid.openItemsCount"> + <de><![CDATA[Offene Posten]]></de> + <en><![CDATA[Open items]]></en> + </locale> + <locale name="panels.OpenItems.btn.showOpenItemsList"> + <de><![CDATA[OPOS-Liste drucken / versenden]]></de> + <en><![CDATA[Print / send Open Items List]]></en> + </locale> + <locale name="panels.OpenItems.details.tpl.placeholderSearch"> + <de><![CDATA[Beleg-Suche...]]></de> + <en><![CDATA[Document search...]]></en> + </locale> + <locale name="panels.OpenItems.search.placeholder"> + <de><![CDATA[Kunden-Suche...]]></de> + <en><![CDATA[Cusomter search...]]></en> + </locale> + <locale name="panel.OpenItems.grid.details.daysDue"> + <de><![CDATA[Tage offen]]></de> + <en><![CDATA[Days open]]></en> + </locale> + <locale name="panel.OpenItems.grid.details.dunningLevel"> + <de><![CDATA[Mahnstufe]]></de> + <en><![CDATA[Dunning level]]></en> + </locale> + <locale name="window.customer.creation.title"> <de><![CDATA[Neuen Kunden anlegen]]></de> <en><![CDATA[Create new customer]]></en> diff --git a/settings.xml b/settings.xml index d548d03..0f8f930 100644 --- a/settings.xml +++ b/settings.xml @@ -12,6 +12,13 @@ <defaultValue>0</defaultValue> </conf> </section> + + <section name="openItems"> + <conf name="considerOrders"> + <type><![CDATA[boolean]]></type> + <defaultvalue>0</defaultvalue> + </conf> + </section> </config> <window name="ERP"> @@ -53,6 +60,22 @@ </input> </settings> + + <settings title="openItems" name="openItems"> + <title> + <locale group="quiqqer/customer" var="openItems.settings.title"/> + </title> + + <input conf="openItems.considerOrders" type="checkbox"> + <text> + <locale group="quiqqer/customer" var="customer.settings.considerOrders"/> + </text> + <description> + <locale group="quiqqer/customer" var="customer.settings.considerOrders.description"/> + </description> + </input> + + </settings> </category> </categories> diff --git a/src/QUI/ERP/Customer/OpenItemsList/Events.php b/src/QUI/ERP/Customer/OpenItemsList/Events.php new file mode 100644 index 0000000..e57cf8c --- /dev/null +++ b/src/QUI/ERP/Customer/OpenItemsList/Events.php @@ -0,0 +1,118 @@ +<?php + +namespace QUI\ERP\Customer\OpenItemsList; + +use QUI; +use QUI\ERP\Accounting\Payments\Transactions\Transaction; +use QUI\ERP\Accounting\Invoice\InvoiceTemporary; +use QUI\ERP\Accounting\Invoice\Invoice; +use QUI\ERP\Order\Settings; + +/** + * Class Events + * + * Event handler for all events related to open items of customers + */ +class Events +{ + /** + * quiqqer/payment-transactions: onTransactionCreate + * + * Update open records of user if a transaction was made against one of his open items + * + * @param Transaction $Transaction + * @return void + */ + public static function onTransactionCreate(Transaction $Transaction) + { + $userId = $Transaction->getAttribute('uid'); + + if (empty($userId)) { + return; + } + + try { + $User = QUI::getUsers()->get($userId); + } catch (\Exception $Exception) { + QUI\System\Log::writeDebugException($Exception); + return; + } + + try { + Handler::updateOpenItemsRecord($User); + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + } + } + + /** + * quiqqer/invoice: onQuiqqerInvoiceTemporaryInvoicePostEnd + * + * Update open records of user if an invoice is posted + * + * @param InvoiceTemporary $TempInvoice + * @param Invoice $Invoice + * @return void + */ + public static function onQuiqqerInvoiceTemporaryInvoicePostEnd( + InvoiceTemporary $TempInvoice, + Invoice $Invoice + ): void { + try { + $User = $Invoice->getCustomer(); + } catch (\Exception $Exception) { + QUI\System\Log::writeDebugException($Exception); + return; + } + + try { + Handler::updateOpenItemsRecord($User); + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + } + } + + /** + * quiqqer/order: onQuiqqerOrderCreated + * + * Update open records of user if an order is created + * + * @param QUI\ERP\Order\Order $Order + */ + public static function onQuiqqerOrderCreated(QUI\ERP\Order\Order $Order) + { + try { + $Conf = QUI::getPackage('quiqqer/customer')->getConfig(); + $considerOrders = $Conf->get('openItems', 'considerOrders'); + + if (empty($considerOrders)) { + return; + } + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + return; + } + + if (!empty($Order->getAttribute('no_invoice_auto_create'))) { + return; + } + + // Do not track order that also are tracked via invoice + if (Settings::getInstance()->createInvoiceOnOrder()) { + return; + } + + try { + $User = $Order->getCustomer(); + } catch (\Exception $Exception) { + QUI\System\Log::writeDebugException($Exception); + return; + } + + try { + Handler::updateOpenItemsRecord($User); + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + } + } +} diff --git a/src/QUI/ERP/Customer/OpenItemsList/Handler.php b/src/QUI/ERP/Customer/OpenItemsList/Handler.php index 733113b..d8b93d1 100644 --- a/src/QUI/ERP/Customer/OpenItemsList/Handler.php +++ b/src/QUI/ERP/Customer/OpenItemsList/Handler.php @@ -11,6 +11,7 @@ use QUI\Utils\Grid; use QUI\Utils\Security\Orthos; use QUI\ERP\Customer\Customers; +use QUI\ERP\Order\Handler as OrderHandler; /** * Class Handler @@ -53,11 +54,28 @@ public static function getOpenItemsList(QUI\Interfaces\Users\User $User) $List->addItem(self::parseInvoiceToOpenItem($Invoice)); } + try { + $Conf = QUI::getPackage('quiqqer/customer')->getConfig(); + $considerOrders = $Conf->get('openItems', 'considerOrders'); + + if (!empty($considerOrders)) { + $orders = self::getOpenOrders($User); + + foreach ($orders as $Order) { + $List->addItem(self::parseOrderToOpenItem($Order)); + } + } + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + } + // @todo Fetch open dunnings return $List; } + // region Invoices + /** * Get all open invoices of a user * @@ -76,19 +94,19 @@ protected static function getOpenInvoices(QUI\Interfaces\Users\User $User) 'select' => ['id'], 'from' => $Invoices->invoiceTable(), 'where' => [ - 'paid_status' => [ + 'paid_status' => [ 'type' => 'NOT IN', 'value' => [ QUI\ERP\Constants::PAYMENT_STATUS_PAID, QUI\ERP\Constants::PAYMENT_STATUS_CANCELED ] ], - 'time_for_payment' => [ - 'type' => '<=', - 'value' => \date('Y-m-d H:i:s') - ], - 'customer_id' => $User->getId(), - 'type' => InvoiceHandler::TYPE_INVOICE +// 'time_for_payment' => [ +// 'type' => '<=', +// 'value' => \date('Y-m-d H:i:s') +// ], + 'customer_id' => $User->getId(), + 'type' => InvoiceHandler::TYPE_INVOICE ] ]); @@ -180,6 +198,133 @@ protected static function parseInvoiceToOpenItem(Invoice $Invoice) return $Item; } + // endregion + + // region Orders + + /** + * Get all open orders of a user + * + * @param QUI\Interfaces\Users\User $User + * @return QUI\ERP\Order\Order[] + */ + protected static function getOpenOrders(QUI\Interfaces\Users\User $User) + { + if (!QUI::getPackageManager()->isInstalled('quiqqer/order')) { + return []; + } + + $Orders = OrderHandler::getInstance(); + + $result = QUI::getDataBase()->fetch([ + 'select' => ['id'], + 'from' => $Orders->table(), + 'where' => [ + 'paid_status' => [ + 'type' => 'NOT IN', + 'value' => [ + QUI\ERP\Constants::PAYMENT_STATUS_PAID, + QUI\ERP\Constants::PAYMENT_STATUS_CANCELED, + QUI\ERP\Constants::PAYMENT_STATUS_PLAN + ] + ], + 'customerId' => $User->getId(), + 'invoice_id' => null + ] + ]); + + $orders = []; + + foreach ($result as $row) { + try { + $orders[] = $Orders->get($row['id']); + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + } + } + + return $orders; + } + + /** + * Parses order data to an open item + * + * @param QUI\ERP\Order\Order $Order + * @return Item + */ + protected static function parseOrderToOpenItem(QUI\ERP\Order\Order $Order) + { + $Item = new Item(self::DOCUMENT_TYPE_ORDER); + + // Basic data + $Item->setDocumentNo($Order->getPrefixedId()); + $Item->setDate(\date_create($Order->getAttribute('c_date'))); + + if (!empty($Order->getAttribute('payment_time'))) { + $Item->setDueDate(\date_create($Order->getAttribute('payment_time'))); + } + + // Invoice amounts + $paidStatus = $Order->getPaidStatusInformation(); + $Item->setAmountPaid($paidStatus['paid']); + $Item->setAmountOpen($paidStatus['toPay']); + + $OrderArticles = $Order->getArticles(); + $calculations = $OrderArticles->getCalculations(); + + $Item->setAmountTotalNet($calculations['nettoSum']); + $Item->setAmountTotalSum($calculations['sum']); + + if (!empty($calculations['vatArray'])) { + $Item->setAmountTotalVat(\array_sum(\array_column($calculations['vatArray'], 'sum'))); + } + + $Item->setCurrency($Order->getCurrency()); + + // Latest transaction date + $Transactions = QUI\ERP\Accounting\Payments\Transactions\Handler::getInstance(); + $transactions = $Transactions->getTransactionsByHash($Order->getHash()); + + if (!empty($transactions)) { + // Sort by date + \usort($transactions, function ($TransactionA, $TransactionB) { + /** + * @var QUI\ERP\Accounting\Payments\Transactions\Transaction $TransactionA + * @var QUI\ERP\Accounting\Payments\Transactions\Transaction $TransactionB + */ + $DateA = \date_create($TransactionA->getDate()); + $DateB = \date_create($TransactionB->getDate()); + + if ($DateA === $DateB) { + return 0; + } + + return $DateA > $DateB ? -1 : 1; + }); + + $LatestTransactionDate = \date_create($transactions[0]->getDate()); + $Item->setLastPaymentDate($LatestTransactionDate); + } + + // Days due +// $Now = \date_create(); +// $TimeForPayment = \date_create($Invoice->getAttribute('time_for_payment')); +// $Item->setDaysDue($TimeForPayment->diff($Now)->days + 1); + + // Check if dunning exist +// if (QUI::getPackageManager()->isInstalled('quiqqer/dunning')) { +// $DunningProcess = DunningsHandler::getInstance()->getDunningProcessByInvoiceId($Invoice->getCleanId()); +// +// if ($DunningProcess && $DunningProcess->getCurrentDunning()) { +// $Item->setDunningLevel($DunningProcess->getCurrentDunning()->getDunningLevel()->getLevel()); +// } +// } + + return $Item; + } + + // endregion + /** * Updates the open items record of a user with up-to-date item data. * @@ -269,6 +414,14 @@ public static function searchOpenItems(array $searchParams) } } + if (!empty($searchParams['currency'])) { + $where[] = '`currency` = \''.Orthos::clear($searchParams['currency']).'\''; + } + + if (!empty($searchParams['userId'])) { + $where[] = '`userId` = '.(int)$searchParams['userId']; + } + // build WHERE query string if (!empty($where)) { $sql .= " WHERE ".implode(" AND ", $where); @@ -389,6 +542,38 @@ public static function parseForGrid(array $result): array return $result; } + /** + * Calculate the totals for a set of customer open items + * + * @param array $entries - Database rows form customer_open_items table + * @param QUI\ERP\Currency\Currency $Currency + * @return array - Totals prepared for backend display + */ + public static function getTotals(array $entries, QUI\ERP\Currency\Currency $Currency) + { + $net = 0; + $vat = 0; + $gross = 0; + $paid = 0; + $open = 0; + + foreach ($entries as $entry) { + $net += $entry['net_sum']; + $vat += $entry['vat_sum']; + $gross += $entry['total_sum']; + $paid += $entry['paid_sum']; + $open += $entry['open_sum']; + } + + return [ + 'display_net' => $Currency->format($net), + 'display_vat' => $Currency->format($vat), + 'display_gross' => $Currency->format($gross), + 'display_paid' => $Currency->format($paid), + 'display_open' => $Currency->format($open) + ]; + } + /** * Get table that contains open items * diff --git a/src/QUI/ERP/Customer/OpenItemsList/Item.php b/src/QUI/ERP/Customer/OpenItemsList/Item.php index bdf216e..277fc2b 100644 --- a/src/QUI/ERP/Customer/OpenItemsList/Item.php +++ b/src/QUI/ERP/Customer/OpenItemsList/Item.php @@ -231,6 +231,10 @@ public function getDate(): \DateTime */ public function getDateFormatted() { + if (empty($this->Date)) { + return '-'; + } + return $this->getLocale()->formatDate($this->Date->getTimestamp()); } @@ -255,6 +259,10 @@ public function getLastPaymentDate() */ public function getLastPaymentDateFormatted() { + if (empty($this->LastPaymentDate)) { + return '-'; + } + return $this->getLocale()->formatDate($this->LastPaymentDate->getTimestamp()); } @@ -279,6 +287,10 @@ public function getDueDate(): \DateTime */ public function getDueDateFormatted() { + if (empty($this->DueDate)) { + return '-'; + } + return $this->getLocale()->formatDate($this->DueDate->getTimestamp()); } -- GitLab