Worum es geht
Eine Vorlage hat mehrere Teile, die man leicht durcheinanderbringt. Der Schlüssel nennt die Vorlage, zum Beispiel audit-report. Unter diesem Schlüssel kann es mehrere Ausgaben geben: eine der Plattform, eine je Mandant, eine je Person. Jede Ausgabe hat eine Variante je Sprache. Und jede Variante hat einen Entwurf zum Arbeiten und eine Kette mit allen Revisionen.
Das Bild
flowchart TB
K["Schlüssel<br/>audit-report"]
K --> E1["Ausgabe SYSTEM<br/>Ausgabeart DOCUMENT<br/>Standardsprache de"]
K --> E2["Ausgabe TENANT<br/>Mandant acme"]
K --> E3["Ausgabe USER<br/>Person anna"]
E1 --> V1["Variante de<br/>(Standard)"]
E1 --> V2["Variante en"]
V1 --> D1["Entwurf<br/>frei speicherbar"]
V1 --> C1["Kette<br/>Revision 1, 2, 3 …"]
Die Teile
| Teil | Was es ist | Beispiel |
|---|---|---|
| Schlüssel | der stabile Name der Vorlage, gleich in jedem Scope und jeder Sprache | audit-report |
| Ausgabeart | was die Vorlage erzeugt: DOCUMENT, WORD oder TABLE. Sie entscheidet, welche Formate möglich sind | DOCUMENT → PDF, HTML |
| Ausgabe (Edition) | die Vorlage eines Eigentümers: der Plattform, eines Mandanten oder einer Person | System-Ausgabe von audit-report |
| Variante | eine Ausgabe in einer Sprache. Eine davon ist die Standardvariante | de, de-CH, en |
| Entwurf | der Arbeitsplatz einer Variante. Er ist nicht Teil der Kette | — |
| Revision | ein eingereichter Stand: Quelltext, Felder, Assets. Unveränderlich | Revision 3 |
Der Schlüssel
Ein Schlüssel ist kebab-case: Kleinbuchstaben, Ziffern und Bindestriche, er beginnt mit einem Buchstaben und hat höchstens 128 Zeichen. audit-report ist gültig, Audit_Report nicht.
Der Scope
Der Scope sagt, wem eine Ausgabe gehört. Es gibt drei.
| SYSTEM | TENANT | USER | |
|---|---|---|---|
| Gehört | der Plattform | einem Mandanten | einer Person in ihrem Mandanten |
| Wer sieht sie | alle angemeldeten Personen | die Personen dieses Mandanten | nur diese Person |
| Wer schreibt sie | die Plattform-Administratoren, die CIAS kennt | wer die Rolle crms-template-author hat | die Person selbst, mit der Rolle crms-template-personal |
| Wo sie liegt | System-Datenbank | Datenbank des Mandanten | Datenbank des Mandanten |
Die Rollennamen crms-template-author und crms-template-personal sind die Standardwerte. Ein Projekt kann sie ändern, siehe Rollen. Eine Installation kann auch Scopes abschalten (CODAMAI_CRMS_TEMPLATE_SCOPES). Eine Ausgabe in einem abgeschalteten Scope anzulegen, endet mit 422 template-scope-not-admitted|<scope>.
Eine Person ohne Mandant hat keine Mandanten-Ausgabe. Versucht sie, eine TENANT-Ausgabe anzulegen, antwortet CRMS mit 422 template-tenant-required.
Ausgabe und Ausgabeart
Je Schlüssel und Eigentümer gibt es genau eine Ausgabe. Eine zweite endet mit 409 template-edition-exists.
Die Ausgabeart gehört zum Schlüssel. Legt ein Mandant eine Ausgabe von audit-report an, muss sie dieselbe Ausgabeart haben wie die System-Ausgabe. Sonst antwortet CRMS mit 422 template-output-type-mismatch. Geprüft wird nur nach oben: Person gegen Mandant und System, Mandant gegen System.
POST /crms/templates
{
"key": "audit-report",
"outputType": "DOCUMENT",
"scope": "TENANT",
"defaultLocale": "de"
}201 Created
{
"id": "6f1c…",
"key": "audit-report",
"outputType": "DOCUMENT",
"scope": "TENANT",
"defaultLocale": "de",
"createdBy": "anna",
"createdAt": "2026-10-17T08:12:00Z"
}Mit der Ausgabe entsteht gleich ihre Standardvariante in der Sprache defaultLocale.
Die Variante
Eine weitere Sprache kommt mit POST /crms/templates/{edition}/variants und {"locale": "en"} dazu. Je Sprache gibt es eine Variante, eine zweite endet mit 409 template-variant-exists. Die Sprache ist ein Sprach-Tag nach BCP 47, also de, de-CH oder en. Wie CRMS bei einer Anfrage die passende Variante wählt, steht unter Wie CRMS die Vorlage findet.
Der Entwurf
Jede Variante hat einen Entwurf. Du speicherst ihn so oft du willst, immer als Ganzes. Damit zwei Personen sich nicht still überschreiben, nennt jede Speicherung die Version, auf der sie aufbaut (expectedVersion, 0 für den ersten Entwurf). Hat jemand anderes inzwischen gespeichert, antwortet CRMS mit 409 template-draft-changed.
Was in einer Revision steckt
Ein Entwurf und eine Revision haben denselben Inhalt: Quelltext, Felder und Assets.
PUT /crms/variants/{variant}/draft
{
"expectedVersion": 2,
"content": {
"source": "<p>${title}</p>",
"fields": [
{ "path": "title", "type": "STRING", "required": true },
{ "path": "items[].amount", "type": "NUMBER" }
],
"assets": [
{ "name": "logo.png", "contentType": "image/png",
"content": "<base64>" }
]
}
}{ "variant": "…", "version": 3, "updatedBy": "anna",
"updatedAt": "…", "content": { … } }| Teil | Inhalt | Regeln |
|---|---|---|
source | der FreeMarker-Quelltext, siehe Die Vorlagensprache | darf nicht leer sein: template-source-empty |
fields | welche Felder die Vorlage in der Payload erwartet | siehe unten |
assets | Dateien, die die Vorlage nutzt: Logo, Schrift, Stylesheet | siehe unten |
Felder
Eine Vorlage hat kein JSON-Schema. Sie deklariert die Felder, die sie braucht. Jede Deklaration hat einen Pfad, einen Typ und zwei Schalter.
| Angabe | Werte | Bedeutung |
|---|---|---|
path | title, customer.name, items[].amount | wo das Feld in der Payload steht, höchstens zwei Ebenen |
type | STRING, NUMBER, BOOLEAN, DATE, DATETIME | was im Feld steht |
required | true / false (Standard) | ob die Payload das Feld haben muss |
list | true / false (Standard) | ob das Feld eine Liste von Werten des Typs ist |
items[] heißt: items ist eine Liste von Objekten, und jedes hat ein Feld amount. Nur die erste Ebene darf so eine Liste sein. Ein dritter Teil wie a.b.c endet mit template-field-too-deep, ein kaputter Pfad mit template-field-path-invalid. Widersprechen sich zwei Deklarationen, etwa derselbe Pfad zweimal oder customer einmal als Wert und einmal als Objekt, antwortet CRMS mit template-field-conflict|<position>. Wie CRMS die Payload gegen diese Felder prüft, steht unter Die Payload-Prüfung.
Assets
Ein Asset ist eine Datei, die zur Revision gehört, nicht zur Vorlage. Ein neues Logo ändert also keine ältere Revision.
- Der Name ist ein einfacher Dateiname: Buchstaben, Ziffern,
.,_,-, kein Pfadtrenner, kein Punkt am Anfang, höchstens 128 Zeichen. Sonsttemplate-asset-name-invalid. - Erlaubte Typen:
image/png,image/jpeg,image/gif,image/svg+xml,image/webp,font/ttf,font/otf,font/woff,font/woff2,text/css. Sonsttemplate-asset-type-not-allowed. - Ein Asset ohne Inhalt ist
template-asset-empty, ein Name zweimaltemplate-asset-duplicate|<position>.
Die Vorlage nennt ein Asset als asset:logo.png. Mehr dazu unter Die Vorlagensprache.
Wo die Zeilen liegen
System-Ausgaben liegen in der System-Datenbank. Mandanten- und Personen-Ausgaben liegen in der Datenbank ihres Mandanten. So bleiben die Vorlagen eines Mandanten bei seinen übrigen Daten. Hat eine Installation nur eine Datenbank, ist es dieselbe. Mehr unter Datenbank.
Alles außer dem Entwurf wird nur eingefügt, nie geändert oder gelöscht. Der Entwurf ist die einzige Zeile, die sich ändert.