Was sind Aktionen?
Aktionen sind serverseitige Operationen, die über das grundlegende CRUD hinausgehen. Sie führen eigene Geschäftslogik aus, die in einer einfachen Skriptsprache (DSL) definiert wird. Beispiele:
- Einen Lead in ein Konto, einen Kontakt und eine Verkaufschance konvertieren
- Eine Rechnung aus einem Angebot generieren
- Bestand zwischen Lagern transferieren
- Benachrichtigungs-E-Mails versenden
DSL-Struktur
Jede Aktion wird in einer .dsl-Datei unter logic/actions/ in Ihrem Modul definiert, mit bis zu drei Blöcken:
params:
param_name: type required "Label" [constraints]
canExecute:
[field] != 'SomeValue'
execute:
// Ihre Logik hier
Die DSL wird zu JavaScript kompiliert und serverseitig in einer sandboxed Engine mit 5-Sekunden-Timeout ausgeführt. Jede Ausführung erhält eine frische Engine — es gibt keinen geteilten Zustand zwischen Läufen. Die Aktion wird in ui/actions.json registriert, das auf die Skriptdatei verweist und Metadaten wie Symbol, Bezeichnung, Ausführungsmodus und Entitätsbindung setzt.
params (optional)
Deklariert Parameter, die der Anwender ausfüllt, bevor die Aktion läuft:
params:
dest_warehouse_id: text required "Ziel-Lager"
quantity: number required "Menge" min=1
reference_no: text optional "Transfer-Referenz"
Parametertypen: text, number, date, datetime, checkbox, lookup.
canExecute (optional)
Eine Formel, die bestimmt, ob die Aktion verfügbar ist. Wenn sie zu false ausgewertet wird, ist der Aktions-Button deaktiviert. Wird auf dem Client für sofortigen Button-Zustand ausgewertet und vor der Ausführung auf dem Server erneut geprüft.
Wenn canExecute weggelassen wird, ist die Aktion für jeden verfügbar, der das E-Recht (Execute) auf sie hat.
canExecute:
[stage] != 'Closed Won' AND [stage] != 'Closed Lost'
Bei Aktionen über mehrere Datensätze müssen alle ausgewählten Datensätze canExecute erfüllen — wenn auch nur einer fehlschlägt, ist der Button deaktiviert.
execute (Pflicht)
Der Hauptlogik-Block. Hat Zugriff auf die Felder des aktuellen Datensatzes, Parameter und eingebaute Funktionen.
Feldzugriff
Greifen Sie auf die Felder des aktuellen Datensatzes mit Klammer-Syntax zu:
var name = [account_name]
var total = [quantity] * [unit_price]
Über Referenzen navigieren:
var industry = [account_id].[industry]
Auf Parameter zugreifen:
var dest = params[dest_warehouse_id]
Eingebaute Funktionen
Datenfunktionen
| Funktion | Beschreibung |
|---|---|
insert(entity, data) | Einen neuen Datensatz einfügen. Gibt die vollständige eingefügte Zeile zurück. |
query(sql, args) | Ein parametrisiertes SELECT ausführen. Tabellenreferenzen werden automatisch mit dem aktuellen Modul-Schema qualifiziert. Gibt ein Array von Zeilen-Objekten zurück. |
getRecord(entity, pk) | Einen einzelnen Datensatz per Primärschlüssel abrufen. Gibt einen schreibgeschützten Accessor zurück. |
preloadRef(fkField) | Einen per Fremdschlüssel referenzierten Datensatz in den Speicher laden (nur Single-/Each-Modus). |
nextNumber(entity) | Die nächste Dokumentnummer aus der Nummernsequenz der Entität generieren. Unterstützt modulübergreifend: nextNumber('fin.invoice'). |
callProc(name, args) | Eine Stored Procedure mit benannten Argumenten aufrufen. |
Benachrichtigungsfunktionen
| Funktion | Beschreibung |
|---|---|
notify(userId, message) | Eine In-App-Benachrichtigung an einen bestimmten Anwender senden. |
sendEmail(to, subject, body) | Eine E-Mail senden (Raw-Modus — direkter Betreff und HTML-Body). |
sendEmail(to, templateCd, data) | Eine E-Mail aus einer Vorlage in email_template rendern und versenden. |
info(message) | Dem Anwender eine Erfolgs-/Info-Meldung anzeigen. |
warn(message) | Eine Warnung anzeigen. Die Ausführung läuft weiter. |
error(message) | Einen Fehler anzeigen und die Aktion abbrechen. |
Kontext & Hilfsfunktionen
| Ausdruck | Beschreibung |
|---|---|
userId | Die ID des aktuellen Anwenders (long). |
TODAY() | Aktuelles Datum. |
NOW() | Aktuelles Datum und Zeit (UTC). |
addDays(date, n) | Tage zu einem Datumswert hinzufügen. |
IF(cond, then, else) | Bedingter Ausdruck. |
Beispiel: Bestand transferieren
params:
dest_warehouse_id: text required "Ziel-Lager"
quantity: number required "Zu transferierende Menge" min=1
canExecute:
[quantity] > 0
execute:
if ([warehouse_id] == params[dest_warehouse_id]) {
error('Quell- und Ziel-Lager dürfen nicht identisch sein')
}
var stock = query(
"SELECT stock_id, quantity FROM stock WHERE warehouse_id = @wh AND product_id = @prod",
{ wh: [warehouse_id], prod: [product_id] }
)
if (stock[0]['quantity'] < params[quantity]) {
error('Unzureichender Bestand. Verfügbar: ' + stock[0]['quantity'])
}
insert('stock_movement', {
movement_type: 'Transfer Out',
warehouse_id: [warehouse_id],
product_id: [product_id],
quantity: params[quantity],
movement_date: TODAY()
})
insert('stock_movement', {
movement_type: 'Transfer In',
warehouse_id: params[dest_warehouse_id],
product_id: [product_id],
quantity: params[quantity],
movement_date: TODAY()
})
info(params[quantity] + ' Einheiten erfolgreich transferiert')
Ausführungsmodi
Aktionen können in verschiedenen Modi laufen, konfiguriert über executionMode (und is_single) in ui/actions.json:
| Modus | Beschreibung |
|---|---|
single | Aktion arbeitet auf genau einem Datensatz. Der Button ist nur aktiv, wenn ein Datensatz ausgewählt ist. |
each | Aktion läuft einmal pro ausgewähltem Datensatz. Jede Ausführung sieht einen Datensatz. |
batch | Aktion erhält alle ausgewählten Datensätze auf einmal und verarbeitet sie als Gruppe. |
Bei Aktionen über mehrere Datensätze steuert das isTransacted-Flag den Transaktionsumfang:
isTransacted: true— alle Datensätze werden in einer Transaktion verarbeitet. Wenn einer fehlschlägt, werden alle Änderungen zurückgerollt.isTransacted: false— jeder Datensatz läuft in seiner eigenen Transaktion. Fehler sind isoliert; erfolgreiche Datensätze werden trotzdem committet.
Hintergrund-Aktionen
Lang laufende Aktionen können asynchron ausgeführt werden, indem is_async: true gesetzt wird:
- Die Aktion wird in der
background_action-Tabelle eingereiht - Die Oberfläche kehrt sofort zurück, sodass der Anwender weiterarbeiten kann
- Ein Hintergrund-Worker pollt die Queue (alle 2 Sekunden) und führt die Aktion aus
- Der Anwender erhält eine Benachrichtigung, wenn der Job abgeschlossen ist
- Fortschritt wird über Server-Sent Events veröffentlicht
Verwenden Sie den asynchronen Modus für Imports, Massen-E-Mails, PDF-Generierung oder alles, was länger als ein paar Sekunden dauert.
Nummernsequenzen
Entitäten können automatische Nummernsequenzen für Dokumentnummerierung definieren:
{
"numberSequence": {
"column": "invoice_no",
"pattern": "{prefix}{yyyy}-{seq:3}",
"resetPeriod": "year"
}
}
Wenn ein neuer Datensatz angelegt wird und die Zielspalte leer ist, generiert die Plattform automatisch die nächste Nummer. Der Präfix wird aus ordnergebundenen Modul-Einstellungen aufgelöst.
Pattern-Platzhalter: {yyyy}, {yy}, {mm}, {dd}, {seq:N} (N = mit Nullen aufgefüllte Breite).
Trigger
Trigger lösen Aktionen automatisch als Reaktion auf Datenänderungen aus. Konfigurieren Sie sie in logic/triggers.json:
| Event | Löst aus bei |
|---|---|
insert | Neuer Datensatz angelegt |
update | Beliebiges Feld am Datensatz geändert |
delete | Datensatz gelöscht |
status_change | Wert eines bestimmten Feldes geändert (vergleicht alt vs. neu) |
any | Alle obigen |
Ein Trigger kann synchron (innerhalb der ursprünglichen Transaktion) oder asynchron (async: true — als Hintergrund-Job nach dem Commit der Transaktion eingereiht) laufen. Siehe Trigger & Webhooks für die vollständige Referenz.