Fix #xml-rechnung: USt-IdNr. des Kunden (BT-48) landet wieder im XML

Invoice::find() selektierte c.ust_id nicht, invoices hat keine eigene
Spalte dafür - der Zweig addDocumentBuyerVATRegistrationNumber() lief
deshalb nie. Bei Kategorie S optional, bei Reverse Charge und
innergemeinschaftlichen Lieferungen aber Pflicht.
This commit is contained in:
fds2610-bot 2026-07-31 18:35:20 +02:00
parent 117c9a5c88
commit d83d63cc14
3 changed files with 13 additions and 1 deletions

View file

@ -21,6 +21,7 @@ Der E-Rechnungsvalidator (KoSIT-Prüfkonfiguration) meldete für die heruntergel
- **PEPPOL-EN16931-R001/R010/R020 (Hinweise):** Geschäftsprozess (BT-23) sowie die elektronischen Adressen von Verkäufer (BT-34) und Käufer (BT-49, aus der Kunden-E-Mail) werden jetzt mit ausgegeben.
- **Die PDF-Einbettung bleibt unverändert** beim Profil EN16931 (Factur-X, `factur-x.xml`) – dem üblichen Format für hybride B2B-Rechnungen. Die neuen Kontakt- und Adressangaben stehen aber auch dort, das PDF-XML wird dadurch nur vollständiger.
- `collectMissingData()` prüft zusätzlich Ansprechpartner (BT-41), Telefon (BT-42), E-Mail (BT-43/BT-34) und die Kunden-E-Mail (BT-49); Test 72 deckt die neuen Elemente sowie eine XSD-Prüfung des erzeugten XML ab.
- **Fix: USt-IdNr. des Kunden (BT-48) fehlte immer im XML.** `Invoice::find()` selektierte vom Kunden zwar Adresse, E-Mail und Zahlungsziel, aber nicht `c.ust_id` – und `invoices` hat keine eigene Spalte dafür. Der Zweig `addDocumentBuyerVATRegistrationNumber()` in `ZugferdService` lief deshalb nie, auch wenn beim Kunden eine USt-IdNr. gepflegt war (Bestandsfehler, betraf auch das in die PDF eingebettete XML). Bei inländischen Leistungen mit Steuerkategorie „S" ist BT-48 optional, weshalb es nicht auffiel; bei Reverse Charge (§ 13b UStG) und innergemeinschaftlichen Lieferungen ist die Angabe dagegen Pflicht. `Invoice::find()` liefert `c.ust_id` jetzt mit, Test 72 prüft BT-31 und BT-48 samt Schemakennung „VA".
## 2026-07-31 – Firmenlogo persistent in `data/logo`, Nutzdaten gebündelt unter `data/`

View file

@ -54,7 +54,7 @@ class Invoice
{
return Database::fetchOne(
'SELECT i.*, i.storno_of_id, c.firma, c.nachname, c.vorname, c.anrede, c.kundennummer,
c.strasse, c.plz, c.ort, c.land, c.email, c.payment_days
c.strasse, c.plz, c.ort, c.land, c.email, c.payment_days, c.ust_id
FROM invoices i
JOIN customers c ON i.customer_id = c.id
WHERE i.id = ?',

View file

@ -25,6 +25,7 @@ class InvoiceXmlExportTest extends TestCase
'ort' => 'Normstadt',
'land' => 'DE',
'email' => 'buchhaltung@xmltest.example',
'ust_id' => 'DE987654321',
'payment_days' => 14,
]);
$invoiceId = \Steuerkram\Models\Invoice::create([
@ -123,6 +124,16 @@ class InvoiceXmlExportTest extends TestCase
'E-Mail des Ansprechpartners (BT-43) vorhanden – BR-DE-7'
);
// USt-IdNr. beider Parteien (BT-31 / BT-48) – BT-48 nur, wenn beim Kunden gepflegt
$sellerVat = $xp->query('//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]')->item(0);
$buyerVat = $xp->query('//ram:BuyerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]')->item(0);
$this->assertEquals('DE123456789', $sellerVat?->nodeValue, 'USt-IdNr. des Verkäufers als BT-31 mit Schema "VA"');
$this->assertEquals('DE987654321', $buyerVat?->nodeValue, 'USt-IdNr. des Kunden als BT-48 mit Schema "VA"');
$this->assertNotEmpty(
$invoice['ust_id'] ?? '',
'Invoice::find() liefert die USt-IdNr. des Kunden mit (Voraussetzung für BT-48)'
);
$sellerUri = $xp->query('//ram:SellerTradeParty/ram:URIUniversalCommunication/ram:URIID')->item(0);
$buyerUri = $xp->query('//ram:BuyerTradeParty/ram:URIUniversalCommunication/ram:URIID')->item(0);
$this->assertEquals('rechnung@muster.example', $sellerUri?->nodeValue, 'Elektronische Adresse des Verkäufers (BT-34)');