generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ── Organization ────────────────────────────────────────────────────────────── model Organization { id String @id @default(cuid()) name String slug String @unique logoUrl String? primaryColor String? stripeAccountId String? publicUrl String? localHostname String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt events AuctionEvent[] bidders Bidder[] staffUsers StaffUser[] } // ── Staff Users ─────────────────────────────────────────────────────────────── model StaffUser { id String @id @default(cuid()) organizationId String email String @unique name String role String // admin | event_manager | auctioneer | spotter | checkin_staff passwordHash String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id]) auditLogs AuditLog[] } // ── Events ──────────────────────────────────────────────────────────────────── model AuctionEvent { id String @id @default(cuid()) organizationId String name String slug String description String? venueAddress String? startAt DateTime endAt DateTime status String @default("draft") // draft | published | active | closed | archived timezone String @default("America/New_York") bannerImageUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id]) auctions Auction[] bidders BidderEventEnrollment[] invoices Invoice[] donations Donation[] paddleRaiseCampaigns PaddleRaiseCampaign[] auditLogs AuditLog[] @@unique([organizationId, slug]) } // ── Auctions ────────────────────────────────────────────────────────────────── model Auction { id String @id @default(cuid()) eventId String type String // live | silent name String status String @default("draft") // draft | active | paused | closed sortOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt event AuctionEvent @relation(fields: [eventId], references: [id]) items AuctionItem[] silentWindows SilentAuctionWindow[] } // ── Auction Items ───────────────────────────────────────────────────────────── model AuctionItem { id String @id @default(cuid()) auctionId String lotNumber String title String description String? donorName String? category String? fairMarketValue Decimal? openingBid Decimal @default(0) reservePrice Decimal? currentHighBid Decimal? currentHighBidderId String? bidIncrement Decimal @default(10) state String @default("preview") // preview | active | going_once | going_twice | sold | passed | closed pickupNotes String? sortOrder Int @default(0) silentWindowId String? softCloseEnabled Boolean @default(false) softCloseExtendMinutes Int @default(2) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt auction Auction @relation(fields: [auctionId], references: [id]) silentWindow SilentAuctionWindow? @relation(fields: [silentWindowId], references: [id]) currentHighBidder Bidder? @relation("CurrentHighBids", fields: [currentHighBidderId], references: [id]) media ItemMedia[] bids Bid[] @@unique([auctionId, lotNumber]) } model ItemMedia { id String @id @default(cuid()) itemId String mediaType String // image | video | document | embed url String thumbnailUrl String? caption String? sortOrder Int @default(0) createdAt DateTime @default(now()) item AuctionItem @relation(fields: [itemId], references: [id], onDelete: Cascade) } model SilentAuctionWindow { id String @id @default(cuid()) auctionId String name String opensAt DateTime closesAt DateTime softCloseEnabled Boolean @default(false) softCloseExtendMinutes Int @default(2) status String @default("pending") // pending | open | closed auction Auction @relation(fields: [auctionId], references: [id]) items AuctionItem[] } // ── Bidders ─────────────────────────────────────────────────────────────────── model Bidder { id String @id @default(cuid()) organizationId String email String? phone String? firstName String lastName String paymentMethodOnFile Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id]) authMethods BidderAuthMethod[] eventEnrollments BidderEventEnrollment[] bids Bid[] currentHighBids AuctionItem[] @relation("CurrentHighBids") invoices Invoice[] donations Donation[] deviceSessions DeviceSession[] notifications Notification[] } model BidderAuthMethod { id String @id @default(cuid()) bidderId String type String // email_magic_link | sms_otp identifier String verifiedAt DateTime? createdAt DateTime @default(now()) bidder Bidder @relation(fields: [bidderId], references: [id], onDelete: Cascade) @@unique([type, identifier]) } model BidderEventEnrollment { id String @id @default(cuid()) bidderId String eventId String paddleNumber String? tableAssignment String? notes String? checkInStatus String @default("pending") // pending | checked_in checkInAt DateTime? createdAt DateTime @default(now()) bidder Bidder @relation(fields: [bidderId], references: [id]) event AuctionEvent @relation(fields: [eventId], references: [id]) @@unique([bidderId, eventId]) @@unique([eventId, paddleNumber]) } // ── Bids ────────────────────────────────────────────────────────────────────── model Bid { id String @id @default(cuid()) itemId String bidderId String amount Decimal clientCreatedAt DateTime serverReceivedAt DateTime @default(now()) originMode String // public | local_dns | local_ip | offline_queue syncStatus String @default("synced") // synced | pending | conflict | rejected deviceId String clientSeq Int isWinning Boolean @default(false) createdAt DateTime @default(now()) item AuctionItem @relation(fields: [itemId], references: [id]) bidder Bidder @relation(fields: [bidderId], references: [id]) @@index([itemId, createdAt]) @@index([bidderId]) } // ── Paddle Raise & Donations ────────────────────────────────────────────────── model PaddleRaiseCampaign { id String @id @default(cuid()) eventId String name String goal Decimal? totalRaised Decimal @default(0) tiers Json @default("[]") // number[] isActive Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt event AuctionEvent @relation(fields: [eventId], references: [id]) donations Donation[] } model Donation { id String @id @default(cuid()) eventId String bidderId String? campaignId String? amount Decimal anonymous Boolean @default(false) stripePaymentIntentId String? createdAt DateTime @default(now()) event AuctionEvent @relation(fields: [eventId], references: [id]) bidder Bidder? @relation(fields: [bidderId], references: [id]) campaign PaddleRaiseCampaign? @relation(fields: [campaignId], references: [id]) } // ── Invoices & Payments ─────────────────────────────────────────────────────── model Invoice { id String @id @default(cuid()) bidderId String eventId String stripeInvoiceId String? totalAmount Decimal @default(0) paidAmount Decimal @default(0) status String @default("draft") // draft | open | paid | partially_paid | void createdAt DateTime @default(now()) updatedAt DateTime @updatedAt bidder Bidder @relation(fields: [bidderId], references: [id]) event AuctionEvent @relation(fields: [eventId], references: [id]) payments Payment[] } model Payment { id String @id @default(cuid()) invoiceId String stripePaymentIntentId String? amount Decimal currency String @default("usd") status String // pending | succeeded | failed | refunded createdAt DateTime @default(now()) invoice Invoice @relation(fields: [invoiceId], references: [id]) } // ── Device Sessions ─────────────────────────────────────────────────────────── model DeviceSession { id String @id @default(cuid()) bidderId String deviceId String @unique userAgent String? lastSeenAt DateTime @default(now()) createdAt DateTime @default(now()) bidder Bidder @relation(fields: [bidderId], references: [id]) } // ── Audit Log ───────────────────────────────────────────────────────────────── model AuditLog { id String @id @default(cuid()) eventId String? staffUserId String? action String entityType String entityId String payload Json? originMode String? // mirrors bid origin when relevant ipAddress String? createdAt DateTime @default(now()) event AuctionEvent? @relation(fields: [eventId], references: [id]) staffUser StaffUser? @relation(fields: [staffUserId], references: [id]) @@index([eventId, createdAt]) @@index([entityType, entityId]) } // ── Notifications ───────────────────────────────────────────────────────────── model Notification { id String @id @default(cuid()) bidderId String type String // outbid | item_closed | checkout_ready | otp | receipt channel String // in_app | push | email | sms payload Json sentAt DateTime? readAt DateTime? createdAt DateTime @default(now()) bidder Bidder @relation(fields: [bidderId], references: [id]) }