Bernd Wurst commited on 2024-02-14 13:27:16
Zeige 5 geänderte Dateien mit 239 Einfügungen und 1 Löschungen.
... | ... |
@@ -8,8 +8,9 @@ from reportlab.pdfbase import pdfmetrics |
8 | 8 |
from reportlab.pdfbase.ttfonts import TTFont, TTFError |
9 | 9 |
from reportlab.pdfgen import canvas |
10 | 10 |
|
11 |
-from .InvoiceObjects import InvoiceTable, InvoiceText, InvoiceImage, GUTSCHRIFT |
|
12 | 11 |
from . import _formatPrice |
12 |
+from .InvoiceObjects import InvoiceTable, InvoiceText, InvoiceImage, GUTSCHRIFT |
|
13 |
+ |
|
13 | 14 |
|
14 | 15 |
def find_font_file(filename): |
15 | 16 |
for n in range(4): |
... | ... |
@@ -0,0 +1,187 @@ |
1 |
+#!/usr/bin/python |
|
2 |
+ |
|
3 |
+import datetime |
|
4 |
+import json |
|
5 |
+import os |
|
6 |
+import subprocess |
|
7 |
+import sys |
|
8 |
+import tempfile |
|
9 |
+ |
|
10 |
+import qrcode |
|
11 |
+ |
|
12 |
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) |
|
13 |
+ |
|
14 |
+from Invoice.InvoiceObjects import Invoice, RECHNUNG, InvoiceText, InvoiceTable, KORREKTUR, PAYMENT_LASTSCHRIFT, \ |
|
15 |
+ InvoiceImage |
|
16 |
+from Invoice.InvoiceToPDF import InvoiceToPDF |
|
17 |
+from Invoice.InvoiceToText import InvoiceToText |
|
18 |
+from Invoice.InvoiceToZUGFeRD import InvoiceToXML, attach_xml |
|
19 |
+ |
|
20 |
+if __name__ == '__main__': |
|
21 |
+ import rechnungsdaten |
|
22 |
+ data = rechnungsdaten.data['data'] |
|
23 |
+ rechnung = rechnungsdaten.data['rechnung'] |
|
24 |
+ mandat = rechnungsdaten.data['mandat'] |
|
25 |
+ adresse = rechnungsdaten.data['adresse'] |
|
26 |
+ daten = rechnungsdaten.data['daten'] |
|
27 |
+ |
|
28 |
+ rechnungsnummer = rechnung['id'] |
|
29 |
+ filename = 'pdf/rechnung_%05i.pdf' % rechnungsnummer |
|
30 |
+ |
|
31 |
+ referenzen = json.loads(rechnung['referenzen']) |
|
32 |
+ |
|
33 |
+ from io import StringIO |
|
34 |
+ file = StringIO() |
|
35 |
+ invoice = Invoice() |
|
36 |
+ if 'leitwegid' in referenzen: |
|
37 |
+ # Behörden-Rechnung, als XRECHNUNG ausgeben! |
|
38 |
+ invoice.leitweg_id = referenzen['leitwegid'] |
|
39 |
+ if 'kundenreferenz' in referenzen: |
|
40 |
+ invoice.buyer_reference = referenzen['kundenreferenz'] |
|
41 |
+ if 'bestellnummer' in referenzen: |
|
42 |
+ invoice.order_number = referenzen['bestellnummer'] |
|
43 |
+ if 'vertragsnummer' in referenzen: |
|
44 |
+ invoice.contract_number = referenzen['vertragsnummer'] |
|
45 |
+ invoice.seller['name'] = 'Bernd Wurst / Johannes Böck' |
|
46 |
+ invoice.seller['trade_name'] = 'schokokeks.org GbR' |
|
47 |
+ invoice.seller['email'] = 'xxxxxxx' |
|
48 |
+ invoice.seller['website'] = 'www.schokokeks.org' |
|
49 |
+ invoice.seller['phone'] = None |
|
50 |
+ invoice.seller['address']['country_id'] = 'DE' |
|
51 |
+ invoice.seller['address']['postcode'] = '71540' |
|
52 |
+ invoice.seller['address']['city_name'] = 'Murrhardt' |
|
53 |
+ invoice.seller['address']['line1'] = 'Köchersberg 32' |
|
54 |
+ invoice.seller['address']['line2'] = None |
|
55 |
+ invoice.seller['address']['line3'] = None |
|
56 |
+ invoice.seller_vat_id = "DE255720588" |
|
57 |
+ invoice.type = RECHNUNG |
|
58 |
+ invoice.logo_image_file = os.path.join(os.path.dirname(__file__), "logo.png") |
|
59 |
+ invoice.seller_bank_data['kontoinhaber'] = "schokokeks.org GbR" |
|
60 |
+ invoice.seller_bank_data['iban'] = "DE91602911200041512006" |
|
61 |
+ invoice.seller_bank_data['bic'] = "GENODES1VBK" |
|
62 |
+ invoice.seller_bank_data['bankname'] = 'Volksbank' |
|
63 |
+ invoice.id = str(rechnungsnummer) |
|
64 |
+ invoice.customerno = rechnung['kunde'] |
|
65 |
+ invoice.customer['name'] = adresse['company'] or adresse['name'] |
|
66 |
+ invoice.customer['email'] = adresse['email'] |
|
67 |
+ invoice.customer['address']['country_id'] = adresse['country'] |
|
68 |
+ invoice.customer['address']['postcode'] = adresse['zip'] |
|
69 |
+ invoice.customer['address']['city_name'] = adresse['city'] |
|
70 |
+ lines = list(filter(None, [ |
|
71 |
+ adresse['name'] if adresse['company'] else None, |
|
72 |
+ ] + adresse['address'].split('\n'))) |
|
73 |
+ while len(lines) < 3: |
|
74 |
+ lines.append(None) |
|
75 |
+ invoice.customer['address']['line1'] = lines[0] |
|
76 |
+ invoice.customer['address']['line2'] = lines[1] |
|
77 |
+ invoice.customer['address']['line3'] = lines[2] |
|
78 |
+ |
|
79 |
+ invoice.setDate(rechnung['datum']) |
|
80 |
+ |
|
81 |
+ for item in data: |
|
82 |
+ if item['info_text']: |
|
83 |
+ text = InvoiceText(item['info_text'], urgent=True, headline=item['info_headline']) |
|
84 |
+ invoice.parts.append(text) |
|
85 |
+ |
|
86 |
+ vatType = 'net' |
|
87 |
+ if data[0]['brutto']: |
|
88 |
+ vatType = 'gross' # Entscheidet anhand des ersten Eintrags ob die Rechnung Netto oder Brutto gestellt wird |
|
89 |
+ tab = InvoiceTable(vatType=vatType) |
|
90 |
+ |
|
91 |
+ for item in data: |
|
92 |
+ # Der Betrag muss immer positiv sein, bei Gutschriften wird nur die Anzahl negativ. |
|
93 |
+ count = float(item['anzahl']) |
|
94 |
+ price = float(item['betrag']) |
|
95 |
+ if price < 0: |
|
96 |
+ count = -count |
|
97 |
+ price = abs(price) |
|
98 |
+ tab.addItem( |
|
99 |
+ {'count': count, |
|
100 |
+ 'unit': item['einheit'], |
|
101 |
+ 'subject': item['beschreibung'], |
|
102 |
+ 'price': price, |
|
103 |
+ 'vat': item['mwst'] / 100, |
|
104 |
+ 'period_start': item['datum'], |
|
105 |
+ 'period_end': item['enddatum'], |
|
106 |
+ } |
|
107 |
+ ) |
|
108 |
+ invoice.parts.append(tab) |
|
109 |
+ |
|
110 |
+ if mandat: |
|
111 |
+ print('''Abbuchung von: |
|
112 |
+ Kontoinhaber: %s |
|
113 |
+ IBAN: %s |
|
114 |
+ BIC: %s |
|
115 |
+ Bank: %s |
|
116 |
+''' % (mandat["kontoinhaber"], mandat["iban"], mandat["bic"], mandat["bankname"])) |
|
117 |
+ iban = mandat['iban'] |
|
118 |
+ iban = iban[:8] + '*' * 10 + iban[-4:] |
|
119 |
+ pretty_iban = iban |
|
120 |
+ for i in range(len(iban) - (len(iban) % 4), 0, -4): |
|
121 |
+ pretty_iban = pretty_iban[:i] + ' ' + pretty_iban[i:] |
|
122 |
+ invoice.debit = True |
|
123 |
+ invoice.creditor_reference_id = mandat['glaeubiger_id'] |
|
124 |
+ invoice.debit_mandate_id = mandat["mandatsreferenz"] |
|
125 |
+ invoice.buyer_bank_data['kontoinhaber'] = mandat['kontoinhaber'] |
|
126 |
+ invoice.buyer_bank_data['iban'] = mandat['iban'] |
|
127 |
+ invoice.buyer_bank_data['bic'] = mandat['bic'] |
|
128 |
+ invoice.buyer_bank_data['bankname'] = mandat['bankname'] |
|
129 |
+ text = None |
|
130 |
+ if tab.sum < 0: |
|
131 |
+ invoice.type = KORREKTUR |
|
132 |
+ invoice.title = 'Korrekturrechnung' |
|
133 |
+ text = InvoiceText("Der verbleibende Betrag wird in wenigen Tagen auf das uns bekannte Konto mit der IBAN %s bei der %s (BIC: %s) überwiesen." % (pretty_iban, mandat["bankname"], mandat["bic"])) |
|
134 |
+ else: |
|
135 |
+ invoice.payment_type = PAYMENT_LASTSCHRIFT |
|
136 |
+ text = InvoiceText("Der fällige Betrag wird gemäß dem von Ihnen erteilten Lastschrift-Mandat in wenigen Tagen vom Konto mit der IBAN %s bei der %s (BIC: %s) abgebucht. Diese Kontodaten beruhen auf dem Mandat Nr. %s vom %s. Unsere Gläubiger-ID lautet %s." % (pretty_iban, mandat["bankname"], mandat["bic"], mandat["mandatsreferenz"], mandat["erteilt"], mandat["glaeubiger_id"])) |
|
137 |
+ invoice.parts.append(text) |
|
138 |
+ else: |
|
139 |
+ if tab.sum < 0: |
|
140 |
+ invoice.type = KORREKTUR |
|
141 |
+ invoice.title = 'Korrekturrechnung' |
|
142 |
+ text = InvoiceText('Der verbleibende Gutschriftsbetrag wird Ihnen in Kürze überwiesen. Sollten Sie uns noch keine Bankverbindung mitgeteilt haben, so bitten wir um eine formlose Nachricht.') |
|
143 |
+ invoice.parts.append(text) |
|
144 |
+ else: |
|
145 |
+ invoice.due_date = datetime.date.today() + datetime.timedelta(days=14) |
|
146 |
+ text = InvoiceText('Bitte begleichen Sie diese Rechnung umgehend nach Erhalt ohne Abzüge auf das unten angegebene Konto. Geben Sie im Verwendungszweck Ihrer Überweisung bitte die Rechnungsnummer %s an, damit Ihre Buchung korrekt zugeordnet werden kann.' % rechnungsnummer) |
|
147 |
+ invoice.parts.append(text) |
|
148 |
+ qr = qrcode.QRCode(box_size=10, border=0, error_correction=qrcode.constants.ERROR_CORRECT_M) |
|
149 |
+ qr.add_data(f"BCD\n002\n1\nSCT\nGENODES1VBK\nschokokeks.org Hosting\nDE91602911200041512006\nEUR{tab.sum:.2f}\n\n\nRE {rechnungsnummer} vom {rechnung['datum']}") |
|
150 |
+ qr.make() |
|
151 |
+ img = qr.make_image() |
|
152 |
+ pilimage = img.get_image() |
|
153 |
+ imgpart = InvoiceImage(pilimage=pilimage, dpi=500, caption="Gerne können Sie diesen GiroCode für Ihre Banking-App verwenden.") |
|
154 |
+ invoice.parts.append(imgpart) |
|
155 |
+ |
|
156 |
+ text = InvoiceText('Wir danken Ihnen, dass Sie unser Angebot in Anspruch genommen haben und hoffen weiterhin auf eine gute Zusammenarbeit. Dieser Rechnung liegen die Allgemeinen Geschäftsbedingungen zum Zeitpunkt des Rechnungsdatums zugrunde, die Sie unter https://www.schokokeks.org/agb abrufen können.') |
|
157 |
+ invoice.parts.append(text) |
|
158 |
+ |
|
159 |
+ textdata = InvoiceToText(invoice) |
|
160 |
+ pdfdata = InvoiceToPDF(invoice) |
|
161 |
+ xmldata = InvoiceToXML(invoice) |
|
162 |
+ if xmldata: |
|
163 |
+ try: |
|
164 |
+ # PDF/A-Konvertierung |
|
165 |
+ with (tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp1, |
|
166 |
+ tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp2): |
|
167 |
+ tmp1.write(pdfdata) |
|
168 |
+ tmp1.close() |
|
169 |
+ tmp2.close() |
|
170 |
+ retcode = subprocess.call( |
|
171 |
+ f'gs -dPDFA -dBATCH -dNOPAUSE -dUseCIEColor -sProcessColorModel=DeviceCMYK -sDEVICE=pdfwrite -dPDFACompatibilityPolicy=1 -sOutputFile={tmp2.name} {tmp1.name}', |
|
172 |
+ shell=True) |
|
173 |
+ # Im Fehlerfall ?! |
|
174 |
+ with open(tmp2.name, 'rb') as f: |
|
175 |
+ pdfdata = f.read() |
|
176 |
+ except: |
|
177 |
+ # Wenn hier was schiefgeht, nutzen wir das alte PDF |
|
178 |
+ pass |
|
179 |
+ # FIXME: Die Metadaten sind noch nicht gültig, da scheint es einen Fehler in der drafthorse-Library zu geben |
|
180 |
+ pdfdata = attach_xml(pdfdata, xmldata, 'EXTENDED') |
|
181 |
+ |
|
182 |
+ f = open(filename, "wb") |
|
183 |
+ f.write(pdfdata) |
|
184 |
+ f.close() |
|
185 |
+ print(filename) |
|
186 |
+ |
|
187 |
+ print(textdata) |
... | ... |
@@ -0,0 +1,48 @@ |
1 |
+import datetime |
|
2 |
+from decimal import Decimal |
|
3 |
+ |
|
4 |
+data = { |
|
5 |
+ 'adresse': {'address': 'Köchersberg 32', |
|
6 |
+ 'city': 'Murrhardt', |
|
7 |
+ 'company': None, |
|
8 |
+ 'country': 'DE', |
|
9 |
+ 'email': None, |
|
10 |
+ 'fax': None, |
|
11 |
+ 'mobile': None, |
|
12 |
+ 'name': 'Bernd Wurst', |
|
13 |
+ 'pgp_id': None, |
|
14 |
+ 'pgp_key': None, |
|
15 |
+ 'phone': None, |
|
16 |
+ 'zip': '71540'}, |
|
17 |
+ 'data': ({'anzahl': Decimal('1.00'), |
|
18 |
+ 'beschreibung': 'Testposten', |
|
19 |
+ 'betrag': Decimal('1.00'), |
|
20 |
+ 'brutto': 1, |
|
21 |
+ 'datum': datetime.date(2023, 9, 20), |
|
22 |
+ 'einheit': None, |
|
23 |
+ 'enddatum': None, |
|
24 |
+ 'info_headline': None, |
|
25 |
+ 'info_text': None, |
|
26 |
+ 'mwst': 19.0},), |
|
27 |
+ 'daten': {'kuendigung': datetime.date(2024, 2, 16), |
|
28 |
+ 'kunde': 5, |
|
29 |
+ 'naechster_kuendigungstermin': datetime.date(2024, 3, 1), |
|
30 |
+ 'startdatum': datetime.date(2004, 10, 1)}, |
|
31 |
+ 'mandat': None, |
|
32 |
+ 'rechnung': {'abbuchung': 0, |
|
33 |
+ 'abschlusstext': None, |
|
34 |
+ 'anrede': None, |
|
35 |
+ 'betrag': Decimal('1.00'), |
|
36 |
+ 'bezahlt': 0, |
|
37 |
+ 'datum': datetime.date(2024, 2, 9), |
|
38 |
+ 'einleitungstext': None, |
|
39 |
+ 'id': 7426, |
|
40 |
+ 'inkasso': 1, |
|
41 |
+ 'kunde': 5, |
|
42 |
+ 'mahnsperre': 0, |
|
43 |
+ 'mahnung': None, |
|
44 |
+ 'notizen': None, |
|
45 |
+ 'pdfdata': None, |
|
46 |
+ 'referenzen': '{"kundenreferenz": "KrfzNr", "bestellnummer": ' |
|
47 |
+ '"08/15-BestNr4711"}', |
|
48 |
+ 'sepamandat': None}} |
|
0 | 49 |