fix(msp): Steuer-Template-Zeilen explizit kopieren, nicht nur referenzieren

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.
This commit is contained in:
David Malinowski
2026-04-14 16:52:02 +02:00
parent f284399fec
commit d165190b87
+25
View File
@@ -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."""