Skip to main content

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).
AspectRegulationImplication
Revenue recognitionHGB § 252 Abs. 1 Nr. 4Revenue at Gefahrenübergang (delivery)
B2C risk transferBGB § 475 Abs. 2At delivery to consumer (not shipment to carrier)
VAT on prepaymentsUStG § 13 Abs. 1 Nr. 1a S. 4VAT due when prepayment is received
Timely bookingGoBD Tz. 50-52Record within 10 days, period-correct
Service date on invoicesUStDV § 31 Abs. 4Leistungsdatum 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 AUSFeature AN
USt März-UStVA190 € (aus Erlösbuchung)190 € (DATEV extrahiert USt aus AG-Buchung automatisch)
USt April-UStVA190 € Erlös-USt abzgl. 190 € Anzahlungs-USt-Korrektur = 0 € Netto-Änderung
Erlös März1.000 €
Erlös April1.000 €
Sammeldebitor OPOSAusgeziffert (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?

SituationBuchungstyp AG/SR?Leistungsdatum?
Zahlung + Versand im selben MonatNein (nicht nötig, USt stimmt ohnehin)Ja
Zahlung + Versand am selben TagNeinJa
Zahlung nach Versand (Kauf auf Rechnung)Nein (keine Anzahlung)Ja
Zahlung vor Versand, unterschiedliche MonateJa (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 AccountsNeinKein 1718 Konto in CONA nötig
Posting Matrix (Debit/Credit)NeinGleiche Kontenkombinationen
Posting Matrix (BU)Ja — VereinfachungBU-Matrix wird durch einfache Mapping-Tabelle ersetzt (s. Section 6)
general_ledger-SchemaJaNeue Spalte bu_schluessel + service_date
Reconciliation EngineNeinGID-basiert auf Sammeldebitor wie bisher
getReferenceDocumentNumber()NeinKein Cross-Account-Lookup nötig
createAccountingImpact / createJournalEntryMinimalBU automatisch setzen + service_date hinzufügen
AuflösungsbuchungenNeinDATEV 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-FeldFormatBeschreibung
95Auftragsnummer0-30 ZeichenVerknüpft alle Buchungen eines Vorgangs
96Buchungstyp2 BuchstabenSteuert die Anzahlungsautomatik
97USt-Schlüssel (Anzahlungen)0-2 ZiffernUSt-Schlüssel der späteren Schlussrechnung
98EU-Mitgliedstaat (Anzahlungen)2 BuchstabenEU-Land der Schlussrechnung
99Sachverhalt L+L (Anzahlungen)0-3 ZiffernL+L-Sachverhalt der Schlussrechnung
100EU-Steuersatz (Anzahlungen)ZiffernEU-Steuersatz der Schlussrechnung
101Erlöskonto (Anzahlungen)4-8 ZiffernErlöskonto der Schlussrechnung
115LeistungsdatumTTMMJJJJLieferdatum / Leistungsdatum
116Datum Zuord. SteuerperiodeTTMMJJJJZuordnung zur USt-Periode

5.2 Buchungstyp-Werte

CodeBedeutungWann wir ihn setzen
AGErhaltene Anzahlung (Geldeingang)Payment-Buchung, wenn Zahlung VOR Fulfillment
SRSchlussrechnungRevenue-Buchung bei Fulfillment
SGSchlussrechnung (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-FeldWertBemerkung
Umsatz1190,00Bruttobetrag
Soll/HabenS
Konto1200Bank
Gegenkonto1400Sammeldebitor
Belegdatum2803Zahlungsdatum
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.)9NEU — BU 9 = 19% Verkauf (aus SR-Buchung, s. 5.4)
Erlöskonto (Anz.)8400NEU — 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-FeldWertBemerkung
Umsatz1190,00Bruttobetrag
Soll/HabenS
Konto1400Sammeldebitor
Gegenkonto8400Erlöse 19% USt (Automatikkonto)
BU-Schlüssel9BU 9 = 19% Verkauf
Belegdatum0204Fulfillment-Datum
Belegfeld 1"SHP-98765"Gleiche GID
Buchungstext"Sales Invoice INV-1042"
Auftragsnummer"1042"NEU — Gleiche Order-Nr.
Buchungstyp"SR"NEU — Schlussrechnung
Leistungsdatum02042026NEU — Versanddatum
Datum Zuord. Steuerperiode02042026NEU — 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):
SteuersatzBU-SchlüsselErlöskontoKontobezeichnung
19%98400Erlöse 19% USt (AM → bucht automatisch auf 1776)
7%148300Erlöse 7% USt (AM → bucht automatisch auf 1771)
0% (steuerfrei)28120Steuerfreie Umsätze §4
0% (ig. Lieferung)38125Steuerfreie 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:
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.tsBU-Dimension überspringen (nicht mehr als GL-Dimension evaluieren)
generateDimensionKey()bu_schluessel-Spalte statt dimensions-JSON für BU
DATEV Entry-Transformerentry.bu_schluessel direkt lesen statt Dimension-Mapping
DATEV Consolidated-TransformerGleiche Vereinfachung
Posting-Matrix-Seed”Tax Code (BU)” Matrix nicht mehr erstellen
datevDimensionType"BU" entfernen (KOST1/KOST2/Kost-Menge bleiben)
DATEV-Settings-UINeuer “BU-Schlüssel” Tab mit Mapping-Tabelle statt Dimension-Mapping
MigrationBestehende 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)

// 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.

Phase 1: BU-Schlüssel Foundation (mittel, s. Section 6)

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)

BereichStatus
Kontenrahmen / Chart of AccountsKeine neuen Konten nötig
Posting Matrix (Debit/Credit)Unverändert — Kontenfindung weiterhin über Rules
KOST1/KOST2/Kost-Menge DimensionsUnverändert — bleiben im dimensions-JSON
Reconciliation EngineUnverändert (GID-basiert auf Sammeldebitor)
getReferenceDocumentNumber()Unverändert
Auflösungsbuchungen in CONANicht nötig (DATEV übernimmt)
Konto 1718 in CONANicht nötig (DATEV bucht intern um)

11. Risks & Mitigations

RiskImpactMitigation
Steuerberater konfiguriert Anzahlungsfunktion nicht in DATEVBuchungstyp AG/SR wird ignoriert, USt stimmt nichtDoku + Onboarding-Checkliste, Hinweis in UI
Shopify liefert kein FulfillmentRevenue bleibt als offene Forderung auf SammeldebitorAlert nach X Tagen ohne Fulfillment
Fulfillment-Datum in geschlossener PeriodeFalsche PeriodenzuordnungBuchung in nächste offene Periode + Warnung
Split Payments + Partial FulfillmentsKomplexe proportionale BerechnungUmfangreiche Tests
CONA-Bilanz zeigt keine “Erhaltenen Anzahlungen”Interne Reports nicht 100% bilanzkonformAkzeptiert — DATEV ist führend
Breaking Change für bestehende KundenFalsche historische DatenFeature-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.