Zum Inhalt springen

Module (NXM)

Ihr erstes Modul

Sie bauen ein kleines NXM-Modul von Grund auf, mit einem Fachobjekt, einer Liste, einem Dialog, einer eigenen Seite und Beispieldaten. Dann paketieren Sie es, installieren es in einem Mandanten und liefern ein Update mit Datenmigration aus.

Für
Entwickler
Dauer
30 Min.

Brandt & Söhne verleiht Laptops, Monitore und Telefone an die eigenen Leute. Die Liste dazu steckt heute in einer Tabelle, der niemand traut: Wer hat das MacBook, und bis wann? In diesem Tutorial bauen Sie Leihgeräte, ein NXM-Modul mit einem eigenen Fachobjekt für Geräte, einer Liste mit Filtern, einem Dialog zum Ausleihen, einer Seite mit allen fälligen Rückgaben und Werkzeugen für den KI-Assistenten. Zum Schluss liefern Sie Version 1.1.0 aus und sehen zu, wie nexxus die gespeicherten Daten umstellt.

Sie schreiben fünf Dateien, packen sie in eine .nxm-Datei und laden sie hoch. Alles, was das Modul zeigt, steht in diesen Dateien. In nexxus selbst läuft kein Code des Moduls.

  1. Schreiben

    module.json, ein Changelog, zwei HTML-Ansichten, Beispieldaten.

  2. Bauen

    Das Paketier-Skript macht aus dem Ordner eine .nxm-Datei.

  3. Hochladen

    nexxus prüft das Paket und zeigt, was es mitbringt.

  4. Installieren

    Sie stimmen zu, vergeben Rollen, und das Modul steht im Menü.

  5. Aktualisieren

    Version 1.1.0 benennt einen Wert um und ergänzt ein Feld.

Das ganze Tutorial auf einen Blick. Ab dem Hochladen läuft alles, ohne dass nexxus neu starten muss.
  • Ein Administratorkonto in einer nexxus-Instanz. Nur Administratoren laden Pakete hoch und installieren sie.
  • Eine Instanz, die Ihr Paket annimmt. Das Paket aus diesem Tutorial ist nicht signiert. Instanzen mit dem Spring-Profil development, local oder demo nehmen unsignierte Pakete an. Anderswo schaltet ein Administrator des Mandanten im Reiter Herausgeber Unsignierte Pakete zulassen ein, oder Sie signieren das Paket. Beides steht unter Paketieren, installieren und aktualisieren.
  • Node.js für das Paketier-Skript module-package.mjs. Es liegt in den nexxus-Quellen unter backend/scripts/ und braucht keine weiteren Pakete. Kopieren Sie es neben Ihren Paketordner.
  • Einen Texteditor.

Bevor Sie die erste Zeile schreiben, legen Sie vier Dinge fest. Sie landen im Manifest und lassen sich später nur schwer ändern.

EntscheidungWahl in diesem TutorialWarum
Schlüsselacme.leihgeraete<herausgeber>.<name>, klein geschrieben. Der Schlüssel ändert sich nie. Rechte, Werkzeugnamen und Routen leiten sich davon ab.
BereichprojekteDer Fachbereich des Moduls. projekte erscheint in der linken Leiste unter Organisation.
Fachobjekteeines, geraetJedes Fachobjekt bekommt eine eigene Liste und eigene Datensatzseiten.
Wer darf wasRollen verwaltung (verwalten und verleihen) und einsicht (nur lesen)Die Rollen entstehen bei der Installation. Niemand hat sie, bis ein Administrator sie vergibt.

Jede Version bekommt einen eigenen Ordner, und der Ordner heißt wie die Version. Sonst meldet das Skript Hinweis: Der Ordner heißt nicht wie die Version 1.0.0.

  • Ordneracme.leihgeraete/
    • Ordner1.0.0/
      • module.json das Manifest
      • CHANGELOG.md
      • Ordnerui/
        • ausleihen.html Dialog zum Ausleihen
        • faellig.html Seite mit fälligen Rückgaben
      • Ordnerdemo/
        • geraet.json Beispieldaten, auf Wunsch installiert

module.json beschreibt alles: das Fachobjekt mit seinen Feldern, Rechte, Rollen, Ansichten, Menüeinträge, Aktionen und was der KI-Assistent tun darf. Legen Sie die Datei mit diesem Inhalt an.

acme.leihgeraete/1.0.0/module.json
{
"key": "acme.leihgeraete",
"kind": "package",
"publisher": "acme",
"name": {"de": "Leihgeräte", "en": "Loan equipment"},
"description": {"de": "Geräte, die das Team ausleiht: wer hat was, bis wann.", "en": "Equipment the team borrows: who has what, until when."},
"icon": "devices",
"area": "projekte",
"version": "1.0.0",
"platform": ">=0.2.0 <1.0.0",
"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"]},
{"key": "einsicht", "label": {"de": "Einsicht", "en": "View"}, "permissions": ["geraet.read"]}
],
"entities": [
{
"key": "geraet",
"label": {"de": "Gerät", "en": "Device"},
"labelPlural": {"de": "Geräte", "en": "Devices"},
"icon": "devices",
"titleField": "bezeichnung",
"searchFields": ["bezeichnung", "inventarnummer"],
"fields": [
{"key": "inventarnummer", "type": "string", "sequence": "GER-{n:4}", "label": {"de": "Inventarnummer", "en": "Inventory no."}},
{"key": "bezeichnung", "type": "string", "required": true, "maxLength": 120, "label": {"de": "Bezeichnung", "en": "Name"}},
{"key": "kategorie", "type": "enum", "indexed": true, "default": "laptop", "label": {"de": "Kategorie", "en": "Category"},
"values": [
{"key": "laptop", "label": {"de": "Laptop", "en": "Laptop"}},
{"key": "monitor", "label": {"de": "Monitor", "en": "Monitor"}},
{"key": "telefon", "label": {"de": "Telefon", "en": "Phone"}},
{"key": "sonstiges", "label": {"de": "Sonstiges", "en": "Other"}}]},
{"key": "status", "type": "enum", "indexed": true, "readonly": true, "default": "verfuegbar", "label": {"de": "Status", "en": "Status"},
"values": [
{"key": "verfuegbar", "label": {"de": "Verfügbar", "en": "Available"}},
{"key": "verliehen", "label": {"de": "Verliehen", "en": "On loan"}}]},
{"key": "ausgeliehen_an", "type": "ref", "target": "core:user", "label": {"de": "Ausgeliehen an", "en": "Lent to"}},
{"key": "rueckgabe_am", "type": "date", "indexed": true, "label": {"de": "Rückgabe am", "en": "Due back"}},
{"key": "notiz", "type": "text", "label": {"de": "Notiz", "en": "Note"}}
]
}
],
"views": [
{"key": "geraet.list", "type": "list", "entity": "geraet", "label": {"de": "Geräte", "en": "Devices"},
"columns": [{"field": "inventarnummer"}, {"field": "bezeichnung"}, {"field": "kategorie"},
{"field": "status", "display": "badge"}, {"field": "ausgeliehen_an"}, {"field": "rueckgabe_am"}],
"filters": ["kategorie", "status", "ausgeliehen_an"], "sort": {"field": "inventarnummer", "dir": "asc"}, "create": true},
{"key": "geraet.record", "type": "record", "entity": "geraet", "label": {"de": "Daten", "en": "Data"},
"sections": [
{"key": "geraet", "label": {"de": "Gerät", "en": "Device"}, "fields": ["inventarnummer", "bezeichnung", "kategorie", "status"]},
{"key": "ausleihe", "label": {"de": "Ausleihe", "en": "Loan"}, "fields": ["ausgeliehen_an", "rueckgabe_am", "notiz"]}],
"actions": ["ausleihen-dialog", "zurueckgeben"]},
{"key": "ausleihen-dialog", "type": "custom", "slot": "record.action", "target": "geraet",
"html": "ui/ausleihen.html", "label": {"de": "Ausleihen", "en": "Lend"}, "icon": "assignment_ind",
"when": {"field": "status", "in": ["verfuegbar"]}},
{"key": "faellig", "type": "custom", "slot": "page", "html": "ui/faellig.html",
"label": {"de": "Fällige Rückgaben", "en": "Due returns"}}
],
"menu": [
{"key": "geraete", "label": {"de": "Geräte", "en": "Devices"}, "icon": "devices", "view": "geraet.list", "order": 1},
{"key": "faellig", "label": {"de": "Fällige Rückgaben", "en": "Due returns"}, "icon": "assignment_late", "view": "faellig", "order": 2}
],
"actions": [
{"key": "ausleihen", "entity": "geraet", "type": "update", "label": {"de": "Ausleihen", "en": "Lend"}, "icon": "assignment_ind",
"permission": "verleihen", "when": {"field": "status", "in": ["verfuegbar"]},
"set": {"status": "verliehen", "ausgeliehen_an": "{{payload.ausgeliehen_an}}", "rueckgabe_am": "{{payload.rueckgabe_am}}"}},
{"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}
],
"aiTools": {"entities": ["geraet"]},
"assistant": {
"instructions": {"de": "Du hilfst bei den Leihgeräten des Teams. Den Status ändern nur die Aktionen „Ausleihen“ und „Zurückgeben“.",
"en": "You help with the team's loan equipment. Only the actions Lend and Return change the status."},
"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."}},
{"view": "faellig", "label": {"de": "Erinnerung entwerfen", "en": "Draft a reminder"},
"prompt": {"de": "Entwirf eine freundliche Erinnerung an alle, deren Rückgabe fällig ist.", "en": "Draft a friendly reminder to everyone whose return is due."}}
]
}
}

Einige Entscheidungen in dieser Datei lohnen sich auch für Ihre eigenen Module:

  • status ist readonly. Niemand setzt ein Gerät über das Formular auf „verliehen“. Nur die beiden Aktionen ändern den Status, und jede prüft auf dem Server ihr Recht verleihen und ihre when-Bedingung. Ein Formular, das den Wert unverändert zurückschickt, speichert trotzdem.
  • Ausleihen braucht Eingaben, Zurückgeben nicht. Die Aktion ausleihen braucht eine Person und ein Datum. Deshalb läuft sie über die Dialog-Ansicht ausleihen-dialog, die beides abfragt und dann die Aktion aufruft. Die Aktion zurueckgeben braucht nichts und steht direkt in der Aktionsleiste des Datensatzes, mit einer Rückfrage vorab. Welche Einträge die Leiste zeigt, legt die Liste actions der Datensatz-Ansicht fest.
  • inventarnummer ist eine sequence. Wird ein Gerät ohne Nummer angelegt, vergibt nexxus die nächste: GER-0001, GER-0002 und so weiter. Ein Sequenzfeld ist per Definition eindeutig und schreibgeschützt, und Sie können danach sortieren.
  • Jedes Filter- und Sortierfeld ist indiziert. kategorie, status und rueckgabe_am tragen "indexed": true. Verweise wie ausgeliehen_an sind automatisch indiziert. Wer nach einem nicht indizierten Feld filtert oder sortiert, bekommt das Paket beim Hochladen zurück.
  • Der KI-Assistent kann mit Geräten arbeiten. aiTools.entities gibt ihm Werkzeuge zum Suchen und Lesen von geraet und die Aktionen zum Anlegen und Ändern (acme.leihgeraete:geraet.create, acme.leihgeraete:geraet.update). "aiTool": true an zurueckgeben ergänzt die Aktion acme.leihgeraete:zurueckgeben. Ausführen tut der KI-Assistent keine Aktion selbst: Er schlägt sie als Karte im Chat vor, und erst der Klick der Person führt sie aus.

Alle Felder und Optionen finden Sie unter Paketaufbau.

Jedes Paket bringt einen Changelog mit einer Überschrift für die eigene Version mit. nexxus zeigt ihn auf der Paketseite und im Dialog zum Aktualisieren.

acme.leihgeraete/1.0.0/CHANGELOG.md
# Changelog: Leihgeräte
## [1.0.0] - 2026-10-12
- Geräte mit Inventarnummer, Kategorie und Status; ausleihen und zurückgeben mit Rückgabedatum.
- Seite „Fällige Rückgaben“.

ui/ausleihen.html ist eine eigene Ansicht im Slot record.action. nexxus zeigt sie als Dialog auf der Seite eines Geräts und zeichnet den Kopf des Dialogs selbst. Die Ansicht zeichnet nur die Felder und ihre Schaltfläche. Sie läuft abgeschottet und spricht mit nexxus über das View-SDK window.nx, das schon geladen ist, wenn das erste Skript der Ansicht startet.

acme.leihgeraete/1.0.0/ui/ausleihen.html
<!doctype html>
<html>
<head><meta charset="utf-8"></head>
<body>
<div class="nx-stack">
<label class="nx-field">
<span class="nx-field__label" id="l-person"></span>
<select class="nx-select" id="person"></select>
</label>
<label class="nx-field">
<span class="nx-field__label" id="l-datum"></span>
<input class="nx-input" type="date" id="datum">
</label>
<div class="nx-row"><span class="nx-spacer"></span><button class="nx-btn nx-btn--primary" id="ok"></button></div>
</div>
<script>
(async () => {
const ctx = await nx.ready();
const $ = id => document.getElementById(id);
$('l-person').textContent = nx.t({de: 'Ausleihen an', en: 'Lend to'});
$('l-datum').textContent = nx.t({de: 'Rückgabe am', en: 'Due back'});
$('ok').textContent = nx.t({de: 'Ausleihen', en: 'Lend'});
for (const person of await nx.lookup('core:user', {})) { // die ersten 20; mit {q} suchen
const option = document.createElement('option');
option.value = String(person.id);
option.textContent = person.label;
$('person').append(option);
}
$('ok').onclick = async () => {
try {
const {record} = await nx.invoke('ausleihen', {
recordId: ctx.record.id,
payload: {ausgeliehen_an: Number($('person').value), rueckgabe_am: $('datum').value || null},
});
nx.dialog.close(record);
} catch (e) {
nx.ui.toast(e.message, {kind: 'error'});
}
};
})();
</script>
</body>
</html>

Was hier passiert:

  • nx.ready() liefert den Kontext. In einem Dialog an einem Datensatz steht in ctx.record das Fachobjekt und die Id des Geräts.
  • nx.lookup('core:user', {}) listet Personen des Mandanten, ohne Suchbegriff die ersten 20. Dienst- und Systemkonten fehlen in der Liste. Ein echter Dialog sucht mit {q}, während die Person tippt.
  • nx.invoke('ausleihen', ...) führt die Aktion als die Person aus, die geklickt hat. nexxus prüft Recht und when-Bedingung noch einmal. Lehnt der Server ab, erscheint die Meldung als Hinweis.
  • nx.dialog.close(record) schließt den Dialog. Die Datensatzseite liest das Gerät neu und zeigt den neuen Stand.
  • Die Beschriftungen kommen aus nx.t, die Ansicht spricht also die Sprache der Person. Text kommt über textContent in die Seite, nie über innerHTML.
  • Die Klassen nx-* stammen aus dem UI-Kit, das nexxus in jede Ansicht einsetzt. So sieht der Dialog aus wie der Rest der Anwendung, im hellen wie im dunklen Modus.

Alle Funktionen des SDK stehen unter Ansichten und das View-SDK.

ui/faellig.html ist eine eigene Seite (Slot page). Der Menüeintrag „Fällige Rückgaben“ führt dorthin. Sie sucht verliehene Geräte, deren Rückgabe heute oder früher fällig ist.

acme.leihgeraete/1.0.0/ui/faellig.html
<!doctype html>
<html>
<head><meta charset="utf-8"><style>body { padding: 16px; }</style></head>
<body>
<p class="nx-muted" id="hint"></p>
<div class="nx-list" id="list"></div>
<div class="nx-empty" id="empty" hidden><div class="nx-empty__title" id="empty-title"></div></div>
<script>
(async () => {
await nx.ready();
const $ = id => document.getElementById(id);
$('hint').textContent = nx.t({de: 'Verliehene Geräte, deren Rückgabe heute oder früher fällig ist.', en: 'Devices on loan due back today or earlier.'});
$('empty-title').textContent = nx.t({de: 'Alles zurück.', en: 'Everything is back.'});
const today = new Date().toLocaleDateString('sv-SE'); // JJJJ-MM-TT in Ortszeit
const {items} = await nx.records.search('geraet', {
filter: {status: 'verliehen', rueckgabe_am: {to: today}},
sort: 'rueckgabe_am', size: 100,
});
$('empty').hidden = items.length > 0;
for (const g of items) {
const item = document.createElement('button');
item.className = 'nx-list__item';
item.textContent = `${g.title} · ${g.refs?.ausgeliehen_an?.label ?? '-'} · ${g.data.rueckgabe_am}`;
item.onclick = () => nx.nav.open({entity: 'geraet', id: g.id});
$('list').append(item);
}
nx.assistant.setContext({
summary: nx.t({de: `${items.length} fällige Rückgaben`, en: `${items.length} due returns`}),
records: items.map(g => ({entity: 'geraet', id: g.id})),
});
})();
</script>
</body>
</html>

Die Suche filtert auf zwei indizierte Felder. Ein Datumsbereich nur mit to schließt diesen Tag ein. g.title ist die bezeichnung des Geräts (sein titleField), und g.refs enthält den Anzeigenamen jeder verwiesenen Person. nx.nav.open öffnet die Seite des Geräts. nx.assistant.setContext sagt dem KI-Assistenten, welche Geräte die Person gerade sieht. Eine Bitte wie „Erinnere sie“ trifft dann die richtigen Leute.

Beispieldaten sind freiwillig. Setzt der Administrator bei der Installation den Haken Beispieldaten anlegen, legt nexxus diese Zeilen an. Platzhalter füllen Werte ein, die es erst im Zielmandanten gibt.

acme.leihgeraete/1.0.0/demo/geraet.json
[
{"key": "thinkpad", "data": {"bezeichnung": "ThinkPad T14", "kategorie": "laptop", "status": "verfuegbar"}},
{"key": "macbook", "data": {"bezeichnung": "MacBook Air", "kategorie": "laptop", "status": "verliehen",
"ausgeliehen_an": {"$currentUser": true}, "rueckgabe_am": {"$date": "-2wd"}}},
{"key": "monitor", "data": {"bezeichnung": "Monitor 27 Zoll", "kategorie": "monitor", "status": "verliehen",
"ausgeliehen_an": {"$currentUser": true}, "rueckgabe_am": {"$date": "+1w"}}}
]
  • {"$currentUser": true} wird zum Administrator, der installiert.
  • {"$date": "-2wd"} liegt zwei Werktage vor dem Installationstag, "+1w" eine Woche danach. Das MacBook ist also immer überfällig, wenn jemand das Modul ausprobiert, der Monitor nie.
  • Beispieldaten dürfen status setzen, obwohl das Feld schreibgeschützt ist: Datendateien schreibt der Installer, keine Person.

Alle Platzhalter beschreibt Daten und Aktualisierungen.

  1. Öffnen Sie ein Terminal in dem Ordner, in dem acme.leihgeraete/ und module-package.mjs liegen.

  2. Bauen Sie das Archiv:

    Terminal-Fenster
    node module-package.mjs build acme.leihgeraete/1.0.0

    Das Skript prüft, ob module.json die Angaben kind, key und version hat und ob CHANGELOG.md die Überschrift ## [1.0.0] enthält. Es schreibt acme.leihgeraete-1.0.0.nxm samt einer Datei MANIFEST.sha256 mit dem Hash jeder Datei und meldet Dateiname, 5 Dateien, Größe und unsigniert.

  3. Wenn Sie möchten, lesen Sie das Archiv zur Kontrolle zurück:

    Terminal-Fenster
    node module-package.mjs verify acme.leihgeraete-1.0.0.nxm

    Geprüft werden die Hashes und die Changelog-Überschrift. Den Inhalt des Manifests prüft erst nexxus beim Hochladen.

Derselbe Ordner ergibt immer dasselbe Archiv, Byte für Byte.

  1. Melden Sie sich als Administrator an und öffnen Sie AdministrationBetriebModule. Wechseln Sie auf den Reiter Pakete.

  2. Klicken Sie auf Paket hochladen (.nxm) und wählen Sie acme.leihgeraete-1.0.0.nxm. nexxus lädt die Datei hoch und prüft sie. Danach erscheint der Hinweis Leihgeräte 1.0.0 hochgeladen. und die Paketseite öffnet sich.

  3. Lesen Sie die Karte Was das Paket mitbringt. Für Leihgeräte steht dort:

    ZeileInhalt
    FachobjekteGerät
    MenüeinträgeGeräte, Fällige Rückgaben
    AnsichtenGeräte · Liste, Daten · Datenreiter, Ausleihen · Dialog am Datensatz, Fällige Rückgaben · Seite
    RollenGeräteverwaltung, Einsicht, jeweils mit ihren Rechten
    Daten außerhalb des Paketskeine
    Externe Dienstekeine
    Werkzeuge für den Assistentennx_acme_leihgeraete_geraet_search, _get, acme.leihgeraete:geraet.create, .update und acme.leihgeraete:zurueckgeben
    RechteGeräte lesen (acme.leihgeraete.geraet.read), Geräte bearbeiten (acme.leihgeraete.geraet.write), Geräte verleihen (acme.leihgeraete.verleihen)

    Weil das Paket nicht signiert ist, steht darüber ein gelber Hinweis: Nicht signiert. Prüfen Sie die Herkunft, bevor Sie es installieren.

    Die Paketseite des Tutorial-Pakets nach dem Hochladen
    Was das Tutorial-Paket mitbringt, gelesen aus seinem Manifest.
  4. Klicken Sie in der Aktionsleiste auf Installieren. Der Dialog Leihgeräte installieren öffnet sich. Unter Ihre Zustimmung steht Das Paket nutzt keine Daten außerhalb seiner eigenen., denn Leihgeräte fragt weder nach Kerndaten noch nach einem externen Dienst.

  5. Wählen Sie unter Rollen zuweisen die Personen für jede Rolle und bestätigen Sie jede Auswahl mit Anwenden. Bei Brandt & Söhne bekommt Tom Keller aus der IT die Rolle Geräteverwaltung und Mia Schulz die Rolle Einsicht. Sie können beide Felder leer lassen und die Rollen später im Rolleneditor vergeben.

  6. Setzen Sie den Haken bei Beispieldaten anlegen und klicken Sie auf Installieren. Der Hinweis Leihgeräte ist installiert. bestätigt es.

    Das Tutorial-Paket installieren, mit Rollen und Beispieldaten
    Keine Daten außerhalb der eigenen, also gibt es nichts zuzustimmen.
  1. Öffnen Sie in der linken Leiste den Bereich Organisation. Unter den eingebauten Abschnitten steht jetzt ein neuer Abschnitt Leihgeräte mit den Einträgen Geräte und Fällige Rückgaben. Neu laden müssen Sie dafür nicht.

    Der neue Menüabschnitt des Moduls
    Das Modul erscheint ohne Neuladen im Menü.
  2. Klicken Sie auf Geräte (Route /x/acme.leihgeraete/geraet). Die Liste zeigt die drei Beispielgeräte mit Nummern ab GER-0001, das Suchfeld Geräte durchsuchen, die Menüs Filter (Kategorie, Status, Person) und Sortieren und die Schaltfläche Gerät anlegen.

    Die Geräteliste des Tutorial-Moduls
    Die Liste entsteht aus der Listenansicht im Manifest.
  3. Klicken Sie auf Gerät anlegen. Der Reiter Daten zeigt die Abschnitte Gerät und Ausleihe. Bei der Inventarnummer steht Wird beim Anlegen vergeben. Tragen Sie eine Bezeichnung ein und klicken Sie auf Anlegen.

  4. Klicken Sie am neuen Gerät in der Aktionsleiste auf Ausleihen. Der Dialog aus ausleihen.html öffnet sich. Wählen Sie eine Person, etwa Mia Schulz, und ein Datum und klicken Sie auf Ausleihen. Der Dialog schließt sich, der Status zeigt Verliehen, und die Aktionsleiste bietet jetzt Zurückgeben an.

    Ein Gerät im Dialog des Moduls ausleihen
    Der Dialog ist eine eigene Ansicht; den Kopf zeichnet nexxus.
  5. Klicken Sie auf Zurückgeben. nexxus fragt Gerät als zurückgegeben buchen?. Klicken Sie auf Ausführen. Der Hinweis lautet „Zurückgeben“ ausgeführt.

  6. Öffnen Sie Fällige Rückgaben (Route /x/acme.leihgeraete/p/faellig). Das Beispiel-MacBook steht dort, weil sein Rückgabedatum zwei Werktage zurückliegt.

    Die Seite mit den fälligen Rückgaben
    Eine eigene Seite, die Geräte mit fälliger Rückgabe sucht.
  7. Öffnen Sie den KI-Assistenten auf der Geräteliste. Die Beispiel-Anweisung Wer hat welches Gerät? erscheint als Chip. Bitten Sie den KI-Assistenten, das MacBook als zurückgegeben zu buchen: Bevor sich etwas ändert, zeigt der Chat eine Aktionskarte mit den drei Prüfungen Eingaben, Zustand und Berechtigung und der Schaltfläche Ausführen. Geprüft hat der Server schon; eine rote Prüfung nennt, was fehlt, und die Schaltfläche bleibt aus. Je nach Formulierung wählt der KI-Assistent auch eine andere Aktion des Moduls, etwa das Ändern des Geräts; die Karte zeigt dann diese. Bitten Sie um zwei Änderungen auf einmal, trägt eine Karte beide, mit Alle 2 ausführen.

Wer keine Administratorrolle hat, sieht das Modul erst mit einer seiner Rollen: Geräteverwaltung für alles, Einsicht zum Lesen. Mehr dazu unter Rollen.

Brandt & Söhne wünscht sich zwei Änderungen: „Telefon“ soll „Smartphone“ heißen, und jedes Gerät braucht einen Standort. Eine ausgelieferte Version ändert sich nie. Deshalb legen Sie Version 1.1.0 daneben an.

  1. Kopieren Sie den Ordner acme.leihgeraete/1.0.0 nach acme.leihgeraete/1.1.0.

  2. Setzen Sie in der Kopie von module.json den Wert "version": "1.1.0". Ersetzen Sie im Feld kategorie den Wert telefon durch:

    {"key": "smartphone", "label": {"de": "Smartphone", "en": "Smartphone"}}
  3. Fügen Sie hinter status ein neues Feld ein und nehmen Sie "standort" in die fields des Abschnitts geraet in geraet.record auf:

    {"key": "standort", "type": "enum", "indexed": true, "default": "buero", "label": {"de": "Standort", "en": "Location"},
    "values": [
    {"key": "buero", "label": {"de": "Büro", "en": "Office"}},
    {"key": "homeoffice", "label": {"de": "Homeoffice", "en": "Home office"}}]}
  4. Sagen Sie nexxus, wie die gespeicherten Daten umziehen. Ergänzen Sie auf oberster Ebene von module.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"}]
    }
    }

    pre-Schritte laufen, bevor nexxus auf die neue Version umschaltet, post-Schritte danach. renameValue macht aus jedem gespeicherten telefon ein smartphone. setDefault gibt jedem vorhandenen Gerät den Standort buero.

  5. Stellen Sie in CHANGELOG.md einen neuen Abschnitt nach oben:

    ## [1.1.0] - 2026-10-19
    - Kategorie „Telefon“ heißt jetzt „Smartphone“; bestehende Geräte werden umgestellt.
    - Neues Feld Standort (Büro oder Homeoffice); bestehende Geräte stehen im Büro.
  6. Bauen Sie mit node module-package.mjs build acme.leihgeraete/1.1.0 und laden Sie acme.leihgeraete-1.1.0.nxm auf dem Reiter Pakete hoch.

  7. Die Paketseite zeigt den Chip Update auf 1.1.0. Klicken Sie auf Auf 1.1.0 aktualisieren. Der Dialog Leihgeräte aktualisieren zeigt Von 1.0.0 auf 1.1.0, unter Was sich ändert den neuen Changelog-Abschnitt und unter Datenanpassungen einen Probelauf auf den heutigen Daten: renameValue auf geraet.kategorie (vor dem Wechsel) und setDefault auf geraet.standort (nach dem Wechsel), jeweils mit der Zahl der Datensätze, die sich ändern. Mit nur den Beispieldaten sind das 0 und 3, dazu das Gerät, das Sie selbst angelegt haben. Einen Abschnitt Neu zuzustimmen gibt es nicht, denn 1.1.0 verlangt keinen neuen Datenzugriff.

    Das Tutorial-Paket auf 1.1.0 aktualisieren, mit zwei Migrationsschritten
    Der Probelauf zählt die Datensätze, die jeder Schritt ändert.
  8. Klicken Sie auf Aktualisieren. Der Hinweis lautet Leihgeräte ist auf 1.1.0 aktualisiert.

Nicht jede Änderung ist in einer Nebenversion erlaubt. Hätten Sie standort zur Pflicht gemacht, ohne einen Standardwert anzugeben, hätte der Dialog abgelehnt: Das neue Pflichtfeld ‘geraet.standort’ hat keinen Standardwert. Die Regeln stehen unter Daten und Aktualisierungen.

Beim Hochladen entpackt nexxus das Archiv und prüft module.json gegen das Paketschema. Danach folgen die Regeln, die ein Schema nicht ausdrücken kann: Jede Spalte, jeder Filter, jeder Abschnitt und jede Aktion nennt ein vorhandenes Feld, Menüeinträge zeigen auf Listen oder Seiten, Rollen nennen vorhandene Rechte, und jede Datei, die das Manifest nennt, liegt im Archiv. nexxus meldet alle Probleme auf einmal. Zuletzt prüft es MANIFEST.sha256 und die Signatur. Gespeichert wird das Paket nur für Ihren Mandanten.

Bei der Installation erledigt nexxus in einer Transaktion:

  • Es legt die Installation mit dem Stand Aktiv an und hält Ihre Zustimmung fest.
  • Es legt je Manifest-Rolle eine Zugriffsrolle an, benannt nach Modul und Rolle: Leihgeräte – Geräteverwaltung und Leihgeräte – Einsicht. Die gewählten Personen bekommen sie sofort.
  • Es legt aus assistant.instructions einen firmenweiten Skill für den KI-Assistenten an, Leihgeräte: Arbeitsweise.
  • Es schreibt die Beispieldaten, löst die Platzhalter auf und vergibt die Inventarnummern.

Nach dem Abschluss stehen Menü, Routen und die Werkzeuge des KI-Assistenten allen zur Verfügung, die die nötigen Rechte haben. Im Rolleneditor erscheinen die Rechte als Geräte lesen, Geräte bearbeiten und Geräte verleihen.

Im Betrieb laufen jede Liste, jedes Formular und jede Aktion durch dieselben Prüfungen auf dem Server, egal ob eine Person klickt, eine Ansicht das SDK aufruft oder der KI-Assistent ein Werkzeug benutzt. Die Ansichten laufen abgeschottet, ohne Netzwerkzugriff und ohne Zugriff auf die Seite um sie herum.

Beim Aktualisieren laufen die pre-Schritte, der Wechsel auf 1.1.0 und die post-Schritte in einer Transaktion. Scheitert ein Schritt, wird alles zurückgerollt, und das Modul bleibt auf 1.0.0. Rollen, die es schon in 1.0.0 gab, bekommen die Rechte, die in 1.1.0 neu sind. Rechte, die ein Administrator entfernt hat, bleiben entfernt.

Lehnt nexxus ein Paket ab, zeigt der Katalog „acme.leihgeraete-1.0.0.nxm“ wurde nicht angenommen. mit dem Grund des Servers und einer Zeile je Problem. Diese Meldungen begegnen Ihnen beim Bauen am häufigsten:

MeldungUrsache und Lösung
module.json menu.faellig: nur Listen und Seiten (slot page) gehören ins MenüEin Menüeintrag zeigt auf eine Ansicht, die weder eine list noch eine custom-Ansicht mit Slot page ist. Verweisen Sie auf eine solche Ansicht.
module.json views.geraet.list: Filter 'notiz' ist nicht indexedSie filtern oder sortieren nach einem Feld ohne "indexed": true. Ergänzen Sie die Angabe. Felder vom Typ text und json lassen sich nicht indizieren.
module.json views.geraet.list: sort 'bezeichnung' ist nicht indexedDasselbe für die Sortierung der Liste.
module.json entities.geraet: searchFields 'rueckgabe_am' muss Text seinSuchfelder müssen vom Typ string, text oder enum sein.
CHANGELOG.md: keine Überschrift '## [1.0.0]' für die Version aus module.jsonDer Changelog hat keine Überschrift für die Version aus dem Manifest. Ergänzen Sie ## [1.0.0]. Das Skript bricht schon vorher ab mit CHANGELOG.md: Überschrift "## [1.0.0]" fehlt.
module.json: area 'lager' ist kein Menübereich (...)area muss einer der Fachbereiche sein, die die Meldung aufzählt.
Das Archiv enthält kein module.json.module.json liegt weder ganz oben im Archiv noch in einem einzigen obersten Ordner. Bauen Sie mit dem Skript oder packen Sie den Versionsordner selbst.
Das Paket ist nicht signiert. Unsignierte Pakete sind auf dieser Plattform nicht zugelassen.Die Instanz nimmt keine unsignierten Pakete an. Signieren Sie das Paket, siehe Paketieren, installieren und aktualisieren.
acme.leihgeraete 1.0.0 gibt es schon mit anderem Inhalt; eine neue Fassung braucht eine neue Versionsnummer.Sie haben Dateien einer Version geändert, die schon hochgeladen war. Ausgelieferte Versionen sind unveränderlich: Erhöhen Sie die Versionsnummer in module.json und im Changelog, mit einem neuen Versionsordner oder, in einem Paketordner mit Git-Tags, mit einem neuen Tag. Dieselbe Datei noch einmal hochzuladen ist kein Problem.

Fehler im Betrieb:

  • Mia Schulz hat nur die Rolle Einsicht, öffnet den Dialog Ausleihen und bekommt den Hinweis Keine Berechtigung: Ihnen fehlt das Recht 'acme.leihgeraete.verleihen'. Eigene Ansichten sieht jeder, der das Fachobjekt lesen darf, und sie kennen die Rechte der Person noch nicht. Der Server lehnt die Aktion ab. Geben Sie Mia Schulz die Rolle Geräteverwaltung, oder nehmen Sie hin, dass Lesende die Schaltfläche sehen.
  • Der Menüabschnitt fehlt: Die Person hat keine Rolle des Moduls, oder das Modul ist deaktiviert. Administratoren sehen ihn immer, solange das Modul aktiv ist. Das Abschalten des Fachbereichs projekte verbirgt ihn nicht.