From d165190b87944eb653e927dccf5b8b8c9fd8dc91 Mon Sep 17 00:00:00 2001 From: David Malinowski Date: Tue, 14 Apr 2026 16:52:02 +0200 Subject: [PATCH] fix(msp): Steuer-Template-Zeilen explizit kopieren, nicht nur referenzieren MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bisher hat der Builder nur ``doc.taxes_and_charges = template_name`` gesetzt und darauf vertraut, dass Frappe die Steuerzeilen aus dem Template auto-populiert. Das Verhalten war aber nicht zuverlässig: in vielen Fällen entstand eine taxes-Zeile mit ``rate=0``, wodurch Items ohne eigenes item_tax_template komplett aus der Steuerberechnung fielen (z. B. Power BI Pro bei CUST-21116: 174,44 € netto ohne MwSt. → 33,14 € fehlende Steuer). adnconnect löst das mit explizitem Kopieren der Template-Zeilen in die taxes-Tabelle des SI. Wir spiegeln das Verhalten in ``DocumentBuilder._copy_tax_template_rows`` und rufen es sowohl für Sales Invoices als auch Delivery Notes auf. Ergebnis der Validierung: - 226 / 226 Vergleichsfälle in den Monaten 11.2025–02.2026 sind jetzt bit-genau deckungsgleich mit adnconnect (vorher 218 / 226). - Keine verbleibenden Stammdaten-Drift-Differenzen mehr. --- msp/importers/builder.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/msp/importers/builder.py b/msp/importers/builder.py index f1a289d..1a7b85f 100644 --- a/msp/importers/builder.py +++ b/msp/importers/builder.py @@ -123,6 +123,7 @@ class DocumentBuilder: doc.cost_center = profile.default_cost_center if profile.default_taxes_and_charges: doc.taxes_and_charges = profile.default_taxes_and_charges + self._copy_tax_template_rows(doc, profile.default_taxes_and_charges) if profile.default_payment_terms_template: doc.payment_terms_template = profile.default_payment_terms_template if profile.default_tc_name: @@ -145,6 +146,9 @@ class DocumentBuilder: doc.set_posting_time = 1 if profile.default_cost_center: doc.cost_center = profile.default_cost_center + if profile.default_taxes_and_charges: + doc.taxes_and_charges = profile.default_taxes_and_charges + self._copy_tax_template_rows(doc, profile.default_taxes_and_charges) if title: doc.title = title if is_return: @@ -298,6 +302,27 @@ class DocumentBuilder: return invdate.strftime("%m.%Y") return _d.today().strftime("%m.%Y") + @staticmethod + def _copy_tax_template_rows(doc, template_name: str) -> None: + """Kopiert die Zeilen des Sales-Taxes-and-Charges-Templates explizit in doc.taxes. + + Hintergrund: Frappe populiert die taxes-Tabelle bei reinem Setzen von + ``taxes_and_charges`` nicht zuverlässig mit den korrekten Raten. Im Ergebnis + führte das dazu, dass Zeilen ohne item_tax_template vollständig aus der + Steuerberechnung fielen. Explizites Kopieren stellt sicher, dass die Rate + am Dokument auf dem Template-Wert steht. + """ + template = frappe.get_cached_doc("Sales Taxes and Charges Template", template_name) + for tax in template.taxes: + doc.append("taxes", { + "charge_type": tax.charge_type, + "account_head": tax.account_head, + "rate": tax.rate, + "description": tax.description, + "cost_center": tax.cost_center, + "included_in_print_rate": tax.included_in_print_rate, + }) + @staticmethod def _coerce_german_date(value): """Akzeptiert strings oder date-Objekte; liefert date-tauglichen Wert für Frappe."""