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