+304
-278
@@ -1,331 +1,357 @@
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
moduleFormat = "cjs"
|
||||
// Compiled to CommonJS by tsc: imports without an extension. Set, not inferred: the client is generated during
|
||||
// install, before the tsconfig it would be inferred from is there
|
||||
importFileExtension = ""
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
enum Role {
|
||||
admin
|
||||
manager
|
||||
superadmin
|
||||
}
|
||||
|
||||
enum PaymentMethod {
|
||||
cash
|
||||
payconiq
|
||||
other
|
||||
}
|
||||
|
||||
enum StockMovementType {
|
||||
add
|
||||
count
|
||||
}
|
||||
|
||||
enum ActorType {
|
||||
user
|
||||
device
|
||||
system
|
||||
}
|
||||
|
||||
enum TerminalEventType {
|
||||
tab_opened
|
||||
tab_renamed
|
||||
tab_deleted
|
||||
line_added
|
||||
line_removed
|
||||
settled
|
||||
stock_set
|
||||
product_created
|
||||
product_updated
|
||||
product_archived
|
||||
product_restored
|
||||
}
|
||||
|
||||
model Venue {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
devices Device[]
|
||||
clientUsers ClientUser[]
|
||||
remoteCommands RemoteCommand[]
|
||||
auditLogs AuditLog[]
|
||||
regCodes RegistrationCode[]
|
||||
plannedTabs PlannedTab[]
|
||||
tabReminders TabReminder[]
|
||||
tabEvents TabEvent[]
|
||||
products Product[]
|
||||
stockAdjustments StockAdjustment[]
|
||||
tabs Tab[]
|
||||
settlements Settlement[]
|
||||
// Set on the first sync after the server started holding the tabs: the tabs were then taken over from the
|
||||
// terminal's own open tabs, once
|
||||
tabsSeededAt DateTime?
|
||||
// Get an email for every tab paid in cash, on a terminal or on the dashboard
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String
|
||||
// Who gets an email for every tab paid in cash, on a terminal or on the dashboard
|
||||
cashPaymentEmails String[] @default([])
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
users User[]
|
||||
devices Device[]
|
||||
registrationCodes RegistrationCode[]
|
||||
customers Customer[]
|
||||
tabs Tab[]
|
||||
settlements Settlement[]
|
||||
units TabUnit[]
|
||||
products Product[]
|
||||
stockMovements StockMovement[]
|
||||
tabEvents TabEvent[]
|
||||
reminders Reminder[]
|
||||
auditLog AuditLog[]
|
||||
}
|
||||
|
||||
model Device {
|
||||
id String @id @default(uuid())
|
||||
// A venue can have several terminals
|
||||
venueId String
|
||||
name String
|
||||
tokenHash String @unique
|
||||
tokenPrefix String
|
||||
tokenExpiresAt DateTime
|
||||
lastSeenAt DateTime @default(now())
|
||||
revokedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
remoteCommands RemoteCommand[]
|
||||
tabEvents TabEvent[]
|
||||
settlements Settlement[]
|
||||
|
||||
@@index([venueId, revokedAt])
|
||||
}
|
||||
|
||||
model ClientUser {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
email String @unique
|
||||
passwordHash String
|
||||
name String
|
||||
// admin (the owner: users and terminals) | manager (runs the venue: products, stock, tabs, reminders) |
|
||||
// superadmin (runs KoolTap itself: every venue, and the admin page)
|
||||
role String @default("manager")
|
||||
createdAt DateTime @default(now())
|
||||
// A venue's admin or manager, or a superadmin, who runs KoolTap itself and belongs to no venue: they view a venue to
|
||||
// act in it as its admin
|
||||
model User {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
// Null exactly for a superadmin
|
||||
venueId String? @db.Uuid
|
||||
// Stored lowercased
|
||||
email String @unique
|
||||
passwordHash String
|
||||
name String
|
||||
role Role @default(manager)
|
||||
// Access tokens issued before this are refused, so a forced logout applies at once instead of when they expire
|
||||
sessionsRevokedAt DateTime?
|
||||
sessionsRevokedAt DateTime? @db.Timestamptz(3)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
venue Venue? @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
refreshTokens RefreshToken[]
|
||||
settlements Settlement[]
|
||||
stockMovements StockMovement[]
|
||||
|
||||
@@index([venueId])
|
||||
}
|
||||
|
||||
model RegistrationCode {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
code String @unique
|
||||
expiresAt DateTime
|
||||
usedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
|
||||
@@index([venueId])
|
||||
}
|
||||
|
||||
// A request from the dashboard. close_tab used to be executed by the owning terminal; the server now settles
|
||||
// the tab itself and records the command as completed right away, for the history.
|
||||
model RemoteCommand {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
// Null for a command the server carried out itself
|
||||
deviceId String?
|
||||
targetTabId String
|
||||
customerName String?
|
||||
amountInCents Int?
|
||||
type String @default("close_tab")
|
||||
expectedVersion Int
|
||||
paymentMethod String
|
||||
requestedBy String
|
||||
status String @default("pending")
|
||||
result String?
|
||||
acknowledgedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
device Device? @relation(fields: [deviceId], references: [id])
|
||||
|
||||
@@index([deviceId, status, createdAt])
|
||||
@@index([venueId, status, createdAt])
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
actorType String
|
||||
actorId String
|
||||
action String
|
||||
details String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
|
||||
@@index([venueId, createdAt])
|
||||
}
|
||||
|
||||
model RefreshToken {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
userId String @db.Uuid
|
||||
tokenHash String @unique
|
||||
expiresAt DateTime
|
||||
revokedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime @db.Timestamptz(3)
|
||||
revokedAt DateTime? @db.Timestamptz(3)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model PlannedTab {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
customerName String
|
||||
// Lowercased, whitespace-collapsed name: customers are matched on this everywhere
|
||||
customerNameKey String
|
||||
email String?
|
||||
position Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
// Set once the terminal reports an open tab with this name; it is then no longer sent to devices
|
||||
deliveredAt DateTime?
|
||||
// A bar terminal, paired with a registration code
|
||||
model Device {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
name String
|
||||
tokenHash String @unique
|
||||
tokenPrefix String
|
||||
tokenExpiresAt DateTime @db.Timestamptz(3)
|
||||
lastSeenAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
revokedAt DateTime? @db.Timestamptz(3)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
tabEvents TabEvent[]
|
||||
settlements Settlement[]
|
||||
|
||||
@@index([venueId, revokedAt])
|
||||
}
|
||||
|
||||
model RegistrationCode {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
code String @unique
|
||||
expiresAt DateTime @db.Timestamptz(3)
|
||||
usedAt DateTime? @db.Timestamptz(3)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([venueId, customerNameKey])
|
||||
@@index([venueId])
|
||||
}
|
||||
|
||||
model TabReminder {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
tabId String
|
||||
customerName String
|
||||
email String
|
||||
amountInCents Int
|
||||
sentAt DateTime @default(now())
|
||||
status String @default("sent")
|
||||
// A regular of the venue. Tabs are matched to customers by name: the next sync opens a tab for a customer that has
|
||||
// none, and the customer's email is used for reminders
|
||||
model Customer {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
name String
|
||||
// Lowercased, whitespace-collapsed name: customers are matched on this everywhere
|
||||
nameKey String
|
||||
email String?
|
||||
position Int @default(0)
|
||||
// Null until the next sync opens their tab
|
||||
tabOpenedAt DateTime? @db.Timestamptz(3)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
reminders Reminder[]
|
||||
|
||||
@@index([venueId, sentAt])
|
||||
@@index([venueId, tabId])
|
||||
@@unique([venueId, nameKey])
|
||||
}
|
||||
|
||||
// What happened on a tab, as reported by the terminal. The id is generated by the terminal so a re-sent event is
|
||||
// stored, and applied to the tab state and stock, only once.
|
||||
model TabEvent {
|
||||
id String @id
|
||||
venueId String
|
||||
deviceId String
|
||||
// Null for stock_set, which is not about a tab
|
||||
tabId String?
|
||||
customerName String?
|
||||
// tab_opened | tab_renamed | tab_deleted | line_added | line_removed | settled | stock_set
|
||||
type String
|
||||
lineId String?
|
||||
// line_added / line_removed: the order on the terminal the drinks belong to
|
||||
orderId String?
|
||||
productId String?
|
||||
productName String?
|
||||
unitPriceInCents Int?
|
||||
// Signed: positive for line_added, negative for line_removed
|
||||
quantityDelta Int?
|
||||
// settled only
|
||||
amountInCents Int?
|
||||
paymentMethod String?
|
||||
commandId String?
|
||||
items Json?
|
||||
// stock_set: the counted stock, null to stop tracking
|
||||
stockSetTo Int?
|
||||
// product_created / product_updated: the product's fields as sent by the terminal
|
||||
productData Json?
|
||||
occurredAt DateTime
|
||||
receivedAt DateTime @default(now())
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
device Device @relation(fields: [deviceId], references: [id])
|
||||
|
||||
@@index([venueId, occurredAt])
|
||||
@@index([venueId, type, occurredAt])
|
||||
}
|
||||
|
||||
// The venue's products and their stock. The dashboard manages the catalog and the terminals get it on every sync;
|
||||
// the first terminal's own catalog was taken over once. The stock follows the terminals' line events and stock
|
||||
// counts and the dashboard's StockAdjustments.
|
||||
model Product {
|
||||
venueId String
|
||||
// Generated by the server, or by the terminal that reported it before the dashboard managed the catalog
|
||||
productId String
|
||||
name String
|
||||
category String?
|
||||
// The sell price: what the terminals charge
|
||||
priceInCents Int
|
||||
// What the bar pays for one; null when not entered. Kept on the dashboard only, the terminals never get it
|
||||
buyPriceInCents Int?
|
||||
// Null when stock is not tracked for this product. Can be negative: a sale is never blocked.
|
||||
stock Int?
|
||||
// When the stock was last counted; line events that happened before it are already in the count
|
||||
countedAt DateTime?
|
||||
// Warn when the stock drops to this level
|
||||
lowStockThreshold Int?
|
||||
// How many the bar wants in stock; the dashboard shows what to order to get there. Not sent to the terminals
|
||||
desiredStock Int?
|
||||
// When it was created or last changed
|
||||
reportedAt DateTime
|
||||
// Deleted on the dashboard: hidden on the terminals, kept because tabs and history refer to it
|
||||
archivedAt DateTime?
|
||||
imageId String?
|
||||
// When each field was last changed ({ name: ISO date, ... }): a change from the dashboard or an (offline)
|
||||
// terminal only wins over a later one per field, so edits of different fields never overwrite each other
|
||||
fieldUpdatedAt Json?
|
||||
|
||||
image ProductImage? @relation(fields: [imageId], references: [id])
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
|
||||
@@id([venueId, productId])
|
||||
}
|
||||
|
||||
// A stock change made on the dashboard. The server applies it right away. Older ones may still have been applied
|
||||
// by a terminal, which reported the outcome.
|
||||
model StockAdjustment {
|
||||
id String @id @default(uuid())
|
||||
venueId String
|
||||
productId String
|
||||
productName String
|
||||
// add (signed: a delivery, or breakage when negative) | set (a stock count)
|
||||
type String
|
||||
quantity Int
|
||||
note String?
|
||||
requestedBy String
|
||||
requestedById String
|
||||
// pending | applied | rejected | cancelled
|
||||
status String @default("pending")
|
||||
// The stock right after applying it
|
||||
stockAfter Int?
|
||||
result String?
|
||||
createdAt DateTime @default(now())
|
||||
processedAt DateTime?
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
|
||||
@@index([venueId, status, createdAt])
|
||||
@@index([venueId, productId, createdAt])
|
||||
}
|
||||
|
||||
// An open tab of the venue: the source of truth the terminals replace their own tabs with on every sync. Tabs are
|
||||
// never closed, only emptied by a settlement; tab_deleted removes one.
|
||||
// An open tab: the source of truth the terminals replace their own tabs with on every sync. Tabs are never closed,
|
||||
// only emptied by settlements; deleting one drops the drinks still on it
|
||||
model Tab {
|
||||
venueId String
|
||||
// Generated by the terminal that opened it
|
||||
id String
|
||||
// Generated by the terminal that opened it, or by the server for a customer's tab
|
||||
id String @id @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
customerName String
|
||||
openedAt DateTime
|
||||
openedAt DateTime @db.Timestamptz(3)
|
||||
// Bumped by every change, so the dashboard can refuse to settle a tab that changed since it was shown
|
||||
version Int @default(0)
|
||||
// [{ id, productId, productName, unitPriceInCents, units: [{ id, addedAt, removedAt }] }]
|
||||
lines Json
|
||||
updatedAt DateTime @updatedAt
|
||||
updatedAt DateTime @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
units TabUnit[]
|
||||
|
||||
@@id([venueId, id])
|
||||
@@index([venueId])
|
||||
}
|
||||
|
||||
// A settlement made on a terminal (id = the settled event's id) or on the dashboard. Terminals read them as a feed
|
||||
// after their cursor, which is the sequence number.
|
||||
// A paid tab, settled on a terminal (id = the settled event's id) or on the dashboard. Terminals read them as a feed
|
||||
// after their cursor, which is the sequence number
|
||||
model Settlement {
|
||||
id String @id
|
||||
sequence Int @unique @default(autoincrement())
|
||||
venueId String
|
||||
tabId String
|
||||
customerName String
|
||||
settledAt DateTime
|
||||
// cash | payconiq | payment_pending | other
|
||||
paymentMethod String
|
||||
amountInCents Int
|
||||
// The settled lines, in the shape of Tab.lines with each line's settled and removed units
|
||||
lines Json
|
||||
id String @id @db.Uuid
|
||||
sequence Int @unique @default(autoincrement())
|
||||
venueId String @db.Uuid
|
||||
// The tab may be deleted since
|
||||
tabId String @db.Uuid
|
||||
customerName String
|
||||
settledAt DateTime @db.Timestamptz(3)
|
||||
paymentMethod PaymentMethod
|
||||
// What was charged; the units are what the server had on the tab
|
||||
amountInCents Int
|
||||
// Who settled it: a terminal, or a dashboard user
|
||||
deviceId String?
|
||||
requestedBy String?
|
||||
commandId String?
|
||||
createdAt DateTime @default(now())
|
||||
deviceId String? @db.Uuid
|
||||
settledByUserId String? @db.Uuid
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id])
|
||||
device Device? @relation(fields: [deviceId], references: [id])
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
device Device? @relation(fields: [deviceId], references: [id], onDelete: SetNull)
|
||||
settledBy User? @relation(fields: [settledByUserId], references: [id], onDelete: SetNull)
|
||||
units TabUnit[]
|
||||
|
||||
@@index([venueId, sequence])
|
||||
@@index([venueId, settledAt])
|
||||
}
|
||||
|
||||
// A product picture, resized and cropped on upload. The id is the SHA-256 of the stored bytes, so the picture at
|
||||
// an address never changes and can be cached forever, and the same picture is stored once.
|
||||
// One drink ever rung up. It is on an open tab until a settlement takes it; a drink taken off again keeps its
|
||||
// removedAt and moves with the settlement too. Lines are the units sharing a lineId
|
||||
model TabUnit {
|
||||
// `<line_added event id>:<index>`, derived the same way on the terminals
|
||||
id String @id
|
||||
venueId String @db.Uuid
|
||||
// Exactly one of tabId and settlementId is set
|
||||
tabId String? @db.Uuid
|
||||
settlementId String? @db.Uuid
|
||||
lineId String
|
||||
productId String? @db.Uuid
|
||||
// As rung up: a later change to the product doesn't change what was sold
|
||||
productName String
|
||||
unitPriceInCents Int
|
||||
// The order on the terminal it was rung up in; null only for drinks settled on a terminal the server didn't have
|
||||
orderId String?
|
||||
addedAt DateTime @db.Timestamptz(3)
|
||||
removedAt DateTime? @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
tab Tab? @relation(fields: [tabId], references: [id], onDelete: Cascade)
|
||||
settlement Settlement? @relation(fields: [settlementId], references: [id], onDelete: Cascade)
|
||||
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([tabId])
|
||||
@@index([settlementId])
|
||||
@@index([venueId, addedAt])
|
||||
@@index([productId])
|
||||
}
|
||||
|
||||
// The venue's catalog and stock. The dashboard and the terminals both change products; per field the latest change
|
||||
// wins. The stock follows the drinks rung up, the terminals' counts and the dashboard's stock movements
|
||||
model Product {
|
||||
// Generated by the server, or by the terminal that created it offline
|
||||
id String @id @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
name String
|
||||
category String?
|
||||
// The sell price: what the terminals charge
|
||||
priceInCents Int
|
||||
// What the bar pays for one. Dashboard only, the terminals never get it
|
||||
buyPriceInCents Int?
|
||||
// Null when stock is not tracked. Can be negative: a sale is never blocked
|
||||
stock Int?
|
||||
// When the stock was last counted; drinks rung up before it are already in the count
|
||||
countedAt DateTime? @db.Timestamptz(3)
|
||||
// Warn when the stock drops to this level
|
||||
lowStockThreshold Int?
|
||||
// How many the bar wants in stock. Dashboard only
|
||||
desiredStock Int?
|
||||
imageId String?
|
||||
// Deleted: hidden on the terminals, kept because tabs and history refer to it
|
||||
archivedAt DateTime? @db.Timestamptz(3)
|
||||
// When each field was last changed ({ name: ISO date, ... }), so edits of different fields never overwrite each other
|
||||
fieldUpdatedAt Json @default("{}")
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
// The last change to what the terminals get (not the stock): part of the catalog version
|
||||
updatedAt DateTime @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
image ProductImage? @relation(fields: [imageId], references: [id], onDelete: SetNull)
|
||||
units TabUnit[]
|
||||
stockMovements StockMovement[]
|
||||
|
||||
@@index([venueId])
|
||||
}
|
||||
|
||||
// A product picture, resized and cropped on upload and stored on disk as <id>.webp. The id is the SHA-256 of the
|
||||
// bytes, so a picture's address never changes and the same picture is stored once
|
||||
model ProductImage {
|
||||
id String @id
|
||||
contentType String
|
||||
width Int
|
||||
height Int
|
||||
bytes Bytes
|
||||
createdAt DateTime @default(now())
|
||||
bytes Int
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
products Product[]
|
||||
}
|
||||
|
||||
// A stock change made on the dashboard: a delivery, breakage or count
|
||||
model StockMovement {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
productId String @db.Uuid
|
||||
type StockMovementType
|
||||
quantity Int
|
||||
stockAfter Int
|
||||
note String?
|
||||
userId String? @db.Uuid
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([venueId, createdAt])
|
||||
@@index([productId, createdAt])
|
||||
}
|
||||
|
||||
// What a terminal reported, as it sent it. The id is generated by the terminal, so an event that is sent again is
|
||||
// stored, and applied, only once
|
||||
model TabEvent {
|
||||
id String @id @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
deviceId String @db.Uuid
|
||||
type TerminalEventType
|
||||
// The event as sent, after validation
|
||||
payload Json
|
||||
occurredAt DateTime @db.Timestamptz(3)
|
||||
receivedAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([venueId, occurredAt])
|
||||
@@index([receivedAt])
|
||||
}
|
||||
|
||||
model Reminder {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
venueId String @db.Uuid
|
||||
// Null when the customer was removed since
|
||||
customerId String? @db.Uuid
|
||||
customerName String
|
||||
email String
|
||||
tabId String @db.Uuid
|
||||
amountInCents Int
|
||||
sentAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([venueId, sentAt])
|
||||
@@index([customerId])
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
// Null for actions that are not about one venue (purging, signing everyone out)
|
||||
venueId String? @db.Uuid
|
||||
actorType ActorType
|
||||
// A user or device id; null for the system
|
||||
actorId String? @db.Uuid
|
||||
action String
|
||||
details Json?
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
venue Venue? @relation(fields: [venueId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([venueId, createdAt])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user