Zahlungsziel als Klartext (BT-20) und Zahlungsreferenz (BT-83) ergänzt.

BT-20: Das Zahlungsziel erscheint zusätzlich zum strukturierten Fälligkeits-
datum im Klartext ("Zahlbar innerhalb von 14 Tagen ohne Abzug."). Bei Vorkasse
bewusst unterdrückt, da sich beide Angaben widersprechen würden.

BT-83: Die Rechnungsnummer als Verwendungszweck für den automatischen
Zahlungsabgleich - cbc:PaymentID (UBL) bzw. ram:PaymentReference (CII), jeweils
an der schema-korrekten Position der Sequenz.

Beides ergab der Vergleich gegen fünf Eingangs-E-Rechnungen: BT-20 lieferten
vier von fünf Lieferanten, BT-83 zwei von fünf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dfuhrmann3 2026-07-30 12:01:10 +02:00
parent 4b4118fe70
commit df015c9f97
1 changed files with 36 additions and 3 deletions

View File

@ -157,13 +157,36 @@ def format_skonto_text(skonto_percent, skonto_days, invoice_date_str):
return f"Bei Zahlung innerhalb von {days} Tagen gewähren wir {percent_str}% Skonto."
def format_payment_term_text(payment_term_days):
"""Klartext des Zahlungsziels fuer BT-20 ('14' -> 'Zahlbar innerhalb von 14 Tagen ohne
Abzug.'). Das Faelligkeitsdatum steht strukturiert in BT-9; der Klartext daneben ist bei
Lieferanten die Regel (4 von 5 Eingangsrechnungen liefern ihn) und fuer den Empfaenger
lesbar. Gibt "" zurueck, wenn kein gueltiges Zahlungsziel angegeben ist - es wird NICHTS
geraten."""
try:
days = int(str(payment_term_days).strip())
except (TypeError, ValueError):
return ""
if days <= 0:
return ""
return f"Zahlbar innerhalb von {days} Tagen ohne Abzug."
def build_payment_terms_note(data):
"""Kombinierter Freitext fuer die Zahlungsbedingungen (BT-20): optionaler Vorkasse-Hinweis
und/oder eine Skonto-Vereinbarung. Beides sind Zahlungsbedingungen, fuer die EN16931 kein
eigenes strukturiertes Feld kennt. Gibt "" zurueck, wenn keiner von beiden gesetzt ist."""
"""Kombinierter Freitext fuer die Zahlungsbedingungen (BT-20): das Zahlungsziel im Klartext
ODER ein Vorkasse-Hinweis, jeweils optional ergaenzt um eine Skonto-Vereinbarung. Fuer all
das kennt EN16931 kein eigenes strukturiertes Feld. Gibt "" zurueck, wenn nichts davon
gesetzt ist.
Vorkasse und Zahlungsziel schliessen sich bewusst aus: 'Zahlung per Vorkasse vor Lieferung'
und 'zahlbar innerhalb von 14 Tagen' wuerden sich widersprechen."""
parts = []
if data.get("vorkasse"):
parts.append(VORKASSE_HINWEIS)
else:
term_text = format_payment_term_text(data.get("payment_term_days"))
if term_text:
parts.append(term_text)
skonto_text = format_skonto_text(
data.get("skonto_percent"), data.get("skonto_days"), data["invoice_date"]
)
@ -1050,6 +1073,11 @@ def generate_compliant_xrechnung(data):
# BT-81 Zahlungsart: Standard bleibt 58 (SEPA-Ueberweisung), aber ueber
# PAYMENT_MEANS_CODES pro Rechnung waehlbar (siehe UN/EDIFACT 4461).
ET.SubElement(payment_means, f"{{{CBC}}}PaymentMeansCode").text = str(data.get("payment_means_code", "58"))
# BT-83 Zahlungsreferenz (Verwendungszweck): die Rechnungsnummer, damit der Empfaenger die
# Zahlung automatisch zuordnen kann. Muss laut UBL-Sequenz NACH PaymentMeansCode und VOR
# PayeeFinancialAccount stehen.
if str(data.get("invoice_number") or "").strip():
ET.SubElement(payment_means, f"{{{CBC}}}PaymentID").text = str(data["invoice_number"]).strip()
payee_acc = ET.SubElement(payment_means, f"{{{CAC}}}PayeeFinancialAccount")
ET.SubElement(payee_acc, f"{{{CBC}}}ID").text = str(data["iban"]).replace(" ", "")
# BT-86 BIC der Verkaeuferbank (optional, aber von vielen Empfaengern erwartet).
@ -1351,6 +1379,11 @@ def generate_compliant_zugferd_cii(data):
ET.SubElement(dn_ref, f"{{{RAM}}}IssuerAssignedID").text = str(data["delivery_note_id"]).strip()
header_settle = ET.SubElement(trade_trans, f"{{{RAM}}}ApplicableHeaderTradeSettlement")
# BT-83 Zahlungsreferenz (Verwendungszweck): die Rechnungsnummer, damit der Empfaenger die
# Zahlung automatisch zuordnen kann. Muss laut CII-Sequenz VOR InvoiceCurrencyCode stehen
# (die Sequenz ist CreditorReferenceID -> PaymentReference -> ... -> InvoiceCurrencyCode).
if str(data.get("invoice_number") or "").strip():
ET.SubElement(header_settle, f"{{{RAM}}}PaymentReference").text = str(data["invoice_number"]).strip()
ET.SubElement(header_settle, f"{{{RAM}}}InvoiceCurrencyCode").text = "EUR"
pay_means = ET.SubElement(header_settle, f"{{{RAM}}}SpecifiedTradeSettlementPaymentMeans")