microtech-graphql — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited microtech-graphql (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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. Siehereferences/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!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.and/or kombinieren.rowModify!), immer in mutation.rowSave sind Positionen nicht gespeichert und werden nicht gebucht.fldBelegNr in fn...-Rueckgaben pruefen.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.
rowDelete umgeht die Storno-Logik: keine Storno-Belegnummer, keine Gegenbuchungen, keine Lagerkorrektur, kein Audit-Trail. Stattdessen: fnReverse verwenden, bei Fehler Nutzer informieren.query (Daten anzeigen, suchen, auflisten, zaehlen)mutation (anlegen, aendern, loeschen, buchen, wandeln)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.
BEVOR du eine Query oder Mutation baust: Pruefe ob ein passendes Beispiel oder Detail-Wissen existiert.
references/workflow-beispiele.md durchsuchen (17 Szenarien)references/feldkatalog-*.md lesenreferences/erweiterte-query-patterns.md (keyFilter, Arithmetik, Datums-Filter)references/adressen-verwaltung.md (verschachtelte Links, rowReAnsNr, assign-Parameter, Massenoperationen)references/mutation-patterns.md (Optimistic Locking, rowCopy, Deep Nesting)references/vorgaenge-erweitert.md (Archiv, externe Bearbeitung)references/parametertabellen.md (Vorgangsarten, Steuerschluessel, Zahlungsbedingungen, Einheiten, Versandarten, Waehrungen, Mahnstufen, Projektstatus, Dokumentenarten u.v.m.)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.
by...-Index fuer das Suchfeld? → keyFilter verwendenfastFilter (einfache Gleichheit auf fld...)slowFilter (Arithmetik, in, fn...)graphql_query-Tool ausfuehrenrowNew (ggf. mit assignAddress/assignProduct)rowModifyrowRead + fnPost/fnReverse/fnConvertrowDelete (aber NICHT als Ersatz fuer fnReverse!)rowSave vs. rowSaveAndModify - bei Vorgaengen mit Positionen immer rowSaveAndModify + inneres rowSave vor fnPosthasNextPage)?fldBelegNr pruefen - leer bedeutet die Operation ist fehlgeschlagen| Nutzer sagt... | Du machst... |
|---|---|
| "Zeig mir Artikel X" | query → tblProducts → rowRead mit keyFilter |
| "Alle Kunden aus PLZ 5xxxx" | query → tblAddresses → lnkPostalAddresses → slowFilter auf fldPLZ |
| "Erstell eine Rechnung fuer Kunde 10000" | mutation mit @acquireLocks → tblTransactions → rowNew + rowSave + fnPost |
| "Wandle Angebot 12345 in AB" | mutation → tblTransactions → rowRead + fnConvert |
| "Wieviele Artikel haben wir?" | query → tblProducts → conRead(first: 100) → paginieren + hasNextPage |
| "Storniere Rechnung 67890" | mutation → tblTransactions → rowRead + fnReverse |
| "Zeig mir Adresse 10000 mit Anschriften und Ansprechpartnern" | query → tblAddresses → lnkPostalAddresses → lnkContactPeople → 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?" | query → tblTransactionTypes → rowsRead → siehe references/parametertabellen.md |
| "Welche Zahlungsbedingungen sind konfiguriert?" | query → tblPaymentTerms → rowsRead → siehe references/parametertabellen.md |
| "Zeig mir die Steuerschluessel" | query → tblValueAddedTaxTypes → rowsRead → siehe references/parametertabellen.md |
| "Welche Einheiten/Waehrungen gibt es?" | query → tblUnitsOfMeasure/tblCurrencies → siehe references/parametertabellen.md |
| GraphQL-Tabelle | Beschreibung | Schreibbar |
|---|---|---|
tblAddresses | Adressen | ja |
tblPostalAddresses | Anschriften | ja |
tblContactPeople | Ansprechpartner | ja |
tblContacts | Kontakte | ja |
tblProducts | Artikel | ja |
tblProductGroups | Warengruppen | ja |
tblSuppliers | Artikel-Lieferanten | ja |
tblTransactions | Vorgaenge | ja |
tblTransactionItems | Vorgangspositionen | ja (verschachtelt in tblTransactions) |
tblTransactionsArchive | Archiv Vorgaenge | ja |
tblProjects | Projekte | ja |
tblDocuments | Dokumente | ja |
tblCalendar | Kalender | ja |
tblWarehouses | Lager | ja |
tblClient | Mandant (Einzeldatensatz) | ja |
tblOpenItems | Offene Posten | nein |
tblInventory | Lagerbestaende | nein |
tblUsers | Benutzer | nein |
| Parametertabellen | ||
tblTransactionTypes | Vorgangsarten (Wandlungskette, Buchungsparameter) | nein |
tblPostingParameters | Buchungsparameter | nein |
tblUnitsOfMeasure | Mengeneinheiten (UN/ECE-Codes fuer E-Rechnung) | nein |
tblValueAddedTaxTypes | Steuerschluessel mit Saetzen und DATEV-Zuordnung | nein |
tblPaymentTerms | Zahlungsbedingungen | nein |
tblCurrencies | Fremdwaehrungen mit Wechselkursen | nein |
tblShippingTypes | Versandarten | nein |
tblAddressStatuses | Adressstatus mit Nummernkreisen | nein |
tblSalutations | Anreden | nein |
tblDunningLevels | Mahnstufen | nein |
tblPaymentMethods | Zahlungsarten | nein |
tblProjectTypes | Projektarten | nein |
tblProjectStates | Projektstatus (Workflow) | nein |
tblDocumentTypes | Dokumentenarten (DMS) | nein |
tblDocumentStates | Dokumentenstatus (Workflow) | nein |
tblCommunicationTypes | Kommunikationsarten | nein |
# 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!
}
}
}
}Alle Operationen beginnen mit tbl... auf oberster Ebene:
query {
tblProducts { ... } # Artikel
tblAddresses { ... } # Adressen
tblTransactions { ... } # Vorgaenge
}| Feld | Beschreibung | Rueckgabe |
|---|---|---|
rowRead(...) | Einzelnen Datensatz lesen | Row oder null |
rowsRead(...) | Liste lesen | [Row] |
conRead(...) | Paginierte Liste (Relay Connection) | Connection |
tblClient braucht keine Suchparameter:
query { tblClient { rowRead { fldMandNr fldMandTyp } } }exactMatch - Exakte SucherowRead(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } })rowRead(exactMatch: {
byArtNrLagNr: {
kf1ArtNr: { string: "LUTSCHER"
kf2LagNr: { string: "HAUPT" }
}
}
})# Statt:
rowRead(exactMatch: { byNr: { kf1ArtNr: { string: "LUTSCHER" } } })
# Kurz (impliziert exactMatch + byNr):
rowRead(kf1ArtNr: { string: "LUTSCHER" })
# Bereich:
rowsRead(kf1ArtNr: { from: { string: "0" }, to: { string: "12000" } })# 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 } } })| Praefix | Bedeutung | Beispiel |
|---|---|---|
tbl... | Tabelle | tblProducts |
fld... | Datenfeld | fldArtNr, fldPreis |
kf... | Schluesselfeld | kf1ArtNr, kf2LagNr |
by... | Sortierfolge | byNr, bySuchBeg |
row... | Verweis auf verknuepften Datensatz | rowWgrNr |
lnk... | Verlinkung zu Datentabelle | lnkInventory |
aco... | Betragsgruppe | acoEPr, acoGPreis |
img... | Bildfeld | imgBild |
fn... | Funktion | fnPost, fnIsCustomer |
_... | System-Feld | _if, _string |
as-Parameter - AusgabeformatfldPreis # 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# 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 # QuickInforow...) - Einzelner verknuepfter DatensatztblProducts {
rowsRead {
fldArtNr
fldWgrNr # Fremdschluessel-Wert
rowWgrNr { fldBez } # Verknuepfte Warengruppe
rowEinh { fldKuBez } # Verknuepfte Einheit
}
}lnk...) - Liste verknuepfter DatensaetzeWICHTIG: by.../using... gehoeren auf rowsRead INNERHALB des Links, NICHT auf lnk...:
# RICHTIG:
lnkInventory {
rowsRead(byArtNrLagNrArtBDat: { usingArtNr: {} }) { fldLagNr fldMge }
}
# FALSCH:
lnkInventory(byArtNrLagNrArtBDat: { usingArtNr: {} }) {
rowsRead { ... }
}tbl... innerhalb Row)tblClient {
rowRead {
fldMandNr
tblBnkVb { rowsRead { fldNr fldIBAN } }
}
}BEVOR du einen Filter schreibst, fuehre IMMER diese Schritte aus:
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.
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 } } }
})by...-Index existiert:fastFilter fuer einfache Vergleiche auf fld...-FelderslowFilter NUR fuer Arithmetik, in-Operator, fn...-FunktionenNIEMALS direkt `slowFilter` verwenden, ohne vorher `keyFilter` geprueft zu haben!
| Filter | Performance | Besonderheiten |
|---|---|---|
keyFilter | Am schnellsten | Filter auf kf...-Felder innerhalb by... - IMMER bevorzugen! |
fastFilter | Schnell (DB-seitig) | Nur einfache Vergleiche auf fld... |
slowFilter | Langsam (App-seitig) | Arithmetik, in-Operator, fn... - nur als letzter Ausweg |
{ 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 } }{ and: [Ausdruck1, Ausdruck2] }
{ or: [Ausdruck1, Ausdruck2] }
{ not: Ausdruck }Pro Filterobjekt nur EIN Operator:
# FALSCH:
fastFilter: { eq: [...], gt: [...] }
# RICHTIG:
fastFilter: { and: [ { eq: [...] }, { gt: [...] } ] }{ in: { left: { field: fldLagBestArt }, list: [1, 7] } }Alle Werte muessen denselben Typ haben (@sametype-Regel).
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.
{ fnPos: [{ value: "Suchtext" }, { field: fldSuchBeg }] } # Textsuche (>0 = gefunden, NUR String-Felder!)
{ fnGetAktDate: [] } # Aktuelles Datum
{ fnIncDate: [{ fnGetAktDate: [] }, { value: 0 }, { value: -12 }] } # Datum +/- Monate# 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.
@store - Wert in Variable speichernVariable 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-BehandlungfldSuchBeg @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 / @includefldPreis @include(if: $includePrice)# 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!")query: Snapshot-Transaktion, keine Sperren, null bei Fehlermutation: Normale Transaktion, Alles-oder-Nichts (Fehler = Rollback)| Operation | Zweck |
|---|---|
rowNew | Neuen 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/rowsDelete | Massenoperationen (Fehler bei einem → Rollback aller) |
| Kontext | Schreiben | lnk... | tbl... schreibbar |
|---|---|---|---|
RowMutationNew/Copy/Modify | ja | nein | ja |
RowMutationRead (nach rowSave) | nein | ja (mit Schreiben) | nein |
RowMutationDelete | nein | nein | nein |
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" } } } })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.
mutation {
tblProducts {
rowModify(kf1ArtNr: { string: "PROD-001" }) {
fldVk0_Preis(set: { float: 129.95 })
fldBez1(as: DISPLAY_TEXT) # Nur lesen
}
}
}mutation {
tblProducts {
rowDelete(kf1ArtNr: { string: "PROD-001" }, ignoreWarnings: true) {
fldArtNr # Noch lesbar vor Loeschung
}
}
}# 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!
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 })# 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 }) { ... } }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.
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 abweichenHinweis: `fnConvert` akzeptiert den Parameter transactionTypeNo sowohl als Integer als auch als Text:
fnConvert(transactionTypeNo: 50, ...) # Integer - Standard-Nummer (50 = Lieferschein)
fnConvert(transactionTypeNo: "Lieferschein", ...) # Text - BezeichnungStandard-Nummern und Bezeichnungen finden sich in references/vorgangsarten.md.
Vollstaendige Liste aller Vorgangsarten mit Standard-Nummern und Buchungsparametern: references/vorgangsarten.md
| Funktion | Beschreibung | Kontext | Rueckgabe |
|---|---|---|---|
fnPost | Buchen | rowRead | Einzelwert |
fnReverse | Stornieren | rowRead | Einzelwert |
fnConvert | Wandeln | rowRead | Array |
fnMoveToArchive | Archivieren | rowRead | Einzelwert |
Alle erfordern: mutation + rowRead-Kontext. Leere Belegnummer = fehlgeschlagen.
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 werdenfldZeilenNr(set: { int: 0 }) fuegt Position an den AnfangfnPost)# Bestehenden Vorgang buchen
mutation {
tblTransactions {
rowRead(exactMatch: { byBelegNr: { kf1BelegNr: { string: "RE12500005" } } }) {
fnPost { fldBelegNr } # Leer = nicht gebucht!
}
}
}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
}
}
}
}
}fnReverse)mutation {
tblTransactions {
rowRead(exactMatch: { byBelegNr: { kf1BelegNr: { string: "RE12500002" } } }) {
fnReverse { fldBelegNr }
}
}
}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
rowSave {
acoGPreis {
totalGrossAmount # Brutto
totalNetAmount # Netto
totalTaxAmount # Steuer
}
}aco...)Kurzbeispiel Lesen: acoEPr { totalGrossAmount totalNetAmount totalTaxAmount }
Details (Lesen, Schreiben, Preisberechnung nach Speichern): references/betragsgruppen.md
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.
| Fehler | Loesung |
|---|---|
fnPost in rowModify-Kontext | Nur in rowRead-Kontext |
fnPost ohne rowSave | Immer rowSave vor fnPost |
Schreiben nach rowSave im selben Block | Nach rowSave nur lesen, oder rowSaveAndModify |
lnk... in schreibendem Kontext | Erst rowSave, dann lnk... im Read-Kontext |
| Mehrere Operatoren in einem Filterobjekt | and/or verwenden (@oneOf-Regel) |
fnMoveToArchive nach fnPost in einer Mutation | Getrennte Mutationen (Auto-Archivierung!) |
| Leere Belegnummer nicht geprueft | Immer fldBelegNr in fn...-Rueckgabe pruefen |
by.../using... auf lnk... statt auf rowsRead | Gehoeren 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 beachtet | Felder werden sequentiell ausgefuehrt - Menge VOR Preis setzen |
rowCopy ohne neuen Schluessel | Kopie braucht eigenen Primaerschluessel |
rowsDelete bricht ab | Ein Fehler bei einem Datensatz → Rollback ALLER Loeschungen |
fnConvert schlaegt fehl → rowNew als Workaround | VERBOTEN! Bricht die Prozesskette. Fehlerursache klaeren und Nutzer informieren |
fnReverse schlaegt fehl → rowDelete als Workaround | VERBOTEN! Umgeht Storno-Logik (keine Gegenbuchungen, kein Audit-Trail). Nutzer informieren |
fnPost schlaegt fehl → naechsten Schritt trotzdem ausfuehren | VERBOTEN! Folgeschritte (z.B. Wandlung) setzen erfolgreiche Buchung voraus. Nutzer informieren |
fnMoveToArchive schlaegt fehl → manuell Daten kopieren/loeschen | VERBOTEN! 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 interpretiert | edgeCount 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 tblAddresses | Diese Felder leben in tblPostalAddresses, Zugriff ueber lnkPostalAddresses |
"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.
"Address \"10002\" is blocked."Ursache: Adresse hat fldGspKz = true (Gesperrt-Kennzeichen). Loesung: Andere Adresse verwenden oder Sperrung im ERP aufheben.
fnPost/fnReverse/fnConvertUrsache: Funktion konnte nicht ausgefuehrt werden (Voraussetzungen nicht erfuellt). Loesung: Immer Belegnummer pruefen: fnPost { fldBelegNr }. Leer = fehlgeschlagen.
fnPost trotz erfolgreicher BuchungUrsache: 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 verfuegbarUrsache: Datensatz ist noch in schreibendem Kontext (RowMutationNew/Copy/Modify). Loesung: Erst rowSave aufrufen → wechselt zu RowMutationRead → lnk... wird verfuegbar.
rowSaveUrsache: 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.
Ursache: @acquireLocks fehlt oder unvollstaendig. Loesung: @acquireLocks(forWriting: [...], forReading: [...]) mit ALLEN beteiligten Tabellen.
Ursache: Vielfaeltig - fehlende Konfiguration (Wandlungspfad), Beleg bereits archiviert, fehlende Berechtigung, Geschaeftsregel verletzt, Beleg gesperrt. Loesung: STOPP - KEINEN Workaround versuchen! Insbesondere:
rowNew einen Ersatzbeleg anlegen (bricht Prozesskette)rowDelete einen Beleg loeschen statt zu stornieren (umgeht Audit-Trail)Stattdessen: Fehlermeldung dem Nutzer zeigen und gemeinsam klaeren. Moegliche Ursachen:
fnConvert nur auf aktive Vorgaenge moeglichnull zurueck obwohl Datensatz existiertUrsache (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.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.