# Produktänderung führt zu neuer Modellversion

> Eine Änderung am Produktstamm im ERP benachrichtigt die verantwortliche Person, die in BatteryPass eine neue Modellversion anlegt; deklarierte Daten werden nie automatisch geschrieben.

Ändert die Entwicklung eine Batterie (ein anderer Zelllieferant, eine neue Kapazität, eine andere Masse), ändert
sich der Produktstamm im ERP, und der Batteriepass braucht eine neue **Modellversion** mit eigenem Gültigkeitsdatum.
Diese Änderung betrifft deklarierte Daten mit rechtlichem Gewicht. In diesem Ablauf **wird in BatteryPass nichts
automatisch geschrieben**: Die Middleware bemerkt die Änderung und sagt einer Person Bescheid, die die neue Version
im Portal anlegt und ihre Werte freigeben lässt. Schlüssel können keine deklarierten Daten schreiben; das Muster
ist also zugleich das, was BatteryPass durchsetzt.

```mermaid
sequenceDiagram
  participant ERP
  participant MW as Middleware
  participant BP as BatteryPass-API
  participant P as Verantwortliche Person
  participant M as Mitglied im Portal
  ERP->>MW: dünnes Ereignis: Produktnummer, Art der Änderung
  MW->>ERP: geändertes Produkt lesen, Ereignis prüfen
  MW->>MW: Passdaten betroffen? sonst verwerfen
  MW->>BP: listBatteryModels (Scope read)
  BP-->>MW: das Modell nach GTIN oder Modellkennung
  MW->>P: Ticket oder Chatnachricht mit Modell und Änderung
  P->>M: entscheidet über eine neue Modellversion
  M->>BP: Version anlegen, Werte erfassen, freigeben lassen
  M->>BP: Auswirkung auf bestehende Einheiten prüfen
```

**Auslöser:** Das ERP meldet eine Änderung am Produkt- oder Materialstamm, etwa ein Business Event oder einen Webhook.

**Scopes des Schlüssels:** read

| # | Wer                | Was                                                                                                                                                                              | Operation                                                                                                             |
| - | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| 1 | ERP                | Sendet ein dünnes Ereignis mit Produktnummer und Art der Änderung.                                                                                                               |                                                                                                                       |
| 2 | Middleware         | Liest das geänderte Produkt aus dem ERP und entscheidet, ob Passdaten betroffen sind (etwa Zellchemie, Kapazität, Masse); Preis- oder Textänderungen verwirft sie.               |                                                                                                                       |
| 3 | Middleware         | Findet das Batteriemodell über GTIN oder Modellkennung.                                                                                                                          | [`listBatteryModels`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-battery-models.md) (Scope: read) |
| 4 | Middleware         | Benachrichtigt die verantwortliche Person mit Modell und Änderung, etwa per Ticket oder Chat.                                                                                    |                                                                                                                       |
| 5 | Mitglied im Portal | Legt im Portal eine neue Modellversion mit Gültigkeitsdatum an, erfasst die geänderten Werte und lässt sie freigeben; BatteryPass zeigt vorher, welche Einheiten betroffen sind. |                                                                                                                       |

**Sicherheit**

- Die Middleware braucht nur den Scope read; Schlüssel können deklarierte Daten nicht schreiben.
- Webhooks des ERP prüft die Middleware nach dem Verfahren des Herstellers (Signatur, Validierungs-Handshake oder geheime URL).

**Datenminimierung**

- Die Benachrichtigung nennt Modell und geänderte Merkmale, keine Lieferanten- oder Preisdaten.

## Der Auslöser

ERP-Systeme melden Produktänderungen auf verschiedene Weise; die [ERP-Seiten](https://passport.kabin.sh/docs/de/integrations/erp/sap-s4hana-cloud)
nennen, was jedes System anbietet. Beispiele: Business Events von SAP S/4HANA über SAP Event Mesh,
Webhook-Abonnements von Business Central, Business Events von Dynamics 365 Finance und Supply Chain Management,
Automatisierungsregeln in Odoo. Die meisten sind dünn: Sie sagen, _dass_ sich etwas geändert hat, nicht _wie_. Die
Middleware

1. prüft das Ereignis so, wie der Hersteller es vorsieht (eine Signatur, ein Validierungs-Handshake, ein
   Client-State oder eine geheime URL), und verwirft, was sie nicht prüfen kann;
2. liest das aktuelle Produkt über die API des ERP;
3. vergleicht die Merkmale, die für den Pass zählen (etwa Zellchemie, Nennkapazität oder -energie, Masse, Zell- oder
   Modullieferant), mit dem zuletzt gesehenen Stand und verwirft Änderungen an Preisen, Texten oder Logistikdaten.

Ohne Ereignisse vergleichen Sie einen nächtlichen Produktexport mit dem vorigen: Das Ergebnis ist dasselbe.

## Das Batteriemodell finden

[`listBatteryModels`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-battery-models) liefert Ihre Modelle mit `gtin` und `sku`
(der Modellkennung). Einen Filter nach GTIN gibt es nicht: Blättern Sie einmal durch die Modelle und halten Sie in
der Middleware eine Zuordnung.

```bash title="cURL"
curl -sS "$BATTERYPASS_URL/api/v1/battery-models?limit=200" \
  -H "Authorization: Bearer $BATTERYPASS_READ_KEY" | jq '.items[] | select(.gtin == "04012345000023") | {id, name, sku}'
```

```ts title="TypeScript"
const BASE = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_READ_KEY}` };

type Model = { id: string; name: string; gtin?: string; sku?: string };

export async function modelsByGtin(): Promise<Map<string, Model>> {
  const out = new Map<string, Model>();
  let cursor: string | null = null;
  do {
    const query = new URLSearchParams({ limit: '200', ...(cursor ? { cursor } : {}) });
    const res = await fetch(`${BASE}/battery-models?${query}`, { headers });
    if (!res.ok) throw new Error(`listBatteryModels: ${res.status} ${(await res.json()).code}`);
    const page: { items: Model[]; next_cursor: string | null } = await res.json();
    for (const m of page.items) if (m.gtin) out.set(m.gtin, m);
    cursor = page.next_cursor;
  } while (cursor);
  return out;
}

// Platzhalter: ein Ticket für die verantwortliche Person anlegen. Hier nie in BatteryPass schreiben.
export function notify(model: Model, changed: string[]) {
  console.log(`Neue Modellversion nötig für ${model.name} (${model.id}): im ERP geändert: ${changed.join(', ')}`);
}
```

```python title="Python"
import os

import requests

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


def models_by_gtin():
    out, cursor = {}, None
    while True:
        params = {"limit": 200, **({"cursor": cursor} if cursor else {})}
        res = session.get(f"{BASE}/battery-models", params=params, timeout=30)
        res.raise_for_status()
        page = res.json()
        out.update({m["gtin"]: m for m in page["items"] if m.get("gtin")})
        cursor = page["next_cursor"]
        if not cursor:
            return out


def notify(model, changed):
    """Platzhalter: ein Ticket für die verantwortliche Person anlegen. Hier nie in BatteryPass schreiben."""
    print(f"Neue Modellversion nötig für {model['name']} ({model['id']}): im ERP geändert: {', '.join(changed)}")
```

## Die Person entscheidet

Die Benachrichtigung nennt das Modell, was sich im ERP geändert hat und ab welchem Tag es gilt. Im Portal

1. legt ein Mitglied eine neue **Modellversion** mit Gültigkeitsdatum an, meist mit den Werten der aktuellen Version
   als Ausgangspunkt;
2. erfasst die geänderten Werte mit ihren Nachweisen und lässt sie freigeben (das Vier-Augen-Prinzip gilt, wo Ihre
   Organisation es eingeschaltet hat);
3. prüft die **Auswirkung der Änderung**: welche bestehenden Einheiten nach ihrem Herstelldatum zur neuen Version
   wechseln würden und welche veröffentlichten Pässe dann eine neue Version brauchen;
4. gibt die Modellversion frei.

Der [Änderungsfeed](https://passport.kabin.sh/docs/de/integrations/workflows/passport-links-to-erp) meldet `model_version.approved` und
später `passport.republished` für Einheiten, deren Pässe erneut veröffentlicht wurden; das ERP kann so folgen, ohne
nachzufragen.

## Was nie fließt

Die Benachrichtigung trägt das Modell und die geänderten Merkmale. Lieferantennamen und -konditionen, Preise und die
Personen, die die Änderung vorgenommen haben, bleiben im ERP und im Ticketsystem.

## Fehlerbehandlung

| Antwort                             | Bedeutung                                               | Was tun                                                               |
| ----------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- |
| `400 validation_error` auf `cursor` | Ein veralteter Cursor                                   | Die Liste neu beginnen.                                               |
| `401 unauthenticated`               | Schlüssel fehlt, ist unbekannt oder widerrufen          | Geheimnis prüfen; Alarm auslösen.                                     |
| `403 forbidden`                     | Dem Schlüssel fehlt der Scope `read`                    | Den Lese-Schlüssel verwenden.                                         |
| Kein Modell mit der GTIN            | Das Produkt ist noch kein Batteriemodell in BatteryPass | Trotzdem benachrichtigen: Das Modell muss vielleicht angelegt werden. |
| `429 rate_limited`                  | Zu viele Anfragen                                       | `Retry-After` Sekunden warten.                                        |
| `5xx`, Zeitüberschreitung           | Ergebnis unbekannt                                      | Mit Backoff wiederholen; Lesen lässt sich gefahrlos wiederholen.      |

Ein ERP-Ereignis, das zweimal ankommt, ergibt höchstens zwei Benachrichtigungen; fassen Sie sie im Ticketsystem nach
Produktnummer und Änderungsdatum zusammen.
