Files
app-padel/apps/web/prisma/schema.prisma
Ivan 981783babb feat(db): add Prisma schema with all models
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>
2026-02-01 06:17:02 +00:00

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])
}