# ED-04-002 – SourceDelivery Persistence

**Status:** Final

**Related**

- ED-04_SourceDelivery_Aggregate.md
- ED-04-001_SourceDelivery_Domain_Object.md
- ADR002_SOURCE_DELIVERY_MODEL_V4.md
- DM001_Domain_Model_v0.7_FINAL.md
- DM002_Persistence_Model_v0.6_FINAL.md

---

# 1. Ziel

Dieses Work Package implementiert die relationale Persistenz für das `SourceDelivery`-Aggregate.

Nach Abschluss kann eine `SourceDelivery`

- vollständig gespeichert,
- anhand ihrer internen ID gelesen,
- und mit allen fachlich relevanten Attributen rehydriert

werden.

Die Persistenz bildet das in DM001 definierte Domain Object unverändert ab.

---

# 2. Scope

Implementiert werden

- die Tabelle `source_delivery`,
- die erforderlichen Spalten und Constraints,
- die Migration,
- der Data Mapper für `SourceDelivery`,
- das Speichern und Rehydrieren aller Attribute.

Nicht Bestandteil dieses Work Packages sind

- die Erzeugung einer `SourceDelivery` aus einem OpenRefine-Request,
- die Zuordnung von `SourceValue`,
- die Repository-Integration,
- der End-to-End-Test.

Diese Aufgaben werden in ED-04-003 bis ED-04-005 umgesetzt.

---

# 3. Persistenzmodell

Für jede fachliche Lieferung wird genau ein Datensatz in

```text
source_delivery
```

gespeichert.

## 3.1 Tabellenstruktur

| Spalte | Typ | Null erlaubt | Bedeutung |
|---|---|---:|---|
| `id` | `VARCHAR(36)` | nein | Interne stabile Identität |
| `tenant_id` | `VARCHAR(255)` | nein | Tenant der Lieferung |
| `source_system_id` | `VARCHAR(36)` | nein | Referenz auf das fachliche Ursprungssystem |
| `delivery_channel` | `VARCHAR(255)` | nein | Kontrollierter technischer Übertragungsweg |
| `external_delivery_id` | `VARCHAR(255)` | ja | Optionale externe Liefer- oder Prozesskennung |
| `status` | `VARCHAR(32)` | nein | Bearbeitungsstatus |
| `received_at` | `DATETIME(6)` | nein | Annahmezeitpunkt |
| `completed_at` | `DATETIME(6)` | ja | Abschlusszeitpunkt |

---

# 4. SQL-Zielstruktur

Die Migration legt mindestens folgende Struktur an:

```sql
CREATE TABLE source_delivery (
    id VARCHAR(36) NOT NULL,
    tenant_id VARCHAR(255) NOT NULL,
    source_system_id VARCHAR(36) NOT NULL,
    delivery_channel VARCHAR(255) NOT NULL,
    external_delivery_id VARCHAR(255) NULL,
    status VARCHAR(32) NOT NULL,
    received_at DATETIME(6) NOT NULL,
    completed_at DATETIME(6) NULL,

    PRIMARY KEY (id),

    CONSTRAINT fk_source_delivery_source_system
        FOREIGN KEY (source_system_id)
        REFERENCES source_system (id)
);
```

Die konkrete Benennung der Constraints folgt den bestehenden Projektkonventionen.

---

# 5. Persistenzregeln

Für die Persistenz gelten folgende Regeln:

- `id` ist verpflichtend und eindeutig.
- `tenant_id` ist verpflichtend.
- `source_system_id` ist verpflichtend.
- `delivery_channel` ist verpflichtend.
- `status` ist verpflichtend.
- `received_at` ist verpflichtend.
- `external_delivery_id` ist optional.
- `completed_at` ist optional.
- Bei `status = COMPLETED` muss `completed_at` gesetzt sein.
- Ein fehlendes `external_delivery_id` wird als `NULL` gespeichert.

---

# 6. External Delivery ID

`externalDeliveryId` wird in der ersten Implementierungsphase als optionaler String persistiert.

Für OpenRefine-Requests gilt:

```text
Request-Parameter delivery_id
→ SourceDelivery.externalDeliveryId
→ source_delivery.external_delivery_id
```

Beispiel:

```text
delivery_id = OR-TEST-20260725-001
```

wird gespeichert als:

```text
external_delivery_id = OR-TEST-20260725-001
```

Fehlt der Request-Parameter, wird

```text
external_delivery_id = NULL
```

persistiert.

Eine spätere Typisierung als eigenes Value Object bleibt möglich, falls fachliche Validierungsregeln entstehen.

---

# 7. Delivery Channel

`DeliveryChannel` wird als kontrollierter Stringwert gespeichert.

Beispiel:

```text
OPENREFINE_RECONCILIATION_API
```

Es wird keine eigene Tabelle für `DeliveryChannel` angelegt.

Die Validierung zulässiger Werte erfolgt im Domain Layer.

---

# 8. SourceDelivery Status

`SourceDeliveryStatus` wird als kontrollierter Stringwert gespeichert.

Zulässige Werte für ED-04 sind:

```text
RECEIVED
PROCESSING
COMPLETED
FAILED
```

Es wird keine eigene Statustabelle angelegt.

Die Validierung zulässiger Werte und Zustandsübergänge erfolgt im Domain Layer.

---

# 9. Data Mapper

Der `SourceDeliveryMapper` übernimmt ausschließlich die Abbildung zwischen Domain Object und relationaler Darstellung.

Er stellt mindestens folgende Operationen bereit:

```php
insert(SourceDelivery $sourceDelivery): void
```

und

```php
mapRowToDomain(array $row): SourceDelivery
```

Der Mapper

- enthält keine Geschäftslogik,
- erzeugt keine neuen fachlichen Zustände,
- verändert keine Statuswerte,
- validiert keine Zustandsübergänge.

---

# 10. Rehydration

Beim Lesen wird die persistierte Darstellung vollständig in ein bestehendes Domain Object überführt.

Dabei werden insbesondere

- die bestehende interne ID,
- `receivedAt`,
- `completedAt`,
- der aktuelle Status,
- `deliveryChannel`,
- und `externalDeliveryId`

unverändert übernommen.

Die Rehydration darf nicht die Factory `SourceDelivery::create()` verwenden, da diese eine neue Lieferung mit neuem Anfangszustand erzeugt.

Dafür ist ein separater Rehydration-Pfad vorzusehen, beispielsweise:

```php
SourceDelivery::rehydrate(...)
```

oder ein äquivalenter interner Konstruktor gemäß den bestehenden Projektkonventionen.

---

# 11. Indizes

Folgende Indizes werden angelegt:

```text
source_system_id
tenant_id
external_delivery_id
```

Für die Kombination aus Tenant und externer Lieferkennung ist zunächst kein Unique Constraint vorgesehen.

Der Grund ist, dass Wiederholungen, Replays und die Eindeutigkeit externer Kennungen noch nicht fachlich abschließend definiert sind.

---

# 12. Migration

Die Migration muss

- die Tabelle `source_delivery` anlegen,
- alle Pflichtspalten definieren,
- den Foreign Key auf `source_system` herstellen,
- und die erforderlichen Indizes anlegen.

Die Migration darf keine bestehenden Tabellen oder Daten verändern, die nicht unmittelbar zu ED-04 gehören.

---

# 13. Tests

Mindestens folgende Persistenztests sind erforderlich:

1. Eine vollständige `SourceDelivery` kann gespeichert werden.
2. Eine `SourceDelivery` ohne `externalDeliveryId` wird mit `NULL` gespeichert.
3. Eine `SourceDelivery` mit `externalDeliveryId` speichert den Wert unverändert.
4. `deliveryChannel` wird korrekt gespeichert.
5. `status` wird korrekt gespeichert.
6. `receivedAt` wird korrekt gespeichert und rehydriert.
7. `completedAt` bleibt bei nicht abgeschlossenen Lieferungen `NULL`.
8. `completedAt` wird bei einer abgeschlossenen Lieferung korrekt gespeichert.
9. Alle Attribute werden bei der Rehydration vollständig wiederhergestellt.

---

# 14. Akzeptanzkriterien

Das Work Package gilt als abgeschlossen, wenn

- die Migration erfolgreich ausgeführt wird,
- die Tabelle `source_delivery` den definierten Constraints entspricht,
- eine `SourceDelivery` vollständig gespeichert werden kann,
- optionale Werte korrekt als `NULL` persistiert werden,
- eine gespeicherte `SourceDelivery` vollständig rehydriert werden kann,
- keine Persistenzlogik im Domain Object implementiert ist,
- und alle Persistenztests erfolgreich sind.