# Aufbau eines Pakets

> Die Referenz für NXM-Pakete. Welche Dateien ein .nxm-Archiv enthält, jedes Feld des Manifests module.json mit Typ und Regeln, die Schlüssel, die nexxus daraus ableitet, und wie nexxus ein Paket prüft, bevor es angenommen wird.

Ein NXM-Paket ist ein Ordner mit einem Manifest, einem Changelog und ein paar optionalen Dateien, gepackt als ZIP-Archiv mit der Endung `.nxm`. Fast alles, was ein Modul ausmacht, steht im Manifest `module.json`: seine Fachobjekte, Oberflächen, Menüeinträge, Aktionen, Rollen und das, worauf es zugreifen darf. nexxus liest diese Datei und baut das Modul daraus. Diese Seite beschreibt jede Datei und jedes Feld. Ein vollständiges Beispiel Schritt für Schritt finden Sie unter [Ihr erstes Modul](/de/modules/first-module/).

## Die Dateien eines Pakets

<FileTree>
- acme.besucher/1.0.0/
  - module.json das Manifest (Pflicht)
  - CHANGELOG.md ein Abschnitt je Version (Pflicht)
  - ui/
    - empfang.html eine Datei je eigener Ansicht
  - templates/
    - brief.html ein Brief, den eine Mail-Aktion füllt und als PDF versendet
  - data/
    - besuch.json Datensätze, die jede Installation bekommt
  - demo/
    - besuch.json Beispieldatensätze, nur auf Wunsch
    - files/
      - hausordnung.pdf ein Dokument, auf das die Beispieldaten verweisen
  - MANIFEST.sha256 schreibt das Paketierungsskript
  - SIGNATURE schreibt das Paketierungsskript beim Signieren
</FileTree>

| Datei | Pflicht | Inhalt |
|---|---|---|
| `module.json` | ja | Das Manifest, beschrieben weiter unten. |
| `CHANGELOG.md` | ja | Im Format Keep a Changelog. Es braucht eine Überschrift `## [<version>]` für die Version aus `module.json`, etwa `## [1.0.0] - 2026-10-12`. nexxus zeigt diese Abschnitte der Administration vor jeder Installation und Aktualisierung. |
| `ui/<name>.html` | für eigene Ansichten | Eine abgeschlossene HTML-Datei je eigener Ansicht, CSS und JavaScript eingebettet, höchstens 2 MB. Siehe [Ansichten und das View-SDK](/de/modules/views/). |
| `templates/<name>.html` | für eine Mail-Aktion mit PDF | Eine statische HTML-Vorlage, die nexxus füllt und als PDF ausgibt, höchstens 512 KB. Ohne Skripte, und sie lädt nichts nach: siehe [Vorlagen](#vorlagen). |
| `data/<fachobjekt>.json` | nein | Datensätze, die jede Installation bekommt, etwa ein Anforderungskatalog. |
| `demo/<fachobjekt>.json` | nein | Beispieldatensätze. nexxus legt sie nur an, wenn die Administration bei der ersten Installation Beispieldaten wünscht. |
| `demo/files/<name>` | nein | Dateien, auf die die Beispieldatensätze verweisen. Sie landen unter Dateien. |
| `MANIFEST.sha256` | nein | Je Datei eine Zeile `<sha256>  <pfad>`, nach Pfad sortiert. Liegt sie bei, muss sie jede andere Datei mit dem richtigen Hash nennen, und nichts sonst. |
| `SIGNATURE` | nein | `{"keyId": "...", "alg": "Ed25519", "sig": "..."}`, eine Ed25519-Signatur über `MANIFEST.sha256`. |

`MANIFEST.sha256` und `SIGNATURE` schreibt das Paketierungsskript für Sie. Wie die Datendateien funktionieren, mit Verweisen zwischen Datensätzen und Datumsangaben relativ zum Installationstag, steht unter [Daten und Aktualisierungen](/de/modules/data-and-upgrades/). Signieren und Hochladen beschreibt [Paketieren, installieren und aktualisieren](/de/modules/publish/).

Eine hochgeladene Version ändert sich nie mehr. Jede Korrektur, auch ein Tippfehler in einer Ansicht, bekommt eine neue Versionsnummer in einem neuen Ordner.

### Vorlagen

Eine Vorlage in `templates/` ist das Layout eines Dokuments, das nexxus mit den Werten eines Datensatzes füllt und als PDF ausgibt, heute der Brief einer [Mail-Aktion](#mail-aktionen). Der Dateiname besteht aus Buchstaben, Ziffern, `-` und `_`, Unterordner gibt es nicht, und eine Vorlage hat höchstens 512 KB.

Der PDF-Dienst würde alles laden, worauf eine Vorlage zeigt. Deshalb enthält eine Vorlage keine Skripte und lädt nichts nach, sonst lehnt nexxus sie beim Hochladen ab: kein `<script>`, `<noscript>`, `<link>`, `<iframe>`, `<frame>`, `<object>`, `<embed>` oder `<applet>`, keine Ereignis-Attribute wie `onclick`, keine `javascript:`-Adressen und kein CSS-`@import`. Jede Adresse in `src`, `href` und den anderen URL-Attributen sowie in CSS-`url()` und `image-set()` muss eine `data:`-URL oder eine Sprungmarke wie `#betreff` sein; Links dürfen auch `mailto:` und `tel:` sein. Bilder kommen als `data:`-URL hinein, Schriften sind Systemschriften, und Stile stehen in einem `<style>` der Vorlage.

## Ein Manifest auf das Nötigste beschränkt

Das kleinste sinnvolle Modul hat ein Fachobjekt, eine Liste und einen Menüeintrag. Dieses Besucherbuch für den Empfang ist vollständig und gültig:

```json
{
  "key": "acme.besucher",
  "kind": "package",
  "publisher": "acme",
  "name": {"de": "Besucherbuch", "en": "Visitor log"},
  "description": {"de": "Wer war wann bei uns zu Besuch und warum.", "en": "Who visited us, when and why."},
  "icon": "badge",
  "area": "projekte",
  "version": "1.0.0",
  "platform": ">=0.2.0 <1.0.0",
  "roles": [
    {"key": "empfang", "label": {"de": "Empfang", "en": "Reception"}, "permissions": ["besuch.read", "besuch.write"]}
  ],
  "entities": [
    {
      "key": "besuch",
      "label": {"de": "Besuch", "en": "Visit"},
      "labelPlural": {"de": "Besuche", "en": "Visits"},
      "titleField": "name",
      "searchFields": ["name"],
      "fields": [
        {"key": "name", "type": "string", "required": true, "label": {"de": "Name", "en": "Name"}},
        {"key": "firma", "type": "ref", "target": "core:partner", "label": {"de": "Firma", "en": "Company"}},
        {"key": "datum", "type": "date", "required": true, "indexed": true, "label": {"de": "Datum", "en": "Date"}},
        {"key": "zweck", "type": "enum", "indexed": true, "default": "termin", "label": {"de": "Zweck", "en": "Purpose"},
         "values": [
           {"key": "termin", "label": {"de": "Termin", "en": "Meeting"}},
           {"key": "lieferung", "label": {"de": "Lieferung", "en": "Delivery"}},
           {"key": "wartung", "label": {"de": "Wartung", "en": "Maintenance"}}]}
      ]
    }
  ],
  "views": [
    {"key": "besuch.list", "type": "list", "entity": "besuch", "label": {"de": "Besuche", "en": "Visits"},
     "columns": ["datum", "name", "firma", "zweck"], "filters": ["zweck", "datum"],
     "sort": {"field": "datum", "dir": "desc"}, "create": true}
  ],
  "menu": [
    {"key": "besuche", "label": {"de": "Besuche", "en": "Visits"}, "icon": "badge", "view": "besuch.list", "order": 1}
  ]
}
```

Nach der Installation hat der Mandant unter **Organisation** einen Menüabschnitt „Besucherbuch“ mit dem Eintrag „Besuche“. Die Liste hat ein Suchfeld, Filter für Zweck und Datum, eine Schaltfläche zum Anlegen und eine Datensatzseite mit dem Reiter <Ui>Daten</Ui>, gebaut aus den vier Feldern. Die Rolle „Empfang“ darf Besuche lesen und schreiben. Eine Ansicht vom Typ `record` braucht das Modul nicht: Ohne sie zeigt nexxus einfach alle Felder.

## Felder auf oberster Ebene

Texte, die Menschen lesen, sind immer ein Objekt mit deutscher und englischer Fassung, `{"de": "...", "en": "..."}`, dazu wahlweise einer polnischen: `{"de": "...", "en": "...", "pl": "..."}`. `name` und `description` brauchen Deutsch und Englisch, überall sonst genügt eine der beiden. Ein polnischer Text allein zählt nie als eine davon. Jede Person bekommt den Text in ihrer Sprache, sonst auf Englisch, sonst auf Deutsch; wer nexxus auf Polnisch nutzt, sieht bei einem Paket ohne polnische Texte also die englischen. Weitere Schlüssel sind in diesen Objekten nicht erlaubt, und unbekannte Felder lehnt nexxus im ganzen Manifest ab.

| Feld | Typ | Pflicht | Bedeutung | Beispiel |
|---|---|---|---|---|
| `key` | Text | ja | Der Name des Moduls, `<herausgeber>.<name>`, Kleinbuchstaben, Ziffern und `-`. Alles andere leitet sich daraus ab. | `"acme.besucher"` |
| `kind` | Text | ja | Immer `"package"`. | `"package"` |
| `publisher` | Text | ja | Muss dem Teil von `key` vor dem Punkt entsprechen. | `"acme"` |
| `name` | Text, de und en | ja | Steht im Menü, in Seitentiteln und im Katalog. | `{"de": "Besucherbuch", "en": "Visitor log"}` |
| `description` | Text, de und en | ja | Steht im Katalog. | |
| `icon` | Text | nein | Ein Name aus Material Symbols. | `"badge"` |
| `area` | Text | ja | Der Fachbereich des Moduls (Tabelle unten). Er ordnet das Modul ein, er schaltet es nicht. | `"projekte"` |
| `version` | Text | ja | `major.minor.patch`, ohne Zusatz. | `"1.0.0"` |
| `platform` | Text | nein | Die nexxus-Versionen, auf denen das Paket läuft, als Versionsbereich. | `">=0.2.0 <1.0.0"` |
| `requires` | Objekt | nein | Eingebaute Module, die dieses Paket braucht, jeweils mit Versionsbereich. Nie ein anderes Paket. | `{"partners": ">=1.0.0"}` |
| `permissions` | Liste | nein | Zusätzliche Rechte neben Lesen und Schreiben je Fachobjekt. | siehe [Rechte und Rollen](#rechte-und-rollen) |
| `roles` | Liste | nein | Rollen, die nexxus bei der Installation anlegt. | |
| `scopes` | Liste von Texten | nein | Daten außerhalb des Pakets, die Aktionen und Dienste nutzen dürfen. Braucht die Zustimmung der Administration. | `["core:partner:read"]` |
| `entities` | Liste | nein | Die Fachobjekte. | siehe [Fachobjekte](#fachobjekte) |
| `views` | Liste | nein | Listen, Datensatzseiten und eigene Ansichten. | siehe [Ansichten](#ansichten) |
| `menu` | Liste | nein | Menüeinträge. | |
| `actions` | Liste | nein | Arbeitsschritte an Datensätzen. | |
| `services` | Liste | nein | Moduldienste außerhalb von nexxus. | |
| `settings` | Liste | nein | Einstellungen einer Installation, die die Administration pflegt. | |
| `aiTools` | Objekt | nein | Für welche Fachobjekte und Aktionen der KI-Assistent Werkzeuge bekommt. | |
| `assistant` | Objekt | nein | Anweisungen, Beispiel-Anweisungen, Avatare, Skills und KI-Knöpfe für den KI-Assistenten. | |
| `migrations` | Objekt | nein | Schritte, die gespeicherte Datensätze bei einer Aktualisierung umstellen. | |
| `portal` | Objekt | nein | Die öffentliche Seite des Pakets für Kunden ohne Konto. | siehe [Kundenportal](/de/modules/portal/) |
| `calendar` | Liste | nein | Datensätze mit Datum, die der Kalender von nexxus zeigt. | siehe [Kalenderquellen](/de/modules/calendar/) |
| `dashboards` | Liste | nein | Bis zu drei Vorlagen für die Startseite, ab nexxus 0.14.0. | siehe [Startseiten-Vorlagen](#startseiten-vorlagen-dashboards) |

Versionsbereiche folgen der Schreibweise von npm: Durch Leerzeichen getrennte Vergleiche müssen alle gelten (`>=0.2.0 <1.0.0`), `||` trennt Alternativen, `^1.2.0` und `~1.2.0` wirken wie bei npm, `*` heißt jede Version, und eine einzelne `1.2.3` heißt genau diese Version.

### Fachbereiche

`area` legt fest, in welchem Menübereich der Abschnitt des Moduls erscheint. Danach richtet sich auch, wo das Modul im Rolleneditor und in der Modulliste steht. Der Bereich ordnet nur ein: Er schaltet das Modul weder ein noch aus.

| `area` | Menübereich in der Leiste |
|---|---|
| `main`, `werkzeuge` | Start |
| `zeit`, `abwesenheit`, `partner`, `projekte`, `rechnungen`, `kontingente` | Organisation |
| `sicherheit` | ISMS |
| `dokumente` | Dokumente |
| `ai` | KI |
| `teams`, `system` | Administration |

Pakete von vor Oktober 2026 dürfen noch `verwaltung` nennen. Ein ausgeliefertes Paket ändert sich nicht, deshalb bleibt es gültig und steht weiter unter Organisation. Ein neues Paket nennt einen der Schlüssel aus der Tabelle.

Ein Mandant kann Fachbereiche im Einrichtungsschritt <Ui>Bereiche</Ui> abschalten (siehe [Erste Einrichtung](/de/setup/first-run/#schritt-3-bereiche)). Ein Paket betrifft das nicht: Sein Menüabschnitt, seine Seiten, Datensätze, Aktionen und Werkzeuge des KI-Assistenten bleiben für alle mit seinen Rechten, und Verweise auf Kerndaten funktionieren weiter, auch wenn der Bereich abgeschaltet ist, dem diese Daten gehören. Nur Deaktivieren oder Deinstallieren nimmt das Paket weg. Die eine Ausnahme ist eine Aktion vom Typ `core-action`: Sie führt eine Aktion von nexxus selbst aus, und die Aktionen abgeschalteter Bereiche bietet nexxus nicht an.

## Schlüssel, die nexxus ableitet

Im Manifest schreiben Sie kurze Schlüssel. nexxus macht daraus vollständige Schlüssel, die im Rolleneditor, in den Werkzeugen des KI-Assistenten und in der Schnittstelle auftauchen:

| Was | Vollständiger Schlüssel | Beispiel |
|---|---|---|
| Lese- und Schreibrecht eines Fachobjekts | `<modul>.<fachobjekt>.read`, `<modul>.<fachobjekt>.write` | `acme.besucher.besuch.read` |
| Zusätzliches Recht | `<modul>.<recht>` | `curiaos.kanzlei.freigeben` |
| Rolle | `<modul>.<rolle>` | `acme.besucher.empfang` |
| Werkzeug des KI-Assistenten für ein Fachobjekt (Suchen, Lesen) | `nx_<herausgeber>_<name>_<fachobjekt>_<search oder get>` | `nx_acme_besucher_besuch_search` |
| Aktion des KI-Assistenten für ein Fachobjekt (Anlegen, Ändern) | `<modul>:<fachobjekt>.<create oder update>` | `acme.besucher:besuch.create` |
| Aktion des KI-Assistenten für eine Aktion | `<modul>:<aktion>` | `curiaos.kanzlei:frist-berechnen` |
| Ereignis eines Fachobjekts | `<fachobjekt>.created`, `.updated`, `.deleted` | `besuch.created` |

In Werkzeugnamen wird aus `-` ein `_`, Aktionsschlüssel behalten ihn. Im Rolleneditor tragen die Rechte eines Pakets deutsche Bezeichnungen: Die Rechte der Fachobjekte bildet nexxus aus der Mehrzahl, also „Besuche lesen“ und „Besuche bearbeiten“, zusätzliche Rechte zeigen ihr `label`.

## Fachobjekte

Ein Fachobjekt (im Manifest `entity`) ist ein Geschäftsobjekt. Alle Datensätze aller Pakete liegen in einem gemeinsamen Datensatzspeicher von nexxus; ein Paket legt nie eigene Datenbanktabellen an.

| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
| `key` | Text | ja | Kleinbuchstaben, Ziffern, `_` und `-`. `actions` und `p` sind reserviert. |
| `label` | Text | ja | Einzahl, etwa „Besuch“. |
| `labelPlural` | Text | ja | Mehrzahl, etwa „Besuche“. Für Menü, Listen und Rechte. |
| `icon` | Text | nein | Name aus Material Symbols, erscheint am Reiter <Ui>Daten</Ui>. |
| `titleField` | Text | nein | Das Feld, das einen Datensatz in Kopfzeilen, Links und Auswahlfeldern benennt. Fehlt es, heißt ein Datensatz `#<id>`. |
| `searchFields` | Liste | nein | Felder vom Typ `string`, `text`, `enum` oder `ref`, in denen das Suchfeld sucht. Ein `ref`-Feld findet den Datensatz über das, worauf es zeigt: einen Datensatz des Pakets über dessen eigene `searchFields`, Kerndaten über ihren Namen, etwa eine Beteiligung über den Namen des Kontakts. |
| `journal` | Wahrheitswert | nein | Das Fachobjekt hält fest, was geschehen ist, etwa wer an einem Auftrag was getan hat. Personen legen keine Einträge an, ändern und löschen keine, auch Administratoren nicht. Das Paket schreibt sie über seinen Moduldienst, auch für eine Person, die das Fachobjekt nur lesen darf, oder über eine `create`-Aktion. Listen und KI-Assistent bieten dort kein Neu, Bearbeiten oder Löschen an, und ein Datenimport schreibt nie hinein. Eine Aktion vom Typ `update` oder `dictate`, eine `core-action` mit `store` und eine `mail` mit `set` dürfen nicht darauf zielen. Ein älteres nexxus lehnt ein Paket mit `journal` beim Hochladen ab. |
| `fields` | Liste | ja | Mindestens ein Feld. |

### Feldtypen

| `type` | Gespeichert als | Optionen und Hinweise |
|---|---|---|
| `string` | Text, ohne Leerzeichen am Rand | `maxLength`, Standard 500. |
| `text` | Text | Langer Text, als Textbereich. `maxLength` Standard 20 000. Nicht indizierbar. |
| `number` | Zahl | `min`, `max`, `format`: `integer`, `decimal` oder `currency` (Anzeige in Euro oder in der Währung, die `currency` nennt). |
| `boolean` | `true` oder `false` | Als Schalter. |
| `date` | `"JJJJ-MM-TT"` | |
| `datetime` | ISO 8601 mit Zeitzone, gespeichert in UTC | |
| `enum` | der Schlüssel eines Werts | `values`: Liste von `{"key", "label"}`, mindestens einer. `multiple: true` speichert eine Liste. |
| `ref` | die Id eines Datensatzes | `target` (siehe unten). `multiple: true` speichert eine Liste. |
| `json` | beliebiges JSON | Nicht indiziert, nicht im erzeugten Formular. |
| `geo` | `{"lat": -33.92, "lng": 18.42}`, Grad in WGS84, sieben Nachkommastellen | Ein Ort: `lat` von -90 bis 90, `lng` von -180 bis 180. Die Datensatzseite zeigt ihn als Nadel auf einer kleinen Karte, im Formular lässt er sich eintippen, über eine Adresse suchen oder durch Verschieben der Nadel setzen. `geocodeFrom` füllt ihn aus Adressfeldern. Nicht indizierbar, nie in einem öffentlichen Formular. |

Ein `ref` zeigt auf:

- ein Fachobjekt desselben Pakets: `"target": "besuch"`, oder
- einen Datensatz von nexxus: `core:partner` (eine Firma oder Person unter Kontakte), `core:partnerContact` (ein Ansprechpartner eines Partners), `core:user`, `core:dmsDocument` (ein Dokument unter Dateien), `core:ismsMeasure`, `core:project`, `core:invoice` oder `core:opportunity` (ein mögliches Geschäft unter Vertrieb).

Ein Verweis zeigt nie auf ein Fachobjekt eines anderen Pakets (siehe [Pakete sind in sich geschlossen](#pakete-sind-in-sich-geschlossen)).

Wählt jemand einen Wert, prüft nexxus, ob der Datensatz im Mandanten existiert, und bei Dokumenten, ob die Person es lesen darf. Die Auswahl für Partner, Ansprechpartner, Projekte, Rechnungen und ISMS-Maßnahmen steht allen offen, die diese Liste in nexxus sehen dürfen; bei Ansprechpartnern stehen nur aktive zur Wahl, und eine Rechnung ohne Nummer erscheint als `#<id>`. Benutzer und Dokumente stehen allen im Mandanten offen, Dokumente nur die, die die Person lesen darf.

### Feldoptionen

| Option | Typ | Bedeutung |
|---|---|---|
| `key` | Text | Pflicht. Kleinbuchstaben, Ziffern, `_` und `-`. |
| `label`, `hint` | Text | Feldbezeichnung und Hilfetext im Formular. |
| `required` | Wahrheitswert | Braucht einen Wert. |
| `default` | Wert | Wird beim Anlegen gesetzt, wenn das Feld leer ist. Muss zum Typ passen. |
| `readonly` | Wahrheitswert | Nur Aktionen, Moduldienste, Migrationen und Datendateien dürfen den Wert setzen. Das Formular einer Person zeigt ihn, ändert ihn aber nicht. |
| `readonlyWhen` | Bedingung | Sperrt das Feld für Personen, sobald der gespeicherte Datensatz eine Bedingung erfüllt, in der Form des `when` einer Aktion, etwa `{ "field": "status", "in": ["zugeordnet", "verworfen"] }`. Siehe unten. |
| `indexed` | Wahrheitswert | Macht das Feld filter- und sortierbar. Nicht für `text`, `json` und `geo`. |
| `unique` | Wahrheitswert | Zwei Datensätze dürfen den Wert nicht teilen (verglichen ohne Leerzeichen am Rand und ohne Groß- und Kleinschreibung). Nur für `string` und `number`, schließt `indexed` ein. |
| `sequence` | Text | Nur für `string`: ein Nummernformat wie `"{yyyy}/{n:4}"`. nexxus vergibt beim Anlegen die nächste Nummer. Schließt `unique` und `readonly` ein. |
| `geocodeFrom` | Liste | Nur für `geo`: Felder desselben Fachobjekts vom Typ `string`, `text` oder `enum`, etwa `["address", "city", "country"]`. Siehe unten. |
| `currency` | Text | Nur für eine `number` mit `format: currency`: der ISO-4217-Code in Großbuchstaben, etwa `"ZAR"`. Listen und Datensatzseiten zeigen den Betrag mit dem Währungssymbol in der Sprache der Person, etwa `12.500,00 R` oder `R 12,500.00`. Ohne Angabe ist der Betrag in Euro (`12.500,00 €`, `€12,500.00`). Ein unbekannter Code wird beim Hochladen abgelehnt. |
| `group` | Text | Abschnitt im erzeugten Formular, wenn die Datensatzansicht keine `sections` hat. |
| `maxLength`, `min`, `max`, `format` | | Siehe die Typen oben. |
| `values`, `multiple` | | Für `enum` (`multiple` auch für `ref`). `unique` und `multiple` schließen sich aus. |
| `target` | Text | Für `ref`. |
| `accept` | Liste | MIME-Typen für Dokumentverweise, etwa `["image/*"]`. Geprüft für Dateien, die eine Ansicht in das Feld hochlädt (`nx.files.upload`), nicht wenn eine Person ein vorhandenes Dokument auswählt. Nur Bildtypen lassen die Datensatzseite die Dokumente als Bilder zeigen (siehe unten). |

Die Währung ist eine Beschriftung, keine Umrechnung: Ändert eine neue Version `currency`, stehen die gespeicherten Beträge danach in der neuen Währung. E-Mail-Texte und Vorlagen bekommen die Zahl ohne Symbol (`12.500,00`, auf Englisch `12,500.00`, auf Polnisch `12 500,00`) und schreiben die Währung selbst.

Mit `geocodeFrom` ermittelt nexxus die Koordinaten selbst. Wird ein Datensatz angelegt oder ändert sich eines der genannten Felder, und ist der Ort leer oder die Adresse eine andere, setzt nexxus den Text dieser Felder zusammen (eine Auswahlliste mit ihrer Bezeichnung), sucht ihn in der Adresssuche der Installation und speichert den ersten Treffer. Setzt dieselbe Anfrage den Ort selbst, gilt dieser Wert. Findet die Suche nichts, scheitert sie oder dauert sie länger als drei Sekunden, wird trotzdem gespeichert, und der Ort bleibt, wie er war. Daten- und Beispieldateien bringen ihre Koordinaten selbst mit; beim Installieren wird nichts gesucht.

Im Formular fragt <Ui>Adresse suchen</Ui> über der Karte ab dem dritten Zeichen dieselbe Adresssuche und bietet bis zu fünf Treffer an. Ein gewählter Treffer setzt die Nadel und zeigt sie auf Straßenebene. Die Suche läuft auf dem Server, der Browser fragt also keinen Dritten, und jede Person hat 20 Suchen pro Minute.

Ein `ref` auf `core:dmsDocument`, dessen `accept` nur Bildtypen nennt, zeigt seine Dokumente auf der Datensatzseite als Vorschaubilder. Ein Klick öffnet ein Bild groß, mit den Pfeiltasten blättern Sie durch die übrigen. Beim Bearbeiten stehen die Vorschaubilder unter der Auswahl. Ohne `accept` macht nexxus das ebenso, wenn jeder gewählte Dateiname eine Bildendung hat (`.jpg`, `.png`, `.webp` und ähnliche).

`readonlyWhen` sperrt ein Feld, sobald ein Datensatz einen bestimmten Stand erreicht hat, etwa Absender, Eingangsdatum und Betreff eines Schreibens, nachdem es einer Akte zugeordnet wurde:

```json
{ "key": "absender", "type": "string", "readonlyWhen": { "field": "status", "in": ["zugeordnet", "verworfen"] } }
```

Die Bedingung nennt Felder desselben Fachobjekts, wie das `when` einer Aktion (auch `set` und `all`), und jeder Wert in `in` muss ein Schlüssel dieser Auswahlliste sein. nexxus prüft sie am gespeicherten Datensatz, nicht an der Änderung, die gerade gespeichert wird: Ein neuer Datensatz ist nie gesperrt, und das Speichern, das den Status setzt, darf die Felder, die er sperrt, noch mitbringen. Ist das Feld gesperrt, zeigt die Datensatzseite es schreibgeschützt mit einem Hinweis, und eine Änderung durch eine Person lehnt der Server mit einer Meldung ab, die den Grund nennt, etwa <Ui>Absender ist gesperrt: Status ist „Zugeordnet“.</Ui> Den unveränderten Wert mitzuspeichern geht. Aktionen, Moduldienste und Migrationen schreiben das Feld weiterhin, und der KI-Assistent kennt die Sperre. Ein Diktat-Knopf (`type: dictate`) auf ein gesperrtes Feld wird nicht angeboten, und im Kalender lässt sich ein Datensatz, dessen Beginn- oder Endfeld gesperrt ist, weder ziehen noch in der Länge ändern noch per Tastatur verschieben. `readonlyWhen` steht nicht neben `readonly`, nicht an einem `sequence`-Feld und nicht in `settings`. Ein Paket mit `readonlyWhen` braucht nexxus 0.14.0 oder neuer und gibt deshalb `"platform": ">=0.14.0 <1.0.0"` an.

Jedes `ref` ist automatisch indiziert. Ein `sequence`-Format kennt die Platzhalter `{yyyy}`, `{yy}` und genau ein `{n}` oder `{n:<stellen>}`; aus `"GER-{n:4}"` wird etwa `GER-0001`. Steht ein Jahr im Format, beginnt der Zähler jedes Jahr neu. Er zählt je Mandant, Modul, Fachobjekt und Feld.

## Ansichten

Ansichten bestimmen, was die Mitarbeitenden sehen. Es gibt fünf Typen:

| `type` | Gezeichnet von | Was es ist |
|---|---|---|
| `list` | nexxus | Die Liste eines Fachobjekts: `entity`, `columns`, `filters`, `sort`, `create`. |
| `record` | nexxus | Der Reiter <Ui>Daten</Ui> eines Datensatzes: `entity`, `sections`, `related`, `actions`. |
| `related` | nexxus | Ein Reiter mit Datensätzen, die auf einen anderen Datensatz verweisen, etwa auf einen Partner. |
| `custom` | Ihr HTML, in der Sandbox | Eine Seite, ein Reiter, ein Dialog oder eine Kachel: `slot`, `html`, `target`, `when`. |
| `map` | nexxus | Die Datensätze eines Fachobjekts auf einer Karte: `entity`, `geoField`, `titleField`, `subtitleFields`, `colorField`, `imageField`. |

| Feld | Genutzt von | Bedeutung |
|---|---|---|
| `key` | allen | Pflicht. Kleinbuchstaben, Ziffern, `_`, `.` und `-`, etwa `besuch.list`. |
| `type` | allen | Pflicht. |
| `label`, `icon` | allen | Bezeichnung für Reiter, Menü und Dialog. |
| `entity` | list, record, related, map | Das gezeigte Fachobjekt. |
| `columns` | list, related | Feldschlüssel oder `{"field": "status", "display": "badge"}`. |
| `filters` | list | Indizierte Felder, die als Filter angeboten werden. Auswahllisten werden zur Mehrfachauswahl, Verweise zum Auswahlfeld, Datumsfelder zum Zeitraum. |
| `sort` | list | `{"field": "<indiziertes feld>", "dir": "asc"}` oder `"desc"`. Ohne Angabe steht der neueste Datensatz oben. |
| `create` | list | `true` zeigt die Schaltfläche zum Anlegen. |
| `sections` | record | `[{"key", "label", "fields": [...]}]`. Nur die genannten Felder stehen auf dem Reiter <Ui>Daten</Ui>. |
| `related` | record | `[{"entity", "via", "label", "columns"}]`: Listen der Datensätze, deren Verweisfeld `via` hierher zeigt. |
| `actions` | record | Genau die Aktionen und Dialoge, die die Aktionsleiste des Datensatzes anbietet, in dieser Reihenfolge. |
| `slot`, `target` | related, custom | Wo die Ansicht erscheint und an welchem Fachobjekt. |
| `via` | related | Das Verweisfeld, das auf das Ziel zeigt. |
| `html` | custom | `ui/<name>.html`. |
| `when` | custom | Zeigt die Ansicht nur, wenn ein Feld bestimmte Werte hat. |
| `size` | custom | `s`, `m` oder `l` für Kacheln auf dem Dashboard. |
| `geoField` | map | Pflicht. Das `geo`-Feld, das einen Datensatz auf die Karte setzt. Datensätze ohne Wert fehlen auf der Karte. |
| `titleField` | map | Titel der Markierung. Ohne Angabe das `titleField` des Fachobjekts. |
| `subtitleFields` | map | Felder unter dem Titel. |
| `colorField` | map | Ein Feld vom Typ `string`, `enum`, `boolean` oder ein einfacher `ref`: Jeder Wert bekommt eine eigene Farbe. |
| `imageField` | map | Ein `ref` auf `core:dmsDocument`: Sein erstes Bild steht in der Liste neben der Karte vor dem Datensatz. |

Eine Karte der Ferienhäuser, eingefärbt nach Region, mit einem Foto je Haus in der Liste, als eigener Menüeintrag:

```json
{"key": "listing.map", "type": "map", "entity": "listing", "label": {"de": "Karte", "en": "Map"},
 "geoField": "location", "titleField": "name", "subtitleFields": ["city", "kind"], "colorField": "region", "imageField": "photos"}
```

nexxus zeichnet die Karte selbst, nicht in der Sandbox einer eigenen Ansicht, und ein Klick auf eine Markierung öffnet den Datensatz. Die Liste neben der Karte zeigt die Datensätze im sichtbaren Ausschnitt: Verschieben oder Zoomen grenzt sie ein, <Ui>Alle zeigen</Ui> holt alle Markierungen zurück ins Bild. Die Karte kommt vom Server der Installation; ist dort keine eingerichtet, zeigt die Ansicht einen Platzhalter.

Um die Karte auf einem Datensatz zu öffnen, ruft eine eigene Ansicht `nx.nav.open({view: 'listing.map', id})` auf; das öffnet `/x/<modul>/p/<ansicht>?focus=<id>`. Die Karte zeigt dann diesen Datensatz auf Straßenebene, seine Markierung ist gewählt und seine Zeile in der Liste hervorgehoben. Hat der Datensatz keinen Ort oder gibt es ihn nicht, zeigt die Karte alle Datensätze wie ohne `focus`.

Ohne `list`-Ansicht baut nexxus die Liste aus dem Titelfeld und den ersten vier indizierten Feldern. Ohne `record`-Ansicht zeigt der Reiter <Ui>Daten</Ui> alle Felder außer `json`, gruppiert nach `group`. Slots, den Aufbau der Datensatzseite und das SDK für eigene Ansichten beschreibt [Ansichten und das View-SDK](/de/modules/views/).

## Menü

| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
| `key` | Text | ja | |
| `label` | Text | ja | Bezeichnung des Eintrags. |
| `icon` | Text | nein | Name aus Material Symbols. |
| `view` | Text | ja | Eine Ansicht vom Typ `list` oder `map` oder eine eigene Ansicht im Slot `page`. |
| `order` | Zahl | nein | Position im Abschnitt des Moduls. |

Die Einträge stehen in einem Abschnitt, der so heißt wie das Modul, hinter den eingebauten Abschnitten des Menübereichs. Eine Person sieht einen Eintrag nur, wenn sie das zugehörige Fachobjekt lesen darf; für eine Seite genügt irgendein Recht des Moduls.

## Aktionen

Aktionen sind die Arbeitsschritte eines Moduls: ein Gerät verleihen, einen Entwurf freigeben, eine Frist erledigen.

```json
{
  "key": "zurueckgeben", "entity": "geraet", "type": "update",
  "label": {"de": "Zurückgeben", "en": "Return"}, "icon": "assignment_return",
  "permission": "verleihen",
  "when": {"field": "status", "in": ["verliehen"]},
  "confirm": {"de": "Gerät als zurückgegeben buchen?", "en": "Book the device as returned?"},
  "set": {"status": "verfuegbar", "ausgeliehen_an": null, "rueckgabe_am": null},
  "aiTool": true
}
```

| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
| `key` | Text | ja | |
| `label` | Text | ja | Beschriftung der Schaltfläche. |
| `type` | Text | ja | `update`, `create`, `core-action`, `service` oder `mail`. |
| `entity` | Text | ja | Das Fachobjekt, an dem die Aktion läuft. Ohne existierendes Fachobjekt lehnt nexxus die Aktion ab. |
| `icon` | Text | nein | Name aus Material Symbols. |
| `set` | Objekt | bei `update` | Feldwerte, die am Datensatz gesetzt werden. |
| `creates`, `values` | Text, Objekt | bei `create` | Das Fachobjekt, das entsteht, und seine Feldwerte. |
| `coreAction`, `scope`, `input`, `store` | | bei `core-action` | Führt eine Aktion von nexxus selbst aus: `partner.create`, `partnerContact.create`, `opportunity.create`, `ismsMeasure.create` oder `invoice.createDraft`. `scope` muss unter `scopes` stehen. `input` geht unverändert weiter, `store: {"field", "from": "id"}` schreibt das Ergebnis in ein Feld. |
| `service`, `operation`, `async` | | bei `service` | Ruft eine Operation eines Moduldienstes auf. Mit `async: true` antwortet nexxus, sobald der Dienst den Aufruf angenommen hat. |
| `mail`, `log`, `onSent`, `onFailed` | | bei `mail` | Versendet den Datensatz per E-Mail, siehe [Mail-Aktionen](#mail-aktionen). |
| `permission` | Text | nein | Ein zusätzliches Recht des Moduls. Ohne Angabe braucht die Aktion das Schreibrecht des Fachobjekts. |
| `when` | Objekt | nein | Die Aktion wird nur angeboten und ausgeführt, wenn die Bedingung gilt. |
| `confirm` | Text | nein | Diese Frage stellt nexxus, bevor die Aktion läuft. |
| `aiTool` | Wahrheitswert | nein | Der KI-Assistent darf diese Aktion im Chat vorschlagen. |
| `distinct` | Liste | nein | Mindestens zwei Felder (oder `$user`), deren Werte sich unterscheiden müssen, etwa `["geprueft_von", "$user"]`: Wer prüft, darf nicht die Person sein, die die Aktion ausführt. |

Werte in `set`, `values` und `input` dürfen Platzhalter enthalten. Ein Wert, der genau aus einem Platzhalter besteht, behält seinen Typ: `"{{record.id}}"` bleibt eine Zahl.

| Platzhalter | Wird zu |
|---|---|
| `{{record.<feld>}}` | Ein Feld des Datensatzes, an dem die Aktion läuft. |
| `{{record.<verweis>.<feld>}}` | Ein Feld des Datensatzes dieses Pakets, auf den der Verweis zeigt (eine Ebene). |
| `{{record.id}}` | Die Id des Datensatzes. |
| `{{payload.<schlüssel>}}` | Ein Wert, den der Aufrufer mitgeschickt hat. Fehlt der Schlüssel, lässt `update` das Feld unverändert und `create` lässt es leer; ein ausdrückliches `null` leert es. |
| `{{user.id}}` | Die Person, die die Aktion ausführt. |
| `$today`, `$now` | Das heutige Datum, die aktuelle Uhrzeit. |

`when` nimmt `{"field": "<schlüssel>", "in": [werte]}` oder `{"field": "<schlüssel>", "set": true}` (oder `false`). Mehrere Bedingungen, die alle gelten müssen, stehen als Liste unter `all`: `{"all": [{"field": "status", "in": ["freigegeben"]}, {"field": "kanal", "in": ["email"]}]}`. In `all` stehen nur einzelne Bedingungen, kein zweites `all`. Dieselben Formen gelten überall, wo ein Manifest `when` kennt, auch an Ansichten. nexxus blendet eine Aktion aus, deren Bedingung nicht gilt, und prüft sie vor dem Ausführen noch einmal auf dem Server.

Aktionen, die Eingaben brauchen (eine Person, ein Datum), gehören nicht in die Aktionsleiste des Datensatzes. Schreiben Sie dafür einen Dialog, der die Aktion mit diesen Eingaben aufruft. [Ihr erstes Modul](/de/modules/first-module/) zeigt dieses Muster.

### Mail-Aktionen

Eine Aktion vom Typ `mail` versendet einen Datensatz per E-Mail an einen Kontakt, auf Wunsch mit einem Brief als PDF: etwa einen freigegebenen Entwurf an die Versicherung. Versenden kann sie immer nur ein Mensch, nachdem er eine Vorschau gesehen hat. Die Adresse ermittelt nexxus selbst, legt das PDF unter Dateien ab, hält den Versand in einem Fachobjekt des Pakets fest und trägt später ein, ob die Mail hinausging.

```json
{
  "key": "entwurf-versenden", "entity": "entwurf", "type": "mail",
  "label": {"de": "Versenden", "en": "Send"}, "icon": "send",
  "permission": "versenden", "scope": "core:mail:send",
  "when": {"all": [{"field": "status", "in": ["freigegeben", "versand_fehlgeschlagen"]},
                   {"field": "kanal", "in": ["email"]}]},
  "mail": {
    "to": "empfaenger",
    "subject": "{{record.betreff}}",
    "textSetting": "versand_mailtext",
    "replyToSetting": "versand_antwort_an",
    "locale": "de",
    "pdf": {"template": "templates/brief.html",
            "fileName": "{{record.akte.aktennummer}} {{record.betreff}}.pdf",
            "folder": ["Kanzlei", "Schriftverkehr", "{{record.akte.aktennummer}}"]}
  },
  "log": {"entity": "versand",
          "values": {"akte": "{{record.akte}}", "entwurf": "{{record.id}}", "betreff": "{{record.betreff}}"}},
  "set": {"status": "im_versand"},
  "onSent": {"set": {"status": "versendet", "versendet_am": "$now"}},
  "onFailed": {"set": {"status": "versand_fehlgeschlagen"}}
}
```

| Feld | Pflicht | Bedeutung |
|---|---|---|
| `scope` | ja | Immer `core:mail:send`, und der Scope steht unter `scopes`. |
| `mail.to` | ja | Ein Verweisfeld des Fachobjekts der Aktion mit dem Ziel `core:partner`, nicht `multiple`. Die Mail geht an die E-Mail-Adresse dieses Kontakts. |
| `mail.subject` | ja | Der Betreff, mit Platzhaltern. Eine Zeile. |
| `mail.textSetting` | ja | Eine Einstellung vom Typ `text` oder `string` mit dem Mailtext, Klartext mit Platzhaltern. Die Administration kann ihn auf der Paketseite ändern. nexxus versendet ihn im eigenen Mail-Layout als Schreiben der Organisation, mit ihrem Namen oder Logo; Leerzeilen trennen die Absätze. |
| `mail.replyToSetting` | nein | Eine Einstellung vom Typ `string` mit der Adresse, an die Antworten gehen. |
| `mail.locale` | nein | `de` (Standard), `en` oder `pl`: die Sprache, in der die Werte in Betreff, Mailtext, Dateiname und Ordnern stehen. |
| `mail.pdf.template` | für ein PDF | Eine Vorlage des Pakets, `templates/<name>.html` oder nur `<name>`. |
| `mail.pdf.fileName` | für ein PDF | Der Name des PDFs, mit Platzhaltern. nexxus ersetzt Zeichen, die in Dateinamen nicht stehen dürfen, etwa `/`, `:` und `?`, durch `-`, behält höchstens 200 Zeichen und lässt den Namen auf `.pdf` enden. |
| `mail.pdf.folder` | für ein PDF | Der Ordnerpfad unter Dateien, ein Name je Eintrag, mit Platzhaltern. Ein `/` in einem Wert wird zu `-`, aus `2026/0184` wird also der Ordner `2026-0184`. |
| `log.entity` | ja | Das Fachobjekt des Pakets, das jeden Versand festhält. |
| `log.values` | nein | Weitere Werte für den Versand-Datensatz, mit den Platzhaltern von `create`. |
| `set` | nein | Wird am Datensatz gesetzt, sobald die Mail in der Warteschlange steht. |
| `onSent.set`, `onFailed.set` | nein | Wird am Datensatz gesetzt, wenn die Mail hinausging oder endgültig scheiterte. `$now` und Platzhalter des Datensatzes gehen. |

**Die Adresse kommt aus dem Kontakt.** nexxus liest sie aus dem Kontakt in `mail.to`, nie aus dem Aufruf oder einer Vorlage. Wer versendet, braucht das Recht, Kontakte zu lesen (`partner.partner.read`, die Administration hat es), denn die Vorschau zeigt die Adresse. Hat der Kontakt keine E-Mail-Adresse, bricht die Aktion ab mit „Der Empfänger hat keine E-Mail-Adresse. Bitte im Kontakt ergänzen.“

**Platzhalter.** Betreff, Mailtext, Dateiname, Ordnernamen und die Vorlage kennen diese:

| Platzhalter | Wird zu |
|---|---|
| `{{record.<feld>}}`, `{{record.id}}` | Ein Feld des Datensatzes, so wie seine Seite es zeigt: Verweise mit Namen, Auswahlwerte mit ihrer Beschriftung, Datumsangaben als `14.10.2026`. |
| `{{record.<verweis>.<feld>}}` | Ein Feld des Datensatzes dieses Pakets, auf den der Verweis zeigt (eine Ebene). |
| `{{empfaenger.name}}` | Der Name des Kontakts. |
| `{{empfaenger.anschrift}}` | Die Postanschrift des Kontakts, eine Zeile je Zeile der Anschrift. |
| `{{settings.<schlüssel>}}` | Eine Einstellung der Installation. |
| `{{heute}}` | Das heutige Datum, `TT.MM.JJJJ`. |

**Sprache der Werte.** Ohne `mail.locale` stehen die Werte auf Deutsch: `14.10.2026`, `1.252,50`, die deutsche Beschriftung, „Ja“. Mit `"locale": "en"` stehen sie auf Englisch, wie in britischen Abrechnungszeilen: `14/10/2026`, `1,252.50`, die englische Beschriftung, „Yes“. Mit `"locale": "pl"` stehen sie auf Polnisch: `14.10.2026`, `1 252,50` (ein geschütztes Leerzeichen zwischen den Tausendern), die polnische Beschriftung, „Tak“. Fehlt eine Beschriftung in dieser Sprache, nimmt nexxus die englische, sonst die deutsche. Die Vorlage richtet sich nicht nach `mail.locale`, sondern nach ihrem eigenen `<html lang>`: `lang="en"` (oder `en-GB`, `en-ZA`) ist Englisch, `lang="pl"` (oder `pl-PL`) Polnisch, alles andere Deutsch. Beträge bekommen nie ein Währungszeichen; das schreiben Text oder Vorlage selbst (`R {{record.payout}}`). Uhrzeiten gelten in Europe/Berlin.

`{{payload.<schlüssel>}}` gibt es hier nicht: Was hinausgeht, ist genau das, was die Person in der Vorschau gesehen hat. In der Vorlage wird jeder Wert maskiert, Zeilenumbrüche werden zu `<br>`. Ein Platzhalter, der nichts benennt, bleibt so stehen, wie er geschrieben ist; ein Tippfehler im Mailtext fällt also in der Vorschau auf.

**Vorschau und Bestätigung.** Vor dem Versand holt die Ansicht eine Vorschau: Name und Adresse des Empfängers, Betreff, Mailtext und den Dateinamen des PDFs. Sie schreibt nichts und kommt mit einem Token, das zehn Minuten gilt. Der Versand bringt dieses Token mit; nexxus stellt die Mail neu zusammen und versendet nur, wenn sie genau dem entspricht, wofür das Token ausgestellt wurde: dieselbe Person, derselbe Datensatz in derselben Version, dieselbe Adresse, derselbe Betreff, Text und Dateiname. Ein Token versendet einmal. Die Aufrufe beschreibt [Ansichten und das View-SDK](/de/modules/views/).

**Was beim Versand geschieht**, in einem Schritt, der ganz gelingt oder nichts hinterlässt:

<Flow
  steps={[
    { icon: 'file-text', title: 'PDF', text: 'nexxus füllt die Vorlage und lässt sie ohne JavaScript als PDF erzeugen.' },
    { icon: 'folder-open', title: 'Dateien', text: 'Das PDF landet im Ordnerpfad, dort, wo die Person bearbeiten darf.', tone: 'lavender' },
    { icon: 'mail', title: 'Warteschlange', text: 'Die Mail kommt mit dem PDF als Anhang und der Antwortadresse in die Warteschlange.' },
    { icon: 'list-checks', title: 'Datensatz', text: 'Ein Versand-Datensatz mit Status in_warteschlange, dann set am Datensatz.', tone: 'cyan' },
  ]}
  caption="Die Warteschlange versendet innerhalb einer Minute und meldet das Ergebnis zurück."
/>

Der Ordnerpfad beginnt bei einem obersten Ordner dieses Namens, den die Person sieht, einem eigenen oder einem, der für sie freigegeben ist. Einen Ordner „Kanzlei“, den die Administration anlegt und für das Team freigibt, nutzen deshalb alle, die versenden, und seine Rechte entscheiden, wer die Schreiben sieht. Ist für die Person kein solcher Ordner freigegeben, beginnt der Pfad in ihren eigenen Dateien. Fehlende Ordner legt nexxus nur dort an, wo die Person bearbeiten darf, und sie braucht das Recht zum Bearbeiten für den Ordner, in dem das PDF landet.

Die Warteschlange versendet innerhalb einer Minute. Ging die Mail hinaus, setzt nexxus den Versand-Datensatz auf `versendet` mit `message_id` und `versendet_am` und wendet `onSent` an; scheiterte sie endgültig, auf `fehlgeschlagen` mit `fehler` und wendet `onFailed` an. „Versendet“ heißt, dass der Mailserver die Mail angenommen hat, nicht dass sie angekommen ist. Wer nach einem Fehlschlag erneut versendet, bekommt einen neuen Versand-Datensatz; der alte bleibt. Die Mails stehen außerdem unter <MenuPath to="Betrieb > Wartung > E-Mail-Versand" />. Wer eine solche Mail dort weiterleitet, braucht das Recht, das PDF unter Dateien zu öffnen.

**Das Fachobjekt für den Versand** braucht diese Felder, die nexxus selbst füllt; `log.values` darf sie nicht nennen:

| Feld | Typ | Gefüllt |
|---|---|---|
| `adresse` | `string` | die Adresse, an die die Mail ging |
| `dokument` | `ref` auf `core:dmsDocument` | das PDF, wenn die Aktion eines hat |
| `status` | `enum` mit mindestens `in_warteschlange`, `versendet`, `fehlgeschlagen` | wie oben beschrieben |
| `message_id` | `string` | nach dem Versand |
| `fehler` | `text` | nach einem endgültigen Fehlschlag |
| `ausgeloest_von` | `ref` auf `core:user` | die Person, die versendet hat |
| `ausgeloest_am`, `versendet_am` | `datetime` | wann die Person versendet hat, wann die Mail hinausging |

Die Versand-Datensätze halten fest, was die Kanzlei verlassen hat, deshalb ändert sie niemand nachträglich: Jedes Feld der Tabelle und aus `log.values` muss `readonly` sein, keine andere `update`-Aktion des Pakets darf auf das Versand-Fachobjekt zielen, und Anlegen oder Löschen eines Versand-Datensatzes über die Datensatz-API lehnt nexxus ab (409 `MAIL_LOG`). Eine andere `create`-Aktion darf einen Eintrag anlegen, den die Mail-Warteschlange nicht kennen kann, etwa einen Brief, den jemand per Post verschickt hat. `message_id` und `fehler` setzt sie nicht: Die meldet nur die Warteschlange. Die Seiten bieten dafür keine Schaltfläche an, und der KI-Assistent kann die Versand-Datensätze lesen, aber nicht schreiben. Sie verschwinden nur, wenn das Paket samt Daten deinstalliert wird.

**Der PDF-Dienst.** Das PDF entsteht beim gehosteten PDF-Dienst, der Brief verlässt dafür kurz den Server. Ob das erlaubt ist, entscheidet der Betreiber einer Instanz (Einstellung `nexxus.modules.mail.allow-hosted-pdf-renderer`); ohne diese Freigabe brechen Vorschau und Versand einer Aktion mit `pdf` mit einer Meldung ab, die die Einstellung nennt.

Eine Mail-Aktion ist nie ein Werkzeug des KI-Assistenten: `"aiTool": true` und einen Eintrag unter `aiTools.actions` lehnt nexxus beim Hochladen ab. Der KI-Assistent kann den Entwurf schreiben, versenden tut ihn ein Mensch.

## Rechte und Rollen

```json
"permissions": [
  {"key": "verleihen", "label": {"de": "Geräte verleihen", "en": "Lend equipment"},
   "description": {"de": "Darf Geräte ausleihen und zurücknehmen.", "en": "May lend equipment and take it back."}}
],
"roles": [
  {"key": "verwaltung", "label": {"de": "Geräteverwaltung", "en": "Equipment management"},
   "permissions": ["geraet.read", "geraet.write", "verleihen"]}
]
```

- Jedes Fachobjekt hat ein Lese- und ein Schreibrecht, ohne dass Sie es deklarieren. `permissions` ergänzt weitere (`key` und `label` sind Pflicht). Ein Recht darf nicht so heißen wie ein Fachobjekt.
- Eine Rolle nennt kurze Schlüssel: `<fachobjekt>.read`, `<fachobjekt>.write` oder ein zusätzliches Recht.
- Bei der Installation legt nexxus je Rolle im Manifest eine Berechtigungsrolle an, und die Administration kann ihr im Installationsdialog gleich Personen zuweisen. Sonst bekommt niemand automatisch eine Rolle. Spätere Zuweisungen erledigen Sie im Rolleneditor, siehe [Rollen und Rechte](/de/setup/roles/).

## Scopes

Scopes nennen die Daten außerhalb des Pakets, die seine **Aktionen und Moduldienste** nutzen dürfen. Eigene Ansichten brauchen keine Scopes: Sie handeln immer mit den Rechten der Person, die gerade hinsieht.

| Scope | Erlaubt |
|---|---|
| `core:partner:read` | Partner lesen |
| `core:partner:create` | Kontakte anlegen, über eine `core-action` `partner.create` |
| `core:user:read` | Benutzer lesen |
| `core:dmsDocument:read` | Dokumente unter Dateien lesen |
| `core:dmsDocument:create` | Dateien, die eine Ansicht hochlädt (`nx.files.upload`, etwa Fotos vom Tablet), als Dokumente unter Dateien ablegen, siehe [Ansichten](/de/modules/views/#fotos-und-dateien-aus-der-ansicht) |
| `core:ismsMeasure:read`, `core:ismsMeasure:create` | ISMS-Maßnahmen lesen und anlegen |
| `core:partnerContact:read` | Ansprechpartner lesen |
| `core:partnerContact:create` | Ansprechpartner eines Kontakts anlegen, über eine `core-action` `partnerContact.create`; <Ui>Aktiv</Ui> und <Ui>ISMS-Empfänger</Ui> setzt nexxus selbst, wenn sie fehlen |
| `core:project:read` | Projekte lesen |
| `core:invoice:read` | Rechnungen lesen |
| `core:invoice:create` | Rechnungsentwürfe mit einer Position anlegen, über eine `core-action` `invoice.createDraft`; stellen tut sie eine Person |
| `core:opportunity:read` | Opportunities lesen |
| `core:opportunity:create` | Opportunities anlegen, über eine `core-action` `opportunity.create`; ohne Angabe bekommt sie die erste offene Stufe und EUR. Braucht den Bereich Vertrieb |
| `core:mail:send` | E-Mails im Namen Ihrer Organisation versenden, über eine [Mail-Aktion](#mail-aktionen) |

Jedem Scope stimmt die Administration im Installationsdialog zu. Bringt eine Aktualisierung neue Scopes, fragt nexxus erneut. Scopes auf Datensätze eines anderen Pakets (`pkg:<paket>:<fachobjekt>:read` oder `:write`) gibt es nicht mehr; nexxus lehnt sie beim Hochladen ab.

## Dienste

```json
"services": [
  {"key": "rechner", "label": {"de": "Rechner", "en": "Calculator"},
   "url": "http://localhost:8390/rechner",
   "events": ["besuch.created"], "operations": ["pruefen"]}
]
```

| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
| `key` | Text | ja | |
| `label` | Text | ja | Steht bei der Zustimmung und in Fehlermeldungen. |
| `description` | Text | nein | |
| `url` | Text | ja | `http://` oder `https://`. Der Betreiber der Instanz kann den Dienst woanders hin zeigen lassen. |
| `events` | Liste | nein | Änderungen, die der Dienst bekommt: `<fachobjekt>.created`, `.updated`, `.deleted` der Fachobjekte des eigenen Pakets. |
| `operations` | Liste | nein | Operationen, die Aktionen vom Typ `service` aufrufen dürfen. |

Wie Dienste aufgerufen, angemeldet und betrieben werden, steht unter [Moduldienste](/de/modules/services/).

## Einstellungen

`settings` hat dieselbe Form wie die Felder eines Fachobjekts, nur ohne `ref`. Die Administration pflegt die Werte auf der Paketseite, und Moduldienste bekommen sie bei jedem Aufruf mit.

```json
"settings": [
  {"key": "vorfrist_werktage", "type": "number", "format": "integer", "min": 1, "max": 10, "default": 3, "required": true,
   "label": {"de": "Vorfrist (Werktage vor Fristende)", "en": "Early warning (working days before the deadline)"}}
]
```

## KI-Assistent: aiTools und assistant

```json
"aiTools": {"entities": ["geraet"], "actions": ["frist-berechnen"]},
"assistant": {
  "instructions": {"de": "Du hilfst bei den Leihgeräten des Teams. ...", "en": "You help with the team's loan equipment. ..."},
  "prompts": [
    {"view": "geraet.list", "label": {"de": "Wer hat welches Gerät?", "en": "Who has which device?"},
     "prompt": {"de": "Liste alle verliehenen Geräte mit Person und Rückgabedatum.", "en": "List every device on loan with person and due date."}}
  ],
  "avatars": [
    {"key": "greta", "name": "Greta", "role": {"de": "Geräteausgabe", "en": "Equipment desk"}, "gradient": "teal",
     "persona": {"de": "Nennt immer das Rückgabedatum. ...", "en": "Always names the due date. ..."},
     "greeting": {"de": "Welches Gerät suchen Sie?", "en": "Which device are you looking for?"},
     "expertise": {"de": ["Ausleihe", "Rückgabe"], "en": ["Loans", "Returns"]}}
  ],
  "skills": [
    {"key": "rueckgabe-erinnern", "avatar": "greta",
     "name": {"de": "An Rückgabe erinnern", "en": "Remind about return"},
     "description": {"de": "Entwirft eine Erinnerung an die Person, die das Gerät hat.", "en": "Drafts a reminder to the person who has the device."},
     "instructions": {"de": "Lies das Gerät und die Ausleihe. Entwirf die Erinnerung, versende nichts. ...", "en": "Read the device and the loan. Draft the reminder, send nothing. ..."},
     "buttons": [
       {"entity": "geraet", "label": {"de": "An Rückgabe erinnern", "en": "Remind about return"},
        "prompt": {"de": "Entwirf eine Erinnerung zur Rückgabe von {{title}} (fällig am {{record.rueckgabe}}).", "en": "Draft a reminder to return {{title}} (due {{record.rueckgabe}})."}}
     ]}
  ]
}
```

| Feld | Bedeutung |
|---|---|
| `aiTools.entities` | Je genanntem Fachobjekt bekommt der KI-Assistent die Werkzeuge zum Suchen und Lesen für Personen mit Leserecht und die Aktionen zum Anlegen und Ändern für Personen mit Schreibrecht. |
| `aiTools.actions` | Der KI-Assistent darf jede genannte Aktion vorschlagen. `"aiTool": true` an der Aktion bewirkt dasselbe. Nie für eine Mail-Aktion. |
| `assistant.instructions` | Wird als Skill des Mandanten angelegt, immer aktiv, abgeschaltet, solange das Modul deaktiviert ist, und entfernt, wenn es deinstalliert wird. |
| `assistant.prompts` | Beispiel-Anweisungen, die als Chips in der Assistentenspalte erscheinen, wenn die genannte Ansicht offen ist. `label` und `prompt` sind Pflicht, mindestens mit deutschem Prompt. |
| `assistant.avatars` | Bis zu 5 Avatare, angelegt als firmenweite Avatare des Mandanten. `key` (kebab-case) und `name` sind Pflicht; `role`, `gradient`, `persona` (höchstens 1200 Zeichen), `greeting` und `expertise` (Schlagworte ohne Komma) sind optional. |
| `assistant.skills` | Bis zu 20 Skills, angelegt als firmenweite Skills, die der freie Chat über ihre `description` findet. `key`, `name`, `description` und `instructions` sind Pflicht; `avatar` nennt einen Avatar des Pakets, der dann antwortet. |
| `assistant.skills[].buttons` | Bis zu 3 KI-Knöpfe je Skill auf den Datensatzseiten von `entity`: einem Fachobjekt dieses Pakets oder einer Kernseite als `core:partner`, `core:dmsDocument`, `core:invoice` oder `core:project`. Ein Klick öffnet einen Dialog mit dem Skill, seinem Avatar und dem `prompt`, gefüllt aus dem Datensatz; die Person kann ihn vor dem Senden ändern. Platzhalter: `{{title}}` und `{{record.<feld>}}`, wobei das Feld am Fachobjekt des Pakets existiert (auf einer Kernseite geht jeder Feldname durch). `label` höchstens 80 Zeichen, `prompt` höchstens 2000. |

Die Texte werden in der Standardsprache des Mandanten angelegt (<MenuPath to="Administration > Betrieb > Systemkonfiguration" />), sonst auf Englisch, sonst auf Deutsch. Einen Knopf sehen Personen, die das Fachobjekt lesen (auf einer Kernseite: die diese Seite öffnen) und den Skill sehen dürfen, solange das Modul aktiv ist. Ein Update schreibt Avatare, Skills und Knöpfe anhand ihres Schlüssels neu; was eine neue Version nicht mehr hat, wird entfernt, ihre Avatare werden archiviert, damit ihre Gespräche bleiben. Die Knöpfe eines Paket-Skills lassen sich im Skill-Editor nicht ändern.

Jedes Werkzeug und jede Aktion läuft mit den Rechten der Person, die chattet. Selbst schreibt der KI-Assistent nie: Er schlägt die Aktionen als Karte im Chat vor, der Server prüft bei jedem Schritt Eingaben, Zustand und Berechtigung, und erst der Klick der Person führt sie aus, mehrere auf einmal in einer Transaktion. Eine Freigabe, die nur ein Mensch erteilen soll, etwa für ein Schreiben, ist deshalb eine Aktion **ohne** `aiTool`. Mehr dazu unter [Mit dem KI-Assistenten arbeiten](/de/use/assistant/).

## Startseiten-Vorlagen: dashboards

Eine Vorlage ersetzt den ganzen Aufbau der Startseite mit einem Klick (siehe [Mit einer Vorlage beginnen](/de/use/dashboard/#mit-einer-vorlage-beginnen)). Ein Paket kann bis zu drei eigene Vorlagen mitbringen, meist eine je Rolle, die es anlegt. Sie bestehen aus den Kacheln des Pakets (Ansichten mit `slot` `dashboard.widget`) und eingebauten Kacheln.

```json
"dashboards": [
  {"key": "ausgabe", "label": {"de": "Geräteausgabe", "en": "Equipment desk"},
   "description": {"de": "Was heute zurückkommt und was verliehen ist."}, "icon": "inventory_2",
   "roles": ["verwaltung"],
   "tiles": [
     {"view": "heute-faellig"},
     {"view": "verliehen", "width": 2},
     {"widget": "calendar", "width": 2},
     {"widget": "ask"}
   ]}
]
```

| Feld | Bedeutung |
|---|---|
| `key` | Eindeutig in der Liste. |
| `label` | Der Name der Vorlage, höchstens 60 Zeichen je Sprache. |
| `description` | Optional, höchstens 300 Zeichen. |
| `icon` | Optional, ein Name aus Material Symbols. Ohne Angabe das Symbol des Pakets. |
| `roles` | Optional, Schlüssel aus den `roles` des Pakets, deren Inhaber auf dieser Vorlage starten. Eine Rolle nennt höchstens eine Vorlage. |
| `tiles` | 1 bis 12 Kacheln in der Reihenfolge auf der Seite. Jede nennt genau eins von `view` und `widget`, keine Kachel zweimal. Die Begrüßung steht immer oben und wird nicht aufgeführt. |
| `tiles[].view` | Eine Ansicht dieses Pakets mit `slot` `dashboard.widget`. |
| `tiles[].widget` | Eine eingebaute Kachel ohne Einstellungen: `ask` (<Ui>Frag den Assistenten</Ui>), `onboarding` (<Ui>Einrichtung</Ui>), `ai-sessions` (<Ui>AI Gateway</Ui>), `users` (<Ui>Benutzer</Ui>) oder `calendar` (<Ui>Kalender</Ui>). Auswertungen und eingebettete Ansichten gehen nicht, weil ihre Einstellungen auf die Daten des Mandanten zeigen. |
| `tiles[].width` | Optional, die Breite in Spalten. Eine Ansicht der Größe `s` 1 bis 2, der Größe `m` oder `l` 2 bis 4; `ask` und `onboarding` 2 bis 4, `ai-sessions` und `users` 1 bis 2, `calendar` 1 bis 4. Ohne Angabe beginnt eine Ansicht mit 1 (`s`), 2 (`m`) oder 4 (`l`) Spalten, eine eingebaute Kachel mit ihrer üblichen Breite. |

Die Vorlagen stehen vor <Ui>Standard</Ui>, <Ui>KI</Ui> und <Ui>Leer</Ui>, mit dem Namen des Moduls neben der Zahl der Kacheln, etwa <Ui>4 Widgets · Modul Leihgeräte</Ui>. Eine Person sieht sie, solange das Modul aktiv ist und sie es nutzen darf, wie beim Menü des Moduls, in der Standardsprache des Mandanten. Beim Anwenden fehlt eine eingebaute Kachel, die die Person nicht sehen darf, etwa der <Ui>Kalender</Ui> ohne das Recht dazu. nexxus liest die Vorlagen bei jedem Aufruf aus dem Manifest: Ein Update ändert sie sofort, Deaktivieren oder Entfernen nimmt sie weg. Ein schon angewendeter Aufbau behält seine Kacheln. Eine Vorlage ist keine Vorgabe für alle; dafür wendet ein Admin sie an und speichert den Aufbau als Vorgabe.

Mit `roles` ist eine Vorlage auch die Startseite dieser Rollen. Wer eine davon hat und sein Dashboard nicht angepasst hat, sieht die Vorlage gleich nach der Anmeldung, vor der Vorgabe des Mandanten und <Ui>Standard</Ui>, und auf der Karte steht <Ui>Startseite für Geräteverwaltung</Ui>. Sie bleibt lebendig: Ein Update ändert sie auch für diese Personen, bis sie ihr Dashboard bearbeiten oder eine Vorlage anwenden. Hat jemand Rollen aus zwei Paketen, gewinnt das Paket, dessen Schlüssel alphabetisch zuerst kommt. In einer solchen Startseite erscheint eine Paket-Kachel nur, wenn ihr Modul aktiv ist und die Person es nutzen darf, auch bei Administratoren; nexxus prüft das Modul, nicht was die Ansicht liest. Legen Sie in die Vorlage einer Rolle also nur Kacheln, die diese Rolle ganz lesen darf.

Ein Paket mit `dashboards` braucht nexxus 0.14.0 oder neuer und gibt deshalb `"platform": ">=0.14.0 <1.0.0"` an. Eine ältere Version lehnt es ohnehin ab, weil sie das Feld nicht kennt.

## Migrationen

```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"}]
  }
}
```

Jeder Schlüssel ist eine Version bis einschließlich der eigenen Paketversion. Die Schritte `setDefault`, `renameValue`, `renameField`, `copyField`, `deleteField` und `deleteEntity` (die letzten beiden nur in einer neuen Hauptversion) und ihre Reihenfolge beschreibt [Daten und Aktualisierungen](/de/modules/data-and-upgrades/).

## Pakete sind in sich geschlossen

Ein Paket arbeitet mit seinen eigenen Fachobjekten, mit Kerndaten (den `core:`-Zielen [oben](#feldtypen)) und mit nichts sonst. Es baut nie auf einem anderen Paket auf: Kein Verweis, keine Ansicht, kein Scope, kein Ereignis und keine Datenzeile nennt Fachobjekte eines anderen Pakets, und `requires` nennt nur eingebaute Module. Was zusammengehört, kommt als ein Paket. Bis zum 9. Oktober 2026 konnten Pakete aufeinander aufbauen. Das band Installationen aneinander (eine musste zuerst installiert sein und ließ sich nicht deaktivieren, solange die andere sie nutzte) und verteilte einen Fachbereich auf zwei Installationen.

nexxus lehnt ein Paket beim Hochladen ab, wenn es ein anderes Paket nennt. Jeder Befund ist eine Zeile, die mit der Stelle und „Pakete bauen nicht aufeinander auf:“ beginnt:

| Wo | Abgelehnt | Meldung nach der Stelle |
|---|---|---|
| `requires` | ein Paketschlüssel | „requires nennt das Paket 'muster.akten'. Was zusammengehört, gehört in ein Paket; requires nennt nur Kernmodule“ |
| `scopes` | `pkg:<paket>:<fachobjekt>:read` oder `:write` | „scope 'pkg:muster.akten:akte:read' greift auf Datensätze des Pakets 'muster.akten' zu“ |
| `target` eines Verweises oder einer Ansicht | `<paket>:<fachobjekt>` | „target 'muster.akten:akte' ist ein Datenobjekt des Pakets 'muster.akten'“ |
| `services[].events` | `<paket>:<fachobjekt>.<op>` | „Ereignis 'muster.akten:aktendokument.created' kommt aus dem Paket 'muster.akten'“ |
| `data/`, `demo/` | `{"$key": "<paket>:<fachobjekt>:<key>"}` | „$key 'muster.akten:akte:2026-0201' nennt einen Datensatz des Pakets 'muster.akten'“ |

Schreiben Sie ein Fachobjekt nur mit seinem Schlüssel. Der eigene Paketschlüssel davor (`acme.besucher:besuch` in `acme.besucher`) nennt kein anderes Paket, ist aber auch nicht die Schreibweise des Manifests: Ein so geschriebenes Ziel lehnt nexxus mit „target 'acme.besucher:besuch' ist kein Datenobjekt des Pakets“ ab.

Eine Version, die vor dieser Regel hochgeladen wurde, kann noch ein anderes Paket nennen. nexxus installiert sie nicht und aktualisiert nicht auf sie: „Diese Version baut auf dem Paket 'muster.akten' auf. Pakete bauen nicht mehr aufeinander auf; was zusammengehört, kommt als ein Paket.“ Eine Installation, die schon auf einer solchen Version läuft, läuft weiter, erreicht vom anderen Paket aber nichts.

Von den Fachbereichen des Mandanten hängt ein Paket ebenfalls nicht ab, siehe [Fachbereiche](#fachbereiche). Die [Fallstudie CuriaOS](/de/modules/curiaos/) zeigt ein Paket, das aus zweien entstanden ist.

## Wie nexxus ein Paket prüft

<Flow
  steps={[
    { icon: 'file-archive', title: 'Archiv', text: 'ZIP lesbar, höchstens 2 000 Dateien und 64 MB entpackt, sichere Pfade.' },
    { icon: 'file-code', title: 'Schema', text: 'module.json gegen das Paketschema: Typen, Pflichtfelder, keine unbekannten Felder.', tone: 'lavender' },
    { icon: 'scale', title: 'Regeln', text: 'Verweise zwischen den Teilen, Schlüssel, Bereiche, Dateien, Überschrift im Changelog.' },
    { icon: 'badge-check', title: 'Hashes und Signatur', text: 'MANIFEST.sha256 stimmt, die Signatur ist gültig, oder das Paket gilt als unsigniert.', tone: 'cyan' },
  ]}
  caption="nexxus sammelt alle Probleme eines Schritts und meldet sie zusammen, eines pro Zeile."
/>

Die Regeln gehen über das Schema hinaus. Einige davon mit der Meldung, die nexxus ausgibt:

| Regel | Meldung |
|---|---|
| `publisher` passt zum Schlüssel | „publisher 'acme' muss dem Teil vor dem Punkt in 'firma.besucher' entsprechen“ |
| `area` ist ein bekannter Bereich | „area 'lager' ist kein Menübereich (...)“ |
| Filter und Sortierung nur auf indizierten Feldern | „Filter 'name' ist nicht indexed“ |
| Menüeinträge zeigen auf Listen oder Seiten | „nur Listen und Seiten (slot page) gehören ins Menü“ |
| Rollen nennen vorhandene Rechte | „Rolle 'empfang' nennt das unbekannte Recht 'besuch.delete'“ |
| Reservierte Namen für Fachobjekte | „Datenobjekt 'p' ist ein reservierter Name“ |
| Pakete bauen nicht aufeinander auf | „Pakete bauen nicht aufeinander auf: scope 'pkg:muster.akten:akte:read' greift auf Datensätze des Pakets 'muster.akten' zu“ |
| Überschrift im Changelog für die Version | „CHANGELOG.md: keine Überschrift '## [1.0.0]' für die Version aus module.json“ |
| Ansichtsdateien sind da und klein genug | „ui/empfang.html fehlt“, „ui/empfang.html ist größer als 2 MB“ |
| Eine Mail-Aktion braucht ihren Scope | „scope 'core:mail:send' muss unter scopes stehen“ |
| Eine Mail-Aktion ist nie ein KI-Werkzeug | „eine Mail-Aktion ist nie ein KI-Werkzeug (aiTool): ein Mensch löst jeden Versand aus“ |
| Die Platzhalter einer Mail gibt es | „Platzhalter '`{{payload.betreff}}`' gibt es in Mails nicht: was hinausgeht, steht vorher unverändert in der Vorschau“ |

<Screenshot src={shot_nxm_04} url="/modules/pakete" alt={"Ein abgelehnter Upload mit der Liste der Probleme"} caption={"nexxus nennt alle Probleme eines abgelehnten Pakets auf einmal."} />

Jede Meldung beginnt mit der Stelle, auf die sie sich bezieht, etwa `module.json views.besuch.list:`. Beim Hochladen zeigt der Katalog <Ui>„&lt;Datei&gt;“ wurde nicht angenommen.</Ui>, darunter den Satz „Das Paket ist fehlerhaft:“ und alle Probleme als Liste. Vor dem Hochladen prüft der Befehl `verify` des Paketierungsskripts Hashes, Dateiliste und Changelog-Überschrift; die Regeln des Manifests prüft nur nexxus selbst.

## Wie es weitergeht

<CardGrid>
  <LinkCard title="Ihr erstes Modul" description="Ein vollständiges Paket mit Dialog, Seite und Beispieldaten schreiben und installieren." href="/de/modules/first-module/" />
  <LinkCard title="Ansichten und das View-SDK" description="Slots, der Aufbau der Datensatzseite und jede Funktion, die eine eigene Ansicht aufrufen kann." href="/de/modules/views/" />
  <LinkCard title="Daten und Aktualisierungen" description="Datendateien, Versionen, Migrationsschritte und was eine Aktualisierung mit gespeicherten Datensätzen macht." href="/de/modules/data-and-upgrades/" />
</CardGrid>