chore: keep tests, test-config and planning docs local-only (untrack from repo)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e4f2315179
commit
36af456cd8
|
|
@ -5,3 +5,6 @@ __pycache__/
|
|||
.git
|
||||
.superpowers/
|
||||
docs/
|
||||
tests/
|
||||
pytest.ini
|
||||
requirements-dev.txt
|
||||
|
|
|
|||
|
|
@ -4,4 +4,10 @@ models
|
|||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Nur lokal - nicht zum Betrieb des Tools noetig (Tests, Test-Config, Planungsdoku)
|
||||
docs/
|
||||
tests/
|
||||
pytest.ini
|
||||
requirements-dev.txt
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,2 +0,0 @@
|
|||
[pytest]
|
||||
pythonpath = .
|
||||
|
|
@ -1 +0,0 @@
|
|||
pytest==8.3.4
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
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,
|
||||
aggregate_tax_subtotals,
|
||||
compute_invoice_totals,
|
||||
format_skonto_text,
|
||||
extract_service_period,
|
||||
detect_contract_type,
|
||||
extract_hardware_serial_numbers,
|
||||
)
|
||||
|
||||
|
||||
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_extract_invoice_items_pure_python_recognizes_person_days():
|
||||
text = "1\nProjektarbeit\n5,00 Personentage\n800,00\n4000,00\n"
|
||||
items = extract_invoice_items_pure_python(text)
|
||||
assert items[0]["quantity"] == 5.0
|
||||
assert items[0]["unit"] == "Personentage"
|
||||
|
||||
|
||||
def test_extract_invoice_items_pure_python_recognizes_license_months():
|
||||
text = "1\nCloud-Lizenz\n3,00 Monat(e)\n49,00\n147,00\n"
|
||||
items = extract_invoice_items_pure_python(text)
|
||||
assert items[0]["unit"] == "Monat(e)"
|
||||
|
||||
|
||||
def test_map_unece_unit_code_extended_units():
|
||||
assert map_unece_unit_code("Monat(e)") == "MON"
|
||||
assert map_unece_unit_code("Personentage") == "DAY"
|
||||
assert map_unece_unit_code("Lizenz(en)") == "C62"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
def test_aggregate_tax_subtotals_single_rate():
|
||||
items = [{"total_amount": 200.0, "vat_rate_percent": 19.0}]
|
||||
buckets = aggregate_tax_subtotals(items)
|
||||
assert buckets == [{"rate": 19.0, "category": "S", "taxable_amount": 200.0, "tax_amount": 38.0}]
|
||||
|
||||
|
||||
def test_aggregate_tax_subtotals_mixed_rates():
|
||||
items = [
|
||||
{"total_amount": 100.0, "vat_rate_percent": 19.0},
|
||||
{"total_amount": 50.0, "vat_rate_percent": 7.0},
|
||||
{"total_amount": 30.0, "vat_rate_percent": 0.0},
|
||||
]
|
||||
buckets = aggregate_tax_subtotals(items)
|
||||
assert buckets == [
|
||||
{"rate": 0.0, "category": "E", "taxable_amount": 30.0, "tax_amount": 0.0},
|
||||
{"rate": 7.0, "category": "S", "taxable_amount": 50.0, "tax_amount": 3.5},
|
||||
{"rate": 19.0, "category": "S", "taxable_amount": 100.0, "tax_amount": 19.0},
|
||||
]
|
||||
|
||||
|
||||
def test_compute_invoice_totals_mixed_rates_no_discount():
|
||||
items = [
|
||||
{"total_amount": 100.0, "vat_rate_percent": 19.0},
|
||||
{"total_amount": 50.0, "vat_rate_percent": 7.0},
|
||||
]
|
||||
totals = compute_invoice_totals(items)
|
||||
assert totals["line_net_total"] == 150.0
|
||||
assert totals["allowance_amount"] == 0.0
|
||||
assert totals["tax_basis_total"] == 150.0
|
||||
assert totals["total_vat_amount"] == 22.5
|
||||
assert totals["total_gross_amount"] == 172.5
|
||||
|
||||
|
||||
def test_compute_invoice_totals_single_rate_matches_old_behavior():
|
||||
items = [{"total_amount": 200.0, "vat_rate_percent": 19.0}]
|
||||
totals = compute_invoice_totals(items)
|
||||
assert totals["tax_basis_total"] == 200.0
|
||||
assert totals["total_vat_amount"] == 38.0
|
||||
assert totals["total_gross_amount"] == 238.0
|
||||
|
||||
|
||||
def _mixed_rate_invoice():
|
||||
data = _single_rate_invoice()
|
||||
items = [
|
||||
{"name": "Beratung", "description": "", "quantity": 2.0, "unit": "Std.",
|
||||
"unit_price": 100.0, "total_amount": 200.0, "vat_rate_percent": 19.0},
|
||||
{"name": "Buch", "description": "", "quantity": 1.0, "unit": "Stk.",
|
||||
"unit_price": 50.0, "total_amount": 50.0, "vat_rate_percent": 7.0},
|
||||
]
|
||||
totals = compute_invoice_totals(items)
|
||||
data["items"] = items
|
||||
data["tax_buckets"] = totals["tax_buckets"]
|
||||
data["line_net_total"] = totals["line_net_total"]
|
||||
data["allowance_amount"] = totals["allowance_amount"]
|
||||
data["total_net_amount"] = totals["tax_basis_total"]
|
||||
data["total_vat_amount"] = totals["total_vat_amount"]
|
||||
data["total_gross_amount"] = totals["total_gross_amount"]
|
||||
return data
|
||||
|
||||
|
||||
def test_generate_compliant_xrechnung_mixed_rates_two_tax_subtotals():
|
||||
xml_str = generate_compliant_xrechnung(_mixed_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"}
|
||||
subtotals = root.findall(".//cac:TaxTotal/cac:TaxSubtotal", ns)
|
||||
assert len(subtotals) == 2
|
||||
percents = sorted(st.find("cac:TaxCategory/cbc:Percent", ns).text for st in subtotals)
|
||||
assert percents == ["19.00", "7.00"] or percents == ["7.00", "19.00"]
|
||||
lines = root.findall(".//cac:InvoiceLine", ns)
|
||||
line_percents = [ln.find(".//cbc:Percent", ns).text for ln in lines]
|
||||
assert set(line_percents) == {"19.00", "7.00"}
|
||||
|
||||
|
||||
def test_generate_compliant_zugferd_cii_mixed_rates_two_header_taxes():
|
||||
xml_str = generate_compliant_zugferd_cii(_mixed_rate_invoice())
|
||||
root = ET.fromstring(xml_str.encode("utf-8"))
|
||||
ns = {"ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"}
|
||||
header_taxes = root.findall(
|
||||
".//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax", ns)
|
||||
assert len(header_taxes) == 2
|
||||
|
||||
|
||||
def test_format_skonto_text_with_valid_percent_and_days():
|
||||
text = format_skonto_text(2.0, 10, "2026-01-15")
|
||||
assert "2026-01-25" in text
|
||||
assert "2%" in text
|
||||
|
||||
|
||||
def test_format_skonto_text_returns_empty_when_no_percent():
|
||||
assert format_skonto_text(0, 10, "2026-01-15") == ""
|
||||
assert format_skonto_text(None, 10, "2026-01-15") == ""
|
||||
|
||||
|
||||
def test_format_skonto_text_returns_empty_when_no_days():
|
||||
assert format_skonto_text(2.0, "", "2026-01-15") == ""
|
||||
assert format_skonto_text(2.0, None, "2026-01-15") == ""
|
||||
|
||||
|
||||
def _discounted_invoice():
|
||||
data = _single_rate_invoice()
|
||||
items = [{"total_amount": 200.0, "vat_rate_percent": 19.0, "name": "Beratung",
|
||||
"description": "", "quantity": 2.0, "unit": "Std.", "unit_price": 100.0}]
|
||||
totals = compute_invoice_totals(items, discount_percent=10.0)
|
||||
data["items"] = items
|
||||
data["tax_buckets"] = totals["tax_buckets"]
|
||||
data["line_net_total"] = totals["line_net_total"]
|
||||
data["allowance_amount"] = totals["allowance_amount"]
|
||||
data["allowance_reason"] = "Treuerabatt"
|
||||
data["total_net_amount"] = totals["tax_basis_total"]
|
||||
data["total_vat_amount"] = totals["total_vat_amount"]
|
||||
data["total_gross_amount"] = totals["total_gross_amount"]
|
||||
data["skonto_percent"] = 2.0
|
||||
data["skonto_days"] = "10"
|
||||
return data
|
||||
|
||||
|
||||
def test_generate_compliant_xrechnung_includes_allowance_charge_and_payment_terms():
|
||||
xml_str = generate_compliant_xrechnung(_discounted_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"}
|
||||
allowance = root.find(".//cac:AllowanceCharge", ns)
|
||||
assert allowance.find("cbc:AllowanceChargeReason", ns).text == "Treuerabatt"
|
||||
assert allowance.find("cbc:Amount", ns).text == "20.00"
|
||||
payment_terms_note = root.find(".//cac:PaymentTerms/cbc:Note", ns).text
|
||||
assert "Skonto" in payment_terms_note
|
||||
|
||||
|
||||
def test_generate_compliant_zugferd_cii_includes_allowance_charge_and_payment_terms_description():
|
||||
xml_str = generate_compliant_zugferd_cii(_discounted_invoice())
|
||||
root = ET.fromstring(xml_str.encode("utf-8"))
|
||||
ns = {"ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"}
|
||||
allowance = root.find(".//ram:SpecifiedTradeAllowanceCharge", ns)
|
||||
assert allowance.find("ram:ActualAmount", ns).text == "20.00"
|
||||
assert allowance.find("ram:Reason", ns).text == "Treuerabatt"
|
||||
description = root.find(".//ram:SpecifiedTradePaymentTerms/ram:Description", ns).text
|
||||
assert "Skonto" in description
|
||||
|
||||
|
||||
def test_generate_compliant_xrechnung_mixed_rate_discount_emits_allowance_per_rate():
|
||||
"""Voll EN16931-konform: ein Rabatt auf eine Rechnung mit 19%- UND 7%-Positionen erzeugt
|
||||
JE Steuersatz einen eigenen AllowanceCharge-Block mit anteiligem Rabattbetrag."""
|
||||
items = [
|
||||
{"total_amount": 100.0, "vat_rate_percent": 19.0, "name": "A", "description": "",
|
||||
"quantity": 1.0, "unit": "Stk.", "unit_price": 100.0},
|
||||
{"total_amount": 100.0, "vat_rate_percent": 7.0, "name": "B", "description": "",
|
||||
"quantity": 1.0, "unit": "Stk.", "unit_price": 100.0},
|
||||
]
|
||||
totals = compute_invoice_totals(items, discount_percent=10.0)
|
||||
data = _single_rate_invoice()
|
||||
data["items"] = items
|
||||
data["tax_buckets"] = totals["tax_buckets"]
|
||||
data["line_net_total"] = totals["line_net_total"]
|
||||
data["allowance_amount"] = totals["allowance_amount"]
|
||||
data["allowance_reason"] = "Aktionsrabatt"
|
||||
data["total_net_amount"] = totals["tax_basis_total"]
|
||||
data["total_vat_amount"] = totals["total_vat_amount"]
|
||||
data["total_gross_amount"] = totals["total_gross_amount"]
|
||||
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"}
|
||||
allowances = root.findall(".//cac:AllowanceCharge", ns)
|
||||
assert len(allowances) == 2
|
||||
percents = sorted(a.find("cac:TaxCategory/cbc:Percent", ns).text for a in allowances)
|
||||
assert percents == ["19.00", "7.00"] or percents == ["7.00", "19.00"]
|
||||
total_alloc = sum(float(a.find("cbc:Amount", ns).text) for a in allowances)
|
||||
assert abs(total_alloc - 20.0) < 0.001
|
||||
|
||||
|
||||
def test_extract_service_period_finds_range():
|
||||
text = "Managed Services\nLeistungszeitraum: 01.03.2026 bis 31.03.2026\n1\n..."
|
||||
start, end = extract_service_period(text)
|
||||
assert start == "2026-03-01"
|
||||
assert end == "2026-03-31"
|
||||
|
||||
|
||||
def test_extract_service_period_returns_empty_when_absent():
|
||||
assert extract_service_period("Keine Zeitangabe hier.") == ("", "")
|
||||
|
||||
|
||||
def test_detect_contract_type_managed_services():
|
||||
assert detect_contract_type("Wartungsvertrag Managed Services Tier 2") == "Managed Services"
|
||||
|
||||
|
||||
def test_detect_contract_type_cloud_abo():
|
||||
assert detect_contract_type("Ihr Cloud-Abonnement für 2026") == "Cloud-Abonnement"
|
||||
|
||||
|
||||
def test_detect_contract_type_returns_empty_when_no_match():
|
||||
assert detect_contract_type("Ganz normale Rechnung ohne Vertragstyp") == ""
|
||||
|
||||
|
||||
def test_extract_hardware_serial_numbers_finds_sn_pattern():
|
||||
text = "Server Dell R750\nS/N: ABCD-1234-XYZ\nSwitch Cisco\nSeriennummer: SN9988771"
|
||||
serials = extract_hardware_serial_numbers(text)
|
||||
assert "ABCD-1234-XYZ" in serials
|
||||
assert "SN9988771" in serials
|
||||
|
||||
|
||||
def test_extract_hardware_serial_numbers_empty_when_absent():
|
||||
assert extract_hardware_serial_numbers("Keine Seriennummern hier.") == []
|
||||
|
||||
|
||||
def test_full_invoice_with_all_phase1_features_combined():
|
||||
"""Realistische IT-Systemhaus-Rechnung: gemischte Steuersaetze, Rabatt, Skonto,
|
||||
dynamischer Zahlungsmittelcode und IT-spezifische Abrechnungseinheiten - alles zusammen."""
|
||||
items = [
|
||||
{"name": "Managed-Services-Pauschale", "description": "", "quantity": 1.0, "unit": "Monat(e)",
|
||||
"unit_price": 500.0, "total_amount": 500.0, "vat_rate_percent": 19.0},
|
||||
{"name": "Fachliteratur", "description": "", "quantity": 1.0, "unit": "Stk.",
|
||||
"unit_price": 40.0, "total_amount": 40.0, "vat_rate_percent": 7.0},
|
||||
]
|
||||
totals = compute_invoice_totals(items, discount_percent=5.0)
|
||||
|
||||
data = _single_rate_invoice()
|
||||
data["items"] = items
|
||||
data["tax_buckets"] = totals["tax_buckets"]
|
||||
data["line_net_total"] = totals["line_net_total"]
|
||||
data["allowance_amount"] = totals["allowance_amount"]
|
||||
data["allowance_reason"] = "Neukundenrabatt"
|
||||
data["total_net_amount"] = totals["tax_basis_total"]
|
||||
data["total_vat_amount"] = totals["total_vat_amount"]
|
||||
data["total_gross_amount"] = totals["total_gross_amount"]
|
||||
data["skonto_percent"] = 2.0
|
||||
data["skonto_days"] = "14"
|
||||
data["payment_means_code"] = "31"
|
||||
|
||||
ubl_xml = generate_compliant_xrechnung(data)
|
||||
ubl_root = ET.fromstring(ubl_xml.encode("utf-8"))
|
||||
ubl_ns = {"cbc": "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
|
||||
"cac": "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"}
|
||||
assert len(ubl_root.findall(".//cac:TaxTotal/cac:TaxSubtotal", ubl_ns)) == 2
|
||||
assert ubl_root.find(".//cac:PaymentMeans/cbc:PaymentMeansCode", ubl_ns).text == "31"
|
||||
ubl_allowances = ubl_root.findall(".//cac:AllowanceCharge", ubl_ns)
|
||||
assert len(ubl_allowances) == 2 # ein Rabatt-Block je Steuersatz (19% und 7%)
|
||||
assert abs(sum(float(a.find("cbc:Amount", ubl_ns).text) for a in ubl_allowances) - 27.0) < 0.001
|
||||
assert "Skonto" in ubl_root.find(".//cac:PaymentTerms/cbc:Note", ubl_ns).text
|
||||
|
||||
cii_xml = generate_compliant_zugferd_cii(data)
|
||||
cii_root = ET.fromstring(cii_xml.encode("utf-8"))
|
||||
cii_ns = {"ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"}
|
||||
assert len(cii_root.findall(".//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax", cii_ns)) == 2
|
||||
assert cii_root.find(".//ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode", cii_ns).text == "31"
|
||||
cii_allowances = cii_root.findall(".//ram:SpecifiedTradeAllowanceCharge", cii_ns)
|
||||
assert len(cii_allowances) == 2 # ein Rabatt-Block je Steuersatz (19% und 7%)
|
||||
assert abs(sum(float(a.find("ram:ActualAmount", cii_ns).text) for a in cii_allowances) - 27.0) < 0.001
|
||||
Loading…
Reference in New Issue