# Funktionsreferenz: Übergreifende fn-Funktionen

Die übergreifenden fn-Funktionen ermöglichen serverseitige Berechnungen, Konvertierungen und Datenfeld-Auswertungen direkt in der GraphQL-Query. Sie sind in `slowFilter`-Ausdrücken, in `expr:`-Parametern (`_string`, `_int`, `_float`, `_if`) und in verschachtelten Expressions verfügbar. Sie operieren auf Feldwerten und Konstanten und werden vom ERP-Server ausgewertet.

Grundlegende GraphQL-Kenntnisse werden vorausgesetzt. Sollten Ihnen Konzepte wie Tabellenzugriff, Filter oder `slowFilter`-Syntax noch nicht vertraut sein, empfehlen wir zunächst die [GraphQL Doku - Abfragen (Queries)](https://hilfe.microtech.de/18_graphql/graphqldoku/index.md).

---

## Inhaltsverzeichnis

1. [Überblick](#1-uberblick)
2. [Syntax](#2-syntax)
3. [Text-Funktionen](#3-text-funktionen)
4. [Datum/Zeit-Funktionen](#4-datumzeit-funktionen)
5. [Konvertierungs-Funktionen](#5-konvertierungs-funktionen)
6. [Benutzer-Funktionen](#6-benutzer-funktionen)
7. [Nachschlagen/Auswerten-Funktionen](#7-nachschlagenauswerten-funktionen)
8. [Praxisbeispiele](#8-praxisbeispiele)
9. [Stolpersteine](#9-stolpersteine)

---

## 1. Überblick

Die Funktionen decken folgende Bereiche ab:

| Kategorie | Beispiele |
|-----------|-----------|
| Text/String | `fnLeft`, `fnPos`, `fnLength`, `fnGetPosString` |
| Datum/Zeit | `fnYear`, `fnDiffDate`, `fnIncDate`, `fnFormatDateTime` |
| Konvertierung | `fnToInt`, `fnToDate`, `fnToBool`, `fnToText` |
| Benutzer | `fnGetAktBzr`, `fnGetAktBzrNr`, `fnGetAktBzrSel` |
| Nachschlagen/Auswerten | `fnDSInfo`, `fnEntfernung`, `fnGetBit`, `fnGetAlter`, `fnGetPlzInfo` |

`fnGetBit` und `fnToBool` können direkt als Filterbedingung verwendet werden, ohne Vergleichsoperator.

!!! warning "Beachten Sie"

    In Filtern sind fn-Funktionen nur über `slowFilter` verfügbar, nicht über `fastFilter`. Wenn ein Filter auch ohne fn-Funktionen formuliert werden kann, ist `fastFilter` immer vorzuziehen, weil der Server die Daten direkt auf dem Index filtert statt sie erst zu laden und dann auszuwerten.

---

## 2. Syntax

### Parameter-Konvention

Alle fn-Funktionen nehmen eine **positionelle Liste** von Expressions als Parameter. Jeder Parameter kann ein Literal (`value`), ein Feldverweis (`field`) oder eine verschachtelte Funktion sein.

```graphql
# Literal
{fnLeft: [{value: "Hallo Welt"}, {value: 5}]}

# Feldverweis
{fnLeft: [{field: fldSuchBeg}, {value: 3}]}

# Verschachtelt: erst Datum konvertieren, dann Jahr extrahieren
{fnYear: [{fnToDate: [{value: "14.04.2026"}]}]}
```

### Verwendung in slowFilter

Expression-Funktionen werden in Vergleichsoperatoren eingebettet:

```graphql
query {
  tblAddresses {
    rowsRead(slowFilter: {
      gt: [{fnLength: [{field: fldSuchBeg}]}, {value: 20}]
    }) {
      fldAdrNr
      fldSuchBeg
    }
  }
}
```

### Verwendung in Mutations-Expressions

Expression-Funktionen können auch direkt als Mutation über die `_string`, `_int`, `_float` Expression-Felder auf Root-Ebene ausgewertet werden:

```graphql
mutation {
  heute: _string(expr: { fnGetAktDate: [] })
  jahr:  _int(expr: { fnYear: [{fnGetAktDate: []}] })
  left:  _string(expr: { fnLeft: [{value: "Testprodukt"}, {value: 4}] })
}
```

```json
{
  "heute": "15.04.2026",
  "jahr": 2026,
  "left": "Test"
}
```

---

## 3. Text-Funktionen

### fnLeft(text, anzahl) → String

Gibt die ersten `anzahl` Zeichen von `text` zurück.

```graphql
mutation {
  result: _string(expr: { fnLeft: [{value: "Testprodukt Premium"}, {value: 4}] })
}
```

```json
{ "result": "Test" }
```

### fnRight(text, anzahl) → String

Gibt die letzten `anzahl` Zeichen von `text` zurück. Gibt einen leeren String zurück wenn `anzahl` größer als die Textlänge ist.

```graphql
mutation {
  result: _string(expr: { fnRight: [{value: "Testprodukt Premium"}, {value: 7}] })
}
```

```json
{ "result": "Premium" }
```

### fnMid(text, start, anzahl) → String

Gibt `anzahl` Zeichen ab Position `start` (1-basiert) aus `text` zurück.

```graphql
mutation {
  result: _string(expr: { fnMid: [{value: "RE12600002"}, {value: 3}, {value: 3}] })
}
```

```json
{ "result": "126" }
```

### fnPos(suchtext, text) → Int

Sucht `suchtext` in `text` und gibt die Position zurück (1-basiert). Gibt 0 zurück wenn nicht gefunden.

```graphql
mutation {
  result: _int(expr: { fnPos: [{value: "Premium"}, {value: "Testprodukt Premium"}] })
}
```

```json
{ "result": 13 }
```

!!! warning "Beachten Sie"

    **Achtung bei RTF-Feldern:** 
    
    Felder wie `fldBez1` sind intern RTF-Blobs. Vor der Verwendung in Text-Funktionen mit `fnToText` konvertieren: `{fnPos: [{value: "Silber"}, {fnToText: [{field: fldBez1}]}]}`

### fnLength(text) → Int

Gibt die Zeichenlänge von `text` zurück.

```graphql
mutation {
  result: _int(expr: { fnLength: [{value: "Hallo Welt"}] })
}
```

```json
{ "result": 10 }
```

### fnToString(wert) → String

Wandelt `wert` in einen String um. Liest Felder über ihren Rohwert. Bei RTF-Blob-Feldern wird daher der **rohe RTF-Markup** zurückgegeben (`{\rtf1\ansi...`), nicht der Klartext. Für RTF-Felder stattdessen `fnToText` verwenden.

```graphql
mutation {
  zahl:  _string(expr: { fnToString: [{value: 42}] })
  komma: _string(expr: { fnToString: [{value: 3.14}] })
}
```

```json
{ "zahl": "42", "komma": "3,14" }
```

### fnToText(wert) → String

Wandelt `wert` in Klartext um. Liest Felder über ihre Display-Darstellung. Bei **RTF-Blob-Feldern** (z.B. `fldBez1`) wird dadurch das RTF-Markup entfernt und der lesbare Text extrahiert.

```graphql
query {
  tblProducts {
    rowsRead(slowFilter: {
      gt: [{fnPos: [{value: "Silber"}, {fnToText: [{field: fldBez1}]}]}, {value: 0}]
    }) {
      fldArtNr
      fldBez1(as: DISPLAY_TEXT)
    }
  }
}
```

### fnGetPosString(text, position, trennzeichen?) → String

Teilt `text` am `trennzeichen` und gibt das Token an `position` zurück (1-basiert, Position 0 ergibt leeren String). `position` muss ein Integer sein. `trennzeichen` ist optional, Standard ist Komma.

```graphql
mutation {
  semikolon: _string(expr: {
    fnGetPosString: [{value: "Berlin;Hamburg;München"}, {value: 2}, {value: ";"}]
  })
  komma: _string(expr: {
    fnGetPosString: [{value: "Berlin,Hamburg,München"}, {value: 3}]
  })
}
```

```json
{ "semikolon": "Hamburg", "komma": "München" }
```

---

## 4. Datum/Zeit-Funktionen

### Datum erzeugen und konvertieren

| Funktion | Parameter | Rückgabe | Beschreibung |
|----------|-----------|----------|-------------|
| `fnDate(wert)` | [String] | Date | String in Datum konvertieren |
| `fnToDate(wert)` | [String] | Date | Alias für fnDate (gleiche Implementierung) |
| `fnTime(wert)` | [String] | Time | String in Uhrzeit konvertieren |
| `fnToTime(wert)` | [String] | Time | Alias für fnTime (gleiche Implementierung) |
| `fnToDateTime(wert)` | [String] | DateTime | String in Datum+Uhrzeit konvertieren |

Das Eingabeformat richtet sich nach den Ländereinstellungen des Servers. Auf einem deutschen Server:

- **Datum:** `"14.04.2026"` oder `"14.04.26"`
- **Uhrzeit:** `"14:30:00"` oder `"14:30"`
- **Datum+Uhrzeit:** `"14.04.2026 08:30:00"`

ISO-Format (`"2026-04-14"`) wird nicht akzeptiert.

```graphql
mutation {
  datum: _string(expr: {
    fnFormatDateTime: [{value: "dd.mm.yyyy"}, {fnToDate: [{value: "14.04.2026"}]}]
  })
}
```

```json
{ "datum": "14.04.2026" }
```

### Datum-Bestandteile extrahieren

| Funktion | Parameter | Rückgabe | Beschreibung |
|----------|-----------|----------|-------------|
| `fnYear(datum)` | [Date/DateTime] | Int | Jahreszahl |
| `fnMonth(datum)` | [Date/DateTime] | Int | Monat (1–12) |
| `fnDay(datum)` | [Date/DateTime] | Int | Tag im Monat (1–31) |
| `fnHour(datum)` | [DateTime] | Int | Stunde (0–23) |
| `fnMinute(datum)` | [DateTime] | Int | Minute (0–59) |
| `fnSecond(datum)` | [DateTime] | Int | Sekunde (0–59) |

```graphql
mutation {
  jahr:   _int(expr: { fnYear:   [{fnGetAktDate: []}] })
  monat:  _int(expr: { fnMonth:  [{fnGetAktDate: []}] })
  tag:    _int(expr: { fnDay:    [{fnGetAktDate: []}] })
  stunde: _int(expr: { fnHour:   [{fnGetAktDate: [{value: "Time"}]}] })
  minute: _int(expr: { fnMinute: [{fnGetAktDate: [{value: "Time"}]}] })
}
```

```json
{ "jahr": 2026, "monat": 4, "tag": 15, "stunde": 16, "minute": 30 }
```

### Aktuelles Datum

| Funktion | Parameter | Rückgabe | Beschreibung |
|----------|-----------|----------|-------------|
| `fnGetDate()` | [] | Date | Aktuelles Datum |
| `fnGetAktDate()` | [] | Date | Aktuelles Programmdatum |
| `fnGetAktDate("Time")` | ["Time"] | DateTime | Programmdatum mit Uhrzeit |

```graphql
mutation {
  datum:   _string(expr: { fnGetAktDate: [] })
  mitZeit: _string(expr: { fnGetAktDate: [{value: "Time"}] })
}
```

```json
{
  "datum": "15.04.2026",
  "mitZeit": "15.04.2026 16:30:31"
}
```

### Datum-Arithmetik

#### fnDiffDate(startDatum, endDatum) → Int

Berechnet die Anzahl der Tage zwischen `startDatum` und `endDatum`. Positiv wenn `endDatum` nach `startDatum` liegt.

```graphql
mutation {
  tage: _int(expr: { fnDiffDate: [
    {fnToDate: [{value: "01.01.2026"}]},
    {fnToDate: [{value: "15.04.2026"}]}
  ] })
}
```

```json
{ "tage": 104 }
```

#### fnIncDate(datum, tage?, monate?) → Date

Addiert Tage und/oder Monate zu einem Datum. Negative Werte subtrahieren.

```graphql
mutation {
  plus7Tage: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnIncDate: [{fnGetAktDate: []}, {value: 7}]}
  ] })
  plus3Monate: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnIncDate: [{fnGetAktDate: []}, {value: 0}, {value: 3}]}
  ] })
}
```

```json
{ "plus7Tage": "22.04.2026", "plus3Monate": "15.07.2026" }
```

#### fnGetDateQuartalAnfang(datum) → Date

Gibt den ersten Tag des Quartals zurück in dem `datum` liegt.

#### fnGetDateQuartalEnde(datum) → Date

Gibt den letzten Tag des Quartals zurück in dem `datum` liegt. Die Rückgabe muss mit `fnFormatDateTime` formatiert werden.

```graphql
mutation {
  anfang: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnGetDateQuartalAnfang: [{fnGetAktDate: []}]}
  ] })
  ende: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnGetDateQuartalEnde: [{fnGetAktDate: []}]}
  ] })
}
```

```json
{ "anfang": "01.04.2026", "ende": "30.06.2026" }
```

### fnFormatDateTime(format, datum) → String

Formatiert `datum` nach der angegebenen `format`-Zeichenkette.

| Platzhalter | Bedeutung | Beispiel |
|------------|-----------|---------|
| `dd` | Tag mit führender Null | 15 |
| `mm` | Monat mit führender Null | 04 |
| `yyyy` | Vierstelliges Jahr | 2026 |
| `yy` | Zweistelliges Jahr | 26 |
| `hh` | Stunde mit führender Null | 16 |
| `nn` | Minute mit führender Null | 30 |
| `ss` | Sekunde mit führender Null | 00 |
| `w` | Kalenderwoche | 16 |
| `ddd` | Wochentag (Abkürzung) | Di |
| `dddd` | Wochentag (ausgeschrieben) | Dienstag |

```graphql
mutation {
  iso:     _string(expr: { fnFormatDateTime: [{value: "yyyy-mm-dd"}, {fnGetAktDate: []}] })
  deutsch: _string(expr: { fnFormatDateTime: [{value: "dd.mm.yyyy hh:nn:ss"}, {fnGetAktDate: [{value: "Time"}]}] })
}
```

```json
{ "iso": "2026-04-15", "deutsch": "15.04.2026 16:30:31" }
```

---

## 5. Konvertierungs-Funktionen

| Funktion | Parameter | Rückgabe | Beschreibung |
|----------|-----------|----------|-------------|
| `fnToInt(wert)` | [Any] | Int | In Ganzzahl konvertieren |
| `fnToFloat(wert)` | [Any] | Float | In Fließkommazahl konvertieren |
| `fnToBool(wert)` | [Any] | Boolean | In Wahrheitswert konvertieren (0/1, true/false) |
| `fnToString(wert)` | [Any] | String | In String konvertieren (bei RTF-Feldern: roher Markup) |
| `fnToText(wert)` | [Any] | String | In Klartext konvertieren (bei RTF-Feldern: Markup entfernt) |
| `fnToDate(wert)` | [String] | Date | In Datum konvertieren (Serverformat) |
| `fnToTime(wert)` | [String] | Time | In Uhrzeit konvertieren |
| `fnToDateTime(wert)` | [String] | DateTime | In Datum+Uhrzeit konvertieren |

```graphql
mutation {
  ganzzahl:  _int(expr: { fnToInt: [{value: "42"}] })
  kommazahl: _float(expr: { fnToFloat: [{value: "3,14"}] })
  bool:      _if(expr: { fnToBool: [{value: 1}] }) { r: _string(value: "true") }
}
```

```json
{ "ganzzahl": 42, "kommazahl": 3.14, "bool": { "r": "true" } }
```

!!! warning "Beachten Sie"

    `fnToFloat` nutzt die Ländereinstellungen des Servers für das Dezimal- und Tausendertrennzeichen. Das Komma wird zusätzlich immer als Dezimaltrennzeichen akzeptiert. Auf einem deutschen Server: `"3,14"` → 3.14, `"3.14"` → 314 (Punkt = Tausender-Separator).

---

## 6. Benutzer-Funktionen

| Funktion | Parameter | Rückgabe | Beschreibung |
|----------|-----------|----------|-------------|
| `fnGetAktBzr()` | [] | String | Benutzerkürzel des angemeldeten Benutzers |
| `fnGetAktBzrNr()` | [] | Int/String | Nummer des angemeldeten Benutzers |
| `fnGetAktBzrSel(n)` | [Int] | String | Selektionsfeld `n` des angemeldeten Benutzers |

```graphql
mutation {
  kuerzel:    _string(expr: { fnGetAktBzr: [] })
  nummer:     _string(expr: { fnGetAktBzrNr: [] })
  selektion1: _string(expr: { fnGetAktBzrSel: [{value: 1}] })
}
```

```json
{ "kuerzel": "gql", "nummer": "2", "selektion1": "" }
```

!!! info "Info"

    Die Rückgabewerte sind abhängig vom angemeldeten Benutzer.

---

## 7. Nachschlagen/Auswerten-Funktionen

### fnDSInfo(feldname, schlüssel, format?, trennzeichen?) → String

Liest einen Wert aus einem Datenfeld das als Key-Value-Liste (CommaText) gespeichert ist. Nicht zu verwechseln mit `DBInfo()` im ERP-Feldeditor (Cross-Table-Lookup), das im GraphQL-Schema nicht verfügbar ist.

| Parameter | Typ | Beschreibung |
|-----------|-----|-------------|
| 1 | String | Feldname oder Key-Value-String |
| 2 | String | Schlüssel dessen Wert zurückgegeben wird (leer = gesamte Liste) |
| 3 (optional) | String | `"Text"` für Zeilenformat, `"SvlFormat"` für StringValueList |
| 4 (optional) | String | Trennzeichen zwischen Name und Value |

Der erste Parameter kann ein **Feldname** des aktuellen Datensatzes sein (wird dann zur Laufzeit gelesen) oder ein **direkter Key-Value-String**. Wenn kein `=` im Wert vorkommt, wird er als Feldname interpretiert.

```graphql
mutation {
  tblAddresses {
    name: _string(expr: {
      fnDSInfo: [{value: "Name=Mueller,Ort=Berlin"}, {value: "Name"}]
    })
    ort: _string(expr: {
      fnDSInfo: [{value: "Name=Mueller,Ort=Berlin"}, {value: "Ort"}]
    })
    alles: _string(expr: {
      fnDSInfo: [{value: "Name=Mueller,Ort=Berlin"}, {value: ""}]
    })
  }
}
```

```json
{
  "tblAddresses": {
    "name": "Mueller",
    "ort": "Berlin",
    "alles": "Name=Mueller,Ort=Berlin"
  }
}
```

Im `slowFilter` kann der erste Parameter auch ein Feldverweis sein. Dann liest `fnDSInfo` den Inhalt dieses Feldes und parst ihn als Key-Value-Liste:

```
slowFilter: {eq: [{fnDSInfo: [{field: fldSel1}, {value: "Typ"}]}, {value: "Gold"}]}
```

!!! warning "Beachten Sie"

    `fnDSInfo` funktioniert im Tabellenkontext (Mutation auf einer Tabelle oder `slowFilter`), nicht in Mutations-Expressions auf Root-Ebene.

### fnGetBit(wert, bitNr) → Boolean

Prüft ob das Bit an Position `bitNr` (0-basiert) in `wert` gesetzt ist.

```graphql
mutation {
  bit0: _if(expr: { fnGetBit: [{value: 5}, {value: 0}] }) { r: _string(value: "gesetzt") }
  bit1: _if(expr: { fnGetBit: [{value: 5}, {value: 1}] }) { r: _string(value: "gesetzt") }
  bit2: _if(expr: { fnGetBit: [{value: 5}, {value: 2}] }) { r: _string(value: "gesetzt") }
}
```

```json
{
  "bit0": { "r": "gesetzt" },
  "bit1": null,
  "bit2": { "r": "gesetzt" }
}
```

5 = binär 101: Bit 0 gesetzt, Bit 1 nicht, Bit 2 gesetzt. Nützlich für Felder die Bitmasken speichern (z.B. Status-Flags, Berechtigungen).

### fnEntfernung(plz1, plz2, land1?, land2?) → Float

Berechnet die Entfernung zwischen zwei Postleitzahlen in Kilometern. Gibt einen negativen Wert zurück wenn die Berechnung nicht möglich ist (fehlende PLZ-Geodaten). Optional können Länderkennzeichen mitgegeben werden.

| Parameter | Typ | Beschreibung |
|-----------|-----|-------------|
| 1 | String | PLZ Ausgangspunkt |
| 2 | String | PLZ Zielpunkt |
| 3 (optional) | String | Länderkennzeichen Ausgangspunkt |
| 4 (optional) | String | Länderkennzeichen Zielpunkt |

```graphql
mutation {
  km: _float(expr: { fnEntfernung: [{value: "55543"}, {value: "60329"}] })
}
```

```json
{ "km": -1 }
```

!!! info "Info"

    Negativer Wert wenn PLZ-Geodaten auf dem Server nicht verfügbar sind.

### fnGetAlter(datum) → Int

Berechnet das Alter in Jahren ausgehend von `datum`. Verwendet das Serverdatum als Referenz.

```graphql
mutation {
  alter: _int(expr: { fnGetAlter: [{fnToDate: [{value: "15.06.1990"}]}] })
}
```

```json
{ "alter": 35 }
```

### fnGetPlzInfo(plz, feldname?) → String

Gibt zu `plz` Informationen aus der PLZ-Tabelle zurück.

| Parameter | Typ | Beschreibung |
|-----------|-----|-------------|
| 1 | String | Postleitzahl |
| 2 (optional) | String | Feldname (leer = Ortsname) |

```graphql
mutation {
  ort: _string(expr: { fnGetPlzInfo: [{value: "55543"}] })
}
```

```json
{ "ort": "Bad Kreuznach" }
```

---

## 8. Praxisbeispiele

### 8.1 Artikel mit „Silber" in der Bezeichnung finden

Die Artikelbezeichnung (`fldBez1`) ist ein RTF-Blob-Feld. Vor der Textsuche muss es mit `fnToText` konvertiert werden. Dieses Feld ist im fastFilter nicht verfügbar.

```graphql
query {
  tblProducts {
    rowsRead(slowFilter: {
      # fnPos gibt 0 zurück wenn nicht gefunden, >0 wenn gefunden
      gt: [{fnPos: [
        {value: "Silber"},
        {fnToText: [{field: fldBez1}]}  # RTF → Klartext
      ]}, {value: 0}]
    }) {
      fldArtNr
      fldBez1(as: DISPLAY_TEXT)
    }
  }
}
```

### 8.2 Adressen mit langem Suchbegriff

Zeichenlänge ist eine Berechnung die nur per slowFilter möglich ist:

```graphql
query {
  tblAddresses {
    rowsRead(slowFilter: {
      gt: [
        {fnLength: [{field: fldSuchBeg}]},  # Länge berechnen
        {value: 20}                          # mit 20 vergleichen
      ]
    }) {
      fldAdrNr
      fldSuchBeg
    }
  }
}
```

### 8.3 Vorgänge aus dem aktuellen Quartal

`fnGetDateQuartalAnfang` und `fnGetDateQuartalEnde` berechnen die Quartalsgrenzen dynamisch. Im Gegensatz zu einem statischen Datumsvergleich per fastFilter passt sich der Filter automatisch an:

```graphql
query {
  tblTransactions {
    rowsRead(slowFilter: {
      and: [
        # Vorgangsdatum >= Quartalsanfang
        {ge: [{field: fldDat}, {fnGetDateQuartalAnfang: [{fnGetAktDate: []}]}]},
        # Vorgangsdatum <= Quartalsende
        {le: [{field: fldDat}, {fnGetDateQuartalEnde: [{fnGetAktDate: []}]}]}
      ]
    }) {
      fldBelegNr
      fldDat
    }
  }
}
```

### 8.4 Quartalsgrenzen und Datumsarithmetik berechnen

Quartalsgrenzen, Datumsverschiebungen und Formatierungen in einer Query:

```graphql
mutation {
  # Quartalsanfang und -ende für das aktuelle Datum
  quartalAnfang: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnGetDateQuartalAnfang: [{fnGetAktDate: []}]}
  ] })
  quartalEnde: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnGetDateQuartalEnde: [{fnGetAktDate: []}]}
  ] })
  # Datum verschieben: 7 Tage in die Zukunft
  plus7Tage: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnIncDate: [{fnGetAktDate: []}, {value: 7}]}
  ] })
  # Datum verschieben: 3 Monate in die Zukunft (0 Tage, 3 Monate)
  plus3Monate: _string(expr: { fnFormatDateTime: [
    {value: "dd.mm.yyyy"},
    {fnIncDate: [{fnGetAktDate: []}, {value: 0}, {value: 3}]}
  ] })
}
```

```json
{
  "quartalAnfang": "01.04.2026",
  "quartalEnde": "30.06.2026",
  "plus7Tage": "22.04.2026",
  "plus3Monate": "15.07.2026"
}
```

### 8.5 Adressen nach Suffix im Suchbegriff filtern

`fnRight` extrahiert die letzten Zeichen eines Feldes. Suffix-Suchen sind mit fastFilter nicht möglich:

```graphql
query {
  tblAddresses {
    rowsRead(slowFilter: {
      eq: [
        {fnRight: [{field: fldSuchBeg}, {value: 8}]},  # letzte 8 Zeichen
        {value: "RECHNUNG"}                              # mit "RECHNUNG" vergleichen
      ]
    }) {
      fldAdrNr
      fldSuchBeg
    }
  }
}
```

### 8.6 Vorgänge aus einem bestimmten Monat per fastFilter-Datumsbereich

Wenn das Datum als Feld im fastFilter verfügbar ist (wie `fldDat` bei Vorgängen), ist ein Datumsbereich per fastFilter immer vorzuziehen:

```graphql
query {
  tblTransactions {
    # fastFilter: performanter als slowFilter mit fnYear+fnMonth
    rowsRead(fastFilter: {
      and: [
        {ge: [{field: fldDat}, {value: "2026-03-01"}]},  # ab 1. März
        {lt: [{field: fldDat}, {value: "2026-04-01"}]}    # bis vor 1. April
      ]
    }) {
      fldBelegNr
      fldDat
    }
  }
}
```

### 8.7 Adressen deren Suchbegriff "GALERIE" enthält

`fnPos` auf einem normalen String-Feld (kein RTF, kein `fnToText` nötig):

```graphql
query {
  tblAddresses {
    rowsRead(slowFilter: {
      gt: [{fnPos: [{value: "GALERIE"}, {field: fldSuchBeg}]}, {value: 0}]
    }) {
      fldAdrNr
      fldSuchBeg
    }
  }
}
```

### 8.8 Semikolon-getrenntes Feld aufsplitten

Ein Selektionsfeld enthält mehrere Werte getrennt durch Semikolon. Das zweite Token extrahieren:

```graphql
mutation {
  token: _string(expr: {
    # "Berlin;Hamburg;München" am ";" splitten, Position 2 holen
    fnGetPosString: [{value: "Berlin;Hamburg;München"}, {value: 2}, {value: ";"}]
  })
}
```

```json
{ "token": "Hamburg" }
```

---

## 9. Stolpersteine

### Parameter-Typen beachten

Die Fehlermeldungen des Servers sind aussagekräftig:

```
GetAktBzrSel(): Ungültiger Parametertype. 1. Parameter Erwartet: Integer. Gefunden: ftWideString
```

Häufige Fehlerquellen:
- Integer-Parameter als String übergeben: `{value: "1"}` statt `{value: 1}`
- Datum als String statt als `fnToDate`-Ergebnis: `{value: "14.04.2026"}` statt `{fnToDate: [{value: "14.04.2026"}]}`
