DATEV: stornierte Rechnungen im Rechnungsmonat, Pruefliste fuer den Steuerberater

- Eine spaeter stornierte Rechnung fehlte im Export ihres Rechnungsmonats,
  weil cancellation = true auch an der Originalrechnung steht. Sie bleibt
  jetzt als Erloes im Rechnungsmonat; die Stornierung wird im Monat des
  Stornobelegs gegengebucht (nur Belege ohne cancellation_id).
- Pruefliste je Buchungsstapel als CSV (Button im Steuerberater-Bereich):
  Belege, die sich ohne Zusatzinformation nicht abstimmen lassen (doppelte
  Rechnungsnummern, Stornos ausserhalb des Systems).
- Korrespondenz und offene Differenzen mit dem Steuerberater unter
  dev/2026-07-17-steuern/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-23 15:42:54 +00:00
parent d01b9bca3a
commit 1a3af17f9a
12 changed files with 1058 additions and 7 deletions

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\DatevExport;
use App\Models\DatevExportLine;
use App\Services\DatevExportService;
use App\Services\DatevReconciliationService;
use App\Services\HTMLHelper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
@ -132,6 +133,26 @@ class PaymentTaxAdvisorController extends Controller
]);
}
/**
* Prüfliste zum Buchungsstapel als CSV herunterladen.
*
* Enthält die Belege, die der Steuerberater nicht ohne Zusatzinformation abstimmen
* kann (Altfälle wie doppelte Rechnungsnummern oder Stornos außerhalb des Systems).
*/
public function checklist($id)
{
$export = DatevExport::findOrFail($id);
$csv = app(DatevReconciliationService::class)->buildCsv($export->month, $export->year);
$filename = sprintf('DATEV_Pruefliste_%04d_%02d.csv', $export->year, $export->month);
return response($csv, 200, [
'Content-Type' => 'text/csv; charset=utf-8',
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
]);
}
/**
* DataTable: Export-Lines eines bestimmten Exports.
*/

View file

@ -244,6 +244,9 @@ class DatevExportService
/**
* Sammelt alle Rechnungszeilen für die Periode.
* Pro Steuersatz in tax_split wird eine eigene Zeile erzeugt.
*
* Später stornierte Rechnungen bleiben hier als Erlös enthalten: Die Stornierung
* wird im Monat des Stornobelegs gebucht, nicht rückwirkend im Rechnungsmonat.
*/
public function collectInvoiceLines(int $month, int $year): Collection
{
@ -258,11 +261,7 @@ class DatevExportService
])
->where('month', $month)
->where('year', $year)
->where(function ($q) {
$q->where('cancellation', false)
->orWhereNull('cancellation');
})
->whereNotIn('status', [11, 12]) // Nicht stornierte
->whereNotIn('status', [11, 12]) // Stornobelege selbst (siehe collectCancellationLines)
->get();
foreach ($invoices as $invoice) {
@ -481,8 +480,10 @@ class DatevExportService
*/
/**
* Sammelt alle Stornorechnungen für die Periode.
* Stornos werden als negative Beträge exportiert.
* Sammelt alle Stornobelege für die Periode und exportiert sie als Gegenbuchung (S).
*
* `cancellation = true` steht sowohl am Stornobeleg als auch an der stornierten
* Originalrechnung; nur der Stornobeleg hat kein `cancellation_id`.
*/
public function collectCancellationLines(int $month, int $year): Collection
{
@ -498,6 +499,7 @@ class DatevExportService
->where('month', $month)
->where('year', $year)
->where('cancellation', true)
->whereNull('cancellation_id') // nur der Stornobeleg, nicht die stornierte Originalrechnung
->get();
foreach ($cancellations as $invoice) {

View file

@ -0,0 +1,363 @@
<?php
namespace App\Services;
use App\Models\ShoppingPayment;
use App\Models\UserInvoice;
use Illuminate\Support\Collection;
/**
* Erzeugt eine Prüfliste zum DATEV-Buchungsstapel.
*
* Der Buchungsstapel selbst bleibt unverändert (EXTF-Format lässt keine Zusatzspalten
* zu). Die Prüfliste markiert stattdessen die Belege, die der Steuerberater nicht ohne
* Zusatzinformation abstimmen kann — Altfälle wie doppelt vergebene Rechnungsnummern
* oder außerhalb des Systems abgewickelte Stornos lassen sich nachträglich nicht mehr
* korrigieren, wohl aber vollständig benennen.
*/
class DatevReconciliationService
{
public const CATEGORY_DUPLICATE_NUMBER = 'doppelte_rechnungsnummer';
public const CATEGORY_EXTERNAL_CANCELLATION = 'storno_ausserhalb_system';
public const CATEGORY_CANCELLED_LATER = 'spaeter_storniert';
public const CATEGORY_CROSS_MONTH_CANCELLATION = 'storno_monatsuebergreifend';
public const CATEGORY_INTRA_COMMUNITY_WITHOUT_VATID = 'igl_ohne_ustid';
public const CATEGORY_REVERSE_CHARGE_WITHOUT_VATID = 'reverse_charge_ohne_ustid';
/**
* @var array<string, string>
*/
public const CATEGORY_LABELS = [
self::CATEGORY_DUPLICATE_NUMBER => 'Doppelt vergebene Rechnungsnummer',
self::CATEGORY_EXTERNAL_CANCELLATION => 'Storno/Rückzahlung außerhalb des Systems',
self::CATEGORY_CANCELLED_LATER => 'Später storniert (Storno in Folgemonat)',
self::CATEGORY_CROSS_MONTH_CANCELLATION => 'Storno zu Rechnung aus Vormonat',
self::CATEGORY_INTRA_COMMUNITY_WITHOUT_VATID => 'Steuerfreie igL ohne USt-ID',
self::CATEGORY_REVERSE_CHARGE_WITHOUT_VATID => 'Reverse Charge ohne USt-ID',
];
public function __construct(private DatevExportService $exportService) {}
/**
* Baut die vollständige Prüfliste für einen Zeitraum.
*
* @return Collection<int, array{kategorie: string, belegnummer: string, belegdatum: ?string, konto: ?int, bu_schluessel: ?int, betrag_brutto: float, name: string, hinweis: string, empfehlung: string}>
*/
public function build(int $month, int $year): Collection
{
return collect()
->concat($this->findDuplicateInvoiceNumbers($month, $year))
->concat($this->findExternalCancellations($month, $year))
->concat($this->findCancellationTimingHints($month, $year))
->concat($this->findMissingVatIds($month, $year))
->sortBy([['kategorie', 'asc'], ['belegnummer', 'asc']])
->values();
}
/**
* Zählt die Prüfhinweise je Kategorie.
*
* @return array<string, int>
*/
public function summarize(Collection $rows): array
{
return $rows->groupBy('kategorie')->map->count()->all();
}
/**
* Rechnungsnummern, die im Zeitraum für mehrere Rechnungen vergeben wurden.
*
* Nicht rückwirkend korrigierbar; die Prüfliste nennt daher zu jeder Nummer alle
* dahinterliegenden Einzelrechnungen mit Kunde und Betrag.
*
* @return Collection<int, array<string, mixed>>
*/
private function findDuplicateInvoiceNumbers(int $month, int $year): Collection
{
$invoices = UserInvoice::with(['shopping_order.shopping_user'])
->where('month', $month)
->where('year', $year)
->whereNotIn('status', [11, 12])
->get()
->filter(fn (UserInvoice $invoice) => ! empty($invoice->full_number));
$rows = collect();
foreach ($invoices->groupBy('full_number') as $number => $group) {
if ($group->count() < 2) {
continue;
}
$parts = $group->map(fn (UserInvoice $invoice) => [
'name' => $this->customerName($invoice),
'amount' => $this->invoiceGross($invoice),
]);
$total = round($parts->sum('amount'), 2);
foreach ($group as $invoice) {
$others = $parts
->reject(fn (array $part) => $part['name'] === $this->customerName($invoice)
&& abs($part['amount'] - $this->invoiceGross($invoice)) < 0.005)
->map(fn (array $part) => $part['name'].' ('.$this->money($part['amount']).')')
->implode(', ');
$rows->push([
'kategorie' => self::CATEGORY_DUPLICATE_NUMBER,
'belegnummer' => (string) $number,
'belegdatum' => $this->date($invoice->getRawOriginal('date')),
'konto' => null,
'bu_schluessel' => null,
'betrag_brutto' => $this->invoiceGross($invoice),
'name' => $this->customerName($invoice),
'hinweis' => 'Nummer im Zeitraum '.$group->count().'× vergeben, Summe unter dieser Nummer '
.$this->money($total).'. Weitere Rechnung(en): '.($others !== '' ? $others : '—'),
'empfehlung' => 'Position anhand des Kundennamens der jeweiligen Einzelrechnung zuordnen.',
]);
}
}
return $rows;
}
/**
* Rechnungen, die über den Zahlungsanbieter (teil-)erstattet wurden, ohne dass im
* System ein Stornobeleg existiert. Der Buchungsstapel enthält solche Rechnungen
* weiterhin mit dem vollen Betrag.
*
* @return Collection<int, array<string, mixed>>
*/
private function findExternalCancellations(int $month, int $year): Collection
{
$invoices = UserInvoice::with(['shopping_order.shopping_user'])
->where('month', $month)
->where('year', $year)
->whereNotIn('status', [11, 12])
->whereNull('cancellation_id')
->get();
if ($invoices->isEmpty()) {
return collect();
}
$refunds = ShoppingPayment::query()
->whereIn('shopping_order_id', $invoices->pluck('shopping_order_id')->filter()->unique())
->where('txaction', 'refund')
->get()
->keyBy('shopping_order_id');
return $invoices
->filter(fn (UserInvoice $invoice) => $refunds->has($invoice->shopping_order_id))
->map(function (UserInvoice $invoice) use ($refunds) {
$refund = $refunds->get($invoice->shopping_order_id);
return [
'kategorie' => self::CATEGORY_EXTERNAL_CANCELLATION,
'belegnummer' => (string) $invoice->full_number,
'belegdatum' => $this->date($invoice->getRawOriginal('date')),
'konto' => null,
'bu_schluessel' => null,
'betrag_brutto' => $this->invoiceGross($invoice),
'name' => $this->customerName($invoice),
'hinweis' => 'Rückzahlung über den Zahlungsanbieter am '
.$this->date($refund->updated_at).', aber kein Stornobeleg im System. '
.'Der Buchungsstapel enthält den vollen Rechnungsbetrag.',
'empfehlung' => 'Erstatteten Betrag manuell gegenbuchen; Betrag laut Zahlungsanbieter prüfen.',
];
})
->values();
}
/**
* Weist auf Stornos hin, deren Original in einem anderen Monat liegt — in beide
* Richtungen, damit der Steuerberater die Gegenbuchung im richtigen Zeitraum sucht.
*
* @return Collection<int, array<string, mixed>>
*/
private function findCancellationTimingHints(int $month, int $year): Collection
{
$rows = collect();
$cancelledOriginals = UserInvoice::with(['shopping_order.shopping_user'])
->where('month', $month)
->where('year', $year)
->where('cancellation', true)
->whereNotNull('cancellation_id')
->get();
foreach ($cancelledOriginals as $invoice) {
$storno = UserInvoice::find($invoice->cancellation_id);
if (! $storno || ($storno->month === $month && $storno->year === $year)) {
continue;
}
$rows->push([
'kategorie' => self::CATEGORY_CANCELLED_LATER,
'belegnummer' => (string) $invoice->full_number,
'belegdatum' => $this->date($invoice->getRawOriginal('date')),
'konto' => null,
'bu_schluessel' => null,
'betrag_brutto' => $this->invoiceGross($invoice),
'name' => $this->customerName($invoice),
'hinweis' => 'Rechnung wurde am '.$this->date($storno->getRawOriginal('date'))
.' storniert (Stornobeleg '.$storno->full_number.'). Der Erlös steht bewusst in diesem '
.'Zeitraum, die Gegenbuchung folgt im Stornomonat.',
'empfehlung' => 'Keine Korrektur in diesem Monat — Gegenbuchung im Stornomonat abwarten.',
]);
}
$stornos = UserInvoice::with(['shopping_order.shopping_user'])
->where('month', $month)
->where('year', $year)
->where('cancellation', true)
->whereNull('cancellation_id')
->get();
foreach ($stornos as $storno) {
$original = UserInvoice::where('cancellation_id', $storno->id)->first();
if (! $original || ($original->month === $month && $original->year === $year)) {
continue;
}
$rows->push([
'kategorie' => self::CATEGORY_CROSS_MONTH_CANCELLATION,
'belegnummer' => (string) $storno->full_number,
'belegdatum' => $this->date($storno->getRawOriginal('date')),
'konto' => null,
'bu_schluessel' => null,
'betrag_brutto' => $this->invoiceGross($storno),
'name' => $this->customerName($storno),
'hinweis' => 'Stornobeleg zur Rechnung '.$original->full_number.' vom '
.$this->date($original->getRawOriginal('date')).' (Zeitraum '
.sprintf('%02d/%d', $original->month, $original->year).').',
'empfehlung' => 'Gegenbuchung zum genannten Beleg aus dem Vormonat.',
]);
}
return $rows;
}
/**
* Belege, deren Steuerschlüssel eine USt-ID des Geschäftspartners voraussetzt,
* für die im System aber keine hinterlegt ist.
*
* @return Collection<int, array<string, mixed>>
*/
private function findMissingVatIds(int $month, int $year): Collection
{
$lines = $this->exportService->collectInvoiceLines($month, $year)
->concat($this->exportService->collectCreditLines($month, $year))
->concat($this->exportService->collectCancellationLines($month, $year));
return $lines
->filter(fn (array $line) => in_array($line['bu_schluessel'] ?? 0, [1, 94], true)
&& empty($line['eu_ustid']))
->map(function (array $line) {
$isReverseCharge = ($line['bu_schluessel'] ?? 0) === 94;
return [
'kategorie' => $isReverseCharge
? self::CATEGORY_REVERSE_CHARGE_WITHOUT_VATID
: self::CATEGORY_INTRA_COMMUNITY_WITHOUT_VATID,
'belegnummer' => (string) ($line['belegfeld1'] ?? ''),
'belegdatum' => $this->date($line['belegdatum'] ?? null),
'konto' => $line['konto'] ?? null,
'bu_schluessel' => $line['bu_schluessel'] ?? null,
'betrag_brutto' => round((float) ($line['amount_gross'] ?? 0), 2),
'name' => (string) ($line['buchungstext'] ?? '-'),
'hinweis' => $isReverseCharge
? 'Provisionsgutschrift ins EU-Ausland mit Reverse Charge (BU 94), '
.'zum Berater ist jedoch keine USt-ID hinterlegt.'
: 'Steuerfreie innergemeinschaftliche Lieferung (BU 1) ohne hinterlegte USt-ID '
.'des Empfängers.',
'empfehlung' => 'Steuerschlüssel prüfen: ohne USt-ID des Empfängers ist der '
.'Nachweis für die Steuerbefreiung bzw. das Reverse-Charge-Verfahren unvollständig.',
];
})
->unique(fn (array $row) => $row['kategorie'].'|'.$row['belegnummer'].'|'.$row['konto'])
->values();
}
/**
* Baut die Prüfliste als CSV im gleichen Format wie der Buchungsstapel.
*/
public function buildCsv(int $month, int $year): string
{
$delimiter = config('datev.delimiter', ';');
$lineEnding = config('datev.line_ending', "\r\n");
$header = [
'Kategorie', 'Belegnummer', 'Belegdatum', 'Konto', 'BU-Schlüssel',
'Betrag brutto', 'Name', 'Hinweis', 'Empfehlung',
];
$csv = "\xEF\xBB\xBF".$this->csvRow($header, $delimiter).$lineEnding;
foreach ($this->build($month, $year) as $row) {
$csv .= $this->csvRow([
self::CATEGORY_LABELS[$row['kategorie']] ?? $row['kategorie'],
$row['belegnummer'],
$row['belegdatum'] ?? '',
$row['konto'] !== null ? (string) $row['konto'] : '',
$row['bu_schluessel'] !== null ? (string) $row['bu_schluessel'] : '',
number_format($row['betrag_brutto'], 2, ',', ''),
$row['name'],
$row['hinweis'],
$row['empfehlung'],
], $delimiter).$lineEnding;
}
return $csv;
}
/**
* @param array<int, string> $values
*/
private function csvRow(array $values, string $delimiter): string
{
return collect($values)
->map(fn (string $value) => '"'.str_replace('"', '""', str_replace(["\r", "\n"], ' ', $value)).'"')
->implode($delimiter);
}
private function invoiceGross(UserInvoice $invoice): float
{
$order = $invoice->shopping_order;
return round((float) ($order->total_shipping ?? $order->total ?? 0), 2);
}
private function customerName(UserInvoice $invoice): string
{
$user = $invoice->shopping_order?->shopping_user;
if (! $user) {
return '-';
}
$name = trim(($user->billing_lastname ?? '').' '.($user->billing_firstname ?? ''));
return $name !== '' ? mb_substr($name, 0, 60) : '-';
}
private function money(float $amount): string
{
return number_format($amount, 2, ',', '.').' €';
}
private function date($value): ?string
{
if (empty($value)) {
return null;
}
return \Carbon::parse($value)->format('d.m.Y');
}
}

View file

@ -0,0 +1,18 @@
Nachfolgend erhalten Sie eine kurze Zusammenfassung:
Differenzen sind noch offen aufgrund von:
• 3 – Doppelt vergebene Rechnungsnummern
• 5 – Stornos 2025 (außerhalb des Systems)
• 6 – Belege außerhalb des Shop-Nummernkreises
• 202537251- ungültige spanische USt-IdNr.
• Gutschrift 09/2025 zur Juli-Rechnung 202535876 nicht berücksichtigt
• 202539011 Rechnung bitte nachreichen
Anbei erhalten Sie eine Excel- Übersicht der offenen Differenzen (aus neuem Abgleich) mit Ihren Kommentaren zu diesen.
Bitte um Klärung:
• Wie werden ungültige USt-ID-Nr. von Ihrem System erfasst, sodass diese richtig verbucht werden?
• Wie wird mit den Fehlern des separaten Rechnungskreislauf (Ursache 6) umgegangen, diese sind zur vollständigen Buchhaltung notwendig. Wie werden diese Belege aus dem externen Rechnungskreislauf zur (internen) Buchhaltung berücksichtigt?
Für das weitere Vorgehen bitten wir Sie, uns einen Datensatz zur Verfügung zu stellen, bei welchem die Fehler hinsichtlich der doppelten Rechnungsnummernvergabe, Stornos außerhalb des Systems nicht mehr auftreten und die zwischenzeitlich vorgenommenen Systemanpassungen berücksichtigt sind. Ziel sollte sein, einen fehlerlosen Abgleich zu erhalten.
Welche aktuellen Monate eignen sich hierfür als Vergleichszeitraum?

View file

@ -0,0 +1,169 @@
# Mail an den Steuerberater — 02.09.2026
**Betreff:** Offene Differenzen 08+09/2025, Prüfliste und Vorschlag für einen Vergleichszeitraum
---
Sehr geehrte Damen und Herren,
vielen Dank für die Übersicht der offenen Differenzen und Ihre Rückfragen. Ich habe die
Punkte ausgewertet und schlage vor, den Abgleich in zwei Teile zu trennen: die Altfälle
aus 08+09/2025 abschließend zu dokumentieren und parallel einen aktuellen Monat
vollständig abzustimmen.
## 1. Vergleichszeitraum: August 2026
Als Vergleichszeitraum eignet sich **August 2026**. Den Buchungsstapel dazu stelle ich
Ihnen zusammen mit diesem Schreiben zur Verfügung.
August 2026 ist der erste Monat, der **vollständig nach der Systemumstellung vom
24.07.2026** entstanden ist. Alle Belege dieses Zeitraums wurden durchgehend über die
korrigierten Abläufe erzeugt. Juli 2026 ist demgegenüber ein gemischter Zeitraum: Belege
aus der ersten Julihälfte entstanden noch nach altem Stand, Belege danach nach neuem.
September 2026 ist noch nicht abgeschlossen.
| Prüfpunkt | Stand 08/2026 |
| --- | --- |
| Doppelt vergebene Rechnungsnummern | keine |
| Beträge über 1.000 € | korrekt |
| Rückzahlungen ohne Stornobeleg im System | keine |
| Stornierungen | 3, alle systemseitig als Stornobeleg erfasst |
| Steuerfreie innergemeinschaftliche Lieferungen ohne USt-IdNr. | keine (28 Lieferungen, alle mit geprüfter USt-IdNr.) |
| Technische Prüfung des Buchungsstapels | 0 Fehler |
## 2. Chronologie: ab wann welche Ursache nicht mehr auftritt
Damit nachvollziehbar ist, welche Zeiträume von welchen Ursachen noch betroffen sind,
habe ich jede Fehlerursache im Datenbestand zurückverfolgt. Zu unterscheiden sind zwei
Arten:
**a) Ursachen im Datenbestand.** Diese wirken bis zu einem Stichtag und lassen sich für
zurückliegende Zeiträume nicht mehr korrigieren.
| Ursache | Letzter Fall im Datenbestand | Betroffene Zeiträume |
| --- | --- | --- |
| Doppelt vergebene Rechnungsnummer | 12.01.2026 | bis einschließlich 01/2026 |
| Beträge über 1.000 € fehlerhaft gespeichert | 05.07.2026 | bis einschließlich 07/2026 |
| Rückzahlung ohne Stornobeleg im System | 27.01.2026 | bis einschließlich 01/2026 |
| USt-IdNr. ohne laufende Prüfung übernommen | 24.07.2026 | bis einschließlich 07/2026 |
Ab **08/2026** tritt keine dieser Ursachen mehr auf.
**b) Ursachen in der Aufbereitung des Buchungsstapels.** Diese lagen nicht im
Datenbestand, sondern in der Erzeugung der Datei. Sie sind behoben und wirken bei jeder
Neuerzeugung **rückwirkend für alle Zeiträume** — auch für 2025.
| Ursache | Status |
| --- | --- |
| Beträge über 1.000 € falsch ausgelesen | behoben, wirkt rückwirkend |
| Kontenzuordnung 8125 ↔ 8400/8300 | behoben, folgt jetzt dem ausgestellten Beleg |
| Steuerschlüssel der Gutschriften | behoben, entspricht dem Beleg |
| Verarbeitung von Stornobelegen | behoben, Erlös im Rechnungsmonat, Gegenbuchung im Stornomonat |
Die Ihnen am 24.07.2026 übermittelten Buchungsstapel für **08/2025 und 09/2025 sind
unverändert auf dem aktuellen Stand**; eine erneute Lieferung ist nicht erforderlich.
**c) Systemseitige Stornierungen.** Stornorechnungen werden seit dem **19.02.2026** als
eigener Beleg erzeugt und erscheinen seither automatisch im Buchungsstapel. Rückzahlungen
davor wurden außerhalb des Systems abgewickelt und sind dort nicht als Storno erfasst.
Der Verlauf lässt sich an den steuerfreien innergemeinschaftlichen Lieferungen ohne
hinterlegte USt-IdNr. ablesen: 08/2025 waren es 10 von 42, 01/2026 noch 4 von 46, 08/2026
keine mehr von 28.
## 3. Neu: Prüfliste zu jedem Buchungsstapel
Damit Sie die nicht automatisch abstimmbaren Positionen nicht einzeln recherchieren
müssen, liefere ich ab sofort zu jedem Buchungsstapel eine **Prüfliste** als zweite
Datei. Sie enthält je Beleg: Kategorie, Belegnummer, Belegdatum, Konto, Steuerschlüssel,
Betrag, Name und einen konkreten Hinweis.
Erfasst werden:
- **Doppelt vergebene Rechnungsnummern** — mit Nennung aller Einzelrechnungen hinter der
Nummer, jeweils mit Kundenname und Betrag
- **Rückzahlungen ohne Stornobeleg** — mit Datum der Rückzahlung
- **Stornierungen über Monatsgrenzen** — in beide Richtungen, damit die Gegenbuchung im
richtigen Zeitraum zugeordnet werden kann
- **Steuerfreie innergemeinschaftliche Lieferungen ohne hinterlegte USt-IdNr.**
- **Provisionsgutschriften mit Reverse Charge ohne hinterlegte USt-IdNr.**
Ein Beispiel aus 08/2025:
> 202537208 | Hoppe Torben | 24,80 € | Nummer im Zeitraum 2× vergeben, Summe unter dieser
> Nummer 169,70 €. Weitere Rechnung(en): Kuhn Laura (144,90 €)
Für **08/2025 umfasst die Prüfliste 47 Positionen, für 09/2025 44 Positionen**. Beide
Listen lege ich bei, damit die verbleibende manuelle Abstimmung dieser beiden Monate auf
einer vollständigen Aufstellung aufsetzen kann.
Ein Hinweis zur Vollständigkeit: Rückzahlungen erkennt die Liste anhand der Daten des
Zahlungsdienstleisters. Bei **202539501** ist im System keinerlei Rückzahlung vermerkt —
dieser Fall wurde vollständig außerhalb abgewickelt und lässt sich nicht automatisiert
ausweisen.
## 4. Ihre Frage: Erfassung ungültiger USt-IdNr.
Die USt-IdNr. wird seit dem 24.07.2026 **bei jeder Bestellung erneut live über die
EU-Datenbank (VIES) geprüft**, nicht mehr nur einmalig bei der Eingabe. Ist sie ungültig,
abgelaufen oder deutsch, wird der Reverse-Charge-Status entzogen, die Bestellung **mit
Umsatzsteuer** berechnet und der Kunde darauf hingewiesen. Deutsche USt-IdNr. werden
systemweit nicht mehr als Grundlage für steuerfreie Lieferungen akzeptiert.
Für die Kontenzuordnung ist maßgeblich, **wie die Rechnung tatsächlich ausgestellt
wurde**: ohne Umsatzsteuer verkaufte EU-Lieferungen auf 8125 (BU 1), mit Umsatzsteuer
berechnete Lieferungen auf 8400/8300. Rechnung und Buchung können dadurch nicht mehr
auseinanderlaufen.
Für Rechnungen, die vor der Umstellung bereits steuerfrei ausgestellt wurden, gilt dies
naturgemäß nicht rückwirkend — siehe 202537251 unter Punkt 7.
## 5. Ihre Frage: Belege aus dem separaten Rechnungskreislauf
Diese Belege werden von MIVITA außerhalb des Shopsystems erstellt. Ich habe darauf weder
Zugriff noch Einblick; sie können im Buchungsstapel des Shops daher systembedingt nicht
enthalten sein. Wie diese Belege in die Buchhaltung überführt werden, stimmen Sie bitte
direkt mit MIVITA ab. Im Abgleich mit dem Shop-Export bitte ich, sie weiterhin als
„extern" zu kennzeichnen.
## 6. Provisionsgutschriften an Berater im Ausland
Zur Einordnung, wie das System aktuell verfährt: Provisionsgutschriften an Berater mit
Sitz außerhalb Deutschlands werden ohne Umsatzsteuer ausgestellt. Im Buchungsstapel
werden sie bei Ansässigkeit in der EU mit dem Steuerschlüssel für Reverse Charge (BU 94)
übergeben.
Bei einem Teil dieser Berater ist im System keine USt-IdNr. hinterlegt. Im August 2026
betrifft das 32 von 44 EU-Gutschriften über zusammen 2.378,18 €; die übrigen 12
(17.838,06 €) verfügen über eine hinterlegte und geprüfte USt-IdNr. Alle betroffenen
Belege sind in der Prüfliste einzeln ausgewiesen.
Sollte für Gutschriften ohne hinterlegte USt-IdNr. ein anderer Steuerschlüssel maßgeblich
sein, setze ich das im Export um und fordere die fehlenden USt-IdNr. bei den betroffenen
Beratern an. Ich bitte hierzu um Ihre Einschätzung.
## 7. Verbleibende Einzelfälle aus 08+09/2025
- **202537251** (spanische USt-IdNr.): Die Rechnung wurde 2025 steuerfrei ausgestellt.
Der Export bildet den Beleg ab und weist sie deshalb auf 8125 aus. Eine Zuordnung auf
8400 setzt eine Rechnungskorrektur gegenüber dem Kunden voraus. Bitte teilen Sie mir
mit, ob Sie eine solche Korrektur für erforderlich halten.
- **202535876** (Teilrückzahlung 16.09.2025 zur Juli-Rechnung): Die Rückzahlung ist im
System ohne Beleg erfolgt; Teilstornierungen kann das System bis heute nicht als eigenen
Beleg abbilden. Ich bitte um manuelle Berücksichtigung. Sollten solche Fälle häufiger
auftreten, richte ich eine Teilstornierung mit eigenem Beleg ein.
- **202539011**: Das Rechnungs-PDF ist beigefügt.
- **Doppelnummern, Stornos 2025 und externe Belege**: abschließend in den beiliegenden
Prüflisten dokumentiert.
## 8. Vorschlag zum weiteren Vorgehen
1. Abgleich von **08/2026** anhand des beiliegenden Buchungsstapels und der Prüfliste —
dieser sollte ohne Differenzen aufgehen.
2. Abschluss der Altfälle **08+09/2025** anhand der Prüflisten; die dort genannten
Positionen sind nicht mehr systemseitig korrigierbar.
3. Ihre Einschätzung zu Punkt 6 und zu 202537251.
Für Rückfragen zu einzelnen Belegen liefere ich gern Detailauswertungen nach.
Mit freundlichen Grüßen

View file

@ -0,0 +1,28 @@
Konto;Rechnung;Betrag Test;Betrag gebucht;Delta_1;Delta;Ursache;Neu = Verbuchung?;Status;Erläuterung;Rückmeldung E-Mail
8400;202538332;40,84;4,12;36,72;36,72;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;"Rechnungsnummer doppelt vergeben (Ursache 3): 4,12 € + 36,72 € netto; Einzelzeilen im Export über Kundennamen unterscheidbar";" Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert."
8400;202538182;231,04;134,54;96,50;96,50;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;"Rechnungsnummer doppelt vergeben (Ursache 3): 134,54 € + 96,49 € netto auf 8400 (analog 8300); Einzelzeilen im Export über Kundennamen unterscheidbar";" Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert."
8400;202537572;177,06;90,34;86,72;86,72;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;"Rechnungsnummer doppelt vergeben (Ursache 3): 90,34 € (verbucht unter 202537572) + 86,72 € (verbucht unter 202537567); Einzelzeilen im Export über Kundennamen unterscheidbar";" Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert."
8400;202537567;18,32;86,72;-68,40;-68,40;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;"Teil des Doppelnummern-Komplexes (Ursache 3): der verbuchte Betrag −86,72 € steht im Export unter 202537572; unter 202537567 exportierte Rechnung (−18,32 €) ggf. separat abstimmen";
8400;202537208;122,52;101,68;20,84;20,84;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;"Rechnungsnummer doppelt vergeben (Ursache 3): Export enthält unter dieser Nummer zwei Rechnungen (netto 101,68 € + 20,84 €); Einzelzeilen im Export über Kundennamen unterscheidbar";"Geklärt, aber unverändert im Export- technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben
Im neuen Export behoben: die Betragsfehler bei Rechnungen über 1.000 € - Formatierungsfehler beim Tausendertrennzeichen"
8300;202537572; 75,42 € ;#NV; 75,42 € ;Fehler;3 – Doppelt vergebene Rechnungsnummern;;Geklärt;Doppelnummer (Ursache 3): der 8300-Anteil −75,42 € gehört zur unter 202537567 verbuchten Rechnung;
8300;202538182; 64,24 € ; 46,64 € ; 17,60 € ;17,6;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;Rechnungsnummer doppelt vergeben (Ursache 3): 46,64 € + 17,59 € netto auf 8300;" Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert."
8300;202537567;#NV; 75,42 € ;-75,42 € ;Fehler;3 – Doppelt vergebene Rechnungsnummern;nein;Geklärt;Teil des Doppelnummern-Komplexes (Ursache 3): der verbuchte Betrag −75,42 € steht im Export unter 202537572;
8400;202539501;93,43;0;93,43;93,43;5 – Stornos 2025 (außerhalb des Systems);nein;Manuell berücksichtigen;"Storno 2025 außerhalb des Systems abgewickelt; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit 0,00 € ist korrekt, Abweichung bitte einmalig manuell berücksichtigen";"Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern"" zu kennzeichnen."
8400;202537513;101,68;22,27;79,41;79,41;5 – Stornos 2025 (außerhalb des Systems);nein;Manuell berücksichtigen;"Teilstorno 2025 außerhalb des Systems; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit Teilbetrag ist korrekt, Abweichung bitte einmalig manuell berücksichtigen";"Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern"" zu kennzeichnen."
8400;202537144;146,97;0;146,97;146,97;5 – Stornos 2025 (außerhalb des Systems);nein;Manuell berücksichtigen;"Storno 2025 außerhalb des Systems abgewickelt; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit 0,00 € ist korrekt, Abweichung bitte einmalig manuell berücksichtigen";"Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern"" zu kennzeichnen."
8400;202536978;95,29;0;95,29;95,29;5 – Stornos 2025 (außerhalb des Systems);nein;Manuell berücksichtigen;"Storno 2025 außerhalb des Systems abgewickelt; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit 0,00 € ist korrekt, Abweichung bitte einmalig manuell berücksichtigen";"Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern"" zu kennzeichnen."
8400;202506147;#NV;115,5;-115,50;Fehler;6 – Belege außerhalb des Shop-Nummernkreises;nein;Manuell berücksichtigen;"Beleg aus separatem Rechnungskreislauf (nicht Shopsystem); erscheint systembedingt nicht im DATEV-Export – im Abgleich als „extern“ kennzeichnen (Ursache 6)";"Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern"" zu kennzeichnen."
8400;202506145;#NV;265,8;-265,80;Fehler;6 – Belege außerhalb des Shop-Nummernkreises;nein;Manuell berücksichtigen;"Beleg aus separatem Rechnungskreislauf (nicht Shopsystem); erscheint systembedingt nicht im DATEV-Export – im Abgleich als „extern“ kennzeichnen (Ursache 6)";"Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern"" zu kennzeichnen."
8400;202539011;239,65;#NV;239,65;Fehler;Rechnung wird nachgereicht;0;Geklärt;"Lt. Systembetreiber reguläre Inlandsrechnung mit USt (285,18 € brutto zu 19 % + 66,22 € brutto zu 7 %), keine igL; Rechnungs-PDF wird nachgereicht";Rechnung wird nachgereicht
8300;202539011; 61,89 € ;#NV; 61,89 € ;Fehler;Rechnung wird nachgereicht;;Geklärt;"Rechnung existiert im System (66,22 € brutto zu 7 %); Rechnungs-PDF wird nachgereicht";Rechnung wird nachgereicht
8300;202535876;#NV;-46,64 € ; 46,64 € ;Fehler;Teilrückzahlung (16.09.2025) offen;nein;Offen / Rückfrage;"Gutschrift 09/2025 zu Juli-Rechnung fehlt weiterhin im Export; in der Systemanalyse nicht adressiert – Rückfrage an Systembetreiber läuft";
8400;202537251;#NV;42,3;-42,30;Fehler;ungültige Spanische USt-ID;nein;Offen / Rückfrage;Widerspruch: lt. Systemanalyse gehört die Rechnung auf 8400 (USt-ID ungültig), im neuen Export steht sie unverändert auf 8125, da die Rechnung steuerfrei ausgestellt wurde – Klärung/Rechnungskorrektur beim Systembetreiber angefragt;ungültige spanische USt-IdNr.
;;;;;;;;;;
;;;;;;;;;;
;;;;;;;;;;
;;;;;;;;;;
;;;;;;;;;;
;;;;;;;;;;
;;;;;;;;;;
;;;;;;;;;;
1 Konto Rechnung Betrag Test Betrag gebucht Delta_1 Delta Ursache Neu = Verbuchung? Status Erläuterung Rückmeldung E-Mail
2 8400 202538332 40,84 4,12 36,72 36,72 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Rechnungsnummer doppelt vergeben (Ursache 3): 4,12 € + 36,72 € netto; Einzelzeilen im Export über Kundennamen unterscheidbar Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert.
3 8400 202538182 231,04 134,54 96,50 96,50 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Rechnungsnummer doppelt vergeben (Ursache 3): 134,54 € + 96,49 € netto auf 8400 (analog 8300); Einzelzeilen im Export über Kundennamen unterscheidbar Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert.
4 8400 202537572 177,06 90,34 86,72 86,72 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Rechnungsnummer doppelt vergeben (Ursache 3): 90,34 € (verbucht unter 202537572) + 86,72 € (verbucht unter 202537567); Einzelzeilen im Export über Kundennamen unterscheidbar Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert.
5 8400 202537567 18,32 86,72 -68,40 -68,40 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Teil des Doppelnummern-Komplexes (Ursache 3): der verbuchte Betrag −86,72 € steht im Export unter 202537572; unter 202537567 exportierte Rechnung (−18,32 €) ggf. separat abstimmen
6 8400 202537208 122,52 101,68 20,84 20,84 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Rechnungsnummer doppelt vergeben (Ursache 3): Export enthält unter dieser Nummer zwei Rechnungen (netto 101,68 € + 20,84 €); Einzelzeilen im Export über Kundennamen unterscheidbar Geklärt, aber unverändert im Export- technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben Im neuen Export behoben: die Betragsfehler bei Rechnungen über 1.000 € - Formatierungsfehler beim Tausendertrennzeichen
7 8300 202537572 75,42 € #NV 75,42 € Fehler 3 – Doppelt vergebene Rechnungsnummern Geklärt Doppelnummer (Ursache 3): der 8300-Anteil −75,42 € gehört zur unter 202537567 verbuchten Rechnung
8 8300 202538182 64,24 € 46,64 € 17,60 € 17,6 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Rechnungsnummer doppelt vergeben (Ursache 3): 46,64 € + 17,59 € netto auf 8300 Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert.
9 8300 202537567 #NV 75,42 € -75,42 € Fehler 3 – Doppelt vergebene Rechnungsnummern nein Geklärt Teil des Doppelnummern-Komplexes (Ursache 3): der verbuchte Betrag −75,42 € steht im Export unter 202537572
10 8400 202539501 93,43 0 93,43 93,43 5 – Stornos 2025 (außerhalb des Systems) nein Manuell berücksichtigen Storno 2025 außerhalb des Systems abgewickelt; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit 0,00 € ist korrekt, Abweichung bitte einmalig manuell berücksichtigen Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
11 8400 202537513 101,68 22,27 79,41 79,41 5 – Stornos 2025 (außerhalb des Systems) nein Manuell berücksichtigen Teilstorno 2025 außerhalb des Systems; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit Teilbetrag ist korrekt, Abweichung bitte einmalig manuell berücksichtigen Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
12 8400 202537144 146,97 0 146,97 146,97 5 – Stornos 2025 (außerhalb des Systems) nein Manuell berücksichtigen Storno 2025 außerhalb des Systems abgewickelt; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit 0,00 € ist korrekt, Abweichung bitte einmalig manuell berücksichtigen Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
13 8400 202536978 95,29 0 95,29 95,29 5 – Stornos 2025 (außerhalb des Systems) nein Manuell berücksichtigen Storno 2025 außerhalb des Systems abgewickelt; Export enthält weiterhin vollen Betrag (Ursache 5) – Verbuchung mit 0,00 € ist korrekt, Abweichung bitte einmalig manuell berücksichtigen Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
14 8400 202506147 #NV 115,5 -115,50 Fehler 6 – Belege außerhalb des Shop-Nummernkreises nein Manuell berücksichtigen Beleg aus separatem Rechnungskreislauf (nicht Shopsystem); erscheint systembedingt nicht im DATEV-Export – im Abgleich als „extern“ kennzeichnen (Ursache 6) Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
15 8400 202506145 #NV 265,8 -265,80 Fehler 6 – Belege außerhalb des Shop-Nummernkreises nein Manuell berücksichtigen Beleg aus separatem Rechnungskreislauf (nicht Shopsystem); erscheint systembedingt nicht im DATEV-Export – im Abgleich als „extern“ kennzeichnen (Ursache 6) Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
16 8400 202539011 239,65 #NV 239,65 Fehler Rechnung wird nachgereicht 0 Geklärt Lt. Systembetreiber reguläre Inlandsrechnung mit USt (285,18 € brutto zu 19 % + 66,22 € brutto zu 7 %), keine igL; Rechnungs-PDF wird nachgereicht Rechnung wird nachgereicht
17 8300 202539011 61,89 € #NV 61,89 € Fehler Rechnung wird nachgereicht Geklärt Rechnung existiert im System (66,22 € brutto zu 7 %); Rechnungs-PDF wird nachgereicht Rechnung wird nachgereicht
18 8300 202535876 #NV -46,64 € 46,64 € Fehler Teilrückzahlung (16.09.2025) offen nein Offen / Rückfrage Gutschrift 09/2025 zu Juli-Rechnung fehlt weiterhin im Export; in der Systemanalyse nicht adressiert – Rückfrage an Systembetreiber läuft
19 8400 202537251 #NV 42,3 -42,30 Fehler ungültige Spanische USt-ID nein Offen / Rückfrage Widerspruch: lt. Systemanalyse gehört die Rechnung auf 8400 (USt-ID ungültig), im neuen Export steht sie unverändert auf 8125, da die Rechnung steuerfrei ausgestellt wurde – Klärung/Rechnungskorrektur beim Systembetreiber angefragt ungültige spanische USt-IdNr.
20
21
22
23
24
25
26
27

View file

@ -0,0 +1,137 @@
# Mail an das Steuerbüro (Herr Dachs) — 08.09.2026
**Betreff:** Provisionsabrechnungen an Berater im Ausland — Klärung der Vorgehensweise
---
Sehr geehrter Herr Dachs,
vielen Dank für Ihren Hinweis. Ich habe den Sachverhalt vollständig aufgearbeitet und
schildere ihn Ihnen nachfolgend mit den Hintergründen. **Ich habe zunächst nichts
geändert** — mir ist wichtig, die richtige Behandlung einmal abschließend mit Ihnen
festzulegen, bevor ich in ein laufendes Verfahren eingreife.
## 1. Ihre Beobachtung trifft zu
Die unterschiedliche Behandlung besteht tatsächlich. Am Beispiel von Frau
Kerschhofer-Fahr:
| Beleg | Datum | Betrag | Steuer |
| --- | --- | --- | --- |
| Provisionsabrechnung GS202649990 | 10.04.2026 | 173,27 € | ohne USt, Reverse-Charge-Hinweis |
| Ausgangsrechnung 202650470 | 20.04.2026 | 275,07 € | mit USt (19 % und 7 %) |
Im August liegen beide Belege sogar am selben Tag: Ausgangsrechnung 202656884 vom
10.08.2026 mit USt, Provisionsabrechnung GS202657040 vom 10.08.2026 ohne USt.
Bei Frau Heugenhauser (GS202656819) und Frau Rud (GS202657039) besteht dasselbe Muster.
Alle drei sind in Österreich ansässig, als Kleinunternehmer geführt und haben **keine
USt-IdNr. hinterlegt**. Genau deshalb wird ihnen bei der Bestellung Umsatzsteuer
berechnet — die Prüfung der USt-IdNr. greift dort und schlägt mangels Nummer fehl.
## 2. Hintergrund: die Vorgabe von Januar 2023
Die Behandlung der Provisionsabrechnungen beruht auf einer schriftlichen Anweisung, die
ich im Januar 2023 erhalten habe. Sie unterscheidet sechs Fälle; die steuerrelevanten
lauten im Wortlaut:
> 2. Bei den Provisionsabrechnungen müssen die USt-ID. Nr. oder die hinterlegte Steuer Nr.
> angeben sein. Hier ist die Provision für deutsche Berater mit MWSt auszubezahlen […]
> 3. Bei Deutschland Berater im Kleingewerbe muss auf der Provisionsabrechnung „Gemäß § 19
> UStG enthält der ausgewiesene Betrag keine Umsatzsteuer". Hier ist keine MWST
> auszubezahlen
> 4. In Dritt Länder z. B. Schweiz muss auf die Rechnung und Provisionen „Reverse Charge
> Verfahren: Umkehr der Steuerschuldnerschaft". Hier ist keine MWST auszubezahlen
> 5. In EU Länder z. B. Österreich muss auf die Provisionsabrechnung die jeweilige z. B.
> ATU Nummer drauf sowie „Reverse Charge Verfahren: Umkehr der Steuerschuldnerschaft".
> Hier ist keine MWST auszubezahlen.
> 6. In EU Länder z. B. Österreich **ohne ATU NR.** muss auf die Provisionsabrechnung
> „Reverse Charge Verfahren: Umkehr der Steuerschuldnerschaft". Hier ist keine MWST
> auszubezahlen
**Punkt 6 beschreibt genau den Fall, den Sie beanstanden**: EU-Berater ohne USt-IdNr.
erhalten den Reverse-Charge-Hinweis und keine Umsatzsteuer. Das System setzt diese
Vorgabe seit der ersten Provisionsabrechnung um.
Die Umsetzung im Einzelnen:
| Vorgabe | Umsetzung im System |
| --- | --- |
| Punkt 2 — Berater in Deutschland | Provision mit 19 % USt, DATEV-Schlüssel 9 |
| Punkt 3 — Kleingewerbe Deutschland | Hinweis „§ 19 Abs. 1 UStG", keine USt, Schlüssel 50 |
| Punkt 4 — Drittland | Reverse-Charge-Hinweis, keine USt |
| Punkt 5 — EU mit USt-IdNr. | Reverse-Charge-Hinweis, keine USt, Schlüssel 94 |
| Punkt 6 — EU ohne USt-IdNr. | Reverse-Charge-Hinweis, keine USt, Schlüssel 94 |
## 3. Zeitlicher Rahmen
Provisionsabrechnungen werden seit dem **10.02.2023** erstellt; bis heute sind es 4.853
Belege. Das Verfahren ist seitdem unverändert. Die Provisionskonten 4760 und 4764 waren
bereits Bestandteil des Abgleichs für 08+09/2025 — in diesen beiden Buchungsstapeln
waren 62 Provisionsabrechnungen mit Reverse-Charge-Hinweis ohne hinterlegte USt-IdNr.
enthalten.
Ich führe das nur an, damit der Umfang der möglichen Rückwirkung von Anfang an klar ist,
und möchte die Frage deshalb gern jetzt vollständig klären — für alle sechs Fälle der
Vorgabe, nicht nur für den beanstandeten.
## 4. Umfang, falls Punkt 6 anders zu behandeln ist
Die Auslandsprovisionen teilen sich wie folgt auf:
| Gruppe | Belege | Berater | Summe |
| --- | --- | --- | --- |
| EU **mit** hinterlegter USt-IdNr. (Punkt 5) | 377 | 35 | 505.563,39 € |
| EU **ohne** USt-IdNr. (Punkt 6) | 832 | 98 | 74.831,01 € |
| Drittland (Punkt 4) | 130 | 18 | 11.263,18 € |
Verteilung der 832 Belege nach Punkt 6 über die Jahre: 2023: 74 · 2024: 238 · 2025: 319 ·
2026: 201.
## 5. Zwei Punkte, die ich bei der Prüfung selbst gefunden habe
Diese möchte ich Ihnen unaufgefordert mitteilen:
**a) Punkt 5 der Vorgabe wird nicht vollständig erfüllt.** Auf der Provisionsabrechnung
wird bevorzugt die nationale Steuernummer ausgegeben; die USt-IdNr. erscheint nur, wenn
keine Steuernummer hinterlegt ist. Bei **21 Beratern mit 161 Abrechnungen über
116.982,75 €** ist zwar eine USt-IdNr. vorhanden, auf dem Beleg steht aber nur die
Steuernummer. Das ist unabhängig von der übrigen Klärung zu korrigieren, und ich setze
das um, sobald Sie mir bestätigen, dass die USt-IdNr. dort künftig ausgewiesen werden
soll.
**b) Drittland: Beleg und Buchung sind nicht deckungsgleich.** Der Beleg trägt gemäß
Punkt 4 den Reverse-Charge-Hinweis, im Buchungsstapel werden diese Fälle jedoch mit dem
Schlüssel für Kleinunternehmer (50) übergeben. Betroffen sind 130 Belege. Bitte teilen
Sie mir mit, welche der beiden Darstellungen die zutreffende ist.
## 6. Was ich von Ihnen benötige
Damit ich die Umstellung einmal vollständig und richtig vornehmen kann:
1. **Welche Behandlung ist für EU-Berater ohne USt-IdNr. zutreffend?** Provision mit
deutscher Umsatzsteuer, oder ohne Umsatzsteuer mit einem anderen Hinweis als Reverse
Charge?
2. **Welcher Hinweistext und welcher DATEV-Steuerschlüssel** sollen in diesem Fall auf
den Beleg bzw. in den Buchungsstapel?
3. **Gilt für Drittländer dieselbe Regelung** wie bisher, oder ist auch dort eine
Anpassung erforderlich (siehe 5b)?
4. **Sind die bereits ausgestellten Abrechnungen rückwirkend zu korrigieren**, oder genügt
eine Umstellung für die Zukunft? Bei einer rückwirkenden Korrektur bitte ich um Angabe
des Zeitraums.
Sobald diese Punkte geklärt sind, setze ich die Anpassung zeitnah um und stelle Ihnen die
betroffenen Belege in einer Übersicht zur Verfügung.
## 7. Vorschlag zur dauerhaften Lösung
Unabhängig von der rückwirkenden Betrachtung würde ich vorschlagen, bei den 98 betroffenen
Beratern die USt-IdNr. nachzufordern. Auch Kleinunternehmer in Österreich können eine UID
beantragen. Damit wäre das Reverse-Charge-Verfahren künftig lückenlos nachweisbar und der
Fall wäre für die Zukunft erledigt. Wenn Sie das für den richtigen Weg halten, bereite ich
die Abfrage vor.
Für Rückfragen stehe ich gern zur Verfügung; einzelne Belege kann ich Ihnen jederzeit im
Detail aufbereiten.
Mit freundlichen Grüßen

View file

@ -0,0 +1,19 @@
Danke für die Zusendung der detaillierten Auswertung für die Datensätzen.
Ich habe die Auswertung analysiert und konnte die Ursachen beheben und die Exporte für 08/2025 und 09/2025 wurden neu erstellt.
Anbei erhalten Sie:
1. die korrigierten DATEV-Buchungsstapel für 08/2025 und 09/2025 (ersetzen die bisherigen Dateien vollständig)
2. eine Statusübersicht, in der jede Ihrer Anmerkungen einzeln aufgeführt ist – mit Betrag laut Verbuchung, altem und neuem Exportwert sowie Status und Erläuterung
Kurz zusammengefasst:
Im neuen Export behoben: die Betragsfehler bei Rechnungen über 1.000 € (202536817, 202536968, 202537532 – Formatierungsfehler beim Tausendertrennzeichen), sämtliche Kontenverwechslungen zwischen 8125 und 8400/8300 sowie die Steuerschlüssel aller 20 beanstandeten Gutschriften auf 4760/4764. Die Beträge entsprechen jetzt centgenau Ihren Verbuchungen.
Geklärt, aber unverändert im Export: Bei 202537208, 202537572, 202538182 und 202538332 wurde dieselbe Rechnungsnummer technisch bedingt für jeweils zwei verschiedene Rechnungen vergeben – daher die nicht zuordenbaren Beträge. Die Einzelrechnungen sind im Export als getrennte Zeilen am Kundennamen erkennbar; Die Nummernvergabe ist inzwischen technisch gegen Doppelvergabe gesichert.
Bitte einmalig manuell berücksichtigen: Die 2025 außerhalb des Systems abgewickelten Stornos (202536978, 202537144, 202539501 voll, 202537513 teilweise) stehen systembedingt weiterhin mit vollem Betrag im Export – Ihre Verbuchung ist korrekt. Ab 2026 erscheinen Stornos automatisch im Export. Die Rechnungen 202506145 und 202506147 stammen aus einem separaten Rechnungskreislauf außerhalb des Shopsystems und bitten wir als „extern" zu kennzeichnen.
Noch offen: 202537251 (ungültige spanische USt-IdNr.), das Nummernpaar 202538744/202538774 sowie die Gutschrift zur Juli-Rechnung 202535876.
Details dazu finden Sie in der Statusübersicht.

View file

@ -78,6 +78,10 @@
class="btn btn-sm btn-success">
<i class="fas fa-download"></i> CSV
</a>
<a href="{{ route('admin_payments_taxadvisor_checklist', $current_export->id) }}"
class="btn btn-sm btn-warning">
<i class="fas fa-list-check"></i> Prüfliste
</a>
@if (!$current_export->isLocked())
<form action="{{ route('admin_payments_taxadvisor_lock', $current_export->id) }}"
method="POST" class="d-inline" onsubmit="return confirm('Export wirklich sperren?')">
@ -251,6 +255,10 @@
class="btn btn-xs btn-success" title="Download">
<i class="fas fa-download"></i>
</a>
<a href="{{ route('admin_payments_taxadvisor_checklist', $exp->id) }}"
class="btn btn-xs btn-warning" title="Prüfliste">
<i class="fas fa-list-check"></i>
</a>
@if (!$exp->isLocked())
<form action="{{ route('admin_payments_taxadvisor_lock', $exp->id) }}"
method="POST" class="d-inline" onsubmit="return confirm('Export sperren?')">

View file

@ -421,6 +421,7 @@ Route::domain(config('app.pre_url_crm').config('app.domain').config('app.tld_car
Route::post('/admin/payments/taxadvisor/preview', 'PaymentTaxAdvisorController@preview')->name('admin_payments_taxadvisor_preview');
Route::post('/admin/payments/taxadvisor/generate', 'PaymentTaxAdvisorController@generate')->name('admin_payments_taxadvisor_generate');
Route::get('/admin/payments/taxadvisor/download/{id}', 'PaymentTaxAdvisorController@download')->name('admin_payments_taxadvisor_download');
Route::get('/admin/payments/taxadvisor/checklist/{id}', 'PaymentTaxAdvisorController@checklist')->name('admin_payments_taxadvisor_checklist');
Route::get('/admin/payments/taxadvisor/datatable', 'PaymentTaxAdvisorController@datatable')->name('admin_payments_taxadvisor_datatable');
Route::post('/admin/payments/taxadvisor/lock/{id}', 'PaymentTaxAdvisorController@lock')->name('admin_payments_taxadvisor_lock');
Route::delete('/admin/payments/taxadvisor/destroy/{id}', 'PaymentTaxAdvisorController@destroy')->name('admin_payments_taxadvisor_destroy');

View file

@ -0,0 +1,130 @@
<?php
use App\Models\Country;
use App\Models\ShoppingOrder;
use App\Models\UserInvoice;
use App\Services\DatevExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
/**
* Legt eine Bestellung mit Original- und Stornorechnung an.
*
* `cancellation = true` steht an beiden Belegen; nur der Stornobeleg hat kein
* `cancellation_id` (siehe InvoiceRepository::cancel).
*
* @return array{original: UserInvoice, storno: UserInvoice}
*/
function makeCancelledInvoicePair(string $invoiceDate, string $cancellationDate): array
{
$country = Country::firstOrCreate(
['code' => 'DE'],
[
'phone' => '49', 'en' => 'Germany', 'de' => 'Deutschland', 'es' => 'Alemania',
'fr' => 'Allemagne', 'it' => 'Germania', 'ru' => 'Германия',
]
);
$shoppingUserId = DB::table('shopping_users')->insertGetId([
'auth_user_id' => 0,
'member_id' => 0,
'billing_firstname' => 'Erika',
'billing_lastname' => 'Mustermann',
'billing_email' => 'erika-'.uniqid('', true).'@example.com',
'billing_country_id' => $country->id,
'shipping_country_id' => $country->id,
'wp_order_number' => 0,
'created_at' => now(), 'updated_at' => now(),
]);
$order = ShoppingOrder::forceCreate([
'shopping_user_id' => $shoppingUserId,
'auth_user_id' => 0,
'member_id' => 0,
'country_id' => $country->id,
'user_shop_id' => 1,
'total' => 119.00,
'total_shipping' => 119.00,
'tax_split' => ['19' => '19.00'],
'net_split' => ['19' => '100.00'],
]);
$original = UserInvoice::forceCreate([
'shopping_order_id' => $order->id,
'date' => $invoiceDate,
'month' => (int) date('n', strtotime($invoiceDate)),
'year' => (int) date('Y', strtotime($invoiceDate)),
'full_number' => '202699001',
'number' => 99001,
'status' => 2,
]);
$storno = UserInvoice::forceCreate([
'shopping_order_id' => $order->id,
'date' => $cancellationDate,
'month' => (int) date('n', strtotime($cancellationDate)),
'year' => (int) date('Y', strtotime($cancellationDate)),
'full_number' => '202699002',
'number' => 99002,
'status' => 12,
'cancellation' => true,
]);
$original->cancellation = true;
$original->cancellation_id = $storno->id;
$original->cancellation_date = $cancellationDate;
$original->save();
return ['original' => $original, 'storno' => $storno];
}
it('buys the revenue in the invoice month and reverses it in the cancellation month', function () {
makeCancelledInvoicePair('2026-08-15', '2026-09-03');
$service = new DatevExportService;
$august = $service->collectInvoiceLines(8, 2026)
->concat($service->collectCancellationLines(8, 2026));
expect($august)->toHaveCount(1);
expect($august->first()['soll_haben'])->toBe('H');
expect($august->first()['belegfeld1'])->toBe('202699001');
expect(round($august->first()['amount_gross'], 2))->toBe(119.00);
$september = $service->collectInvoiceLines(9, 2026)
->concat($service->collectCancellationLines(9, 2026));
expect($september)->toHaveCount(1);
expect($september->first()['soll_haben'])->toBe('S');
expect($september->first()['belegfeld1'])->toBe('202699002');
expect(round($september->first()['amount_gross'], 2))->toBe(119.00);
});
it('nets a same-month cancellation to zero instead of booking it twice', function () {
makeCancelledInvoicePair('2026-08-15', '2026-08-20');
$service = new DatevExportService;
$lines = $service->collectInvoiceLines(8, 2026)
->concat($service->collectCancellationLines(8, 2026));
expect($lines)->toHaveCount(2);
$signed = $lines->sum(fn (array $line) => $line['soll_haben'] === 'S'
? -$line['amount_gross']
: $line['amount_gross']);
expect(round($signed, 2))->toBe(0.00);
});
it('does not treat the cancelled original invoice as a cancellation document', function () {
makeCancelledInvoicePair('2026-08-15', '2026-08-20');
$cancellationLines = (new DatevExportService)->collectCancellationLines(8, 2026);
expect($cancellationLines)->toHaveCount(1);
expect($cancellationLines->first()['belegfeld1'])->toBe('202699002');
});

View file

@ -0,0 +1,155 @@
<?php
use App\Models\Country;
use App\Models\ShoppingOrder;
use App\Models\UserInvoice;
use App\Services\DatevReconciliationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function makeChecklistCountry(): Country
{
return Country::firstOrCreate(
['code' => 'DE'],
[
'phone' => '49', 'en' => 'Germany', 'de' => 'Deutschland', 'es' => 'Alemania',
'fr' => 'Allemagne', 'it' => 'Germania', 'ru' => 'Германия',
]
);
}
function makeChecklistOrder(string $lastName, float $gross): ShoppingOrder
{
$country = makeChecklistCountry();
$shoppingUserId = DB::table('shopping_users')->insertGetId([
'auth_user_id' => 0,
'member_id' => 0,
'billing_firstname' => 'Test',
'billing_lastname' => $lastName,
'billing_email' => strtolower($lastName).'-'.uniqid('', true).'@example.com',
'billing_country_id' => $country->id,
'shipping_country_id' => $country->id,
'wp_order_number' => 0,
'created_at' => now(), 'updated_at' => now(),
]);
$net = round($gross / 1.19, 2);
return ShoppingOrder::forceCreate([
'shopping_user_id' => $shoppingUserId,
'auth_user_id' => 0,
'member_id' => 0,
'country_id' => $country->id,
'user_shop_id' => 1,
'total' => $gross,
'total_shipping' => $gross,
'tax_split' => ['19' => (string) round($gross - $net, 2)],
'net_split' => ['19' => (string) $net],
]);
}
function makeChecklistInvoice(ShoppingOrder $order, string $number, string $date, array $overrides = []): UserInvoice
{
return UserInvoice::forceCreate(array_merge([
'shopping_order_id' => $order->id,
'date' => $date,
'month' => (int) date('n', strtotime($date)),
'year' => (int) date('Y', strtotime($date)),
'full_number' => $number,
'number' => (int) substr($number, -5),
'status' => 2,
], $overrides));
}
it('lists both invoices behind a duplicated invoice number with the counterpart named', function () {
makeChecklistInvoice(makeChecklistOrder('Hoppe', 24.80), '202699100', '2026-08-11');
makeChecklistInvoice(makeChecklistOrder('Kuhn', 144.90), '202699100', '2026-08-11');
$rows = app(DatevReconciliationService::class)->build(8, 2026)
->where('kategorie', DatevReconciliationService::CATEGORY_DUPLICATE_NUMBER);
expect($rows)->toHaveCount(2);
$hoppe = $rows->firstWhere('name', 'Hoppe Test');
expect($hoppe['betrag_brutto'])->toBe(24.80);
expect($hoppe['hinweis'])->toContain('2× vergeben');
expect($hoppe['hinweis'])->toContain('169,70');
expect($hoppe['hinweis'])->toContain('Kuhn Test (144,90 €)');
});
it('flags an invoice refunded through the payment provider without a cancellation document', function () {
$order = makeChecklistOrder('Matyjaszek', 113.40);
makeChecklistInvoice($order, '202699200', '2026-08-05');
DB::table('shopping_payments')->insert([
'shopping_order_id' => $order->id,
'clearingtype' => 'wlt',
'reference' => 'ref-'.uniqid('', true),
'identifier' => 'id-'.uniqid('', true),
'txaction' => 'refund',
'status' => 'success',
'amount' => 11340,
'currency' => 'EUR',
'created_at' => '2026-08-05 10:00:00',
'updated_at' => '2026-08-28 10:00:00',
]);
$rows = app(DatevReconciliationService::class)->build(8, 2026)
->where('kategorie', DatevReconciliationService::CATEGORY_EXTERNAL_CANCELLATION);
expect($rows)->toHaveCount(1);
expect($rows->first()['belegnummer'])->toBe('202699200');
expect($rows->first()['hinweis'])->toContain('28.08.2026');
expect($rows->first()['hinweis'])->toContain('kein Stornobeleg');
});
it('points a cross-month cancellation at its original invoice and back', function () {
$order = makeChecklistOrder('Ebner', 123.10);
$original = makeChecklistInvoice($order, '202699300', '2026-08-24');
$storno = makeChecklistInvoice($order, '202699301', '2026-09-25', [
'status' => 12,
'cancellation' => true,
]);
$original->cancellation = true;
$original->cancellation_id = $storno->id;
$original->save();
$service = app(DatevReconciliationService::class);
$august = $service->build(8, 2026)
->where('kategorie', DatevReconciliationService::CATEGORY_CANCELLED_LATER);
expect($august)->toHaveCount(1);
expect($august->first()['hinweis'])->toContain('202699301');
expect($august->first()['empfehlung'])->toContain('Stornomonat');
$september = $service->build(9, 2026)
->where('kategorie', DatevReconciliationService::CATEGORY_CROSS_MONTH_CANCELLATION);
expect($september)->toHaveCount(1);
expect($september->first()['hinweis'])->toContain('202699300');
expect($september->first()['hinweis'])->toContain('08/2026');
});
it('stays empty for a clean period', function () {
makeChecklistInvoice(makeChecklistOrder('Sauber', 119.00), '202699400', '2026-08-15');
expect(app(DatevReconciliationService::class)->build(8, 2026))->toBeEmpty();
});
it('renders the checklist as a quoted csv with a header row', function () {
makeChecklistInvoice(makeChecklistOrder('Hoppe', 24.80), '202699100', '2026-08-11');
makeChecklistInvoice(makeChecklistOrder('Kuhn', 144.90), '202699100', '2026-08-11');
$csv = app(DatevReconciliationService::class)->buildCsv(8, 2026);
expect($csv)->toStartWith("\xEF\xBB\xBF\"Kategorie\";\"Belegnummer\"");
expect($csv)->toContain('"Doppelt vergebene Rechnungsnummer"');
expect($csv)->toContain('"24,80"');
expect(substr_count($csv, "\r\n"))->toBe(3);
});