from decimal import Decimal import xml.etree.ElementTree as ET from invoice_logic import ( berechne_faelligkeit, kaufmaennisches_runden, clean_german_float, extract_invoice_items_pure_python, map_unece_unit_code, parse_to_strict_xml_date, generate_compliant_xrechnung, generate_compliant_zugferd_cii, VERKAEUFER_IBAN, PAYMENT_MEANS_CODES, ) def _single_rate_invoice(): """Eine einfache Einzelsteuersatz-Rechnung, wie main.py sie vor Phase 1 erzeugt hat.""" items = [ {"name": "Beratung", "description": "", "quantity": 2.0, "unit": "Std.", "unit_price": 100.0, "total_amount": 200.0}, ] return { "invoice_type_code": "380", "invoice_number": "R26-001", "invoice_date": "2026-01-15", "delivery_date": "", "buyer_reference": "", "customer_number": "", "recipient_name": "Test GmbH", "recipient_email": "kunde@example.com", "recipient_street": "Musterweg 1", "recipient_zip": "12345", "recipient_city": "Berlin", "items": items, "total_net_amount": 200.0, "total_vat_amount": 38.0, "vat_rate_percent": 19.0, "total_gross_amount": 238.0, "iban": VERKAEUFER_IBAN, "delivery_note_id": "", "order_id": "", "contract_id": "", "service_start_date": "", "service_end_date": "", "payment_term_days": "14", "invoice_note": "", } def test_generate_compliant_xrechnung_single_rate_unchanged(): 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"} assert root.find("cbc:ID", ns).text == "R26-001" tax_amount = root.find(".//cac:TaxTotal/cbc:TaxAmount", ns).text assert tax_amount == "38.00" payable = root.find(".//cac:LegalMonetaryTotal/cbc:PayableAmount", ns).text assert payable == "238.00" def test_generate_compliant_zugferd_cii_single_rate_unchanged(): xml_str = generate_compliant_zugferd_cii(_single_rate_invoice()) root = ET.fromstring(xml_str.encode("utf-8")) ns = {"ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"} grand_total = root.find(".//ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount", ns).text assert grand_total == "238.00" def test_berechne_faelligkeit_basic(): assert berechne_faelligkeit("2026-01-15", "14") == "2026-01-29" def test_kaufmaennisches_runden_half_up(): assert kaufmaennisches_runden(1.005) == 1.01 def test_clean_german_float_thousands_and_comma(): assert clean_german_float("1.234,56") == 1234.56 def test_extract_invoice_items_pure_python_basic(): text = "1\nBeratung\n2,00 Std.\n100,00\n200,00\n" items = extract_invoice_items_pure_python(text) assert items[0]["name"] == "Beratung" assert items[0]["quantity"] == 2.0 assert items[0]["unit_price"] == 100.0 def test_map_unece_unit_code_hours(): assert map_unece_unit_code("Std.") == "HUR" 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"