> ## Documentation Index
> Fetch the complete documentation index at: https://cona.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Revenue recognition by delivery date

# RFC: Revenue Recognition by Delivery Date (Erlösrealisation nach Versanddatum)

**Status:** Draft
**Author:** CONA Engineering
**Created:** 2026-04-02
**Updated:** 2026-04-08
**Strategy:** B — DATEV-gestützte Anzahlungsautomatik
**Related:** HGB § 252 Abs. 1 Nr. 4 (Realisationsprinzip), § 13 Abs. 1 Nr. 1a UStG, GoBD

***

## 1. Motivation

A customer wants to recognize revenue not at order/invoice time, but at **shipment date** (Versanddatum / Fulfillment date). This is the correct approach under HGB's Realisationsprinzip, which requires revenue recognition at the point of **Gefahrenübergang** (transfer of risk).

For B2C e-commerce (Verbrauchsgüterkauf), § 475 Abs. 2 BGB places the Gefahrenübergang at delivery to the consumer. We use the **fulfillment date** as a pragmatic proxy accepted by most Steuerberater.

### Current Model

```
Order placed + Payment captured + Invoice created → all on the same date (order.processedAt)
Revenue = booked immediately at order time
```

### Target Model

```
Order placed + Payment captured → Day 1 (order.processedAt)
Fulfillment / Shipment         → Day 2 (fulfillment.createdAt)  ← Revenue recognized HERE
```

### Design Decision: Strategie B (DATEV-Automatik)

CONA is not the leading accounting system — it delivers booking entries to DATEV, which is the system of record. Therefore:

* **CONA's internal GL** keeps its current structure (Bank ↔ Sammeldebitor ↔ Erlöse)
* **CONA does NOT book** to "Erhaltene Anzahlungen" (1718) or generate Auflösungsbuchungen
* **The DATEV export** enriches bookings with Anzahlungsfelder (`Buchungstyp`, `Auftragsnummer`, `USt-Schlüssel (Anzahlungen)`, `Erlöskonto (Anzahlungen)`)
* **DATEV Kanzlei-Rechnungswesen** uses its built-in Anzahlungsfunktion to handle USt-Korrektur, 1718-Umbuchung, and Auflösung automatically

**Prerequisite**: Der Steuerberater richtet einmalig die Anzahlungsfunktion in DATEV ein (Stapeleigenschaften → Anzahlungen → Konten und Buchungsfall konfigurieren).

***

## 2. Legal Basis

| Aspect                   | Regulation                   | Implication                                       |
| ------------------------ | ---------------------------- | ------------------------------------------------- |
| Revenue recognition      | HGB § 252 Abs. 1 Nr. 4       | Revenue at Gefahrenübergang (delivery)            |
| B2C risk transfer        | BGB § 475 Abs. 2             | At delivery to consumer (not shipment to carrier) |
| VAT on prepayments       | UStG § 13 Abs. 1 Nr. 1a S. 4 | VAT due when prepayment is received               |
| Timely booking           | GoBD Tz. 50-52               | Record within 10 days, period-correct             |
| Service date on invoices | UStDV § 31 Abs. 4            | Leistungsdatum mandatory on invoices              |

**Critical**: When payment is received **before** delivery, VAT is already due at payment time (§ 13 Abs. 1 Nr. 1a S. 4 UStG). DATEV handles this automatically via the Anzahlungsfelder — CONA does not need to book separate USt entries.

***

## 3. Vorher / Nachher — EXTF-Export am konkreten Beispiel

**Szenario**: Bestellung #1042, bezahlt am 28.03.2026, versendet am 02.04.2026. Brutto 1.190,00 € (netto 1.000 €, 19% USt). Shopify-Transaktions-ID: 98765.

### 3.1 VORHER — Feature AUS (Umsatzrealisierung nach Bestelldatum)

Beide Buchungssätze landen im **März-Stapel** mit Belegdatum 28.03.:

**Buchung 1 — Zahlung (März-Stapel):**

```
#   Feld                              Wert
──────────────────────────────────────────────────────────────
1   Umsatz                            1190,00
2   Soll/Haben                        S
7   Konto                             1200
8   Gegenkonto                        1400
9   BU-Schlüssel
10  Belegdatum                        2803
11  Belegfeld 1                       "SHP98765"
14  Buchungstext                      "Shopify Payment #1042"
95  Auftragsnummer                    "1042"
96  Buchungstyp
97  USt-Schlüssel (Anzahlungen)
101 Erlöskonto (Anzahlungen)
114 Festschreibung                    0
115 Leistungsdatum
116 Datum Zuord. Steuerperiode
```

**Buchung 2 — Erlös (März-Stapel):**

```
#   Feld                              Wert
──────────────────────────────────────────────────────────────
1   Umsatz                            1190,00
2   Soll/Haben                        S
7   Konto                             1400
8   Gegenkonto                        8400
9   BU-Schlüssel                      9
10  Belegdatum                        2803          ← Bestelldatum
11  Belegfeld 1                       "SHP98765"
14  Buchungstext                      "Sales Invoice INV-1042"
95  Auftragsnummer                    "1042"
96  Buchungstyp
97  USt-Schlüssel (Anzahlungen)
101 Erlöskonto (Anzahlungen)
114 Festschreibung                    0
115 Leistungsdatum                                  ← leer
116 Datum Zuord. Steuerperiode                      ← leer
```

**Ergebnis in DATEV:**

* März-UStVA: 190 € USt (aus Erlösbuchung)
* Sammeldebitor: OPOS ausgeziffert über Belegfeld 1
* Erlös: im März verbucht

***

### 3.2 NACHHER — Feature AN (Umsatzrealisierung nach Versanddatum)

Die Zahlung bleibt im **März-Stapel**, der Erlös wandert in den **April-Stapel**. Zusätzlich werden die Anzahlungsfelder und das Leistungsdatum gesetzt:

**Buchung 1 — Zahlung (März-Stapel):**

```
#   Feld                              Wert
──────────────────────────────────────────────────────────────
1   Umsatz                            1190,00
2   Soll/Haben                        S
7   Konto                             1200
8   Gegenkonto                        1400
9   BU-Schlüssel
10  Belegdatum                        2803
11  Belegfeld 1                       "SHP98765"
14  Buchungstext                      "Shopify Payment #1042"
95  Auftragsnummer                    "1042"
96  Buchungstyp                       "AG"          ← NEU: Erhaltene Anzahlung
97  USt-Schlüssel (Anzahlungen)       9             ← NEU: 19% USt der Schlussrechn.
101 Erlöskonto (Anzahlungen)          8400          ← NEU: Erlöskonto d. Schlussrechn.
114 Festschreibung                    0
115 Leistungsdatum
116 Datum Zuord. Steuerperiode
```

**Buchung 2 — Erlös (April-Stapel):**

```
#   Feld                              Wert
──────────────────────────────────────────────────────────────
1   Umsatz                            1190,00
2   Soll/Haben                        S
7   Konto                             1400
8   Gegenkonto                        8400
9   BU-Schlüssel                      9
10  Belegdatum                        0204          ← NEU: Versanddatum statt Bestelldatum
11  Belegfeld 1                       "SHP98765"
14  Buchungstext                      "Sales Invoice INV-1042"
95  Auftragsnummer                    "1042"
96  Buchungstyp                       "SR"          ← NEU: Schlussrechnung
97  USt-Schlüssel (Anzahlungen)
101 Erlöskonto (Anzahlungen)
114 Festschreibung                    0
115 Leistungsdatum                    02042026      ← NEU: Versanddatum
116 Datum Zuord. Steuerperiode        02042026      ← NEU: USt-Periode April
```

### 3.3 Was DATEV daraus macht

|                             | Feature AUS                | Feature AN                                                                     |
| --------------------------- | -------------------------- | ------------------------------------------------------------------------------ |
| **USt März-UStVA**          | 190 € (aus Erlösbuchung)   | 190 € (DATEV extrahiert USt aus AG-Buchung automatisch)                        |
| **USt April-UStVA**         | —                          | 190 € Erlös-USt abzgl. 190 € Anzahlungs-USt-Korrektur = **0 € Netto-Änderung** |
| **Erlös März**              | 1.000 €                    | —                                                                              |
| **Erlös April**             | —                          | 1.000 €                                                                        |
| **Sammeldebitor OPOS**      | Ausgeziffert (Belegfeld 1) | Ausgeziffert (Belegfeld 1)                                                     |
| **Erh. Anzahlungen (1718)** | —                          | DATEV bucht intern um + löst automatisch auf                                   |

**Zusammengefasst**: An den Buchungssätzen in CONA ändert sich fast nichts — gleiche Konten, gleiche Beträge, gleiche GID. Nur das **Datum der Erlösbuchung** verschiebt sich, und es kommen **4 zusätzliche DATEV-Felder** hinzu (Buchungstyp, USt-Schlüssel Anz., Erlöskonto Anz., Leistungsdatum + Datum Zuord. Steuerperiode), damit DATEV die USt periodengerecht zuordnen kann.

### 3.4 Wann werden die Anzahlungsfelder NICHT gesetzt?

| Situation                                            | Buchungstyp AG/SR?                     | Leistungsdatum? |
| ---------------------------------------------------- | -------------------------------------- | --------------- |
| Zahlung + Versand im **selben Monat**                | Nein (nicht nötig, USt stimmt ohnehin) | Ja              |
| Zahlung + Versand am **selben Tag**                  | Nein                                   | Ja              |
| Zahlung **nach** Versand (Kauf auf Rechnung)         | Nein (keine Anzahlung)                 | Ja              |
| Zahlung **vor** Versand, **unterschiedliche Monate** | **Ja** (USt-relevant)                  | Ja              |

***

## 4. Booking Model in CONA

### 4.1 Principle: Minimal Changes

CONA's internal GL structure stays **identical** to today. The only change is the **timing** of when the revenue entry is created:

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        ORDER LIFECYCLE                                  │
│                                                                         │
│  Day 1: Order + Payment              Day 2: Fulfillment                │
│  ┌──────────────────────┐            ┌──────────────────────┐          │
│  │ Customer places order│            │ Warehouse ships      │          │
│  │ Payment captured     │            │ Fulfillment created  │          │
│  │                      │            │ in Shopify           │          │
│  │ → CONA books payment │            │                      │          │
│  │   Bank → Sammeldeb.  │            │ → CONA books revenue │          │
│  │   (wie bisher)       │            │   Sammeldeb. → Erlöse│          │
│  │                      │            │   (mit Leistungsdat.)│          │
│  └──────────────────────┘            └──────────────────────┘          │
│                                                                         │
│  DATEV Export:                        DATEV Export:                     │
│  Buchungstyp "AG"                     Buchungstyp "SR"                 │
│  + USt-Schlüssel (Anz.)              + Leistungsdatum                  │
│  + Erlöskonto (Anz.)                 + Datum Zuord. Steuerperiode      │
│  + Auftragsnummer                     + Auftragsnummer                 │
│                                                                         │
│  ───────────── DATEV erledigt automatisch: ──────────────              │
│  • Umbuchung auf Erh. Anzahlungen (1718)                               │
│  • USt-Abführung auf Anzahlung                                         │
│  • Auflösung bei Schlussrechnung                                       │
│  • USt-Korrektur bei Schlussrechnung                                   │
└─────────────────────────────────────────────────────────────────────────┘
```

### 4.2 CONA GL Entries

#### Entry #1 — Payment Received (Day 1, e.g. March 28)

```
┌────────────────────────────────────────────────────────────────┐
│  Bank (1200)                          1.190,00 €   SOLL        │
│  an Sammeldebitor (1400)              1.190,00 €   HABEN       │
│                                                                │
│  GID: "shopify_txn_98765"                                      │
│  Belegdatum: 28.03.                                            │
│  Buchungstext: "Shopify Payment Order #1042"                   │
│                                                                │
│  ► Identisch zum heutigen Modell — keine Änderung             │
└────────────────────────────────────────────────────────────────┘
```

#### Entry #2 — Revenue at Fulfillment (Day 2, e.g. April 2)

```
┌────────────────────────────────────────────────────────────────┐
│  Sammeldebitor (1400)                 1.190,00 €   SOLL        │
│  an Erlöse 19% (8400)                1.000,00 €   HABEN       │
│  an USt 19% (1776)                     190,00 €   HABEN       │
│                                                                │
│  GID: "shopify_txn_98765"                                      │
│  Belegdatum: 02.04.                                            │
│  Buchungstext: "Sales Invoice INV-1042"                        │
│  Leistungsdatum: 02.04.2026  ← NEU                            │
│                                                                │
│  ► Gleiche Buchung wie heute, nur mit anderem Datum            │
│  ► Plus: Leistungsdatum wird gesetzt                           │
└────────────────────────────────────────────────────────────────┘
```

#### Reconciliation on Sammeldebitor (1400)

```
Sammeldebitor (1400) + GID "shopify_txn_98765":
  #1: HABEN -1.190 €  (Zahlung, 28. März)
  #2: SOLL  +1.190 €  (Erlös, 2. April)
  ─────────────────
  Saldo:        0 €   ✓ Ausgeziffert

► Gleiche Auszifferungslogik wie heute — keine Änderung
```

### 4.3 Same-Day Scenario (Payment + Fulfillment am selben Tag)

Wenn Zahlung und Versand am selben Tag, ist es exakt wie das heutige Modell. Kein Buchungstyp AG/SR nötig im DATEV-Export — nur `Leistungsdatum` wird zusätzlich gesetzt.

### 4.4 Was sich in CONA ändert / nicht ändert

| Bereich                                         | Änderung?              | Begründung                                                           |
| ----------------------------------------------- | ---------------------- | -------------------------------------------------------------------- |
| Kontenrahmen / Chart of Accounts                | Nein                   | Kein 1718 Konto in CONA nötig                                        |
| Posting Matrix (Debit/Credit)                   | Nein                   | Gleiche Kontenkombinationen                                          |
| **Posting Matrix (BU)**                         | **Ja — Vereinfachung** | BU-Matrix wird durch einfache Mapping-Tabelle ersetzt (s. Section 6) |
| **`general_ledger`-Schema**                     | **Ja**                 | Neue Spalte `bu_schluessel` + `service_date`                         |
| Reconciliation Engine                           | Nein                   | GID-basiert auf Sammeldebitor wie bisher                             |
| `getReferenceDocumentNumber()`                  | Nein                   | Kein Cross-Account-Lookup nötig                                      |
| `createAccountingImpact` / `createJournalEntry` | Minimal                | BU automatisch setzen + `service_date` hinzufügen                    |
| Auflösungsbuchungen                             | Nein                   | DATEV erzeugt diese automatisch                                      |

***

## 5. DATEV Export — Die Anzahlungsfelder

### 5.1 Relevante DATEV-Felder

Die DATEV EXTF-Spezifikation enthält einen Anzahlungsblock (Felder #95-#101), den wir bisher nicht befüllen:

| #   | DATEV-Feld                       | Format       | Beschreibung                               |
| --- | -------------------------------- | ------------ | ------------------------------------------ |
| 95  | `Auftragsnummer`                 | 0-30 Zeichen | Verknüpft alle Buchungen eines Vorgangs    |
| 96  | `Buchungstyp`                    | 2 Buchstaben | Steuert die Anzahlungsautomatik            |
| 97  | `USt-Schlüssel (Anzahlungen)`    | 0-2 Ziffern  | USt-Schlüssel der späteren Schlussrechnung |
| 98  | `EU-Mitgliedstaat (Anzahlungen)` | 2 Buchstaben | EU-Land der Schlussrechnung                |
| 99  | `Sachverhalt L+L (Anzahlungen)`  | 0-3 Ziffern  | L+L-Sachverhalt der Schlussrechnung        |
| 100 | `EU-Steuersatz (Anzahlungen)`    | Ziffern      | EU-Steuersatz der Schlussrechnung          |
| 101 | `Erlöskonto (Anzahlungen)`       | 4-8 Ziffern  | Erlöskonto der Schlussrechnung             |
| 115 | `Leistungsdatum`                 | TTMMJJJJ     | Lieferdatum / Leistungsdatum               |
| 116 | `Datum Zuord. Steuerperiode`     | TTMMJJJJ     | Zuordnung zur USt-Periode                  |

### 5.2 Buchungstyp-Werte

| Code   | Bedeutung                         | Wann wir ihn setzen                            |
| ------ | --------------------------------- | ---------------------------------------------- |
| **AG** | Erhaltene Anzahlung (Geldeingang) | Payment-Buchung, wenn Zahlung VOR Fulfillment  |
| **SR** | Schlussrechnung                   | Revenue-Buchung bei Fulfillment                |
| **SG** | Schlussrechnung (Geldeingang)     | Payment-Buchung, wenn Zahlung NACH Fulfillment |
| —      | (leer)                            | Zahlung und Fulfillment am selben Tag          |

### 5.3 DATEV-Export je Buchung

#### Payment-Buchung (Buchungstyp "AG")

Wird gesetzt wenn `paymentDate < fulfillmentDate`:

| DATEV-Feld               | Wert                      | Bemerkung                                             |
| ------------------------ | ------------------------- | ----------------------------------------------------- |
| Umsatz                   | `1190,00`                 | Bruttobetrag                                          |
| Soll/Haben               | `S`                       |                                                       |
| Konto                    | `1200`                    | Bank                                                  |
| Gegenkonto               | `1400`                    | Sammeldebitor                                         |
| Belegdatum               | `2803`                    | Zahlungsdatum                                         |
| Belegfeld 1              | `"SHP-98765"`             | GID für OPOS-Ausgleich                                |
| Buchungstext             | `"Shopify Payment #1042"` |                                                       |
| **Auftragsnummer**       | `"1042"`                  | **NEU** — Shopify Order-Nr.                           |
| **Buchungstyp**          | `"AG"`                    | **NEU** — Erhaltene Anzahlung                         |
| **USt-Schlüssel (Anz.)** | `9`                       | **NEU** — BU 9 = 19% Verkauf (aus SR-Buchung, s. 5.4) |
| **Erlöskonto (Anz.)**    | `8400`                    | **NEU** — Erlöse 19% (aus SR-Buchung, s. 5.4)         |

> **Was DATEV damit macht**: Beim Import erkennt DATEV den Buchungstyp "AG" und speichert den Vorgang als offene Anzahlung. Die USt auf die Anzahlung wird automatisch in die korrekte UStVA-Periode gebucht (§ 13 Abs. 1 Nr. 1a S. 4 UStG).

#### Revenue-Buchung (Buchungstyp "SR")

| DATEV-Feld                     | Wert                       | Bemerkung                       |
| ------------------------------ | -------------------------- | ------------------------------- |
| Umsatz                         | `1190,00`                  | Bruttobetrag                    |
| Soll/Haben                     | `S`                        |                                 |
| Konto                          | `1400`                     | Sammeldebitor                   |
| Gegenkonto                     | `8400`                     | Erlöse 19% USt (Automatikkonto) |
| BU-Schlüssel                   | `9`                        | BU 9 = 19% Verkauf              |
| Belegdatum                     | `0204`                     | Fulfillment-Datum               |
| Belegfeld 1                    | `"SHP-98765"`              | Gleiche GID                     |
| Buchungstext                   | `"Sales Invoice INV-1042"` |                                 |
| **Auftragsnummer**             | `"1042"`                   | **NEU** — Gleiche Order-Nr.     |
| **Buchungstyp**                | `"SR"`                     | **NEU** — Schlussrechnung       |
| **Leistungsdatum**             | `02042026`                 | **NEU** — Versanddatum          |
| **Datum Zuord. Steuerperiode** | `02042026`                 | **NEU** — USt-Periodenzuordnung |

> **Was DATEV damit macht**: Beim Import erkennt DATEV anhand der gleichen `Auftragsnummer` die Zugehörigkeit zur Anzahlung "AG". DATEV erzeugt **automatisch** die Auflösungsbuchung (Erh. Anz. an Sammeldebitor), korrigiert die Anzahlungs-USt und bucht die endgültige USt der Schlussrechnung.

### 5.4 Herkunft von USt-Schlüssel und Erlöskonto auf der AG-Buchung

**Problem**: Die Payment-Buchung (Bank → Sammeldebitor) hat keinen BU-Schlüssel und kein Erlöskonto — das sind Eigenschaften der *noch nicht existierenden* Revenue-Buchung. DATEV braucht die Werte aber beim Import der AG-Buchung.

**Lösung**: Durch die BU-Schlüssel-Vereinfachung (s. Section 6) ist der BU direkt als DB-Spalte auf dem GL-Entry verfügbar:

```
DATEV-Export für AG-Buchung:
  │
  ├─ 1. Suche Revenue-Buchung (SR) mit gleicher GID (periodenübergreifend)
  │
  ├─ 2. Gefunden? → Übernehme:
  │     • sr_entry.bu_schluessel       →  USt-Schlüssel (Anzahlungen)
  │     • sr_entry.credit_account_nr   →  Erlöskonto (Anzahlungen)
  │
  └─ 3. Nicht gefunden? (Fulfillment noch nicht erfolgt)
        → deriveBuSchluessel(order.taxRate, country, buConfig)
        → Erlöskonto aus buConfig.taxRateMappings
```

**Warum das funktioniert:**

* Der DATEV-Export wird typischerweise nach Periodenabschluss ausgelöst (z.B. März-Export Anfang April)
* Zu diesem Zeitpunkt existiert die Revenue-Buchung (02.04.) **bereits in CONAs GL**
* `sr_entry.bu_schluessel` enthält den korrekten BU direkt als Spalte — kein JSON-Parsing, kein Dimension-Mapping
* Fallback: `deriveBuSchluessel()` (gleiche Funktion wie bei Journal-Entry-Erstellung, s. 6.4)

**Edge Case — Fulfillment steht noch aus:**

Wenn der Nutzer den März-Export vor dem Fulfillment macht, gibt es noch keine SR-Buchung. Dann:

1. CONA ruft `deriveBuSchluessel()` mit dem Order-Steuersatz auf (Shopify liefert taxRate bei Bestellung)
2. Erlöskonto wird analog aus der BU-Config aufgelöst
3. Werte sind in 99% identisch zur späteren SR (gleiche Ware, gleicher Steuersatz)

**SKR03 — Standard-Erlöskonten und BU-Schlüssel (Referenz):**

| Steuersatz         | BU-Schlüssel | Erlöskonto | Kontobezeichnung                                 |
| ------------------ | ------------ | ---------- | ------------------------------------------------ |
| 19%                | 9            | 8400       | Erlöse 19% USt (AM → bucht automatisch auf 1776) |
| 7%                 | 14           | 8300       | Erlöse 7% USt (AM → bucht automatisch auf 1771)  |
| 0% (steuerfrei)    | 2            | 8120       | Steuerfreie Umsätze §4                           |
| 0% (ig. Lieferung) | 3            | 8125       | Steuerfreie ig. Lieferungen §4 Nr.1b             |

> **Hinweis zu Automatikkonten**: 8400 und 8300 sind **Automatikkonten** (AM) in DATEV. Wenn das Gegenkonto ein Automatikkonto ist, berechnet DATEV die USt automatisch. Der BU-Schlüssel 9 bzw. 14 dient als zusätzliche Absicherung.

### 5.5 Automatische DATEV-Buchungen (transparent für CONA)

DATEV erzeugt intern bei Import der SR-Buchung mit passender Auftragsnummer:

```
Automatisch erzeugt durch DATEV Kanzlei-Rechnungswesen:
─────────────────────────────────────────────────────────

1. Umbuchung Zahlung auf Anzahlungskonto:
   Sammeldebitor (1400) an Erh. Anzahlungen (1718)    1.190,00 €

2. Bei Schlussrechnung — Auflösung:
   Erh. Anzahlungen (1718) an Sammeldebitor (1400)    1.190,00 €

3. USt-Korrektur:
   Anzahlungs-USt aus März wird neutralisiert,
   endgültige USt aus April wird verbucht.
```

### 5.6 DATEV-Einrichtung durch Steuerberater (einmalig)

In DATEV Kanzlei-Rechnungswesen → Buchungsstapel → Eigenschaften → Anzahlungen:

1. **Anzahlungskonto** festlegen: z.B. 1718 (Erhaltene Anzahlungen)
2. **Buchungsfall** einstellen: "Erhaltene Anzahlungen ohne Abschlagsrechnung"
3. **Buchungstext** für Auflösung konfigurieren

Diese Einrichtung erfolgt einmalig pro Mandant und muss nicht wiederholt werden.

### 5.7 Hinweis zu Feld #115 Leistungsdatum

Aus der DATEV-Spezifikation:

> "Beim Import des Leistungsdatums **muss** das Feld `Datum Zuord. Steuerperiode` (#116) gefüllt sein. Der Einsatz des Leistungsdatums muss in Absprache mit dem Steuerberater erfolgen."

**Beide Felder** (#115 + #116) werden daher immer zusammen gesetzt. `Datum Zuord. Steuerperiode` steuert, in welche UStVA-Periode die Buchung fällt.

***

## 6. BU-Schlüssel — Vereinfachung des Steuerschlüssel-Systems

### 6.1 Motivation

Aktuell wird der BU-Schlüssel über eine vollständige **Posting-Matrix** (`Tax Code (BU)` Matrix) mit Rules bestimmt, als `gl_dimensions`-Wert im `dimensions`-JSON des GL-Eintrags gespeichert, und beim DATEV-Export über ein Dimension-Mapping aufgelöst. Diese Architektur ist für den BU-Schlüssel overengineered:

* Der BU-Schlüssel ist **deterministisch** aus Steuersatz + Lieferland ableitbar
* 95%+ der Nutzer haben identische BU-Rules (19% → 9, 7% → 14, ig. Lieferung → 3)
* Die Posting-Matrix-Konfiguration für BU ist unnötig komplex
* Für die AG-Buchungen im Revenue-Recognition-Feature brauchen wir den BU **direkt als DB-Spalte**, nicht versteckt im JSON

### 6.2 Architektur-Entscheidung

```
VORHER (komplex):
  Posting Matrix → Rules evaluieren → dimensions JSON { buDimUuid: "9" }
  → DATEV-Export: dimensionMapping BU → buDimUuid → Wert "9"

NACHHER (einfach):
  createJournalEntry() → deriveBuSchluessel(taxRate, country, buConfig)
  → general_ledger.bu_schluessel = "9"
  → DATEV-Export: entry.bu_schluessel → fertig
```

### 6.3 Neue DB-Spalte

```
general_ledger:
  + bu_schluessel  String?    -- "9", "14", "3", "7", "2", etc.
```

Die `dimensions`-JSON-Spalte bleibt bestehen für KOST1, KOST2 und Kost-Menge — nur der BU-Schlüssel wird herausgezogen.

### 6.4 Automatische BU-Ableitung

Neue Funktion `deriveBuSchluessel()` in `@cona/core`:

```
Eingabe: taxRate, senderCountry, shippingCountry, buConfig
  │
  ├─ taxRate > 0?
  │   └─ Ja → buConfig.taxRateMappings nachschlagen
  │           z.B. 19% → "9",  7% → "14"
  │
  ├─ taxRate = 0 + EU-Export?
  │   └─ (shippingCountry ∈ EU ∧ shippingCountry ≠ senderCountry)
  │      → buConfig.euDeliveryBu (default: "3")
  │
  ├─ taxRate = 0 + Drittland?
  │   └─ (shippingCountry ∉ EU ∧ shippingCountry ≠ senderCountry)
  │      → buConfig.thirdCountryBu (default: "7")
  │
  └─ taxRate = 0 + Inland?
      └─ buConfig.taxFreeDomesticBu (default: "2")
```

Die Funktion nutzt die bestehende `isEUCountry()`-Logik aus `@cona/utils`.

### 6.5 BU-Konfiguration in DATEV-Settings

Statt Posting-Matrix-Rules: einfache Mapping-Tabelle in den DATEV Export Settings:

```typescript theme={null}
buSchluesselConfig: {
  enabled: true,
  taxRateMappings: [
    { taxRatePercent: 19, bu: "9",  label: "Inland 19% USt" },
    { taxRatePercent: 7,  bu: "14", label: "Inland 7% USt" },
  ],
  euDeliveryBu: "3",       // ig. Lieferung §4 Nr.1b
  thirdCountryBu: "7",     // Drittland-Export
  taxFreeDomesticBu: "2",  // Steuerfrei Inland §4
}
```

Der Nutzer kann die BU-Werte in den DATEV-Settings anpassen — aber die meisten werden die Defaults behalten.

### 6.6 Auswirkungen auf bestehende Systeme

| Bereich                        | Änderung                                                                       |
| ------------------------------ | ------------------------------------------------------------------------------ |
| `createJournalEntry()`         | BU automatisch setzen via `deriveBuSchluessel()`                               |
| `process-dimensions.ts`        | BU-Dimension überspringen (nicht mehr als GL-Dimension evaluieren)             |
| `generateDimensionKey()`       | `bu_schluessel`-Spalte statt dimensions-JSON für BU                            |
| DATEV Entry-Transformer        | `entry.bu_schluessel` direkt lesen statt Dimension-Mapping                     |
| DATEV Consolidated-Transformer | Gleiche Vereinfachung                                                          |
| Posting-Matrix-Seed            | "Tax Code (BU)" Matrix nicht mehr erstellen                                    |
| `datevDimensionType`           | `"BU"` entfernen (KOST1/KOST2/Kost-Menge bleiben)                              |
| DATEV-Settings-UI              | Neuer "BU-Schlüssel" Tab mit Mapping-Tabelle statt Dimension-Mapping           |
| Migration                      | Bestehende BU-Werte aus `dimensions`-JSON in `bu_schluessel`-Spalte backfillen |

### 6.7 Vorteil für Revenue Recognition

Mit `bu_schluessel` als eigene Spalte wird die AG/SR-Logik im DATEV-Export deutlich einfacher:

* **USt-Schlüssel (Anzahlungen)** auf der AG-Buchung = `sr_entry.bu_schluessel` (direkt lesbar)
* **Fallback** wenn noch kein SR existiert = `deriveBuSchluessel(order.taxRate, ...)` (gleiche Funktion)
* Kein Dimension-Mapping-Lookup mehr nötig, keine UUID-Auflösung, kein JSON-Parsing

***

## 7. GID / Auszifferung

### 7.1 Keine Änderung an der Reconciliation-Logik

Die bestehende GID-basierte Auszifferung auf dem Sammeldebitor bleibt identisch:

```
Sammeldebitor (1400) + GID "shopify_txn_98765":
  Payment:  HABEN -1.190 €  (28.03.)
  Revenue:  SOLL  +1.190 €  (02.04.)
  ─────────────────
  Saldo:        0 €  ✓ Ausgeziffert
```

### 7.2 GID-Quelle (wie bisher)

```typescript theme={null}
// packages/temporal-workflows/src/adapters/shopify/orders/helpers.ts
const reconciliationId =
  chosen.gateway === "shopify_payments" || chosen.gateway === "gift_card"
    ? chosen.id
    : chosen.authorizationCode || chosen.id;
```

Der GID wird aus der Shopify-Transaktion abgeleitet — verfügbar bei Payment UND bei Fulfillment.

### 7.3 Split Payments

Für Bestellungen mit mehreren Zahlungsmethoden erzeugt die bestehende Multi-GID-Allocation proportionale GL-Einträge. Das funktioniert unverändert:

```
Order #1042: €50 Kreditkarte (txn_111) + €20 Gutschein (txn_222)

Payment (Day 1):
  Bank → Sammeldebitor  50 €  GID: txn_111  Buchungstyp: "AG"
  Bank → Sammeldebitor  20 €  GID: txn_222  Buchungstyp: "AG"

Revenue (Day 2):
  Sammeldebitor → Erlöse  50 €  GID: txn_111  Buchungstyp: "SR"
  Sammeldebitor → Erlöse  20 €  GID: txn_222  Buchungstyp: "SR"
```

***

## 8. Edge Cases

### 8.1 Same-Day (Zahlung + Versand am selben Tag)

Kein Buchungstyp AG/SR nötig. Standard-Buchung wie bisher, nur `Leistungsdatum` wird gesetzt. Optional können wir trotzdem die Auftragsnummer setzen — schadet nicht, löst aber keine Anzahlungsautomatik aus wenn kein AG vorhanden ist.

### 8.2 Partial Fulfillment (Teillieferung)

Shopify unterstützt mehrere Fulfillments pro Order. Jedes Fulfillment enthält eine Teilmenge der Line Items.

```
Order #1042: Item A (€50) + Item B (€30) = €80 total, vorab bezahlt

Payment (Day 1):
  Bank → Sammeldebitor  80 €  GID: txn_98765  Buchungstyp: "AG"

Fulfillment 1 (Day 3): Item A versendet
  Sammeldebitor → Erlöse  50 €  GID: txn_98765  Buchungstyp: "SR"

Fulfillment 2 (Day 7): Item B versendet
  Sammeldebitor → Erlöse  30 €  GID: txn_98765  Buchungstyp: "SR"
```

Im DATEV-Export erhalten beide SR-Buchungen die gleiche `Auftragsnummer`. DATEV löst die Anzahlung bei der ersten Schlussrechnung anteilig auf und den Rest bei der zweiten.

**Auszifferung in CONA**: Auf dem Sammeldebitor stehen Payment (-80 €) und zwei Revenue-Buchungen (+50 €, +30 €). Der Reconciliation Engine erkennt über den `autoDifferenceThreshold` die Teilzahlungen.

### 8.3 Cancelled Order (Stornierung ohne Versand)

```
Order + Payment → Bank an Sammeldebitor (wie bisher)
Order storniert → Refund erzeugt:
  Sammeldebitor an Bank  (Rückzahlung)

► Sammeldebitor ausgeziffert (Payment + Refund = 0), kein Revenue gebucht
```

Im DATEV-Export: Die Payment-Buchung hatte Buchungstyp "AG". Die Refund-Buchung bekommt keinen speziellen Buchungstyp — DATEV erkennt anhand der Auftragsnummer, dass die Anzahlung zurückgezahlt wurde.

### 8.4 Payment After Fulfillment (Kauf auf Rechnung)

Zahlung kommt NACH Fulfillment. Kein Anzahlungssachverhalt:

```
Day 1: Fulfillment → Revenue direkt:
  Sammeldebitor → Erlöse+USt   Buchungstyp: —  (kein AG/SR)
  + Leistungsdatum = Fulfillment-Datum

Day 5: Zahlung → Standard-Payment:
  Bank → Sammeldebitor          Buchungstyp: —
```

Die Erkennung: Wenn zum Zeitpunkt des Revenue-Booking bereits Zahlung vorhanden ist → AG/SR. Wenn Fulfillment vor Zahlung → kein Buchungstyp nötig.

### 8.5 Fulfillment in geschlossener Buchungsperiode

1. Warnung loggen: "Revenue für Fulfillment vom 31.03. wird in April gebucht (März geschlossen)"
2. CONA bucht in die nächste offene Periode
3. `Leistungsdatum` wird trotzdem auf das tatsächliche Fulfillment-Datum gesetzt (31.03.)
4. `Datum Zuord. Steuerperiode` = tatsächliches Fulfillment-Datum → DATEV ordnet die USt korrekt zu

### 8.6 Refund After Revenue Recognition

Standard-Refund-Handling. Die Gutschrift referenziert die Original-Rechnung:

```
Erlöse + USt an Sammeldebitor  (Revenue-Storno)
Sammeldebitor an Bank          (Rückzahlung)
```

GID der Refund-Transaktion verknüpft die Einträge für die Auszifferung.

***

## 9. Implementation Plan

Each phase is independently shippable and delivers value on its own. Later phases build on earlier ones but are not blocked by them unless noted.

<h3 id="phase-1">
  Phase 1: BU-Schlüssel Foundation (mittel, s. Section 6)
</h3>

Refactor the BU-Schlüssel from a dimension-JSON value into a first-class DB column. No user-facing behaviour change — purely internal simplification that unblocks everything else.

**Data Model:**

* [ ] Add `bu_schluessel String?` column to `general_ledger` table
* [ ] Migration: Backfill existing entries from `dimensions`-JSON → `bu_schluessel` column
* [ ] Migration: Remove BU key from `dimensions`-JSON (cleanup)

**Core Logic:**

* [ ] Create `packages/core/src/general_ledger/derive-bu-schluessel.ts` — zentrale Ableitungsfunktion
* [ ] `createJournalEntry()`: BU automatisch setzen via `deriveBuSchluessel(taxRate, country, buConfig)`
* [ ] `process-dimensions.ts`: BU-Dimension aus Rule-Verarbeitung ausnehmen
* [ ] `consolidate-entries.ts`: `generateDimensionKey()` um `bu_schluessel`-Spalte erweitern (statt aus dimensions-JSON)

**Settings & Types:**

* [ ] Add `buSchluesselConfig` to `datevExportSettingsSchema` in `@cona/types`
* [ ] Remove `"BU"` from `datevDimensionType` enum (KOST1/KOST2/Kost-Menge bleiben)
* [ ] Default `buSchluesselConfig` in `posting-matrix-seed.ts` setzen statt BU-Dimension + Matrix

**DATEV Export:**

* [ ] `entry-transformer.ts` (Temporal + Webapp): `entry.bu_schluessel` direkt lesen
* [ ] `consolidated-transformer.ts` (Temporal + Webapp): gleiche Vereinfachung

**UI:**

* [ ] DATEV-Settings-Dialog: "BU-Schlüssel" Tab mit einfacher Mapping-Tabelle
* [ ] `"BU"` aus Dimension-Mapping-Optionen entfernen
* [ ] Posting-Matrix: "Tax Code (BU)" Matrix nicht mehr für neue Gruppen erstellen

***

### Phase 2: Leistungsdatum — always set service date (klein)

Set `Leistungsdatum` (#115) and `Datum Zuord. Steuerperiode` (#116) on every revenue booking in the DATEV export. This is legally required (UStDV § 31 Abs. 4) and valuable independent of any revenue-timing change. No toggle needed — this is unconditionally correct.

**Data Model:**

* [ ] Add `service_date` field to `general_ledger` (nullable `DateTime`)
* [ ] Add `fulfillment_date` field to `documents` table (nullable `DateTime`)

**Shopify Adapter:**

* [ ] Extend Shopify Orders GraphQL query to include fulfillment data (`fulfillments.createdAt`)
* [ ] Set `service_date` on revenue GL entries: use `fulfillment.createdAt` when available, fall back to `order.processedAt`
* [ ] Set `fulfillment_date` on the document when a fulfillment exists

**DATEV Export:**

* [ ] `Leistungsdatum` (#115): emit `entry.service_date` in TTMMJJJJ format when present
* [ ] `Datum Zuord. Steuerperiode` (#116): always set together with Leistungsdatum

**UI:**

* [ ] Show `Leistungsdatum` in the GL detail view

**Shippable independently**: Yes. Revenue is still booked at order date — only the DATEV export gains the Leistungsdatum fields. Existing bookings without `service_date` export exactly as before (fields left empty).

***

### Phase 3: Revenue Recognition by Fulfillment Date (mittel)

Shift revenue booking from order date to fulfillment date. Opt-in per Shopify integration.

**Depends on**: Phase 2 (service\_date column + fulfillment data from Shopify)

**Settings:**

* [ ] Add `revenue_recognition_mode` to Shopify integration settings (`"order_date"` | `"fulfillment_date"`)
* [ ] Toggle in Shopify-Integration-Settings UI: "Umsatzrealisierung: Bestelldatum / Versanddatum"

**Shopify Adapter:**

* [ ] When `revenue_recognition_mode = "fulfillment_date"`:
  * Payment booking: weiterhin bei Zahlungseingang (wie bisher)
  * Sales Invoice: erst bei Fulfillment erzeugen, mit `date = fulfillment.createdAt`
  * `service_date` = `fulfillment.createdAt` (already wired from Phase 2)
* [ ] Handle partial fulfillments: proportionale Revenue-Buchungen je Fulfillment

**Shippable independently**: Yes. Revenue moves to the correct period. DATEV export already emits Leistungsdatum from Phase 2. The AG/SR Anzahlungsfelder are not yet set — the Steuerberater can still process the export, just without the automatic Anzahlungsauflösung in DATEV.

***

### Phase 4: DATEV Anzahlungsfelder — AG/SR for cross-month bookings (klein)

Enrich the DATEV export with Buchungstyp AG/SR and Anzahlungsfelder so DATEV can automatically handle prepayment reclassification and VAT correction across periods.

**Depends on**: Phase 1 (bu\_schluessel column) + Phase 3 (revenue timing shift)

**DATEV Export — Änderungen in `entry-transformer.ts`:**

* [ ] `Auftragsnummer` (#95) mit Shopify Order-Nr. befüllen
* [ ] `Buchungstyp` (#96) setzen — nur wenn Zahlung vor Fulfillment UND unterschiedliche Kalendermonate:
  * `"AG"` für Payment-Buchungen (Erhaltene Anzahlung)
  * `"SR"` für Revenue-Buchungen (Schlussrechnung)
* [ ] `USt-Schlüssel (Anzahlungen)` (#97) auf AG-Buchungen:
  * Primär: `sr_entry.bu_schluessel` (direkt aus DB-Spalte, dank Phase 1)
  * Fallback: `deriveBuSchluessel(order.taxRate, country, buConfig)`
* [ ] `Erlöskonto (Anzahlungen)` (#101) auf AG-Buchungen:
  * Primär: `sr_entry.credit_account.account_nr` (Gegenkonto der SR)
  * Fallback: Erlöskonto aus `buConfig.taxRateMappings`

**UI:**

* [ ] Hinweis-Banner: "Steuerberater muss Anzahlungsfunktion in DATEV einmalig konfigurieren"

**Shippable independently**: Yes, but only meaningful when Phase 3 is active (otherwise payment and revenue are always on the same date → no cross-month scenario). Can be shipped together with Phase 3 or as a fast follow-up.

***

## 10. Was sich NICHT ändert (Abgrenzung)

| Bereich                           | Status                                           |
| --------------------------------- | ------------------------------------------------ |
| Kontenrahmen / Chart of Accounts  | Keine neuen Konten nötig                         |
| Posting Matrix (Debit/Credit)     | Unverändert — Kontenfindung weiterhin über Rules |
| KOST1/KOST2/Kost-Menge Dimensions | Unverändert — bleiben im `dimensions`-JSON       |
| Reconciliation Engine             | Unverändert (GID-basiert auf Sammeldebitor)      |
| `getReferenceDocumentNumber()`    | Unverändert                                      |
| Auflösungsbuchungen in CONA       | Nicht nötig (DATEV übernimmt)                    |
| Konto 1718 in CONA                | Nicht nötig (DATEV bucht intern um)              |

***

## 11. Risks & Mitigations

| Risk                                                         | Impact                                                | Mitigation                                        |
| ------------------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------- |
| Steuerberater konfiguriert Anzahlungsfunktion nicht in DATEV | Buchungstyp AG/SR wird ignoriert, USt stimmt nicht    | Doku + Onboarding-Checkliste, Hinweis in UI       |
| Shopify liefert kein Fulfillment                             | Revenue bleibt als offene Forderung auf Sammeldebitor | Alert nach X Tagen ohne Fulfillment               |
| Fulfillment-Datum in geschlossener Periode                   | Falsche Periodenzuordnung                             | Buchung in nächste offene Periode + Warnung       |
| Split Payments + Partial Fulfillments                        | Komplexe proportionale Berechnung                     | Umfangreiche Tests                                |
| CONA-Bilanz zeigt keine "Erhaltenen Anzahlungen"             | Interne Reports nicht 100% bilanzkonform              | Akzeptiert — DATEV ist führend                    |
| Breaking Change für bestehende Kunden                        | Falsche historische Daten                             | Feature-Flag, opt-in, keine rückwirkende Änderung |

***

## 12. Resolved Questions

1. **Fulfillment-Datum**: `fulfillment.createdAt` (Versanddatum) wird verwendet. Nicht `deliveryStatus === "delivered"`, da die meisten Shops kein Carrier-Tracking haben.

2. **Multi-Currency**: Es wird der FX-Kurs vom jeweiligen Buchungstag genommen. Für die Payment-Buchung der Kurs vom Zahlungstag, für die Revenue-Buchung der Kurs vom Fulfillment-Tag. Daraus entstehende Währungsdifferenzen werden wie gewohnt ausgebucht.

3. **Accounting Period Auto-Creation**: Ja, Buchungsperioden werden automatisch erstellt wie bei allen anderen Vorgängen auch.

4. **Consolidated Entries + AG/SR**: Bereits gelöst durch die bestehende Konsolidierungslogik. In `consolidate-entries.ts` werden Reconciliation Groups, die mehrere Perioden umspannen, automatisch als "problematic" markiert und von der Konsolidierung ausgeschlossen. Da bei monatsübergreifenden Vorgängen Payment (März) und Revenue (April) über den gleichen GID verknüpft sind, erkennt die Engine die periodenübergreifende Recon Group und exportiert die Einträge einzeln — genau das Verhalten, das wir für AG/SR brauchen. Keine Code-Änderung nötig.

5. **DATEV Anzahlungs-Verhalten bei Konsolidierung**: Siehe #4 — monatsübergreifende Buchungen werden bereits nicht konsolidiert. Bei Same-Month-Vorgängen (kein AG/SR nötig) können Buchungen weiterhin konsolidiert werden, da dort kein Buchungstyp gesetzt wird.
