agentsclimarketplace

Microtech graphql

Skill microtech-gmbh/microtech-skills/microtech-graphql

KI Skills für das microtech ERP-System. GraphQL-Queries, Vorgangsverwaltung und mehr - optimiert für die Nutzung mit GraphQL MCP-Servern

Install
npx -y skills add microtech-gmbh/microtech-skills --skill microtech-graphql

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Erstellt und optimiert GraphQL-Queries und Mutations fuer die microtech ERP microtech ERP. Deckt den gesamten ERP-Datenbestand ab: Artikel, Warengruppen, Stuecklisten, Adressen mit Anschriften und Ansprechpartnern, Vorgaenge (Angebote, Auftragsbestaetigungen, Lieferscheine, Rechnungen, Gutschriften, Bestellungen), Vorgangspositionen, offene Posten, Lagerbestaende, Projekte, Dokumente (DMS), Kalender, Kontakte, Kontenplan, Zahlungsverkehr und SEPA-Mandate. Unterstuetzt Lesen, Anlegen, Aendern, Loeschen, Buchen (fnPost), Stornieren (fnReverse), Wandeln (fnConvert), Archivieren sowie ERP-Parametertabellen (Vorgangsarten, Steuerschluessel, Zahlungsbedingungen, Einheiten, Versandarten, Waehrungen, Mahnstufen, Adressstatus, Projektstatus, Dokumentenarten, Kommunikationsarten u.v.m.). Verwende diesen Skill wenn der Nutzer mit microtech ERP oder microtech ERP arbeiten will, ERP-Daten abfragen, Belege erstellen oder verarbeiten will, oder microtech-spezifische Begriffe wie tbl, fld, fnPost, fnConvert, rowRead, rowNew, rowSave, acoGPreis, lnkPostalAddresses, @acquireLocks erwaehnt. NICHT verwenden fuer andere ERP-Systeme (SAP, Shopify, Odoo, Dynamics), allgemeine SQL/Datenbank-Fragen oder generische GraphQL-Schemas ohne microtech-Bezug.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

37.8 KB, as published. Nobody here has run it

microtech GraphQL - Skill-Referenz

Kompakte Referenz fuer die GraphQL-Schnittstelle des microtech ERP. Optimiert fuer Query-Generierung ueber einen MCP-Server mit graphql_query-Tool.

ASCII: Dieses Dokument verwendet ae/oe/ue/ss statt Umlaute.

WICHTIG - Introspection: NIEMALS das vollstaendige Schema abrufen (__schema). Stattdessen gezielt einzelne Typen abfragen. Siehe references/introspection-guide.md.

Feldkataloge: Fuer detaillierte Feldlisten pro Tabelle siehe references/feldkatalog-*.md.

Erweiterte Themen: Fuer Archiv-Funktionen, externe Bearbeitung, Berechtigungen, Cross-Table-Patterns siehe references/vorgaenge-erweitert.md.

Mutation-Patterns: Fuer Optimistic Locking, rowCopy, ifNotExists, Deep Nesting, Massenoperationen siehe references/mutation-patterns.md.

Query-Patterns: Fuer keyFilter-Details, Arithmetik, skip-Wert, erweitertes @onNull, Schema-Metadaten siehe references/erweiterte-query-patterns.md.

Adressen-Verwaltung: Praxisbeispiele fuer Adressen, Anschriften und Ansprechpartner (lesen, anlegen, aendern, loeschen) mit verschachtelten Links, Optimistic Locking, rowCopy, Massenoperationen und assign-Parametern in references/adressen-verwaltung.md. Bei Adress-Aufgaben dort zuerst nachschlagen!

Parametertabellen: Konfigurations- und Parametertabellen auslesen (Vorgangsarten, Zahlungsbedingungen, Steuerschluessel, Einheiten, Versandarten, Waehrungen, Mahnstufen, Projektstatus u.v.m.) in references/parametertabellen.md. Bei Fragen zur mandantenspezifischen Konfiguration dort nachschlagen!

Workflow-Beispiele: 17 vollstaendige Szenarien (Rechnung erstellen, Vorgang wandeln, Storno, Lagerbestand u.v.m.) in references/workflow-beispiele.md. Bei komplexen Aufgaben dort zuerst nachschlagen!


Kritische Regeln (Top 9)

  1. keyFilter-First: Bei JEDEM Filter zuerst pruefen, ob ein passender by...-Index existiert und keyFilter verwenden. Erst wenn kein passender Index vorhanden ist, auf fastFilter oder slowFilter zurueckfallen. Siehe Abschnitt 5 fuer den verbindlichen Workflow.
  2. @oneOf-Regel: Pro Filterobjekt nur EIN Operator. Mehrere Bedingungen mit and/or kombinieren.
  3. fnPost/fnReverse/fnConvert nur in rowRead-Kontext (nicht rowModify!), immer in mutation.
  4. rowSave vor fnPost - ohne rowSave sind Positionen nicht gespeichert und werden nicht gebucht.
  5. Nach rowSave nicht mehr schreiben im selben Block - fuehrt zu Laufzeitfehler und Rollback.
  6. Leere Belegnummer = Operation fehlgeschlagen - immer fldBelegNr in fn...-Rueckgaben pruefen.
  7. Prozessketten-Integritaet - NIEMALS fn-Funktionen durch manuelle Operationen ersetzen! ERP-Prozessfunktionen (fnPost, fnReverse, fnConvert, fnMoveToArchive) sind die EINZIGE korrekte Art, den Belegstatus zu veraendern. Sie sichern die Prozesskette (z.B. Angebot → AB → Lieferschein → Rechnung), verknuepfen Belege, aktualisieren Lagerbestaende, erzeugen Buchungssaetze und fuehren Plausibilitaetspruefungen durch. Wenn eine fn-Funktion fehlschlaegt, STOPPEN und den Nutzer informieren - KEINEN Workaround versuchen.
  8. NIEMALS rowNew als Ersatz fuer fnConvert - ein manuell angelegter Folgebeleg hat KEINE Verknuepfung zum Vorgaenger. Die Prozesskette ist unterbrochen, Mengen werden nicht korrekt gefuehrt, Lieferstatus wird nicht aktualisiert. Stattdessen: Fehlerursache klaeren (Wandlungspfad nicht konfiguriert, Beleg bereits archiviert, etc.) und Nutzer fragen.
  9. NIEMALS rowDelete als Ersatz fuer fnReverse - eine Loesung per rowDelete umgeht die Storno-Logik: keine Storno-Belegnummer, keine Gegenbuchungen, keine Lagerkorrektur, kein Audit-Trail. Stattdessen: fnReverse verwenden, bei Fehler Nutzer informieren.

Arbeitsweise: So bearbeitest du Nutzeranfragen

Schritt 1: Anfrage klassifizieren

  • Lesen/Suchenquery (Daten anzeigen, suchen, auflisten, zaehlen)
  • Schreiben/Aendernmutation (anlegen, aendern, loeschen, buchen, wandeln)
  • Unbekannte Felder/Tabelle → Erst Introspection (references/introspection-guide.md)

Source of Truth: Bei Unsicherheit ueber Feldnamen, Schreibbarkeit oder verfuegbare Operationen IMMER die Live-API per Introspection pruefen. Dokumentierte Feldlisten koennen veraltet sein - die API ist die einzig zuverlaessige Quelle.

Schritt 2: Tabelle identifizieren

  • Nutze die Tabelle aus "Schnellreferenz" unten
  • Bei Unsicherheit: Introspection oder Nutzer fragen

Schritt 3: Reference-Dateien konsultieren

BEVOR du eine Query oder Mutation baust: Pruefe ob ein passendes Beispiel oder Detail-Wissen existiert.

  • Komplexe Aufgabe? → Zuerst references/workflow-beispiele.md durchsuchen (17 Szenarien)
  • Feldnamen unklar? → Passenden references/feldkatalog-*.md lesen
  • Filter-Logik?references/erweiterte-query-patterns.md (keyFilter, Arithmetik, Datums-Filter)
  • Adressen/Anschriften/Ansprechpartner?references/adressen-verwaltung.md (verschachtelte Links, rowReAnsNr, assign-Parameter, Massenoperationen)
  • Mutation-Pattern?references/mutation-patterns.md (Optimistic Locking, rowCopy, Deep Nesting)
  • Vorgangsfunktionen?references/vorgaenge-erweitert.md (Archiv, externe Bearbeitung)
  • Parametertabellen/Konfiguration?references/parametertabellen.md (Vorgangsarten, Steuerschluessel, Zahlungsbedingungen, Einheiten, Versandarten, Waehrungen, Mahnstufen, Projektstatus, Dokumentenarten u.v.m.)
  • Introspection noetig?references/introspection-guide.md (Typ-Namenskonventionen)

Nicht raten - nachschlagen! Die Reference-Dateien enthalten getestete, korrekte Beispiele. Lieber 30 Sekunden lesen als eine fehlerhafte Query bauen.

Schritt 4a: Query bauen (Lesen)

  1. Filter-Strategie (keyFilter-First!):
    • Gibt es einen by...-Index fuer das Suchfeld? → keyFilter verwenden
    • Kein Index? → fastFilter (einfache Gleichheit auf fld...)
    • Komplex? → slowFilter (Arithmetik, in, fn...)
  2. Query mit graphql_query-Tool ausfuehren
  3. Ergebnis dem Nutzer verstaendlich praesentieren

Schritt 4b: Mutation bauen (Schreiben)

  1. Operation waehlen:
    • Neuer Datensatz → rowNew (ggf. mit assignAddress/assignProduct)
    • Aendern → rowModify
    • Buchen/Stornieren/Wandeln → rowRead + fnPost/fnReverse/fnConvert
    • Loeschen → rowDelete (aber NICHT als Ersatz fuer fnReverse!)
  2. @acquireLocks pruefen: Bei Multi-Tabellen-Mutations immer angeben
  3. Speicher-Strategie: rowSave vs. rowSaveAndModify - bei Vorgaengen mit Positionen immer rowSaveAndModify + inneres rowSave vor fnPost
  4. Mutation ausfuehren und Ergebnis pruefen (leere Belegnummer = fehlgeschlagen!)

Schritt 5: Ergebnis verifizieren

  • Query: Kamen Daten zurueck? Paginierung noetig (hasNextPage)?
  • Mutation: fldBelegNr pruefen - leer bedeutet die Operation ist fehlgeschlagen
  • Fehler: Abschnitt "Troubleshooting" pruefen, Nutzer informieren
  • Nie stillschweigend Fehler ignorieren - immer dem Nutzer mitteilen was passiert ist

Typische Nutzer-Szenarien

Nutzer sagt...Du machst...
"Zeig mir Artikel X"querytblProductsrowRead mit keyFilter
"Alle Kunden aus PLZ 5xxxx"querytblAddresseslnkPostalAddresses → slowFilter auf fldPLZ
"Erstell eine Rechnung fuer Kunde 10000"mutation mit @acquireLockstblTransactionsrowNew + rowSave + fnPost
"Wandle Angebot 12345 in AB"mutationtblTransactionsrowRead + fnConvert
"Wieviele Artikel haben wir?"querytblProductsconRead(first: 100) → paginieren + hasNextPage
"Storniere Rechnung 67890"mutationtblTransactionsrowRead + fnReverse
"Zeig mir Adresse 10000 mit Anschriften und Ansprechpartnern"querytblAddresseslnkPostalAddresseslnkContactPeople → siehe references/adressen-verwaltung.md
"Leg einen neuen Kunden / Lieferanten / Interesenten an mit Anschrift und Ansprechpartner"mutation → verschachtelt: rowNew + rowSave + lnkPostalAddresses + lnkContactPeople → siehe references/adressen-verwaltung.md
"Welche Vorgangsarten gibt es?"querytblTransactionTypesrowsRead → siehe references/parametertabellen.md
"Welche Zahlungsbedingungen sind konfiguriert?"querytblPaymentTermsrowsRead → siehe references/parametertabellen.md
"Zeig mir die Steuerschluessel"querytblValueAddedTaxTypesrowsRead → siehe references/parametertabellen.md
"Welche Einheiten/Waehrungen gibt es?"querytblUnitsOfMeasure/tblCurrencies → siehe references/parametertabellen.md

Schnellreferenz

Wichtigste Tabellen

GraphQL-TabelleBeschreibungSchreibbar
tblAddressesAdressenja
tblPostalAddressesAnschriftenja
tblContactPeopleAnsprechpartnerja
tblContactsKontakteja
tblProductsArtikelja
tblProductGroupsWarengruppenja
tblSuppliersArtikel-Lieferantenja
tblTransactionsVorgaengeja
tblTransactionItemsVorgangspositionenja (verschachtelt in tblTransactions)
tblTransactionsArchiveArchiv Vorgaengeja
tblProjectsProjekteja
tblDocumentsDokumenteja
tblCalendarKalenderja
tblWarehousesLagerja
tblClientMandant (Einzeldatensatz)ja
tblOpenItemsOffene Postennein
tblInventoryLagerbestaendenein
tblUsersBenutzernein
Parametertabellen
tblTransactionTypesVorgangsarten (Wandlungskette, Buchungsparameter)nein
tblPostingParametersBuchungsparameternein
tblUnitsOfMeasureMengeneinheiten (UN/ECE-Codes fuer E-Rechnung)nein
tblValueAddedTaxTypesSteuerschluessel mit Saetzen und DATEV-Zuordnungnein
tblPaymentTermsZahlungsbedingungennein
tblCurrenciesFremdwaehrungen mit Wechselkursennein
tblShippingTypesVersandartennein
tblAddressStatusesAdressstatus mit Nummernkreisennein
tblSalutationsAnredennein
tblDunningLevelsMahnstufennein
tblPaymentMethodsZahlungsartennein
tblProjectTypesProjektartennein
tblProjectStatesProjektstatus (Workflow)nein
tblDocumentTypesDokumentenarten (DMS)nein
tblDocumentStatesDokumentenstatus (Workflow)nein
tblCommunicationTypesKommunikationsartennein

Haeufigste Patterns (Copy-Paste-fertig)

# LESEN - Einzelner Datensatz
query { tblProducts { rowRead(kf1ArtNr: { string: "LUTSCHER" }) { fldArtNr fldSuchBeg fldBez1(as: DISPLAY_TEXT) } } }

# LESEN - Liste mit Filter
query { tblProducts { rowsRead(slowFilter: { gt: [{field: fldVk0_Preis}, {value: 0}] }) { fldArtNr fldVk0_Preis(as: TEXT) } } }

# LESEN - Paginiert
query ($after: String) { tblProducts { conRead(first: 10, after: $after) { edges { node { fldArtNr } cursor } pageInfo { hasNextPage endCursor } } } }

# SCHREIBEN - Neuer Datensatz
mutation { tblProducts { rowNew { fldArtNr(set: { string: "NEU-001" }) fldBez1(set: { text: "Bezeichnung" } as: DISPLAY_TEXT) } } }

# SCHREIBEN - Datensatz aendern
mutation { tblProducts { rowModify(kf1ArtNr: { string: "LUTSCHER" }) { fldBez1(set: { text: "Neuer Name" } as: DISPLAY_TEXT) } } }

# SCHREIBEN - Vorgang anlegen + buchen
mutation @acquireLocks(forWriting: [tblTransactions, tblTransactionItems], forReading: [tblAddresses, tblProducts]) {
  tblTransactions {
    rowNew(assignAddress: { kf1AdrNr: { text: "10000" } }) {
      fldArt(set: { text: "Rechnung I" })
      rowSaveAndModify {
        fldBelegNr
        tblTransactionItems {
          rowNew(assignProduct: { kf1ArtNr: { text: "LUTSCHER" } }) { fldMge(set: { float: 10 }) }
        }
        rowSave { fnPost { fldBelegNr } }   # rowSave VOR fnPost!
      }
    }
  }
}

1. Grundstruktur

Tabellenzugriff

Alle Operationen beginnen mit tbl... auf oberster Ebene:

query {
  tblProducts { ... }       # Artikel
  tblAddresses { ... }      # Adressen
  tblTransactions { ... }   # Vorgaenge
}

Leseoperationen

FeldBeschreibungRueckgabe
rowRead(...)Einzelnen Datensatz lesenRow oder null
rowsRead(...)Liste lesen[Row]
conRead(...)Paginierte Liste (Relay Connection)Connection

Einzeldatensatz-Tabellen

tblClient braucht keine Suchparameter:

query { tblClient { rowRead { fldMandNr fldMandTyp } } }

2. Datensatzauswahl

exactMatch - Exakte Suche

rowRead(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } })

Verschachtelte Schluesselfelder

rowRead(exactMatch: {
  byArtNrLagNr: {
    kf1ArtNr: { string: "LUTSCHER"
      kf2LagNr: { string: "HAUPT" }
    }
  }
})

Verkuerzte Schreibweisen

# Statt:
rowRead(exactMatch: { byNr: { kf1ArtNr: { string: "LUTSCHER" } } })
# Kurz (impliziert exactMatch + byNr):
rowRead(kf1ArtNr: { string: "LUTSCHER" })

# Bereich:
rowsRead(kf1ArtNr: { from: { string: "0" }, to: { string: "12000" } })

Weitere Sucharten

# Naechster Treffer
rowRead(nearestMatch: { byNr: { kf1ArtNr: { string: "PROD-000" } } })

# Aktueller Benutzer
tblUsers { rowRead(using: current) { fldAnmNa } }

# Bereich mit exklusiver Obergrenze
rowsRead(allBetween: { byNr: { kf1ArtNr: { from: { string: "A" }, to: { string: "Z" }, toExclusive: true } } })

3. Datenfelder und Ausgabeformate

Praefix-System

PraefixBedeutungBeispiel
tbl...TabelletblProducts
fld...DatenfeldfldArtNr, fldPreis
kf...Schluesselfeldkf1ArtNr, kf2LagNr
by...SortierfolgebyNr, bySuchBeg
row...Verweis auf verknuepften DatensatzrowWgrNr
lnk...Verlinkung zu DatentabellelnkInventory
aco...BetragsgruppeacoEPr, acoGPreis
img...BildfeldimgBild
fn...FunktionfnPost, fnIsCustomer
_...System-Feld_if, _string

as-Parameter - Ausgabeformat

fldPreis                    # Standardformat
fldPreis(as: TEXT)          # "99,95 EUR" (lokalisiert)
fldPreis(as: FLOAT)         # 99.95
fldPreis(as: STRING)        # Roher String
fldPreis(as: INT)           # Ganzzahl
fldPreis(as: DISPLAY_TEXT)  # Anzeigeformat (auch fuer RTF-Felder → Klartext)
fldGueltigAb(as: TEXT)      # "24.12.2023" (lokalisiert)
fldGspKz(as: BOOLEAN)       # true/false

Gemeinsame Felder (in fast allen Tabellen)

# Identifikation
fldID                       # Automatische ID
fldModifyLSN / fldInsertLSN # Versionierung (BigInt)

# Audit-Trail
fldErstDat / fldErstBzr     # Erstellungsdatum / -benutzer
fldAendDat / fldAendBzr     # Aenderungsdatum / -benutzer

# Sperren
fldGspKz                    # Gesperrt-Kennzeichen (Boolean)
fldGspDat / fldGspInfo      # Sperrdatum / -information
fldGspGrp                   # Gesperrtgruppe

# Freitext
fldInfo                     # Informationsfeld
fldMemo                     # Memo-Feld
fldQuickInfo                # QuickInfo

4. Verknuepfungen

Verweise (row...) - Einzelner verknuepfter Datensatz

tblProducts {
  rowsRead {
    fldArtNr
    fldWgrNr                  # Fremdschluessel-Wert
    rowWgrNr { fldBez }       # Verknuepfte Warengruppe
    rowEinh { fldKuBez }      # Verknuepfte Einheit
  }
}

Verlinkungen (lnk...) - Liste verknuepfter Datensaetze

WICHTIG: by.../using... gehoeren auf rowsRead INNERHALB des Links, NICHT auf lnk...:

# RICHTIG:
lnkInventory {
  rowsRead(byArtNrLagNrArtBDat: { usingArtNr: {} }) { fldLagNr fldMge }
}

# FALSCH:
lnkInventory(byArtNrLagNrArtBDat: { usingArtNr: {} }) {
  rowsRead { ... }
}

Verschachtelte Tabellen (tbl... innerhalb Row)

tblClient {
  rowRead {
    fldMandNr
    tblBnkVb { rowsRead { fldNr fldIBAN } }
  }
}

5. Filter

VERBINDLICHER WORKFLOW: keyFilter-First

BEVOR du einen Filter schreibst, fuehre IMMER diese Schritte aus:

  1. Sortierfolgen ermitteln: Pruefe per Introspection, welche by...-Sortierfolgen die Tabelle hat:

    { __type(name: "{SingularName}SlowAnyFields") { enumValues { name } } }
    

    WICHTIG: Der Enum-Name verwendet den Singular-Namen der Entitaet, NICHT den Tabellennamen! Beispiele: ProductSlowAnyFields, TransactionSlowAnyFields, AddressSlowAnyFields. Sonderfaelle: CalendarEntrySlowAnyFields, AccountSlowAnyFields, BillOfMaterialsEntrySlowAnyFields, InventoryEntrySlowAnyFields. Gleiches Muster gilt fuer {SingularName}FastAnyFields. Die by...-Parameter findest du auf den rowsRead/conRead-Argumenten. Alternativ: Probiere rowsRead(allBetween: { by und schau welche Vorschlaege die API akzeptiert.

  2. keyFilter pruefen: Gibt es eine by...-Sortierfolge, die das gewuenschte Filterfeld als kf...-Schluessel enthaelt? → keyFilter verwenden!

    # Beispiel: Laender ohne Staatsangehoerigkeitsnummer
    # byStaatsNr existiert mit kf1StaatsNr → keyFilter nutzen!
    rowsRead(allBetween: {
      byStaatsNr: { keyFilter: { isNull: { field: kf1StaatsNr } } }
    })
    
  3. Fallback: Nur wenn KEIN passender by...-Index existiert:

    • fastFilter fuer einfache Vergleiche auf fld...-Felder
    • slowFilter NUR fuer Arithmetik, in-Operator, fn...-Funktionen

NIEMALS direkt slowFilter verwenden, ohne vorher keyFilter geprueft zu haben!

Drei Filtertypen

FilterPerformanceBesonderheiten
keyFilterAm schnellstenFilter auf kf...-Felder innerhalb by... - IMMER bevorzugen!
fastFilterSchnell (DB-seitig)Nur einfache Vergleiche auf fld...
slowFilterLangsam (App-seitig)Arithmetik, in-Operator, fn... - nur als letzter Ausweg

Vergleichsoperatoren

{ eq: [{field: fldStatus}, {value: 1}] }       # gleich
{ ne: [{field: fldStatus}, {value: 0}] }       # ungleich
{ gt: [{field: fldMge}, {value: 0}] }          # groesser
{ lt: [{field: fldMge}, {value: 100}] }        # kleiner
{ ge: [{field: fldMge}, {value: 1}] }          # groesser-gleich
{ le: [{field: fldMge}, {value: 99}] }         # kleiner-gleich
{ isNull: { field: fldAuftrNr } }              # NULL-Pruefung
{ isNotNull: { field: fldAuftrNr } }

Boolesche Kombinationen

{ and: [Ausdruck1, Ausdruck2] }
{ or:  [Ausdruck1, Ausdruck2] }
{ not: Ausdruck }

WICHTIG: @oneOf-Regel

Pro Filterobjekt nur EIN Operator:

# FALSCH:
fastFilter: { eq: [...], gt: [...] }

# RICHTIG:
fastFilter: { and: [ { eq: [...] }, { gt: [...] } ] }

IN-Operator (nur slowFilter)

{ in: { left: { field: fldLagBestArt }, list: [1, 7] } }

Alle Werte muessen denselben Typ haben (@sametype-Regel).

keyFilter - Performantester Filter

Filtert auf kf...-Felder INNERHALB des by...-Parameters. Schneller als fast/slowFilter.

# Alle Artikel ab Nummer "10"
rowsRead(
  allBetween: {
    byNr: {
      keyFilter: { ge: [{ field: kf1ArtNr }, { value: "10" }] }
      kf1ArtNr: { from: { string: "0" }, to: { string: "ZZZZZ" } }
    }
  }
)

# Mehrere Werte per OR
keyFilter: {
  or: [
    { eq: [{ field: kf1AdrNr }, { value: "10000" }] }
    { eq: [{ field: kf1AdrNr }, { value: "70000" }] }
  ]
}

Prioritaet: keyFilter > fastFilter > slowFilter (immer schnellsten moegl. Typ waehlen).

Detaillierte keyFilter-Patterns siehe references/erweiterte-query-patterns.md.

Funktionen in Filtern (nur slowFilter)

{ fnPos: [{ value: "Suchtext" }, { field: fldSuchBeg }] }   # Textsuche (>0 = gefunden, NUR String-Felder!)
{ fnGetAktDate: [] }                                      # Aktuelles Datum
{ fnIncDate: [{ fnGetAktDate: [] }, { value: 0 }, { value: -12 }] }  # Datum +/- Monate

Arithmetik in slowFilter

# Brutto > 100 pruefen
slowFilter: { gt: [{ mul: [{ field: fldVk0_Preis }, { value: 1.19 }] }, { value: 100 }] }

Operatoren: add, sub, mul, div, mod, neg. Details in references/erweiterte-query-patterns.md.


6. Direktiven

@store - Wert in Variable speichern

Variable muss in der Operations-Signatur deklariert werden:

query ($storedValue: Any = null) {
  tblProducts {
    rowsRead {
      fldSuchBeg @store(in: $storedValue)
      echo: _any(value: $storedValue)
    }
  }
}

@store funktioniert ueber Verschachtelungsebenen hinweg (aeussere Tabelle → verschachtelte tbl/lnk).

@onNull - Null-Behandlung

fldSuchBeg @onNull(returnValue: "Nicht gefunden")     # Alternativwert
fldPreis @onNull(returnValue: skip)                    # Feld ueberspringen
fldArtNr @onNull(errorMessage: "ArtNr fehlt")          # Fehler ausloesen

@acquireLocks - Praventive Sperren (nur mutation)

mutation @acquireLocks(
  forWriting: [tblTransactions, tblTransactionItems],
  forReading: [tblAddresses, tblProducts]
) { ... }

Verhindert Deadlocks bei Multi-Tabellen-Mutationen.

@skip / @include

fldPreis @include(if: $includePrice)

7. System-Felder

# Wertausgabe
_any(value: $variable)
_string(expr: { add: [{field: fldArtNr}, {value: " - "}, {field: fldSuchBeg}] })
_boolean(expr: { gt: [{field: fldLagMge}, {value: 0}] })
_localDate(expr: { fnGetAktDate: [] })

# Bedingte Ausfuehrung
details: _if(expr: { gt: [{field: fldLagMge}, {value: 0}] }) {
  fldStdPreis
  fldLagMge
}

# Bedingter Skalarwert
status: _ifThenElse(
  expr: { gt: [{field: fldLagMge}, {value: 0}] },
  then: "Verfuegbar",
  else: "Nicht auf Lager"
)

# Fehler ausloesen
_raise(message: "Negative Lagermenge!")

8. Mutationen

query vs. mutation

  • query: Snapshot-Transaktion, keine Sperren, null bei Fehler
  • mutation: Normale Transaktion, Alles-oder-Nichts (Fehler = Rollback)

Schreiboperationen

OperationZweck
rowNewNeuen Datensatz erstellen
rowNew(ifNotExists: ...)Nur erstellen wenn nicht vorhanden (gibt null statt Fehler)
rowCopy(exactMatch: ...)Datensatz kopieren (nicht-gesetzte Felder vom Original)
rowModify(exactMatch: ...)Datensatz aendern
rowDelete(exactMatch: ...)Datensatz loeschen
rowsModify/rowsCopy/rowsDeleteMassenoperationen (Fehler bei einem → Rollback aller)

Row-Kontexte

KontextSchreibenlnk...tbl... schreibbar
RowMutationNew/Copy/Modifyjaneinja
RowMutationRead (nach rowSave)neinja (mit Schreiben)nein
RowMutationDeleteneinneinnein

Datensatz erstellen

mutation {
  tblProducts {
    rowNew {
      fldArtNr(set: { string: "PROD-001" })
      fldBez1(set: { text: "Neuer Artikel" } as: DISPLAY_TEXT)
      fldVk0_Preis(set: { float: 99.95 })
    }   # Automatisch gespeichert am Block-Ende
  }
}

Bedingt (kein Fehler wenn vorhanden, gibt null zurueck):

rowNew(ifNotExists: { exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } } })

Datensatz kopieren

mutation {
  tblProducts {
    rowCopy(kf1ArtNr: { string: "LUTSCHER" }) {
      fldArtNr(set: { string: "LUTSCHER-V2" })
      # Alle anderen Felder (Preis, Bez., etc.) vom Original uebernommen
      fldBez1(as: DISPLAY_TEXT)
      fldVk0_Preis(as: FLOAT)
    }
  }
}

Details zu rowCopy, ifNotExists und Optimistic Locking in references/mutation-patterns.md.

Datensatz aendern

mutation {
  tblProducts {
    rowModify(kf1ArtNr: { string: "PROD-001" }) {
      fldVk0_Preis(set: { float: 129.95 })
      fldBez1(as: DISPLAY_TEXT)       # Nur lesen
    }
  }
}

Datensatz loeschen

mutation {
  tblProducts {
    rowDelete(kf1ArtNr: { string: "PROD-001" }, ignoreWarnings: true) {
      fldArtNr                         # Noch lesbar vor Loeschung
    }
  }
}

Speicheroperationen

# rowSave - Speichern, dann Read-Kontext (lnk... verfuegbar)
rowNew {
  fldArtNr(set: { string: "X" })
  rowSave { fldID fldModifyLSN }     # Ab hier nur lesen + lnk...
}

# rowSaveAndModify - Speichern, weiter schreiben
rowNew {
  fldArtNr(set: { string: "X" })
  rowSaveAndModify {
    fldBez1(set: { text: "Name" } as: DISPLAY_TEXT)
    tblTransactionItems { ... }       # Verschachtelte Tabelle schreibbar
  }
}

WICHTIG: Nach rowSave/rowSaveAndModify NICHT mehr im aeusseren Block schreiben!

Feldmanipulation (set-Parameter)

fldArtNr(set: { string: "PROD-005" })           # String
fldVk0_Preis(set: { float: 99.95 })             # Float
fldVk0_Preis(set: { text: "99,95" })            # Lokalisierter Text
fldGspAbDat(set: { text: "2023-12-24" })          # Datum (ISO-Format YYYY-MM-DD, auch localdate moeglich)
fldGspKz(set: { boolean: true })                 # Boolean
fldArt(set: { text: "Rechnung I" })              # Vorgangsart (immer Text, Nummern koennen abweichen!)
fldMge(set: { int: 5 })                         # Integer
fldGspAbDat(set: {})                             # NULL setzen

# Auto-Nummerierung (naechste freie Nr wenn vergeben)
fldAdrNr(set: { text: "10000", allowAutoNrOnSave: true })

# Kaskadierende Updates unterdruecken
fldZahlBed(set: { text: "30 Tage netto", suppressRelatedUpdates: true })

Assign-Parameter

# Row-Assign: Felder aus anderem Datensatz befuellen
rowNew(assignAddress: { kf1AdrNr: { text: "10000" } }) { ... }
rowNew(assignUser: { using: current }) { ... }

# Feld-Assign: Einzelnes Feld per Suche zuweisen
fldWgrNr(assignProductGroup: { exactMatch: { byBez: { kf1Bez: { text: "Elektronik" } } } })
fldSBzrNr(assignUser: { using: current })

# using: context in Verlinkungen (nimmt aeusseren Datensatz)
lnkProjects { rowNew(assignAddress: { using: context }) { ... } }

Verlinkungen in Mutationen (nur in RowMutationRead)

mutation {
  tblAddresses {
    rowRead(kf1AdrNr: { string: "10000" }) {
      lnkPostalAddresses {
        rowNew(setAdrNrAnsNr: usingAdrNr) {
          fldNa2(set: { text: "Lieferanschrift" })
          rowSave { fldAnsNr }
        }
      }
    }
  }
}

set...-Parameter bei rowNew in Verlinkungen: befuellt Schluesselfelder automatisch aus Kontext.


9. Vorgaenge - Kern-Workflow

Vorgangsarten

WICHTIG: Immer text statt int verwenden - Nummern koennen pro Installation abweichen!

fldArt(set: { text: "Rechnung I" })     # RICHTIG - Bezeichnung
fldArt(set: { int: 70 })                # VERMEIDEN - Nummer kann abweichen

Hinweis: fnConvert akzeptiert den Parameter transactionTypeNo sowohl als Integer als auch als Text:

fnConvert(transactionTypeNo: 50, ...)              # Integer - Standard-Nummer (50 = Lieferschein)
fnConvert(transactionTypeNo: "Lieferschein", ...)  # Text - Bezeichnung

Standard-Nummern und Bezeichnungen finden sich in references/vorgangsarten.md.

Vollstaendige Liste aller Vorgangsarten mit Standard-Nummern und Buchungsparametern: references/vorgangsarten.md

Funktionsuebersicht

FunktionBeschreibungKontextRueckgabe
fnPostBuchenrowReadEinzelwert
fnReverseStornierenrowReadEinzelwert
fnConvertWandelnrowReadArray
fnMoveToArchiveArchivierenrowReadEinzelwert

Alle erfordern: mutation + rowRead-Kontext. Leere Belegnummer = fehlgeschlagen.

Vorgang anlegen mit Positionen

mutation @acquireLocks(
  forWriting: [tblTransactions, tblTransactionItems]
  forReading: [tblAddresses, tblProducts]
) {
  tblTransactions {
    rowNew(assignAddress: { kf1AdrNr: { text: "10000" } }) {
      fldArt(set: { text: "Rechnung I" })

      rowSaveAndModify {
        fldBelegNr

        tblTransactionItems {
          pos1: rowNew(assignProduct: { kf1ArtNr: { text: "LUTSCHER" } }) {
            fldMge(set: { float: 10 })
          }
          pos2: rowNew(assignProduct: { kf1ArtNr: { text: "MYSTERYBOX" } }) {
            fldMge(set: { float: 2 })
          }
        }
      }
    }
  }
}

Hinweise:

  • fldArtNr akzeptiert Artikelnummern UND Barcodes (Auto-Resolve)
  • fldArt kann als Nummer (int: 50) oder Text (text: "Rechnung I") gesetzt werden
  • fldZeilenNr(set: { int: 0 }) fuegt Position an den Anfang

Vorgang buchen (fnPost)

# Bestehenden Vorgang buchen
mutation {
  tblTransactions {
    rowRead(exactMatch: { byBelegNr: { kf1BelegNr: { string: "RE12500005" } } }) {
      fnPost { fldBelegNr }     # Leer = nicht gebucht!
    }
  }
}

Anlegen + Buchen in einer Mutation

mutation @acquireLocks(
  forWriting: [tblTransactions, tblTransactionItems]
  forReading: [tblAddresses, tblProducts]
) {
  tblTransactions {
    rowNew(assignAddress: { kf1AdrNr: { text: "10000" } }) {
      fldArt(set: { text: "Rechnung I" })
      rowSaveAndModify {
        fldBelegNr
        tblTransactionItems {
          pos1: rowNew(assignProduct: { kf1ArtNr: { text: "LUTSCHER" } }) {
            fldMge(set: { float: 10 })
          }
        }
        rowSave {                    # WICHTIG: Erst speichern!
          fnPost { fldBelegNr }      # Dann buchen
        }
      }
    }
  }
}

Vorgang stornieren (fnReverse)

mutation {
  tblTransactions {
    rowRead(exactMatch: { byBelegNr: { kf1BelegNr: { string: "RE12500002" } } }) {
      fnReverse { fldBelegNr }
    }
  }
}

Vorgang wandeln (fnConvert)

Gibt ein Array zurueck (kann mehrere Vorgaenge erzeugen):

mutation {
  tblTransactions {
    rowRead(exactMatch: { byBelegNr: { kf1BelegNr: { string: "AN2500001" } } }) {
      fnConvert(transactionTypeNo: 50, deliveryQuantityHandling: IGNORE) {
        fldBelegNr
      }
    }
  }
}

deliveryQuantityHandling: IGNORE | KEEP_UNCHANGED | RECALCULATE | RECALCULATE_IF_ZERO

Preisberechnung nach Speichern

rowSave {
  acoGPreis {
    totalGrossAmount          # Brutto
    totalNetAmount            # Netto
    totalTaxAmount            # Steuer
  }
}

10. Betragsgruppen (aco...)

Kurzbeispiel Lesen: acoEPr { totalGrossAmount totalNetAmount totalTaxAmount }

Details (Lesen, Schreiben, Preisberechnung nach Speichern): references/betragsgruppen.md


11. Paginierung (Relay Connection)

query ($pageSize: Int = 10, $after: String) {
  tblProducts {
    conRead(first: $pageSize, after: $after) {
      edges {
        cursor
        node { fldArtNr fldSuchBeg }
      }
      pageInfo { hasNextPage endCursor startCursor edgeCount }
    }
  }
}

WICHTIG: edgeCount gibt nur die Anzahl der zurueckgegebenen Edges zurueck, NICHT die Gesamtanzahl der Datensaetze. Es gibt kein totalCount-Feld. Zum Zaehlen: first mit ausreichend grossem Wert setzen und hasNextPage pruefen.


12. Haeufige Fehler vermeiden

FehlerLoesung
fnPost in rowModify-KontextNur in rowRead-Kontext
fnPost ohne rowSaveImmer rowSave vor fnPost
Schreiben nach rowSave im selben BlockNach rowSave nur lesen, oder rowSaveAndModify
lnk... in schreibendem KontextErst rowSave, dann lnk... im Read-Kontext
Mehrere Operatoren in einem Filterobjektand/or verwenden (@oneOf-Regel)
fnMoveToArchive nach fnPost in einer MutationGetrennte Mutationen (Auto-Archivierung!)
Leere Belegnummer nicht geprueftImmer fldBelegNr in fn...-Rueckgabe pruefen
by.../using... auf lnk... statt auf rowsReadGehoeren auf rowsRead/rowsModify innerhalb des Links
@store(as: "name")Korrekt: @store(in: $variable) mit Deklaration in Signatur
"Argument \"set\" is not defined"Fehlende Berechtigung - set wird ohne Recht nicht angeboten
Feldreihenfolge nicht beachtetFelder werden sequentiell ausgefuehrt - Menge VOR Preis setzen
rowCopy ohne neuen SchluesselKopie braucht eigenen Primaerschluessel
rowsDelete bricht abEin Fehler bei einem Datensatz → Rollback ALLER Loeschungen
fnConvert schlaegt fehl → rowNew als WorkaroundVERBOTEN! Bricht die Prozesskette. Fehlerursache klaeren und Nutzer informieren
fnReverse schlaegt fehl → rowDelete als WorkaroundVERBOTEN! Umgeht Storno-Logik (keine Gegenbuchungen, kein Audit-Trail). Nutzer informieren
fnPost schlaegt fehl → naechsten Schritt trotzdem ausfuehrenVERBOTEN! Folgeschritte (z.B. Wandlung) setzen erfolgreiche Buchung voraus. Nutzer informieren
fnMoveToArchive schlaegt fehl → manuell Daten kopieren/loeschenVERBOTEN! Archivierung hat eigene Logik. Nutzer informieren
Prozessschritte ueberspringen (z.B. direkt RE statt AB→LI→RE)Nur wenn Nutzer dies explizit bestaetigt. Auf moeglichen Kettenverlust hinweisen
edgeCount als Gesamtanzahl interpretiertedgeCount zaehlt nur zurueckgegebene Edges, NICHT alle Datensaetze. first: 0 ergibt immer edgeCount: 0. Zum Zaehlen: first: 100+ und hasNextPage pruefen
fldNa2/fldOrt/fldPLZ/fldStr auf tblAddressesDiese Felder leben in tblPostalAddresses, Zugriff ueber lnkPostalAddresses

13. Troubleshooting

Fehlermeldung: "Argument \"set\" is not defined"

Ursache: Fehlende Berechtigung. Der API-Nutzer hat kein Recht, dieses Feld zu setzen. Loesung: Berechtigung im ERP pruefen. Berechtigungs-pflichtige Felder: fldDat, fldVerk, fldKredLimit, fldInExtBeaKz, fldGspKz, fldEPreis.

Fehlermeldung: "Address \"10002\" is blocked."

Ursache: Adresse hat fldGspKz = true (Gesperrt-Kennzeichen). Loesung: Andere Adresse verwenden oder Sperrung im ERP aufheben.

Leere Belegnummer nach fnPost/fnReverse/fnConvert

Ursache: Funktion konnte nicht ausgefuehrt werden (Voraussetzungen nicht erfuellt). Loesung: Immer Belegnummer pruefen: fnPost { fldBelegNr }. Leer = fehlgeschlagen.

Rollback nach fnPost trotz erfolgreicher Buchung

Ursache: Eine NACHFOLGENDE Operation in derselben Mutation ist fehlgeschlagen. Alles-oder-Nichts. Loesung: fnPost und Folgeoperationen in getrennte Mutationen aufteilen, oder Fehlerquelle beheben.

fnPost hat keine Wirkung (Positionen fehlen)

Ursache: rowSave vor fnPost vergessen. fnPost arbeitet nur auf gespeicherten Daten. Loesung: Immer rowSave { fnPost { fldBelegNr } } - rowSave VOR fnPost.

lnk...-Felder nicht verfuegbar

Ursache: Datensatz ist noch in schreibendem Kontext (RowMutationNew/Copy/Modify). Loesung: Erst rowSave aufrufen → wechselt zu RowMutationReadlnk... wird verfuegbar.

Laufzeitfehler nach rowSave

Ursache: Versuch im selben Block nach rowSave noch Felder zu schreiben. Loesung: rowSaveAndModify verwenden wenn danach noch geschrieben werden soll, oder alle Schreiboperationen VOR rowSave platzieren.

Deadlock bei parallelen Mutationen

Ursache: @acquireLocks fehlt oder unvollstaendig. Loesung: @acquireLocks(forWriting: [...], forReading: [...]) mit ALLEN beteiligten Tabellen.

fn-Funktion schlaegt fehl (fnConvert/fnPost/fnReverse/fnMoveToArchive)

Ursache: Vielfaeltig - fehlende Konfiguration (Wandlungspfad), Beleg bereits archiviert, fehlende Berechtigung, Geschaeftsregel verletzt, Beleg gesperrt. Loesung: STOPP - KEINEN Workaround versuchen! Insbesondere:

  • NICHT per rowNew einen Ersatzbeleg anlegen (bricht Prozesskette)
  • NICHT per rowDelete einen Beleg loeschen statt zu stornieren (umgeht Audit-Trail)
  • NICHT den naechsten Prozessschritt ausfuehren wenn der aktuelle fehlschlug Stattdessen: Fehlermeldung dem Nutzer zeigen und gemeinsam klaeren. Moegliche Ursachen:
  • Wandlungspfad im ERP nicht konfiguriert → ERP-Administrator muss Pfad freischalten
  • Beleg bereits im Archiv → fnConvert nur auf aktive Vorgaenge moeglich
  • Fehlende Berechtigung → API-Nutzer-Rechte pruefen
  • Geschaeftsregel verletzt → Beleg-Daten pruefen (z.B. fehlende Pflichtfelder)

Query gibt null zurueck obwohl Datensatz existiert

Ursache (in mutation): modifyLSN stimmt nicht ueberein (Optimistic Locking Konflikt). Ursache (in query): Schluessel falsch oder Sortierfolge stimmt nicht. Loesung: @onNull-Pattern verwenden um Konflikte abzufangen. Siehe references/mutation-patterns.md.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.