Terminal im Geraetehaus mit Offline-Betrieb ergaenzen
Phase 4: eine eigene Oberflaeche unter /kiosk fuer das Wandtablet in der Fahrzeughalle — mit Handschuhen bedienbar und arbeitsfaehig, wenn das Netz weg ist. Datenmodell: Fahrt, Anwesenheit, Waescheabgabe, Inventurgang, Inventurposten, KioskVorgang und SyncKonflikt. Die Kennung eines Vorgangs erzeugt der Browser beim Einreihen und ist damit der Idempotenzschluessel; kommt derselbe Vorgang nach abgebrochener Uebertragung erneut an, erkennt ihn der Server wieder. Die Datenbank setzt vier Regeln selbst durch und verweigert UPDATE und DELETE auf kiosk_vorgang. Offline: von Hand geschriebener Service Worker, Warteschlange in der IndexedDB (Dexie), Fotos als Blob daneben, sichtbarer Zustand als Leiste ueber allem. Was sich beim Nachtragen nicht anwenden laesst, landet als Konflikt unter /terminal statt verworfen zu werden. Dazu: Anmeldung per Ausweis, PIN oder signierter QR-Marke vom Handy, Hallenmonitor mit optionalen DWD-Warnungen, Installation als App. Behoben: die Warteschlange zog ihren Wiederholungstakt bei jedem Rendern neu auf und haette einen liegen gebliebenen Vorgang nie ein zweites Mal geschickt. Gefunden vom Offline-E2E-Test. 31 neue Tests (20 Unit, 11 gegen eine echte Datenbank, 9 E2E je Browser). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d8d692098d
commit
791523027f
+364
-7
@@ -190,6 +190,15 @@ model Organisation {
|
||||
uebungen Uebung[]
|
||||
uebungsteilnahmen Uebungsteilnahme[]
|
||||
|
||||
// Phase 4 — Terminal im Gerätehaus
|
||||
fahrten Fahrt[]
|
||||
anwesenheiten Anwesenheit[]
|
||||
waescheabgaben Waescheabgabe[]
|
||||
inventurgaenge Inventurgang[]
|
||||
inventurposten Inventurposten[]
|
||||
kioskvorgaenge KioskVorgang[]
|
||||
syncKonflikte SyncKonflikt[]
|
||||
|
||||
@@index([aktiv])
|
||||
@@map("organisation")
|
||||
}
|
||||
@@ -272,6 +281,17 @@ model Person {
|
||||
geleiteteUebungen Uebung[] @relation("UebungLeitung")
|
||||
uebungsteilnahmen Uebungsteilnahme[] @relation("UebungTeilnahme")
|
||||
|
||||
// Phase 4 — Terminal im Gerätehaus
|
||||
fahrten Fahrt[] @relation("FahrtFahrer")
|
||||
anwesenheiten Anwesenheit[] @relation("AnwesenheitPerson")
|
||||
waescheabgaben Waescheabgabe[] @relation("WaescheTraeger")
|
||||
angenommeneWaesche Waescheabgabe[] @relation("WaescheAnnahme")
|
||||
geleiteteInventuren Inventurgang[] @relation("InventurLeitung")
|
||||
inventurposten Inventurposten[] @relation("InventurErfasser")
|
||||
kioskvorgaenge KioskVorgang[] @relation("KioskVorgangPerson")
|
||||
verursachteKonflikte SyncKonflikt[] @relation("KonfliktVerursacher")
|
||||
erledigteKonflikte SyncKonflikt[] @relation("KonfliktErledigung")
|
||||
|
||||
/// E-Mail und OIDC-Subject sind je Wehr eindeutig, nicht global — dieselbe
|
||||
/// Person kann in zwei Wehren Mitglied sein.
|
||||
@@unique([organisationId, email])
|
||||
@@ -616,9 +636,10 @@ model Standort {
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
geloeschtAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
fahrzeuge Fahrzeug[]
|
||||
geraete Geraet[]
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
fahrzeuge Fahrzeug[]
|
||||
geraete Geraet[]
|
||||
inventurgaenge Inventurgang[]
|
||||
|
||||
@@unique([organisationId, name])
|
||||
@@index([organisationId, aktiv])
|
||||
@@ -676,6 +697,10 @@ model Fahrzeug {
|
||||
einsaetze Einsatzfahrzeug[]
|
||||
einsatzkraefte Einsatzkraft[]
|
||||
|
||||
// Phase 4
|
||||
fahrten Fahrt[]
|
||||
inventurgaenge Inventurgang[]
|
||||
|
||||
@@unique([organisationId, funkrufname])
|
||||
@@unique([organisationId, kennzeichen])
|
||||
@@index([organisationId, aktiv])
|
||||
@@ -813,6 +838,10 @@ model Geraet {
|
||||
rueckrufBetroffenheiten RueckrufBetroffenheit[]
|
||||
materialverbrauch Materialverbrauch[]
|
||||
|
||||
// Phase 4
|
||||
waescheabgaben Waescheabgabe[]
|
||||
inventurposten Inventurposten[]
|
||||
|
||||
@@unique([organisationId, inventarnummer])
|
||||
@@index([organisationId, status])
|
||||
@@index([organisationId, naechstePruefung])
|
||||
@@ -1620,6 +1649,8 @@ model Einsatz {
|
||||
material Materialverbrauch[]
|
||||
lagemeldungen Lagemeldung[]
|
||||
portalzugaenge PortalZugang[]
|
||||
fahrts Fahrt[]
|
||||
anwesenheits Anwesenheit[]
|
||||
|
||||
@@unique([organisationId, einsatznummer])
|
||||
@@index([organisationId, alarmiertUm(sort: Desc)])
|
||||
@@ -1822,10 +1853,12 @@ model Uebung {
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
geloeschtAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
leiter Person? @relation("UebungLeitung", fields: [leiterId], references: [id], onDelete: SetNull)
|
||||
berichtDatei Datei? @relation("UebungBericht", fields: [berichtDateiId], references: [id], onDelete: SetNull)
|
||||
teilnahmen Uebungsteilnahme[]
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
leiter Person? @relation("UebungLeitung", fields: [leiterId], references: [id], onDelete: SetNull)
|
||||
berichtDatei Datei? @relation("UebungBericht", fields: [berichtDateiId], references: [id], onDelete: SetNull)
|
||||
teilnahmen Uebungsteilnahme[]
|
||||
fahrten Fahrt[]
|
||||
anwesenheiten Anwesenheit[]
|
||||
|
||||
@@unique([organisationId, nummer])
|
||||
@@index([organisationId, datum(sort: Desc)])
|
||||
@@ -1856,3 +1889,327 @@ model Uebungsteilnahme {
|
||||
@@index([organisationId, personId])
|
||||
@@map("uebungsteilnahme")
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Phase 4 — Terminal im Gerätehaus
|
||||
//
|
||||
// Was am Wandtablet in der Fahrzeughalle erfasst wird:
|
||||
//
|
||||
// Fahrt das Fahrtenbuch — wer ist wann womit wohin gefahren
|
||||
// Anwesenheit ein- und auschecken zu Dienst, Übung oder Einsatz
|
||||
// Waescheabgabe Schutzkleidung zur Reinigung geben und zurückbekommen
|
||||
// Inventurgang Bestand am Regal scannen, Fehlendes finden
|
||||
// KioskVorgang Idempotenz: was das Terminal offline erfasst hat
|
||||
// SyncKonflikt was sich beim Nachtragen nicht auflösen ließ
|
||||
//
|
||||
// Die beiden letzten sind der Kern des Offline-Betriebs. Ein Tablet in einer
|
||||
// Halle aus Stahlbeton hat regelmäßig kein Netz; erfasst wird trotzdem, und
|
||||
// nachgetragen wird, sobald es wieder geht. Damit ein zweimal abgeschickter
|
||||
// Vorgang nicht zweimal ankommt, trägt jeder eine im Browser erzeugte Kennung.
|
||||
// =============================================================================
|
||||
|
||||
/// Wozu eine Fahrt unternommen wurde.
|
||||
enum FahrtZweck {
|
||||
EINSATZ
|
||||
UEBUNG
|
||||
AUSBILDUNG
|
||||
WERKSTATT
|
||||
BESCHAFFUNG
|
||||
/// Probefahrt, Überführung, Fahrsicherheitstraining.
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
/// Weg der Schutzkleidung durch die Reinigung.
|
||||
enum WaescheStatus {
|
||||
ABGEGEBEN
|
||||
IN_REINIGUNG
|
||||
ZURUECK
|
||||
/// Nicht wieder aufgetaucht — muss ersetzt werden.
|
||||
VERSCHOLLEN
|
||||
}
|
||||
|
||||
/// Wozu jemand am Terminal eincheckt.
|
||||
enum AnwesenheitAnlass {
|
||||
DIENST
|
||||
UEBUNG
|
||||
EINSATZ
|
||||
ARBEITSDIENST
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
/// Stand eines Inventurgangs.
|
||||
enum InventurStatus {
|
||||
LAEUFT
|
||||
ABGESCHLOSSEN
|
||||
ABGEBROCHEN
|
||||
}
|
||||
|
||||
/// Was ein offline erfasster Vorgang bewirken soll.
|
||||
enum KioskVorgangArt {
|
||||
MANGEL_MELDEN
|
||||
FAHRT_BEGINNEN
|
||||
FAHRT_BEENDEN
|
||||
ANWESENHEIT_KOMMEN
|
||||
ANWESENHEIT_GEHEN
|
||||
WAESCHE_ABGEBEN
|
||||
INVENTUR_SCANNEN
|
||||
PRUEFUNG_ERFASSEN
|
||||
}
|
||||
|
||||
/// Warum sich ein nachgetragener Vorgang nicht anwenden ließ.
|
||||
enum KonfliktGrund {
|
||||
/// Der Datensatz, auf den er sich bezieht, gibt es nicht mehr.
|
||||
ZIEL_FEHLT
|
||||
/// Der Datensatz wurde zwischenzeitlich festgeschrieben.
|
||||
ZIEL_GESPERRT
|
||||
/// Zwei Terminals haben dasselbe erfasst.
|
||||
DOPPELT
|
||||
/// Die Angaben passen nicht mehr zum Stand auf dem Server.
|
||||
VERALTET
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Eine Fahrt im Fahrtenbuch.
|
||||
///
|
||||
/// Nach § 6 StVO-Nachweispflicht und nach der Dienstanweisung der meisten
|
||||
/// Wehren muss nachvollziehbar sein, wer wann mit welchem Fahrzeug unterwegs
|
||||
/// war. Am Terminal ist das ein Vorgang von zwanzig Sekunden: Fahrzeug wählen,
|
||||
/// Kilometerstand eintippen, Zweck antippen.
|
||||
model Fahrt {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
fahrzeugId String @db.Uuid
|
||||
fahrerId String @db.Uuid
|
||||
|
||||
zweck FahrtZweck @default(SONSTIGES)
|
||||
/// Wenn die Fahrt zu einem Einsatz oder einer Übung gehört.
|
||||
einsatzId String? @db.Uuid
|
||||
uebungId String? @db.Uuid
|
||||
|
||||
abfahrtUm DateTime @default(now()) @db.Timestamptz(3)
|
||||
rueckkehrUm DateTime? @db.Timestamptz(3)
|
||||
|
||||
kilometerAbfahrt Int
|
||||
kilometerRueckkehr Int?
|
||||
|
||||
ziel String? @db.VarChar(200)
|
||||
/// Getankte Menge in Litern, wenn unterwegs getankt wurde.
|
||||
tankmenge Decimal? @db.Decimal(8, 2)
|
||||
bemerkung String? @db.VarChar(500)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
fahrzeug Fahrzeug @relation(fields: [fahrzeugId], references: [id], onDelete: Restrict)
|
||||
fahrer Person @relation("FahrtFahrer", fields: [fahrerId], references: [id], onDelete: Restrict)
|
||||
einsatz Einsatz? @relation(fields: [einsatzId], references: [id], onDelete: SetNull)
|
||||
uebung Uebung? @relation(fields: [uebungId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([organisationId, fahrzeugId, abfahrtUm(sort: Desc)])
|
||||
@@index([organisationId, fahrerId])
|
||||
@@map("fahrt")
|
||||
}
|
||||
|
||||
/// Anwesenheit am Gerätehaus.
|
||||
///
|
||||
/// Wer eincheckt, ist da — für die Übungsteilnahme, für den Arbeitsdienst und
|
||||
/// im Einsatzfall dafür, dass die Einsatzleitung weiß, wer im Haus ist.
|
||||
model Anwesenheit {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
personId String @db.Uuid
|
||||
|
||||
anlass AnwesenheitAnlass @default(DIENST)
|
||||
angemeldetUm DateTime @default(now()) @db.Timestamptz(3)
|
||||
abgemeldetUm DateTime? @db.Timestamptz(3)
|
||||
|
||||
einsatzId String? @db.Uuid
|
||||
uebungId String? @db.Uuid
|
||||
bemerkung String? @db.VarChar(500)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
person Person @relation("AnwesenheitPerson", fields: [personId], references: [id], onDelete: Cascade)
|
||||
einsatz Einsatz? @relation(fields: [einsatzId], references: [id], onDelete: SetNull)
|
||||
uebung Uebung? @relation(fields: [uebungId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([organisationId, personId, angemeldetUm(sort: Desc)])
|
||||
@@index([organisationId, abgemeldetUm])
|
||||
@@map("anwesenheit")
|
||||
}
|
||||
|
||||
/// Schutzkleidung in der Reinigung.
|
||||
///
|
||||
/// Wer seine Einsatzjacke abgibt, steht ohne da — deshalb hängt an der Abgabe
|
||||
/// auch die Frage, ob Ersatz vorhanden ist. Erfasst wird am Terminal beim
|
||||
/// Einwurf in den Wäschekorb.
|
||||
model Waescheabgabe {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
personId String @db.Uuid
|
||||
/// Wenn das Kleidungsstück als Gerät erfasst ist.
|
||||
geraetId String? @db.Uuid
|
||||
|
||||
bezeichnung String @db.VarChar(200)
|
||||
menge Int @default(1)
|
||||
status WaescheStatus @default(ABGEGEBEN)
|
||||
/// Grund: Einsatzverschmutzung, Kontamination, Routine.
|
||||
grund String? @db.VarChar(200)
|
||||
|
||||
abgegebenAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
zurueckAm DateTime? @db.Timestamptz(3)
|
||||
/// Wer die Rückgabe bestätigt hat.
|
||||
angenommenVonId String? @db.Uuid
|
||||
|
||||
bemerkung String? @db.VarChar(500)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
person Person @relation("WaescheTraeger", fields: [personId], references: [id], onDelete: Cascade)
|
||||
geraet Geraet? @relation(fields: [geraetId], references: [id], onDelete: SetNull)
|
||||
angenommenVon Person? @relation("WaescheAnnahme", fields: [angenommenVonId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([organisationId, status])
|
||||
@@index([organisationId, personId])
|
||||
@@map("waescheabgabe")
|
||||
}
|
||||
|
||||
/// Ein Inventurgang.
|
||||
///
|
||||
/// Der Gerätewart geht mit dem Tablet durch die Halle und scannt, was da ist.
|
||||
/// Am Ende steht die Liste dessen, was fehlt — das ist der Zweck der Sache.
|
||||
model Inventurgang {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
bezeichnung String @db.VarChar(160)
|
||||
status InventurStatus @default(LAEUFT)
|
||||
|
||||
/// Worauf sich der Gang bezieht — ein Standort oder ein Fahrzeug.
|
||||
standortId String? @db.Uuid
|
||||
fahrzeugId String? @db.Uuid
|
||||
|
||||
begonnenAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
abgeschlossenAm DateTime? @db.Timestamptz(3)
|
||||
begonnenVonId String? @db.Uuid
|
||||
bemerkung String? @db.VarChar(1000)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
standort Standort? @relation(fields: [standortId], references: [id], onDelete: SetNull)
|
||||
fahrzeug Fahrzeug? @relation(fields: [fahrzeugId], references: [id], onDelete: SetNull)
|
||||
begonnenVon Person? @relation("InventurLeitung", fields: [begonnenVonId], references: [id], onDelete: SetNull)
|
||||
posten Inventurposten[]
|
||||
|
||||
@@index([organisationId, status])
|
||||
@@map("inventurgang")
|
||||
}
|
||||
|
||||
/// Ein gescanntes Gerät innerhalb eines Inventurgangs.
|
||||
model Inventurposten {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
inventurId String @db.Uuid
|
||||
geraetId String @db.Uuid
|
||||
|
||||
gescanntAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
/// Am erwarteten Ort vorgefunden.
|
||||
gefunden Boolean @default(true)
|
||||
/// Wo es tatsächlich lag, wenn es nicht dort war, wo es sein sollte.
|
||||
vorgefundenAn String? @db.VarChar(200)
|
||||
bemerkung String? @db.VarChar(500)
|
||||
erfasstVonId String? @db.Uuid
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
inventur Inventurgang @relation(fields: [inventurId], references: [id], onDelete: Cascade)
|
||||
geraet Geraet @relation(fields: [geraetId], references: [id], onDelete: Cascade)
|
||||
erfasstVon Person? @relation("InventurErfasser", fields: [erfasstVonId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([inventurId, geraetId])
|
||||
@@index([organisationId, inventurId])
|
||||
@@map("inventurposten")
|
||||
}
|
||||
|
||||
/// Ein vom Terminal übermittelter Vorgang.
|
||||
///
|
||||
/// Der Schlüssel zum Offline-Betrieb: die Kennung erzeugt der Browser, bevor
|
||||
/// er den Vorgang in seine Warteschlange legt. Kommt derselbe Vorgang nach
|
||||
/// einem abgebrochenen Sendeversuch ein zweites Mal an, erkennt der Server ihn
|
||||
/// an dieser Kennung wieder und antwortet mit dem Ergebnis von damals, statt
|
||||
/// alles noch einmal anzulegen.
|
||||
///
|
||||
/// Deshalb ist die Kennung der Primärschlüssel und wird nicht vergeben,
|
||||
/// sondern angenommen.
|
||||
model KioskVorgang {
|
||||
/// Vom Browser erzeugt (crypto.randomUUID) — nicht vom Server.
|
||||
id String @id @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
art KioskVorgangArt
|
||||
personId String? @db.Uuid
|
||||
|
||||
/// Wann der Vorgang am Terminal erfasst wurde — kann deutlich vor dem
|
||||
/// Eingang liegen.
|
||||
erfasstAm DateTime @db.Timestamptz(3)
|
||||
empfangenAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
/// Was der Vorgang bewirkt hat: die Kennung des angelegten Datensatzes.
|
||||
ergebnisId String? @db.Uuid
|
||||
/// Die Nutzlast, wie sie das Terminal geschickt hat — für die Fehlersuche
|
||||
/// und für den Fall, dass ein Vorgang von Hand nachgezogen werden muss.
|
||||
nutzlast Json? @db.JsonB
|
||||
|
||||
erfolgreich Boolean @default(true)
|
||||
meldung String? @db.VarChar(500)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
person Person? @relation("KioskVorgangPerson", fields: [personId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([organisationId, empfangenAm(sort: Desc)])
|
||||
@@index([organisationId, art])
|
||||
@@map("kiosk_vorgang")
|
||||
}
|
||||
|
||||
/// Ein Vorgang, der sich beim Nachtragen nicht anwenden ließ.
|
||||
///
|
||||
/// Die Regel dahinter: bei Stammdaten gewinnt der Server, bei neuen
|
||||
/// Datensätzen der Client. Was in keine der beiden Schubladen passt — ein
|
||||
/// Mangel zu einem inzwischen ausgesonderten Gerät, eine Prüfung zu einem
|
||||
/// festgeschriebenen Protokoll — landet hier und wartet auf einen Menschen.
|
||||
model SyncKonflikt {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
/// Die Kennung des Vorgangs, der nicht durchging.
|
||||
vorgangId String @db.Uuid
|
||||
art KioskVorgangArt
|
||||
grund KonfliktGrund @default(SONSTIGES)
|
||||
|
||||
beschreibung String @db.VarChar(500)
|
||||
nutzlast Json? @db.JsonB
|
||||
|
||||
personId String? @db.Uuid
|
||||
|
||||
entstandenAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
erledigtAm DateTime? @db.Timestamptz(3)
|
||||
erledigtVonId String? @db.Uuid
|
||||
erledigung String? @db.VarChar(500)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
person Person? @relation("KonfliktVerursacher", fields: [personId], references: [id], onDelete: SetNull)
|
||||
erledigtVon Person? @relation("KonfliktErledigung", fields: [erledigtVonId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([organisationId, erledigtAm])
|
||||
@@map("sync_konflikt")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user