Registrierung eines serverseitigen Plug-in-Schritts
Schema-ID: plugin-step@1
Maschinenlesbares Schema: plugin-step.schema.json
Der maßgebliche Registrierungsrahmen für einen serverseitigen Plug-in-Schritt (Abschnitt 6.6) – serverseitige Logik, die ausgeführt wird, wenn ein Datensatz einer Tabelle erstellt, aktualisiert oder gelöscht wird. Der Rahmen legt fest, wann und wie die Logik ausgeführt wird; eine BS-Lang-Implementierung enthält zusätzlich das Skript inline, das neben diesem Schema auch anhand der BS-Lang-Grammatik validiert wird. Eine Registrierung wird identisch validiert, ob sie aus einem Designer, einem API-Aufruf, einem KI-Assistenten oder einem Lösungsimport stammt.
Aufbau
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
logicalName | string | ja | Mit Herausgeberpräfix, Snake-Case in Kleinbuchstaben. |
displayName | string | ja | Lesbarer Name des Schritts. |
publisherPrefix | string | ja | Das Präfix des besitzenden Herausgebers, mit dem logicalName benannt sein muss – nicht das der überwachten Tabelle. Eine Standardtabelle wie account hat bewusst kein Präfix, und die Validierung dagegen machte es unmöglich, einen Schritt für eine solche Tabelle zu benennen. |
entity | string | ja | Die Tabelle, deren Schreibvorgänge den Schritt auslösen. |
message | string | ja | Create, Update oder Delete. |
stage | string | ja | PreValidation, PreOperation oder PostOperation. |
executionMode | string | ja | Synchronous (inline, innerhalb des Schreibvorgangs) oder Asynchronous (über den Outbox eingereiht und nach dem Commit des Schreibvorgangs ausgeführt). Ein asynchroner Schritt muss PostOperation sein: Er läuft, nachdem der Datensatz bereits gespeichert ist, hat also nichts mehr abzulehnen. |
rank | integer | ja | Ausführungsreihenfolge unter den Schritten mit derselben Tabelle + Nachricht + Phase; niedrigere Werte laufen zuerst. |
isEnabled | boolean | ja | Ein deaktivierter Schritt wird gespeichert, aber nie ausgeführt. |
implementation | object | ja | Genau eine Implementierung (siehe unten). |
Implementierung
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
type | string | ja | BsLangScript oder DotNetAssembly. |
script | string | bei BsLangScript | Der BS-Lang-Quelltext. |
assemblyReference | string | bei DotNetAssembly | Der Speicherschlüssel der hochgeladenen Assembly. |
typeName | string | bei DotNetAssembly | Der zu instanziierende Plug-in-Typ. |
Phasen
- PreValidation – läuft vor der Operation; grobe Prüfungen von Form und Sicherheit.
- PreOperation – läuft unmittelbar vor dem Schreibvorgang; kann Feldwerte des Datensatzes ändern oder die gesamte Operation ablehnen.
- PostOperation – läuft unmittelbar nach dem Schreibvorgang; für kaskadierende oder abhängige Änderungen.
Ein Schritt lehnt eine Operation ab, indem er throwValidationError("reason") aufruft; das stoppt
den Schreibvorgang und gibt den Grund an den Aufrufer zurück.
Beispiel
{
"logicalName": "par_invoice_reject_negative",
"displayName": "Reject negative invoice total",
"entity": "par_invoice",
"message": "Create",
"stage": "PreOperation",
"executionMode": "Synchronous",
"rank": 1,
"isEnabled": true,
"implementation": {
"type": "BsLangScript",
"script": "if (getValue(\"par_total\") < 0) { throwValidationError(\"Total cannot be negative.\"); }"
}
}
Hostfunktionen für BS-Lang-Skripte
Ein BsLangScript-Schritt liest und ändert den Datensatz über diese Hostfunktionen:
| Funktion | Zweck |
|---|---|
getValue("field") | Der aktuelle Wert eines Felds des Datensatzes, der geschrieben wird. |
setValue("field", value) | Ändert den Wert eines Felds (wird nur in PreOperation berücksichtigt). |
getPreValue("field") | Der vorherige Wert des Felds (bei Aktualisierungen und Löschungen). |
throwValidationError("message") | Lehnt die Operation ab und macht sie rückgängig. |
message() / stage() / entityName() | Die aktuelle Operation, Phase und Tabelle. |
userId() / depth() | Der auslösende Benutzer und die Rekursionstiefe. |
Andere Tabellen erreichen
Die obigen Funktionen sehen nur den Datensatz, der geschrieben wird. Die folgenden erreichen den Rest der Umgebung, und jede von ihnen läuft als der Benutzer, der den Schritt ausgelöst hat – ein Skript ist kein Weg, eine Berechtigung zu umgehen, und ein Schritt, dessen Aufrufer eine Tabelle nicht lesen darf, erhält daraus nichts zurück.
| Funktion | Zweck |
|---|---|
getRecord("table", id) | Ein Datensatz als Objekt oder leer, wenn er nicht vorhanden ist. .id ist darauf enthalten. |
queryRecords("table", "field", value) | Die Zeilen, in denen ein Feld einem Wert entspricht, als Liste von Objekten. Begrenzt auf 200. |
runQuery({ … }) | Führt ein vollständiges parsware-query@1-Dokument aus – verschachtelte UND/ODER-Filter mit zehn Operatoren, Joins, Sortierungen, Seitenaufteilung oder ein aggregate, das Gruppen liefert. Dieselbe Abfragesprache, die die erweiterte Suche erzeugt und eine gespeicherte Abfrage speichert. Zeilen sind auf 200 begrenzt; ein Aggregat nicht. Eine per Join eingebundene Spalte wird unter ihrem Alias gelesen (row.acct.par_industry), und das 'from' einer Verknüpfung muss in Anführungszeichen stehen, weil from ein BS-Lang-Schlüsselwort ist. |
runSavedQuery("name") | Führt eine gespeicherte Abfrage per logischem Namen aus. Die Komplexität liegt in einem Designer und reist in der Lösung mit, sodass das Skript nur die Frage benennt. |
countRecords("table", "field", value) | Wie viele es gibt, ohne sie abzurufen – ein echtes COUNT, also auch jenseits der 200-Zeilen-Grenze von queryRecords korrekt. |
createRecord("table", { field: value, … }) | Erstellt einen Datensatz und liefert seine ID. |
updateRecord("table", id, { field: value, … }) | Ändert die genannten Felder, und nur diese. |
deleteRecord("table", id) | Löscht einen Datensatz. |
Ein auf diese Weise ausgeführter Schreibvorgang durchläuft die gewöhnlichen Regeln, sodass ein
für die beschriebene Tabelle registrierter Schritt seinerseits ausgeführt wird. Diese
Verschachtelung ist auf eine Tiefe von acht begrenzt, und depth() teilt einem Skript mit, wo
es sich befindet – aber die Grenze ist eine Absicherung, kein Entwurfsmittel. Ein Schritt, der in
die Tabelle schreibt, die er überwacht, löst sich selbst aus; die Lösung ist eine Bedingung oder
eine andere Tabelle.
Gemeinsame Logik importieren
Das Skript eines Schritts kann ein Lösungsskript per Name mit import einbinden, genau wie eine
clientseitige Geschäftsregel, sodass eine Regel, die mehrere Schritte durchsetzen, nur einmal
geschrieben wird:
import 'par_approval_rules';
if (getValue('par_total') > par_approval_rules.limit()) { … }
Beide Anführungszeichenstile funktionieren. Das Modul muss ein Lösungsskript in derselben Umgebung sein; alles andere schlägt beim Speichern des Schritts fehl, nicht erst bei seiner Ausführung.