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>
This commit is contained in:
4
apps/web/.env.example
Normal file
4
apps/web/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
DATABASE_URL="postgresql://user:password@localhost:5432/padel_pro?schema=public"
|
||||
NEXTAUTH_SECRET="your-secret-key-here"
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
||||
18
apps/web/lib/db.ts
Normal file
18
apps/web/lib/db.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var prisma: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
export const db =
|
||||
globalThis.prisma ||
|
||||
new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalThis.prisma = db;
|
||||
}
|
||||
|
||||
export default db;
|
||||
@@ -7,9 +7,13 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --noEmit"
|
||||
"type-check": "tsc --noEmit",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
"db:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.10.0",
|
||||
"next": "14.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
@@ -20,6 +24,7 @@
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.35",
|
||||
"prisma": "^5.10.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
||||
546
apps/web/prisma/schema.prisma
Normal file
546
apps/web/prisma/schema.prisma
Normal file
@@ -0,0 +1,546 @@
|
||||
// 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])
|
||||
}
|
||||
Reference in New Issue
Block a user