97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
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,
|
|
)
|
|
|
|
|
|
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"
|