feat: make payment means code (BT-81) dynamically selectable
This commit is contained in:
parent
ae986b4f3f
commit
6ecd59e5ea
|
|
@ -58,6 +58,16 @@ VERKAEUFER = {
|
|||
"register_id": "HRB 6058 NP", # BT-30 (Handelsregisternummer, NICHT die USt-ID)
|
||||
}
|
||||
|
||||
# UN/EDIFACT 4461 Zahlungsmittelcodes (BT-81), Auswahl der fuer B2B/B2G relevanten Codes.
|
||||
# "58" bleibt der Standardwert (SEPA-Ueberweisung), da alle bisherigen Rechnungen diesen nutzen.
|
||||
PAYMENT_MEANS_CODES = {
|
||||
"58": "SEPA-Überweisung",
|
||||
"30": "Überweisung (allgemein)",
|
||||
"31": "SEPA-Lastschrift",
|
||||
"42": "Einzahlung auf Bankkonto",
|
||||
"48": "Bankkarte (Kredit-/Debitkarte)",
|
||||
}
|
||||
|
||||
|
||||
def berechne_faelligkeit(invoice_date_str, term_days):
|
||||
"""Fälligkeitsdatum = Rechnungsdatum + term_days. Gibt 'YYYY-MM-DD' zurück, oder leer,
|
||||
|
|
@ -287,9 +297,9 @@ def generate_compliant_xrechnung(data):
|
|||
ET.SubElement(delivery, f"{{{CBC}}}ActualDeliveryDate").text = parse_to_strict_xml_date(data["delivery_date"])
|
||||
|
||||
payment_means = ET.SubElement(root, f"{{{CAC}}}PaymentMeans")
|
||||
# BT-81 Zahlungsart: Code 58 = SEPA-Überweisung (statt 30, das für Überweisungen
|
||||
# außerhalb des SEPA-Raums gedacht ist - VD-Valitool-169-Hinweis).
|
||||
ET.SubElement(payment_means, f"{{{CBC}}}PaymentMeansCode").text = "58"
|
||||
# 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"))
|
||||
payee_acc = ET.SubElement(payment_means, f"{{{CAC}}}PayeeFinancialAccount")
|
||||
ET.SubElement(payee_acc, f"{{{CBC}}}ID").text = str(data["iban"]).replace(" ", "")
|
||||
|
||||
|
|
@ -506,10 +516,9 @@ def generate_compliant_zugferd_cii(data):
|
|||
ET.SubElement(header_settle, f"{{{RAM}}}InvoiceCurrencyCode").text = "EUR"
|
||||
|
||||
pay_means = ET.SubElement(header_settle, f"{{{RAM}}}SpecifiedTradeSettlementPaymentMeans")
|
||||
# BT-81 Zahlungsart: Code 58 = SEPA-Überweisung. Code 30 ist für Überweisungen AUSSERHALB
|
||||
# des SEPA-Raums gedacht (VD-Valitool-169-Hinweis) - da hier immer eine IBAN verwendet wird,
|
||||
# ist 58 der korrekte Code.
|
||||
ET.SubElement(pay_means, f"{{{RAM}}}TypeCode").text = "58"
|
||||
# BT-81 Zahlungsart: Standard bleibt 58 (SEPA-Ueberweisung), aber ueber
|
||||
# PAYMENT_MEANS_CODES pro Rechnung waehlbar (siehe UN/EDIFACT 4461).
|
||||
ET.SubElement(pay_means, f"{{{RAM}}}TypeCode").text = str(data.get("payment_means_code", "58"))
|
||||
payee_acc = ET.SubElement(pay_means, f"{{{RAM}}}PayeePartyCreditorFinancialAccount")
|
||||
ET.SubElement(payee_acc, f"{{{RAM}}}IBANID").text = str(data["iban"]).replace(" ", "")
|
||||
|
||||
|
|
|
|||
13
main.py
13
main.py
|
|
@ -11,6 +11,7 @@ from invoice_logic import (
|
|||
InvoiceMetadata,
|
||||
VERKAEUFER,
|
||||
VERKAEUFER_IBAN,
|
||||
PAYMENT_MEANS_CODES,
|
||||
berechne_faelligkeit,
|
||||
kaufmaennisches_runden,
|
||||
clean_german_float,
|
||||
|
|
@ -252,6 +253,17 @@ with col2:
|
|||
r_zip = st.text_input("PLZ (BT-53)", value=data.get("recipient_zip", ""))
|
||||
r_city = st.text_input("Stadt (BT-52) :red[*]", value=data.get("recipient_city", ""))
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
payment_means_codes_list = list(PAYMENT_MEANS_CODES.keys())
|
||||
payment_means_labels = [f"{code} - {PAYMENT_MEANS_CODES[code]}" for code in payment_means_codes_list]
|
||||
selected_payment_means_label = st.selectbox(
|
||||
"Zahlungsmittel (BT-81)",
|
||||
options=payment_means_labels,
|
||||
index=payment_means_codes_list.index("58"),
|
||||
)
|
||||
selected_payment_means_code = selected_payment_means_label.split(" - ")[0]
|
||||
|
||||
st.markdown("### 3. Positionen")
|
||||
|
||||
if st.button("➕ Neue Position hinzufügen"):
|
||||
|
|
@ -335,6 +347,7 @@ with col2:
|
|||
"recipient_street": r_street, "recipient_zip": r_zip, "recipient_city": r_city,
|
||||
"items": edited_items, "total_net_amount": computed_net_amount, "total_vat_amount": computed_vat_amount,
|
||||
"vat_rate_percent": vat_rate, "total_gross_amount": computed_gross_amount, "iban": VERKAEUFER_IBAN,
|
||||
"payment_means_code": selected_payment_means_code,
|
||||
"delivery_note_id": dn_id,
|
||||
"order_id": o_id,
|
||||
"contract_id": c_id,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from invoice_logic import (
|
|||
generate_compliant_xrechnung,
|
||||
generate_compliant_zugferd_cii,
|
||||
VERKAEUFER_IBAN,
|
||||
PAYMENT_MEANS_CODES,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -94,3 +95,38 @@ def test_map_unece_unit_code_hours():
|
|||
|
||||
def test_parse_to_strict_xml_date_german_format():
|
||||
assert parse_to_strict_xml_date("15.01.2026") == "2026-01-15"
|
||||
|
||||
|
||||
def test_payment_means_codes_contains_expected_entries():
|
||||
assert PAYMENT_MEANS_CODES["58"] == "SEPA-Überweisung"
|
||||
assert set(["30", "31", "42", "48", "58"]).issubset(PAYMENT_MEANS_CODES.keys())
|
||||
|
||||
|
||||
def test_generate_compliant_xrechnung_uses_dynamic_payment_means_code():
|
||||
data = _single_rate_invoice()
|
||||
data["payment_means_code"] = "31"
|
||||
xml_str = generate_compliant_xrechnung(data)
|
||||
root = ET.fromstring(xml_str.encode("utf-8"))
|
||||
ns = {"cbc": "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
|
||||
"cac": "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"}
|
||||
code = root.find(".//cac:PaymentMeans/cbc:PaymentMeansCode", ns).text
|
||||
assert code == "31"
|
||||
|
||||
|
||||
def test_generate_compliant_xrechnung_defaults_payment_means_code_to_58():
|
||||
xml_str = generate_compliant_xrechnung(_single_rate_invoice())
|
||||
root = ET.fromstring(xml_str.encode("utf-8"))
|
||||
ns = {"cbc": "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
|
||||
"cac": "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"}
|
||||
code = root.find(".//cac:PaymentMeans/cbc:PaymentMeansCode", ns).text
|
||||
assert code == "58"
|
||||
|
||||
|
||||
def test_generate_compliant_zugferd_cii_uses_dynamic_payment_means_code():
|
||||
data = _single_rate_invoice()
|
||||
data["payment_means_code"] = "48"
|
||||
xml_str = generate_compliant_zugferd_cii(data)
|
||||
root = ET.fromstring(xml_str.encode("utf-8"))
|
||||
ns = {"ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"}
|
||||
code = root.find(".//ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode", ns).text
|
||||
assert code == "48"
|
||||
|
|
|
|||
Loading…
Reference in New Issue