From c82da602a4da2ed6c3abe88714ba2a2aed8ca54c Mon Sep 17 00:00:00 2001 From: Henning Leutz <leutz@pcsg.de> Date: Fri, 6 Dec 2024 10:43:36 +0100 Subject: [PATCH 1/4] feat: new e-invoice as attachment --- composer.json | 3 +- locale.xml | 37 +++++ settings.xml | 85 ++++++++++++ .../ERP/Accounting/Invoice/EventHandler.php | 31 ++++- .../ERP/Accounting/Invoice/Utils/Invoice.php | 130 +++++++++++++++++- 5 files changed, 283 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 116e88b..fe340cb 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "quiqqer/htmltopdf": "^3", "quiqqer/utils": "^2", "ramsey/uuid": "^3|^4", - "chillerlan/php-qrcode": "^4.3" + "chillerlan/php-qrcode": "^4.3", + "horstoeko/zugferd": "^1" }, "autoload": { "psr-4": { diff --git a/locale.xml b/locale.xml index 82acacb..c0aeba9 100644 --- a/locale.xml +++ b/locale.xml @@ -415,6 +415,43 @@ <en><![CDATA[Edit Invoice status:]]></en> </locale> + <locale name="invoice.settings.e-invoice.xInvoiceAttachment.text"> + <de><![CDATA[X-Rechnung anhängen]]></de> + <en><![CDATA[Attach X-bill]]></en> + </locale> + <locale name="invoice.settings.e-invoice.xInvoiceAttachment.description"> + <de><![CDATA[ + Bei Rechnungsmails werden Rechnungen automatisch auch als X-Rechnung XML angehängt, + um den Standard für elektronische Rechnungen zu erfüllen. + ]]></de> + <en><![CDATA[ + Invoices are automatically attached to invoice e-mails as X-invoices xml format, + to comply with the standard for electronic invoices. + ]]></en> + </locale> + <locale name="invoice.settings.e-invoice.xInvoiceAttachmentType.text"> + <de><![CDATA[X-Rechnung Profil]]></de> + <en><![CDATA[X-bill profile]]></en> + </locale> + + <locale name="invoice.settings.e-invoice.zugferdInvoiceAttachment.text"> + <de><![CDATA[Zugferd-Rechnung anhängen]]></de> + <en><![CDATA[Attach Zugferd-bill]]></en> + </locale> + <locale name="invoice.settings.e-invoice.zugferdInvoiceAttachment.description"> + <de><![CDATA[ + Bei Rechnungsmails werden Rechnungen automatisch im Zugferd PDF Format angehängt. + ]]></de> + <en><![CDATA[ + Invoices are automatically attached to invoice emails in Zugferd PDF format. + ]]></en> + </locale> + <locale name="invoice.settings.e-invoice.zugferdInvoiceAttachmentType.text"> + <de><![CDATA[Zugferd-Rechnung Profil]]></de> + <en><![CDATA[Zugferd-bill profile]]></en> + </locale> + + <locale name="invoice.type.creditNote"> <de><![CDATA[Gutschrift]]></de> <en><![CDATA[Credit Note]]></en> diff --git a/settings.xml b/settings.xml index 53981c2..478a7e3 100644 --- a/settings.xml +++ b/settings.xml @@ -41,6 +41,23 @@ <conf name="invoiceAddressRequirementThreshold"> <type><![CDATA[float]]></type> </conf> + + <conf name="xInvoiceAttachment"> + <type><![CDATA[bool]]></type> + <defaultvalue><![CDATA[1]]></defaultvalue> + </conf> + <conf name="xInvoiceAttachmentType"> + <type><![CDATA[int]]></type> + <defaultvalue><![CDATA[2]]></defaultvalue> + </conf> + <conf name="zugferdInvoiceAttachment"> + <type><![CDATA[bool]]></type> + <defaultvalue><![CDATA[1]]></defaultvalue> + </conf> + <conf name="zugferdInvoiceAttachmentType"> + <type><![CDATA[int]]></type> + <defaultvalue><![CDATA[2]]></defaultvalue> + </conf> </section> <section name="temporaryInvoice"> @@ -214,6 +231,74 @@ </input> </settings> + + <settings title="eInvoice" name="eInvoice"> + <title> + <locale group="quiqqer/invoice" var="invoice.settings.e-invoice.title"/> + </title> + + <input conf="invoice.xInvoiceAttachment" type="checkbox"> + <text> + <locale group="quiqqer/invoice" + var="invoice.settings.e-invoice.xInvoiceAttachment.text"/> + </text> + <description> + <locale group="quiqqer/invoice" + var="invoice.settings.e-invoice.xInvoiceAttachment.description"/> + </description> + </input> + + <select conf="invoice.xInvoiceAttachmentType"> + <text> + <locale group="quiqqer/invoice" + var="invoice.settings.e-invoice.xInvoiceAttachmentType.text"/> + </text> + + <option value="0">Basic</option> + <option value="1">Basic WL</option> + <option value="2">EN16931</option> + <option value="3">Extended</option> + <option value="4">XRechnung (Germany only)</option> + <option value="5">XRechnung 2.0 (Germany only)</option> + <option value="6">XRechnung 2.1 (Germany only)</option> + <option value="7">XRechnung 2.2 (Germany only)</option> + <option value="8">Minimum</option> + <option value="9">XRechnung 2.3 (Germany only)</option> + <option value="10">XRechnung 3.0 (Germany only)</option> + </select> + + <input conf="invoice.zugferdInvoiceAttachment" type="checkbox"> + <text> + <locale group="quiqqer/invoice" + var="invoice.settings.e-invoice.zugferdInvoiceAttachment.text"/> + </text> + <description> + <locale group="quiqqer/invoice" + var="invoice.settings.e-invoice.zugferdInvoiceAttachment.description"/> + </description> + </input> + + <select conf="invoice.zugferdInvoiceAttachmentType"> + <text> + <locale group="quiqqer/invoice" + var="invoice.settings.e-invoice.zugferdInvoiceAttachmentType.text"/> + </text> + + <option value="0">Basic</option> + <option value="1">Basic WL</option> + <option value="2">EN16931</option> + <option value="3">Extended</option> + <option value="4">XRechnung (Germany only)</option> + <option value="5">XRechnung 2.0 (Germany only)</option> + <option value="6">XRechnung 2.1 (Germany only)</option> + <option value="7">XRechnung 2.2 (Germany only)</option> + <option value="8">Minimum</option> + <option value="9">XRechnung 2.3 (Germany only)</option> + <option value="10">XRechnung 3.0 (Germany only)</option> + </select> + + </settings> + <settings title="invoiceDownload" name="invoiceDownload"> <title> <locale group="quiqqer/invoice" var="invoice.settings.download.title"/> diff --git a/src/QUI/ERP/Accounting/Invoice/EventHandler.php b/src/QUI/ERP/Accounting/Invoice/EventHandler.php index f405b0d..c427995 100644 --- a/src/QUI/ERP/Accounting/Invoice/EventHandler.php +++ b/src/QUI/ERP/Accounting/Invoice/EventHandler.php @@ -6,6 +6,9 @@ namespace QUI\ERP\Accounting\Invoice; +use horstoeko\zugferd\ZugferdDocumentBuilder; +use horstoeko\zugferd\ZugferdDocumentPdfBuilder; +use horstoeko\zugferd\ZugferdDocumentPdfMerger; use QUI; use QUI\ERP\Accounting\Invoice\Output\OutputProviderCancelled; use QUI\ERP\Accounting\Invoice\Output\OutputProviderCreditNote; @@ -23,6 +26,7 @@ use function file_get_contents; use function in_array; use function is_numeric; +use function str_replace; use function strtolower; use function strtotime; @@ -287,6 +291,7 @@ public static function onQuiqqerErpGetHistoryByUser( * @param string $entityType * @param string $recipient * @param Mailer $Mailer + * @param string $mailFile * @return void * * @throws Exception @@ -297,7 +302,8 @@ public static function onQuiqqerErpOutputSendMailBefore( $entityId, string $entityType, string $recipient, - QUI\Mail\Mailer $Mailer + QUI\Mail\Mailer $Mailer, + string $mailFile = '' ): void { $allowedEntityTypes = [ OutputProviderInvoice::getEntityType(), @@ -316,6 +322,29 @@ public static function onQuiqqerErpOutputSendMailBefore( return; } + // extend pdf with e-invoice + $Config = QUI::getPackage('quiqqer/invoice')->getConfig(); + + if ($Config->getValue('invoice', 'xInvoiceAttachment')) { + $xmlFile = str_replace('.pdf', '.xml', $mailFile); + $document = QUI\ERP\Accounting\Invoice\Utils\Invoice::getElectronicInvoice( + $Invoice, + $Config->getValue('invoice', 'xInvoiceAttachmentType') + ); + + $document->writeFile($xmlFile); + $Mailer->addAttachment($xmlFile); + } + + if (file_exists($mailFile) && $Config->getValue('invoice', 'zugferdInvoiceAttachment')) { + $document = QUI\ERP\Accounting\Invoice\Utils\Invoice::getElectronicInvoice( + $Invoice, + $Config->getValue('invoice', 'xInvoiceAttachmentType') + ); + $pdfBuilder = new ZugferdDocumentPdfBuilder($document, $mailFile); + $pdfBuilder->generateDocument()->saveDocument($mailFile); + } + // @todo $customerFiles = $Invoice->getCustomerFiles(); diff --git a/src/QUI/ERP/Accounting/Invoice/Utils/Invoice.php b/src/QUI/ERP/Accounting/Invoice/Utils/Invoice.php index f5bd1f1..9916b9b 100644 --- a/src/QUI/ERP/Accounting/Invoice/Utils/Invoice.php +++ b/src/QUI/ERP/Accounting/Invoice/Utils/Invoice.php @@ -6,13 +6,17 @@ namespace QUI\ERP\Accounting\Invoice\Utils; -use IntlDateFormatter; +use DateTime; use QUI; use QUI\ERP\Accounting\Invoice\Exception; use QUI\ERP\Accounting\Invoice\InvoiceTemporary; use QUI\ERP\Accounting\Invoice\ProcessingStatus\Handler as ProcessingStatuses; use QUI\ERP\Currency\Currency; use QUI\ExceptionStack; +use QUI\ERP\Defaults; +use IntlDateFormatter; +use horstoeko\zugferd\ZugferdDocumentBuilder; +use horstoeko\zugferd\ZugferdProfiles; use function array_map; use function array_merge; @@ -657,4 +661,128 @@ public static function addressRequirementThreshold(): float return floatval($threshold); } + + public static function getElectronicInvoice( + InvoiceTemporary|QUI\ERP\Accounting\Invoice\Invoice $Invoice, + $type = ZugferdProfiles::PROFILE_EN16931 + ): ZugferdDocumentBuilder { + $document = ZugferdDocumentBuilder::CreateNew($type); + + $date = $Invoice->getAttribute('date'); + $date = strtotime($date); + $date = (new DateTime())->setTimestamp($date); + + $document->setDocumentInformation( + $Invoice->getPrefixedNumber(), + "380", + $date, + $Invoice->getCurrency()->getCode() + ); + + // seller / owner + $document + ->setDocumentSeller(Defaults::conf('company', 'name')) + ->addDocumentSellerGlobalId("4000001123452", "0088") + ->addDocumentSellerTaxRegistration("FC", "201/113/40209") + ->addDocumentSellerTaxRegistration("VA", "DE123456789") + ->setDocumentSellerAddress( + Defaults::conf('company', 'street'), + "", + "", + Defaults::conf('company', 'zip'), + Defaults::conf('company', 'city'), + Defaults::conf('company', 'country') // @todo country ->code<- + ) + ->setDocumentSellerCommunication( + 'EM', + Defaults::conf('company', 'email') + ) + ->setDocumentSellerContact( + Defaults::conf('company', 'owner'), // @todo contact person + '', // @todo contact department + Defaults::conf('company', 'phone'), // @todo contact phone + Defaults::conf('company', 'fax'), // @todo contact fax + Defaults::conf('company', 'email') // @todo contact email + ); + + // bank stuff + $bankAccount = QUI\ERP\BankAccounts\Handler::getCompanyBankAccount(); + + if (!empty($bankAccount)) { + $document->addDocumentPaymentMeanToDirectDebit( + $bankAccount['iban'], + $Invoice->getPrefixedNumber() + ); + } + + // customer + $Customer = $Invoice->getCustomer(); + + $document + ->setDocumentBuyer( + $Customer->getName(), + $Customer->getCustomerNo() + ) + ->setDocumentBuyerAddress( + $Customer->getAddress()->getAttribute('street_no'), + "", + "", + $Customer->getAddress()->getAttribute('zip'), + $Customer->getAddress()->getAttribute('city'), + $Customer->getAddress()->getCountry()->getCode() + ) + ->setDocumentBuyerCommunication('EM', $Customer->getAddress()->getAttribute('email')) + ->setDocumentBuyerReference($Customer->getUUID()); + + // total + $priceCalculation = $Invoice->getPriceCalculation(); + $vatTotal = 0; + + foreach ($priceCalculation->getVat() as $vat) { + $document->addDocumentTax( + "S", + "VAT", + $priceCalculation->getSum()->value(), + $vat->value(), + $vat->getVat() + ); + + $vatTotal = $vatTotal + $vat->value(); + } + + $document->setDocumentSummation( + $priceCalculation->getSum()->value(), + $priceCalculation->getSum()->value(), + $priceCalculation->getSum()->value(), + 0.0, + 0.0, + $priceCalculation->getSum()->value(), + $vatTotal, + null, + 0.0 + ); + + // products + foreach ($Invoice->getArticles() as $Article) { + /* @var $Article QUI\ERP\Accounting\Article */ + $article = $Article->toArray(); + + $document + ->addNewPosition($article['position']) + ->setDocumentPositionProductDetails( + $article['title'], + $article['description'], + null, + null, + null, + null + ) + ->setDocumentPositionNetPrice($article['calculated']['nettoPrice']) + ->setDocumentPositionQuantity($article['quantity'], "H87") + ->addDocumentPositionTax('S', 'VAT', $article['vat']) + ->setDocumentPositionLineSummation($article['sum']); + } + + return $document; + } } -- GitLab From 2dbb4c1ff9b9605f74cfe6aa9ade6522dcfc2a9c Mon Sep 17 00:00:00 2001 From: Henning <leutz@pcsg.de> Date: Sat, 14 Dec 2024 15:45:45 +0100 Subject: [PATCH 2/4] feat: invoice download in different formats --- bin/backend/controls/panels/Invoice.js | 16 +++ bin/backend/download.php | 67 ++++++++++++ bin/backend/utils/Dialogs.css | 23 ++++- bin/backend/utils/Dialogs.js | 136 ++++++++++++++++++------- locale.xml | 13 +++ 5 files changed, 217 insertions(+), 38 deletions(-) create mode 100644 bin/backend/download.php diff --git a/bin/backend/controls/panels/Invoice.js b/bin/backend/controls/panels/Invoice.js index 1835322..7a2d894 100644 --- a/bin/backend/controls/panels/Invoice.js +++ b/bin/backend/controls/panels/Invoice.js @@ -36,6 +36,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/Invoice', [ Binds: [ 'print', 'storno', + 'download', 'copy', 'creditNote', 'openInfo', @@ -199,6 +200,14 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/Invoice', [ } }); + Actions.appendChild({ + icon: 'fa fa-download', + text: QUILocale.get(lg, 'erp.panel.invoice.button.download'), + events: { + onClick: this.download + } + }); + this.fireEvent('actionButtonCreate', [ this, Actions @@ -554,6 +563,13 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/Invoice', [ }); }, + download: function() + { + require(['package/quiqqer/invoice/bin/backend/utils/Dialogs'], (Dialogs)=> { + Dialogs.openDownloadDialog(this.getAttribute('data').hash); + }); + }, + /** * Opens the storno / cancellation dialog * diff --git a/bin/backend/download.php b/bin/backend/download.php new file mode 100644 index 0000000..bf7c601 --- /dev/null +++ b/bin/backend/download.php @@ -0,0 +1,67 @@ +<?php + +use QUI\ERP\Accounting\Invoice\Handler; +use QUI\ERP\Accounting\Invoice\Utils\Invoice as InvoiceUtils; +use horstoeko\zugferd\ZugferdProfiles; +use QUI\ERP\Output\Output; +use Symfony\Component\HttpFoundation\Response; + +define('QUIQQER_SYSTEM', true); +define('QUIQQER_AJAX', true); + +require_once dirname(__FILE__, 5) . '/header.php'; + +$User = QUI::getUserBySession(); + +if (!$User->canUseBackend()) { + exit; +} + +if (empty($_REQUEST['invoice'])) { + exit; +} + +if (empty($_REQUEST['type'])) { + exit; +} + +$invoiceHash = $_REQUEST['invoice']; +$type = mb_strtoupper($_REQUEST['type']); + +$profileMap = [ + 'PROFILE_BASIC' => ZugferdProfiles::PROFILE_BASIC, + 'PROFILE_BASICWL' => ZugferdProfiles::PROFILE_BASICWL, + 'PROFILE_EN16931' => ZugferdProfiles::PROFILE_EN16931, + 'PROFILE_EXTENDED' => ZugferdProfiles::PROFILE_EXTENDED, + 'PROFILE_XRECHNUNG' => ZugferdProfiles::PROFILE_XRECHNUNG, + 'PROFILE_XRECHNUNG_2' => ZugferdProfiles::PROFILE_XRECHNUNG_2, + 'PROFILE_XRECHNUNG_2_1' => ZugferdProfiles::PROFILE_XRECHNUNG_2_1, + 'PROFILE_XRECHNUNG_2_2' => ZugferdProfiles::PROFILE_XRECHNUNG_2_2, + 'PROFILE_MINIMUM' => ZugferdProfiles::PROFILE_MINIMUM, + 'PROFILE_XRECHNUNG_2_3' => ZugferdProfiles::PROFILE_XRECHNUNG_2_3, + 'PROFILE_XRECHNUNG_3' => ZugferdProfiles::PROFILE_XRECHNUNG_3, +]; + +try { + if ($type === 'PDF') { + $OutputDocument = Output::getDocumentPdf( + $invoiceHash, + QUI\ERP\Accounting\Invoice\Output\OutputProviderInvoice::getEntityType() + ); + + $OutputDocument->download(); + exit; + } + + $Invoice = Handler::getInstance()->getInvoiceByHash($invoiceHash); + $document = InvoiceUtils::getElectronicInvoice($Invoice, $profileMap[$type]); + $xmlContent = $document->getContent(); + $fileName = InvoiceUtils::getInvoiceFilename($Invoice); + + $response = new Response($xmlContent); + $response->headers->set('Content-Type', 'application/xml'); + $response->headers->set('Content-Disposition', 'attachment; filename="'. $fileName . '.xml"'); + $response->headers->set('Content-Length', strlen($xmlContent)); + $response->send(); +} catch (Exception $e) { +} diff --git a/bin/backend/utils/Dialogs.css b/bin/backend/utils/Dialogs.css index 638963a..a5c3d6a 100644 --- a/bin/backend/utils/Dialogs.css +++ b/bin/backend/utils/Dialogs.css @@ -1,4 +1,25 @@ .quiqqer-invoice-dialog-refund-label input { float: left; margin-right: 10px; -} \ No newline at end of file +} + +/** invoice download dialog + ============================================================================== */ + +.quiqqer-invoice-download-dialog { + text-align: center; +} + +.quiqqer-invoice-download-dialog-buttons { + display: flex; + width: 100%; + gap: 1rem; + flex-wrap: wrap; + margin-top: 2rem; + justify-content: center; +} + +.quiqqer-invoice-download-dialog button { + width: 200px; +} + diff --git a/bin/backend/utils/Dialogs.js b/bin/backend/utils/Dialogs.js index 040dc38..b6449c6 100644 --- a/bin/backend/utils/Dialogs.js +++ b/bin/backend/utils/Dialogs.js @@ -11,10 +11,11 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ 'Locale', 'package/quiqqer/invoice/bin/Invoices', 'qui/controls/windows/Confirm', + 'qui/controls/windows/Popup', 'css!package/quiqqer/invoice/bin/backend/utils/Dialogs.css' -], function(QUI, QUILocale, Invoices, QUIConfirm) { +], function (QUI, QUILocale, Invoices, QUIConfirm, QUIPopup) { 'use strict'; const lg = 'quiqqer/invoice'; @@ -28,14 +29,14 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param {String} [entityType] * @return {Promise} */ - openPrintDialog: function(invoiceId, entityType) { + openPrintDialog: function (invoiceId, entityType) { entityType = entityType || 'Invoice'; - return Invoices.getInvoiceHistory(invoiceId).then(function(comments) { - return new Promise(function(resolve) { + return Invoices.getInvoiceHistory(invoiceId).then(function (comments) { + return new Promise(function (resolve) { require([ 'package/quiqqer/erp/bin/backend/controls/OutputDialog' - ], function(OutputDialog) { + ], function (OutputDialog) { new OutputDialog({ entityId: invoiceId, entityType: entityType, @@ -55,11 +56,11 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param {String} invoiceId - Invoice ID or Hash * @return {Promise} */ - openStornoDialog: function(invoiceId) { - return Invoices.get(invoiceId).then(function(result) { + openStornoDialog: function (invoiceId) { + return Invoices.get(invoiceId).then(function (result) { const id = result.id_prefix + result.id; - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { new QUIConfirm({ icon: 'fa fa-ban', texticon: 'fa fa-ban', @@ -80,7 +81,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ maxHeight: 500, maxWidth: 750, events: { - onOpen: function(Win) { + onOpen: function (Win) { const Container = Win.getContent().getElement('.textbody'); // #locale @@ -111,14 +112,14 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ Reason.focus(); }, - onSubmit: function(Win) { + onSubmit: function (Win) { const Reason = Win.getContent().getElement('[name="reason"]'); const value = Reason.value; if (value === '') { Reason.focus(); Reason.required = true; - + if ('reportValidity' in Reason) { Reason.reportValidity(); } @@ -128,10 +129,10 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ Win.Loader.show(); - Invoices.reversalInvoice(result.hash, value).then(function(result) { + Invoices.reversalInvoice(result.hash, value).then(function (result) { Win.close(); resolve(result); - }).catch(function(Exception) { + }).catch(function (Exception) { Win.close(); reject(Exception); }); @@ -150,7 +151,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param {String} invoiceId - Invoice ID or Hash * @return {*|Promise} */ - openCancellationDialog: function(invoiceId) { + openCancellationDialog: function (invoiceId) { return this.openStornoDialog(invoiceId); }, @@ -160,7 +161,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param {String} invoiceId - Invoice ID or Hash * @return {*|Promise} */ - openReversalDialog: function(invoiceId) { + openReversalDialog: function (invoiceId) { return this.openStornoDialog(invoiceId); }, @@ -170,11 +171,11 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param {String} invoiceId - Invoice ID or Hash * @return {Promise} */ - openCopyDialog: function(invoiceId) { - return Invoices.get(invoiceId).then(function(result) { + openCopyDialog: function (invoiceId) { + return Invoices.get(invoiceId).then(function (result) { const id = result.id_prefix + result.id; - return new Promise(function(resolve) { + return new Promise(function (resolve) { new QUIConfirm({ title: QUILocale.get(lg, 'dialog.invoice.copy.title'), text: QUILocale.get(lg, 'dialog.invoice.copy.text'), @@ -191,13 +192,13 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ textimage: 'fa fa-copy' }, events: { - onSubmit: function(Win) { + onSubmit: function (Win) { Win.Loader.show(); - Invoices.copyInvoice(result.hash).then(function(newId) { + Invoices.copyInvoice(result.hash).then(function (newId) { Win.close(); resolve(newId); - }).then(function() { + }).then(function () { Win.Loader.hide(); }); } @@ -213,14 +214,14 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param {String} invoiceId - Invoice ID or Hash * @return {Promise} */ - openCreateCreditNoteDialog: function(invoiceId) { + openCreateCreditNoteDialog: function (invoiceId) { const self = this; - return Invoices.get(invoiceId).then(function(result) { + return Invoices.get(invoiceId).then(function (result) { let paymentHasRefund = false; const id = result.id_prefix + result.id; - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { new QUIConfirm({ icon: 'fa fa-clipboard', texticon: 'fa fa-clipboard', @@ -241,10 +242,10 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ maxHeight: 400, maxWidth: 600, events: { - onOpen: function(Win) { + onOpen: function (Win) { Win.Loader.show(); - Invoices.hasRefund(id).then(function(hasRefund) { + Invoices.hasRefund(id).then(function (hasRefund) { paymentHasRefund = hasRefund; QUI.fireEvent('quiqqerInvoiceCreateCreditNoteDialogOpen', [id, Win]); @@ -274,16 +275,16 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ }); }, - onSubmit: function(Win) { + onSubmit: function (Win) { Win.Loader.show(); const Content = Win.getContent(), Refund = Content.getElement('[name="refund"]'); - const createInvoice = function(values) { + const createInvoice = function (values) { values = values || {}; - Invoices.createCreditNote(result.hash, values).then(function(newId) { + Invoices.createCreditNote(result.hash, values).then(function (newId) { QUI.fireEvent( 'quiqqerInvoiceCreateCreditNoteDialogSubmit', [newId, Win] @@ -291,7 +292,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ resolve(newId); Win.close(); - }).catch(function(Err) { + }).catch(function (Err) { Win.Loader.hide(); console.error(Err); @@ -300,7 +301,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ }; if (paymentHasRefund && Refund.checked) { - self.openRefundWindow(invoiceId).then(function(RefundWindow) { + self.openRefundWindow(invoiceId).then(function (RefundWindow) { if (!RefundWindow) { Win.Loader.hide(); return; @@ -309,7 +310,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ createInvoice({ refund: RefundWindow.getValues() }); - }).catch(function(Err) { + }).catch(function (Err) { Win.Loader.hide(); console.error(Err); }); @@ -319,7 +320,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ createInvoice(); }, - onCancel: function() { + onCancel: function () { resolve(false); } } @@ -333,23 +334,84 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ * @param invoiceId * @return {Promise} */ - openRefundWindow: function(invoiceId) { - return new Promise(function(resolve) { + openRefundWindow: function (invoiceId) { + return new Promise(function (resolve) { require([ 'package/quiqqer/invoice/bin/backend/controls/panels/refund/Window' - ], function(RefundWindow) { + ], function (RefundWindow) { new RefundWindow({ invoiceId: invoiceId, autoRefund: false, events: { onSubmit: resolve, - onCancel: function() { + onCancel: function () { resolve(false); } } }).open(); }); }); + }, + + openDownloadDialog: function (hash) { + new QUIPopup({ + icon: 'fa fa-download', + title: QUILocale.get(lg, 'dialog.invoice.download.title'), + autoclose: false, + maxHeight: 400, + maxWidth: 600, + buttons: false, + events: { + onOpen: function (Win) { + Win.Loader.show(); + + const Content = Win.getContent(); + Content.classList.add('quiqqer-invoice-download-dialog'); + + Content.set( + 'html', + + '<h3>' + QUILocale.get(lg, 'dialog.invoice.download.header') + '</h3>' + + QUILocale.get(lg, 'dialog.invoice.download.text') + + '<div class="quiqqer-invoice-download-dialog-buttons">' + + ' <button value="PDF" class="qui-button">PDF</button>' + + ' <button value="PROFILE_BASIC" class="qui-button">ZUGFeRD Basic</button>' + + ' <button value="PROFILE_EN16931" class="qui-button">ZUGFeRD EN16931</button>' + + ' <button value="PROFILE_EXTENDED" class="qui-button">ZUGFeRD Extended</button>' + + ' <button value="PROFILE_XRECHNUNG_2_3" class="qui-button">XRechnung 2.3</button>' + + ' <button value="PROFILE_XRECHNUNG_3" class="qui-button">XRechnung 3</button>' + + '</div>' + ); + + Content.querySelectorAll('button').forEach(function (Button) { + Button.addEventListener('click', function () { + const id = 'download-invoice-' + hash + '-' + Button.value; + + new Element('iframe', { + src: URL_OPT_DIR + 'quiqqer/invoice/bin/backend/download.php?' + Object.toQueryString({ + invoice: hash, + type: Button.value + }), + id: id, + styles: { + position: 'absolute', + top: -200, + left: -200, + width: 50, + height: 50 + } + }).inject(document.body); + + (function () { + document.getElements('#' + id).destroy(); + }).delay(1000, this); + }); + }); + + Win.Loader.hide(); + } + } + }).open(); } }; }); diff --git a/locale.xml b/locale.xml index c0aeba9..483a0f6 100644 --- a/locale.xml +++ b/locale.xml @@ -1282,6 +1282,19 @@ <en><![CDATA[Please enter a reason for cancellation.]]></en> </locale> + <locale name="dialog.invoice.download.title"> + <de><![CDATA[Lade die Rechnung in verschiedenen Formaten herunter]]></de> + <en><![CDATA[Download the invoice in various formats]]></en> + </locale> + <locale name="dialog.invoice.download.header"> + <de><![CDATA[Rechnung herunterladen]]></de> + <en><![CDATA[Download invoice]]></en> + </locale> + <locale name="dialog.invoice.download.text" html="true"> + <de><![CDATA[<p>Wähle das gewünschte Format aus, um deine Rechnung herunterzuladen.</p>]]></de> + <en><![CDATA[<p>Select the desired format to download your invoice.</p>]]></en> + </locale> + <locale name="dialog.create.address.title"> <de><![CDATA[Rechnungsadresse anlegen]]></de> <en><![CDATA[Create invoice address]]></en> -- GitLab From 0501a860087af46d559f128576360ac9bcbf96b7 Mon Sep 17 00:00:00 2001 From: Henning <leutz@pcsg.de> Date: Sat, 14 Dec 2024 15:50:23 +0100 Subject: [PATCH 3/4] fix: download button - locale --- bin/backend/controls/panels/Invoice.js | 2 +- locale.xml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/backend/controls/panels/Invoice.js b/bin/backend/controls/panels/Invoice.js index 7a2d894..b31874b 100644 --- a/bin/backend/controls/panels/Invoice.js +++ b/bin/backend/controls/panels/Invoice.js @@ -202,7 +202,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/Invoice', [ Actions.appendChild({ icon: 'fa fa-download', - text: QUILocale.get(lg, 'erp.panel.invoice.button.download'), + text: QUILocale.get(lg, 'dialog.invoice.download.button'), events: { onClick: this.download } diff --git a/locale.xml b/locale.xml index 483a0f6..5bf5b5c 100644 --- a/locale.xml +++ b/locale.xml @@ -1282,6 +1282,10 @@ <en><![CDATA[Please enter a reason for cancellation.]]></en> </locale> + <locale name="dialog.invoice.download.button"> + <de><![CDATA[Rechnung herunterladen]]></de> + <en><![CDATA[Download invoice]]></en> + </locale> <locale name="dialog.invoice.download.title"> <de><![CDATA[Lade die Rechnung in verschiedenen Formaten herunter]]></de> <en><![CDATA[Download the invoice in various formats]]></en> -- GitLab From 7397935fd0440804711a18e6294fb4f50f2f8cce Mon Sep 17 00:00:00 2001 From: Henning <leutz@pcsg.de> Date: Sat, 14 Dec 2024 18:33:25 +0100 Subject: [PATCH 4/4] fix: pdf download --- bin/backend/download.php | 52 ++++++++++++++++++++++++------------ bin/backend/utils/Dialogs.js | 2 +- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/bin/backend/download.php b/bin/backend/download.php index bf7c601..408f235 100644 --- a/bin/backend/download.php +++ b/bin/backend/download.php @@ -3,7 +3,8 @@ use QUI\ERP\Accounting\Invoice\Handler; use QUI\ERP\Accounting\Invoice\Utils\Invoice as InvoiceUtils; use horstoeko\zugferd\ZugferdProfiles; -use QUI\ERP\Output\Output; +use QUI\ERP\Defaults; +use QUI\System\Log; use Symfony\Component\HttpFoundation\Response; define('QUIQQER_SYSTEM', true); @@ -43,25 +44,42 @@ ]; try { + $Invoice = Handler::getInstance()->getInvoiceByHash($invoiceHash); + $fileName = InvoiceUtils::getInvoiceFilename($Invoice); + if ($type === 'PDF') { - $OutputDocument = Output::getDocumentPdf( - $invoiceHash, - QUI\ERP\Accounting\Invoice\Output\OutputProviderInvoice::getEntityType() - ); + $defaultTemplates = Defaults::conf('output', 'default_templates'); + $defaultTemplates = json_decode($defaultTemplates, true); - $OutputDocument->download(); - exit; - } + $templateProvider = ''; + $templateId = ''; - $Invoice = Handler::getInstance()->getInvoiceByHash($invoiceHash); - $document = InvoiceUtils::getElectronicInvoice($Invoice, $profileMap[$type]); - $xmlContent = $document->getContent(); - $fileName = InvoiceUtils::getInvoiceFilename($Invoice); + if (!empty($defaultTemplates['Invoice'])) { + $templateProvider = $defaultTemplates['Invoice']['provider']; + $templateId = $defaultTemplates['Invoice']['id']; + } + + $Request = QUI::getRequest(); + + $Request->query->set('id', $invoiceHash); + $Request->query->set('t', 'Invoice'); + $Request->query->set('ep', 'quiqqer/invoice'); + $Request->query->set('tpl', $templateId); + $Request->query->set('tplpr', $templateProvider); - $response = new Response($xmlContent); - $response->headers->set('Content-Type', 'application/xml'); - $response->headers->set('Content-Disposition', 'attachment; filename="'. $fileName . '.xml"'); - $response->headers->set('Content-Length', strlen($xmlContent)); - $response->send(); + include dirname(__FILE__, 4) . '/erp/bin/output/backend/download.php'; + } else { + $document = InvoiceUtils::getElectronicInvoice($Invoice, $profileMap[$type]); + $content = $document->getContent(); + $contentType = 'application/xml'; + $fileName .= '.xml'; + + $response = new Response($content); + $response->headers->set('Content-Type', $contentType); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $fileName . '"'); + $response->headers->set('Content-Length', strlen($content)); + $response->send(); + } } catch (Exception $e) { + Log::writeException($e); } diff --git a/bin/backend/utils/Dialogs.js b/bin/backend/utils/Dialogs.js index b6449c6..96e06de 100644 --- a/bin/backend/utils/Dialogs.js +++ b/bin/backend/utils/Dialogs.js @@ -403,7 +403,7 @@ define('package/quiqqer/invoice/bin/backend/utils/Dialogs', [ }).inject(document.body); (function () { - document.getElements('#' + id).destroy(); + //document.getElements('#' + id).destroy(); }).delay(1000, this); }); }); -- GitLab