# JTL → DATEV Buchungsstapel

## Sicherheitsprinzip

Das Modul wurde absichtlich **rein additiv** gebaut:

- `ImportWebsaleOrderJob.php` bleibt unverändert.
- `JtlApiService.php` bleibt unverändert.
- `WebsaleOrdersPullCommand.php` bleibt unverändert.
- `routes/console.php` und damit alle bisherigen Zeitpläne bleiben unverändert.
- Das DATEV-Modul verwendet ausschließlich `JtlApiService::rawGet()` und die vorhandenen lesenden Stammdatenmethoden.
- Es schreibt nur in Tabellen mit dem Präfix `datev_`.
- `DATEV_ENABLED` und `DATEV_SCHEDULER_ENABLED` sind standardmäßig `false`.
- Es wird kein Queue-Job registriert und kein bestehender Worker beeinflusst.

Damit kann die erste Testphase vollständig parallel zum produktiven Auftragsimport laufen.

## Umfang der ersten Version

Enthalten sind:

1. Filial- und Kontenstamm für alle 45 in der Excel-Datei genannten Kostenstellen. Alle bleiben zunächst inaktiv.
2. Zuordnung der 26 aktiven Filialen zu JTL-Verkaufskanälen.
3. Zuordnung der JTL-Zahlungsarten zu `cash` oder `card`.
4. Rein lesender Abruf der Kassenaufträge über `/salesOrders`.
5. Rein lesender Abruf der Auftragspositionen über `/salesOrders/{id}/lineitems`.
6. Lokale, idempotente Speicherung in MariaDB.
7. Erzeugung eines DATEV-Buchungsstapels mit 125 Spalten nach dem gelieferten Referenzformat.
8. Validierung, SHA-256-Prüfsumme, Versionierung und Finalisierungsschutz.
9. Semantischer Vergleich eines erzeugten Stapels mit dem originalen POS-Stapel.

Noch nicht automatisch enthalten sind Kassenbewegungen ohne Auftrag, zum Beispiel:

- Barentnahmen und Bankeinzahlungen
- Rollgeldgebühren
- Kassendifferenzen
- sonstige Einlagen und Entnahmen

Diese Zeilen werden durch `datev:compare` sichtbar. Erst nachdem die genaue JTL-Datenquelle dafür identifiziert wurde, sollten sie ergänzt werden. Mischzahlungen werden ebenfalls bewusst nicht geraten. Eine unbekannte oder nicht freigegebene Zahlungsart stoppt den Export.

## Installation

Die Dateien aus dem Patch in das Laravel-Projekt kopieren. Danach:

```bash
cp .env.datev.example .env.datev.local
```

Die benötigten Werte aus `.env.datev.example` in die echte `.env` übernehmen. Danach:

```bash
php artisan optimize:clear
php artisan migrate
php artisan datev:stores:seed
php artisan datev:status
```

Die Seed-Routine legt Konten anhand der bestätigten Systematik an:

```text
Kasse:         16000 + KOST1
Bartransit:    14600 + KOST1
Kartentransit: 14660 + KOST1
Erlöse:        44000 + KOST1
```

Keine Filiale wird automatisch aktiviert.

## Kontrollierte Inbetriebnahme

### 1. API nur lesen

```bash
php artisan datev:probe
php artisan datev:probe --order=B-009871-S-1
```

Dieser Befehl schreibt weder nach JTL noch in die DATEV-Tabellen.

### 2. Stammdaten einlesen

```bash
php artisan datev:masterdata:sync --force
```

Anschließend Zahlungsarten anzeigen, beispielsweise direkt in MariaDB:

```sql
SELECT * FROM datev_payment_methods ORDER BY name;
```

Bar und Karte explizit freigeben:

```bash
php artisan datev:payment:map 4 cash --label="Cash/Bargeld"
php artisan datev:payment:map 7 card --label="Kartenzahlung"
```

Die IDs sind Beispiele und müssen zu den tatsächlich gelesenen JTL-Zahlungsarten passen.

### 3. Stuttgart als Referenzfiliale verbinden

```bash
php artisan datev:store:map 41 "JTL-SALES-CHANNEL-ID" --activate
php artisan datev:status
```

### 4. Januar 2026 synchronisieren

```bash
php artisan datev:sync --month=2026-01 --store=41 --force
```

Der Vorgang ist idempotent. Wiederholungen aktualisieren nur geänderte Aufträge und Positionen.

### 5. Stapel erzeugen

```bash
php artisan datev:generate 2026-01 --store=41 --force
```

Die Datei wird standardmäßig hier abgelegt:

```text
storage/app/private/datev/2026/01/
```

### 6. Gegen den Originalstapel vergleichen

```bash
php artisan datev:compare EXPORT_ID /pfad/DTVF_Buchungsstapel_20260609_102656_00002.csv
```

Verglichen werden Belegnummer, Betrag, Soll/Haben, Konto, Gegenkonto, Buchungstext und Kostenstelle. GUIDs und dynamische Headerwerte werden bewusst ignoriert.

### 7. Erst nach erfolgreichem Vergleich finalisieren

```bash
php artisan datev:finalize EXPORT_ID
```

Ein finalisierter Stapel wird nicht überschrieben. Eine spätere Korrektur erzeugt eine neue Version.

## Aktivierung aller 26 Filialen

Jede Filiale wird explizit verbunden und aktiviert:

```bash
php artisan datev:store:map KOST1 SALES_CHANNEL_ID --activate
```

`datev:generate --all` besitzt einen Sicherheitsstopp und läuft nur, wenn exakt `DATEV_EXPECTED_STORE_COUNT=26` aktive Filialen vorhanden sind.

```bash
php artisan datev:status
php artisan datev:generate 2026-06 --all
```

## Automatisierung erst nach Abnahme

Der bestehende Laravel-Scheduler wurde absichtlich nicht verändert. Nach erfolgreicher Stuttgart-Abnahme können separate Cron-Einträge verwendet werden, ohne den Websale-Importplan anzufassen:

```cron
# Stündlicher, überlappender Lese-Sync mit eigenem Cache-Lock
37 * * * * cd /var/www/app && php artisan datev:sync --lookback=45 >> storage/logs/datev-cron.log 2>&1

# Monatlicher Export des abgeschlossenen Vormonats sollte über ein kleines Wrapper-Skript
# mit explizitem YYYY-MM erfolgen und erst nach fachlicher Abnahme aktiviert werden.
```

Die Synchronisation überlappt absichtlich mehrere Tage. Die lokale Speicherung ist anhand der JTL-Auftrags-ID und Positions-ID idempotent.

## Bekannte fachliche Grenzen

1. Die API filtert Aufträge über `createdSince` und `createdUntil`. Spät stornierte, deutlich ältere Aufträge benötigen einen größeren Rückblick oder einen gezielten Periodenlauf.
2. Der normale Sales-Order-Datensatz enthält eine Zahlungsart. Echte Split-/Mischzahlungen müssen mit einem realen Beispiel geprüft werden.
3. Der gelieferte Referenzstapel enthält Kassenbuchbewegungen ohne Auftrag. Dafür muss noch die zentrale JTL-Tabelle oder eine weitere POS-Quelle bestimmt werden.
4. Die Kontenliste enthält je Filiale nur ein Erlöskonto. Mehrere Umsatzsteuersätze müssen mit der Buchhaltung gegen das DATEV-Automatikkonto geprüft werden.
5. Der erste DATEV-Import muss in einer Test-/Stapelprüfung erfolgen. Der Header ist an die Referenzdatei angelehnt, verwendet aber korrekt `EXTF` als Drittanbieterkennzeichen.
