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 |