Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert
Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.9 KiB
Steuerberater-Abgleich 08+09/2025 — Analyse & Umsetzung der Fixes
Datum: 17.07.2026
Anlass: Korrektur-CSVs des Steuerberaters (Abgleich 08._09.2025-Konto-*.csv in diesem Ordner) zum DATEV-Export aus admin/payments/taxadvisor.
Betroffene Exporte: datev_exports #9 (08/2025) und #10 (09/2025), generiert am 08.05.2026.
Lesehilfe zu den Abgleich-CSVs
Die Spalte „Betrag Test" des Steuerberaters ist nicht unser Exportbetrag, sondern der Saldo auf dem Erlöskonto nach DATEV-Steuerautomatik: Wir exportieren Bruttobeträge mit BU-Schlüssel (9 = 19 %, 8 = 7 %), DATEV rechnet die USt automatisch heraus.
Beispiel: Export 277,92 € brutto (BU 9) → 233,55 € netto auf 8400 = „Betrag Test".
Mit diesem Schlüssel lassen sich fast alle gemeldeten Zeilen exakt nachvollziehen.
Befund 1: Beträge ≥ 1.000 € massiv zu niedrig (kritisch)
Gemeldete Fälle: 202536817 (1.434,71 statt 230,28), 202536968 (1.044,69 statt 167,67), 202537532 (1.075,04 statt 172,55)
Ursache: ShoppingOrder::makeTaxSplit() speicherte net_split/tax_split mit
number_format($value, 2) — also mit Tausendertrennzeichen ("1,434.70").
DatevExportService::parseNumber() machte daraus per str_replace(',', '.') den String
"1.434.70" → floatval() = 1,43 €. Jede Rechnung mit ≥ 1.000 € Netto pro Steuersatz
war im Export um den Faktor ~1000 zu niedrig.
Fix:
app/Services/DatevExportService.php→parseNumber()erkennt jetzt US-Format (1,434.70), deutsches Format (1.434,70) und reine Tausendertrenner (1,434) korrekt. Nötig, weil die Altdaten in der DB das fehlerhafte Format behalten.app/Models/ShoppingOrder.php→makeTaxSplit()undmakeHomepartyTaxSplit()schreiben neu mitnumber_format($value, 2, '.', '')(ohne Tausendertrenner).
Befund 2: Falsche Kontenzuordnung 8125 (igL) ↔ 8400/8300
Gemeldete Fälle (Auswahl): 202536848, 202536858, 202536910, 202537173, 202537290, 202538011, 202538144, 202538445/446, 202539399 (gehörten auf 8125, standen auf 8400) — umgekehrt 202536841, 202537251, 202537460, 202537543, 202538398 (standen auf 8125, gehörten auf 8400/8300).
Ursachen (mehrere):
- Export nutzte den aktuellen Account-Zustand statt des Rechnungszustands.
Die betroffenen Berater (z. B. User 781, 850, 974) hatten ihre USt-ID nach dem Kauf
wieder gelöscht (
reverse_charge_deleteleert alle Felder). Die Rechnungen wurden damals korrekt ohne USt (igL) ausgestellt, der Export fand aber keine USt-ID mehr und buchte auf 8400. - Keinerlei Validierung der USt-ID:
hasValidUstidprüfte nur „nicht leer". Dadurch lösten auch deutsche USt-IDs (202536841:DE296797691), offensichtliche Dummy-IDs (202538398:DE123456789) und formal ungültige Werte (202537460:53618009— gar kein USt-ID-Format) eine igL-Buchung auf 8125 aus. - Abgelaufene/ungültige IDs: Die VIES-Prüfung lief nur einmalig bei der Eingabe
(202537251:
ESY9284953M, validiert 08/2023, laut StB inzwischen ungültig).
Datenlage als Beleg: In 08+09/2025 hat jede DE-Bestellung ein tax_split;
EU-Bestellungen ohne tax_split sind exakt die steuerfrei verkauften (AT: 71, ES: 14).
Fix:
app/Services/DatevExportService.php→determineRevenueAccount(): Maßgeblich ist jetzt, wie die Rechnung tatsächlich ausgestellt wurde:- EU-Bestellung ohne
tax_split(= ohne USt verkauft) → 8125 / BU 1 (igL) — unabhängig vom heutigen Account-Zustand. - EU-Bestellung mit
tax_split(= mit USt berechnet) → 8400/8300 nach Steuersatz, nie 8125. - Drittland unverändert 8120 / BU 11.
- EU-Bestellung ohne
resolveEuUstid()verwirft deutsche und formal unplausible USt-IDs (neuer zentralerApp\Services\VatIdService). Fehlt bei einer igL-Zeile die USt-ID, erscheint wie bisher eine Validierungs-Warnung im Export (validation_summary).
Befund 3: „Betrag fehlerhaft" durch doppelte Rechnungsnummern
Gemeldete Fälle: 202537208, 202537572, 202538182, 202538332 — exakt die vier Nummern, die in 08+09/2025 doppelt vergeben wurden (je zwei verschiedene Rechnungen/Bestellungen). Der StB sah unter einer Belegnummer die Summe zweier Rechnungen.
Ursache: Historisches Race bei der Nummernvergabe (Zähler-Setting invoice-number).
Für Rechnungen ist das seit Invoice::makeNextInvoiceNumber() behoben
(DB::transaction + lockForUpdate). Gutschriften nutzten aber denselben Zähler
weiterhin ungesichert: CreditRepository::create() las die Nummer (plain read) und
zählte erst viel später in finishUserCredit() hoch — dazwischen konnten parallele Läufe
dieselbe Nummer ziehen. Folge in den Bestandsdaten: 38 Gutschriften teilen sich ihre
laufende Nummer mit einer Rechnung.
Fix:
app/Services/Credit.php→makeNextCreditNumber()delegiert anInvoice::makeNextInvoiceNumber()(atomar, gelockt).app/Repositories/CreditRepository.php→ Nummer wird direkt bei der Erstellung atomar reserviert; das nachträgliche Hochzählen infinishUserCredit()entfällt.
Bestehende Duplikate in den Altdaten bleiben unverändert (nicht rückwirkend korrigierbar).
Befund 4: Gutschriften auf 4764 mit falschem BU-Schlüssel
Gemeldete Fälle: GS202536995 (67,47 statt 56,70), GS202538597 (14,88 statt 12,50)
Ursache: Beide Gutschriften wurden mit taxable = 3 (nicht USt-pflichtig / Ausland)
ausgestellt — der Beleg weist keine USt aus. Der Export leitete den BU-Schlüssel aber
vom aktuellen Account-Status ab und exportierte BU 9 (19 %) → DATEV rechnete fälschlich
19 % aus dem steuerfreien Betrag heraus.
Fix:
app/Services/DatevExportService.php→determineCommissionTaxStatus()nutzt jetzt dentaxable-Snapshot der Gutschrift (1 = BU 9, 2 = Kleinunternehmer BU 50, 3 = Ausland → Reverse Charge BU 94 bei EU). Nur Alt-Gutschriften ohne Snapshot (taxable = 0) fallen auf die Account-Logik zurück. Der Brutto-Aufschlag nutzt den in der Gutschrift gespeichertentax_rate.
Befund 5: USt-ID-Prüfung nur einmalig (Anforderung Kevin)
Anforderung: USt-ID bei jeder Verwendung prüfen (v. a. im Checkout); bei ungültiger ID Hinweis anzeigen und mit USt berechnen; deutsche USt-IDs nie anerkennen.
Umsetzung — neuer zentraler Service app/Services/VatIdService.php:
normalize(),isPlausibleFormat()(EU-Länderpräfix + Nummernteil),qualifiesForReverseCharge()(Format ok und nichtDE...),checkVies()(SOAP gegenec.europa.euVIES, Timeout konfigurierbar),checkViesCached()(24 h Cache; VIES-Ausfall nur 10 Min. gecacht).- Eingabe im Account (
UserRepository::reverse_charge_activate): nutzt den Service; deutsche USt-IDs → eigene Fehlermeldung (msg.VATID_german_not_allowed, in de/en/es/fr ergänzt). SSL-Verifikation ist jetzt aktiv (vorher deaktiviert). - Checkout (
UserService::performUserTaxShippingCountry): Wenn Reverse-Charge greifen würde, wird die USt-ID live (gecacht) geprüft:- ungültig / DE / Format falsch → Reverse-Charge-Status wird entzogen
(
reverse_charge = 0,reverse_charge_valid = null; die USt-ID selbst bleibt zur Neu-Validierung erhalten), Growl-Hinweismsg.VATID_no_longer_valid_taxes_applied, Bestellung läuft mit USt weiter. - VIES nicht erreichbar → gespeicherter Status gilt (Checkout wird nie blockiert), Log-Warnung.
- ungültig / DE / Format falsch → Reverse-Charge-Status wird entzogen
(
- Konfiguration:
services.vies.enabled(VIES_ENABLED, Defaulttrue) undservices.vies.timeout(VIES_TIMEOUT, Default 10 s). Inphpunit.xmldeaktiviert.
Nicht im Code fixbar / offene Punkte
| Punkt | Status |
|---|---|
202506145 / 202506147: existieren nicht in user_invoices — stammen aus einem fremden Nummernkreis des Kunden (dort werden teils eigene Rechnungen + Stornorechnungen erstellt) |
Im Abgleich ignorieren und als „extern" markieren (Vorgabe Kevin) |
Stornos 2025 (202536978, 202537144, 202537513 teilweise, 202539501): im System nicht als Storno erfasst — Stornorechnungen (cancellation = 1) existieren erst ab 01/2026 |
Manuelle Abstimmung mit dem StB / Datenpflege |
| 202535876: Juli-Rechnung, Gutschrift im September — monatsübergreifende Korrekturen kennt der Export konzeptionell nicht | Offen, bei Bedarf separates Konzept |
| 202539011: „Rechnung liegt uns nicht vor" — Daten sind im System vorhanden (66,22 € + 285,18 €) | PDF an StB nachliefern |
| Exporte 08+09/2025 neu generieren, sobald die Fixes deployed sind | To-do nach Merge |
Tests
tests/Unit/Services/DatevExportServiceTest.php— erweitert: Tausendertrenner-Parsing, igL-Semantik (soldTaxFree), Ablehnung deutscher/unplausibler USt-IDs,taxable-Snapshot-Logik der Gutschriften.tests/Unit/Services/VatIdServiceTest.php— neu: Normalisierung, Formatprüfung, DE-Ablehnung, VIES-Verhalten (deaktiviert/unplausibel).tests/Feature/CheckoutVatIdRevalidationTest.php— neu: 6 Checkout-Szenarien (VIES ok / ungültig / ausgefallen, DE-ID, Format-Müll, Ländercode-Mismatch).
Alle 63 betroffenen Tests grün; ./vendor/bin/pint sauber. Vorbestehende, unabhängige
Testfehler (InvoiceServiceTest, tests/Unit/BusinessPlan/*, makeUser()-Kollision der
Gesamtsuite) bestehen unverändert auch auf dem Stand vor diesen Änderungen.
Geänderte Dateien
app/Services/DatevExportService.php— parseNumber, Kontenlogik, USt-ID-Filter, Gutschrift-BUapp/Services/VatIdService.php— neu, zentrale USt-ID-Prüfungapp/Services/UserService.php— Live-Prüfung im Checkout + Entzug + Hinweisapp/Services/Credit.php,app/Repositories/CreditRepository.php— atomare Nummernvergabeapp/Repositories/UserRepository.php— VIES via Service, DE-Ablehnung bei Eingabeapp/Models/ShoppingOrder.php— number_format ohne Tausendertrennerconfig/services.php,phpunit.xml— VIES-Konfigurationresources/lang/{de,en,es,fr}/msg.php— neue Meldungen