# ERP-Inventar-Export (ApiCore → ERPNext) Stand: 2026-10-12 · Vertragsversion `1` · Betrifft: ApiCore-Backend (`src/erp-export/`), ERPNext-Abgleich (Teilprojekt T8) Nur lesende Schnittstelle, über die ERPNext nachts den Inventar-Spiegel holt (ERPNext-Spec `2026-10-12-erpnext-ausbau-design.md`, Abschnitt 8.3, Entscheidungen E2 und E3). ApiCore bleibt führend für Bestand, Chargen und MHD; ERPNext spiegelt nur und bucht daraus Vertrauensschrank und Eigenbedarf. Der ERPNext-Importer wird genau nach diesem Dokument gebaut. Änderungen am Vertrag gehen deshalb nur additiv (siehe [Versionierung](#10-versionierung)). ## 1. Zugang | Punkt | Festlegung | |---|---| | Verfahren | API-Schlüssel als Bearer-Token: `Authorization: Bearer ack_<8 Zeichen>_<43 Zeichen>` | | Recht | `erp-inventory:read` („Inventar-Spiegel lesen (ERPNext)“). Öffnet ausschließlich die vier Endpunkte unten. | | Schreiben | Nicht möglich. Alle Endpunkte sind `GET`; das Recht öffnet keinen anderen Endpunkt (auch nicht `/shop-sync`). | | Speicherung | ApiCore speichert nur den SHA-256-Hash. Gesucht wird über den Hash (eindeutiger Index), nie durch Vergleich des Geheimnisses. Der Schlüssel selbst wird genau einmal beim Anlegen angezeigt. | | Abschalten | Jederzeit widerrufbar (wirkt sofort), optional mit Ablaufdatum. | | Absender | Optional nur von bestimmten IP-Adressen/Netzen (Allowlist am Schlüssel). | | Fehlversuche | Nach 20 ungültigen Schlüsseln (unbekannt, widerrufen, abgelaufen) innerhalb von 15 Minuten bekommt diese IP-Adresse (bei IPv6 das ganze /64-Netz) für weitere ungültige Schlüssel 15 Minuten lang `429`; die Zählung gilt für alle ApiCore-Instanzen gemeinsam. Ein gültiger Schlüssel wird davon nie abgewiesen, auch nicht von einer gesperrten Adresse – sonst könnte jeder, der dieselbe Adresse nutzt oder vortäuscht, ERPNext oder den Shop aussperren. Nicht wie ein Schlüssel aufgebaute Tokens (`ack_…` im falschen Format) bekommen sofort `401` und zählen nicht. Zusätzlich gilt das allgemeine Limit von 100 Anfragen je Minute und IP. | | Protokoll | Abgelehnte Schlüssel werden mit Grund, Präfix (`ack_abcd1234`) und IP protokolliert, nie mit dem Schlüssel. Query-Strings landen nicht im Log. | | Trennung | Eigener Schlüssel nur für ERPNext. Der Wabenhain-Schlüssel (`shop-sync:*`) kommt an diese Endpunkte nicht heran und umgekehrt. | Benutzer mit dem Recht `erp-inventory:read` (z. B. die Rolle Administrator) können die Endpunkte mit ihrem Login ebenfalls lesen, etwa zum Prüfen. ### Schlüssel anlegen (Inhaber) 1. ApiCore → Verwaltung → Automatisierung → API-Schlüssel. 2. Name: `ERPNext`. Unter „Berechtigung“ **„ERP-Inventar lesen“** wählen (vorausgewählt ist „Shop lesen“). Ein Schlüssel trägt genau ein Recht; einen Schlüssel mit Lese- und Buchungsrecht lehnt ApiCore ab. 3. IP-Adressen: die Adresse, von der ERPNext ApiCore erreicht (siehe [Rollout](#11-rollout)). Unbekannt? Leer lassen, einmal abrufen, dann am Schlüssel „Adressen ändern“ und die zuletzt benutzte Adresse übernehmen. 4. Schlüssel kopieren und **nur** als Kubernetes-Secret für den ERPNext-Abgleich hinterlegen (nicht in `config.json`, nicht ins Repo, nicht in Tickets). ## 2. Allgemeines - Basis: `https:///api/v1/export/inventar` - Antworten: JSON (UTF-8), `Cache-Control: no-store`. - Zeitpunkte: ISO 8601 in UTC mit Millisekunden, z. B. `2026-10-01T08:15:30.123Z`. Kalendertage (Abfülldatum, MHD, Erntedatum) stehen als Mitternacht UTC, z. B. `2026-08-01T00:00:00.000Z`; der Tag ist der Datumsteil. - Kennungen (`id`, `productId`, `batchId`) sind undurchsichtige Zeichenketten (cuid), stabil über die Lebensdauer des Datensatzes. - Mengen in **Stück** (Gläser bzw. hergestellte Stück) sind ganze Zahlen; Gewichte stehen mit Einheit im Feldnamen (`fillWeightGrams`, `netWeightKg`). - Unbekannte Query-Parameter werden mit `400` abgelehnt. ### Seitenweises Lesen `produkte`, `chargen` und `bewegungen` liefern Seiten in einer gemeinsamen Hülle: | Feld | Typ | Bedeutung | |---|---|---| | `contractVersion` | number | Vertragsversion, derzeit `1` | | `generatedAt` | string (Zeitpunkt) | Zeitpunkt der Antwort | | `items` | array | Einträge dieser Seite, stabil sortiert | | `nextCursor` | string \| null | Position hinter dem letzten Eintrag. Bei leerer Seite der übergebene `nach`-Wert (oder `null`, wenn keiner übergeben wurde). | | `hasMore` | boolean | `true`, wenn hinter dieser Seite weitere Einträge liegen | | Parameter | Typ | Bedeutung | |---|---|---| | `limit` | Ganzzahl 1–500, Vorgabe 100 | Einträge je Seite | | `nach` | Cursor | `nextCursor` der vorigen Antwort, unverändert. Undurchsichtig (base64url); nur von dem Endpunkt gültig, der ihn ausgegeben hat. | Ablauf: ohne `nach` beginnen, dann mit `nach=` weiterlesen, solange `hasMore` `true` ist. **Voll-Spiegel für `produkte` und `chargen` (verbindlich):** ERPNext liest beide Listen in **jedem** Lauf vollständig neu, beginnend ohne `nach`, und speichert für sie keinen Cursor über den Lauf hinaus. Die Kennungen (cuid) sind nicht streng monoton: ein während eines Durchlaufs neu angelegter Artikel oder eine neue Charge kann in diesem Durchlauf fehlen und erscheint im nächsten. Ändert sich zwischen zwei Seiten ein Bestand, gleicht ebenfalls der nächste Lauf das aus. Eine bereits bekannte Charge kann so nicht verloren gehen: fehlt sie in einem vollständigen Durchlauf, ist sie gelöscht (siehe Abschnitt 4). **Inkrementell für `bewegungen`:** Der Importer speichert den letzten `nextCursor` und liest im nächsten Lauf dort weiter. Die Sortierung nach (`createdAt`, `id`) zusammen mit der Wartezeit (Abschnitt 5) sorgt dafür, dass keine Bewegung übersprungen wird. ## 3. `GET /produkte` – Artikel Alle ApiCore-Artikel (auch eingestellte), sortiert nach `id` aufsteigend. | Feld | Typ | Bedeutung | |---|---|---| | `id` | string | ApiCore-Produkt-ID | | `name` | string | Bezeichnung | | `sku` | string \| null | Artikelnummer in ApiCore (eindeutig, optional) | | `kind` | `BOTTLED_HONEY` \| `MANUFACTURED` | Wo der Bestand liegt: Honig je Abfüllung, andere Waren (Kerzen, Propolis …) je Herstellungscharge | | `shopCategory` | `HONIG` \| `KERZEN` \| `PROPOLIS` \| `SONSTIGES` | Kategorie/Sorte des Artikels | | `status` | `ACTIVE` \| `DISCONTINUED` | eingestellt = nicht mehr hergestellt; Restbestände können noch existieren | | `packagingSize` | number \| null | Gebindegröße, Einheit in ApiCore nicht festgelegt; Konvention **Gramm** (wie der Wabenhain-Import annimmt) | | `weight` | number \| null | Gewicht, gleiche Einheiten-Konvention | | `salesUnit` | string \| null | Verkaufseinheit, z. B. `Glas` | | `updatedAt` | string (Zeitpunkt) | letzte Änderung am Artikel | Für Honig ist die verlässliche Füllmenge `fillWeightGrams` an der Charge (siehe unten), nicht `packagingSize`. **Verknüpfung zu den Shop-Artikeln `WH-`** (Wabenhain kennt die ApiCore-IDs): - Honig: Wabenhain führt ein Shop-Produkt je Abfüllung; `Product.apicoreBottlingId` = `chargen[].id` mit `batchType = BOTTLING`. Der ApiCore-Artikel dazu steht in `chargen[].productId`. - Andere Waren: `Product.apicoreProductId` = `produkte[].id` (Artikel mit `kind = MANUFACTURED`). ## 4. `GET /chargen` – Chargen mit Rest Erst alle Abfüllungen (`BOTTLING`), dann alle Herstellungschargen (`PRODUCTION`), jeweils nach `id` aufsteigend. Rohstoff-Chargen (Wachs, Propolis) sind keine Ware und fehlen bewusst. | Feld | Typ | Bedeutung | |---|---|---| | `id` | string | ApiCore-ID der Charge | | `batchType` | `BOTTLING` \| `PRODUCTION` | Abfüllung (Honig) oder Herstellungscharge | | `lotNumber` | string | Losnummer (MHD-Etikett). Eindeutig **je Art**; über beide Arten hinweg nicht garantiert. Eindeutiger Schlüssel ist (`batchType`, `id`). | | `productId` | string \| null | ApiCore-Artikel. Bei Abfüllungen optional (`null` = keinem Artikel zugeordnet). | | `date` | string (Kalendertag) | Abfüll- bzw. Herstelldatum | | `bestBeforeDate` | string (Kalendertag) \| null | MHD; bei Abfüllungen immer gesetzt, bei Herstellungschargen optional | | `initialQuantity` | Ganzzahl (Stück) | abgefüllte Gläser bzw. hergestellte Stück | | `remainingQuantity` | Ganzzahl (Stück) | Restbestand = `initialQuantity` + Summe aller Bewegungen. Kann negativ sein (Shop hat mehr verkauft als gebucht; in ApiCore rot „Prüfen“). | | `fillWeightGrams` | number \| null | Füllmenge je Glas in Gramm; nur `BOTTLING` | | `honeyType` | string \| null | Sorte der Honigcharge hinter der Abfüllung (z. B. `Sommertracht`); nur `BOTTLING`, `null` wenn nicht erfasst | Hinweise für den Abgleich: - `remainingQuantity` ist der Sollwert für den Bestandsabgleich (Stock Reconciliation) im Lager „Imkerei (ApiCore)“. Er ist **live**: er enthält alle Bewegungen bis zum Abruf. `/bewegungen` liefert dagegen nur Bewegungen, die älter als 5 Minuten sind. Eine Buchung der letzten Minuten steckt also schon im Rest, ihre Bewegung kommt erst im nächsten Lauf. Der Importer darf deshalb den Rest nicht aus den gelieferten Bewegungen nachrechnen und mit `remainingQuantity` vergleichen; Sollwert ist immer `remainingQuantity`, die Bewegungen dienen nur der Einordnung (Vertrauensschrank, Eigenbedarf). - Wird `initialQuantity` in ApiCore korrigiert, ändert sich der Rest sofort mit. - Eine irrtümlich angelegte Herstellungscharge ohne Bewegungen kann in ApiCore gelöscht werden; sie fehlt dann in der Liste und ist im Spiegel auf 0 zu setzen. Abfüllungen und Chargen mit Bewegungen werden nie gelöscht. ## 5. `GET /bewegungen` – Bestandsbewegungen Jede Bestandsänderung einer Abfüllung oder Herstellungscharge, nie gelöscht (Fehler werden in ApiCore gegengebucht). Sortiert nach (`createdAt`, `id`) aufsteigend. | Parameter | Typ | Bedeutung | |---|---|---| | `seit` | ISO-Zeitpunkt oder -Datum | Nur Bewegungen ab diesem Zeitpunkt (einschließlich). Für den ersten Lauf, z. B. `seit=2026-01-01T00:00:00Z`. Nicht zusammen mit `nach`. | | `nach` | Cursor | Weiterlesen hinter der zuletzt gelieferten Bewegung | | `limit` | 1–500 | wie oben | Ohne `seit` und `nach` beginnt die Liste bei der ältesten Bewegung. **Wartezeit:** Es erscheinen nur Bewegungen, die älter als **5 Minuten** sind. Eine Buchung bekommt ihren Zeitstempel beim Einfügen, sichtbar wird sie erst beim Abschluss ihrer Transaktion; ohne Wartezeit könnte der Cursor über eine noch offene, frühere Buchung hinweglaufen. Die Antwort nennt die Grenze zusätzlich: | Feld | Typ | Bedeutung | |---|---|---| | `settledUntil` | string (Zeitpunkt) | Bis hierhin ist die Liste vollständig (Abrufzeit minus 5 Minuten) | Felder je Bewegung: | Feld | Typ | Bedeutung | |---|---|---| | `id` | string | ApiCore-ID der Bewegung; zum Entdoppeln im Importer | | `createdAt` | string (Zeitpunkt) | Buchungszeitpunkt | | `batchType` | `BOTTLING` \| `PRODUCTION` | Art der Charge | | `batchId` | string | `chargen[].id` | | `lotNumber` | string | Losnummer der Charge (zur Bequemlichkeit) | | `productId` | string \| null | Artikel der Charge | | `type` | siehe Tabelle | Art der Bewegung | | `reason` | siehe Tabelle \| null | Grund, nur bei `WITHDRAWAL` | | `quantity` | Ganzzahl (Stück) | mit Vorzeichen: **minus = aus dem Bestand**, plus = zurück in den Bestand | | `type` | `reason` | Vorzeichen | Bedeutung | Behandlung in ERPNext (Spec 8.3) | |---|---|---|---|---| | `WITHDRAWAL` | `HOFLADEN` | − | Ware in den **Vertrauensschrank** gestellt | Umlagerung „Imkerei (ApiCore)“ → „Vertrauensschrank“ | | `WITHDRAWAL` | `EIGENBEDARF` | − | Eigenverbrauch | unentgeltliche Wertabgabe zu Herstellkosten | | `WITHDRAWAL` | `BRUCH` | − | Bruch | nur im Spiegel | | `WITHDRAWAL` | `PROBE` | − | Probe/Muster | nur im Spiegel | | `WITHDRAWAL` | `SONSTIGES` | − | sonstige Entnahme | nur im Spiegel | | `SHOP_SALE` | `null` | − | **Verkauf** im Wabenhain-Shop (bezahlt) | nur im Spiegel; die Rechnung kommt aus dem Shop-Import | | `SHOP_RETURN` | `null` | + | **Retoure**/Storno einer bezahlten Shop-Bestellung | nur im Spiegel | | `CORRECTION` | `null` | ± | Inventurkorrektur (gezählt minus gebucht), auch zum Gegenbuchen einer Fehlbuchung | nur im Spiegel | Rohstoff-Bewegungen (Wachs, Propolis, Art `CONSUMPTION`) gehören nicht zur Ware und erscheinen nie. Kommt künftig ein weiterer `type` oder `reason` hinzu, behandelt der Importer ihn als „nur im Spiegel“ und meldet ihn im Cockpit. Ablauf im Importer: erster Lauf mit `seit=`, danach immer mit dem gespeicherten `nextCursor`. Derselbe Cursor liefert bei erneutem Abruf dieselben Bewegungen (plus inzwischen hinzugekommene); der Importer entdoppelt trotzdem über `id`. ## 6. `GET /ernten` – Erntemengen je Jahr Grundlage für die Honigkosten je kg (Imkerei-Ausgaben des Jahres / geerntete kg). Nicht seitenweise (eine Zeile je Jahr). | Parameter | Typ | Bedeutung | |---|---|---| | `jahr` | Ganzzahl 1900–2100, optional | nur dieses Kalenderjahr; ohne Angabe alle Jahre mit Ernten | | Feld | Typ | Bedeutung | |---|---|---| | `contractVersion` | number | `1` | | `generatedAt` | string (Zeitpunkt) | Zeitpunkt der Antwort | | `years[].year` | Ganzzahl | Kalenderjahr (aus dem Erntedatum, UTC) | | `years[].netWeightKg` | number | Summe der Nettogewichte in **kg**, auf Gramm (3 Nachkommastellen) gerundet | | `years[].harvestCount` | Ganzzahl | Anzahl Ernten im Jahr | | `years[].harvestsWithoutNetWeight` | Ganzzahl | davon ohne erfasstes Nettogewicht (zählen nicht zur Menge) | Definition wie die ApiCore-Statistik „Honigertrag je Jahr“: Summe `netWeight` der Ernten mit Erntedatum im Jahr. Mit `jahr` kommt immer genau eine Zeile, bei einem Jahr ohne Ernten mit Nullen. Ist `harvestsWithoutNetWeight > 0`, sind die Kosten je kg zu hoch – der Importer sollte das im Cockpit anzeigen. Standorte, Völker und Notizen sind nicht enthalten. ## 7. Fehler Fehlerantworten sind JSON mit `statusCode` und `message` (bei Validierungsfehlern `message` als Liste). | Status | Wann | Verhalten des Importers | |---|---|---| | `400` | `limit` außerhalb 1–500 oder keine Ganzzahl; `nach` kein gültiger Cursor oder von einem anderen Endpunkt; `seit` kein ISO-Zeitpunkt; `seit` und `nach` zusammen; `jahr` außerhalb 1900–2100; unbekannter Parameter | Programmfehler – Schritt abbrechen, im Cockpit melden | | `401` | Schlüssel fehlt, ist fehlerhaft, unbekannt, widerrufen oder abgelaufen | Schritt abbrechen; Hinweis „ApiCore-Schlüssel prüfen“ | | `403` | Schlüssel ohne `erp-inventory:read`, oder IP-Adresse nicht in der Allowlist des Schlüssels | Schritt abbrechen; Hinweis „Recht bzw. Adressen am Schlüssel prüfen“ | | `429` | ungültiger Schlüssel von einer Adresse mit zu vielen Fehlversuchen, oder allgemeines Anfragelimit überschritten | Schritt abbrechen, im nächsten Lauf erneut | | `5xx` / keine Antwort | Störung in ApiCore | Schritt abbrechen, im nächsten Lauf erneut | Ein fehlgeschlagener Abruf ist nie „leer“: der Importer setzt dann keine Bestände auf 0 und schiebt keinen Cursor weiter. Die anderen Schritte des Abgleich-Jobs laufen weiter. ## 8. Beispiele Alle Werte synthetisch. ```http GET /api/v1/export/inventar/produkte?limit=2 Authorization: Bearer ``` ```json { "contractVersion": 1, "generatedAt": "2026-10-13T02:00:04.512Z", "items": [ { "id": "cmg0honig500sommer01", "name": "Sommertracht 500 g", "sku": "HON-SO-500", "kind": "BOTTLED_HONEY", "shopCategory": "HONIG", "status": "ACTIVE", "packagingSize": 500, "weight": 500, "salesUnit": "Glas", "updatedAt": "2026-09-02T07:41:10.000Z" }, { "id": "cmg0kerze0bienen0001", "name": "Bienenwachskerze klein", "sku": null, "kind": "MANUFACTURED", "shopCategory": "KERZEN", "status": "ACTIVE", "packagingSize": null, "weight": 120, "salesUnit": "Stück", "updatedAt": "2026-09-20T15:03:55.000Z" } ], "nextCursor": "eyJrIjoicHJvZHVrdGUiLCJpZCI6ImNtZzBrZXJ6ZTBiaWVuZW4wMDAxIn0", "hasMore": true } ``` ```http GET /api/v1/export/inventar/chargen?limit=500 ``` ```json { "contractVersion": 1, "generatedAt": "2026-10-13T02:00:05.130Z", "items": [ { "id": "cmg0abf0sommer2026a1", "batchType": "BOTTLING", "lotNumber": "L-2026-014", "productId": "cmg0honig500sommer01", "date": "2026-08-01T00:00:00.000Z", "bestBeforeDate": "2028-08-01T00:00:00.000Z", "initialQuantity": 60, "remainingQuantity": 49, "fillWeightGrams": 500, "honeyType": "Sommertracht" }, { "id": "cmg0her0kerze2026b01", "batchType": "PRODUCTION", "lotNumber": "K-2026-003", "productId": "cmg0kerze0bienen0001", "date": "2026-09-01T00:00:00.000Z", "bestBeforeDate": null, "initialQuantity": 20, "remainingQuantity": 19, "fillWeightGrams": null, "honeyType": null } ], "nextCursor": "eyJrIjoiY2hhcmdlbiIsInQiOiJQIiwiaWQiOiJjbWcwaGVyMGtlcnplMjAyNmIwMSJ9", "hasMore": false } ``` ```http GET /api/v1/export/inventar/bewegungen?seit=2026-01-01T00:00:00Z&limit=3 ``` ```json { "contractVersion": 1, "generatedAt": "2026-10-13T02:00:05.877Z", "settledUntil": "2026-10-13T01:55:05.877Z", "items": [ { "id": "cmg0bew000000000001", "createdAt": "2026-09-05T16:20:11.402Z", "batchType": "BOTTLING", "batchId": "cmg0abf0sommer2026a1", "lotNumber": "L-2026-014", "productId": "cmg0honig500sommer01", "type": "WITHDRAWAL", "reason": "HOFLADEN", "quantity": -6 }, { "id": "cmg0bew000000000002", "createdAt": "2026-09-06T09:02:47.118Z", "batchType": "BOTTLING", "batchId": "cmg0abf0sommer2026a1", "lotNumber": "L-2026-014", "productId": "cmg0honig500sommer01", "type": "SHOP_SALE", "reason": null, "quantity": -3 }, { "id": "cmg0bew000000000003", "createdAt": "2026-09-12T18:44:03.950Z", "batchType": "BOTTLING", "batchId": "cmg0abf0sommer2026a1", "lotNumber": "L-2026-014", "productId": "cmg0honig500sommer01", "type": "WITHDRAWAL", "reason": "EIGENBEDARF", "quantity": -2 } ], "nextCursor": "eyJrIjoiYmV3ZWd1bmdlbiIsInRzIjoiMjAyNi0wOS0xMlQxODo0NDowMy45NTBaIiwiaWQiOiJjbWcwYmV3MDAwMDAwMDAwMDAzIn0", "hasMore": true } ``` ```http GET /api/v1/export/inventar/ernten?jahr=2026 ``` ```json { "contractVersion": 1, "generatedAt": "2026-10-13T02:00:06.240Z", "years": [ { "year": 2026, "netWeightKg": 184.35, "harvestCount": 7, "harvestsWithoutNetWeight": 1 } ] } ``` ```json { "statusCode": 400, "message": "Ungültiger Cursor - bitte nextCursor der vorigen Seite unverändert als \"nach\" übergeben", "error": "Bad Request" } ``` ## 9. Was nicht enthalten ist Bewusst nicht im Export (Feld-Allowlist im Code, `src/erp-export/erp-export.service.ts`): Notizen an Chargen und Bewegungen, Benutzer (wer gebucht hat), API-Schlüssel und Shop-Anfrage-Kennungen, Artikelbeschreibungen (in ApiCore interne Notizen), Standorte, Völker, Behandlungen, Rohstoffe. Neue Felder an den ApiCore-Modellen erscheinen erst, wenn sie hier ausdrücklich ergänzt werden. ## 10. Versionierung `contractVersion` steigt nur bei inkompatiblen Änderungen (Feld entfernt, umbenannt oder in der Bedeutung geändert). Neue Felder, neue Enum-Werte und neue Endpunkte kommen ohne Versionswechsel; der Importer ignoriert unbekannte Felder und bricht bei einer anderen `contractVersion` ab, ohne etwas zu ändern. ## 11. Rollout 1. ApiCore ausrollen. Keine Datenbank-Migration nötig: das Recht `erp-inventory:read` legt ApiCore beim Start an und gibt es der Systemrolle Administrator. 2. Schlüssel anlegen (Abschnitt 1). 3. Schlüssel als Kubernetes-Secret für den ERPNext-Abgleich ablegen, dazu die Basis-URL (`…/api/v1`) als Konfiguration. 4. Erreichbarkeit: wie der Wabenhain-Shop über die öffentliche ApiCore-Adresse. Dann ist die Absender-IP die Ausgangsadresse des Clusters, und die Allowlist des Schlüssels kann genau diese Adresse enthalten. Ein Aufruf über den clusterinternen Service ginge mit wechselnder Pod-Adresse ein und bräuchte eine NetworkPolicy-Freigabe; die Allowlist taugt dann nur noch als Netzbereich. 5. Probeabruf aller vier Endpunkte, dann den nächtlichen Schritt aktivieren.