MIS-Daten auf das CoCoCo-Datenmodell abbilden
Gilt für die CoCoCo-Plattform v1.0.0-rc.31. Jede Aussage unten wurde auf dieser Version geprüft; das SQL-Server-Beispiel ließ sich dort nicht ausführen (kein SQL Server auf dem Testmandanten), und der Hinweis zu festen
CHAR-Spalten wurde nicht nachgestellt.
Zeilen aus einer MIS-Datenbank zu lesen ist die leichte Hälfte. Die Arbeit steckt in der Frage, zu welchem Datensatz in CoCoCo eine Zeile gehört — bei jedem Lauf, ohne Dubletten und ohne eigene Zuordnungstabelle.
Genau dafür hat CoCoCo drei Mechanismen. Einmal verstanden, sieht jede weitere Integration gleich aus.
1. externalId — der Anker
Abschnitt betitelt „1. externalId — der Anker“Kunden, Aufträge, Jobs und Arbeitsgänge tragen jeweils eine externalId: die Kennung, die der Datensatz in Ihrem Quellsystem hat. Beim Anlegen mitschreiben — und die Nummerierung des Quellsystems wird der Schlüssel, über den Sie Datensätze wiederfinden.
Verwenden Sie ein Präfix, damit die Herkunft sichtbar bleibt, und halten Sie die Quellnummer lesbar:
| Datensatz in der Quelle | externalId |
|---|---|
| Kunde 1042 | MIS:K-001042 |
| Auftrag 26-04711 | MIS:26-04711 |
| Arbeitsgang 20 dieses Auftrags | MIS:26-04711:20 |
Das Importmuster ist dann immer dasselbe — erst suchen, nur bei Fehlen anlegen:
local ok, res = ctx.graphql.query([[ query($e: String!) { listJobs(first: 1, filter: { externalId: { eq: $e } }) { edges { node { id } } } }]], { e = "MIS:26-04711" })if not ok then error(res) endlocal edge = res.data.listJobs.edges[1] -- nil, wenn der Job noch nicht existiertctx.graphql.query liefert zwei Werte: ob der Aufruf durchging, und das Ergebnis. Wer ihn einer einzigen Variablen zuweist, behält nur den ersten — true — und die Suche sieht nie einen Job.
Kommt der Job zurück, verwenden Sie seine ID. Kommt er nicht, legen Sie ihn mit dieser externalId an. Ein zweiter Lauf desselben Imports erzeugt dann gar nichts — genau das, was man will, wenn ein Timer alle fünf Minuten feuert.
Trimmen Sie Ihre Quellwerte, bevor Sie sie als Kennung verwenden. Feste
CHAR-Spalten kommen mit Leerzeichen aufgefüllt an, und"26-04711 "ist eine andere Kennung als"26-04711".
2. externalRef — Fortschritt buchen, ohne CoCoCo-IDs zu kennen
Abschnitt betitelt „2. externalRef — Fortschritt buchen, ohne CoCoCo-IDs zu kennen“Rückmeldungen aus der Produktion sind in jeder MIS-Integration die größte Datenmenge — und die Stelle, an der eine Zuordnungstabelle am meisten schmerzt. recordProgressPoints nimmt deshalb statt einer Arbeitsgang-ID eine externalRef und löst das Ziel über Ihre eigenen Koordinaten auf:
local ok, res = ctx.graphql.query([[ mutation($i: RecordProgressPointsInput!) { recordProgressPoints(input: $i) { recorded errors { message } } }]], { i = { points = { { externalRef = { jobExternalId = "MIS:26-04711", operationExternalId = "MIS:26-04711:20", }, timestamp = "2026-09-04T06:41:00Z", phase = "RUNNING", goodCount = 1420, wasteCount = 95, source = "MANUAL", } } } })if not ok then error(res) end-- res.data.recordProgressPoints.errors nennt die Punkte, die sich nicht auflösen ließenKein Lookup, keine ID-Übersetzung. Die Auflösung läuft von oben nach unten (Job → Komponente → Arbeitsgang → Position), Teilkoordinaten funktionieren also auch.
Setzen Sie source ehrlich: MANUAL für eine Eingabe am Terminal durch einen Menschen, JMF für eine Maschinenmeldung. Wer die Daten liest, erkennt daran, ob eine Zahl gemessen oder getippt wurde.
3. Ein Wasserzeichen — woher man weiß, was neu ist
Abschnitt betitelt „3. Ein Wasserzeichen — woher man weiß, was neu ist“Fragen Sie die Datenbank bei jedem Lauf nur nach dem, was sich geändert hat. Welche Spalte dafür taugt, hängt davon ab, was die Tabelle hergibt:
| Die Tabelle hat | Wasserzeichen |
|---|---|
| Einen Änderungszeitstempel | Den höchsten bereits importierten Zeitstempel |
| Eine fortlaufende ID, keinen Zeitstempel | Die höchste bereits importierte ID |
| Keins von beidem | Ein festes Fenster erneut lesen (z. B. 48 Stunden). Die externalId-Suche fängt wiederholte Kunden, Aufträge, Jobs und Arbeitsgänge ab — keine Fortschrittspunkte (siehe unten) |
Das Wasserzeichen gehört in ctx.cache — ein Zeiger, nie die Daten:
ctx.cache.set({ key = "mis:rueckmeldung:letzte", value = tostring(hoechste), ttl = 604800 })Dazu zwei Regeln, die echte Mühe ersparen:
- Das Wasserzeichen nur nach einem fehlerfreien Lauf fortschreiben. Für Kunden, Aufträge, Jobs und Arbeitsgänge ist ein wiederholter Lauf harmlos: die
externalId-Suche findet, was der letzte Lauf angelegt hat. Fortschrittspunkte haben keinen solchen Schlüssel — ein zweimal gesendeter Punkt wird zweimal gespeichert und lässt sich nicht löschen. Bei Teilfehlern senden Sie nur die Rückmeldungen erneut, die nicht gespeichert wurden (recordProgressPointsantwortet mit der Zahlrecordedund denerrors). - Kaltstart beherrschen. Der Cache kann verfallen. Fehlt der Zeiger, importieren Sie nicht alles von Anfang an: für Stammdaten ein festes Fenster erneut lesen, für Rückmeldungen nach dem jüngsten schon gespeicherten Punkt des Arbeitsgangs beginnen.
Zeitstempel: in SQL zusammensetzen
Abschnitt betitelt „Zeitstempel: in SQL zusammensetzen“Viele MIS-Schemas halten Datum und Uhrzeit in getrennten Spalten. Setzen Sie sie in der Abfrage zusammen, nicht im Skript — und rechnen Sie nach UTC um, bevor Sie das Z anhängen. Ein BDE-Terminal schreibt die Ortszeit; so wie sie ist als UTC ausgewiesen, liegt in Mitteleuropa jeder Punkt ein bis zwei Stunden in der Zukunft:
-- PostgreSQL: über die Zeitzone der Datenbank selbstto_char(((beginn_datum + beginn_zeit::time) AT TIME ZONE current_setting('TimeZone')) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS') || 'Z'
-- SQL Server: die Zeitzone nennen, in der das MIS schreibtFORMAT((DATEADD(second, DATEDIFF(second, 0, beginn_zeit), CAST(beginn_datum AS datetime2)) AT TIME ZONE 'W. Europe Standard Time') AT TIME ZONE 'UTC', 'yyyy-MM-ddTHH:mm:ss') + 'Z'Speichert Ihr MIS bereits UTC, lassen Sie die Umrechnung weg. Fragen Sie den Hersteller; die Spaltennamen verraten es selten.
Zwei Gründe, es in SQL zu tun. Eine DATE-Spalte kommt als vollständiger Zeitstempel an (2026-09-02T00:00:00Z), nicht als 2026-09-02 — eine angehängte Uhrzeit ergibt also eine unbrauchbare Zeichenfolge. Und ein falscher Zeitstempel ist teuer: Fortschrittspunkte sind append-only. Eine falsch datierte Produktionshistorie lässt sich nicht korrigieren, nur ergänzen, und latestProgress meldet den jüngsten Punkt.
Codes und Einheiten
Abschnitt betitelt „Codes und Einheiten“Statuscodes und Kostenstellen brauchen eine Übersetzung, und die gehört in die Konfiguration. STATUS = 2 oder KST-OFF bedeutet für CoCoCo nichts; Sie bilden es auf CONFIRMED oder OFFSET_PRINTING ab. Die Codes unterscheiden sich von Kunde zu Kunde — halten Sie die Tabelle an einer Stelle Ihrer Integrationskonfiguration, nicht über den Code verstreut.
Einheiten sind typisiert. Für Bogen und Drucke gilt countUnit: CUSTOM mit customCountUnit: "SHEETS", geschrieben genau so, wie verticalProductionUnits es führt — die Zeichenfolge ist Freitext, ein Tippfehler erzeugt still eine zweite Reihe.
Wie es typischerweise aussieht
Abschnitt betitelt „Wie es typischerweise aussieht“Für ein Druck-MIS fällt die Abbildung meist so aus:
| MIS | CoCoCo |
|---|---|
| Kundenstamm | Kunde (externalId, Name, Zahlungsbedingungen) |
| Auftragskopf | Auftrag (orderNumber, externalId, Termine, Beträge) |
| Auftragspositionen | Auftragspositionen; die Produktionsposition trägt jobId |
| Auftrag + Auflage + Liefertermin | Job (quantity, dueAt, jobNumber) |
| Arbeitsgänge | Arbeitsgänge (kind aus der Kostenstelle, requestedWorkCenterId aus der Maschine) |
| Rückmeldungen | Fortschrittspunkte über externalRef |
Zeigt die Auftragsposition auf den Job, liefert orderLifecycle Auftrag, Positionen und Produktionsjob in einem Aufruf.
Wie es weitergeht
Abschnitt betitelt „Wie es weitergeht“- Eine externe Datenbank (MIS/ERP) über SQL anbinden — die Verbindung überhaupt erst herstellen
- Was sind Integrationen? und Eine Integration bauen — wohin dieser Code gehört, sobald er auf einem Timer laufen soll statt von Hand
- Lua Playground: Erste Schritte und Skripte verwenden — um die Abbildung erst einmal auszuprobieren
- API-Tokens erstellen und verwalten — wenn Sie den Import von außen fahren statt aus einem Skript