Merkmale (categories)
Zuletzt geändert: 18.07.2026 15:19

Merkmale (API: categories) #

Der EULANDA-Merkmalbaum ist die zentrale Filter-Struktur der Stammdaten: Datensätze werden Baumknoten zugeordnet (Mehrfachzuordnung möglich) und darüber gefiltert. Die API führt diese Struktur unter dem Namen categories - der branchenübliche Begriff für einen hierarchischen Kategoriebaum mit Mehrfachzuordnung. Abgrenzung: productGroup (Warengruppe) ist ein einwertiges Feld am Artikel, categories ist der mehrwertige Baum.

Unterstützt für articles und addresses (weitere Ressourcen folgen).

Endpunkte #

MethodePfadScopeZweck
GET/api/v1/articles/categoriesarticles:readMerkmalbaum der Artikel
GET/api/v1/addresses/categoriesaddresses:readMerkmalbaum der Adressen
GET/api/v1/articles/{id}/categoriesarticles:readMerkmale eines Artikels
GET/api/v1/addresses/{id}/categoriesaddresses:readMerkmale einer Adresse
PUT/api/v1/{entity}/{id}/categories<entity>:writeZuordnung komplett ersetzen
PATCH/api/v1/{entity}/{id}/categories<entity>:writeZuordnung ändern (add/remove)
POST/api/v1/{entity}/categoriescategories:manageKnoten anlegen
PATCH/api/v1/{entity}/categories/{nodeId}categories:manageKnoten umbenennen/verschieben
DELETE/api/v1/{entity}/categories/{nodeId}categories:manageKnoten löschen (?force=true für Äste)

Der Baum kommt als verschachtelte Struktur; ?lang= (bzw. Accept-Language) steuert die Sprache des rootLabel:

{
  "rootLabel": "Artikel",
  "nodes": [
    {
      "id": 103,
      "path": "\\KEYWORDS",
      "name": "KEYWORDS",
      "type": "folder",
      "children": [
        { "id": 108, "path": "\\KEYWORDS\\EANCOM", "name": "EANCOM", "type": "manual" }
      ]
    }
  ]
}
  • path ist der DB-Pfad ohne Stamm-Präfix - exakt dieser Wert geht in die Filter-Parameter. rootLabel dient nur der Anzeige.
  • type: folder (Ordner), manual (Listen-Merkmal, direkte Zuordnung), smart (SQL-Merkmal, dynamische Menge - siehe unten).
  • Knoten mit führendem Punkt im Namen (.USER-Zweige) sowie Arbeitslisten und CustomValues erscheinen nicht im Baum.

Die Merkmale eines Datensatzes liefern dieselben Knotenwerte, damit der Client sie direkt gegen den Baum abgleichen kann. Ein unbekannter Datensatz liefert 404 NOT_FOUND:

{
  "items": [
    { "id": 108, "path": "\\KEYWORDS\\EANCOM", "type": "manual" },
    { "id": 124, "path": "\\Shop\\Software", "type": "manual" }
  ]
}

Zuordnung schreiben #

PUT ersetzt die Zuordnung komplett (leere Liste = alle Merkmale entfernen), PATCH ändert sie im Delta. Die Antwort zählt echte Änderungen; skipped sind No-Ops (add eines bereits zugeordneten bzw. remove eines nicht zugeordneten Pfades):

PUT   { "paths": ["\\KEYWORDS\\EANCOM", "\\Shop\\Software"] }
PATCH { "add": ["\\KEYWORDS\\EANCOM"], "remove": ["\\Shop\\Sonstiges"] }

Antwort: { "added": 1, "removed": 1, "skipped": 0 }
  • Pfade zielen auf den exakten Knoten (keine Nachfahren). Ordner dürfen zugeordnet werden, SQL-Merkmale (smart) nicht (422 CATEGORY_NOT_ASSIGNABLE).
  • PUT ersetzt nur den sichtbaren Bestand - Zuordnungen in ausgeblendeten Zweigen (.USER u.a.) bleiben unberührt.
  • Das Anwenden läuft in einer Transaktion; das Entfernen ist gegen den Merkmalbaum der eigenen Tabelle abgesichert.

Listen-Filter #

Jede Listen-Ressource mit Merkmalbaum versteht drei zusätzliche, wiederholbare Query-Parameter:

ParameterVerknüpfung
categoryAndjeder Pfad muss zutreffen (UND)
categoryOrmindestens ein Pfad muss zutreffen (ODER-Gruppe)
categoryNotkeiner der Pfade darf zutreffen (NICHT-Gruppe)

Gesamtformel: And1 AND And2 AND (Or1 OR Or2) AND NOT (Not1 OR Not2) - identisch zur Merkmal-Filterung in EULANDA selbst. Ein Pfad trifft zu, wenn der Datensatz dem Knoten oder einem seiner Unterknoten zugeordnet ist. Die Filter kombinieren sich frei mit q, Gleichheitsfiltern, changedSince, Paging und Sortierung.

Beispiele #

$h = @{ Authorization = "Bearer $key" }

# Merkmalbaum der Artikel (deutsch)
Invoke-RestMethod 'http://localhost:8100/api/v1/articles/categories?lang=de' -Headers $h

# Artikel mit Merkmal \KEYWORDS\EANCOM, aber ohne \Shop\Sonstiges
$url = 'http://localhost:8100/api/v1/articles?fields=id,articleNumber,name' +
    '&categoryAnd=' + [uri]::EscapeDataString('\KEYWORDS\EANCOM') +
    '&categoryNot=' + [uri]::EscapeDataString('\Shop\Sonstiges')
Invoke-RestMethod $url -Headers $h

# ODER-Gruppe: Parameter einfach wiederholen
Invoke-RestMethod ('http://localhost:8100/api/v1/articles?categoryOr=%5CShop%5CSoftware&categoryOr=%5CShop%5CHardware') -Headers $h
curl.exe -H "Authorization: Bearer %KEY%" "http://localhost:8100/api/v1/articles/categories?lang=de"

curl.exe -H "Authorization: Bearer %KEY%" "http://localhost:8100/api/v1/articles?categoryAnd=%5CKEYWORDS%5CEANCOM&categoryNot=%5CShop%5CSonstiges&fields=id,articleNumber,name"

curl.exe -H "Authorization: Bearer %KEY%" "http://localhost:8100/api/v1/addresses?categoryOr=%5CShop&categoryOr=%5CTEST"

Der Backslash muss im Query-String URL-kodiert werden (%5C).

Baumpflege (Scope categories:manage) #

Die Baumpflege hat bewusst einen eigenen Scope, getrennt von den Entity-Scopes - wer Datensätze pflegen darf, darf damit noch nicht die Merkmalstruktur umbauen.

Anlegen (POST): Body { "path": "\\Neu\\Unterordner", "type": "folder" | "manual", "description"? }. Fehlende Zwischenordner legt EULANDA selbst an (cn_MerkOpenPath). Existiert der Pfad bereits mit demselben Typ, ist der Aufruf idempotent (200 statt 201); bei anderem Typ 409 CATEGORY_CONFLICT.

Umbenennen/Verschieben (PATCH /{nodeId}): Body mit einer Kombination aus name, parentId (0 = direkt unter die Wurzel), description, color. Verschieben gilt für den ganzen Ast und ist im Adjazenzlisten-Modell ein einziges Update - Pfade werden zur Laufzeit berechnet, nichts muss umgeschrieben werden. Abgesichert: Zyklen (422) und Namenskollisionen am Zielort (409 CATEGORY_CONFLICT). Achtung: Gespeicherte Filter-Presets mit alten Pfaden zeigen nach dem Verschieben ins Leere - wie in EULANDA selbst.

Löschen (DELETE /{nodeId}): ohne Parameter nur leere Knoten; ?force=true löscht den ganzen Ast samt aller Zuordnungen in einer Transaktion. Antwort 204.

Bewusste Grenzen: SQL-Merkmale (smart) sind per REST weder anlegbar noch ist ihre SqlBedingung änderbar - wer die Bedingung ändern darf, schreibt SQL, das der Server ausführt; diese Pflege bleibt der EULANDA-Oberfläche vorbehalten. Umbenennen/Verschieben/Löschen von smart-Knoten ist erlaubt. Wurzel, versteckte Zweige (führender Punkt), Arbeitslisten und CustomValues sind nicht erreichbar.

SQL-Merkmale (smart) #

SQL-Merkmale speichern statt einer Zuordnungsliste eine WHERE-Bedingung in der Datenbank; ihre Treffermenge ist dynamisch (im Baum type: "smart"). Der Filter bindet sie automatisch ein: Ein Pfad trifft zu, wenn der Datensatz einem Knoten des Teilbaums zugeordnet ist oder eine der SQL-Bedingungen des Teilbaums erfüllt - exakt wie in EULANDA selbst. Alias-Platzhalter ([THIS]., historische Tabellen-Aliasse) werden dabei serverseitig auf die Zieltabelle umgeschrieben, ausschließlich außerhalb von Stringliteralen und Kommentaren.

Die Bedingungen sind admin-gepflegter Datenbank-Inhalt (Vertrauensstufe wie Views und Prozeduren). Wer sie in der API nicht möchte, setzt in der xrest.config.json "EnableSmartCategories": false - Filter-Pfade mit SQL-Merkmal im Teilbaum liefern dann 400 CATEGORY_SMART_DISABLED statt stillschweigend unvollständiger Ergebnisse.

Fehler #

StatusCodeBedeutung
400VALIDATION_CATEGORY_UNKNOWNMerkmalpfad existiert nicht (Pfad steht in detail und errors[])
400CATEGORY_SMART_DISABLEDPfad enthält ein SQL-Merkmal (smart) und EnableSmartCategories ist aus
400VALIDATION_QUERY_INVALIDRessource ohne Merkmalbaum oder ungültiger Pfad (z.B. nur der Stamm)
400VALIDATION_CATEGORY_INVALIDUngültiger Pfad im Request-Body (z.B. nur der Stamm)
404NOT_FOUNDDatensatz zu /{id}/categories existiert nicht
409CATEGORY_CONFLICTPfad existiert mit anderem Typ bzw. Namenskollision am Zielort
409CATEGORY_NOT_EMPTYKnoten hat Unterknoten/Zuordnungen und force fehlt
422CATEGORY_NOT_ASSIGNABLEZuordnungsziel ist ein SQL-Merkmal (smart)
422VALIDATION_FIELD_INVALIDUngültiger Feldwert (z.B. type, parentId-Zyklus)
422VALIDATION_NO_FIELDSPUT ohne paths bzw. PATCH ohne änderbare Felder