Bernd Wurst commited on 2025-05-03 07:43:46
Zeige 6 geänderte Dateien mit 69 Einfügungen und 52 Löschungen.
... | ... |
@@ -24,7 +24,7 @@ class InvoiceText(object): |
24 | 24 |
|
25 | 25 |
|
26 | 26 |
class InvoiceTable(object): |
27 |
- def __init__(self, vatType='gross', tender=False, summary=True): |
|
27 |
+ def __init__(self, vatType='net', tender=False, summary=True): |
|
28 | 28 |
self.entries = [] |
29 | 29 |
self.vat = {} |
30 | 30 |
self.sum = Decimal('0.0') |
... | ... |
@@ -43,11 +43,15 @@ class InvoiceTable(object): |
43 | 43 |
e = entry |
44 | 44 |
if not ('count' in k and 'unit' in k and 'subject' in k and 'price' in k and 'vat' in k): |
45 | 45 |
raise ValueError('Some data is missing!') |
46 |
+ # Finde heraus, wie viele Dezimalstellen übergeben wurden, minimum aber 2. |
|
47 |
+ digits = max(2, abs(Decimal(f"{e['price']}").as_tuple().exponent)) |
|
46 | 48 |
ret = {'type': 'entry', |
47 |
- 'count': e['count'], |
|
49 |
+ 'count': Decimal(f"{e['count']:.4f}"), |
|
48 | 50 |
'unit': e['unit'], |
49 | 51 |
'subject': e['subject'], |
50 |
- 'price': e['price'], |
|
52 |
+ 'net_price': round(e['price'] if self.vatType == 'net' else e['price'] / Decimal(f"{e['vat'] + 1:.2f}"), digits), |
|
53 |
+ 'gross_price': round(e['price'] * (e['vat'] + 1) if self.vatType == 'net' else e['price'], digits), |
|
54 |
+ 'price': Decimal(e['price']), |
|
51 | 55 |
'total': e['price'] * e['count'], |
52 | 56 |
'vat': Decimal(e['vat']), |
53 | 57 |
'tender': False, |
... | ... |
@@ -74,9 +78,14 @@ class InvoiceTable(object): |
74 | 78 |
Typen sein""" |
75 | 79 |
d = self.validEntry(data) |
76 | 80 |
if not d['vat'] in self.vat.keys(): |
77 |
- self.vat[d['vat']] = [0, chr(65+len(self.vat))] |
|
81 |
+ self.vat[d['vat']] = {'char': chr(65+len(self.vat)), 'net': Decimal("0.00"), |
|
82 |
+ 'vat_amount': Decimal("0.00"), 'gross': Decimal("0.00"), |
|
83 |
+ 'vat': d['vat'],} |
|
78 | 84 |
if 'tender' not in data or not data['tender']: |
79 |
- self.vat[d['vat']][0] += d['total'] |
|
85 |
+ self.vat[d['vat']]['net'] += d['net_price'] * d['count'] |
|
86 |
+ self.vat[d['vat']]['gross'] += d['gross_price'] * d['count'] |
|
87 |
+ # vat wurde bereits vereinheitlicht auf einen Dezimalfaktor |
|
88 |
+ self.vat[d['vat']]['vat_amount'] += d['net_price'] * d['count'] * d['vat'] |
|
80 | 89 |
self.sum += d['total'] |
81 | 90 |
self.entries.append(d) |
82 | 91 |
|
... | ... |
@@ -9,7 +9,7 @@ from reportlab.pdfbase.ttfonts import TTFont, TTFError |
9 | 9 |
from reportlab.pdfgen import canvas |
10 | 10 |
|
11 | 11 |
from . import _formatPrice |
12 |
-from .InvoiceObjects import InvoiceTable, InvoiceText, InvoiceImage, GUTSCHRIFT, KORREKTUR |
|
12 |
+from .InvoiceObjects import InvoiceTable, InvoiceText, InvoiceImage, GUTSCHRIFT, KORREKTUR, VAT_INNERGEM |
|
13 | 13 |
|
14 | 14 |
|
15 | 15 |
def find_font_file(filename): |
... | ... |
@@ -444,7 +444,7 @@ class PDF(object): |
444 | 444 |
self.canvas.drawRightString(left + 13.3 * cm, self.y - self.font_height, |
445 | 445 |
_formatPrice(el['price'])) |
446 | 446 |
self.canvas.drawString(left + 13.7 * cm, self.y - self.font_height, |
447 |
- str(part.vat[el['vat']][1])) |
|
447 |
+ str(part.vat[el['vat']]['char'])) |
|
448 | 448 |
if el['tender']: |
449 | 449 |
self.canvas.drawRightString(left + 16.8 * cm, self.y - self.font_height, 'eventual') |
450 | 450 |
else: |
... | ... |
@@ -493,31 +493,39 @@ class PDF(object): |
493 | 493 |
self.canvas.setFont(self.font, font_size) |
494 | 494 |
self.y -= self.line_height + self.line_padding |
495 | 495 |
summaries = [] |
496 |
- vat = Decimal('0.0') |
|
497 |
- if len(part.vat) == 1 and list(part.vat.keys())[0] == 0.0: |
|
496 |
+ netsum = Decimal('0.0') |
|
497 |
+ if len(part.vat) == 1 and Decimal("0.0") in part.vat: |
|
498 |
+ vat = list(part.vat.values())[0] |
|
499 |
+ netsum += vat['net'] |
|
500 |
+ if self.invoice.vat_type == VAT_INNERGEM: |
|
501 |
+ self.canvas.drawString(left, self.y - self.font_height, |
|
502 |
+ 'Innergemeinschaftliche Lieferung ohne USt.') |
|
503 |
+ else: |
|
498 | 504 |
self.canvas.drawString(left, self.y - self.font_height, |
499 | 505 |
'Dieser Beleg wurde ohne Ausweis von MwSt erstellt.') |
500 | 506 |
self.y -= self.line_height |
501 | 507 |
else: |
502 | 508 |
if len(part.vat) == 1: |
503 |
- vat = list(part.vat.keys())[0] |
|
509 |
+ vat = list(part.vat.values())[0] |
|
510 |
+ netsum += vat['net'] |
|
504 | 511 |
if self.invoice.tender: |
505 |
- summaries.append(('Im Gesamtbetrag sind %.1f%% MwSt enthalten:' % (vat * 100), |
|
506 |
- _formatPrice((part.sum / (vat + 1)) * vat))) |
|
512 |
+ summaries.append(('Im Gesamtbetrag sind %.1f%% MwSt enthalten:' % (vat['vat'] * 100), |
|
513 |
+ _formatPrice(vat['vat_amount']))) |
|
507 | 514 |
else: |
508 | 515 |
summaries.append(( |
509 |
- 'Im Rechnungsbetrag sind %.1f%% MwSt enthalten:' % (vat * 100), |
|
510 |
- _formatPrice((part.sum / (vat + 1)) * vat))) |
|
516 |
+ 'Im Rechnungsbetrag sind %.1f%% MwSt enthalten:' % (vat['vat'] * 100), |
|
517 |
+ _formatPrice(vat['vat_amount']))) |
|
511 | 518 |
else: |
512 |
- for vat, vatdata in part.vat.items(): |
|
513 |
- if vat > 0: |
|
519 |
+ for vat in part.vat.values(): |
|
520 |
+ netsum += vat['net'] |
|
521 |
+ if vat['vat'] > 0: |
|
514 | 522 |
summaries.append(('%s: Im Teilbetrag von %s sind %.1f%% MwSt enthalten:' % ( |
515 |
- vatdata[1], _formatPrice(vatdata[0]), vat * 100), |
|
516 |
- _formatPrice((vatdata[0] / (vat + 1)) * vat))) |
|
523 |
+ vat['char'], _formatPrice(vat['gross']), vat['vat'] * 100), |
|
524 |
+ _formatPrice(vat['vat_amount']))) |
|
517 | 525 |
else: |
518 | 526 |
summaries.append(('%s: Durchlaufende Posten ohne Berechnung von MwSt.' % ( |
519 |
- vatdata[1]), Decimal('0.0'))) |
|
520 |
- summaries.append(('Nettobetrag:', _formatPrice(part.sum - (part.sum / (vat + 1)) * vat))) |
|
527 |
+ vat['char']), Decimal('0.0'))) |
|
528 |
+ summaries.append(('Nettobetrag:', _formatPrice(netsum))) |
|
521 | 529 |
summaries.sort() |
522 | 530 |
for line in summaries: |
523 | 531 |
self.canvas.drawRightString(left + 11.5 * cm, self.y - self.font_height, '('+line[0]) |
... | ... |
@@ -529,7 +537,12 @@ class PDF(object): |
529 | 537 |
self.canvas.drawRightString(left + 16.8 * cm, self.y - self.font_height, _formatPrice(part.sum)) |
530 | 538 |
self.y -= self.line_height |
531 | 539 |
summaries = [] |
532 |
- if list(part.vat.keys())[0] == 0.0: |
|
540 |
+ if len(part.vat) == 1 and Decimal("0.0") in part.vat: |
|
541 |
+ vat = list(part.vat.values())[0] |
|
542 |
+ if self.invoice.vat_type == VAT_INNERGEM: |
|
543 |
+ self.canvas.drawString(left, self.y - self.font_height, |
|
544 |
+ 'Innergemeinschaftliche Lieferung ohne USt.') |
|
545 |
+ else: |
|
533 | 546 |
self.canvas.drawString(left, self.y - self.font_height, |
534 | 547 |
'Dieser Beleg wurde ohne Ausweis von MwSt erstellt.') |
535 | 548 |
self.y -= self.line_height |
... | ... |
@@ -538,17 +551,17 @@ class PDF(object): |
538 | 551 |
vat = list(part.vat.keys())[0] |
539 | 552 |
summaries.append(('zzgl. %.1f%% MwSt:' % (vat * 100), _formatPrice(vat * part.sum))) |
540 | 553 |
else: |
541 |
- for vat, vatdata in part.vat.items(): |
|
542 |
- summaries.append(('zzgl. %.1f%% MwSt (%s):' % (vat * 100, vatdata[1]), |
|
543 |
- _formatPrice(vat * vatdata[0]))) |
|
554 |
+ for vat in part.vat.values(): |
|
555 |
+ summaries.append(('zzgl. %.1f%% MwSt (%s):' % (vat['vat'] * 100, vat['char']), |
|
556 |
+ _formatPrice(vat['vat_amount']))) |
|
544 | 557 |
summaries.sort() |
545 | 558 |
for line in summaries: |
546 | 559 |
self.canvas.drawRightString(left + 14.5 * cm, self.y - self.font_height, line[0]) |
547 | 560 |
self.canvas.drawRightString(left + 16.8 * cm, self.y - self.font_height, line[1]) |
548 | 561 |
self.y -= self.line_height |
549 | 562 |
sum = Decimal('0.0') |
550 |
- for vat, vatdata in part.vat.items(): |
|
551 |
- sum += (vat + 1) * vatdata[0] |
|
563 |
+ for vat in part.vat.values(): |
|
564 |
+ sum += vat['gross'] |
|
552 | 565 |
self.canvas.setFont(self.font + '-Bold', font_size) |
553 | 566 |
if self.invoice.tender: |
554 | 567 |
self.canvas.drawRightString(left + 14.5 * cm, self.y - self.font_height, 'Gesamtbetrag:') |
... | ... |
@@ -578,8 +591,8 @@ class PDF(object): |
578 | 591 |
sum = part.sum |
579 | 592 |
if part.vatType == 'net': |
580 | 593 |
sum = Decimal('0.0') |
581 |
- for vat, vatdata in part.vat.items(): |
|
582 |
- sum += (vat + 1) * vatdata[0] |
|
594 |
+ for vatdata in part.vat.values(): |
|
595 |
+ sum += vatdata['gross'] |
|
583 | 596 |
rest = sum - paysum |
584 | 597 |
if part.payments and rest > 0: |
585 | 598 |
self.canvas.setFont(self.font + '-Bold', font_size) |
... | ... |
@@ -98,7 +98,7 @@ def InvoiceTableToText(invoiceTable: InvoiceTable): |
98 | 98 |
else: |
99 | 99 |
subject = _breakLine(el['subject'], width=41) |
100 | 100 |
ret.append(u'%5.2f %-3s %-37s %10s %s %10s' % ( |
101 |
- el['count'], unit, subject[0], _formatPrice(el['price']), invoiceTable.vat[el['vat']][1], |
|
101 |
+ el['count'], unit, subject[0], _formatPrice(el['price']), invoiceTable.vat[el['vat']]['char'], |
|
102 | 102 |
_formatPrice(el['total']))) |
103 | 103 |
for i in range(1, len(subject)): |
104 | 104 |
ret.append(u' %s' % subject[i]) |
... | ... |
@@ -122,13 +122,13 @@ def InvoiceTableToText(invoiceTable: InvoiceTable): |
122 | 122 |
ret.append('') |
123 | 123 |
summaries = [] |
124 | 124 |
if len(invoiceTable.vat) == 1: |
125 |
- vat = list(invoiceTable.vat.keys())[0] |
|
125 |
+ vat = list(invoiceTable.vat.values())[0] |
|
126 | 126 |
summaries.append(u' Im Rechnungsbetrag sind %.1f%% MwSt enthalten (%s)' % ( |
127 |
- vat * 100, _formatPrice((invoiceTable.sum / (vat + 1)) * vat))) |
|
127 |
+ vat['vat'] * 100, _formatPrice(vat['vat_amount']))) |
|
128 | 128 |
else: |
129 |
- for vat, vatdata in list(invoiceTable.vat.items()): |
|
129 |
+ for vat in invoiceTable.vat.values(): |
|
130 | 130 |
summaries.append(u' %s: Im Teilbetrag von %s sind %.1f%% MwSt enthalten (%s)' % ( |
131 |
- vatdata[1], _formatPrice(vatdata[0]), vat * 100, _formatPrice((vatdata[0] / (vat + 1)) * vat))) |
|
131 |
+ vat['char'], _formatPrice(vat['gross']), vat['vat'] * 100, _formatPrice(vat['vat_amount']))) |
|
132 | 132 |
summaries.sort() |
133 | 133 |
for line in summaries: |
134 | 134 |
ret.append(line) |
... | ... |
@@ -136,19 +136,19 @@ def InvoiceTableToText(invoiceTable: InvoiceTable): |
136 | 136 |
ret.append((u'Nettobetrag: %11s' % _formatPrice(invoiceTable.sum)).rjust(72)) |
137 | 137 |
summaries = [] |
138 | 138 |
if len(invoiceTable.vat) == 1: |
139 |
- vat = list(invoiceTable.vat.keys())[0] |
|
140 |
- summaries.append((u'zzgl. %.1f%% MwSt: %11s' % (vat * 100, _formatPrice(vat * invoiceTable.sum))).rjust(72)) |
|
139 |
+ vat = list(invoiceTable.vat.values())[0] |
|
140 |
+ summaries.append((u'zzgl. %.1f%% MwSt: %11s' % (vat['vat'] * 100, _formatPrice(vat['vat_amount']))).rjust(72)) |
|
141 | 141 |
elif len(invoiceTable.vat) > 1: |
142 |
- for vat, vatdata in list(invoiceTable.vat.items()): |
|
142 |
+ for vat in invoiceTable.vat.values(): |
|
143 | 143 |
summaries.append( |
144 |
- (u'zzgl. %4.1f%% MwSt (%s): %11s' % (vat * 100, vatdata[1], _formatPrice(vat * vatdata[0]))).rjust( |
|
144 |
+ (u'zzgl. %4.1f%% MwSt (%s): %11s' % (vat['vat'] * 100, vat['char'], _formatPrice(vat['vat_amount']))).rjust( |
|
145 | 145 |
72)) |
146 | 146 |
summaries.sort() |
147 | 147 |
for line in summaries: |
148 | 148 |
ret.append(line) |
149 | 149 |
tablesum = invoiceTable.sum |
150 |
- for vat, vatdata in list(invoiceTable.vat.items()): |
|
151 |
- tablesum += vat * vatdata[0] |
|
150 |
+ for vat in invoiceTable.vat.values(): |
|
151 |
+ tablesum += vat['vat_amount'] |
|
152 | 152 |
ret.append((u'Rechnungsbetrag: %11s' % _formatPrice(tablesum)).rjust(72)) |
153 | 153 |
ret.append('') |
154 | 154 |
return '\n'.join(ret) |
... | ... |
@@ -246,14 +246,10 @@ def InvoiceToXML(invoice): |
246 | 246 |
for vat, vatdata in part.vat.items(): |
247 | 247 |
trade_tax = ApplicableTradeTax() |
248 | 248 |
# Steuerbetrag dieses Steuersatzes |
249 |
- amount = vatdata[0] * vat |
|
250 |
- if part.vatType == 'gross': |
|
251 |
- amount = (vatdata[0] / (vat + 1)) * vat |
|
249 |
+ amount = vatdata['vat_amount'] |
|
252 | 250 |
trade_tax.calculated_amount = Decimal(f"{amount:.2f}") |
253 | 251 |
# Nettosumme dieses Steuersatzes |
254 |
- amount = vatdata[0] |
|
255 |
- if part.vatType == 'gross': |
|
256 |
- amount = amount / (1 + vat) |
|
252 |
+ amount = vatdata['net'] |
|
257 | 253 |
trade_tax.basis_amount = Decimal(f"{amount:.2f}") |
258 | 254 |
trade_tax.type_code = "VAT" |
259 | 255 |
if invoice.vat_type == VAT_REGULAR: |
... | ... |
@@ -263,11 +259,10 @@ def InvoiceToXML(invoice): |
263 | 259 |
trade_tax.exemption_reason = 'Als Kleinunternehmer wird gemäß §19 UStG keine USt in Rechnung gestellt.' |
264 | 260 |
elif invoice.vat_type == VAT_INNERGEM: |
265 | 261 |
trade_tax.category_code = "K" |
262 |
+ trade_tax.exemption_reason_code = 'vatex-eu-ic' |
|
263 |
+ trade_tax.exemption_reason = 'Innergemeinschaftliche Lieferung ohne Berechnung von Umsatzsteuer.' |
|
266 | 264 |
trade_tax.rate_applicable_percent = Decimal(f"{vat * 100:.1f}") |
267 |
- if part.vatType == 'gross': |
|
268 |
- summe_ust += (vatdata[0] / (vat + 1)) * vat |
|
269 |
- else: |
|
270 |
- summe_ust += vatdata[0] * vat |
|
265 |
+ summe_ust += vatdata['vat_amount'] |
|
271 | 266 |
doc.trade.settlement.trade_tax.add(trade_tax) |
272 | 267 |
|
273 | 268 |
for paragraph in textparts: |
... | ... |
@@ -12,7 +12,7 @@ import qrcode |
12 | 12 |
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) |
13 | 13 |
|
14 | 14 |
from Invoice.InvoiceObjects import Invoice, RECHNUNG, InvoiceText, InvoiceTable, KORREKTUR, PAYMENT_LASTSCHRIFT, \ |
15 |
- InvoiceImage |
|
15 |
+ InvoiceImage, VAT_INNERGEM |
|
16 | 16 |
from Invoice.InvoiceToPDF import InvoiceToPDF, find_font_file |
17 | 17 |
from Invoice.InvoiceToText import InvoiceToText |
18 | 18 |
from Invoice.InvoiceToZUGFeRD import InvoiceToXML, attach_xml |
... | ... |
@@ -17,13 +17,13 @@ data = { |
17 | 17 |
'data': ({'anzahl': Decimal('12.00'), |
18 | 18 |
'beschreibung': 'Testposten', |
19 | 19 |
'betrag': Decimal('8'), |
20 |
- 'brutto': 1, |
|
20 |
+ 'brutto': 0, |
|
21 | 21 |
'datum': datetime.date(2023, 9, 20), |
22 | 22 |
'einheit': 'Stk', |
23 | 23 |
'enddatum': None, |
24 | 24 |
'info_headline': None, |
25 | 25 |
'info_text': None, |
26 |
- 'mwst': 19.0},), |
|
26 |
+ 'mwst': Decimal('19.0')},), |
|
27 | 27 |
'daten': {'kuendigung': datetime.date(2024, 2, 16), |
28 | 28 |
'kunde': 5, |
29 | 29 |
'naechster_kuendigungstermin': datetime.date(2024, 3, 1), |
30 | 30 |