fix: extract tax number, price basis quantity, spaced VAT IDs and contact email

- supplier/buyer: extract FC tax registration (BT-32/BT-49) into new
  tax_number field; previously only VA (USt-IdNr.) was read
- pflichtfelder: critical error only when neither vat_id nor tax_number
  is present (EN 16931 BR-CO-26)
- ustid: tolerate formatting whitespace inside VAT IDs (e.g. 'DE 140 978 617')
- line items: normalize unit price by basis quantity (BT-149), so a
  price per 100 units no longer inflates line totals by factor 100
- supplier email: also look in DefinedTradeContact (BT-42)
- rewrite xrechnung fallback test to assert only our own behavior,
  independent of factur-x version specifics
This commit is contained in:
m3ta-chiron
2026-09-11 16:05:41 +02:00
parent e2e1d8c046
commit 7fcb3ff21d
7 changed files with 294 additions and 21 deletions
+18 -5
View File
@@ -19,13 +19,13 @@ def validate_pflichtfelder(xml_data: XmlData) -> list[ErrorDetail]:
"""Check required fields are present."""
errors = []
def add_error(field: str, severity: str) -> None:
def add_error(field: str, severity: str, message: str | None = None) -> None:
errors.append(
ErrorDetail(
check="pflichtfelder",
field=field,
error_code="missing_required",
message=f"Required field '{field}' is missing or empty",
message=message or f"Required field '{field}' is missing or empty",
severity=severity,
)
)
@@ -40,8 +40,20 @@ def validate_pflichtfelder(xml_data: XmlData) -> list[ErrorDetail]:
if not xml_data.supplier.name or not xml_data.supplier.name.strip():
add_error("supplier.name", "critical")
if not xml_data.supplier.vat_id or not xml_data.supplier.vat_id.strip():
add_error("supplier.vat_id", "critical")
# EN 16931 BR-CO-26: seller identified by VAT ID (BT-31) OR tax number (BT-32)
has_vat_id = bool(xml_data.supplier.vat_id and xml_data.supplier.vat_id.strip())
has_tax_number = bool(
xml_data.supplier.tax_number and xml_data.supplier.tax_number.strip()
)
if not has_vat_id and not has_tax_number:
add_error(
"supplier.vat_id",
"critical",
message=(
"Supplier must have a VAT ID (supplier.vat_id) "
"or a tax number (supplier.tax_number)"
),
)
if not xml_data.buyer.name or not xml_data.buyer.name.strip():
add_error("buyer.name", "critical")
@@ -163,7 +175,8 @@ def validate_ustid(vat_id: str) -> ErrorDetail | None:
severity="critical",
)
vat_id = vat_id.strip()
# Whitespace inside VAT IDs is formatting only (e.g. "DE 140 978 617")
vat_id = re.sub(r"\s+", "", vat_id)
# German VAT ID: DE followed by 9 digits
if vat_id.startswith("DE"):