# Daten und Aktualisierungen

> Wo die Datensätze eines NXM-Moduls liegen, wie Paketdaten und Beispieldaten installiert werden, was eine neue Version ändern darf und was mit Ihren Daten beim Aktualisieren, Deaktivieren und Deinstallieren geschieht.

Ein NXM-Modul ist Code, den niemand im Mandanten selbst geschrieben hat, und es arbeitet mit Daten, die der Mandant nicht verlieren darf. Deshalb übernimmt nexxus die Verantwortung für diese Daten. Das Paket beschreibt seine Fachobjekte, die Plattform speichert, prüft, indiziert und migriert die Datensätze. Eine neue Version darf nur ändern, was sich gefahrlos ändern lässt. Jede Aktualisierung läuft in einer einzigen Transaktion, und bevor ein Modul deinstalliert wird, fragt nexxus, was mit seinen Daten geschehen soll.

Diese Seite begleitet einen Datensatz durch sein Leben: wie er gespeichert wird, wie ein Paket ihn bei der Installation anlegt, wie eine Aktualisierung ihn umbaut und was bleibt, wenn das Modul geht.

## Wo die Datensätze liegen

nexxus hält die Datensätze aller Pakete in **einem gemeinsamen Datensatzspeicher**. Die Felder eines Datensatzes liegen dort als ein JSON-Dokument. Die Werte der Felder, die ein Paket als `indexed` oder `unique` kennzeichnet, und jeder Verweis werden in derselben Transaktion zusätzlich in typisierte Nachschlagetabellen geschrieben. Ein Paket zu installieren ändert deshalb nie das Datenbankschema: Es gibt keine Tabellen je Paket und kein DDL je Mandant.

Als Entwicklerin oder Entwickler merken Sie das an mehreren Stellen:

- **Filtern und sortieren geht nur über indizierte Felder.** Indiziert ist ein Feld mit `indexed: true`, ein Feld mit `unique`, ein `sequence`-Feld und jeder `ref`. Einen Filter auf ein anderes Feld lehnt nexxus mit `Nach '<feld>' lässt sich nicht filtern: das Feld ist nicht indexed.` ab.
- **Auswerten geht ebenfalls nur über indizierte Felder.** Jedes Fachobjekt ist eine Quelle für Kacheln, Benachrichtigungsregeln und den KI-Assistenten, siehe [Quellen aus Paketen](/de/reference/nql/#quellen-aus-paketen). Ein Feld, das dort auftauchen soll, braucht also `indexed: true`.
- **Die Volltextsuche** (`?q=` in der API, das Suchfeld der Liste) durchsucht die `searchFields` des Fachobjekts, ohne auf Groß- und Kleinschreibung zu achten. Steht ein Verweis unter den `searchFields`, findet sie den Datensatz auch über den aktuellen Namen dessen, worauf er zeigt, und nur in dem, was die Person ohnehin sehen darf.
- **Eindeutige Werte** vergleicht nexxus ohne Leerzeichen am Rand und ohne Rücksicht auf Groß- und Kleinschreibung. `12/26` und ` 12/26 ` gelten als derselbe Wert.
- **Gelöschte Datensätze werden nur als gelöscht markiert.** Der Datensatz verschwindet aus Listen und API, die Zeile bleibt in der Datenbank.
- In der globalen Suche erscheinen Datensätze von Paketen in dieser Version nicht.

### So sieht ein Datensatz aus

Ein Aktendokument aus der CuriaOS-Fallstudie, wie API und View-SDK es liefern. Die Werte stammen aus den Beispieldaten des Pakets, IDs, Datumsangaben und Person sind zur Veranschaulichung gewählt:

```json
{
  "id": 12,
  "module": "curiaos.kanzlei",
  "entity": "aktendokument",
  "title": "Vollmacht Sabine Vogl",
  "data": {
    "akte": 4,
    "dokument": 88,
    "titel": "Vollmacht Sabine Vogl",
    "typ": "vollmacht",
    "eingegangen_am": "2026-09-14",
    "status": "geprueft"
  },
  "refs": {
    "akte": { "id": 4, "label": "2026/0184" },
    "dokument": { "id": 88, "label": "2026-0184_vollmacht.pdf" }
  },
  "version": 3,
  "createdAt": "2026-10-08T07:12:44Z",
  "updatedAt": "2026-10-08T09:30:02Z",
  "createdBy": { "id": 7, "name": "Jana Brandt" },
  "updatedBy": { "id": 7, "name": "Jana Brandt" }
}
```

- `title` ist der Wert des `titleField` des Fachobjekts, oder `#<id>`, wenn es keines gibt.
- `refs` liefert zu jedem Verweis eine lesbare Bezeichnung. Eine Ansicht muss die Verweise also nicht einzeln nachschlagen.
- `data` enthält die kanonische Form jedes Werts: Datum als `YYYY-MM-DD`, Zeitpunkte als ISO-8601 in UTC, Verweise als Zahl.

### Versionen schützen vor dem Überschreiben

Jeder Datensatz trägt eine `version`, die bei 0 beginnt. Schickt ein Client die Version, die er gelesen hat, mit seiner Änderung mit, lehnt nexxus das Schreiben ab, wenn jemand den Datensatz in der Zwischenzeit geändert hat. Die API antwortet mit HTTP 409, Code `CONFLICT` und der Meldung „Der Datensatz wurde inzwischen geändert (Version 4, gesendet 3). Bitte neu laden.“ Die Datensatzseite in der App zeigt „Jemand hat diesen Datensatz inzwischen geändert. Laden Sie ihn neu und tragen Sie Ihre Änderung danach erneut ein.“ mit der Schaltfläche <Ui>Neu laden</Ui>.

Eigene Ansichten sollten immer die gelesene Version mitschicken (`nx.records.update(entity, id, patch, {version})`). Dann kommt eine gleichzeitige Änderung als `CONFLICT` zurück und wird angezeigt, statt still überschrieben zu werden.

### Werte, die nur die Plattform schreibt

Zwei Feldoptionen überlassen den Wert der Plattform:

- **`readonly`**-Felder setzen Aktionen, Migrationsschritte, Paketdatendateien und Moduldienste, aber nie das Formular einer Person und nie `records.update` aus einer Ansicht. nexxus lehnt einen Schreibversuch einer Person nur ab, wenn sich der Wert wirklich ändert. Ein Formular, das den ganzen Datensatz unverändert zurückschickt, kommt also durch. CuriaOS nutzt das für den Status einer Frist: Auf „Erledigt“ setzt ihn nur die Vier-Augen-Aktion.
- **`sequence`**-Felder bekommen beim Anlegen die nächste Nummer, wenn das Feld leer ist. Das Format ist Text mit `{yyyy}`, `{yy}` und genau einem `{n}` oder `{n:<stellen>}`, etwa `{yyyy}/{n:4}` für `2026/0184`. Der Zähler läuft je Mandant, Modul, Fachobjekt und Feld, und je Jahr, wenn das Format das Jahr enthält. Setzt eine Datendatei oder ein Moduldienst den Wert ausdrücklich, zieht der Zähler nach, und die nächste Nummer liegt darüber. Beim Bearbeiten eines Datensatzes vergibt nexxus keine Nummer. Ein `sequence`-Feld ist immer eindeutig und schreibgeschützt.

## Paketdaten und Beispieldaten

Ein Paket kann Datensätze mitbringen, in zwei Ordnern:

| Ordner | Wird installiert | Typischer Inhalt |
|---|---|---|
| `data/<entity>.json` | immer, bei der Installation und bei jeder Aktualisierung erneut | Datensätze, die zum Produkt gehören: ein Anforderungskatalog, Standardkategorien |
| `demo/<entity>.json` | bei der ersten Installation, wenn die Administration <Ui>Beispieldaten anlegen</Ui> anhakt; bei einer Aktualisierung die Zeilen, die die neue Version neu mitbringt, wenn sie <Ui>Neue Beispieldaten anlegen</Ui> anhakt | erfundene Musterdatensätze zum Ausprobieren |
| `demo/files/<name>` | mit den Beispieldaten | Dateien (PDFs), auf die Beispieldatensätze verweisen |

Jede Datei ist ein JSON-Array. Jede Zeile hat einen paketinternen `key` und ein Objekt `data` mit den Feldwerten. Untereinander und auf Kerndaten verweisen die Zeilen über Platzhalter, die der Installer auflöst. Eine echte Zeile aus den CuriaOS-Beispieldaten:

```json
{
  "key": "d-0184-vollmacht",
  "data": {
    "akte": { "$key": "akte:2026-0184" },
    "dokument": { "$file": "files/2026-0184_vollmacht.pdf" },
    "titel": "Vollmacht Sabine Vogl",
    "typ": "vollmacht",
    "eingegangen_am": { "$date": "-17wd" },
    "beteiligter": { "$key": "beteiligung:b-0184-vogl" },
    "status": "geprueft"
  }
}
```

| Platzhalter | Wird zu |
|---|---|
| `{"$key": "<entity>:<key>"}` | der ID des Datensatzes, der aus dieser Zeile des Pakets entstanden ist |
| `{"$key": "<entity>:<key>", "field": "<field>"}` | dem Feldwert dieses Datensatzes statt seiner ID |
| `{"$file": "files/<name>.pdf"}` | einem Dokument im Dateien-Ordner „&lt;Modulname&gt; (Beispieldaten)“ |
| `{"$partner": "<name>"}` | dem Partner des Mandanten mit diesem Namen (ohne Rücksicht auf Groß- und Kleinschreibung); fehlt er, legt nexxus ihn wie das Partnerformular mit Nummer und Standard-Niederlassung an, sodass an ihn fakturiert werden kann |
| `{"$partner": "<name>", "email": "<adresse>"}` | ebenso; ein Partner, den nexxus neu anlegt, bekommt diese E-Mail-Adresse (siehe unten) |
| `{"$project": "<bezeichnung>", "partner": "<name>"}` | dem Projekt des Mandanten mit dieser Bezeichnung; fehlt es, legt nexxus es an, mit `partner` für diesen Partner |
| `{"$contact": "<vorname> <nachname>", "partner": "<name>", "email": "<adresse>"}` | dem Kontakt dieses Namens bei diesem Partner; fehlt er, legt nexxus ihn mit dieser Adresse an (und optional `"position"`). `partner` und `email` sind Pflicht |
| `{"$currentUser": true}` | der Person, die das Paket installiert |
| `{"$date": "<schritte>"}` | einem Datum relativ zum Installationstag |

### Musterkontakte mit E-Mail-Adresse

Beispieldaten, die versendet werden sollen, etwa ein Schreiben an die Musterversicherung, brauchen einen Kontakt mit E-Mail-Adresse: `{"$partner": "Muster Rechtsschutz eG", "email": "rsv@muster-rechtsschutz.example"}`. Erlaubt sind nur Adressen unter den reservierten Top-Level-Domains `.example`, `.test` und `.invalid`, damit eine Demo nie eine echte Person anschreibt. Jede andere Adresse, und alles, was keine Adresse ist, lehnt nexxus beim Hochladen ab, etwa mit „E-Mail 'rsv@muster-rechtsschutz.de' ist nicht erlaubt: Kontakte aus Paketdaten bekommen nur Adressen unter .example, .test oder .invalid, damit keine echte Person erreicht wird“. Vor der Installation prüft nexxus noch einmal, bevor etwas angelegt wird. Ein Partner, den es unter diesem Namen schon gibt, wird so genommen, wie er ist, und behält seine eigene Adresse.

### Relative Datumsangaben

Beispieldaten mit festen Datumsangaben sehen eine Woche später falsch aus: Fristen sind überfällig, Termine liegen in der Vergangenheit. `$date` zählt deshalb vom Tag der Installation aus (Zeitzone Europe/Berlin). Der Wert ist eine Folge von Schritten, die von links nach rechts gelten. Jeder Schritt besteht aus Vorzeichen, Zahl und Einheit:

| Einheit | Bedeutung | Beispiel |
|---|---|---|
| `d` | Tage | `"0d"` ist der Installationstag |
| `w` | Wochen | `"+1w"` |
| `m` | Monate | `"-1m"` |
| `wd` | Werktage: Montag bis Freitag ohne die bundesweiten Feiertage; 24. und 31. Dezember zählen als Werktage | `"+2wd"` |

Schritte lassen sich verketten: `"+2wd-14d"` heißt zwei Werktage vor, dann 14 Tage zurück. Bei einem `datetime`-Feld geben Sie daneben `"time": "HH:MM"` an (Standard `09:00`). `$date` gibt es nur für `date`- und `datetime`-Felder, alles andere bricht die Installation ab.

### In welcher Reihenfolge die Zeilen entstehen

Ein `$key` findet nur eine Zeile, die der Installer schon angelegt hat. Er legt sie in dieser Reihenfolge an:

1. `data/` vor `demo/`. Eine Beispielzeile darf auf eine Zeile aus `data/` verweisen. Umgekehrt geht es nicht, denn Beispieldaten sind freiwillig.
2. In jedem Ordner ein Fachobjekt nach dem anderen. Ein Fachobjekt, dessen `ref`-Felder auf ein anderes Fachobjekt des Pakets zeigen, kommt nach diesem. Wo die Verweise die Wahl offen lassen, entscheidet die alphabetische Reihenfolge der Dateinamen.
3. In einer Datei von der obersten Zeile zur untersten. Eine Zeile, die auf eine andere Zeile desselben Fachobjekts verweist, etwa auf einen übergeordneten Datensatz, muss unter ihr stehen.

Zwei Fachobjekte, deren `ref`-Felder aufeinander zeigen, können nicht beide zuerst kommen. Der Installer nimmt sie dann alphabetisch, und ein `$key` vom ersten auf das zweite scheitert. Halten Sie den Verweis zwischen zwei Fachobjekten in einer Richtung; die andere Seite findet ihre Datensätze über eine Suche nach diesem Feld. Zeilen, die eine frühere Version oder eine Installation mit behaltenen Daten angelegt hat, zählen als angelegt (siehe den nächsten Abschnitt).

Findet ein `$key` noch kein Ziel, scheitert die ganze Installation mit einer Meldung wie `demo/aktendokument.json 'd-0184-vollmacht'.akte: $key 'akte:2026-0184' gibt es nicht (oder er steht weiter hinten).`

### Doppelt installieren schadet nicht

Der Installer überspringt jede Zeile, deren Schlüssel es im Mandanten schon gibt, egal ob sie aus einer früheren Version stammt oder aus einer Installation, deren Daten behalten wurden. Deshalb darf `data/` bei jeder Aktualisierung erneut laufen: Nur Zeilen mit neuen Schlüsseln legen Datensätze an. Daraus folgt auch: **Ändern Sie eine bestehende Zeile in einer späteren Version, ändert sich der Datensatz nicht.** Dafür gibt es Migrationsschritte.

Bei den Beispieldaten zählen auch gelöschte Datensätze: Einen Beispieldatensatz, den jemand gelöscht hat, legt eine Aktualisierung nicht wieder an, und ebenso wenig eine neue Zeile, die per `$key` auf ihn verweist.

Ein `$key` nennt nur eine Zeile desselben Pakets. Einen `$key` mit dem Schlüssel eines anderen Pakets davor lehnt nexxus beim Hochladen ab, weil Pakete nicht aufeinander aufbauen (siehe [Aufbau eines Pakets](/de/modules/structure/#pakete-sind-in-sich-geschlossen)). Datendateien dürfen schreibgeschützte und `sequence`-Felder direkt setzen. Lässt sich etwas nicht auflösen, scheitert die ganze Installation, und die Meldung nennt Datei, Zeile und Feld, zum Beispiel `demo/frist.json 'f-0184-wv-regulierung'.faellig_am: '+2x' ist keine relative Angabe wie "+2wd" oder "+2wd-14d"`.

## Versionen und was eine neue Version ändern darf

Eine Paketversion ist eine SemVer-Nummer wie `1.1.0`. Ist eine Version einmal hochgeladen, steht ihr Inhalt fest. Jede Korrektur, auch ein Tippfehler in einer Ansicht, braucht eine neue Version. Zwei weitere Felder des Manifests legen fest, wo eine Version laufen darf:

- `platform` ist ein Versionsbereich der nexxus-Plattform, zum Beispiel `">=0.2.0 <1.0.0"`.
- `requires` nennt eingebaute Module mit einem Versionsbereich, zum Beispiel `"partners": ">=1.0.0"`. Ein solches Modul muss es in der Instanz in passender Version geben. Ein Paket setzt nie ein anderes Paket voraus.

Die Bereiche folgen der Schreibweise von npm: Durch Leerzeichen getrennte Vergleiche müssen alle zutreffen, `||` trennt Alternativen, `^` und `~` wirken wie bei npm.

Vor jeder Aktualisierung vergleicht nexxus die neue Version mit der installierten. Aus diesen Gründen lehnt es ab, mit genau dem Text, den der Dialog zeigt:

| Änderung | Text der Ablehnung |
|---|---|
| Die neue Version braucht eine andere Plattform | „Version 1.1.0 verlangt die Plattform >=1.0.0, hier läuft 0.2.0.“ |
| Die Hauptversion sinkt | „Die Hauptversion sinkt von 2 auf 1.“ |
| Die neue Version ist nicht neuer | „Version 1.0.0 ist nicht neuer als die installierte 1.0.0.“ |
| Ein Fachobjekt fehlt ohne neue Hauptversion | „Das Datenobjekt 'notiz' fehlt, das geht nur mit einer neuen Hauptversion.“ |
| Ein Feld fehlt ohne neue Hauptversion | „Das Feld 'frist.ereignis' fehlt, das geht nur mit einer neuen Hauptversion.“ |
| Ein Feld ändert seinen Typ | „Das Feld 'frist.faellig_am' ändert seinen Typ von date auf datetime; dafür braucht es ein neues Feld und eine Migration.“ |
| Ein Feld ändert `unique` | „Das Feld 'akte.aktennummer' ändert die Eindeutigkeit (unique).“ |
| Ein Verweis zeigt woanders hin | „Der Verweis 'notiz.aktendokument' zeigt jetzt auf 'akte' statt auf 'aktendokument'.“ |
| Ein Feld ändert `multiple` | „Das Feld 'frist.zustaendig' ändert multiple.“ |
| Ein neues Pflichtfeld hat keinen Standardwert | „Das neue Pflichtfeld 'frist.quelle' hat keinen Standardwert.“ |
| Ein vorausgesetztes eingebautes Modul fehlt | „Das Modul 'partners' gibt es auf dieser Plattform nicht.“ |
| Ein vorausgesetztes eingebautes Modul hat eine andere Version | „Das Modul 'partners' läuft in Version 1.0.0, verlangt ist >=2.0.0.“ |
| Die neue Version stammt aus der Zeit vor geschlossenen Paketen und nennt ein anderes Paket | „Diese Version baut auf dem Paket 'muster.akten' auf. Pakete bauen nicht mehr aufeinander auf; was zusammengehört, kommt als ein Paket.“ |

Frei ändern darf eine Version: Fachobjekte, Felder, Ansichten, Aktionen, Menüeinträge und Rollen hinzufügen, Werte von Auswahllisten hinzufügen oder entfernen, jede Beschriftung ändern. Ein neuer Typ für ein Feld ist ein neues Feld plus ein Migrationsschritt, der die Werte hinüberkopiert. Eine Rückkehr zu einer älteren Version gibt es nicht.

## Migrationsschritte

Müssen gespeicherte Daten mit einer Version umgebaut werden, beschreibt das Manifest die Schritte unter `migrations`, geordnet nach der Version, die sie einführt:

```json
"migrations": {
  "1.1.0": {
    "pre":  [ { "op": "renameValue", "entity": "geraet", "field": "kategorie", "from": "telefon", "to": "smartphone" } ],
    "post": [ { "op": "setDefault", "entity": "geraet", "field": "standort", "value": "buero" } ]
  }
}
```

| `op` | Parameter | Wirkung | Erlaubt in |
|---|---|---|---|
| `setDefault` | `entity`, `field`, `value` | füllt das Feld, wo es leer ist | jeder Version |
| `renameValue` | `entity`, `field`, `from`, `to` | ersetzt einen Wert einer Auswahlliste, auch in `multiple`-Listen | jeder Version |
| `renameField` | `entity`, `from`, `to` (Feldschlüssel) | verschiebt den Wert in ein anderes Feld | jeder Version |
| `copyField` | `entity`, `from`, `to` (Feldschlüssel) | kopiert den Wert in ein anderes Feld | jeder Version |
| `deleteField` | `entity`, `field` | entfernt den Wert des Felds aus jedem Datensatz | nur Hauptversionen (`x.0.0`) |
| `deleteEntity` | `entity` | markiert alle Datensätze des Fachobjekts als gelöscht | nur Hauptversionen (`x.0.0`) |

**`pre` oder `post`?** Schritte unter `pre` laufen, solange noch die Beschreibung der alten Version gilt, Schritte unter `post` nach dem Wechsel auf die neue. Werte benennen Sie vor dem Wechsel um, neue Felder füllen Sie danach. Bei einer Aktualisierung von Version A auf Version B sammelt nexxus die Schritte jeder Version V mit A &lt; V ≤ B: erst alle `pre`-Schritte in Versionsreihenfolge, dann alle `post`-Schritte in Versionsreihenfolge. Eine Version unter `migrations` darf nicht höher sein als die Version des Pakets selbst.

Zwei Dinge sollten Sie wissen:

- nexxus trägt jeden ausgeführten Schritt je Mandant in ein Protokoll ein, das jeden Schritt nur einmal zulässt. Ein Schritt, der schon gelaufen ist, wird übersprungen.
- Migrationsschritte schreiben ihre Werte so, wie sie im Manifest stehen. nexxus prüft sie nicht gegen Typ oder Auswahlwerte des Felds. Achten Sie selbst darauf, dass `to` und `value` zur neuen Version passen.

## Die Aktualisierung Schritt für Schritt

Die Administration startet die Aktualisierung auf der Paketseite (<MenuPath to="Administration > Betrieb > Module" />, Reiter <Ui>Pakete</Ui>) mit <Ui>Auf 1.1.0 aktualisieren</Ui>. Den Dialog beschreibt [Paketieren, installieren und aktualisieren](/de/modules/publish/). Hinter dieser Schaltfläche arbeitet die Plattform in dieser Reihenfolge:

<Flow
  steps={[
    { icon: 'file-search', title: 'Plan', text: 'Versionsregeln, vorausgesetzte eingebaute Module, neue Zustimmungen und ein Probelauf, der zählt, welche Datensätze jeder Schritt ändert.' },
    { icon: 'shield-check', title: 'Zustimmung', text: 'Jeden neuen Scope und jeden neuen Host eines Diensts hakt die Administration an.', tone: 'cyan' },
    { icon: 'replace', title: 'pre-Schritte', text: 'Laufen, solange die alte Version gilt.', tone: 'lavender' },
    { icon: 'refresh-cw', title: 'Wechsel und post-Schritte', text: 'Neue Version aktiv, post-Schritte, neu indizieren, Protokoll, Rollen, Skill für den KI-Assistenten, neue Paketdaten, auf Wunsch neue Beispieldaten.', tone: 'lavender' },
    { icon: 'circle-check-big', title: 'Abschluss', text: 'Alles oder nichts. Die Dienste erfahren es danach.' },
  ]}
  caption="Eine Transaktion je Mandant. Scheitert ein Schritt, bleibt die Installation auf ihrer bisherigen Version."
/>

1. **Plan.** Bevor sich etwas ändert, zeigt der Dialog das Ergebnis des Plans: unter <Ui>Was sich ändert</Ui> die Einträge des Changelogs zwischen den beiden Versionen, unter <Ui>Datenanpassungen</Ui> je Migrationsschritt eine Zeile mit Phase („vor dem Wechsel“ oder „nach dem Wechsel“) und der Zahl der Datensätze, die er heute ändern würde („1 Datensatz“, „9 Datensätze“). Bringt die neue Version Beispielzeilen mit, die es im Mandanten noch nicht gibt, bietet der Dialog <Ui>Neue Beispieldaten anlegen</Ui> mit ihrer Zahl an; der Haken ist zu Beginn nicht gesetzt. Ablehnungsgründe stehen unter „Diese Aktualisierung ist nicht möglich:“ und sperren die Schaltfläche.
2. **Zustimmung.** Will die neue Version Daten oder Dienste nutzen, denen die Administration noch nicht zugestimmt hat, stehen sie unter <Ui>Neu zuzustimmen</Ui>. Solange nicht jeder Punkt angehakt ist, bleibt das Paket auf der alten Version.
3. **Die Transaktion.** nexxus führt alle `pre`-Schritte aus, stellt die Installation auf die neue Version um, führt alle `post`-Schritte aus, schreibt die Nachschlagetabellen jedes Datensatzes nach der neuen Beschreibung neu, trägt die Schritte ins Protokoll ein, legt neue Rollen an, erweitert die eigenen Rollen des Moduls, schreibt den Skill für den KI-Assistenten aus den neuen Anweisungen neu und installiert neue Zeilen aus `data/`, mit gesetztem Haken auch die neuen Zeilen aus `demo/`.
4. **Nach dem Abschluss.** Die Plattform meldet die Aktualisierung an die Dienste des Moduls.

Scheitert etwas in Schritt 3, rollt nexxus die ganze Transaktion zurück. Installation, Datensätze und Rollen bleiben genau auf der alten Version, und der Dialog zeigt „Nicht aktualisiert:“ mit der Meldung des Servers, etwa `Die Migration auf 1.1.0 ist gescheitert, 'acme.leihgeraete' bleibt auf 1.0.0: <Grund>`.

<Screenshot src={shot_nxm_35} url="/modules/pakete/acme.leihgeraete?version=1.1.0" alt={"Der Dialog zum Aktualisieren mit Probelauf"} caption={"Der Probelauf zählt die Datensätze, die jeder Migrationsschritt ändert."} />

### Rollen nach einer Aktualisierung

Neue Rollen im Manifest legt nexxus als neue Rollen an. Rollen, die das Modul früher angelegt hat, bekommen die Berechtigungen, die **in dieser Version neu** sind (neue Fachobjekte, neue zusätzliche Rechte), und das nur, wenn es die Rolle im Manifest schon in der alten Version gab. Was die Administration einer solchen Rolle entzogen hat, bleibt entzogen. Rollen, die nicht dem Modul gehören, fasst nexxus nie an. So bekommt durch eine Aktualisierung niemand Rechte, die die Administration nicht vergeben hat.

### Ereignisse an Ihre Dienste

Moduldienste, die Ereignisse zu Datensätzen abonniert haben, sehen jede Änderung eines Migrationsschritts: ein Ereignis je Datensatz und Schritt, versendet innerhalb der Aktualisierung. Ändern zwei Schritte denselben Datensatz, kommt er zweimal. Nach diesen Ereignissen erhält jeder Dienst der Installation `installation.upgraded` mit der `previousVersion`. Damit kann er nachholen, was er berechnet hätte, wäre er von Anfang an dabei gewesen. Die Inhalte der Ereignisse stehen unter [Moduldienste](/de/modules/services/).

## Deaktivieren, deinstallieren, wieder installieren

Die Administration hat drei Wege, ein Modul aus dem Betrieb zu nehmen. Sie unterscheiden sich darin, was bleibt:

| Aktion | Menü, Seiten, Werkzeuge für den KI-Assistenten | Datensätze | Rollen | Skill für den KI-Assistenten | Dienstkonto |
|---|---|---|---|---|---|
| <Ui>Deaktivieren</Ui> | für alle weg | bleiben, unsichtbar | bleiben | ausgeschaltet | gesperrt |
| <Ui>Deinstallieren</Ui> mit <Ui>Daten behalten</Ui> | weg | bleiben, unsichtbar | bleiben | entfernt | gesperrt |
| <Ui>Deinstallieren</Ui> mit <Ui>Daten löschen</Ui> | weg | als gelöscht markiert | gelöscht, samt Zuweisungen | entfernt | gelöscht, samt seiner Rolle |

- **Deaktivieren** ist eine Pause. <Ui>Aktivieren</Ui> stellt alles wieder her, wie es war.
- **Daten behalten** entfernt das Modul, lässt seine Datensätze aber liegen. Der Katalog führt es als „Entfernt, Daten behalten“, und die Paketseite bietet <Ui>Wieder installieren</Ui> an. Eine neue Installation hängt Datensätze und Rollen wieder an. Installiert die Administration dabei eine neuere Version als die behaltene, laufen die Migrationsschritte zwischen beiden Versionen als Teil der Installation.
- **Daten löschen** markiert die Datensätze als gelöscht, entfernt die Rollen des Moduls mit ihren Rechten und Zuweisungen, legt den Ordner mit den Beispieldokumenten in den Dateien als gelöscht ab und leert das Migrationsprotokoll. Partner, die aus Beispieldaten entstanden sind, bleiben, weil andere Datensätze des Mandanten inzwischen auf sie verweisen können.

Jedes Paket lässt sich für sich deaktivieren oder deinstallieren: Kein anderes Paket baut darauf auf.

<Screenshot src={shot_nxm_11} url="/modules/pakete/acme.leihgeraete" alt={"Der Deinstallationsdialog mit Export und der Wahl für die Daten"} caption={"Erst der eingetippte Modulschlüssel gibt das Deinstallieren frei."} />

### Vor dem Deinstallieren exportieren

Der Deinstallationsdialog bietet <Ui>Daten exportieren (ZIP)</Ui> an. Die Datei `<key>-export.zip` enthält je Fachobjekt der installierten Version eine `<entity>.json` und eine `module.json` mit Schlüssel, Version und Zeitpunkt des Exports. Jeder Datensatz steht darin so:

```json
{
  "id": 12,
  "key": "d-0184-vollmacht",
  "data": { "titel": "Vollmacht Sabine Vogl", "typ": "vollmacht", "status": "geprueft" },
  "version": 3,
  "createdAt": "2026-10-08T07:12:44Z",
  "updatedAt": "2026-10-08T09:30:02Z"
}
```

`key` ist der Schlüssel aus der Datendatei, wenn der Datensatz aus Paket- oder Beispieldaten stammt, und `null` bei Datensätzen, die Menschen angelegt haben.

## Durchgerechnet: Leihgeräte 1.0.0 auf 1.1.0

Das Tutorial-Paket aus [Ihr erstes Modul](/de/modules/first-module/) zeigt eine vollständige Aktualisierung. Version 1.0.0 speichert Telefone mit dem Kategoriewert `telefon`. Version 1.1.0 benennt diesen Wert in `smartphone` um und ergänzt das Feld `standort` mit dem Standardwert `buero`. Ihr Block `migrations` ist genau der unter [Migrationsschritte](#migrationsschritte).

Die Beispieldaten von 1.0.0 enthalten drei Geräte, keines davon ein Telefon. In einem Mandanten, der 1.0.0 mit diesen Beispieldaten und sonst nichts installiert hat, zeigt der Dialog:

- unter <Ui>Datenanpassungen</Ui>: `renameValue` auf `geraet.kategorie`, „1.1.0 · vor dem Wechsel“, 0 Datensätze, und `setDefault` auf `geraet.standort`, „1.1.0 · nach dem Wechsel“, 3 Datensätze;
- keinen Abschnitt <Ui>Neu zuzustimmen</Ui>, denn 1.1.0 verlangt keinen neuen Datenzugriff;
- keine Ablehnung: Kein Feld fällt weg, kein Typ ändert sich, und das neue Feld hat einen Standardwert.

Wäre `standort` ein Pflichtfeld ohne Standardwert, lehnte der Dialog mit „Das neue Pflichtfeld 'geraet.standort' hat keinen Standardwert.“ ab.

## Wenn etwas schiefgeht

| Meldung | Bedeutung und Abhilfe |
|---|---|
| „Version 1.1.0 ist nicht neuer als die installierte 1.1.0.“ | Sie haben die installierte Version gewählt. Laden Sie eine höhere Versionsnummer hoch. |
| „Das Feld 'frist.ereignis' fehlt, das geht nur mit einer neuen Hauptversion.“ | In einer Nebenversion ist ein Feld weggefallen. Behalten Sie das Feld, oder veröffentlichen Sie eine neue Hauptversion mit einem Schritt `deleteField`. |
| „Das neue Pflichtfeld 'frist.quelle' hat keinen Standardwert.“ | Geben Sie dem neuen Pflichtfeld einen `default`, oder machen Sie es optional und füllen Sie es mit einem `setDefault` unter `post`. |
| `Zustimmung fehlt für: <Punkte>` | In der Anfrage fehlte eine neue Zustimmung. Haken Sie jeden Punkt unter <Ui>Neu zuzustimmen</Ui> an. |
| `Die Migration auf 1.1.0 ist gescheitert, 'acme.leihgeraete' bleibt auf 1.0.0: <Grund>` | Ein Schritt ist gescheitert, alles wurde zurückgerollt. Der Grund am Ende nennt das Problem. Korrigieren Sie den Schritt in einer neuen Version. |
| „'acme.leihgeraete' ist schon installiert (Version 1.0.0); für eine neue Version gibt es das Upgrade.“ | Das Paket ist schon installiert. Nutzen Sie auf der Paketseite die Schaltfläche zum Aktualisieren statt einer neuen Installation. |
| „'acme.leihgeraete' ist deinstalliert; eine neue Installation hängt die Daten wieder an.“ | Das Modul wurde mit behaltenen Daten deinstalliert. Nutzen Sie <Ui>Wieder installieren</Ui>. |

## Wie es weitergeht

<CardGrid>
  <LinkCard title="Paketieren, installieren und aktualisieren" description="Die Seite der Administration: Katalog, Installationsdialog, Aktualisieren und Deinstallieren." href="/de/modules/publish/" />
  <LinkCard title="Aufbau eines Pakets" description="Jedes Feld des Manifests, auch Fachobjekte, Felder und ihre Optionen." href="/de/modules/structure/" />
  <LinkCard title="Moduldienste" description="Was ein Dienst während einer Aktualisierung erhält und wie er zurückruft." href="/de/modules/services/" />
</CardGrid>