├── .gitignore ├── requirements.txt ├── templates ├── import-template.csv └── ubl-3.0.1-xrechnung-template.xml ├── main.spec ├── logoicon.py ├── LICENSE ├── ReadMe.md ├── logofp.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.31.0 2 | jinja2==3.1.2 3 | PyQt5==5.15.9 4 | PyQtWebEngine==5.15.6 5 | pyinstaller==5.13.0 6 | -------------------------------------------------------------------------------- /templates/import-template.csv: -------------------------------------------------------------------------------- 1 | RECHNUNGSNUMMER;LEITSTELLEN_ID_KUNDE;RECHNUNGSDATUM;FAELLIGKEITSDATUM;LEISTUNGSDATUM_BEGINN;LEISTUNGSDATUM_ENDE;UMST_ID_KUNDE;ADRESSE;PLZ;ORT;EMAIL_KUNDE;FIRMENNAME_KUNDE;NETTOPREIS;BRUTTOPREIS;LEISTUNGSNAME;MEHRWERTSTEUER;EIGENE_ADRESSE;EIGENE_PLZ;EIGENER_ORT;EIGENER_FIRMENNAME;EIGENE_UMST_ID;EIGENE_STEUERNUMMER;EIGENER_ANSPRECHPARTNER_NAME;EIGENER_ANSPRECHPARTNER_TELEFON;EIGENER_ANSPRECHPARTNER_EMAIL;IBAN;BIC;KONTOINHABER;REGISTERNUMMER;REGISTERGERICHT;NOTIZ 2 | RE-1234;04011000-12345ABCXYZ-86 ;01.02.2024;07.02.2024;20.02.2024;22.02.2024;DE1234567890;Musterstraße 1;12345;Musterstadt;kunden@email.de;Kunde GmbH;1000;1190;Leistung 1;19;Musterweg 1;54321;Musterstadt;Seller GmbH;DE1234567891;123/456/123;Herr Mustermann;0202 0000000;seller@mail.de;DE02120300000000202051;BYLADEM1001;Musterchef;HRB 12345;Musterstadt;Weitere Infos 3 | -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | 4 | a = Analysis( 5 | ['main.py'], 6 | pathex=[], 7 | binaries=[], 8 | datas=[('templates/ubl-3.0.1-xrechnung-template.xml', 'templates')], 9 | hiddenimports=[], 10 | hookspath=[], 11 | hooksconfig={}, 12 | runtime_hooks=[], 13 | excludes=[], 14 | noarchive=False, 15 | ) 16 | pyz = PYZ(a.pure) 17 | 18 | exe = EXE( 19 | pyz, 20 | a.scripts, 21 | a.binaries, 22 | a.datas, 23 | [], 24 | name='xRechnung-v', 25 | debug=False, 26 | bootloader_ignore_signals=False, 27 | strip=False, 28 | upx=True, 29 | upx_exclude=[], 30 | runtime_tmpdir=None, 31 | console=False, 32 | disable_windowed_traceback=False, 33 | argv_emulation=False, 34 | target_arch=None, 35 | codesign_identity=None, 36 | entitlements_file=None, 37 | icon='assets/exe-icon.ico' 38 | ) 39 | -------------------------------------------------------------------------------- /logoicon.py: -------------------------------------------------------------------------------- 1 | icon = b'iVBORw0KGgoAAAANSUhEUgAAABsAAAAuCAYAAADZY2JLAAAACXBIWXMAAAsTAAALEwEAmpwYAAADA0lEQVRYhb2Yy2tTQRSHv4mCWkVN3w/TasTgQhsx3YhQEYJLwYW6y0Z3/hdu9C8Q3GYjuHHnoi24EHRhCwaLSMFgW1/V6K2irS3CuJjk5r56z4mFHkjvzdw5893fZGbOOTXWWgBMrZoH8qSbZ4uVuWijqVXLgh/A3G5jjIM8unOdY8M3JQdgwocYU+b2jRNcnrwnkWyxYjLAVWCaB1MSCKAUAJWAaebrIqj5kmRaNzx8ovABU6tmm7fuenJM41YHyFhrZ/ym9180jqXIVWO+Mv8LjVWNY3gRnTreMazu/n7QOLamUbMCE2FO2ZtFjWNYWWFU6u/ZYsWLw56/6gSmVebvyzBsuQHrG1oYXLuogfkLMANgrfUAzwFXRJgxppPfqx6CNc2pm38ru9+6MgR0tMeiMCf3Y0N2P7h/UkMBCJ6lQZh7g6XP8gjDfd2AZo/NBL/EYVOzMmxjcxyArr1Sz1CE8GHW2vaDhaX0IUb6ewA40i/BvERY05zsd5/Sh9iVyZLrlUCwlbLQw19r6UPkBuDcaZFki5UtfzNoyZaOrX174MJZiRWL6MnKNLHt/Pj2YP8R29KsHm2IKmu/0aJiv6WbOI3tTivfdgTm5A/0bBcWSxuSYC6E9B3eQZgcgSWLJbxJsDKXJhKaI/ZyQeqRrqyZeMqn+foGPH62PZjfYVBYHMsrqkzM1KohYBTm5vnoUPooX1dhVpxGiKhLVpYbSB+itQe//5BgoUUSh00U3EGbZpt/Xe7QEGHJypqlU5YzBWkAaKy66CrFvZRpdA+GFUHxxeungBz3INssMmMw15gfkWG1upOkycQC6uLKRoXF4Q5Yd37+/K2BJSorkeuF7kOSczt71hWQfvacATDGZIG8Jq8AZkJBVraYMjeFunQ6bFLaB/lWaRyG6WJYKyg6dWt/ND6lIMxJHRvUOHqhq65aDcGcMjnDDRYKsbCfYvkwTFfYebEWXWnslPmFnW5xzMXuO5xGN4UHujRO9di9LtT4/9+6C1jhc99aS/Sj8LNAudX/Hylm+tkCtzZyAAAAAElFTkSuQmCC' 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 [XSentry](https://github.com/xSentry) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # XRechnung XML Generator 2 | 3 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | 5 | ## Introduction 6 | The **XRechnung XML Generator** is a tool designed to generate XML files that comply with the XRechnung standard from CSV exports. XRechnung is an XML-based semantic data model used for electronic invoices, particularly in transactions with public clients in Germany. Starting January 1, 2025, all companies will be required to issue and receive e-invoices in accordance with EN 16931. 7 | 8 | ## Table of Contents 9 | 1. [Features](#features) 10 | 2. [Installation](#installation) 11 | 3. [Usage](#usage) 12 | 4. [Adding a New UBL Template](#adding-a-new-ubl-template) 13 | 5. [Contributing](#contributing) 14 | 6. [License](#license) 15 | 7. [Contact](#contact) 16 | 17 | ## Features 18 | - Generate XRechnung-compliant XML files from CSV inputs. 19 | - Supports multiple UBL template versions. 20 | - Simple command-line interface for generating executable files. 21 | 22 | ## Installation 23 | To install and set up the project locally, follow these steps: 24 | 25 | 1. Clone the repository: 26 | ```bash 27 | git clone https://github.com/xSentry/xrechnungs-generator.git 28 | cd xrechnungs-generator 29 | ``` 30 | 31 | 2. Install the required Python packages: 32 | ```bash 33 | pip install -r requirements.txt 34 | ``` 35 | 36 | 3. To create an executable file, run: 37 | ```bash 38 | pyinstaller main.spec 39 | ``` 40 | 41 | 4. The new executable file (`xRechnung-v*.exe`) will be located in the `dist/` folder. Replace `*` with the version number. 42 | 43 | ## Usage 44 | To generate XML files using the tool: 45 | 46 | 1. Place your CSV file in the appropriate directory. 47 | 2. Run the executable or the `main.py` script. 48 | 3. Follow the prompts to select the CSV file and desired output location. 49 | 50 | ## Adding a New UBL Template 51 | To add a new UBL template, follow these steps: 52 | 53 | 1. Create a template in the `/templates/` folder with the format `ubl-VERSION_NUMBER-xrechnung-template.xml`. 54 | 2. Add the version number to the array on line 81 of `main.py`: 55 | ```python 56 | self.selectUblVersion.addItems(['3.0.1', 'VERSION_NUMBER']) 57 | ``` 58 | 3. Update `main.spec` to include the new template file in the `datas` field: 59 | ```python 60 | datas=[('templates/ubl-3.0.1-xrechnung-template.xml', 'templates'), ('templates/ubl-VERSION_NUMBER-xrechnung-template.xml', 'templates')], 61 | ``` 62 | 4. Generate a new executable as described in the Installation section. 63 | 64 | ## Contributing 65 | We welcome contributions! To contribute: 66 | 67 | 1. Fork the repository. 68 | 2. Create a new branch for your feature or bug fix: 69 | ```bash 70 | git checkout -b feature-name 71 | ``` 72 | 3. Commit your changes: 73 | ```bash 74 | git commit -m "Add feature-name" 75 | ``` 76 | 4. Push to the branch: 77 | ```bash 78 | git push origin feature-name 79 | ``` 80 | 5. Create a Pull Request. 81 | 82 | ## License 83 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 84 | 85 | ## Contact 86 | For questions or support, please open an issue on the GitHub repository or contact the maintainer directly through GitHub. 87 | -------------------------------------------------------------------------------- /templates/ubl-3.0.1-xrechnung-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 6 | urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 7 | {{ data.invoiceNumber }} 8 | {{ data.invoiceDate }} 9 | {{ data.dueDate }} 10 | 380 11 | {{ data.note }} 12 | EUR 13 | {{ data.leitwegID }} 14 | 15 | 16 | {{ data.ownContactEmail }} 17 | 18 | {{ data.ownCompanyName }} 19 | 20 | 21 | {{ data.ownStreetname }} 22 | {{ data.ownCityname }} 23 | {{ data.ownPostalCode }} 24 | 25 | DE 26 | 27 | 28 | 29 | {{ data.ownCompanyID }} 30 | 31 | VAT 32 | 33 | 34 | 35 | {{ data.ownCompanyName }} 36 | {{ data.ownHraNo }} 37 | {{ data.ownHraNo }}, Eintrag im {{ data.ownHraName }} 38 | 39 | 40 | {{ data.ownContactName }} 41 | {{ data.ownContactNPhone }} 42 | {{ data.ownContactEmail }} 43 | 44 | 45 | 46 | 47 | 48 | {{ data.customerEmail }} 49 | 50 | {{ data.customerCompanyID }} 51 | 52 | 53 | {{ data.customerStreetname }} 54 | {{ data.customerCityname }} 55 | {{ data.customerPostalZone }} 56 | 57 | DE 58 | 59 | 60 | 61 | {{ data.customerCompanyName }} 62 | 63 | 64 | 65 | 66 | 58 67 | 68 | {{ data.ownIban }} 69 | 70 | 71 | 72 | Zahlung innerhalb von {{ data.dueDays }} Tagen ab Rechnungseingang ohne Abzüge 73 | 74 | 75 | {{ data.priceTax }} 76 | 77 | {{ data.priceNet }} 78 | {{ data.priceTax }} 79 | 80 | S 81 | {{ data.taxPercent }} 82 | 83 | VAT 84 | 85 | 86 | 87 | 88 | 89 | {{ data.priceNet }} 90 | {{ data.priceNet }} 91 | {{ data.priceFull }} 92 | {{ data.priceFull }} 93 | 94 | 95 | {{ data.positionName }} 96 | 1 97 | {{ data.priceNet }} 98 | 99 | {{ data.periodStart }} 100 | {{ data.periodEnd }} 101 | 102 | 103 | {{ data.positionName }} 104 | 105 | 246 106 | 107 | 108 | S 109 | {{ data.taxPercent }} 110 | 111 | VAT 112 | 113 | 114 | 115 | 116 | {{ data.priceNet }} 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /logofp.py: -------------------------------------------------------------------------------- 1 | img = b'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' 2 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import sys 4 | import requests 5 | from pathlib import Path 6 | from datetime import datetime 7 | from jinja2 import Template 8 | from PyQt5.QtGui import QCursor, QPixmap, QIcon 9 | from PyQt5.QtCore import Qt, QByteArray 10 | from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QFileDialog, QMessageBox, QLabel, QGridLayout, QComboBox, QDialog 11 | from PyQt5.QtWebEngineWidgets import QWebEngineView 12 | import ctypes 13 | ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('xrechnungs.gen') 14 | 15 | from logofp import img 16 | from logoicon import icon 17 | 18 | class Invoice(): 19 | def __init__(self): 20 | self.invoiceNumber = None 21 | self.leitwegID = None 22 | self.invoiceDate = None 23 | self.dueDate = None 24 | self.note = None 25 | self.dueDays = None #Berechnen 26 | self.periodStart = None 27 | self.periodEnd = None 28 | self.customerCompanyID = None #UmstID 29 | self.customerStreetname = None 30 | self.customerPostalZone = None 31 | self.customerCityname = None 32 | self.customerEmail = None 33 | self.customerCompanyName = None 34 | #Zusatzadresse 35 | self.priceNet = None 36 | self.priceFull = None 37 | self.priceTax = None #Berechnen 38 | self.positionName = None 39 | self.taxPercent = None 40 | self.ownStreetname = None 41 | self.ownPostalCode = None 42 | self.ownCityname = None 43 | self.ownCompanyName = None 44 | self.ownCompanyID = None #UmstID 45 | self.ownTaxNo = None #Steuernummer 46 | #ownContactCompanyName 47 | self.ownContactName = None 48 | self.ownContactPhone = None 49 | self.ownContactEmail = None 50 | self.ownIban = None 51 | self.ownBic = None 52 | self.ownAccountOwner = None 53 | self.ownHraNo = None #Handelsregisternummer 54 | self.ownHraName = None #Handelsregister Name 55 | 56 | class XRechnungGenerator(QWidget): 57 | 58 | def __init__(self): 59 | super().__init__() 60 | self.title = 'XRechnungs Generator' 61 | self.left = 100 62 | self.top = 100 63 | self.width = 640 64 | self.height = 100 65 | 66 | self.windowIcon = QPixmap() 67 | self.windowIcon.loadFromData(QByteArray.fromBase64(icon)) 68 | self.setWindowIcon(QIcon(self.windowIcon)) 69 | 70 | self.selectedFileName = None 71 | self.selectedUblVersion = None 72 | self.alertText = '' 73 | 74 | self.initUI() 75 | 76 | def initUI(self): 77 | self.setWindowTitle(self.title) 78 | self.setGeometry(self.left, self.top, self.width, self.height) 79 | self.setFixedSize(self.width, 220) 80 | 81 | selectUblVersionLabel = QLabel() 82 | selectUblVersionLabel.setStyleSheet("color: grey") 83 | selectUblVersionLabel.setText("Select UBL Version") 84 | self.selectUblVersion = QComboBox() 85 | self.selectUblVersion.addItems(['3.0.1']) 86 | 87 | self.selectedFileInfo = QLabel() 88 | self.selectedFileInfo.setStyleSheet("color: grey") 89 | 90 | buttonFileSelect = QPushButton('Select CSV') 91 | buttonFileSelect.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) 92 | 93 | self.buttonStartGen = QPushButton('Generate XRechnungen') 94 | self.buttonStartGen.setEnabled(False) 95 | self.buttonStartGen.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) 96 | 97 | buttonValidateXml = QPushButton('Validate XRechnung') 98 | buttonValidateXml.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) 99 | 100 | buttonFileSelect.clicked.connect(self.openFileNameDialog) 101 | self.buttonStartGen.clicked.connect(self.generateXRechnungFromCsv) 102 | buttonValidateXml.clicked.connect(self.openXmlValidationDialog) 103 | 104 | logo = QPixmap() 105 | logo.loadFromData(QByteArray.fromBase64(img)) 106 | logoLabel = QLabel() 107 | logoLabel.setPixmap(logo) 108 | 109 | layout = QGridLayout(self) 110 | layout.setContentsMargins(50, 10, 50, 50) 111 | layout.setRowStretch(0 | 1 | 2 | 3 | 4, 25) 112 | layout.setColumnStretch(0 | 1 | 2, 25) 113 | layout.setColumnMinimumWidth(0, 400) 114 | layout.setColumnMinimumWidth(1, 400) 115 | layout.addWidget(logoLabel, 0, 0) 116 | layout.addWidget(selectUblVersionLabel, 1, 0) 117 | layout.addWidget(self.selectUblVersion, 2, 0) 118 | layout.addWidget(buttonValidateXml, 2, 1) 119 | layout.addWidget(self.selectedFileInfo, 3, 0) 120 | layout.addWidget(buttonFileSelect, 4, 0) 121 | layout.addWidget(self.buttonStartGen, 4, 1) 122 | 123 | self.show() 124 | 125 | def openFileNameDialog(self): 126 | options = QFileDialog.Options() 127 | options |= QFileDialog.DontUseNativeDialog 128 | fileName, _ = QFileDialog.getOpenFileName(self,"Select Invoice-CSVs", "./","CSV Files (*.csv)", options=options) 129 | if fileName: 130 | self.selectedFileName = fileName 131 | self.selectedFileInfo.setText(fileName) 132 | self.buttonStartGen.setEnabled(True) 133 | 134 | def openXmlValidationDialog(self): 135 | options = QFileDialog.Options() 136 | options |= QFileDialog.DontUseNativeDialog 137 | fileName, _ = QFileDialog.getOpenFileName(self,"Select XRechnung", "./rechnungen","XML Files (*.xml)", options=options) 138 | if fileName: 139 | QApplication.setOverrideCursor(Qt.WaitCursor) 140 | self.validateXmlFile(fileName) 141 | 142 | def validateXmlFile(self, fileName): 143 | try: 144 | url = 'https://xrechnung-validator.demo.epoconsulting.com/' 145 | 146 | files = {'file': open(fileName, 'rb')} 147 | 148 | request = requests.post(url, files=files) 149 | response = request.text 150 | 151 | if (response): 152 | QApplication.restoreOverrideCursor() 153 | 154 | dialog = QDialog() 155 | dialog.setGeometry(100, 100, 0, 0) 156 | dialog.setStyleSheet('padding: 0') 157 | dialog.setFixedSize(900, 800) 158 | dialog.setWindowIcon(QIcon(self.windowIcon)) 159 | dialog.setWindowTitle('Validate XRechnung') 160 | 161 | htmlView = QWebEngineView(dialog) 162 | htmlView.setHtml(response) 163 | htmlView.setGeometry(0, 0, 900, 800) 164 | dialog.exec_() 165 | except: 166 | self.alertText = 'Validating XML failed. Is the file format correct and an internet connection available?' 167 | self.openAlert() 168 | 169 | def openAlert(self): 170 | windowIcon = QPixmap() 171 | windowIcon.loadFromData(QByteArray.fromBase64(icon)) 172 | 173 | alert = QMessageBox() 174 | alert.setWindowIcon(QIcon(windowIcon)) 175 | alert.setWindowTitle('XRechnung - An error has occurred!') 176 | alert.setText(self.alertText) 177 | alert.exec() 178 | self.alertText = '' 179 | 180 | def generateXRechnungFromCsv(self): 181 | if not self.selectedFileName or self.selectedFileName == '': 182 | return 183 | 184 | csvInvoices = [] 185 | 186 | file = self.selectedFileName 187 | 188 | failedCsvImports = 0 189 | 190 | with open(file, 'r') as f: 191 | reader = csv.reader(f, delimiter=';') 192 | 193 | expectedCsvStructure = ['RECHNUNGSNUMMER', 'LEITSTELLEN_ID_KUNDE', 'RECHNUNGSDATUM', 'FAELLIGKEITSDATUM', 'LEISTUNGSDATUM_BEGINN', 'LEISTUNGSDATUM_ENDE', 'UMST_ID_KUNDE', 'ADRESSE', 'PLZ', 'ORT', 'EMAIL_KUNDE', 'FIRMENNAME_KUNDE', 'NETTOPREIS', 'BRUTTOPREIS', 'LEISTUNGSNAME', 'MEHRWERTSTEUER', 'EIGENE_ADRESSE', 'EIGENE_PLZ', 'EIGENER_ORT', 'EIGENER_FIRMENNAME', 'EIGENE_UMST_ID', 'EIGENE_STEUERNUMMER', 'EIGENER_ANSPRECHPARTNER_NAME', 'EIGENER_ANSPRECHPARTNER_TELEFON', 'EIGENER_ANSPRECHPARTNER_EMAIL', 'IBAN', 'BIC', 'KONTOINHABER', 'REGISTERNUMMER', 'REGISTERGERICHT', 'NOTIZ'] 194 | 195 | for index, row in enumerate(reader): 196 | if index == 0: 197 | if row != expectedCsvStructure: 198 | self.alertText = 'The CSV file is in an incorrect format! \n \n Expected: \n' + str(expectedCsvStructure) + '\n \n Erhalten: \n' + str(row) 199 | self.openAlert() 200 | return 201 | continue 202 | 203 | invoice = Invoice() 204 | 205 | invoiceDate = datetime.strptime(row[2], '%d.%m.%Y').strftime('%Y-%m-%d') 206 | dueDate = datetime.strptime(row[3], '%d.%m.%Y').strftime('%Y-%m-%d') 207 | periodStartDate = datetime.strptime(row[4], '%d.%m.%Y').strftime('%Y-%m-%d') 208 | periodEndDate = datetime.strptime(row[5], '%d.%m.%Y').strftime('%Y-%m-%d') 209 | 210 | invoice.invoiceNumber = row[0] 211 | invoice.leitwegID = row[1] 212 | invoice.invoiceDate = invoiceDate 213 | invoice.dueDate = dueDate 214 | invoice.note = row[30] 215 | invoice.periodStart = periodStartDate 216 | invoice.periodEnd = periodEndDate 217 | invoice.customerCompanyID = row[6] 218 | invoice.customerStreetname = row[7] 219 | invoice.customerPostalZone = row[8] 220 | invoice.customerCityname = row[9] 221 | invoice.customerEmail = row[10] 222 | invoice.customerCompanyName = row[11].replace('&', '&') 223 | invoice.priceNet = float(row[12].replace(',', '.')) 224 | invoice.priceFull = float(row[13].replace(',', '.')) 225 | invoice.priceTax = round(invoice.priceFull - invoice.priceNet, 2) 226 | invoice.positionName = row[14] 227 | invoice.taxPercent = row[15] 228 | invoice.ownStreetname = row[16] 229 | invoice.ownPostalCode = row[17] 230 | invoice.ownCityname = row[18] 231 | invoice.ownCompanyName = row[19].replace('&', '&') 232 | invoice.ownCompanyID = row[20] 233 | invoice.ownTaxNo = row[21] 234 | invoice.ownContactName = row[22] 235 | invoice.ownContactPhone = row[23] 236 | invoice.ownContactEmail = row[24] 237 | invoice.ownIban = row[25] 238 | invoice.ownBic = row[26] 239 | invoice.ownAccountOwner = row[27] 240 | invoice.ownHraNo = row[28] 241 | invoice.ownHraName = row[29] 242 | 243 | invoiceDateObject = datetime.strptime(invoice.invoiceDate, '%Y-%m-%d') 244 | dueDateObject = datetime.strptime(invoice.dueDate, '%Y-%m-%d') 245 | 246 | dateDiff = dueDateObject - invoiceDateObject 247 | invoice.dueDays = dateDiff.days 248 | 249 | csvInvoices.append(invoice) 250 | 251 | if failedCsvImports == 1: 252 | self.alertText = str(failedCsvImports) + ' CSV entry was incorrect and could not be imported!' 253 | self.openAlert() 254 | 255 | if failedCsvImports > 1: 256 | self.alertText = str(failedCsvImports) + ' CSV entries were incorrect and could not be imported!' 257 | self.openAlert() 258 | 259 | Path("./rechnungen").mkdir(exist_ok=True) 260 | 261 | for invoice in csvInvoices: 262 | templateFile = resource_path('./templates/ubl-' + self.selectUblVersion.currentText() + '-xrechnung-template.xml') 263 | with open(templateFile, encoding='utf-8') as xrechnung: 264 | template = Template(xrechnung.read()) 265 | 266 | renderedXml = template.render(data=invoice) 267 | 268 | with open('./rechnungen/' + invoice.invoiceNumber + '.xml', 'w', encoding='utf-8') as renderedXRechnung: 269 | renderedXRechnung.write(renderedXml) 270 | 271 | if getattr(sys, 'frozen', False): 272 | application_path = os.path.dirname(sys.executable) 273 | elif __file__: 274 | application_path = os.path.dirname(__file__) 275 | 276 | folderPath = application_path + '/rechnungen' 277 | os.startfile( 278 | folderPath 279 | ) 280 | 281 | def resource_path(relative_path): 282 | try: 283 | base_path = sys._MEIPASS 284 | except Exception: 285 | base_path = os.path.abspath(".") 286 | 287 | return os.path.join(base_path, relative_path) 288 | 289 | app = QApplication(sys.argv) 290 | 291 | app.setStyle('Fusion') 292 | 293 | ex = XRechnungGenerator() 294 | sys.exit(app.exec_()) --------------------------------------------------------------------------------