# Rücksendungen und Reparaturen als Lebenszyklus-Ereignisse

> Abgeschlossene Service-, Reparatur- und Retourenaufträge im ERP werden Lebenszyklus-Ereignisse der Einheit, ohne Kundendaten und ohne Duplikate.

Kommt eine Batterie zur Prüfung, Reparatur oder für einen Komponententausch zurück, schließt das ERP oder sein
Servicemodul einen Auftrag ab. In diesem Ablauf macht die Middleware aus dem abgeschlossenen Auftrag ein
Lebenszyklus-Ereignis der Einheit, mit
[`createLifecycleEvent`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/create-lifecycle-event). Ereignisse sind Aufzeichnungen:
Sie erscheinen in der Historie der Einheit und ändern keine Passdaten. Wie Ereignisse funktionieren, beschreibt
[Lebenszyklus-Ereignisse erfassen](https://passport.kabin.sh/docs/de/guides/record-lifecycle-events).

```mermaid
sequenceDiagram
  participant ERP
  participant MW as Middleware
  participant BP as BatteryPass-API
  participant M as Mitglied im Portal
  ERP->>ERP: Auftrag abschließen, Outbox-Zeile schreiben
  MW->>ERP: Outbox-Zeile lesen (Seriennummer, Art der Arbeit, Datum)
  MW->>MW: Auftrag schon gemeldet? Dann aufhören
  MW->>BP: listBatteries mit serial_prefix (Scope read)
  MW->>BP: createLifecycleEvent (Scope condition)
  BP-->>MW: 201 Ereignis
  MW->>MW: Auftrag und Ereignis-ID merken
  M->>BP: Historie prüfen, bei Bedarf ein zweites Leben anlegen
```

**Auslöser:** Ein Service-, Reparatur- oder Retourenauftrag wird im ERP abgeschlossen.

**Scopes des Schlüssels:** read, condition

| # | Wer                | Was                                                                                                                    | Operation                                                                                                                          |
| - | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| 1 | ERP                | Schließt den Auftrag mit Seriennummer und Art der Arbeit ab und hält die Änderung in der Outbox fest.                  |                                                                                                                                    |
| 2 | Middleware         | Findet die Einheit über ihre Seriennummer.                                                                             | [`listBatteries`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-batteries.md) (Scope: read)                       |
| 3 | Middleware         | Legt das Ereignis an (zum Beispiel repair, inspection oder component_replaced) mit Datum und kurzer Beschreibung.      | [`createLifecycleEvent`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/create-lifecycle-event.md) (Scope: condition) |
| 4 | Mitglied im Portal | Prüft die Historie im Portal; ein zweites Leben (Umwidmung, Wiederaufbereitung) legt ein Mitglied als neue Einheit an. |                                                                                                                                    |

**Sicherheit**

- Ereignisse sind Aufzeichnungen und ändern keine Passdaten; der Schlüssel braucht nur condition und read.
- Die Middleware merkt sich die Auftragsnummer, für die sie ein Ereignis angelegt hat, und legt es kein zweites Mal an.

**Datenminimierung**

- Keine Kundennamen, Adressen, Auftragsnummern oder Fehlerbeschreibungen mit Personenbezug in der Beschreibung; die Seriennummer verbindet Ereignis und Auftrag.

## Auftrag einem Ereignistyp zuordnen

| ERP-Auftrag                                   | `type`               |
| --------------------------------------------- | -------------------- |
| Prüfung oder Diagnose ohne Arbeit             | `inspection`         |
| Reparatur                                     | `repair`             |
| Ein Modul, eine Zelle oder ein Teil getauscht | `component_replaced` |
| Batterie zur Entsorgung zurückgenommen        | `waste`              |
| Batterie recycelt                             | `recycled`           |
| Alles andere, das festgehalten werden soll    | `custom`             |

Im Service gemessene Zustandswerte gehören nicht ins Ereignis: Melden Sie sie mit
[`updateCondition`](https://passport.kabin.sh/docs/de/integrations/workflows/condition-from-bms), das sein eigenes Ereignis
`condition_update` anlegt. Eine umgewidmete oder wiederaufbereitete Batterie bekommt eine neue Einheit mit eigenem
Pass, die ein Mitglied im Portal anlegt (zweites Leben).

## Ereignis anlegen

`occurred_on` ist der Tag der Arbeit, zwischen Herstelldatum und heute. `description` nimmt höchstens 2 000 Zeichen;
`evidence_ids` kann bis zu 20 Dokumente Ihrer Nachweisbibliothek nennen, etwa ein Reparaturprotokoll.

```bash title="cURL"
curl -sS -X POST "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/lifecycle-events" \
  -H "Authorization: Bearer $BATTERYPASS_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "component_replaced", "occurred_on": "2026-10-21", "description": "Modul 3 getauscht (Servicecode R-12)"}'
```

```ts title="TypeScript"
const BASE = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_SERVICE_KEY}`, 'Content-Type': 'application/json' };

// reported: Ihre eigene Tabelle schon gesendeter Aufträge (Auftragsnummer → Ereignis-ID).
export async function recordOrder(order: { number: string; batteryId: string; type: string; doneOn: string; code: string },
  reported: Map<string, string>) {
  if (reported.has(order.number)) return reported.get(order.number); // nie einen Auftrag zweimal senden
  const res = await fetch(`${BASE}/batteries/${order.batteryId}/lifecycle-events`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ type: order.type, occurred_on: order.doneOn, description: `Servicecode ${order.code}` }),
  });
  const body = await res.json();
  if (res.status !== 201) throw new Error(`createLifecycleEvent: ${res.status} ${body.code} (request_id ${body.request_id})`);
  reported.set(order.number, body.id);
  return body.id;
}
```

```python title="Python"
import os

import requests

BASE = os.environ["BATTERYPASS_URL"] + "/api/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["BATTERYPASS_SERVICE_KEY"]


def record_order(order, reported):
    """reported: Ihre eigene Tabelle schon gesendeter Aufträge (Auftragsnummer -> Ereignis-ID)."""
    if order["number"] in reported:
        return reported[order["number"]]  # nie einen Auftrag zweimal senden
    res = session.post(
        f"{BASE}/batteries/{order['battery_id']}/lifecycle-events",
        json={"type": order["type"], "occurred_on": order["done_on"], "description": f"Servicecode {order['code']}"},
        timeout=30,
    )
    body = res.json()
    if res.status_code != 201:
        raise SystemExit(f"createLifecycleEvent: {res.status_code} {body['code']} (request_id {body.get('request_id')})")
    reported[order["number"]] = body["id"]
    return body["id"]
```

Das Ereignis hat die Quelle `api` und nennt den Schlüssel in `api_key`; das Audit-Log nennt den Schlüssel als
Handelnden.

## Keine Duplikate

`createLifecycleEvent` ist nicht idempotent: Jeder erfolgreiche Aufruf legt ein neues Ereignis an. Führen Sie eine
Tabelle der gemeldeten Aufträge (Auftragsnummer, Ereignis-ID in BatteryPass). Sehen Sie nach einer
Zeitüberschreitung in [`listLifecycleEvents`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/list-lifecycle-events) der Einheit
nach (dafür braucht es `read`), bevor Sie erneut senden: Ein Ereignis desselben Typs und Tages mit Ihrem Servicecode
heißt, dass der erste Aufruf durchging.

## Was nie fließt

Keine Namen, Adressen oder Kontaktdaten von Kundinnen und Kunden, keine Auftrags- oder Rechnungsnummern, keine
Namen von Technikern und keine Fehlerbeschreibungen, die Personen erkennen lassen. Die Seriennummer verbindet das
Ereignis mit dem Auftrag in Ihrem ERP. Bauen Sie die `description` wie oben aus festen Codes, nicht aus dem
Freitext des Auftrags.

## Fehlerbehandlung

| Antwort                   | Bedeutung                                                                                                                                                                   | Was tun                                               |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `400 validation_error`    | Unbekannter `type` (auch `condition_update`, das nur `updateCondition` anlegt), `occurred_on` vor der Herstellung oder in der Zukunft, ein Dokument, das nicht Ihnen gehört | Zuordnung korrigieren; nicht unverändert wiederholen. |
| `401 unauthenticated`     | Schlüssel fehlt, ist unbekannt oder widerrufen                                                                                                                              | Geheimnis prüfen; Alarm auslösen.                     |
| `403 forbidden`           | Dem Schlüssel fehlt der Scope `condition`                                                                                                                                   | Den Service-Schlüssel verwenden.                      |
| `404 not_found`           | Die Einheit gehört nicht zu Ihrer Organisation                                                                                                                              | Seriennummer prüfen.                                  |
| `409 conflict`            | Das Modell der Einheit ist archiviert                                                                                                                                       | Ereignis im Portal erfassen oder gar nicht.           |
| `429 rate_limited`        | Zu viele Anfragen                                                                                                                                                           | `Retry-After` Sekunden warten.                        |
| `5xx`, Zeitüberschreitung | Ergebnis unbekannt                                                                                                                                                          | Erst `listLifecycleEvents` prüfen, dann wiederholen.  |
