Configure Prisma ORM with PostgreSQL database schema including: - Organization, Site, Court models for multi-tenancy - User with role-based access and Client for customers - Booking and Payment models for reservations - MembershipPlan and Membership for subscriptions - Product, Sale, SaleItem, CashRegister for POS - Tournament, TournamentInscription, Match for competitions - All necessary enums, relations, indexes, and cascading deletes - Prisma client singleton for Next.js Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
547 lines
16 KiB
Plaintext
547 lines
16 KiB
Plaintext
// This is your Prisma schema file,
|
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// =============================================================================
|
|
// ENUMS
|
|
// =============================================================================
|
|
|
|
enum UserRole {
|
|
SUPER_ADMIN
|
|
ORG_ADMIN
|
|
SITE_ADMIN
|
|
RECEPTIONIST
|
|
TRAINER
|
|
}
|
|
|
|
enum CourtType {
|
|
INDOOR
|
|
OUTDOOR
|
|
COVERED
|
|
}
|
|
|
|
enum CourtStatus {
|
|
AVAILABLE
|
|
MAINTENANCE
|
|
CLOSED
|
|
}
|
|
|
|
enum BookingStatus {
|
|
PENDING
|
|
CONFIRMED
|
|
CANCELLED
|
|
COMPLETED
|
|
NO_SHOW
|
|
}
|
|
|
|
enum PaymentType {
|
|
CASH
|
|
CARD
|
|
TRANSFER
|
|
MEMBERSHIP
|
|
FREE
|
|
}
|
|
|
|
enum MembershipStatus {
|
|
ACTIVE
|
|
EXPIRED
|
|
CANCELLED
|
|
SUSPENDED
|
|
}
|
|
|
|
enum TournamentType {
|
|
AMERICANO
|
|
MEXICANO
|
|
BRACKET
|
|
ROUND_ROBIN
|
|
LEAGUE
|
|
}
|
|
|
|
enum TournamentStatus {
|
|
DRAFT
|
|
REGISTRATION_OPEN
|
|
REGISTRATION_CLOSED
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
CANCELLED
|
|
}
|
|
|
|
enum MatchStatus {
|
|
SCHEDULED
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
CANCELLED
|
|
WALKOVER
|
|
}
|
|
|
|
// =============================================================================
|
|
// ORGANIZATION & SITES
|
|
// =============================================================================
|
|
|
|
model Organization {
|
|
id String @id @default(cuid())
|
|
name String
|
|
slug String @unique
|
|
logo String?
|
|
settings Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
sites Site[]
|
|
users User[]
|
|
clients Client[]
|
|
memberships MembershipPlan[]
|
|
products Product[]
|
|
categories ProductCategory[]
|
|
tournaments Tournament[]
|
|
|
|
@@index([slug])
|
|
}
|
|
|
|
model Site {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
name String
|
|
slug String
|
|
address String?
|
|
phone String?
|
|
email String?
|
|
timezone String @default("Europe/Madrid")
|
|
openTime String @default("08:00")
|
|
closeTime String @default("22:00")
|
|
settings Json @default("{}")
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
courts Court[]
|
|
users User[]
|
|
bookings Booking[]
|
|
cashRegisters CashRegister[]
|
|
tournaments Tournament[]
|
|
|
|
@@unique([organizationId, slug])
|
|
@@index([organizationId])
|
|
@@index([slug])
|
|
}
|
|
|
|
model Court {
|
|
id String @id @default(cuid())
|
|
siteId String
|
|
name String
|
|
type CourtType @default(INDOOR)
|
|
status CourtStatus @default(AVAILABLE)
|
|
pricePerHour Decimal @db.Decimal(10, 2)
|
|
description String?
|
|
features String[] @default([])
|
|
displayOrder Int @default(0)
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
bookings Booking[]
|
|
matches Match[]
|
|
|
|
@@index([siteId])
|
|
@@index([status])
|
|
}
|
|
|
|
// =============================================================================
|
|
// USERS & CLIENTS
|
|
// =============================================================================
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
email String
|
|
password String
|
|
firstName String
|
|
lastName String
|
|
role UserRole @default(RECEPTIONIST)
|
|
phone String?
|
|
avatar String?
|
|
siteIds String[] @default([])
|
|
isActive Boolean @default(true)
|
|
lastLogin DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
sites Site[]
|
|
bookingsCreated Booking[] @relation("BookingCreatedBy")
|
|
salesCreated Sale[] @relation("SaleCreatedBy")
|
|
cashRegisters CashRegister[]
|
|
|
|
@@unique([organizationId, email])
|
|
@@index([organizationId])
|
|
@@index([email])
|
|
@@index([role])
|
|
}
|
|
|
|
model Client {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
email String?
|
|
phone String?
|
|
firstName String
|
|
lastName String
|
|
dni String?
|
|
dateOfBirth DateTime?
|
|
address String?
|
|
notes String?
|
|
avatar String?
|
|
level String?
|
|
preferredHand String?
|
|
emergencyContact String?
|
|
emergencyPhone String?
|
|
tags String[] @default([])
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
memberships Membership[]
|
|
bookings Booking[]
|
|
payments Payment[]
|
|
sales Sale[]
|
|
inscriptions TournamentInscription[]
|
|
|
|
@@unique([organizationId, email])
|
|
@@unique([organizationId, dni])
|
|
@@index([organizationId])
|
|
@@index([email])
|
|
@@index([phone])
|
|
@@index([lastName, firstName])
|
|
}
|
|
|
|
// =============================================================================
|
|
// BOOKINGS & PAYMENTS
|
|
// =============================================================================
|
|
|
|
model Booking {
|
|
id String @id @default(cuid())
|
|
siteId String
|
|
courtId String
|
|
clientId String?
|
|
createdById String?
|
|
startTime DateTime
|
|
endTime DateTime
|
|
status BookingStatus @default(PENDING)
|
|
paymentType PaymentType @default(CASH)
|
|
totalPrice Decimal @db.Decimal(10, 2)
|
|
paidAmount Decimal @default(0) @db.Decimal(10, 2)
|
|
notes String?
|
|
playerNames String[] @default([])
|
|
isRecurring Boolean @default(false)
|
|
recurringId String?
|
|
cancelledAt DateTime?
|
|
cancelReason String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
|
client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull)
|
|
createdBy User? @relation("BookingCreatedBy", fields: [createdById], references: [id], onDelete: SetNull)
|
|
payments Payment[]
|
|
match Match?
|
|
|
|
@@index([siteId])
|
|
@@index([courtId])
|
|
@@index([clientId])
|
|
@@index([startTime, endTime])
|
|
@@index([status])
|
|
@@index([recurringId])
|
|
}
|
|
|
|
model Payment {
|
|
id String @id @default(cuid())
|
|
bookingId String?
|
|
clientId String?
|
|
saleId String?
|
|
membershipId String?
|
|
amount Decimal @db.Decimal(10, 2)
|
|
paymentType PaymentType
|
|
reference String?
|
|
notes String?
|
|
cashRegisterId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: SetNull)
|
|
client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull)
|
|
sale Sale? @relation(fields: [saleId], references: [id], onDelete: SetNull)
|
|
membership Membership? @relation(fields: [membershipId], references: [id], onDelete: SetNull)
|
|
cashRegister CashRegister? @relation(fields: [cashRegisterId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([bookingId])
|
|
@@index([clientId])
|
|
@@index([saleId])
|
|
@@index([membershipId])
|
|
@@index([cashRegisterId])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
// =============================================================================
|
|
// MEMBERSHIPS
|
|
// =============================================================================
|
|
|
|
model MembershipPlan {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
name String
|
|
description String?
|
|
price Decimal @db.Decimal(10, 2)
|
|
durationMonths Int
|
|
courtHours Int?
|
|
discountPercent Decimal? @db.Decimal(5, 2)
|
|
benefits String[] @default([])
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
memberships Membership[]
|
|
|
|
@@index([organizationId])
|
|
@@index([isActive])
|
|
}
|
|
|
|
model Membership {
|
|
id String @id @default(cuid())
|
|
planId String
|
|
clientId String
|
|
startDate DateTime
|
|
endDate DateTime
|
|
status MembershipStatus @default(ACTIVE)
|
|
remainingHours Int?
|
|
autoRenew Boolean @default(false)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
plan MembershipPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
payments Payment[]
|
|
|
|
@@index([planId])
|
|
@@index([clientId])
|
|
@@index([status])
|
|
@@index([endDate])
|
|
}
|
|
|
|
// =============================================================================
|
|
// PRODUCTS & SALES
|
|
// =============================================================================
|
|
|
|
model ProductCategory {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
name String
|
|
description String?
|
|
displayOrder Int @default(0)
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
products Product[]
|
|
|
|
@@unique([organizationId, name])
|
|
@@index([organizationId])
|
|
}
|
|
|
|
model Product {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
categoryId String?
|
|
name String
|
|
description String?
|
|
sku String?
|
|
price Decimal @db.Decimal(10, 2)
|
|
costPrice Decimal? @db.Decimal(10, 2)
|
|
stock Int @default(0)
|
|
minStock Int @default(0)
|
|
trackStock Boolean @default(true)
|
|
image String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
category ProductCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
|
saleItems SaleItem[]
|
|
|
|
@@unique([organizationId, sku])
|
|
@@index([organizationId])
|
|
@@index([categoryId])
|
|
@@index([name])
|
|
@@index([isActive])
|
|
}
|
|
|
|
model Sale {
|
|
id String @id @default(cuid())
|
|
clientId String?
|
|
createdById String?
|
|
cashRegisterId String?
|
|
subtotal Decimal @db.Decimal(10, 2)
|
|
discount Decimal @default(0) @db.Decimal(10, 2)
|
|
tax Decimal @default(0) @db.Decimal(10, 2)
|
|
total Decimal @db.Decimal(10, 2)
|
|
paymentType PaymentType
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull)
|
|
createdBy User? @relation("SaleCreatedBy", fields: [createdById], references: [id], onDelete: SetNull)
|
|
cashRegister CashRegister? @relation(fields: [cashRegisterId], references: [id], onDelete: SetNull)
|
|
items SaleItem[]
|
|
payments Payment[]
|
|
|
|
@@index([clientId])
|
|
@@index([createdById])
|
|
@@index([cashRegisterId])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model SaleItem {
|
|
id String @id @default(cuid())
|
|
saleId String
|
|
productId String
|
|
quantity Int
|
|
unitPrice Decimal @db.Decimal(10, 2)
|
|
subtotal Decimal @db.Decimal(10, 2)
|
|
createdAt DateTime @default(now())
|
|
|
|
sale Sale @relation(fields: [saleId], references: [id], onDelete: Cascade)
|
|
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([saleId])
|
|
@@index([productId])
|
|
}
|
|
|
|
model CashRegister {
|
|
id String @id @default(cuid())
|
|
siteId String
|
|
userId String
|
|
openedAt DateTime @default(now())
|
|
closedAt DateTime?
|
|
openingAmount Decimal @db.Decimal(10, 2)
|
|
closingAmount Decimal? @db.Decimal(10, 2)
|
|
expectedAmount Decimal? @db.Decimal(10, 2)
|
|
difference Decimal? @db.Decimal(10, 2)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
sales Sale[]
|
|
payments Payment[]
|
|
|
|
@@index([siteId])
|
|
@@index([userId])
|
|
@@index([openedAt])
|
|
@@index([closedAt])
|
|
}
|
|
|
|
// =============================================================================
|
|
// TOURNAMENTS
|
|
// =============================================================================
|
|
|
|
model Tournament {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
siteId String?
|
|
name String
|
|
description String?
|
|
type TournamentType @default(AMERICANO)
|
|
status TournamentStatus @default(DRAFT)
|
|
startDate DateTime
|
|
endDate DateTime?
|
|
maxPlayers Int?
|
|
entryFee Decimal? @db.Decimal(10, 2)
|
|
prizePool Decimal? @db.Decimal(10, 2)
|
|
rules String?
|
|
settings Json @default("{}")
|
|
image String?
|
|
isPublic Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
site Site? @relation(fields: [siteId], references: [id], onDelete: SetNull)
|
|
inscriptions TournamentInscription[]
|
|
matches Match[]
|
|
|
|
@@index([organizationId])
|
|
@@index([siteId])
|
|
@@index([status])
|
|
@@index([startDate])
|
|
@@index([type])
|
|
}
|
|
|
|
model TournamentInscription {
|
|
id String @id @default(cuid())
|
|
tournamentId String
|
|
clientId String
|
|
partnerId String?
|
|
teamName String?
|
|
seedNumber Int?
|
|
paidAmount Decimal @default(0) @db.Decimal(10, 2)
|
|
isPaid Boolean @default(false)
|
|
notes String?
|
|
registeredAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([tournamentId, clientId])
|
|
@@index([tournamentId])
|
|
@@index([clientId])
|
|
}
|
|
|
|
model Match {
|
|
id String @id @default(cuid())
|
|
tournamentId String
|
|
bookingId String? @unique
|
|
courtId String?
|
|
round Int
|
|
position Int
|
|
scheduledAt DateTime?
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
status MatchStatus @default(SCHEDULED)
|
|
team1Players String[] @default([])
|
|
team2Players String[] @default([])
|
|
team1Score Int[] @default([])
|
|
team2Score Int[] @default([])
|
|
winnerId String?
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: SetNull)
|
|
court Court? @relation(fields: [courtId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([tournamentId])
|
|
@@index([courtId])
|
|
@@index([status])
|
|
@@index([round, position])
|
|
@@index([scheduledAt])
|
|
}
|