Compare commits
9 Commits
fd4d8b7abd
...
ui-redesig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
000277b312 | ||
|
|
7a3452c2d1 | ||
|
|
d6e32f3e84 | ||
|
|
82dbc8ad99 | ||
|
|
fb19458ddf | ||
|
|
c63f0f2109 | ||
|
|
f1a7e6c24a | ||
|
|
c63b5a0a07 | ||
|
|
503c29613b |
211
.interface-design/system.md
Normal file
211
.interface-design/system.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Design System - Playzer
|
||||
|
||||
## Direction
|
||||
|
||||
**Feel:** Technical but warm. Like a well-organized sports facility — functional, clean, but with life. Not cold like a trading terminal, not playful like a toy app.
|
||||
|
||||
**Users:** Owners and employees managing sports complex bookings. They're juggling schedules, phone calls, and walk-ins. The interface should stay out of the way while they're working.
|
||||
|
||||
---
|
||||
|
||||
## Domain
|
||||
|
||||
**Concepts:**
|
||||
- Turno (booking slot) — A scheduled time block
|
||||
- Cancha (court) — Sports court/pitch
|
||||
- Horario — Time slot
|
||||
- Complex — The sports facility
|
||||
- Estado — Booking status (confirmed, cancelled, etc.)
|
||||
|
||||
**Color world:**
|
||||
- Court green (grass/tennis)
|
||||
- Clay tan (paddle court)
|
||||
- Pool blue (swimming)
|
||||
- Wood grain (locker rooms)
|
||||
- Concrete gray (tech floor)
|
||||
|
||||
**Signature:**
|
||||
- Status pills use subtle borders + background tint, not bold badges
|
||||
- Status communicates without demanding attention — whisper-level hierarchy
|
||||
|
||||
---
|
||||
|
||||
## Decisions
|
||||
|
||||
### Depth
|
||||
**Approach:** Borders-only with surface elevation shifts
|
||||
- No drop shadows
|
||||
- Higher elevation = slightly lighter surface (in dark: lighter = higher)
|
||||
- Subtle rgba borders define edges quietly
|
||||
|
||||
### Spacing
|
||||
**Base unit:** 4px
|
||||
- Micro: 2-4px (icon gaps)
|
||||
- Component: 8-12px (inside buttons, badges)
|
||||
- Section: 16-24px (between groups)
|
||||
- Major: 32-40px (between distinct areas)
|
||||
|
||||
### Typography
|
||||
**Font:** Geist Variable
|
||||
**Scale:**
|
||||
- Display: 28px / semibold / -0.02em tracking
|
||||
- Heading: 20px / semibold / -0.01em tracking
|
||||
- Body: 14px / normal / 0 tracking
|
||||
- Label: 12px / medium / 0 tracking
|
||||
- Caption: 11px / normal / 0.02em tracking
|
||||
|
||||
### Border Radius
|
||||
**Scale:**
|
||||
- `sm`: 4px (inputs, small badges)
|
||||
- `md`: 6px (buttons, cards)
|
||||
- `lg`: 10px (dialogs, large cards)
|
||||
- `xl`: 14px (modals)
|
||||
|
||||
### Borders
|
||||
**Progression:**
|
||||
- Subtle: `oklch(1 0 0 / 8%)` (background edges)
|
||||
- Default: `oklch(1 0 0 / 12%)` (card borders)
|
||||
- Emphasis: `oklch(1 0 0 / 20%)` (active states)
|
||||
|
||||
### Status Semantic Colors
|
||||
**Light mode:**
|
||||
- Confirmed: `bg-emerald-50 border-emerald-200 text-emerald-700`
|
||||
- Pending: `bg-amber-50 border-amber-200 text-amber-700`
|
||||
- Cancelled: `bg-rose-50 border-rose-200 text-rose-700`
|
||||
- No-show: `bg-orange-50 border-orange-200 text-orange-700`
|
||||
- Completed: `bg-sky-50 border-sky-200 text-sky-700`
|
||||
|
||||
**Dark mode:**
|
||||
- Slight desaturation, lower opacity backgrounds
|
||||
|
||||
---
|
||||
|
||||
## Patterns
|
||||
|
||||
### Card
|
||||
- Background: `bg-card`
|
||||
- Border: `border` token
|
||||
- Radius: `md`
|
||||
- Padding: `p-4` (compact) / `p-5` (default)
|
||||
|
||||
### Status Badge
|
||||
```tsx
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs font-medium">
|
||||
{icon}
|
||||
{label}
|
||||
</span>
|
||||
```
|
||||
|
||||
### Input
|
||||
- Background: `bg-input` (darker than surface)
|
||||
- No heavy borders by default
|
||||
- Focus ring via `ring-3 ring-ring/50`
|
||||
|
||||
### Layout
|
||||
- Sidebar: Same background as main, subtle border separation
|
||||
- Content: Max-width 6xl, centered
|
||||
- Padding: `px-4 sm:px-6`
|
||||
|
||||
---
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Status tokens in CSS variables
|
||||
- [x] Refactor status badges to use semantic classes
|
||||
- [x] StatusBadge component created
|
||||
- [x] Home panel redesign with metrics + timeline
|
||||
|
||||
---
|
||||
|
||||
## Home Page Patterns (v2)
|
||||
|
||||
### Structure
|
||||
- Header + URL Card: 2-column grid (lg:)
|
||||
- Main: Timeline de reservas
|
||||
- Sidebar: Stats grid consolidado (sticky)
|
||||
|
||||
### URL Card (destacada)
|
||||
```tsx
|
||||
<div className="group relative overflow-hidden rounded-2xl border border-blue-200/60 bg-blue-50/50 p-5">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-50/80 to-blue-100/30" />
|
||||
<div className="relative flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.28em] text-blue-600/70">
|
||||
Reserva online
|
||||
</p>
|
||||
<a href={url} className="mt-1 block truncate text-sm font-medium text-blue-700 underline-offset-2 hover:underline">
|
||||
{url}
|
||||
</a>
|
||||
</div>
|
||||
<button className="relative flex shrink-0 items-center gap-2 rounded-lg bg-blue-500/10 px-3 py-2 text-sm font-medium text-blue-700 transition-colors hover:bg-blue-500/20">
|
||||
<Copy className="size-4" />
|
||||
<span>Copiar</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Stats Pills (header)
|
||||
```tsx
|
||||
<span className="rounded-full border border-emerald-200/60 bg-emerald-50/60 px-3 py-1 text-xs font-medium text-emerald-700">
|
||||
{count} hoy
|
||||
</span>
|
||||
```
|
||||
|
||||
### Stats Sidebar (grid 2x2)
|
||||
```tsx
|
||||
<section className="space-y-3 rounded-2xl border border-border/70 bg-card p-4">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-[0.28em] text-muted-foreground">
|
||||
Resumen
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="rounded-xl border border-border/50 bg-background/80 p-3">
|
||||
<p className="text-xs text-muted-foreground">Total</p>
|
||||
<p className="mt-1 text-xl font-semibold tracking-tight">{value}</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-emerald-200/40 bg-emerald-50/40 p-3">
|
||||
<p className="text-xs text-emerald-700/80">Hoy</p>
|
||||
<p className="mt-1 text-xl font-semibold tracking-tight text-emerald-700">{value}</p>
|
||||
</div>
|
||||
{/* ... confirmed (blue), completed (sky) */}
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
### Timeline Item (v2 - mejorado)
|
||||
```tsx
|
||||
<div className={[
|
||||
'group flex items-center justify-between gap-3 rounded-xl border border-border/70 bg-card/60 p-3.5 transition-all hover:bg-muted/50',
|
||||
isPast && 'opacity-50',
|
||||
isNow && 'border-l-2 border-l-emerald-500 bg-emerald-50/30',
|
||||
].join(' ')}>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="flex w-14 flex-col items-center">
|
||||
<span className="text-sm font-medium tabular-nums">{startTime}</span>
|
||||
<span className="text-[11px] text-muted-foreground">{endTime}</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">{courtName}</p>
|
||||
<p className="truncate text-xs text-muted-foreground">{customer}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<StatusBadge status={...} />
|
||||
<div className="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<Button size="icon-xs" variant="ghost" title="Marcar cumplida"><Check className="size-3.5" /></Button>
|
||||
<Button size="icon-xs" variant="ghost" title="Cancelar">×</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Date Group Header
|
||||
```tsx
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-sm font-semibold capitalize">{dateLabel}</h2>
|
||||
<div className="h-px flex-1 border-t border-dashed border-border/40" />
|
||||
<span className="shrink-0 rounded-full border border-border/70 bg-background px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground">
|
||||
{count}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
114
AGENTS.md
114
AGENTS.md
@@ -33,62 +33,6 @@ packages/api-contract/ # Shared Zod schemas, types, route definitions
|
||||
2. Implement handler in `apps/backend`
|
||||
3. Consume from `apps/frontend` via workspace import `@repo/api-contract`
|
||||
|
||||
## Endpoint Pattern
|
||||
|
||||
Every new endpoint must follow the **Result pattern** with the **validate helper**.
|
||||
|
||||
### Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `apps/backend/src/lib/result.ts` | `Result<T>` type, `ok()`, `err()` |
|
||||
| `apps/backend/src/lib/errors.ts` | `AppError` discriminated union, `Errors` factory |
|
||||
| `apps/backend/src/lib/http/handle-result.ts` | `handleResult()` — bridges `Result` to HTTP |
|
||||
| `apps/backend/src/lib/http/validate.ts` | `validate.json()`, `.query()`, `.param()`, `.header()`, `.form()` |
|
||||
|
||||
### Layers
|
||||
|
||||
**1. Route** — Use `validate.json(schema)`, `validate.param(schema)`, etc. as Hono middleware.
|
||||
|
||||
```typescript
|
||||
import { validate } from '@/lib/http/validate';
|
||||
import { z } from 'zod';
|
||||
|
||||
const idParams = z.object({ id: z.string() });
|
||||
router.post('/', validate.json(createSchema), createHandler);
|
||||
router.get('/:id', validate.param(idParams), getHandler);
|
||||
```
|
||||
|
||||
**2. Service** — Return `Result<T>`. Use `ok(value)` on success, `err(Errors.*(...))` on failure.
|
||||
|
||||
```typescript
|
||||
import { Result, ok, err } from '@/lib/result';
|
||||
import { Errors } from '@/lib/errors';
|
||||
|
||||
export async function doSomething(input: Input): Promise<Result<Output>> {
|
||||
if (conflict) return err(Errors.conflict('Already exists'));
|
||||
return ok(result);
|
||||
}
|
||||
```
|
||||
|
||||
**3. Handler** — Call the service and pass the `Result` to `handleResult`.
|
||||
|
||||
```typescript
|
||||
import { handleResult } from '@/lib/http/handle-result';
|
||||
import { doSomething } from './service';
|
||||
|
||||
export async function myHandler(c: AppContext) {
|
||||
const payload = c.req.valid('json') as Input;
|
||||
return handleResult(c, await doSomething(payload), 201);
|
||||
}
|
||||
```
|
||||
|
||||
### Never
|
||||
|
||||
- ❌ Throw custom error classes from services
|
||||
- ❌ Use try/catch in handlers for business logic errors
|
||||
- ❌ Use `zValidator` directly — always use `validate.*`
|
||||
|
||||
## Authentication (Better Auth)
|
||||
|
||||
The project uses **Better Auth** for session management, replacing the legacy Supabase Auth.
|
||||
@@ -249,61 +193,3 @@ await getAvailability('my-club', { date: '2026-04-20' });
|
||||
1. Create `lib/api/resources/[resource].ts`
|
||||
2. Export functions using `http` from `../http`
|
||||
3. Add exports in `lib/api/index.ts`
|
||||
|
||||
## Email Templates
|
||||
|
||||
Todos los emails deben usar la misma estética. El layout compartido está en `apps/backend/src/emails/booking-confirmation.ts`.
|
||||
|
||||
### Layout (`wrapLayout`)
|
||||
|
||||
Exportado como `wrapLayout(content)`. Proporciona:
|
||||
- Fondo: `#edf7f4`
|
||||
- Card blanca: `max-width: 520px`, `border-radius: 28px`, `box-shadow: 0 24px 70px rgba(15,23,42,0.12)`, `border: 1px solid rgba(5,9,20,0.1)`
|
||||
- Playzer favicon: `{APP_BASE_URL}/playzer-favicon-512-transparent.png` (está en `apps/frontend/public/`)
|
||||
- **Sin `min-height: 100vh`**: la card empieza arriba, no centrada verticalmente
|
||||
|
||||
```typescript
|
||||
import { wrapLayout } from '@/emails/booking-confirmation';
|
||||
|
||||
const html = wrapLayout(`<tr>...contenido...</tr>`);
|
||||
```
|
||||
|
||||
### Estructura de cada email
|
||||
|
||||
| Sección | Descripción |
|
||||
|---------|-------------|
|
||||
| **Header** | Dos columnas: badge pill a la izquierda + Playzer (favicon + texto) a la derecha. El badge usa `border-radius: 999px`, `padding: 4px 12px`, `font-size: 11px`, `font-weight: 700`, `letter-spacing: 0.14em`, `text-transform: uppercase`. |
|
||||
| **Card fecha/hora** | Fondo `#f0fdf4`, borde `1px solid rgba(5,150,105,0.3)`, `border-radius: 24px`. Siempre verde aunque el email sea de cancelación. |
|
||||
| **Grilla detalles** | `border: 1px solid #e5e7eb`, `border-radius: 22px`, dos celdas de 50% con `border-right` en la primera. |
|
||||
| **Botón CTA** | Tabla con fondo `#059669`, `border-radius: 12px`, link blanco con `padding: 14px 32px`. |
|
||||
| **Pie** | Sin pie de marca. Solo texto secundario opcional centrado si es necesario. |
|
||||
| **Sin botones de acción** | Los emails de booking **no** incluyen los botones "Compartir por WhatsApp" ni "Hacer otra reserva". |
|
||||
|
||||
### Padding estándar
|
||||
|
||||
| Ubicación | Valor |
|
||||
|-----------|-------|
|
||||
| Wrapper (outer `<td>`) | `padding: 24px 12px` |
|
||||
| Primer `<td>` del contenido (header) | `padding: 24px 20px 16px` |
|
||||
| `<td>` intermedios (cards, texto) | `padding: 0 20px 16px` |
|
||||
| Último `<td>` del contenido | `padding: 0 20px 24px` |
|
||||
|
||||
### Colores de badges según estado
|
||||
|
||||
| Estado | Fondo badge | Texto badge |
|
||||
|--------|-------------|-------------|
|
||||
| Confirmado | `#f0fdf4` | `#15803d` |
|
||||
| Cancelado | `#fef2f2` | `#dc2626` |
|
||||
| No concretado | `#fffbeb` | `#d97706` |
|
||||
| Neutro (verificación, etc.) | `#f4f4f5` | `#71717a` |
|
||||
|
||||
### Archivos de templates
|
||||
|
||||
| Archivo | Templates |
|
||||
|---------|-----------|
|
||||
| `apps/backend/src/emails/booking-confirmation.ts` | `bookingConfirmationHtml`, `bookingCancelledHtml`, `bookingNoShowHtml` + exporta `wrapLayout` |
|
||||
| `apps/backend/src/lib/auth.ts` | Email de verificación de Better Auth (usa `wrapLayout` inline) |
|
||||
|
||||
### Regla general
|
||||
|
||||
Para emails nuevos: importar `wrapLayout`, construir el HTML interno con `<tr>`s, usar la misma estructura de cabecera (badge + Playzer), y nunca incluir el pie "Playzer — Reserva de canchas online". Usar `APP_BASE_URL` para construir URLs absolutas al logo.
|
||||
|
||||
18
Dockerfile
18
Dockerfile
@@ -32,19 +32,25 @@ COPY packages/api-contract/package.json packages/api-contract/
|
||||
RUN bun install
|
||||
|
||||
# =============================================================================
|
||||
# Stage 4: Build Backend (prisma generate + tsc)
|
||||
# Stage 4: Prisma Generate
|
||||
# =============================================================================
|
||||
FROM deps-backend AS build-backend
|
||||
FROM deps-backend AS prisma
|
||||
WORKDIR /app
|
||||
ARG DATABASE_URL
|
||||
ENV DATABASE_URL=${DATABASE_URL:-postgresql://dummy:dummy@localhost:5432/dummy}
|
||||
ENV DATABASE_URL=${DATABASE_URL}
|
||||
COPY apps/backend ./apps/backend
|
||||
COPY packages ./packages
|
||||
RUN bun --cwd apps/backend prisma:generate
|
||||
|
||||
# =============================================================================
|
||||
# Stage 5: Build Backend
|
||||
# =============================================================================
|
||||
FROM prisma AS build-backend
|
||||
WORKDIR /app
|
||||
COPY packages ./packages
|
||||
RUN bun --cwd apps/backend build
|
||||
|
||||
# =============================================================================
|
||||
# Stage 5: Runner - Production image
|
||||
# Stage 6: Runner - Production image
|
||||
# =============================================================================
|
||||
FROM oven/bun:1.3.11 AS runner
|
||||
WORKDIR /app
|
||||
@@ -54,7 +60,7 @@ COPY --from=build-backend /app/node_modules ./node_modules
|
||||
COPY --from=build-frontend /app/apps/frontend/dist ./apps/backend/public
|
||||
COPY --from=build-backend /app/apps/backend ./apps/backend
|
||||
COPY --from=build-backend /app/packages ./packages
|
||||
COPY package.json bun.lock ./
|
||||
COPY package.json ./
|
||||
COPY entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
5
apps/backend/.gitignore
vendored
5
apps/backend/.gitignore
vendored
@@ -1,6 +1,3 @@
|
||||
# deps
|
||||
node_modules/
|
||||
prisma.config.prod.ts
|
||||
|
||||
# build output
|
||||
dist/
|
||||
prisma.config.prod.ts
|
||||
@@ -1,3 +1,2 @@
|
||||
[test]
|
||||
preload = ["./test/support/prisma.mock.ts"]
|
||||
coverageThreshold = 0.8
|
||||
@@ -4,7 +4,7 @@
|
||||
"dev": "bun run --hot src/server.ts",
|
||||
"start": "bun src/server.ts",
|
||||
"build": "tsc -b",
|
||||
"test": "bun test --preload ./test/support/prisma.mock.ts ./test",
|
||||
"test": "bun test",
|
||||
"lint": "biome check .",
|
||||
"lint:fix": "biome check --write .",
|
||||
"format": "biome format --write .",
|
||||
|
||||
@@ -4,10 +4,6 @@ model User {
|
||||
email String
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
phone String?
|
||||
banned Boolean @default(false)
|
||||
bannedAt DateTime?
|
||||
banReason String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
sessions Session[]
|
||||
@@ -20,20 +16,15 @@ model User {
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id
|
||||
expiresAt DateTime
|
||||
token String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
country String?
|
||||
city String?
|
||||
countryCode String?
|
||||
latitude Float?
|
||||
longitude Float?
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
id String @id
|
||||
expiresAt DateTime
|
||||
token String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([token])
|
||||
@@index([userId])
|
||||
|
||||
@@ -14,7 +14,6 @@ model Complex {
|
||||
users ComplexUser[]
|
||||
invitations ComplexInvitation[]
|
||||
courts Court[]
|
||||
recurringGroups RecurringBookingGroup[]
|
||||
|
||||
@@index([planCode])
|
||||
@@index([complexSlug])
|
||||
|
||||
@@ -12,12 +12,6 @@ enum CourtBookingStatus {
|
||||
CONFIRMED
|
||||
CANCELLED
|
||||
COMPLETED
|
||||
NOSHOW
|
||||
}
|
||||
|
||||
enum RecurringBookingGroupStatus {
|
||||
ACTIVE
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model Sport {
|
||||
@@ -39,8 +33,6 @@ model Court {
|
||||
name String
|
||||
slotDurationMinutes Int @map("slot_duration_minutes")
|
||||
basePrice Decimal @map("base_price") @db.Decimal(19, 2)
|
||||
isUnderMaintenance Boolean @default(false) @map("is_under_maintenance")
|
||||
maintenanceReason String? @map("maintenance_reason") @db.VarChar(500)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)
|
||||
@@ -48,29 +40,12 @@ model Court {
|
||||
availabilities CourtAvailability[]
|
||||
priceRules CourtPriceRule[]
|
||||
bookings CourtBooking[]
|
||||
maintenances CourtMaintenance[]
|
||||
recurringGroups RecurringBookingGroup[]
|
||||
|
||||
@@index([complexId])
|
||||
@@index([sportId])
|
||||
@@map("courts")
|
||||
}
|
||||
|
||||
model CourtMaintenance {
|
||||
id String @id @db.Uuid
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
startDate DateTime @map("start_date") @db.Date
|
||||
startTime String? @map("start_time") @db.VarChar(5)
|
||||
endTime String? @map("end_time") @db.VarChar(5)
|
||||
reason String? @db.VarChar(500)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([courtId, startDate])
|
||||
@@map("court_maintenances")
|
||||
}
|
||||
|
||||
model CourtAvailability {
|
||||
id String @id @db.Uuid
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
@@ -101,68 +76,22 @@ model CourtPriceRule {
|
||||
}
|
||||
|
||||
model CourtBooking {
|
||||
id String @id @db.Uuid
|
||||
bookingCode String @unique @map("booking_code") @db.VarChar(8)
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
bookingDate DateTime @map("booking_date") @db.Date
|
||||
startTime String @map("start_time") @db.VarChar(5)
|
||||
endTime String @map("end_time") @db.VarChar(5)
|
||||
customerName String @map("customer_name") @db.VarChar(120)
|
||||
customerPhone String @map("customer_phone") @db.VarChar(30)
|
||||
customerEmail String @map("customer_email") @db.VarChar(254)
|
||||
status CourtBookingStatus @default(CONFIRMED)
|
||||
recurringGroupId String? @map("recurring_group_id") @db.Uuid
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
recurringGroup RecurringBookingGroup? @relation(fields: [recurringGroupId], references: [id])
|
||||
id String @id @db.Uuid
|
||||
bookingCode String @unique @map("booking_code") @db.VarChar(8)
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
bookingDate DateTime @map("booking_date") @db.Date
|
||||
startTime String @map("start_time") @db.VarChar(5)
|
||||
endTime String @map("end_time") @db.VarChar(5)
|
||||
price Decimal? @map("price") @db.Decimal(19, 2)
|
||||
customerName String @map("customer_name") @db.VarChar(120)
|
||||
customerPhone String @map("customer_phone") @db.VarChar(30)
|
||||
status CourtBookingStatus @default(CONFIRMED)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([courtId, bookingDate, startTime])
|
||||
@@index([courtId, bookingDate])
|
||||
@@index([bookingDate])
|
||||
@@index([recurringGroupId])
|
||||
@@map("court_bookings")
|
||||
}
|
||||
|
||||
model CourtBookingLog {
|
||||
id String @id @db.Uuid
|
||||
bookingCode String @map("booking_code") @db.VarChar(8)
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
bookingDate DateTime @map("booking_date") @db.Date
|
||||
startTime String @map("start_time") @db.VarChar(5)
|
||||
endTime String @map("end_time") @db.VarChar(5)
|
||||
previousStatus CourtBookingStatus @map("previous_status")
|
||||
newStatus CourtBookingStatus @map("new_status")
|
||||
customerName String @map("customer_name") @db.VarChar(120)
|
||||
customerPhone String @map("customer_phone") @db.VarChar(30)
|
||||
customerEmail String @map("customer_email") @db.VarChar(254)
|
||||
changedAt DateTime @default(now()) @map("changed_at")
|
||||
|
||||
@@map("court_booking_logs")
|
||||
@@index([courtId, newStatus])
|
||||
}
|
||||
|
||||
model RecurringBookingGroup {
|
||||
id String @id @db.Uuid
|
||||
complexId String @map("complex_id") @db.Uuid
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
startTime String @map("start_time") @db.VarChar(5)
|
||||
endTime String @map("end_time") @db.VarChar(5)
|
||||
dayOfWeek DayOfWeek @map("day_of_week")
|
||||
startDate DateTime @map("start_date") @db.Date
|
||||
endDate DateTime? @map("end_date") @db.Date
|
||||
status RecurringBookingGroupStatus @default(ACTIVE)
|
||||
customerName String @map("customer_name") @db.VarChar(120)
|
||||
customerPhone String @map("customer_phone") @db.VarChar(30)
|
||||
customerEmail String @map("customer_email") @db.VarChar(254)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)
|
||||
bookings CourtBooking[]
|
||||
|
||||
@@index([complexId])
|
||||
@@index([courtId])
|
||||
@@index([status])
|
||||
@@map("recurring_booking_groups")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_bookings" ADD COLUMN "price" DECIMAL(19,2);
|
||||
@@ -1,17 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "court_booking_logs" (
|
||||
"id" UUID NOT NULL,
|
||||
"booking_code" UUID NOT NULL,
|
||||
"court_id" UUID NOT NULL,
|
||||
"booking_date" DATE NOT NULL,
|
||||
"start_time" VARCHAR(5) NOT NULL,
|
||||
"end_time" VARCHAR(5) NOT NULL,
|
||||
"previous_status" "CourtBookingStatus" NOT NULL,
|
||||
"new_status" "CourtBookingStatus" NOT NULL,
|
||||
"changed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "court_booking_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_booking_logs_court_id_new_status_idx" ON "court_booking_logs"("court_id", "new_status");
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `customer_name` to the `court_booking_logs` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `customer_phone` to the `court_booking_logs` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterEnum
|
||||
ALTER TYPE "CourtBookingStatus" ADD VALUE 'NOSHOW';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_booking_logs" ADD COLUMN "customer_name" VARCHAR(120) NOT NULL,
|
||||
ADD COLUMN "customer_phone" VARCHAR(30) NOT NULL;
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Changed the type of `booking_code` on the `court_booking_logs` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_booking_logs" DROP COLUMN "booking_code",
|
||||
ADD COLUMN "booking_code" VARCHAR(8) NOT NULL;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "phone" TEXT;
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `onboarding_requests` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_booking_logs" ADD COLUMN "customer_email" VARCHAR(254);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_bookings" ADD COLUMN "customer_email" VARCHAR(254);
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "onboarding_requests";
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `customer_email` on table `court_booking_logs` required. This step will fail if there are existing NULL values in that column.
|
||||
- Made the column `customer_email` on table `court_bookings` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- Backfill existing NULL values with placeholder
|
||||
UPDATE "court_booking_logs" SET "customer_email" = 'missing@playzer.app' WHERE "customer_email" IS NULL;
|
||||
UPDATE "court_bookings" SET "customer_email" = 'missing@playzer.app' WHERE "customer_email" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_booking_logs" ALTER COLUMN "customer_email" SET NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_bookings" ALTER COLUMN "customer_email" SET NOT NULL;
|
||||
@@ -1,23 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "court_maintenances" (
|
||||
"id" UUID NOT NULL,
|
||||
"court_id" UUID NOT NULL,
|
||||
"start_date" DATE NOT NULL,
|
||||
"start_time" VARCHAR(5),
|
||||
"end_time" VARCHAR(5),
|
||||
"reason" VARCHAR(500),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "court_maintenances_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "courts" ADD COLUMN "is_under_maintenance" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "courts" ADD COLUMN "maintenance_reason" VARCHAR(500);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_maintenances_court_id_start_date_idx" ON "court_maintenances"("court_id", "start_date");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "court_maintenances" ADD CONSTRAINT "court_maintenances_court_id_fkey" FOREIGN KEY ("court_id") REFERENCES "courts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,4 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "banReason" TEXT,
|
||||
ADD COLUMN "banned" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "bannedAt" TIMESTAMP(3);
|
||||
@@ -1,6 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "sessions" ADD COLUMN "city" TEXT,
|
||||
ADD COLUMN "country" TEXT,
|
||||
ADD COLUMN "countryCode" TEXT,
|
||||
ADD COLUMN "latitude" DOUBLE PRECISION,
|
||||
ADD COLUMN "longitude" DOUBLE PRECISION;
|
||||
@@ -1,46 +0,0 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RecurringBookingGroupStatus" AS ENUM ('ACTIVE', 'CANCELLED');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_bookings" ADD COLUMN "recurring_group_id" UUID;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "recurring_booking_groups" (
|
||||
"id" UUID NOT NULL,
|
||||
"complex_id" UUID NOT NULL,
|
||||
"court_id" UUID NOT NULL,
|
||||
"start_time" VARCHAR(5) NOT NULL,
|
||||
"end_time" VARCHAR(5) NOT NULL,
|
||||
"day_of_week" "DayOfWeek" NOT NULL,
|
||||
"start_date" DATE NOT NULL,
|
||||
"end_date" DATE,
|
||||
"status" "RecurringBookingGroupStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"customer_name" VARCHAR(120) NOT NULL,
|
||||
"customer_phone" VARCHAR(30) NOT NULL,
|
||||
"customer_email" VARCHAR(254) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "recurring_booking_groups_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_booking_groups_complex_id_idx" ON "recurring_booking_groups"("complex_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_booking_groups_court_id_idx" ON "recurring_booking_groups"("court_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "recurring_booking_groups_status_idx" ON "recurring_booking_groups"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_bookings_recurring_group_id_idx" ON "court_bookings"("recurring_group_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "court_bookings" ADD CONSTRAINT "court_bookings_recurring_group_id_fkey" FOREIGN KEY ("recurring_group_id") REFERENCES "recurring_booking_groups"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "recurring_booking_groups" ADD CONSTRAINT "recurring_booking_groups_court_id_fkey" FOREIGN KEY ("court_id") REFERENCES "courts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "recurring_booking_groups" ADD CONSTRAINT "recurring_booking_groups_complex_id_fkey" FOREIGN KEY ("complex_id") REFERENCES "complexes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
18
apps/backend/prisma/onboarding.prisma
Normal file
18
apps/backend/prisma/onboarding.prisma
Normal file
@@ -0,0 +1,18 @@
|
||||
model OnboardingRequest {
|
||||
id String @id @db.Uuid
|
||||
fullName String @map("full_name")
|
||||
email String
|
||||
otpHash String @map("otp_hash")
|
||||
otpExpiresAt DateTime @map("otp_expires_at")
|
||||
otpAttempts Int @default(0) @map("otp_attempts")
|
||||
otpLastSentAt DateTime @map("otp_last_sent_at")
|
||||
otpResendCount Int @default(0) @map("otp_resend_count")
|
||||
emailVerifiedAt DateTime? @map("email_verified_at")
|
||||
completedAt DateTime? @map("completed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([email])
|
||||
@@index([otpExpiresAt])
|
||||
@@map("onboarding_requests")
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export const planSeeds = [
|
||||
name: 'Basic',
|
||||
price: '49.00',
|
||||
rules: {
|
||||
version: 'v2',
|
||||
version: 'v1',
|
||||
limits: {
|
||||
maxCourts: 2,
|
||||
maxBookingsPerDay: 80,
|
||||
@@ -21,12 +21,6 @@ export const planSeeds = [
|
||||
publicBookingPage: true,
|
||||
advancedReports: false,
|
||||
whatsappReminders: false,
|
||||
fixedSlots: false,
|
||||
},
|
||||
pricing: {
|
||||
overrides: {
|
||||
AR: { amount: 14999, currency: 'ARS' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -35,7 +29,7 @@ export const planSeeds = [
|
||||
name: 'Advanced',
|
||||
price: '99.00',
|
||||
rules: {
|
||||
version: 'v2',
|
||||
version: 'v1',
|
||||
limits: {
|
||||
maxCourts: 5,
|
||||
maxBookingsPerDay: 220,
|
||||
@@ -52,12 +46,6 @@ export const planSeeds = [
|
||||
publicBookingPage: true,
|
||||
advancedReports: true,
|
||||
whatsappReminders: true,
|
||||
fixedSlots: true,
|
||||
},
|
||||
pricing: {
|
||||
overrides: {
|
||||
AR: { amount: 29999, currency: 'ARS' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -66,7 +54,7 @@ export const planSeeds = [
|
||||
name: 'Enterprise',
|
||||
price: '199.00',
|
||||
rules: {
|
||||
version: 'v2',
|
||||
version: 'v1',
|
||||
limits: {
|
||||
maxCourts: 20,
|
||||
maxBookingsPerDay: 2000,
|
||||
@@ -83,12 +71,6 @@ export const planSeeds = [
|
||||
publicBookingPage: true,
|
||||
advancedReports: true,
|
||||
whatsappReminders: true,
|
||||
fixedSlots: true,
|
||||
},
|
||||
pricing: {
|
||||
overrides: {
|
||||
AR: { amount: 59999, currency: 'ARS' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,15 +3,10 @@ import type { AppEnv } from '@/types/hono';
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { requestId } from 'hono/request-id';
|
||||
import { errorHandler } from './lib/http/error-handler';
|
||||
import { requestIdMiddleware } from './lib/http/request-id';
|
||||
|
||||
export function createApp() {
|
||||
const app = new Hono<AppEnv>();
|
||||
|
||||
app.use('*', requestIdMiddleware);
|
||||
app.use('*', errorHandler);
|
||||
|
||||
const allowedOrigins = (Bun.env.CORS_ORIGIN ?? 'http://localhost:5173,http://127.0.0.1:5173')
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
type BookingEmailData = {
|
||||
bookingCode: string;
|
||||
complexSlug?: string;
|
||||
complexName: string;
|
||||
date: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
courtName: string;
|
||||
sportName: string;
|
||||
customerName: string;
|
||||
price?: number;
|
||||
};
|
||||
|
||||
const APP_BASE_URL = process.env.APP_BASE_URL ?? 'http://localhost:5173';
|
||||
|
||||
function formatBookingPrice(price: number): string {
|
||||
if (price === 0) {
|
||||
return 'Sin cargo';
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat('es-AR', {
|
||||
style: 'currency',
|
||||
currency: 'ARS',
|
||||
maximumFractionDigits: Number.isInteger(price) ? 0 : 2,
|
||||
}).format(price);
|
||||
}
|
||||
|
||||
function formatFriendlyDate(isoDate: string): string {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(isoDate);
|
||||
if (!match) return isoDate;
|
||||
|
||||
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
||||
|
||||
const weekday = new Intl.DateTimeFormat('es-AR', { weekday: 'long' }).format(date);
|
||||
const day = date.getDate();
|
||||
const month = new Intl.DateTimeFormat('es-AR', { month: 'long' }).format(date);
|
||||
|
||||
return `${weekday.charAt(0).toUpperCase() + weekday.slice(1)}, ${day} de ${month}`;
|
||||
}
|
||||
|
||||
const BASE_STYLES = `
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #edf7f4;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
|
||||
export function wrapLayout(content: string) {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Playzer</title>
|
||||
<style>${BASE_STYLES}</style>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background-color:#edf7f4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#edf7f4;">
|
||||
<tr>
|
||||
<td align="center" style="padding:24px 12px;">
|
||||
<table role="presentation" width="100%" style="max-width:520px;background-color:#ffffff;border-radius:28px;overflow:hidden;box-shadow:0 24px 70px rgba(15,23,42,0.12);border:1px solid rgba(5,9,20,0.1);">
|
||||
${content}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
export function bookingConfirmationHtml(data: BookingEmailData): string {
|
||||
const formattedPrice = formatBookingPrice(data.price ?? 0);
|
||||
const friendlyDate = formatFriendlyDate(data.date);
|
||||
|
||||
const content = `
|
||||
<tr>
|
||||
<td style="padding:24px 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="display:inline-block;background-color:#f0fdf4;border-radius:999px;padding:4px 12px;">
|
||||
<tr>
|
||||
<td style="font-size:11px;font-weight:700;letter-spacing:0.14em;color:#15803d;text-transform:uppercase;line-height:1.25rem;">
|
||||
✓ Reserva confirmada
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h1 style="margin:12px 0 0;font-size:28px;font-weight:700;color:#111827;letter-spacing:-0.025em;">
|
||||
${data.complexName}
|
||||
</h1>
|
||||
</td>
|
||||
<td valign="top" align="right" style="white-space:nowrap;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="middle" style="padding-right:8px;">
|
||||
<img src="${APP_BASE_URL}/playzer-favicon-512-transparent.png" alt="Playzer" width="32" height="32" style="display:block;" />
|
||||
</td>
|
||||
<td valign="middle">
|
||||
<span style="font-size:18px;font-weight:700;color:#475569;letter-spacing:-0.025em;">Playzer</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f0fdf4;border-radius:24px;border:1px solid rgba(5,150,105,0.3);">
|
||||
<tr>
|
||||
<td style="padding:20px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="bottom">
|
||||
<p style="margin:0;font-size:13px;font-weight:600;color:#15803d;">
|
||||
Tu turno
|
||||
</p>
|
||||
<p style="margin:12px 0 0;font-size:28px;font-weight:900;color:#111827;line-height:1.1;letter-spacing:-0.025em;">
|
||||
${friendlyDate}
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:24px;font-weight:900;color:#059669;line-height:1;letter-spacing:-0.025em;">
|
||||
${data.startTime} — ${data.endTime}
|
||||
</p>
|
||||
</td>
|
||||
<td valign="bottom" align="right" style="padding-left:16px;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="background-color:rgba(255,255,255,0.8);border-radius:16px;border:1px solid rgba(5,150,105,0.2);">
|
||||
<tr>
|
||||
<td style="padding:12px 16px;text-align:right;min-width:120px;">
|
||||
<p style="margin:0;font-size:11px;font-weight:500;color:#6b7280;">Precio del turno</p>
|
||||
<p style="margin:4px 0 0;font-size:20px;font-weight:700;color:#111827;">${formattedPrice}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:22px;overflow:hidden;">
|
||||
<tr>
|
||||
<td width="50%" style="padding:16px;border-right:1px solid #e5e7eb;background-color:#ffffff;">
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#6b7280;">
|
||||
Cancha
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:14px;font-weight:600;color:#111827;">
|
||||
${data.courtName} — ${data.sportName}
|
||||
</p>
|
||||
</td>
|
||||
<td width="50%" style="padding:16px;background-color:#ffffff;">
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#6b7280;">
|
||||
Código de reserva
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-family:monospace;font-size:16px;font-weight:700;letter-spacing:0.18em;color:#111827;">
|
||||
${data.bookingCode}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
${
|
||||
data.complexSlug
|
||||
? `
|
||||
<tr>
|
||||
<td style="padding:0 20px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="background-color:#059669;border-radius:12px;">
|
||||
<tr>
|
||||
<td style="padding:14px 32px;font-size:15px;font-weight:600;">
|
||||
<a href="${APP_BASE_URL}/${data.complexSlug}/booking/confirmed/${data.bookingCode}" style="color:#ffffff;text-decoration:none;display:inline-block;">
|
||||
Cancelar reserva
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>`
|
||||
: ''
|
||||
}`;
|
||||
|
||||
return wrapLayout(content);
|
||||
}
|
||||
|
||||
export function bookingCancelledHtml(data: BookingEmailData): string {
|
||||
const friendlyDate = formatFriendlyDate(data.date);
|
||||
|
||||
const content = `
|
||||
<tr>
|
||||
<td style="padding:24px 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="display:inline-block;background-color:#fef2f2;border-radius:999px;padding:4px 12px;">
|
||||
<tr>
|
||||
<td style="font-size:11px;font-weight:700;letter-spacing:0.14em;color:#dc2626;text-transform:uppercase;line-height:1.25rem;">
|
||||
✗ Reserva cancelada
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h1 style="margin:12px 0 0;font-size:28px;font-weight:700;color:#111827;letter-spacing:-0.025em;">
|
||||
${data.complexName}
|
||||
</h1>
|
||||
</td>
|
||||
<td valign="top" align="right" style="white-space:nowrap;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="middle" style="padding-right:8px;">
|
||||
<img src="${APP_BASE_URL}/playzer-favicon-512-transparent.png" alt="Playzer" width="32" height="32" style="display:block;" />
|
||||
</td>
|
||||
<td valign="middle">
|
||||
<span style="font-size:18px;font-weight:700;color:#475569;letter-spacing:-0.025em;">Playzer</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f0fdf4;border-radius:24px;border:1px solid rgba(5,150,105,0.3);">
|
||||
<tr>
|
||||
<td style="padding:20px 24px;">
|
||||
<p style="margin:0;font-size:13px;font-weight:600;color:#15803d;">
|
||||
Tu turno
|
||||
</p>
|
||||
<p style="margin:12px 0 0;font-size:28px;font-weight:900;color:#111827;line-height:1.1;letter-spacing:-0.025em;">
|
||||
${friendlyDate}
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:24px;font-weight:900;color:#059669;line-height:1;letter-spacing:-0.025em;">
|
||||
${data.startTime} — ${data.endTime}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#fef2f2;border-radius:24px;border:1px solid rgba(220,38,38,0.3);">
|
||||
<tr>
|
||||
<td style="padding:20px 24px;">
|
||||
<p style="margin:0;font-size:14px;color:#374151;line-height:1.6;">
|
||||
La reserva <strong style="font-family:monospace;font-weight:700;letter-spacing:2px;">${data.bookingCode}</strong>
|
||||
fue cancelada. Si tenés dudas, contactate con el complejo.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:22px;overflow:hidden;">
|
||||
<tr>
|
||||
<td width="50%" style="padding:16px;border-right:1px solid #e5e7eb;background-color:#ffffff;">
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#6b7280;">
|
||||
Cancha
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:14px;font-weight:600;color:#111827;">
|
||||
${data.courtName} — ${data.sportName}
|
||||
</p>
|
||||
</td>
|
||||
<td width="50%" style="padding:16px;background-color:#ffffff;">
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#6b7280;">
|
||||
Cliente
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:14px;font-weight:600;color:#111827;">
|
||||
${data.customerName}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>`;
|
||||
|
||||
return wrapLayout(content);
|
||||
}
|
||||
|
||||
export function bookingNoShowHtml(data: BookingEmailData): string {
|
||||
const friendlyDate = formatFriendlyDate(data.date);
|
||||
|
||||
const content = `
|
||||
<tr>
|
||||
<td style="padding:24px 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="display:inline-block;background-color:#fffbeb;border-radius:999px;padding:4px 12px;">
|
||||
<tr>
|
||||
<td style="font-size:11px;font-weight:700;letter-spacing:0.14em;color:#d97706;text-transform:uppercase;line-height:1.25rem;">
|
||||
Reserva no concretada
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h1 style="margin:12px 0 0;font-size:28px;font-weight:700;color:#111827;letter-spacing:-0.025em;">
|
||||
${data.complexName}
|
||||
</h1>
|
||||
</td>
|
||||
<td valign="top" align="right" style="white-space:nowrap;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="middle" style="padding-right:8px;">
|
||||
<img src="${APP_BASE_URL}/playzer-favicon-512-transparent.png" alt="Playzer" width="32" height="32" style="display:block;" />
|
||||
</td>
|
||||
<td valign="middle">
|
||||
<span style="font-size:18px;font-weight:700;color:#475569;letter-spacing:-0.025em;">Playzer</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f0fdf4;border-radius:24px;border:1px solid rgba(5,150,105,0.3);">
|
||||
<tr>
|
||||
<td style="padding:20px 24px;">
|
||||
<p style="margin:0;font-size:13px;font-weight:600;color:#15803d;">
|
||||
Tu turno
|
||||
</p>
|
||||
<p style="margin:12px 0 0;font-size:28px;font-weight:900;color:#111827;line-height:1.1;letter-spacing:-0.025em;">
|
||||
${friendlyDate}
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:24px;font-weight:900;color:#059669;line-height:1;letter-spacing:-0.025em;">
|
||||
${data.startTime} — ${data.endTime}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#fffbeb;border-radius:24px;border:1px solid rgba(217,119,6,0.3);">
|
||||
<tr>
|
||||
<td style="padding:20px 24px;">
|
||||
<p style="margin:0;font-size:14px;color:#374151;line-height:1.6;">
|
||||
La reserva <strong style="font-family:monospace;font-weight:700;letter-spacing:2px;">${data.bookingCode}</strong>
|
||||
fue registrada como no concretada por falta de asistencia.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:22px;overflow:hidden;">
|
||||
<tr>
|
||||
<td width="50%" style="padding:16px;border-right:1px solid #e5e7eb;background-color:#ffffff;">
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#6b7280;">
|
||||
Cancha
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:14px;font-weight:600;color:#111827;">
|
||||
${data.courtName} — ${data.sportName}
|
||||
</p>
|
||||
</td>
|
||||
<td width="50%" style="padding:16px;background-color:#ffffff;">
|
||||
<p style="margin:0;font-size:13px;font-weight:500;color:#6b7280;">
|
||||
Cliente
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:14px;font-weight:600;color:#111827;">
|
||||
${data.customerName}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>`;
|
||||
|
||||
return wrapLayout(content);
|
||||
}
|
||||
@@ -62,11 +62,6 @@ export type Sport = Prisma.SportModel
|
||||
*
|
||||
*/
|
||||
export type Court = Prisma.CourtModel
|
||||
/**
|
||||
* Model CourtMaintenance
|
||||
*
|
||||
*/
|
||||
export type CourtMaintenance = Prisma.CourtMaintenanceModel
|
||||
/**
|
||||
* Model CourtAvailability
|
||||
*
|
||||
@@ -83,15 +78,10 @@ export type CourtPriceRule = Prisma.CourtPriceRuleModel
|
||||
*/
|
||||
export type CourtBooking = Prisma.CourtBookingModel
|
||||
/**
|
||||
* Model CourtBookingLog
|
||||
* Model OnboardingRequest
|
||||
*
|
||||
*/
|
||||
export type CourtBookingLog = Prisma.CourtBookingLogModel
|
||||
/**
|
||||
* Model RecurringBookingGroup
|
||||
*
|
||||
*/
|
||||
export type RecurringBookingGroup = Prisma.RecurringBookingGroupModel
|
||||
export type OnboardingRequest = Prisma.OnboardingRequestModel
|
||||
/**
|
||||
* Model PasswordResetRequest
|
||||
*
|
||||
|
||||
@@ -86,11 +86,6 @@ export type Sport = Prisma.SportModel
|
||||
*
|
||||
*/
|
||||
export type Court = Prisma.CourtModel
|
||||
/**
|
||||
* Model CourtMaintenance
|
||||
*
|
||||
*/
|
||||
export type CourtMaintenance = Prisma.CourtMaintenanceModel
|
||||
/**
|
||||
* Model CourtAvailability
|
||||
*
|
||||
@@ -107,15 +102,10 @@ export type CourtPriceRule = Prisma.CourtPriceRuleModel
|
||||
*/
|
||||
export type CourtBooking = Prisma.CourtBookingModel
|
||||
/**
|
||||
* Model CourtBookingLog
|
||||
* Model OnboardingRequest
|
||||
*
|
||||
*/
|
||||
export type CourtBookingLog = Prisma.CourtBookingLogModel
|
||||
/**
|
||||
* Model RecurringBookingGroup
|
||||
*
|
||||
*/
|
||||
export type RecurringBookingGroup = Prisma.RecurringBookingGroupModel
|
||||
export type OnboardingRequest = Prisma.OnboardingRequestModel
|
||||
/**
|
||||
* Model PasswordResetRequest
|
||||
*
|
||||
|
||||
@@ -49,17 +49,6 @@ export type StringNullableFilter<$PrismaModel = never> = {
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||
}
|
||||
|
||||
export type DateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
|
||||
export type DateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -120,20 +109,6 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -148,31 +123,29 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type FloatNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||
export type DateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
|
||||
export type FloatNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type UuidFilter<$PrismaModel = never> = {
|
||||
@@ -307,6 +280,17 @@ export type EnumDayOfWeekNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedEnumDayOfWeekNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type DecimalNullableFilter<$PrismaModel = never> = {
|
||||
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel> | null
|
||||
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDecimalNullableFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
}
|
||||
|
||||
export type EnumCourtBookingStatusFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.CourtBookingStatus | Prisma.EnumCourtBookingStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.CourtBookingStatus[] | Prisma.ListEnumCourtBookingStatusFieldRefInput<$PrismaModel>
|
||||
@@ -314,16 +298,20 @@ export type EnumCourtBookingStatusFilter<$PrismaModel = never> = {
|
||||
not?: Prisma.NestedEnumCourtBookingStatusFilter<$PrismaModel> | $Enums.CourtBookingStatus
|
||||
}
|
||||
|
||||
export type UuidNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedUuidNullableFilter<$PrismaModel> | string | null
|
||||
export type DecimalNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel> | null
|
||||
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDecimalNullableWithAggregatesFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type EnumCourtBookingStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
@@ -336,38 +324,6 @@ export type EnumCourtBookingStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedEnumCourtBookingStatusFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type UuidNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
mode?: Prisma.QueryMode
|
||||
not?: Prisma.NestedUuidNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type EnumRecurringBookingGroupStatusFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.RecurringBookingGroupStatus | Prisma.EnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel> | $Enums.RecurringBookingGroupStatus
|
||||
}
|
||||
|
||||
export type EnumRecurringBookingGroupStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.RecurringBookingGroupStatus | Prisma.EnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumRecurringBookingGroupStatusWithAggregatesFilter<$PrismaModel> | $Enums.RecurringBookingGroupStatus
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type JsonFilter<$PrismaModel = never> =
|
||||
| Prisma.PatchUndefined<
|
||||
Prisma.Either<Required<JsonFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||
@@ -452,17 +408,6 @@ export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||
}
|
||||
|
||||
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
|
||||
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -538,20 +483,6 @@ export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
|
||||
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -566,31 +497,29 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
|
||||
export type NestedFloatNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedUuidFilter<$PrismaModel = never> = {
|
||||
@@ -723,6 +652,17 @@ export type NestedEnumDayOfWeekNullableWithAggregatesFilter<$PrismaModel = never
|
||||
_max?: Prisma.NestedEnumDayOfWeekNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedDecimalNullableFilter<$PrismaModel = never> = {
|
||||
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel> | null
|
||||
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDecimalNullableFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
}
|
||||
|
||||
export type NestedEnumCourtBookingStatusFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.CourtBookingStatus | Prisma.EnumCourtBookingStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.CourtBookingStatus[] | Prisma.ListEnumCourtBookingStatusFieldRefInput<$PrismaModel>
|
||||
@@ -730,15 +670,20 @@ export type NestedEnumCourtBookingStatusFilter<$PrismaModel = never> = {
|
||||
not?: Prisma.NestedEnumCourtBookingStatusFilter<$PrismaModel> | $Enums.CourtBookingStatus
|
||||
}
|
||||
|
||||
export type NestedUuidNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedUuidNullableFilter<$PrismaModel> | string | null
|
||||
export type NestedDecimalNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel> | null
|
||||
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel> | null
|
||||
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDecimalNullableWithAggregatesFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDecimalNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedEnumCourtBookingStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
@@ -751,37 +696,6 @@ export type NestedEnumCourtBookingStatusWithAggregatesFilter<$PrismaModel = neve
|
||||
_max?: Prisma.NestedEnumCourtBookingStatusFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedUuidNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedUuidNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.RecurringBookingGroupStatus | Prisma.EnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel> | $Enums.RecurringBookingGroupStatus
|
||||
}
|
||||
|
||||
export type NestedEnumRecurringBookingGroupStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.RecurringBookingGroupStatus | Prisma.EnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.RecurringBookingGroupStatus[] | Prisma.ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumRecurringBookingGroupStatusWithAggregatesFilter<$PrismaModel> | $Enums.RecurringBookingGroupStatus
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedEnumRecurringBookingGroupStatusFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedJsonFilter<$PrismaModel = never> =
|
||||
| Prisma.PatchUndefined<
|
||||
Prisma.Either<Required<NestedJsonFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||
|
||||
@@ -33,16 +33,7 @@ export type DayOfWeek = (typeof DayOfWeek)[keyof typeof DayOfWeek]
|
||||
export const CourtBookingStatus = {
|
||||
CONFIRMED: 'CONFIRMED',
|
||||
CANCELLED: 'CANCELLED',
|
||||
COMPLETED: 'COMPLETED',
|
||||
NOSHOW: 'NOSHOW'
|
||||
COMPLETED: 'COMPLETED'
|
||||
} as const
|
||||
|
||||
export type CourtBookingStatus = (typeof CourtBookingStatus)[keyof typeof CourtBookingStatus]
|
||||
|
||||
|
||||
export const RecurringBookingGroupStatus = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
CANCELLED: 'CANCELLED'
|
||||
} as const
|
||||
|
||||
export type RecurringBookingGroupStatus = (typeof RecurringBookingGroupStatus)[keyof typeof RecurringBookingGroupStatus]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -393,12 +393,10 @@ export const ModelName = {
|
||||
ComplexInvitation: 'ComplexInvitation',
|
||||
Sport: 'Sport',
|
||||
Court: 'Court',
|
||||
CourtMaintenance: 'CourtMaintenance',
|
||||
CourtAvailability: 'CourtAvailability',
|
||||
CourtPriceRule: 'CourtPriceRule',
|
||||
CourtBooking: 'CourtBooking',
|
||||
CourtBookingLog: 'CourtBookingLog',
|
||||
RecurringBookingGroup: 'RecurringBookingGroup',
|
||||
OnboardingRequest: 'OnboardingRequest',
|
||||
PasswordResetRequest: 'PasswordResetRequest',
|
||||
Plan: 'Plan'
|
||||
} as const
|
||||
@@ -416,7 +414,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
omit: GlobalOmitOptions
|
||||
}
|
||||
meta: {
|
||||
modelProps: "user" | "session" | "account" | "verification" | "complex" | "complexUser" | "complexInvitation" | "sport" | "court" | "courtMaintenance" | "courtAvailability" | "courtPriceRule" | "courtBooking" | "courtBookingLog" | "recurringBookingGroup" | "passwordResetRequest" | "plan"
|
||||
modelProps: "user" | "session" | "account" | "verification" | "complex" | "complexUser" | "complexInvitation" | "sport" | "court" | "courtAvailability" | "courtPriceRule" | "courtBooking" | "onboardingRequest" | "passwordResetRequest" | "plan"
|
||||
txIsolationLevel: TransactionIsolationLevel
|
||||
}
|
||||
model: {
|
||||
@@ -1086,80 +1084,6 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
}
|
||||
}
|
||||
}
|
||||
CourtMaintenance: {
|
||||
payload: Prisma.$CourtMaintenancePayload<ExtArgs>
|
||||
fields: Prisma.CourtMaintenanceFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.CourtMaintenanceFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.CourtMaintenanceFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.CourtMaintenanceFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.CourtMaintenanceFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.CourtMaintenanceFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.CourtMaintenanceCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.CourtMaintenanceCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
createManyAndReturn: {
|
||||
args: Prisma.CourtMaintenanceCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>[]
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.CourtMaintenanceDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.CourtMaintenanceUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.CourtMaintenanceDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.CourtMaintenanceUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateManyAndReturn: {
|
||||
args: Prisma.CourtMaintenanceUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>[]
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.CourtMaintenanceUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtMaintenancePayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.CourtMaintenanceAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateCourtMaintenance>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.CourtMaintenanceGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.CourtMaintenanceGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.CourtMaintenanceCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.CourtMaintenanceCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
CourtAvailability: {
|
||||
payload: Prisma.$CourtAvailabilityPayload<ExtArgs>
|
||||
fields: Prisma.CourtAvailabilityFieldRefs
|
||||
@@ -1382,151 +1306,77 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
}
|
||||
}
|
||||
}
|
||||
CourtBookingLog: {
|
||||
payload: Prisma.$CourtBookingLogPayload<ExtArgs>
|
||||
fields: Prisma.CourtBookingLogFieldRefs
|
||||
OnboardingRequest: {
|
||||
payload: Prisma.$OnboardingRequestPayload<ExtArgs>
|
||||
fields: Prisma.OnboardingRequestFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.CourtBookingLogFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload> | null
|
||||
args: Prisma.OnboardingRequestFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.CourtBookingLogFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||
args: Prisma.OnboardingRequestFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.CourtBookingLogFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload> | null
|
||||
args: Prisma.OnboardingRequestFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.CourtBookingLogFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||
args: Prisma.OnboardingRequestFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.CourtBookingLogFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>[]
|
||||
args: Prisma.OnboardingRequestFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.CourtBookingLogCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||
args: Prisma.OnboardingRequestCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.CourtBookingLogCreateManyArgs<ExtArgs>
|
||||
args: Prisma.OnboardingRequestCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
createManyAndReturn: {
|
||||
args: Prisma.CourtBookingLogCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>[]
|
||||
args: Prisma.OnboardingRequestCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>[]
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.CourtBookingLogDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||
args: Prisma.OnboardingRequestDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.CourtBookingLogUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||
args: Prisma.OnboardingRequestUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.CourtBookingLogDeleteManyArgs<ExtArgs>
|
||||
args: Prisma.OnboardingRequestDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.CourtBookingLogUpdateManyArgs<ExtArgs>
|
||||
args: Prisma.OnboardingRequestUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateManyAndReturn: {
|
||||
args: Prisma.CourtBookingLogUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>[]
|
||||
args: Prisma.OnboardingRequestUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>[]
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.CourtBookingLogUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||
args: Prisma.OnboardingRequestUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OnboardingRequestPayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.CourtBookingLogAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateCourtBookingLog>
|
||||
args: Prisma.OnboardingRequestAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateOnboardingRequest>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.CourtBookingLogGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.CourtBookingLogGroupByOutputType>[]
|
||||
args: Prisma.OnboardingRequestGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.OnboardingRequestGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.CourtBookingLogCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.CourtBookingLogCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
RecurringBookingGroup: {
|
||||
payload: Prisma.$RecurringBookingGroupPayload<ExtArgs>
|
||||
fields: Prisma.RecurringBookingGroupFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.RecurringBookingGroupFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.RecurringBookingGroupFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.RecurringBookingGroupFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.RecurringBookingGroupFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.RecurringBookingGroupFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.RecurringBookingGroupCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.RecurringBookingGroupCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
createManyAndReturn: {
|
||||
args: Prisma.RecurringBookingGroupCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>[]
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.RecurringBookingGroupDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.RecurringBookingGroupUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.RecurringBookingGroupDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.RecurringBookingGroupUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateManyAndReturn: {
|
||||
args: Prisma.RecurringBookingGroupUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>[]
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.RecurringBookingGroupUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RecurringBookingGroupPayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.RecurringBookingGroupAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateRecurringBookingGroup>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.RecurringBookingGroupGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.RecurringBookingGroupGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.RecurringBookingGroupCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.RecurringBookingGroupCountAggregateOutputType> | number
|
||||
args: Prisma.OnboardingRequestCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.OnboardingRequestCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1723,10 +1573,6 @@ export const UserScalarFieldEnum = {
|
||||
email: 'email',
|
||||
emailVerified: 'emailVerified',
|
||||
image: 'image',
|
||||
phone: 'phone',
|
||||
banned: 'banned',
|
||||
bannedAt: 'bannedAt',
|
||||
banReason: 'banReason',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
role: 'role'
|
||||
@@ -1743,11 +1589,6 @@ export const SessionScalarFieldEnum = {
|
||||
updatedAt: 'updatedAt',
|
||||
ipAddress: 'ipAddress',
|
||||
userAgent: 'userAgent',
|
||||
country: 'country',
|
||||
city: 'city',
|
||||
countryCode: 'countryCode',
|
||||
latitude: 'latitude',
|
||||
longitude: 'longitude',
|
||||
userId: 'userId'
|
||||
} as const
|
||||
|
||||
@@ -1846,8 +1687,6 @@ export const CourtScalarFieldEnum = {
|
||||
name: 'name',
|
||||
slotDurationMinutes: 'slotDurationMinutes',
|
||||
basePrice: 'basePrice',
|
||||
isUnderMaintenance: 'isUnderMaintenance',
|
||||
maintenanceReason: 'maintenanceReason',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
@@ -1855,20 +1694,6 @@ export const CourtScalarFieldEnum = {
|
||||
export type CourtScalarFieldEnum = (typeof CourtScalarFieldEnum)[keyof typeof CourtScalarFieldEnum]
|
||||
|
||||
|
||||
export const CourtMaintenanceScalarFieldEnum = {
|
||||
id: 'id',
|
||||
courtId: 'courtId',
|
||||
startDate: 'startDate',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
reason: 'reason',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type CourtMaintenanceScalarFieldEnum = (typeof CourtMaintenanceScalarFieldEnum)[keyof typeof CourtMaintenanceScalarFieldEnum]
|
||||
|
||||
|
||||
export const CourtAvailabilityScalarFieldEnum = {
|
||||
id: 'id',
|
||||
courtId: 'courtId',
|
||||
@@ -1903,11 +1728,10 @@ export const CourtBookingScalarFieldEnum = {
|
||||
bookingDate: 'bookingDate',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
price: 'price',
|
||||
customerName: 'customerName',
|
||||
customerPhone: 'customerPhone',
|
||||
customerEmail: 'customerEmail',
|
||||
status: 'status',
|
||||
recurringGroupId: 'recurringGroupId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
@@ -1915,42 +1739,22 @@ export const CourtBookingScalarFieldEnum = {
|
||||
export type CourtBookingScalarFieldEnum = (typeof CourtBookingScalarFieldEnum)[keyof typeof CourtBookingScalarFieldEnum]
|
||||
|
||||
|
||||
export const CourtBookingLogScalarFieldEnum = {
|
||||
export const OnboardingRequestScalarFieldEnum = {
|
||||
id: 'id',
|
||||
bookingCode: 'bookingCode',
|
||||
courtId: 'courtId',
|
||||
bookingDate: 'bookingDate',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
previousStatus: 'previousStatus',
|
||||
newStatus: 'newStatus',
|
||||
customerName: 'customerName',
|
||||
customerPhone: 'customerPhone',
|
||||
customerEmail: 'customerEmail',
|
||||
changedAt: 'changedAt'
|
||||
} as const
|
||||
|
||||
export type CourtBookingLogScalarFieldEnum = (typeof CourtBookingLogScalarFieldEnum)[keyof typeof CourtBookingLogScalarFieldEnum]
|
||||
|
||||
|
||||
export const RecurringBookingGroupScalarFieldEnum = {
|
||||
id: 'id',
|
||||
complexId: 'complexId',
|
||||
courtId: 'courtId',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
dayOfWeek: 'dayOfWeek',
|
||||
startDate: 'startDate',
|
||||
endDate: 'endDate',
|
||||
status: 'status',
|
||||
customerName: 'customerName',
|
||||
customerPhone: 'customerPhone',
|
||||
customerEmail: 'customerEmail',
|
||||
fullName: 'fullName',
|
||||
email: 'email',
|
||||
otpHash: 'otpHash',
|
||||
otpExpiresAt: 'otpExpiresAt',
|
||||
otpAttempts: 'otpAttempts',
|
||||
otpLastSentAt: 'otpLastSentAt',
|
||||
otpResendCount: 'otpResendCount',
|
||||
emailVerifiedAt: 'emailVerifiedAt',
|
||||
completedAt: 'completedAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type RecurringBookingGroupScalarFieldEnum = (typeof RecurringBookingGroupScalarFieldEnum)[keyof typeof RecurringBookingGroupScalarFieldEnum]
|
||||
export type OnboardingRequestScalarFieldEnum = (typeof OnboardingRequestScalarFieldEnum)[keyof typeof OnboardingRequestScalarFieldEnum]
|
||||
|
||||
|
||||
export const PasswordResetRequestScalarFieldEnum = {
|
||||
@@ -2061,20 +1865,6 @@ export type ListDateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaM
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Float'
|
||||
*/
|
||||
export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Float[]'
|
||||
*/
|
||||
export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'ComplexUserRole'
|
||||
*/
|
||||
@@ -2145,20 +1935,6 @@ export type ListEnumCourtBookingStatusFieldRefInput<$PrismaModel> = FieldRefInpu
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'RecurringBookingGroupStatus'
|
||||
*/
|
||||
export type EnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'RecurringBookingGroupStatus'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'RecurringBookingGroupStatus[]'
|
||||
*/
|
||||
export type ListEnumRecurringBookingGroupStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'RecurringBookingGroupStatus[]'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Json'
|
||||
*/
|
||||
@@ -2172,6 +1948,20 @@ export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'J
|
||||
export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Float'
|
||||
*/
|
||||
export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Float[]'
|
||||
*/
|
||||
export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'>
|
||||
|
||||
|
||||
/**
|
||||
* Batch Payload for updateMany & deleteMany & createMany
|
||||
*/
|
||||
@@ -2276,12 +2066,10 @@ export type GlobalOmitConfig = {
|
||||
complexInvitation?: Prisma.ComplexInvitationOmit
|
||||
sport?: Prisma.SportOmit
|
||||
court?: Prisma.CourtOmit
|
||||
courtMaintenance?: Prisma.CourtMaintenanceOmit
|
||||
courtAvailability?: Prisma.CourtAvailabilityOmit
|
||||
courtPriceRule?: Prisma.CourtPriceRuleOmit
|
||||
courtBooking?: Prisma.CourtBookingOmit
|
||||
courtBookingLog?: Prisma.CourtBookingLogOmit
|
||||
recurringBookingGroup?: Prisma.RecurringBookingGroupOmit
|
||||
onboardingRequest?: Prisma.OnboardingRequestOmit
|
||||
passwordResetRequest?: Prisma.PasswordResetRequestOmit
|
||||
plan?: Prisma.PlanOmit
|
||||
}
|
||||
|
||||
@@ -60,12 +60,10 @@ export const ModelName = {
|
||||
ComplexInvitation: 'ComplexInvitation',
|
||||
Sport: 'Sport',
|
||||
Court: 'Court',
|
||||
CourtMaintenance: 'CourtMaintenance',
|
||||
CourtAvailability: 'CourtAvailability',
|
||||
CourtPriceRule: 'CourtPriceRule',
|
||||
CourtBooking: 'CourtBooking',
|
||||
CourtBookingLog: 'CourtBookingLog',
|
||||
RecurringBookingGroup: 'RecurringBookingGroup',
|
||||
OnboardingRequest: 'OnboardingRequest',
|
||||
PasswordResetRequest: 'PasswordResetRequest',
|
||||
Plan: 'Plan'
|
||||
} as const
|
||||
@@ -92,10 +90,6 @@ export const UserScalarFieldEnum = {
|
||||
email: 'email',
|
||||
emailVerified: 'emailVerified',
|
||||
image: 'image',
|
||||
phone: 'phone',
|
||||
banned: 'banned',
|
||||
bannedAt: 'bannedAt',
|
||||
banReason: 'banReason',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
role: 'role'
|
||||
@@ -112,11 +106,6 @@ export const SessionScalarFieldEnum = {
|
||||
updatedAt: 'updatedAt',
|
||||
ipAddress: 'ipAddress',
|
||||
userAgent: 'userAgent',
|
||||
country: 'country',
|
||||
city: 'city',
|
||||
countryCode: 'countryCode',
|
||||
latitude: 'latitude',
|
||||
longitude: 'longitude',
|
||||
userId: 'userId'
|
||||
} as const
|
||||
|
||||
@@ -215,8 +204,6 @@ export const CourtScalarFieldEnum = {
|
||||
name: 'name',
|
||||
slotDurationMinutes: 'slotDurationMinutes',
|
||||
basePrice: 'basePrice',
|
||||
isUnderMaintenance: 'isUnderMaintenance',
|
||||
maintenanceReason: 'maintenanceReason',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
@@ -224,20 +211,6 @@ export const CourtScalarFieldEnum = {
|
||||
export type CourtScalarFieldEnum = (typeof CourtScalarFieldEnum)[keyof typeof CourtScalarFieldEnum]
|
||||
|
||||
|
||||
export const CourtMaintenanceScalarFieldEnum = {
|
||||
id: 'id',
|
||||
courtId: 'courtId',
|
||||
startDate: 'startDate',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
reason: 'reason',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type CourtMaintenanceScalarFieldEnum = (typeof CourtMaintenanceScalarFieldEnum)[keyof typeof CourtMaintenanceScalarFieldEnum]
|
||||
|
||||
|
||||
export const CourtAvailabilityScalarFieldEnum = {
|
||||
id: 'id',
|
||||
courtId: 'courtId',
|
||||
@@ -272,11 +245,10 @@ export const CourtBookingScalarFieldEnum = {
|
||||
bookingDate: 'bookingDate',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
price: 'price',
|
||||
customerName: 'customerName',
|
||||
customerPhone: 'customerPhone',
|
||||
customerEmail: 'customerEmail',
|
||||
status: 'status',
|
||||
recurringGroupId: 'recurringGroupId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
@@ -284,42 +256,22 @@ export const CourtBookingScalarFieldEnum = {
|
||||
export type CourtBookingScalarFieldEnum = (typeof CourtBookingScalarFieldEnum)[keyof typeof CourtBookingScalarFieldEnum]
|
||||
|
||||
|
||||
export const CourtBookingLogScalarFieldEnum = {
|
||||
export const OnboardingRequestScalarFieldEnum = {
|
||||
id: 'id',
|
||||
bookingCode: 'bookingCode',
|
||||
courtId: 'courtId',
|
||||
bookingDate: 'bookingDate',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
previousStatus: 'previousStatus',
|
||||
newStatus: 'newStatus',
|
||||
customerName: 'customerName',
|
||||
customerPhone: 'customerPhone',
|
||||
customerEmail: 'customerEmail',
|
||||
changedAt: 'changedAt'
|
||||
} as const
|
||||
|
||||
export type CourtBookingLogScalarFieldEnum = (typeof CourtBookingLogScalarFieldEnum)[keyof typeof CourtBookingLogScalarFieldEnum]
|
||||
|
||||
|
||||
export const RecurringBookingGroupScalarFieldEnum = {
|
||||
id: 'id',
|
||||
complexId: 'complexId',
|
||||
courtId: 'courtId',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
dayOfWeek: 'dayOfWeek',
|
||||
startDate: 'startDate',
|
||||
endDate: 'endDate',
|
||||
status: 'status',
|
||||
customerName: 'customerName',
|
||||
customerPhone: 'customerPhone',
|
||||
customerEmail: 'customerEmail',
|
||||
fullName: 'fullName',
|
||||
email: 'email',
|
||||
otpHash: 'otpHash',
|
||||
otpExpiresAt: 'otpExpiresAt',
|
||||
otpAttempts: 'otpAttempts',
|
||||
otpLastSentAt: 'otpLastSentAt',
|
||||
otpResendCount: 'otpResendCount',
|
||||
emailVerifiedAt: 'emailVerifiedAt',
|
||||
completedAt: 'completedAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
|
||||
export type RecurringBookingGroupScalarFieldEnum = (typeof RecurringBookingGroupScalarFieldEnum)[keyof typeof RecurringBookingGroupScalarFieldEnum]
|
||||
export type OnboardingRequestScalarFieldEnum = (typeof OnboardingRequestScalarFieldEnum)[keyof typeof OnboardingRequestScalarFieldEnum]
|
||||
|
||||
|
||||
export const PasswordResetRequestScalarFieldEnum = {
|
||||
|
||||
@@ -17,12 +17,10 @@ export type * from './models/ComplexUser'
|
||||
export type * from './models/ComplexInvitation'
|
||||
export type * from './models/Sport'
|
||||
export type * from './models/Court'
|
||||
export type * from './models/CourtMaintenance'
|
||||
export type * from './models/CourtAvailability'
|
||||
export type * from './models/CourtPriceRule'
|
||||
export type * from './models/CourtBooking'
|
||||
export type * from './models/CourtBookingLog'
|
||||
export type * from './models/RecurringBookingGroup'
|
||||
export type * from './models/OnboardingRequest'
|
||||
export type * from './models/PasswordResetRequest'
|
||||
export type * from './models/Plan'
|
||||
export type * from './commonInputTypes'
|
||||
@@ -234,7 +234,6 @@ export type ComplexWhereInput = {
|
||||
users?: Prisma.ComplexUserListRelationFilter
|
||||
invitations?: Prisma.ComplexInvitationListRelationFilter
|
||||
courts?: Prisma.CourtListRelationFilter
|
||||
recurringGroups?: Prisma.RecurringBookingGroupListRelationFilter
|
||||
}
|
||||
|
||||
export type ComplexOrderByWithRelationInput = {
|
||||
@@ -253,7 +252,6 @@ export type ComplexOrderByWithRelationInput = {
|
||||
users?: Prisma.ComplexUserOrderByRelationAggregateInput
|
||||
invitations?: Prisma.ComplexInvitationOrderByRelationAggregateInput
|
||||
courts?: Prisma.CourtOrderByRelationAggregateInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupOrderByRelationAggregateInput
|
||||
}
|
||||
|
||||
export type ComplexWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -275,7 +273,6 @@ export type ComplexWhereUniqueInput = Prisma.AtLeast<{
|
||||
users?: Prisma.ComplexUserListRelationFilter
|
||||
invitations?: Prisma.ComplexInvitationListRelationFilter
|
||||
courts?: Prisma.CourtListRelationFilter
|
||||
recurringGroups?: Prisma.RecurringBookingGroupListRelationFilter
|
||||
}, "id" | "complexSlug">
|
||||
|
||||
export type ComplexOrderByWithAggregationInput = {
|
||||
@@ -327,7 +324,6 @@ export type ComplexCreateInput = {
|
||||
users?: Prisma.ComplexUserCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedCreateInput = {
|
||||
@@ -345,7 +341,6 @@ export type ComplexUncheckedCreateInput = {
|
||||
users?: Prisma.ComplexUserUncheckedCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtUncheckedCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUpdateInput = {
|
||||
@@ -363,7 +358,6 @@ export type ComplexUpdateInput = {
|
||||
users?: Prisma.ComplexUserUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateInput = {
|
||||
@@ -381,7 +375,6 @@ export type ComplexUncheckedUpdateInput = {
|
||||
users?: Prisma.ComplexUserUncheckedUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUncheckedUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexCreateManyInput = {
|
||||
@@ -524,20 +517,6 @@ export type ComplexUpdateOneRequiredWithoutCourtsNestedInput = {
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ComplexUpdateToOneWithWhereWithoutCourtsInput, Prisma.ComplexUpdateWithoutCourtsInput>, Prisma.ComplexUncheckedUpdateWithoutCourtsInput>
|
||||
}
|
||||
|
||||
export type ComplexCreateNestedOneWithoutRecurringGroupsInput = {
|
||||
create?: Prisma.XOR<Prisma.ComplexCreateWithoutRecurringGroupsInput, Prisma.ComplexUncheckedCreateWithoutRecurringGroupsInput>
|
||||
connectOrCreate?: Prisma.ComplexCreateOrConnectWithoutRecurringGroupsInput
|
||||
connect?: Prisma.ComplexWhereUniqueInput
|
||||
}
|
||||
|
||||
export type ComplexUpdateOneRequiredWithoutRecurringGroupsNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.ComplexCreateWithoutRecurringGroupsInput, Prisma.ComplexUncheckedCreateWithoutRecurringGroupsInput>
|
||||
connectOrCreate?: Prisma.ComplexCreateOrConnectWithoutRecurringGroupsInput
|
||||
upsert?: Prisma.ComplexUpsertWithoutRecurringGroupsInput
|
||||
connect?: Prisma.ComplexWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ComplexUpdateToOneWithWhereWithoutRecurringGroupsInput, Prisma.ComplexUpdateWithoutRecurringGroupsInput>, Prisma.ComplexUncheckedUpdateWithoutRecurringGroupsInput>
|
||||
}
|
||||
|
||||
export type ComplexCreateNestedManyWithoutPlanInput = {
|
||||
create?: Prisma.XOR<Prisma.ComplexCreateWithoutPlanInput, Prisma.ComplexUncheckedCreateWithoutPlanInput> | Prisma.ComplexCreateWithoutPlanInput[] | Prisma.ComplexUncheckedCreateWithoutPlanInput[]
|
||||
connectOrCreate?: Prisma.ComplexCreateOrConnectWithoutPlanInput | Prisma.ComplexCreateOrConnectWithoutPlanInput[]
|
||||
@@ -594,7 +573,6 @@ export type ComplexCreateWithoutUsersInput = {
|
||||
plan?: Prisma.PlanCreateNestedOneWithoutComplexesInput
|
||||
invitations?: Prisma.ComplexInvitationCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedCreateWithoutUsersInput = {
|
||||
@@ -611,7 +589,6 @@ export type ComplexUncheckedCreateWithoutUsersInput = {
|
||||
updatedAt?: Date | string
|
||||
invitations?: Prisma.ComplexInvitationUncheckedCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtUncheckedCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexCreateOrConnectWithoutUsersInput = {
|
||||
@@ -644,7 +621,6 @@ export type ComplexUpdateWithoutUsersInput = {
|
||||
plan?: Prisma.PlanUpdateOneWithoutComplexesNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateWithoutUsersInput = {
|
||||
@@ -661,7 +637,6 @@ export type ComplexUncheckedUpdateWithoutUsersInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
invitations?: Prisma.ComplexInvitationUncheckedUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUncheckedUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexCreateWithoutInvitationsInput = {
|
||||
@@ -678,7 +653,6 @@ export type ComplexCreateWithoutInvitationsInput = {
|
||||
plan?: Prisma.PlanCreateNestedOneWithoutComplexesInput
|
||||
users?: Prisma.ComplexUserCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedCreateWithoutInvitationsInput = {
|
||||
@@ -695,7 +669,6 @@ export type ComplexUncheckedCreateWithoutInvitationsInput = {
|
||||
updatedAt?: Date | string
|
||||
users?: Prisma.ComplexUserUncheckedCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtUncheckedCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexCreateOrConnectWithoutInvitationsInput = {
|
||||
@@ -728,7 +701,6 @@ export type ComplexUpdateWithoutInvitationsInput = {
|
||||
plan?: Prisma.PlanUpdateOneWithoutComplexesNestedInput
|
||||
users?: Prisma.ComplexUserUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateWithoutInvitationsInput = {
|
||||
@@ -745,7 +717,6 @@ export type ComplexUncheckedUpdateWithoutInvitationsInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
users?: Prisma.ComplexUserUncheckedUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUncheckedUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexCreateWithoutCourtsInput = {
|
||||
@@ -762,7 +733,6 @@ export type ComplexCreateWithoutCourtsInput = {
|
||||
plan?: Prisma.PlanCreateNestedOneWithoutComplexesInput
|
||||
users?: Prisma.ComplexUserCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedCreateWithoutCourtsInput = {
|
||||
@@ -779,7 +749,6 @@ export type ComplexUncheckedCreateWithoutCourtsInput = {
|
||||
updatedAt?: Date | string
|
||||
users?: Prisma.ComplexUserUncheckedCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexCreateOrConnectWithoutCourtsInput = {
|
||||
@@ -812,7 +781,6 @@ export type ComplexUpdateWithoutCourtsInput = {
|
||||
plan?: Prisma.PlanUpdateOneWithoutComplexesNestedInput
|
||||
users?: Prisma.ComplexUserUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateWithoutCourtsInput = {
|
||||
@@ -829,91 +797,6 @@ export type ComplexUncheckedUpdateWithoutCourtsInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
users?: Prisma.ComplexUserUncheckedUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexCreateWithoutRecurringGroupsInput = {
|
||||
id: string
|
||||
complexName: string
|
||||
physicalAddress?: string | null
|
||||
city?: string | null
|
||||
state?: string | null
|
||||
country?: string | null
|
||||
complexSlug: string
|
||||
adminEmail: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
plan?: Prisma.PlanCreateNestedOneWithoutComplexesInput
|
||||
users?: Prisma.ComplexUserCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedCreateWithoutRecurringGroupsInput = {
|
||||
id: string
|
||||
complexName: string
|
||||
physicalAddress?: string | null
|
||||
city?: string | null
|
||||
state?: string | null
|
||||
country?: string | null
|
||||
complexSlug: string
|
||||
adminEmail: string
|
||||
planCode?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
users?: Prisma.ComplexUserUncheckedCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtUncheckedCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexCreateOrConnectWithoutRecurringGroupsInput = {
|
||||
where: Prisma.ComplexWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.ComplexCreateWithoutRecurringGroupsInput, Prisma.ComplexUncheckedCreateWithoutRecurringGroupsInput>
|
||||
}
|
||||
|
||||
export type ComplexUpsertWithoutRecurringGroupsInput = {
|
||||
update: Prisma.XOR<Prisma.ComplexUpdateWithoutRecurringGroupsInput, Prisma.ComplexUncheckedUpdateWithoutRecurringGroupsInput>
|
||||
create: Prisma.XOR<Prisma.ComplexCreateWithoutRecurringGroupsInput, Prisma.ComplexUncheckedCreateWithoutRecurringGroupsInput>
|
||||
where?: Prisma.ComplexWhereInput
|
||||
}
|
||||
|
||||
export type ComplexUpdateToOneWithWhereWithoutRecurringGroupsInput = {
|
||||
where?: Prisma.ComplexWhereInput
|
||||
data: Prisma.XOR<Prisma.ComplexUpdateWithoutRecurringGroupsInput, Prisma.ComplexUncheckedUpdateWithoutRecurringGroupsInput>
|
||||
}
|
||||
|
||||
export type ComplexUpdateWithoutRecurringGroupsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
complexName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
physicalAddress?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
state?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
country?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
complexSlug?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
adminEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
plan?: Prisma.PlanUpdateOneWithoutComplexesNestedInput
|
||||
users?: Prisma.ComplexUserUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateWithoutRecurringGroupsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
complexName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
physicalAddress?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
city?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
state?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
country?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
complexSlug?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
adminEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
planCode?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
users?: Prisma.ComplexUserUncheckedUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUncheckedUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexCreateWithoutPlanInput = {
|
||||
@@ -930,7 +813,6 @@ export type ComplexCreateWithoutPlanInput = {
|
||||
users?: Prisma.ComplexUserCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedCreateWithoutPlanInput = {
|
||||
@@ -947,7 +829,6 @@ export type ComplexUncheckedCreateWithoutPlanInput = {
|
||||
users?: Prisma.ComplexUserUncheckedCreateNestedManyWithoutComplexInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedCreateNestedManyWithoutComplexInput
|
||||
courts?: Prisma.CourtUncheckedCreateNestedManyWithoutComplexInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutComplexInput
|
||||
}
|
||||
|
||||
export type ComplexCreateOrConnectWithoutPlanInput = {
|
||||
@@ -1020,7 +901,6 @@ export type ComplexUpdateWithoutPlanInput = {
|
||||
users?: Prisma.ComplexUserUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateWithoutPlanInput = {
|
||||
@@ -1037,7 +917,6 @@ export type ComplexUncheckedUpdateWithoutPlanInput = {
|
||||
users?: Prisma.ComplexUserUncheckedUpdateManyWithoutComplexNestedInput
|
||||
invitations?: Prisma.ComplexInvitationUncheckedUpdateManyWithoutComplexNestedInput
|
||||
courts?: Prisma.CourtUncheckedUpdateManyWithoutComplexNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutComplexNestedInput
|
||||
}
|
||||
|
||||
export type ComplexUncheckedUpdateManyWithoutPlanInput = {
|
||||
@@ -1062,14 +941,12 @@ export type ComplexCountOutputType = {
|
||||
users: number
|
||||
invitations: number
|
||||
courts: number
|
||||
recurringGroups: number
|
||||
}
|
||||
|
||||
export type ComplexCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
users?: boolean | ComplexCountOutputTypeCountUsersArgs
|
||||
invitations?: boolean | ComplexCountOutputTypeCountInvitationsArgs
|
||||
courts?: boolean | ComplexCountOutputTypeCountCourtsArgs
|
||||
recurringGroups?: boolean | ComplexCountOutputTypeCountRecurringGroupsArgs
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1103,13 +980,6 @@ export type ComplexCountOutputTypeCountCourtsArgs<ExtArgs extends runtime.Types.
|
||||
where?: Prisma.CourtWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* ComplexCountOutputType without action
|
||||
*/
|
||||
export type ComplexCountOutputTypeCountRecurringGroupsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.RecurringBookingGroupWhereInput
|
||||
}
|
||||
|
||||
|
||||
export type ComplexSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
@@ -1127,7 +997,6 @@ export type ComplexSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||
users?: boolean | Prisma.Complex$usersArgs<ExtArgs>
|
||||
invitations?: boolean | Prisma.Complex$invitationsArgs<ExtArgs>
|
||||
courts?: boolean | Prisma.Complex$courtsArgs<ExtArgs>
|
||||
recurringGroups?: boolean | Prisma.Complex$recurringGroupsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.ComplexCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["complex"]>
|
||||
|
||||
@@ -1181,7 +1050,6 @@ export type ComplexInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||
users?: boolean | Prisma.Complex$usersArgs<ExtArgs>
|
||||
invitations?: boolean | Prisma.Complex$invitationsArgs<ExtArgs>
|
||||
courts?: boolean | Prisma.Complex$courtsArgs<ExtArgs>
|
||||
recurringGroups?: boolean | Prisma.Complex$recurringGroupsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.ComplexCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type ComplexIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
@@ -1198,7 +1066,6 @@ export type $ComplexPayload<ExtArgs extends runtime.Types.Extensions.InternalArg
|
||||
users: Prisma.$ComplexUserPayload<ExtArgs>[]
|
||||
invitations: Prisma.$ComplexInvitationPayload<ExtArgs>[]
|
||||
courts: Prisma.$CourtPayload<ExtArgs>[]
|
||||
recurringGroups: Prisma.$RecurringBookingGroupPayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
@@ -1610,7 +1477,6 @@ export interface Prisma__ComplexClient<T, Null = never, ExtArgs extends runtime.
|
||||
users<T extends Prisma.Complex$usersArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Complex$usersArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ComplexUserPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
invitations<T extends Prisma.Complex$invitationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Complex$invitationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ComplexInvitationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
courts<T extends Prisma.Complex$courtsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Complex$courtsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CourtPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
recurringGroups<T extends Prisma.Complex$recurringGroupsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Complex$recurringGroupsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$RecurringBookingGroupPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -2142,30 +2008,6 @@ export type Complex$courtsArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
distinct?: Prisma.CourtScalarFieldEnum | Prisma.CourtScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Complex.recurringGroups
|
||||
*/
|
||||
export type Complex$recurringGroupsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the RecurringBookingGroup
|
||||
*/
|
||||
select?: Prisma.RecurringBookingGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the RecurringBookingGroup
|
||||
*/
|
||||
omit?: Prisma.RecurringBookingGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.RecurringBookingGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.RecurringBookingGroupWhereInput
|
||||
orderBy?: Prisma.RecurringBookingGroupOrderByWithRelationInput | Prisma.RecurringBookingGroupOrderByWithRelationInput[]
|
||||
cursor?: Prisma.RecurringBookingGroupWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.RecurringBookingGroupScalarFieldEnum | Prisma.RecurringBookingGroupScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Complex without action
|
||||
*/
|
||||
|
||||
@@ -43,8 +43,6 @@ export type CourtMinAggregateOutputType = {
|
||||
name: string | null
|
||||
slotDurationMinutes: number | null
|
||||
basePrice: runtime.Decimal | null
|
||||
isUnderMaintenance: boolean | null
|
||||
maintenanceReason: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
}
|
||||
@@ -56,8 +54,6 @@ export type CourtMaxAggregateOutputType = {
|
||||
name: string | null
|
||||
slotDurationMinutes: number | null
|
||||
basePrice: runtime.Decimal | null
|
||||
isUnderMaintenance: boolean | null
|
||||
maintenanceReason: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
}
|
||||
@@ -69,8 +65,6 @@ export type CourtCountAggregateOutputType = {
|
||||
name: number
|
||||
slotDurationMinutes: number
|
||||
basePrice: number
|
||||
isUnderMaintenance: number
|
||||
maintenanceReason: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
_all: number
|
||||
@@ -94,8 +88,6 @@ export type CourtMinAggregateInputType = {
|
||||
name?: true
|
||||
slotDurationMinutes?: true
|
||||
basePrice?: true
|
||||
isUnderMaintenance?: true
|
||||
maintenanceReason?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
}
|
||||
@@ -107,8 +99,6 @@ export type CourtMaxAggregateInputType = {
|
||||
name?: true
|
||||
slotDurationMinutes?: true
|
||||
basePrice?: true
|
||||
isUnderMaintenance?: true
|
||||
maintenanceReason?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
}
|
||||
@@ -120,8 +110,6 @@ export type CourtCountAggregateInputType = {
|
||||
name?: true
|
||||
slotDurationMinutes?: true
|
||||
basePrice?: true
|
||||
isUnderMaintenance?: true
|
||||
maintenanceReason?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
_all?: true
|
||||
@@ -220,8 +208,6 @@ export type CourtGroupByOutputType = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal
|
||||
isUnderMaintenance: boolean
|
||||
maintenanceReason: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
_count: CourtCountAggregateOutputType | null
|
||||
@@ -256,8 +242,6 @@ export type CourtWhereInput = {
|
||||
name?: Prisma.StringFilter<"Court"> | string
|
||||
slotDurationMinutes?: Prisma.IntFilter<"Court"> | number
|
||||
basePrice?: Prisma.DecimalFilter<"Court"> | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFilter<"Court"> | boolean
|
||||
maintenanceReason?: Prisma.StringNullableFilter<"Court"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"Court"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Court"> | Date | string
|
||||
complex?: Prisma.XOR<Prisma.ComplexScalarRelationFilter, Prisma.ComplexWhereInput>
|
||||
@@ -265,8 +249,6 @@ export type CourtWhereInput = {
|
||||
availabilities?: Prisma.CourtAvailabilityListRelationFilter
|
||||
priceRules?: Prisma.CourtPriceRuleListRelationFilter
|
||||
bookings?: Prisma.CourtBookingListRelationFilter
|
||||
maintenances?: Prisma.CourtMaintenanceListRelationFilter
|
||||
recurringGroups?: Prisma.RecurringBookingGroupListRelationFilter
|
||||
}
|
||||
|
||||
export type CourtOrderByWithRelationInput = {
|
||||
@@ -276,8 +258,6 @@ export type CourtOrderByWithRelationInput = {
|
||||
name?: Prisma.SortOrder
|
||||
slotDurationMinutes?: Prisma.SortOrder
|
||||
basePrice?: Prisma.SortOrder
|
||||
isUnderMaintenance?: Prisma.SortOrder
|
||||
maintenanceReason?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
complex?: Prisma.ComplexOrderByWithRelationInput
|
||||
@@ -285,8 +265,6 @@ export type CourtOrderByWithRelationInput = {
|
||||
availabilities?: Prisma.CourtAvailabilityOrderByRelationAggregateInput
|
||||
priceRules?: Prisma.CourtPriceRuleOrderByRelationAggregateInput
|
||||
bookings?: Prisma.CourtBookingOrderByRelationAggregateInput
|
||||
maintenances?: Prisma.CourtMaintenanceOrderByRelationAggregateInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupOrderByRelationAggregateInput
|
||||
}
|
||||
|
||||
export type CourtWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -299,8 +277,6 @@ export type CourtWhereUniqueInput = Prisma.AtLeast<{
|
||||
name?: Prisma.StringFilter<"Court"> | string
|
||||
slotDurationMinutes?: Prisma.IntFilter<"Court"> | number
|
||||
basePrice?: Prisma.DecimalFilter<"Court"> | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFilter<"Court"> | boolean
|
||||
maintenanceReason?: Prisma.StringNullableFilter<"Court"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"Court"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Court"> | Date | string
|
||||
complex?: Prisma.XOR<Prisma.ComplexScalarRelationFilter, Prisma.ComplexWhereInput>
|
||||
@@ -308,8 +284,6 @@ export type CourtWhereUniqueInput = Prisma.AtLeast<{
|
||||
availabilities?: Prisma.CourtAvailabilityListRelationFilter
|
||||
priceRules?: Prisma.CourtPriceRuleListRelationFilter
|
||||
bookings?: Prisma.CourtBookingListRelationFilter
|
||||
maintenances?: Prisma.CourtMaintenanceListRelationFilter
|
||||
recurringGroups?: Prisma.RecurringBookingGroupListRelationFilter
|
||||
}, "id">
|
||||
|
||||
export type CourtOrderByWithAggregationInput = {
|
||||
@@ -319,8 +293,6 @@ export type CourtOrderByWithAggregationInput = {
|
||||
name?: Prisma.SortOrder
|
||||
slotDurationMinutes?: Prisma.SortOrder
|
||||
basePrice?: Prisma.SortOrder
|
||||
isUnderMaintenance?: Prisma.SortOrder
|
||||
maintenanceReason?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
_count?: Prisma.CourtCountOrderByAggregateInput
|
||||
@@ -340,8 +312,6 @@ export type CourtScalarWhereWithAggregatesInput = {
|
||||
name?: Prisma.StringWithAggregatesFilter<"Court"> | string
|
||||
slotDurationMinutes?: Prisma.IntWithAggregatesFilter<"Court"> | number
|
||||
basePrice?: Prisma.DecimalWithAggregatesFilter<"Court"> | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolWithAggregatesFilter<"Court"> | boolean
|
||||
maintenanceReason?: Prisma.StringNullableWithAggregatesFilter<"Court"> | string | null
|
||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"Court"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Court"> | Date | string
|
||||
}
|
||||
@@ -351,8 +321,6 @@ export type CourtCreateInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
@@ -360,8 +328,6 @@ export type CourtCreateInput = {
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateInput = {
|
||||
@@ -371,15 +337,11 @@ export type CourtUncheckedCreateInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUpdateInput = {
|
||||
@@ -387,8 +349,6 @@ export type CourtUpdateInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
@@ -396,8 +356,6 @@ export type CourtUpdateInput = {
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateInput = {
|
||||
@@ -407,15 +365,11 @@ export type CourtUncheckedUpdateInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtCreateManyInput = {
|
||||
@@ -425,8 +379,6 @@ export type CourtCreateManyInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -436,8 +388,6 @@ export type CourtUpdateManyMutationInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -449,8 +399,6 @@ export type CourtUncheckedUpdateManyInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -472,8 +420,6 @@ export type CourtCountOrderByAggregateInput = {
|
||||
name?: Prisma.SortOrder
|
||||
slotDurationMinutes?: Prisma.SortOrder
|
||||
basePrice?: Prisma.SortOrder
|
||||
isUnderMaintenance?: Prisma.SortOrder
|
||||
maintenanceReason?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -490,8 +436,6 @@ export type CourtMaxOrderByAggregateInput = {
|
||||
name?: Prisma.SortOrder
|
||||
slotDurationMinutes?: Prisma.SortOrder
|
||||
basePrice?: Prisma.SortOrder
|
||||
isUnderMaintenance?: Prisma.SortOrder
|
||||
maintenanceReason?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -503,8 +447,6 @@ export type CourtMinOrderByAggregateInput = {
|
||||
name?: Prisma.SortOrder
|
||||
slotDurationMinutes?: Prisma.SortOrder
|
||||
basePrice?: Prisma.SortOrder
|
||||
isUnderMaintenance?: Prisma.SortOrder
|
||||
maintenanceReason?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -619,20 +561,6 @@ export type DecimalFieldUpdateOperationsInput = {
|
||||
divide?: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
}
|
||||
|
||||
export type CourtCreateNestedOneWithoutMaintenancesInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtCreateWithoutMaintenancesInput, Prisma.CourtUncheckedCreateWithoutMaintenancesInput>
|
||||
connectOrCreate?: Prisma.CourtCreateOrConnectWithoutMaintenancesInput
|
||||
connect?: Prisma.CourtWhereUniqueInput
|
||||
}
|
||||
|
||||
export type CourtUpdateOneRequiredWithoutMaintenancesNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtCreateWithoutMaintenancesInput, Prisma.CourtUncheckedCreateWithoutMaintenancesInput>
|
||||
connectOrCreate?: Prisma.CourtCreateOrConnectWithoutMaintenancesInput
|
||||
upsert?: Prisma.CourtUpsertWithoutMaintenancesInput
|
||||
connect?: Prisma.CourtWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CourtUpdateToOneWithWhereWithoutMaintenancesInput, Prisma.CourtUpdateWithoutMaintenancesInput>, Prisma.CourtUncheckedUpdateWithoutMaintenancesInput>
|
||||
}
|
||||
|
||||
export type CourtCreateNestedOneWithoutAvailabilitiesInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtCreateWithoutAvailabilitiesInput, Prisma.CourtUncheckedCreateWithoutAvailabilitiesInput>
|
||||
connectOrCreate?: Prisma.CourtCreateOrConnectWithoutAvailabilitiesInput
|
||||
@@ -675,35 +603,17 @@ export type CourtUpdateOneRequiredWithoutBookingsNestedInput = {
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CourtUpdateToOneWithWhereWithoutBookingsInput, Prisma.CourtUpdateWithoutBookingsInput>, Prisma.CourtUncheckedUpdateWithoutBookingsInput>
|
||||
}
|
||||
|
||||
export type CourtCreateNestedOneWithoutRecurringGroupsInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtCreateWithoutRecurringGroupsInput, Prisma.CourtUncheckedCreateWithoutRecurringGroupsInput>
|
||||
connectOrCreate?: Prisma.CourtCreateOrConnectWithoutRecurringGroupsInput
|
||||
connect?: Prisma.CourtWhereUniqueInput
|
||||
}
|
||||
|
||||
export type CourtUpdateOneRequiredWithoutRecurringGroupsNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtCreateWithoutRecurringGroupsInput, Prisma.CourtUncheckedCreateWithoutRecurringGroupsInput>
|
||||
connectOrCreate?: Prisma.CourtCreateOrConnectWithoutRecurringGroupsInput
|
||||
upsert?: Prisma.CourtUpsertWithoutRecurringGroupsInput
|
||||
connect?: Prisma.CourtWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.CourtUpdateToOneWithWhereWithoutRecurringGroupsInput, Prisma.CourtUpdateWithoutRecurringGroupsInput>, Prisma.CourtUncheckedUpdateWithoutRecurringGroupsInput>
|
||||
}
|
||||
|
||||
export type CourtCreateWithoutComplexInput = {
|
||||
id: string
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
sport: Prisma.SportCreateNestedOneWithoutCourtsInput
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutComplexInput = {
|
||||
@@ -712,15 +622,11 @@ export type CourtUncheckedCreateWithoutComplexInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutComplexInput = {
|
||||
@@ -759,8 +665,6 @@ export type CourtScalarWhereInput = {
|
||||
name?: Prisma.StringFilter<"Court"> | string
|
||||
slotDurationMinutes?: Prisma.IntFilter<"Court"> | number
|
||||
basePrice?: Prisma.DecimalFilter<"Court"> | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFilter<"Court"> | boolean
|
||||
maintenanceReason?: Prisma.StringNullableFilter<"Court"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"Court"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Court"> | Date | string
|
||||
}
|
||||
@@ -770,16 +674,12 @@ export type CourtCreateWithoutSportInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutSportInput = {
|
||||
@@ -788,15 +688,11 @@ export type CourtUncheckedCreateWithoutSportInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutSportInput = {
|
||||
@@ -825,105 +721,17 @@ export type CourtUpdateManyWithWhereWithoutSportInput = {
|
||||
data: Prisma.XOR<Prisma.CourtUpdateManyMutationInput, Prisma.CourtUncheckedUpdateManyWithoutSportInput>
|
||||
}
|
||||
|
||||
export type CourtCreateWithoutMaintenancesInput = {
|
||||
id: string
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
sport: Prisma.SportCreateNestedOneWithoutCourtsInput
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutMaintenancesInput = {
|
||||
id: string
|
||||
complexId: string
|
||||
sportId: string
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutMaintenancesInput = {
|
||||
where: Prisma.CourtWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.CourtCreateWithoutMaintenancesInput, Prisma.CourtUncheckedCreateWithoutMaintenancesInput>
|
||||
}
|
||||
|
||||
export type CourtUpsertWithoutMaintenancesInput = {
|
||||
update: Prisma.XOR<Prisma.CourtUpdateWithoutMaintenancesInput, Prisma.CourtUncheckedUpdateWithoutMaintenancesInput>
|
||||
create: Prisma.XOR<Prisma.CourtCreateWithoutMaintenancesInput, Prisma.CourtUncheckedCreateWithoutMaintenancesInput>
|
||||
where?: Prisma.CourtWhereInput
|
||||
}
|
||||
|
||||
export type CourtUpdateToOneWithWhereWithoutMaintenancesInput = {
|
||||
where?: Prisma.CourtWhereInput
|
||||
data: Prisma.XOR<Prisma.CourtUpdateWithoutMaintenancesInput, Prisma.CourtUncheckedUpdateWithoutMaintenancesInput>
|
||||
}
|
||||
|
||||
export type CourtUpdateWithoutMaintenancesInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
sport?: Prisma.SportUpdateOneRequiredWithoutCourtsNestedInput
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutMaintenancesInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
complexId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
sportId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtCreateWithoutAvailabilitiesInput = {
|
||||
id: string
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
sport: Prisma.SportCreateNestedOneWithoutCourtsInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutAvailabilitiesInput = {
|
||||
@@ -933,14 +741,10 @@ export type CourtUncheckedCreateWithoutAvailabilitiesInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutAvailabilitiesInput = {
|
||||
@@ -964,16 +768,12 @@ export type CourtUpdateWithoutAvailabilitiesInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
sport?: Prisma.SportUpdateOneRequiredWithoutCourtsNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutAvailabilitiesInput = {
|
||||
@@ -983,14 +783,10 @@ export type CourtUncheckedUpdateWithoutAvailabilitiesInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtCreateWithoutPriceRulesInput = {
|
||||
@@ -998,16 +794,12 @@ export type CourtCreateWithoutPriceRulesInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
sport: Prisma.SportCreateNestedOneWithoutCourtsInput
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutPriceRulesInput = {
|
||||
@@ -1017,14 +809,10 @@ export type CourtUncheckedCreateWithoutPriceRulesInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutPriceRulesInput = {
|
||||
@@ -1048,16 +836,12 @@ export type CourtUpdateWithoutPriceRulesInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
sport?: Prisma.SportUpdateOneRequiredWithoutCourtsNestedInput
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutPriceRulesInput = {
|
||||
@@ -1067,14 +851,10 @@ export type CourtUncheckedUpdateWithoutPriceRulesInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtCreateWithoutBookingsInput = {
|
||||
@@ -1082,16 +862,12 @@ export type CourtCreateWithoutBookingsInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
sport: Prisma.SportCreateNestedOneWithoutCourtsInput
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutBookingsInput = {
|
||||
@@ -1101,14 +877,10 @@ export type CourtUncheckedCreateWithoutBookingsInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutBookingsInput = {
|
||||
@@ -1132,16 +904,12 @@ export type CourtUpdateWithoutBookingsInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
sport?: Prisma.SportUpdateOneRequiredWithoutCourtsNestedInput
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutBookingsInput = {
|
||||
@@ -1151,98 +919,10 @@ export type CourtUncheckedUpdateWithoutBookingsInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtCreateWithoutRecurringGroupsInput = {
|
||||
id: string
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
complex: Prisma.ComplexCreateNestedOneWithoutCourtsInput
|
||||
sport: Prisma.SportCreateNestedOneWithoutCourtsInput
|
||||
availabilities?: Prisma.CourtAvailabilityCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedCreateWithoutRecurringGroupsInput = {
|
||||
id: string
|
||||
complexId: string
|
||||
sportId: string
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedCreateNestedManyWithoutCourtInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedCreateNestedManyWithoutCourtInput
|
||||
bookings?: Prisma.CourtBookingUncheckedCreateNestedManyWithoutCourtInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedCreateNestedManyWithoutCourtInput
|
||||
}
|
||||
|
||||
export type CourtCreateOrConnectWithoutRecurringGroupsInput = {
|
||||
where: Prisma.CourtWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.CourtCreateWithoutRecurringGroupsInput, Prisma.CourtUncheckedCreateWithoutRecurringGroupsInput>
|
||||
}
|
||||
|
||||
export type CourtUpsertWithoutRecurringGroupsInput = {
|
||||
update: Prisma.XOR<Prisma.CourtUpdateWithoutRecurringGroupsInput, Prisma.CourtUncheckedUpdateWithoutRecurringGroupsInput>
|
||||
create: Prisma.XOR<Prisma.CourtCreateWithoutRecurringGroupsInput, Prisma.CourtUncheckedCreateWithoutRecurringGroupsInput>
|
||||
where?: Prisma.CourtWhereInput
|
||||
}
|
||||
|
||||
export type CourtUpdateToOneWithWhereWithoutRecurringGroupsInput = {
|
||||
where?: Prisma.CourtWhereInput
|
||||
data: Prisma.XOR<Prisma.CourtUpdateWithoutRecurringGroupsInput, Prisma.CourtUncheckedUpdateWithoutRecurringGroupsInput>
|
||||
}
|
||||
|
||||
export type CourtUpdateWithoutRecurringGroupsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
sport?: Prisma.SportUpdateOneRequiredWithoutCourtsNestedInput
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutRecurringGroupsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
complexId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
sportId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtCreateManyComplexInput = {
|
||||
@@ -1251,8 +931,6 @@ export type CourtCreateManyComplexInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -1262,16 +940,12 @@ export type CourtUpdateWithoutComplexInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
sport?: Prisma.SportUpdateOneRequiredWithoutCourtsNestedInput
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutComplexInput = {
|
||||
@@ -1280,15 +954,11 @@ export type CourtUncheckedUpdateWithoutComplexInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateManyWithoutComplexInput = {
|
||||
@@ -1297,8 +967,6 @@ export type CourtUncheckedUpdateManyWithoutComplexInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -1309,8 +977,6 @@ export type CourtCreateManySportInput = {
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -1320,16 +986,12 @@ export type CourtUpdateWithoutSportInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
complex?: Prisma.ComplexUpdateOneRequiredWithoutCourtsNestedInput
|
||||
availabilities?: Prisma.CourtAvailabilityUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateWithoutSportInput = {
|
||||
@@ -1338,15 +1000,11 @@ export type CourtUncheckedUpdateWithoutSportInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
availabilities?: Prisma.CourtAvailabilityUncheckedUpdateManyWithoutCourtNestedInput
|
||||
priceRules?: Prisma.CourtPriceRuleUncheckedUpdateManyWithoutCourtNestedInput
|
||||
bookings?: Prisma.CourtBookingUncheckedUpdateManyWithoutCourtNestedInput
|
||||
maintenances?: Prisma.CourtMaintenanceUncheckedUpdateManyWithoutCourtNestedInput
|
||||
recurringGroups?: Prisma.RecurringBookingGroupUncheckedUpdateManyWithoutCourtNestedInput
|
||||
}
|
||||
|
||||
export type CourtUncheckedUpdateManyWithoutSportInput = {
|
||||
@@ -1355,8 +1013,6 @@ export type CourtUncheckedUpdateManyWithoutSportInput = {
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slotDurationMinutes?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
basePrice?: Prisma.DecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
isUnderMaintenance?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
maintenanceReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -1370,16 +1026,12 @@ export type CourtCountOutputType = {
|
||||
availabilities: number
|
||||
priceRules: number
|
||||
bookings: number
|
||||
maintenances: number
|
||||
recurringGroups: number
|
||||
}
|
||||
|
||||
export type CourtCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
availabilities?: boolean | CourtCountOutputTypeCountAvailabilitiesArgs
|
||||
priceRules?: boolean | CourtCountOutputTypeCountPriceRulesArgs
|
||||
bookings?: boolean | CourtCountOutputTypeCountBookingsArgs
|
||||
maintenances?: boolean | CourtCountOutputTypeCountMaintenancesArgs
|
||||
recurringGroups?: boolean | CourtCountOutputTypeCountRecurringGroupsArgs
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1413,20 +1065,6 @@ export type CourtCountOutputTypeCountBookingsArgs<ExtArgs extends runtime.Types.
|
||||
where?: Prisma.CourtBookingWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* CourtCountOutputType without action
|
||||
*/
|
||||
export type CourtCountOutputTypeCountMaintenancesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.CourtMaintenanceWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* CourtCountOutputType without action
|
||||
*/
|
||||
export type CourtCountOutputTypeCountRecurringGroupsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.RecurringBookingGroupWhereInput
|
||||
}
|
||||
|
||||
|
||||
export type CourtSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
@@ -1435,8 +1073,6 @@ export type CourtSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
name?: boolean
|
||||
slotDurationMinutes?: boolean
|
||||
basePrice?: boolean
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
complex?: boolean | Prisma.ComplexDefaultArgs<ExtArgs>
|
||||
@@ -1444,8 +1080,6 @@ export type CourtSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
availabilities?: boolean | Prisma.Court$availabilitiesArgs<ExtArgs>
|
||||
priceRules?: boolean | Prisma.Court$priceRulesArgs<ExtArgs>
|
||||
bookings?: boolean | Prisma.Court$bookingsArgs<ExtArgs>
|
||||
maintenances?: boolean | Prisma.Court$maintenancesArgs<ExtArgs>
|
||||
recurringGroups?: boolean | Prisma.Court$recurringGroupsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.CourtCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["court"]>
|
||||
|
||||
@@ -1456,8 +1090,6 @@ export type CourtSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensi
|
||||
name?: boolean
|
||||
slotDurationMinutes?: boolean
|
||||
basePrice?: boolean
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
complex?: boolean | Prisma.ComplexDefaultArgs<ExtArgs>
|
||||
@@ -1471,8 +1103,6 @@ export type CourtSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensi
|
||||
name?: boolean
|
||||
slotDurationMinutes?: boolean
|
||||
basePrice?: boolean
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
complex?: boolean | Prisma.ComplexDefaultArgs<ExtArgs>
|
||||
@@ -1486,21 +1116,17 @@ export type CourtSelectScalar = {
|
||||
name?: boolean
|
||||
slotDurationMinutes?: boolean
|
||||
basePrice?: boolean
|
||||
isUnderMaintenance?: boolean
|
||||
maintenanceReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
}
|
||||
|
||||
export type CourtOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "complexId" | "sportId" | "name" | "slotDurationMinutes" | "basePrice" | "isUnderMaintenance" | "maintenanceReason" | "createdAt" | "updatedAt", ExtArgs["result"]["court"]>
|
||||
export type CourtOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "complexId" | "sportId" | "name" | "slotDurationMinutes" | "basePrice" | "createdAt" | "updatedAt", ExtArgs["result"]["court"]>
|
||||
export type CourtInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
complex?: boolean | Prisma.ComplexDefaultArgs<ExtArgs>
|
||||
sport?: boolean | Prisma.SportDefaultArgs<ExtArgs>
|
||||
availabilities?: boolean | Prisma.Court$availabilitiesArgs<ExtArgs>
|
||||
priceRules?: boolean | Prisma.Court$priceRulesArgs<ExtArgs>
|
||||
bookings?: boolean | Prisma.Court$bookingsArgs<ExtArgs>
|
||||
maintenances?: boolean | Prisma.Court$maintenancesArgs<ExtArgs>
|
||||
recurringGroups?: boolean | Prisma.Court$recurringGroupsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.CourtCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type CourtIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
@@ -1520,8 +1146,6 @@ export type $CourtPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||
availabilities: Prisma.$CourtAvailabilityPayload<ExtArgs>[]
|
||||
priceRules: Prisma.$CourtPriceRulePayload<ExtArgs>[]
|
||||
bookings: Prisma.$CourtBookingPayload<ExtArgs>[]
|
||||
maintenances: Prisma.$CourtMaintenancePayload<ExtArgs>[]
|
||||
recurringGroups: Prisma.$RecurringBookingGroupPayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
@@ -1530,8 +1154,6 @@ export type $CourtPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||
name: string
|
||||
slotDurationMinutes: number
|
||||
basePrice: runtime.Decimal
|
||||
isUnderMaintenance: boolean
|
||||
maintenanceReason: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}, ExtArgs["result"]["court"]>
|
||||
@@ -1933,8 +1555,6 @@ export interface Prisma__CourtClient<T, Null = never, ExtArgs extends runtime.Ty
|
||||
availabilities<T extends Prisma.Court$availabilitiesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Court$availabilitiesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CourtAvailabilityPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
priceRules<T extends Prisma.Court$priceRulesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Court$priceRulesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CourtPriceRulePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
bookings<T extends Prisma.Court$bookingsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Court$bookingsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CourtBookingPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
maintenances<T extends Prisma.Court$maintenancesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Court$maintenancesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CourtMaintenancePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
recurringGroups<T extends Prisma.Court$recurringGroupsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Court$recurringGroupsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$RecurringBookingGroupPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -1970,8 +1590,6 @@ export interface CourtFieldRefs {
|
||||
readonly name: Prisma.FieldRef<"Court", 'String'>
|
||||
readonly slotDurationMinutes: Prisma.FieldRef<"Court", 'Int'>
|
||||
readonly basePrice: Prisma.FieldRef<"Court", 'Decimal'>
|
||||
readonly isUnderMaintenance: Prisma.FieldRef<"Court", 'Boolean'>
|
||||
readonly maintenanceReason: Prisma.FieldRef<"Court", 'String'>
|
||||
readonly createdAt: Prisma.FieldRef<"Court", 'DateTime'>
|
||||
readonly updatedAt: Prisma.FieldRef<"Court", 'DateTime'>
|
||||
}
|
||||
@@ -2446,54 +2064,6 @@ export type Court$bookingsArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
distinct?: Prisma.CourtBookingScalarFieldEnum | Prisma.CourtBookingScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Court.maintenances
|
||||
*/
|
||||
export type Court$maintenancesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the CourtMaintenance
|
||||
*/
|
||||
select?: Prisma.CourtMaintenanceSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the CourtMaintenance
|
||||
*/
|
||||
omit?: Prisma.CourtMaintenanceOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.CourtMaintenanceInclude<ExtArgs> | null
|
||||
where?: Prisma.CourtMaintenanceWhereInput
|
||||
orderBy?: Prisma.CourtMaintenanceOrderByWithRelationInput | Prisma.CourtMaintenanceOrderByWithRelationInput[]
|
||||
cursor?: Prisma.CourtMaintenanceWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.CourtMaintenanceScalarFieldEnum | Prisma.CourtMaintenanceScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Court.recurringGroups
|
||||
*/
|
||||
export type Court$recurringGroupsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the RecurringBookingGroup
|
||||
*/
|
||||
select?: Prisma.RecurringBookingGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the RecurringBookingGroup
|
||||
*/
|
||||
omit?: Prisma.RecurringBookingGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.RecurringBookingGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.RecurringBookingGroupWhereInput
|
||||
orderBy?: Prisma.RecurringBookingGroupOrderByWithRelationInput | Prisma.RecurringBookingGroupOrderByWithRelationInput[]
|
||||
cursor?: Prisma.RecurringBookingGroupWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.RecurringBookingGroupScalarFieldEnum | Prisma.RecurringBookingGroupScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Court without action
|
||||
*/
|
||||
|
||||
@@ -20,10 +20,20 @@ export type CourtBookingModel = runtime.Types.Result.DefaultSelection<Prisma.$Co
|
||||
|
||||
export type AggregateCourtBooking = {
|
||||
_count: CourtBookingCountAggregateOutputType | null
|
||||
_avg: CourtBookingAvgAggregateOutputType | null
|
||||
_sum: CourtBookingSumAggregateOutputType | null
|
||||
_min: CourtBookingMinAggregateOutputType | null
|
||||
_max: CourtBookingMaxAggregateOutputType | null
|
||||
}
|
||||
|
||||
export type CourtBookingAvgAggregateOutputType = {
|
||||
price: runtime.Decimal | null
|
||||
}
|
||||
|
||||
export type CourtBookingSumAggregateOutputType = {
|
||||
price: runtime.Decimal | null
|
||||
}
|
||||
|
||||
export type CourtBookingMinAggregateOutputType = {
|
||||
id: string | null
|
||||
bookingCode: string | null
|
||||
@@ -31,11 +41,10 @@ export type CourtBookingMinAggregateOutputType = {
|
||||
bookingDate: Date | null
|
||||
startTime: string | null
|
||||
endTime: string | null
|
||||
price: runtime.Decimal | null
|
||||
customerName: string | null
|
||||
customerPhone: string | null
|
||||
customerEmail: string | null
|
||||
status: $Enums.CourtBookingStatus | null
|
||||
recurringGroupId: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
}
|
||||
@@ -47,11 +56,10 @@ export type CourtBookingMaxAggregateOutputType = {
|
||||
bookingDate: Date | null
|
||||
startTime: string | null
|
||||
endTime: string | null
|
||||
price: runtime.Decimal | null
|
||||
customerName: string | null
|
||||
customerPhone: string | null
|
||||
customerEmail: string | null
|
||||
status: $Enums.CourtBookingStatus | null
|
||||
recurringGroupId: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
}
|
||||
@@ -63,17 +71,24 @@ export type CourtBookingCountAggregateOutputType = {
|
||||
bookingDate: number
|
||||
startTime: number
|
||||
endTime: number
|
||||
price: number
|
||||
customerName: number
|
||||
customerPhone: number
|
||||
customerEmail: number
|
||||
status: number
|
||||
recurringGroupId: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
_all: number
|
||||
}
|
||||
|
||||
|
||||
export type CourtBookingAvgAggregateInputType = {
|
||||
price?: true
|
||||
}
|
||||
|
||||
export type CourtBookingSumAggregateInputType = {
|
||||
price?: true
|
||||
}
|
||||
|
||||
export type CourtBookingMinAggregateInputType = {
|
||||
id?: true
|
||||
bookingCode?: true
|
||||
@@ -81,11 +96,10 @@ export type CourtBookingMinAggregateInputType = {
|
||||
bookingDate?: true
|
||||
startTime?: true
|
||||
endTime?: true
|
||||
price?: true
|
||||
customerName?: true
|
||||
customerPhone?: true
|
||||
customerEmail?: true
|
||||
status?: true
|
||||
recurringGroupId?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
}
|
||||
@@ -97,11 +111,10 @@ export type CourtBookingMaxAggregateInputType = {
|
||||
bookingDate?: true
|
||||
startTime?: true
|
||||
endTime?: true
|
||||
price?: true
|
||||
customerName?: true
|
||||
customerPhone?: true
|
||||
customerEmail?: true
|
||||
status?: true
|
||||
recurringGroupId?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
}
|
||||
@@ -113,11 +126,10 @@ export type CourtBookingCountAggregateInputType = {
|
||||
bookingDate?: true
|
||||
startTime?: true
|
||||
endTime?: true
|
||||
price?: true
|
||||
customerName?: true
|
||||
customerPhone?: true
|
||||
customerEmail?: true
|
||||
status?: true
|
||||
recurringGroupId?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
_all?: true
|
||||
@@ -158,6 +170,18 @@ export type CourtBookingAggregateArgs<ExtArgs extends runtime.Types.Extensions.I
|
||||
* Count returned CourtBookings
|
||||
**/
|
||||
_count?: true | CourtBookingCountAggregateInputType
|
||||
/**
|
||||
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
|
||||
*
|
||||
* Select which fields to average
|
||||
**/
|
||||
_avg?: CourtBookingAvgAggregateInputType
|
||||
/**
|
||||
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
|
||||
*
|
||||
* Select which fields to sum
|
||||
**/
|
||||
_sum?: CourtBookingSumAggregateInputType
|
||||
/**
|
||||
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
|
||||
*
|
||||
@@ -191,6 +215,8 @@ export type CourtBookingGroupByArgs<ExtArgs extends runtime.Types.Extensions.Int
|
||||
take?: number
|
||||
skip?: number
|
||||
_count?: CourtBookingCountAggregateInputType | true
|
||||
_avg?: CourtBookingAvgAggregateInputType
|
||||
_sum?: CourtBookingSumAggregateInputType
|
||||
_min?: CourtBookingMinAggregateInputType
|
||||
_max?: CourtBookingMaxAggregateInputType
|
||||
}
|
||||
@@ -202,14 +228,15 @@ export type CourtBookingGroupByOutputType = {
|
||||
bookingDate: Date
|
||||
startTime: string
|
||||
endTime: string
|
||||
price: runtime.Decimal | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status: $Enums.CourtBookingStatus
|
||||
recurringGroupId: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
_count: CourtBookingCountAggregateOutputType | null
|
||||
_avg: CourtBookingAvgAggregateOutputType | null
|
||||
_sum: CourtBookingSumAggregateOutputType | null
|
||||
_min: CourtBookingMinAggregateOutputType | null
|
||||
_max: CourtBookingMaxAggregateOutputType | null
|
||||
}
|
||||
@@ -239,15 +266,13 @@ export type CourtBookingWhereInput = {
|
||||
bookingDate?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
startTime?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
endTime?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
price?: Prisma.DecimalNullableFilter<"CourtBooking"> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
customerPhone?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
customerEmail?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
status?: Prisma.EnumCourtBookingStatusFilter<"CourtBooking"> | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.UuidNullableFilter<"CourtBooking"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
court?: Prisma.XOR<Prisma.CourtScalarRelationFilter, Prisma.CourtWhereInput>
|
||||
recurringGroup?: Prisma.XOR<Prisma.RecurringBookingGroupNullableScalarRelationFilter, Prisma.RecurringBookingGroupWhereInput> | null
|
||||
}
|
||||
|
||||
export type CourtBookingOrderByWithRelationInput = {
|
||||
@@ -257,15 +282,13 @@ export type CourtBookingOrderByWithRelationInput = {
|
||||
bookingDate?: Prisma.SortOrder
|
||||
startTime?: Prisma.SortOrder
|
||||
endTime?: Prisma.SortOrder
|
||||
price?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
customerName?: Prisma.SortOrder
|
||||
customerPhone?: Prisma.SortOrder
|
||||
customerEmail?: Prisma.SortOrder
|
||||
status?: Prisma.SortOrder
|
||||
recurringGroupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
court?: Prisma.CourtOrderByWithRelationInput
|
||||
recurringGroup?: Prisma.RecurringBookingGroupOrderByWithRelationInput
|
||||
}
|
||||
|
||||
export type CourtBookingWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -279,15 +302,13 @@ export type CourtBookingWhereUniqueInput = Prisma.AtLeast<{
|
||||
bookingDate?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
startTime?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
endTime?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
price?: Prisma.DecimalNullableFilter<"CourtBooking"> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
customerPhone?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
customerEmail?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
status?: Prisma.EnumCourtBookingStatusFilter<"CourtBooking"> | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.UuidNullableFilter<"CourtBooking"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
court?: Prisma.XOR<Prisma.CourtScalarRelationFilter, Prisma.CourtWhereInput>
|
||||
recurringGroup?: Prisma.XOR<Prisma.RecurringBookingGroupNullableScalarRelationFilter, Prisma.RecurringBookingGroupWhereInput> | null
|
||||
}, "id" | "bookingCode" | "courtId_bookingDate_startTime">
|
||||
|
||||
export type CourtBookingOrderByWithAggregationInput = {
|
||||
@@ -297,16 +318,17 @@ export type CourtBookingOrderByWithAggregationInput = {
|
||||
bookingDate?: Prisma.SortOrder
|
||||
startTime?: Prisma.SortOrder
|
||||
endTime?: Prisma.SortOrder
|
||||
price?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
customerName?: Prisma.SortOrder
|
||||
customerPhone?: Prisma.SortOrder
|
||||
customerEmail?: Prisma.SortOrder
|
||||
status?: Prisma.SortOrder
|
||||
recurringGroupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
_count?: Prisma.CourtBookingCountOrderByAggregateInput
|
||||
_avg?: Prisma.CourtBookingAvgOrderByAggregateInput
|
||||
_max?: Prisma.CourtBookingMaxOrderByAggregateInput
|
||||
_min?: Prisma.CourtBookingMinOrderByAggregateInput
|
||||
_sum?: Prisma.CourtBookingSumOrderByAggregateInput
|
||||
}
|
||||
|
||||
export type CourtBookingScalarWhereWithAggregatesInput = {
|
||||
@@ -319,11 +341,10 @@ export type CourtBookingScalarWhereWithAggregatesInput = {
|
||||
bookingDate?: Prisma.DateTimeWithAggregatesFilter<"CourtBooking"> | Date | string
|
||||
startTime?: Prisma.StringWithAggregatesFilter<"CourtBooking"> | string
|
||||
endTime?: Prisma.StringWithAggregatesFilter<"CourtBooking"> | string
|
||||
price?: Prisma.DecimalNullableWithAggregatesFilter<"CourtBooking"> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringWithAggregatesFilter<"CourtBooking"> | string
|
||||
customerPhone?: Prisma.StringWithAggregatesFilter<"CourtBooking"> | string
|
||||
customerEmail?: Prisma.StringWithAggregatesFilter<"CourtBooking"> | string
|
||||
status?: Prisma.EnumCourtBookingStatusWithAggregatesFilter<"CourtBooking"> | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.UuidNullableWithAggregatesFilter<"CourtBooking"> | string | null
|
||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"CourtBooking"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"CourtBooking"> | Date | string
|
||||
}
|
||||
@@ -334,14 +355,13 @@ export type CourtBookingCreateInput = {
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
price?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
court: Prisma.CourtCreateNestedOneWithoutBookingsInput
|
||||
recurringGroup?: Prisma.RecurringBookingGroupCreateNestedOneWithoutBookingsInput
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedCreateInput = {
|
||||
@@ -351,11 +371,10 @@ export type CourtBookingUncheckedCreateInput = {
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
price?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
recurringGroupId?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -366,14 +385,13 @@ export type CourtBookingUpdateInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
court?: Prisma.CourtUpdateOneRequiredWithoutBookingsNestedInput
|
||||
recurringGroup?: Prisma.RecurringBookingGroupUpdateOneWithoutBookingsNestedInput
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedUpdateInput = {
|
||||
@@ -383,11 +401,10 @@ export type CourtBookingUncheckedUpdateInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -399,11 +416,10 @@ export type CourtBookingCreateManyInput = {
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
price?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
recurringGroupId?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -414,9 +430,9 @@ export type CourtBookingUpdateManyMutationInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -429,11 +445,10 @@ export type CourtBookingUncheckedUpdateManyInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -461,15 +476,18 @@ export type CourtBookingCountOrderByAggregateInput = {
|
||||
bookingDate?: Prisma.SortOrder
|
||||
startTime?: Prisma.SortOrder
|
||||
endTime?: Prisma.SortOrder
|
||||
price?: Prisma.SortOrder
|
||||
customerName?: Prisma.SortOrder
|
||||
customerPhone?: Prisma.SortOrder
|
||||
customerEmail?: Prisma.SortOrder
|
||||
status?: Prisma.SortOrder
|
||||
recurringGroupId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type CourtBookingAvgOrderByAggregateInput = {
|
||||
price?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type CourtBookingMaxOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
bookingCode?: Prisma.SortOrder
|
||||
@@ -477,11 +495,10 @@ export type CourtBookingMaxOrderByAggregateInput = {
|
||||
bookingDate?: Prisma.SortOrder
|
||||
startTime?: Prisma.SortOrder
|
||||
endTime?: Prisma.SortOrder
|
||||
price?: Prisma.SortOrder
|
||||
customerName?: Prisma.SortOrder
|
||||
customerPhone?: Prisma.SortOrder
|
||||
customerEmail?: Prisma.SortOrder
|
||||
status?: Prisma.SortOrder
|
||||
recurringGroupId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -493,15 +510,18 @@ export type CourtBookingMinOrderByAggregateInput = {
|
||||
bookingDate?: Prisma.SortOrder
|
||||
startTime?: Prisma.SortOrder
|
||||
endTime?: Prisma.SortOrder
|
||||
price?: Prisma.SortOrder
|
||||
customerName?: Prisma.SortOrder
|
||||
customerPhone?: Prisma.SortOrder
|
||||
customerEmail?: Prisma.SortOrder
|
||||
status?: Prisma.SortOrder
|
||||
recurringGroupId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type CourtBookingSumOrderByAggregateInput = {
|
||||
price?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type CourtBookingCreateNestedManyWithoutCourtInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtBookingCreateWithoutCourtInput, Prisma.CourtBookingUncheckedCreateWithoutCourtInput> | Prisma.CourtBookingCreateWithoutCourtInput[] | Prisma.CourtBookingUncheckedCreateWithoutCourtInput[]
|
||||
connectOrCreate?: Prisma.CourtBookingCreateOrConnectWithoutCourtInput | Prisma.CourtBookingCreateOrConnectWithoutCourtInput[]
|
||||
@@ -544,65 +564,30 @@ export type CourtBookingUncheckedUpdateManyWithoutCourtNestedInput = {
|
||||
deleteMany?: Prisma.CourtBookingScalarWhereInput | Prisma.CourtBookingScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type NullableDecimalFieldUpdateOperationsInput = {
|
||||
set?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
increment?: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
decrement?: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
multiply?: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
divide?: runtime.Decimal | runtime.DecimalJsLike | number | string
|
||||
}
|
||||
|
||||
export type EnumCourtBookingStatusFieldUpdateOperationsInput = {
|
||||
set?: $Enums.CourtBookingStatus
|
||||
}
|
||||
|
||||
export type CourtBookingCreateNestedManyWithoutRecurringGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtBookingCreateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput> | Prisma.CourtBookingCreateWithoutRecurringGroupInput[] | Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput[]
|
||||
connectOrCreate?: Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput | Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput[]
|
||||
createMany?: Prisma.CourtBookingCreateManyRecurringGroupInputEnvelope
|
||||
connect?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedCreateNestedManyWithoutRecurringGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtBookingCreateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput> | Prisma.CourtBookingCreateWithoutRecurringGroupInput[] | Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput[]
|
||||
connectOrCreate?: Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput | Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput[]
|
||||
createMany?: Prisma.CourtBookingCreateManyRecurringGroupInputEnvelope
|
||||
connect?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type CourtBookingUpdateManyWithoutRecurringGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtBookingCreateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput> | Prisma.CourtBookingCreateWithoutRecurringGroupInput[] | Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput[]
|
||||
connectOrCreate?: Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput | Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput[]
|
||||
upsert?: Prisma.CourtBookingUpsertWithWhereUniqueWithoutRecurringGroupInput | Prisma.CourtBookingUpsertWithWhereUniqueWithoutRecurringGroupInput[]
|
||||
createMany?: Prisma.CourtBookingCreateManyRecurringGroupInputEnvelope
|
||||
set?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
disconnect?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
delete?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
connect?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
update?: Prisma.CourtBookingUpdateWithWhereUniqueWithoutRecurringGroupInput | Prisma.CourtBookingUpdateWithWhereUniqueWithoutRecurringGroupInput[]
|
||||
updateMany?: Prisma.CourtBookingUpdateManyWithWhereWithoutRecurringGroupInput | Prisma.CourtBookingUpdateManyWithWhereWithoutRecurringGroupInput[]
|
||||
deleteMany?: Prisma.CourtBookingScalarWhereInput | Prisma.CourtBookingScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedUpdateManyWithoutRecurringGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.CourtBookingCreateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput> | Prisma.CourtBookingCreateWithoutRecurringGroupInput[] | Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput[]
|
||||
connectOrCreate?: Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput | Prisma.CourtBookingCreateOrConnectWithoutRecurringGroupInput[]
|
||||
upsert?: Prisma.CourtBookingUpsertWithWhereUniqueWithoutRecurringGroupInput | Prisma.CourtBookingUpsertWithWhereUniqueWithoutRecurringGroupInput[]
|
||||
createMany?: Prisma.CourtBookingCreateManyRecurringGroupInputEnvelope
|
||||
set?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
disconnect?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
delete?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
connect?: Prisma.CourtBookingWhereUniqueInput | Prisma.CourtBookingWhereUniqueInput[]
|
||||
update?: Prisma.CourtBookingUpdateWithWhereUniqueWithoutRecurringGroupInput | Prisma.CourtBookingUpdateWithWhereUniqueWithoutRecurringGroupInput[]
|
||||
updateMany?: Prisma.CourtBookingUpdateManyWithWhereWithoutRecurringGroupInput | Prisma.CourtBookingUpdateManyWithWhereWithoutRecurringGroupInput[]
|
||||
deleteMany?: Prisma.CourtBookingScalarWhereInput | Prisma.CourtBookingScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type CourtBookingCreateWithoutCourtInput = {
|
||||
id: string
|
||||
bookingCode: string
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
price?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
recurringGroup?: Prisma.RecurringBookingGroupCreateNestedOneWithoutBookingsInput
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedCreateWithoutCourtInput = {
|
||||
@@ -611,11 +596,10 @@ export type CourtBookingUncheckedCreateWithoutCourtInput = {
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
price?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
recurringGroupId?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -656,82 +640,24 @@ export type CourtBookingScalarWhereInput = {
|
||||
bookingDate?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
startTime?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
endTime?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
price?: Prisma.DecimalNullableFilter<"CourtBooking"> | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
customerPhone?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
customerEmail?: Prisma.StringFilter<"CourtBooking"> | string
|
||||
status?: Prisma.EnumCourtBookingStatusFilter<"CourtBooking"> | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.UuidNullableFilter<"CourtBooking"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"CourtBooking"> | Date | string
|
||||
}
|
||||
|
||||
export type CourtBookingCreateWithoutRecurringGroupInput = {
|
||||
id: string
|
||||
bookingCode: string
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
court: Prisma.CourtCreateNestedOneWithoutBookingsInput
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedCreateWithoutRecurringGroupInput = {
|
||||
id: string
|
||||
bookingCode: string
|
||||
courtId: string
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
|
||||
export type CourtBookingCreateOrConnectWithoutRecurringGroupInput = {
|
||||
where: Prisma.CourtBookingWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.CourtBookingCreateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput>
|
||||
}
|
||||
|
||||
export type CourtBookingCreateManyRecurringGroupInputEnvelope = {
|
||||
data: Prisma.CourtBookingCreateManyRecurringGroupInput | Prisma.CourtBookingCreateManyRecurringGroupInput[]
|
||||
skipDuplicates?: boolean
|
||||
}
|
||||
|
||||
export type CourtBookingUpsertWithWhereUniqueWithoutRecurringGroupInput = {
|
||||
where: Prisma.CourtBookingWhereUniqueInput
|
||||
update: Prisma.XOR<Prisma.CourtBookingUpdateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedUpdateWithoutRecurringGroupInput>
|
||||
create: Prisma.XOR<Prisma.CourtBookingCreateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedCreateWithoutRecurringGroupInput>
|
||||
}
|
||||
|
||||
export type CourtBookingUpdateWithWhereUniqueWithoutRecurringGroupInput = {
|
||||
where: Prisma.CourtBookingWhereUniqueInput
|
||||
data: Prisma.XOR<Prisma.CourtBookingUpdateWithoutRecurringGroupInput, Prisma.CourtBookingUncheckedUpdateWithoutRecurringGroupInput>
|
||||
}
|
||||
|
||||
export type CourtBookingUpdateManyWithWhereWithoutRecurringGroupInput = {
|
||||
where: Prisma.CourtBookingScalarWhereInput
|
||||
data: Prisma.XOR<Prisma.CourtBookingUpdateManyMutationInput, Prisma.CourtBookingUncheckedUpdateManyWithoutRecurringGroupInput>
|
||||
}
|
||||
|
||||
export type CourtBookingCreateManyCourtInput = {
|
||||
id: string
|
||||
bookingCode: string
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
price?: runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
recurringGroupId?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -742,13 +668,12 @@ export type CourtBookingUpdateWithoutCourtInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
recurringGroup?: Prisma.RecurringBookingGroupUpdateOneWithoutBookingsNestedInput
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedUpdateWithoutCourtInput = {
|
||||
@@ -757,11 +682,10 @@ export type CourtBookingUncheckedUpdateWithoutCourtInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -772,70 +696,9 @@ export type CourtBookingUncheckedUpdateManyWithoutCourtInput = {
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
price?: Prisma.NullableDecimalFieldUpdateOperationsInput | runtime.Decimal | runtime.DecimalJsLike | number | string | null
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
recurringGroupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
export type CourtBookingCreateManyRecurringGroupInput = {
|
||||
id: string
|
||||
bookingCode: string
|
||||
courtId: string
|
||||
bookingDate: Date | string
|
||||
startTime: string
|
||||
endTime: string
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status?: $Enums.CourtBookingStatus
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
|
||||
export type CourtBookingUpdateWithoutRecurringGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
bookingCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
court?: Prisma.CourtUpdateOneRequiredWithoutBookingsNestedInput
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedUpdateWithoutRecurringGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
bookingCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
courtId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
export type CourtBookingUncheckedUpdateManyWithoutRecurringGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
bookingCode?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
courtId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
bookingDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
startTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
endTime?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerPhone?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
customerEmail?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
status?: Prisma.EnumCourtBookingStatusFieldUpdateOperationsInput | $Enums.CourtBookingStatus
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -850,15 +713,13 @@ export type CourtBookingSelect<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
bookingDate?: boolean
|
||||
startTime?: boolean
|
||||
endTime?: boolean
|
||||
price?: boolean
|
||||
customerName?: boolean
|
||||
customerPhone?: boolean
|
||||
customerEmail?: boolean
|
||||
status?: boolean
|
||||
recurringGroupId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
court?: boolean | Prisma.CourtDefaultArgs<ExtArgs>
|
||||
recurringGroup?: boolean | Prisma.CourtBooking$recurringGroupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["courtBooking"]>
|
||||
|
||||
export type CourtBookingSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
@@ -868,15 +729,13 @@ export type CourtBookingSelectCreateManyAndReturn<ExtArgs extends runtime.Types.
|
||||
bookingDate?: boolean
|
||||
startTime?: boolean
|
||||
endTime?: boolean
|
||||
price?: boolean
|
||||
customerName?: boolean
|
||||
customerPhone?: boolean
|
||||
customerEmail?: boolean
|
||||
status?: boolean
|
||||
recurringGroupId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
court?: boolean | Prisma.CourtDefaultArgs<ExtArgs>
|
||||
recurringGroup?: boolean | Prisma.CourtBooking$recurringGroupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["courtBooking"]>
|
||||
|
||||
export type CourtBookingSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
@@ -886,15 +745,13 @@ export type CourtBookingSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.
|
||||
bookingDate?: boolean
|
||||
startTime?: boolean
|
||||
endTime?: boolean
|
||||
price?: boolean
|
||||
customerName?: boolean
|
||||
customerPhone?: boolean
|
||||
customerEmail?: boolean
|
||||
status?: boolean
|
||||
recurringGroupId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
court?: boolean | Prisma.CourtDefaultArgs<ExtArgs>
|
||||
recurringGroup?: boolean | Prisma.CourtBooking$recurringGroupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["courtBooking"]>
|
||||
|
||||
export type CourtBookingSelectScalar = {
|
||||
@@ -904,34 +761,29 @@ export type CourtBookingSelectScalar = {
|
||||
bookingDate?: boolean
|
||||
startTime?: boolean
|
||||
endTime?: boolean
|
||||
price?: boolean
|
||||
customerName?: boolean
|
||||
customerPhone?: boolean
|
||||
customerEmail?: boolean
|
||||
status?: boolean
|
||||
recurringGroupId?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
}
|
||||
|
||||
export type CourtBookingOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "bookingCode" | "courtId" | "bookingDate" | "startTime" | "endTime" | "customerName" | "customerPhone" | "customerEmail" | "status" | "recurringGroupId" | "createdAt" | "updatedAt", ExtArgs["result"]["courtBooking"]>
|
||||
export type CourtBookingOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "bookingCode" | "courtId" | "bookingDate" | "startTime" | "endTime" | "price" | "customerName" | "customerPhone" | "status" | "createdAt" | "updatedAt", ExtArgs["result"]["courtBooking"]>
|
||||
export type CourtBookingInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
court?: boolean | Prisma.CourtDefaultArgs<ExtArgs>
|
||||
recurringGroup?: boolean | Prisma.CourtBooking$recurringGroupArgs<ExtArgs>
|
||||
}
|
||||
export type CourtBookingIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
court?: boolean | Prisma.CourtDefaultArgs<ExtArgs>
|
||||
recurringGroup?: boolean | Prisma.CourtBooking$recurringGroupArgs<ExtArgs>
|
||||
}
|
||||
export type CourtBookingIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
court?: boolean | Prisma.CourtDefaultArgs<ExtArgs>
|
||||
recurringGroup?: boolean | Prisma.CourtBooking$recurringGroupArgs<ExtArgs>
|
||||
}
|
||||
|
||||
export type $CourtBookingPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
name: "CourtBooking"
|
||||
objects: {
|
||||
court: Prisma.$CourtPayload<ExtArgs>
|
||||
recurringGroup: Prisma.$RecurringBookingGroupPayload<ExtArgs> | null
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
@@ -940,11 +792,10 @@ export type $CourtBookingPayload<ExtArgs extends runtime.Types.Extensions.Intern
|
||||
bookingDate: Date
|
||||
startTime: string
|
||||
endTime: string
|
||||
price: runtime.Decimal | null
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
customerEmail: string
|
||||
status: $Enums.CourtBookingStatus
|
||||
recurringGroupId: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}, ExtArgs["result"]["courtBooking"]>
|
||||
@@ -1342,7 +1193,6 @@ readonly fields: CourtBookingFieldRefs;
|
||||
export interface Prisma__CourtBookingClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
court<T extends Prisma.CourtDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.CourtDefaultArgs<ExtArgs>>): Prisma.Prisma__CourtClient<runtime.Types.Result.GetResult<Prisma.$CourtPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||
recurringGroup<T extends Prisma.CourtBooking$recurringGroupArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.CourtBooking$recurringGroupArgs<ExtArgs>>): Prisma.Prisma__RecurringBookingGroupClient<runtime.Types.Result.GetResult<Prisma.$RecurringBookingGroupPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -1378,11 +1228,10 @@ export interface CourtBookingFieldRefs {
|
||||
readonly bookingDate: Prisma.FieldRef<"CourtBooking", 'DateTime'>
|
||||
readonly startTime: Prisma.FieldRef<"CourtBooking", 'String'>
|
||||
readonly endTime: Prisma.FieldRef<"CourtBooking", 'String'>
|
||||
readonly price: Prisma.FieldRef<"CourtBooking", 'Decimal'>
|
||||
readonly customerName: Prisma.FieldRef<"CourtBooking", 'String'>
|
||||
readonly customerPhone: Prisma.FieldRef<"CourtBooking", 'String'>
|
||||
readonly customerEmail: Prisma.FieldRef<"CourtBooking", 'String'>
|
||||
readonly status: Prisma.FieldRef<"CourtBooking", 'CourtBookingStatus'>
|
||||
readonly recurringGroupId: Prisma.FieldRef<"CourtBooking", 'String'>
|
||||
readonly createdAt: Prisma.FieldRef<"CourtBooking", 'DateTime'>
|
||||
readonly updatedAt: Prisma.FieldRef<"CourtBooking", 'DateTime'>
|
||||
}
|
||||
@@ -1785,25 +1634,6 @@ export type CourtBookingDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* CourtBooking.recurringGroup
|
||||
*/
|
||||
export type CourtBooking$recurringGroupArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the RecurringBookingGroup
|
||||
*/
|
||||
select?: Prisma.RecurringBookingGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the RecurringBookingGroup
|
||||
*/
|
||||
omit?: Prisma.RecurringBookingGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.RecurringBookingGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.RecurringBookingGroupWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* CourtBooking without action
|
||||
*/
|
||||
|
||||
1395
apps/backend/src/generated/prisma/models/OnboardingRequest.ts
Normal file
1395
apps/backend/src/generated/prisma/models/OnboardingRequest.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,10 +30,6 @@ export type UserMinAggregateOutputType = {
|
||||
email: string | null
|
||||
emailVerified: boolean | null
|
||||
image: string | null
|
||||
phone: string | null
|
||||
banned: boolean | null
|
||||
bannedAt: Date | null
|
||||
banReason: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
role: string | null
|
||||
@@ -45,10 +41,6 @@ export type UserMaxAggregateOutputType = {
|
||||
email: string | null
|
||||
emailVerified: boolean | null
|
||||
image: string | null
|
||||
phone: string | null
|
||||
banned: boolean | null
|
||||
bannedAt: Date | null
|
||||
banReason: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
role: string | null
|
||||
@@ -60,10 +52,6 @@ export type UserCountAggregateOutputType = {
|
||||
email: number
|
||||
emailVerified: number
|
||||
image: number
|
||||
phone: number
|
||||
banned: number
|
||||
bannedAt: number
|
||||
banReason: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
role: number
|
||||
@@ -77,10 +65,6 @@ export type UserMinAggregateInputType = {
|
||||
email?: true
|
||||
emailVerified?: true
|
||||
image?: true
|
||||
phone?: true
|
||||
banned?: true
|
||||
bannedAt?: true
|
||||
banReason?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
role?: true
|
||||
@@ -92,10 +76,6 @@ export type UserMaxAggregateInputType = {
|
||||
email?: true
|
||||
emailVerified?: true
|
||||
image?: true
|
||||
phone?: true
|
||||
banned?: true
|
||||
bannedAt?: true
|
||||
banReason?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
role?: true
|
||||
@@ -107,10 +87,6 @@ export type UserCountAggregateInputType = {
|
||||
email?: true
|
||||
emailVerified?: true
|
||||
image?: true
|
||||
phone?: true
|
||||
banned?: true
|
||||
bannedAt?: true
|
||||
banReason?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
role?: true
|
||||
@@ -195,10 +171,6 @@ export type UserGroupByOutputType = {
|
||||
email: string
|
||||
emailVerified: boolean
|
||||
image: string | null
|
||||
phone: string | null
|
||||
banned: boolean
|
||||
bannedAt: Date | null
|
||||
banReason: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
role: string
|
||||
@@ -231,10 +203,6 @@ export type UserWhereInput = {
|
||||
email?: Prisma.StringFilter<"User"> | string
|
||||
emailVerified?: Prisma.BoolFilter<"User"> | boolean
|
||||
image?: Prisma.StringNullableFilter<"User"> | string | null
|
||||
phone?: Prisma.StringNullableFilter<"User"> | string | null
|
||||
banned?: Prisma.BoolFilter<"User"> | boolean
|
||||
bannedAt?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null
|
||||
banReason?: Prisma.StringNullableFilter<"User"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
role?: Prisma.StringFilter<"User"> | string
|
||||
@@ -249,10 +217,6 @@ export type UserOrderByWithRelationInput = {
|
||||
email?: Prisma.SortOrder
|
||||
emailVerified?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
phone?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
banned?: Prisma.SortOrder
|
||||
bannedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
banReason?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
role?: Prisma.SortOrder
|
||||
@@ -270,10 +234,6 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
name?: Prisma.StringFilter<"User"> | string
|
||||
emailVerified?: Prisma.BoolFilter<"User"> | boolean
|
||||
image?: Prisma.StringNullableFilter<"User"> | string | null
|
||||
phone?: Prisma.StringNullableFilter<"User"> | string | null
|
||||
banned?: Prisma.BoolFilter<"User"> | boolean
|
||||
bannedAt?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null
|
||||
banReason?: Prisma.StringNullableFilter<"User"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
role?: Prisma.StringFilter<"User"> | string
|
||||
@@ -288,10 +248,6 @@ export type UserOrderByWithAggregationInput = {
|
||||
email?: Prisma.SortOrder
|
||||
emailVerified?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
phone?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
banned?: Prisma.SortOrder
|
||||
bannedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
banReason?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
role?: Prisma.SortOrder
|
||||
@@ -309,10 +265,6 @@ export type UserScalarWhereWithAggregatesInput = {
|
||||
email?: Prisma.StringWithAggregatesFilter<"User"> | string
|
||||
emailVerified?: Prisma.BoolWithAggregatesFilter<"User"> | boolean
|
||||
image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
|
||||
phone?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
|
||||
banned?: Prisma.BoolWithAggregatesFilter<"User"> | boolean
|
||||
bannedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"User"> | Date | string | null
|
||||
banReason?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
|
||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
|
||||
role?: Prisma.StringWithAggregatesFilter<"User"> | string
|
||||
@@ -324,10 +276,6 @@ export type UserCreateInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -342,10 +290,6 @@ export type UserUncheckedCreateInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -360,10 +304,6 @@ export type UserUpdateInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -378,10 +318,6 @@ export type UserUncheckedUpdateInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -396,10 +332,6 @@ export type UserCreateManyInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -411,10 +343,6 @@ export type UserUpdateManyMutationInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -426,10 +354,6 @@ export type UserUncheckedUpdateManyInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -441,10 +365,6 @@ export type UserCountOrderByAggregateInput = {
|
||||
email?: Prisma.SortOrder
|
||||
emailVerified?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrder
|
||||
phone?: Prisma.SortOrder
|
||||
banned?: Prisma.SortOrder
|
||||
bannedAt?: Prisma.SortOrder
|
||||
banReason?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
role?: Prisma.SortOrder
|
||||
@@ -456,10 +376,6 @@ export type UserMaxOrderByAggregateInput = {
|
||||
email?: Prisma.SortOrder
|
||||
emailVerified?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrder
|
||||
phone?: Prisma.SortOrder
|
||||
banned?: Prisma.SortOrder
|
||||
bannedAt?: Prisma.SortOrder
|
||||
banReason?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
role?: Prisma.SortOrder
|
||||
@@ -471,10 +387,6 @@ export type UserMinOrderByAggregateInput = {
|
||||
email?: Prisma.SortOrder
|
||||
emailVerified?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrder
|
||||
phone?: Prisma.SortOrder
|
||||
banned?: Prisma.SortOrder
|
||||
bannedAt?: Prisma.SortOrder
|
||||
banReason?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
role?: Prisma.SortOrder
|
||||
@@ -497,10 +409,6 @@ export type NullableStringFieldUpdateOperationsInput = {
|
||||
set?: string | null
|
||||
}
|
||||
|
||||
export type NullableDateTimeFieldUpdateOperationsInput = {
|
||||
set?: Date | string | null
|
||||
}
|
||||
|
||||
export type DateTimeFieldUpdateOperationsInput = {
|
||||
set?: Date | string
|
||||
}
|
||||
@@ -553,10 +461,6 @@ export type UserCreateWithoutSessionsInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -570,10 +474,6 @@ export type UserUncheckedCreateWithoutSessionsInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -603,10 +503,6 @@ export type UserUpdateWithoutSessionsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -620,10 +516,6 @@ export type UserUncheckedUpdateWithoutSessionsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -637,10 +529,6 @@ export type UserCreateWithoutAccountsInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -654,10 +542,6 @@ export type UserUncheckedCreateWithoutAccountsInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -687,10 +571,6 @@ export type UserUpdateWithoutAccountsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -704,10 +584,6 @@ export type UserUncheckedUpdateWithoutAccountsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -721,10 +597,6 @@ export type UserCreateWithoutComplexesInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -738,10 +610,6 @@ export type UserUncheckedCreateWithoutComplexesInput = {
|
||||
email: string
|
||||
emailVerified?: boolean
|
||||
image?: string | null
|
||||
phone?: string | null
|
||||
banned?: boolean
|
||||
bannedAt?: Date | string | null
|
||||
banReason?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
role?: string
|
||||
@@ -771,10 +639,6 @@ export type UserUpdateWithoutComplexesInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -788,10 +652,6 @@ export type UserUncheckedUpdateWithoutComplexesInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
emailVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
phone?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
banned?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
bannedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
banReason?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
@@ -854,10 +714,6 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
||||
email?: boolean
|
||||
emailVerified?: boolean
|
||||
image?: boolean
|
||||
phone?: boolean
|
||||
banned?: boolean
|
||||
bannedAt?: boolean
|
||||
banReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
role?: boolean
|
||||
@@ -873,10 +729,6 @@ export type UserSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
||||
email?: boolean
|
||||
emailVerified?: boolean
|
||||
image?: boolean
|
||||
phone?: boolean
|
||||
banned?: boolean
|
||||
bannedAt?: boolean
|
||||
banReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
role?: boolean
|
||||
@@ -888,10 +740,6 @@ export type UserSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
||||
email?: boolean
|
||||
emailVerified?: boolean
|
||||
image?: boolean
|
||||
phone?: boolean
|
||||
banned?: boolean
|
||||
bannedAt?: boolean
|
||||
banReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
role?: boolean
|
||||
@@ -903,16 +751,12 @@ export type UserSelectScalar = {
|
||||
email?: boolean
|
||||
emailVerified?: boolean
|
||||
image?: boolean
|
||||
phone?: boolean
|
||||
banned?: boolean
|
||||
bannedAt?: boolean
|
||||
banReason?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
role?: boolean
|
||||
}
|
||||
|
||||
export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "emailVerified" | "image" | "phone" | "banned" | "bannedAt" | "banReason" | "createdAt" | "updatedAt" | "role", ExtArgs["result"]["user"]>
|
||||
export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "emailVerified" | "image" | "createdAt" | "updatedAt" | "role", ExtArgs["result"]["user"]>
|
||||
export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
sessions?: boolean | Prisma.User$sessionsArgs<ExtArgs>
|
||||
accounts?: boolean | Prisma.User$accountsArgs<ExtArgs>
|
||||
@@ -935,10 +779,6 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
email: string
|
||||
emailVerified: boolean
|
||||
image: string | null
|
||||
phone: string | null
|
||||
banned: boolean
|
||||
bannedAt: Date | null
|
||||
banReason: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
role: string
|
||||
@@ -1373,10 +1213,6 @@ export interface UserFieldRefs {
|
||||
readonly email: Prisma.FieldRef<"User", 'String'>
|
||||
readonly emailVerified: Prisma.FieldRef<"User", 'Boolean'>
|
||||
readonly image: Prisma.FieldRef<"User", 'String'>
|
||||
readonly phone: Prisma.FieldRef<"User", 'String'>
|
||||
readonly banned: Prisma.FieldRef<"User", 'Boolean'>
|
||||
readonly bannedAt: Prisma.FieldRef<"User", 'DateTime'>
|
||||
readonly banReason: Prisma.FieldRef<"User", 'String'>
|
||||
readonly createdAt: Prisma.FieldRef<"User", 'DateTime'>
|
||||
readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'>
|
||||
readonly role: Prisma.FieldRef<"User", 'String'>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { wrapLayout } from '@/emails/booking-confirmation';
|
||||
import { dash } from '@better-auth/infra';
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
||||
import { openAPI } from 'better-auth/plugins';
|
||||
import { sendMail } from './mailer';
|
||||
import { db } from './prisma';
|
||||
|
||||
export const auth = betterAuth({
|
||||
@@ -20,108 +18,6 @@ export const auth = betterAuth({
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
user: {
|
||||
additionalFields: {
|
||||
role: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
banned: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
},
|
||||
bannedAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
banReason: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
emailVerification: {
|
||||
sendOnSignUp: true,
|
||||
autoSignInAfterVerification: true,
|
||||
sendVerificationEmail: async ({ user, url }) => {
|
||||
const verificationUrl = new URL(url);
|
||||
const appUrl = process.env.APP_BASE_URL ?? 'http://localhost:5173';
|
||||
verificationUrl.searchParams.set('callbackURL', appUrl);
|
||||
|
||||
const content = `
|
||||
<tr>
|
||||
<td style="padding:24px 20px 16px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="display:inline-block;background-color:#f4f4f5;border-radius:999px;padding:4px 12px;">
|
||||
<tr>
|
||||
<td style="font-size:11px;font-weight:700;letter-spacing:0.14em;color:#71717a;text-transform:uppercase;line-height:1.25rem;">
|
||||
Verificación de email
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h1 style="margin:16px 0 0;font-size:24px;font-weight:700;color:#111827;letter-spacing:-0.025em;">
|
||||
Verificá tu dirección de correo electrónico
|
||||
</h1>
|
||||
</td>
|
||||
<td valign="top" align="right" style="white-space:nowrap;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="middle" style="padding-right:8px;">
|
||||
<img src="${appUrl}/playzer-favicon-512-transparent.png" alt="Playzer" width="32" height="32" style="display:block;" />
|
||||
</td>
|
||||
<td valign="middle">
|
||||
<span style="font-size:18px;font-weight:700;color:#475569;letter-spacing:-0.025em;">Playzer</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
padding:0 20px 16px;
|
||||
<p style="margin:0;font-size:15px;color:#374151;line-height:1.6;">
|
||||
Hacé click en el botón de abajo para verificar tu dirección de correo electrónico y empezar a usar Playzer.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 24px;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td style="background-color:#059669;border-radius:12px;text-align:center;">
|
||||
<a href="${verificationUrl.toString()}" style="display:block;padding:14px 32px;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;line-height:1.25rem;">Verificar email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:0 20px 24px;">
|
||||
<p style="margin:0;font-size:13px;color:#9ca3af;text-align:center;line-height:1.5;">
|
||||
Si no creaste una cuenta en Playzer, ignorá este mensaje.
|
||||
</p>
|
||||
</td>
|
||||
</tr>`;
|
||||
|
||||
await sendMail({
|
||||
to: user.email,
|
||||
subject: 'Verificá tu email en Playzer',
|
||||
html: wrapLayout(content),
|
||||
text: `Hacé click para verificar tu email: ${verificationUrl.toString()}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// lib/errors.ts
|
||||
export type ValidationIssue = {
|
||||
path: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type AppError =
|
||||
| {
|
||||
type: 'validation';
|
||||
message: string;
|
||||
issues?: ValidationIssue[];
|
||||
}
|
||||
| {
|
||||
type: 'not_found';
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
type: 'conflict';
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
type: 'unauthorized';
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
type: 'forbidden';
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
type: 'unexpected';
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const Errors = {
|
||||
validation(message: string, issues?: ValidationIssue[]): AppError {
|
||||
return { type: 'validation', message, issues };
|
||||
},
|
||||
|
||||
notFound(message = 'Resource not found'): AppError {
|
||||
return { type: 'not_found', message };
|
||||
},
|
||||
|
||||
conflict(message: string): AppError {
|
||||
return { type: 'conflict', message };
|
||||
},
|
||||
|
||||
unauthorized(message = 'Unauthorized'): AppError {
|
||||
return { type: 'unauthorized', message };
|
||||
},
|
||||
|
||||
forbidden(message = 'Forbidden'): AppError {
|
||||
return { type: 'forbidden', message };
|
||||
},
|
||||
|
||||
unexpected(message = 'Unexpected error'): AppError {
|
||||
return { type: 'unexpected', message };
|
||||
},
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
type IpGeoInfo = {
|
||||
ip: string;
|
||||
city: string;
|
||||
country: string;
|
||||
countryCode: string;
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
};
|
||||
|
||||
const cache = new Map<string, { data: IpGeoInfo; expiresAt: number }>();
|
||||
const CACHE_TTL_MS = 60 * 60 * 1000;
|
||||
|
||||
function getCached(ip: string): IpGeoInfo | undefined {
|
||||
const entry = cache.get(ip);
|
||||
if (!entry) return undefined;
|
||||
if (Date.now() > entry.expiresAt) {
|
||||
cache.delete(ip);
|
||||
return undefined;
|
||||
}
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
function setCache(ip: string, info: IpGeoInfo): void {
|
||||
cache.set(ip, { data: info, expiresAt: Date.now() + CACHE_TTL_MS });
|
||||
}
|
||||
|
||||
async function fetchGeoInfo(ip: string): Promise<IpGeoInfo | null> {
|
||||
if (ip === '127.0.0.1' || ip === '::1' || ip.startsWith('192.168.') || ip.startsWith('10.')) {
|
||||
return {
|
||||
ip,
|
||||
city: 'Red local',
|
||||
country: 'Red local',
|
||||
countryCode: 'LOCAL',
|
||||
lat: null,
|
||||
lon: null,
|
||||
};
|
||||
}
|
||||
|
||||
const cached = getCached(ip);
|
||||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://ip-api.com/json/${ip}?fields=status,country,countryCode,city,lat,lon,query`
|
||||
);
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'success') return null;
|
||||
|
||||
const info: IpGeoInfo = {
|
||||
ip: data.query,
|
||||
city: data.city || 'Desconocida',
|
||||
country: data.country || 'Desconocido',
|
||||
countryCode: data.countryCode || '',
|
||||
lat: data.lat ?? null,
|
||||
lon: data.lon ?? null,
|
||||
};
|
||||
|
||||
setCache(ip, info);
|
||||
return info;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export type { IpGeoInfo };
|
||||
export { fetchGeoInfo };
|
||||
@@ -1,20 +0,0 @@
|
||||
// http/error-handler.ts
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
import { logger } from '../logger';
|
||||
import { unexpectedProblem } from './problem-builders';
|
||||
|
||||
export const errorHandler: MiddlewareHandler = async (c, next) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (error) {
|
||||
const requestId = c.get('requestId') as string | undefined;
|
||||
const instance = requestId ? `/requests/${requestId}` : undefined;
|
||||
|
||||
logger.error({
|
||||
requestId,
|
||||
error,
|
||||
});
|
||||
|
||||
return c.json(unexpectedProblem({ instance }), 500);
|
||||
}
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { Result } from '@/lib/result';
|
||||
import type { Context } from 'hono';
|
||||
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
||||
import { mapAppErrorToProblem } from './problem-mapper';
|
||||
|
||||
export function handleResult<T>(
|
||||
c: Context,
|
||||
result: Result<T>,
|
||||
successStatus: ContentfulStatusCode = 200
|
||||
) {
|
||||
if (!result.ok) {
|
||||
const requestId = c.get('requestId') as string | undefined;
|
||||
const instance = requestId ? `/requests/${requestId}` : undefined;
|
||||
const problem = mapAppErrorToProblem(result.error, instance);
|
||||
|
||||
return c.json(problem, problem.status);
|
||||
}
|
||||
|
||||
return c.json(result.value, successStatus);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// http/problem-builders.ts
|
||||
import type { ProblemDetails } from './problem-details';
|
||||
|
||||
export function validationProblem(params: {
|
||||
detail?: string;
|
||||
instance?: string;
|
||||
errors?: Record<string, string[]>;
|
||||
}): ProblemDetails {
|
||||
return {
|
||||
type: 'https://api.myapp.dev/problems/validation',
|
||||
title: 'Bad Request',
|
||||
status: 400,
|
||||
detail: params.detail ?? 'Invalid request data',
|
||||
instance: params.instance,
|
||||
errors: params.errors,
|
||||
};
|
||||
}
|
||||
|
||||
export function unexpectedProblem(params?: {
|
||||
instance?: string;
|
||||
}): ProblemDetails {
|
||||
return {
|
||||
type: 'https://api.myapp.dev/problems/unexpected',
|
||||
title: 'Internal Server Error',
|
||||
status: 500,
|
||||
detail: 'Internal server error',
|
||||
instance: params?.instance,
|
||||
};
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
||||
|
||||
export type ProblemDetails = {
|
||||
type: string;
|
||||
title: string;
|
||||
status: ContentfulStatusCode;
|
||||
detail?: string;
|
||||
instance?: string;
|
||||
errors?: Record<string, string[]>;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { AppError } from '@/lib/errors';
|
||||
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
||||
import type { ProblemDetails } from './problem-details';
|
||||
|
||||
function statusFromError(error: AppError): ContentfulStatusCode {
|
||||
switch (error.type) {
|
||||
case 'validation':
|
||||
return 400;
|
||||
case 'unauthorized':
|
||||
return 401;
|
||||
case 'forbidden':
|
||||
return 403;
|
||||
case 'not_found':
|
||||
return 404;
|
||||
case 'conflict':
|
||||
return 409;
|
||||
case 'unexpected':
|
||||
return 500;
|
||||
default:
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
function titleFromStatus(status: ContentfulStatusCode): string {
|
||||
switch (status) {
|
||||
case 400:
|
||||
return 'Bad Request';
|
||||
case 401:
|
||||
return 'Unauthorized';
|
||||
case 403:
|
||||
return 'Forbidden';
|
||||
case 404:
|
||||
return 'Not Found';
|
||||
case 409:
|
||||
return 'Conflict';
|
||||
default:
|
||||
return 'Internal Server Error';
|
||||
}
|
||||
}
|
||||
|
||||
export function mapAppErrorToProblem(error: AppError, instance?: string): ProblemDetails {
|
||||
const status = statusFromError(error);
|
||||
|
||||
return {
|
||||
type: `https://api.myapp.dev/problems/${error.type}`,
|
||||
title: titleFromStatus(status),
|
||||
status,
|
||||
detail: status === 500 ? 'Internal server error' : error.message,
|
||||
instance,
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { MiddlewareHandler } from 'hono';
|
||||
|
||||
type Env = {
|
||||
Variables: {
|
||||
requestId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const requestIdMiddleware: MiddlewareHandler<Env> = async (c, next) => {
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
c.set('requestId', requestId);
|
||||
c.header('X-Request-Id', requestId);
|
||||
|
||||
await next();
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import type { ZodSchema } from 'zod';
|
||||
import { validationProblem } from './problem-builders';
|
||||
import { zodIssuesToRecord } from './zod-issues';
|
||||
|
||||
type ValidationTarget = 'json' | 'query' | 'param' | 'header' | 'form';
|
||||
|
||||
function makeValidator<TSchema extends ZodSchema>(target: ValidationTarget, schema: TSchema) {
|
||||
return zValidator(target, schema, (result, c) => {
|
||||
if (result.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = c.get('requestId') as string | undefined;
|
||||
const instance = requestId ? `/requests/${requestId}` : undefined;
|
||||
|
||||
return c.json(
|
||||
validationProblem({
|
||||
detail: `Invalid ${target} data`,
|
||||
instance,
|
||||
errors: zodIssuesToRecord(result.error.issues),
|
||||
}),
|
||||
400
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const validate = {
|
||||
json<TSchema extends ZodSchema>(schema: TSchema) {
|
||||
return makeValidator('json', schema);
|
||||
},
|
||||
|
||||
query<TSchema extends ZodSchema>(schema: TSchema) {
|
||||
return makeValidator('query', schema);
|
||||
},
|
||||
|
||||
param<TSchema extends ZodSchema>(schema: TSchema) {
|
||||
return makeValidator('param', schema);
|
||||
},
|
||||
|
||||
header<TSchema extends ZodSchema>(schema: TSchema) {
|
||||
return makeValidator('header', schema);
|
||||
},
|
||||
|
||||
form<TSchema extends ZodSchema>(schema: TSchema) {
|
||||
return makeValidator('form', schema);
|
||||
},
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
type IssueLike = {
|
||||
path: PropertyKey[];
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function zodIssuesToRecord(issues: IssueLike[]): Record<string, string[]> {
|
||||
const out: Record<string, string[]> = {};
|
||||
|
||||
for (const issue of issues) {
|
||||
const key = issue.path.length ? issue.path.join('.') : 'root';
|
||||
out[key] ??= [];
|
||||
out[key].push(issue.message);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// lib/result.ts
|
||||
import type { AppError } from '@/lib/errors';
|
||||
|
||||
export type Result<T, E = AppError> = { ok: true; value: T } | { ok: false; error: E };
|
||||
|
||||
export function ok<T>(value: T): Result<T> {
|
||||
return { ok: true, value };
|
||||
}
|
||||
|
||||
export function err<E>(error: E): Result<never, E> {
|
||||
return { ok: false, error };
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
function toMinutes(value: string): number {
|
||||
const [hours, minutes] = value.split(':').map((part) => Number(part));
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
export function isSlotInPast(
|
||||
bookingDate: Date,
|
||||
startTime: string,
|
||||
slotDurationMinutes: number,
|
||||
now?: Date
|
||||
): boolean {
|
||||
const currentTime = now ?? new Date();
|
||||
|
||||
const todayStart = new Date(
|
||||
currentTime.getFullYear(),
|
||||
currentTime.getMonth(),
|
||||
currentTime.getDate()
|
||||
);
|
||||
|
||||
const bookingLocalStart = new Date(
|
||||
bookingDate.getUTCFullYear(),
|
||||
bookingDate.getUTCMonth(),
|
||||
bookingDate.getUTCDate()
|
||||
);
|
||||
|
||||
if (bookingLocalStart < todayStart) return true;
|
||||
if (bookingLocalStart > todayStart) return false;
|
||||
|
||||
const currentMinutes = currentTime.getHours() * 60 + currentTime.getMinutes();
|
||||
const slotStartMinutes = toMinutes(startTime);
|
||||
const elapsed = currentMinutes - slotStartMinutes;
|
||||
|
||||
return elapsed > slotDurationMinutes / 2;
|
||||
}
|
||||
@@ -1,20 +1,7 @@
|
||||
import { auth } from '@/lib/auth';
|
||||
import { fetchGeoInfo } from '@/lib/geoip';
|
||||
import { db } from '@/lib/prisma';
|
||||
import type { AppEnv } from '@/types/hono';
|
||||
import { createMiddleware } from 'hono/factory';
|
||||
|
||||
type SessionWithGeo = {
|
||||
id: string;
|
||||
ipAddress: string | null;
|
||||
userAgent: string | null;
|
||||
country: string | null;
|
||||
city: string | null;
|
||||
countryCode: string | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
};
|
||||
|
||||
export const requireAuth = createMiddleware<AppEnv>(async (c, next) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: c.req.raw.headers,
|
||||
@@ -24,45 +11,8 @@ export const requireAuth = createMiddleware<AppEnv>(async (c, next) => {
|
||||
return c.json({ message: 'Unauthorized' }, 401);
|
||||
}
|
||||
|
||||
const user = session.user as { banned?: boolean; banReason?: string | null };
|
||||
|
||||
if (user.banned) {
|
||||
return c.json(
|
||||
{
|
||||
message: 'Tu cuenta ha sido bloqueada.',
|
||||
...(user.banReason ? { reason: user.banReason } : {}),
|
||||
},
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
const s = session.session as unknown as SessionWithGeo;
|
||||
|
||||
c.set('user', session.user);
|
||||
c.set('session', session.session);
|
||||
|
||||
if (s.ipAddress && !s.country) {
|
||||
resolveSessionGeo(s.id, s.ipAddress);
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
async function resolveSessionGeo(sessionId: string, ipAddress: string) {
|
||||
try {
|
||||
const geo = await fetchGeoInfo(ipAddress);
|
||||
if (!geo) return;
|
||||
await db.session.update({
|
||||
where: { id: sessionId },
|
||||
data: {
|
||||
country: geo.country,
|
||||
city: geo.city,
|
||||
countryCode: geo.countryCode,
|
||||
latitude: geo.lat,
|
||||
longitude: geo.lon,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
// Silently fail — geo enrichment is best-effort
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import { requireAuth } from '@/middlewares/require-auth.middleware';
|
||||
import { cancelRecurringGroupHandler } from '@/modules/admin-booking/handlers/cancel-recurring-group.handler';
|
||||
import { createAdminBookingHandler } from '@/modules/admin-booking/handlers/create-admin-booking.handler';
|
||||
import { createAdminRecurringBookingHandler } from '@/modules/admin-booking/handlers/create-admin-recurring-booking.handler';
|
||||
import { listAdminBookingsHandler } from '@/modules/admin-booking/handlers/list-admin-bookings.handler';
|
||||
import { listRecurringGroupsHandler } from '@/modules/admin-booking/handlers/list-recurring-groups.handler';
|
||||
import { updateAdminBookingStatusHandler } from '@/modules/admin-booking/handlers/update-admin-booking-status.handler';
|
||||
import { updateRecurringGroupHandler } from '@/modules/admin-booking/handlers/update-recurring-group.handler';
|
||||
import type { AppEnv } from '@/types/hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import {
|
||||
createAdminBookingSchema,
|
||||
createRecurringBookingSchema,
|
||||
listAdminBookingsQuerySchema,
|
||||
updateAdminBookingStatusSchema,
|
||||
updateRecurringGroupSchema,
|
||||
} from '@repo/api-contract';
|
||||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
@@ -21,7 +15,6 @@ import { z } from 'zod';
|
||||
export const adminBookingRoutes = new Hono<AppEnv>();
|
||||
const complexIdParamsSchema = z.object({ complexId: z.uuid() });
|
||||
const bookingIdParamsSchema = z.object({ id: z.uuid() });
|
||||
const groupIdParamsSchema = z.object({ groupId: z.uuid() });
|
||||
|
||||
adminBookingRoutes.use('*', requireAuth);
|
||||
|
||||
@@ -39,32 +32,6 @@ adminBookingRoutes.post(
|
||||
createAdminBookingHandler
|
||||
);
|
||||
|
||||
adminBookingRoutes.post(
|
||||
'/complex/:complexId/recurring',
|
||||
zValidator('param', complexIdParamsSchema),
|
||||
zValidator('json', createRecurringBookingSchema),
|
||||
createAdminRecurringBookingHandler
|
||||
);
|
||||
|
||||
adminBookingRoutes.post(
|
||||
'/recurring/:groupId/cancel',
|
||||
zValidator('param', groupIdParamsSchema),
|
||||
cancelRecurringGroupHandler
|
||||
);
|
||||
|
||||
adminBookingRoutes.get(
|
||||
'/complex/:complexId/recurring',
|
||||
zValidator('param', complexIdParamsSchema),
|
||||
listRecurringGroupsHandler
|
||||
);
|
||||
|
||||
adminBookingRoutes.patch(
|
||||
'/recurring/:groupId',
|
||||
zValidator('param', groupIdParamsSchema),
|
||||
zValidator('json', updateRecurringGroupSchema),
|
||||
updateRecurringGroupHandler
|
||||
);
|
||||
|
||||
adminBookingRoutes.patch(
|
||||
'/:id/status',
|
||||
zValidator('param', bookingIdParamsSchema),
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { AdminBookingServiceError } from '@/modules/admin-booking/services/admin-booking.service';
|
||||
import { cancelRecurringGroup } from '@/modules/admin-booking/services/admin-recurring-booking.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
type GroupIdParams = { groupId: string };
|
||||
|
||||
export async function cancelRecurringGroupHandler(c: AppContext) {
|
||||
const { groupId } = c.req.valid('param' as never) as GroupIdParams;
|
||||
const user = c.get('user');
|
||||
|
||||
try {
|
||||
const result = await cancelRecurringGroup(user.id, groupId);
|
||||
return c.json(result);
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
import {
|
||||
AdminBookingServiceError,
|
||||
createAdminBooking,
|
||||
} from '@/modules/admin-booking/services/admin-booking.service';
|
||||
import { sendBookingConfirmation } from '@/services/booking-email.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { CreateAdminBookingInput } from '@repo/api-contract';
|
||||
|
||||
@@ -16,26 +14,6 @@ export async function createAdminBookingHandler(c: AppContext) {
|
||||
|
||||
try {
|
||||
const booking = await createAdminBooking(user.id, complexId, payload);
|
||||
|
||||
const complex = await db.complex.findUnique({
|
||||
where: { id: complexId },
|
||||
select: { complexSlug: true },
|
||||
});
|
||||
|
||||
void sendBookingConfirmation({
|
||||
bookingCode: booking.bookingCode,
|
||||
complexSlug: complex?.complexSlug ?? '',
|
||||
complexName: booking.complexName,
|
||||
date: booking.date,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
courtName: booking.courtName,
|
||||
sportName: booking.sport.name,
|
||||
customerName: booking.customerName,
|
||||
customerEmail: booking.customerEmail,
|
||||
price: booking.price,
|
||||
});
|
||||
|
||||
return c.json(booking, 201);
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { AdminBookingServiceError } from '@/modules/admin-booking/services/admin-booking.service';
|
||||
import { createAdminRecurringBooking } from '@/modules/admin-booking/services/admin-recurring-booking.service';
|
||||
import { sendBookingConfirmation } from '@/services/booking-email.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { CreateRecurringBookingInput } from '@repo/api-contract';
|
||||
|
||||
type ComplexIdParams = { complexId: string };
|
||||
|
||||
export async function createAdminRecurringBookingHandler(c: AppContext) {
|
||||
const { complexId } = c.req.valid('param' as never) as ComplexIdParams;
|
||||
const payload = c.req.valid('json' as never) as CreateRecurringBookingInput;
|
||||
const user = c.get('user');
|
||||
|
||||
try {
|
||||
const group = await createAdminRecurringBooking(user.id, complexId, payload);
|
||||
|
||||
const firstBooking = group.bookings[0];
|
||||
if (firstBooking?.customerEmail) {
|
||||
void sendBookingConfirmation({
|
||||
bookingCode: firstBooking.bookingCode,
|
||||
complexName: firstBooking.complexName,
|
||||
date: firstBooking.date,
|
||||
startTime: firstBooking.startTime,
|
||||
endTime: firstBooking.endTime,
|
||||
courtName: firstBooking.courtName,
|
||||
sportName: firstBooking.sport.name,
|
||||
customerName: firstBooking.customerName,
|
||||
customerEmail: firstBooking.customerEmail,
|
||||
price: firstBooking.price,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json(group, 201);
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { AdminBookingServiceError } from '@/modules/admin-booking/services/admin-booking.service';
|
||||
import { listRecurringGroups } from '@/modules/admin-booking/services/admin-recurring-booking.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
type ComplexIdParams = { complexId: string };
|
||||
|
||||
export async function listRecurringGroupsHandler(c: AppContext) {
|
||||
const { complexId } = c.req.valid('param' as never) as ComplexIdParams;
|
||||
|
||||
try {
|
||||
const groups = await listRecurringGroups(complexId);
|
||||
return c.json({ groups });
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
AdminBookingServiceError,
|
||||
updateAdminBookingStatus,
|
||||
} from '@/modules/admin-booking/services/admin-booking.service';
|
||||
import { sendBookingCancelled, sendBookingNoShow } from '@/services/booking-email.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { UpdateAdminBookingStatusInput } from '@repo/api-contract';
|
||||
|
||||
@@ -15,33 +14,6 @@ export async function updateAdminBookingStatusHandler(c: AppContext) {
|
||||
|
||||
try {
|
||||
const booking = await updateAdminBookingStatus(user.id, id, payload);
|
||||
|
||||
if (payload.status === 'CANCELLED') {
|
||||
void sendBookingCancelled({
|
||||
bookingCode: booking.bookingCode,
|
||||
complexName: booking.complexName,
|
||||
date: booking.date,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
courtName: booking.courtName,
|
||||
sportName: booking.sport.name,
|
||||
customerName: booking.customerName,
|
||||
customerEmail: booking.customerEmail,
|
||||
});
|
||||
} else if (payload.status === 'NOSHOW') {
|
||||
void sendBookingNoShow({
|
||||
bookingCode: booking.bookingCode,
|
||||
complexName: booking.complexName,
|
||||
date: booking.date,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
courtName: booking.courtName,
|
||||
sportName: booking.sport.name,
|
||||
customerName: booking.customerName,
|
||||
customerEmail: booking.customerEmail,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json(booking);
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { AdminBookingServiceError } from '@/modules/admin-booking/services/admin-booking.service';
|
||||
import { updateRecurringGroup } from '@/modules/admin-booking/services/admin-recurring-booking.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { UpdateRecurringGroupInput } from '@repo/api-contract';
|
||||
|
||||
type GroupIdParams = { groupId: string };
|
||||
|
||||
export async function updateRecurringGroupHandler(c: AppContext) {
|
||||
const { groupId } = c.req.valid('param' as never) as GroupIdParams;
|
||||
const payload = c.req.valid('json' as never) as UpdateRecurringGroupInput;
|
||||
const user = c.get('user');
|
||||
|
||||
try {
|
||||
const group = await updateRecurringGroup(user.id, groupId, payload);
|
||||
return c.json(group);
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { randomInt } from 'node:crypto';
|
||||
import { CourtBookingStatus } from '@/generated/prisma/enums';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { isSlotInPast } from '@/lib/slot-validator';
|
||||
import { evaluatePlanUsage, parsePlanRules } from '@/modules/plan/services/plan-rules.service';
|
||||
import type { DayOfWeek } from '@repo/api-contract';
|
||||
import type {
|
||||
AdminBooking,
|
||||
CreateAdminBookingInput,
|
||||
@@ -158,40 +156,6 @@ async function ensureComplexAccess(complexId: string, userId: string) {
|
||||
return complexUser.complex;
|
||||
}
|
||||
|
||||
function resolvePrice(
|
||||
court: {
|
||||
basePrice: unknown;
|
||||
priceRules: Array<{
|
||||
dayOfWeek: string | null;
|
||||
startTime: string | null;
|
||||
endTime: string | null;
|
||||
price: unknown;
|
||||
}>;
|
||||
},
|
||||
dayOfWeek: string,
|
||||
startTime: string,
|
||||
endTime: string
|
||||
): number {
|
||||
const slotStart = toMinutes(startTime);
|
||||
const slotEnd = toMinutes(endTime);
|
||||
|
||||
const matchingRules = court.priceRules
|
||||
.filter((rule) => {
|
||||
if (rule.dayOfWeek && rule.dayOfWeek !== dayOfWeek) return false;
|
||||
if (!rule.startTime || !rule.endTime) return true;
|
||||
return slotStart >= toMinutes(rule.startTime) && slotEnd <= toMinutes(rule.endTime);
|
||||
})
|
||||
.sort((first, second) => {
|
||||
const firstSpecificity =
|
||||
(first.dayOfWeek ? 2 : 0) + (first.startTime && first.endTime ? 1 : 0);
|
||||
const secondSpecificity =
|
||||
(second.dayOfWeek ? 2 : 0) + (second.startTime && second.endTime ? 1 : 0);
|
||||
return secondSpecificity - firstSpecificity;
|
||||
});
|
||||
|
||||
return Number(matchingRules[0]?.price ?? court.basePrice);
|
||||
}
|
||||
|
||||
function mapBookingResponse(booking: {
|
||||
id: string;
|
||||
bookingCode: string;
|
||||
@@ -200,9 +164,7 @@ function mapBookingResponse(booking: {
|
||||
endTime: string;
|
||||
customerName: string;
|
||||
customerPhone: string;
|
||||
customerEmail: string;
|
||||
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NOSHOW';
|
||||
recurringGroupId: string | null;
|
||||
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED';
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
court: {
|
||||
@@ -218,7 +180,6 @@ function mapBookingResponse(booking: {
|
||||
slug: string;
|
||||
};
|
||||
};
|
||||
price?: number;
|
||||
}): AdminBooking {
|
||||
return {
|
||||
id: booking.id,
|
||||
@@ -237,10 +198,7 @@ function mapBookingResponse(booking: {
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
price: booking.price ?? 0,
|
||||
status: booking.status,
|
||||
recurringGroupId: booking.recurringGroupId,
|
||||
createdAt: booking.createdAt.toISOString(),
|
||||
updatedAt: booking.updatedAt.toISOString(),
|
||||
};
|
||||
@@ -300,16 +258,6 @@ export async function createAdminBooking(
|
||||
input: CreateAdminBookingInput
|
||||
) {
|
||||
const complex = await ensureComplexAccess(complexId, userId);
|
||||
|
||||
const adminUser = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { emailVerified: true },
|
||||
});
|
||||
|
||||
if (!adminUser?.emailVerified) {
|
||||
throw new AdminBookingServiceError('Debés verificar tu email para poder crear reservas.', 403);
|
||||
}
|
||||
|
||||
const bookingDate = parseIsoDate(input.date);
|
||||
const dayOfWeek = getDayOfWeek(bookingDate);
|
||||
|
||||
@@ -334,10 +282,6 @@ export async function createAdminBooking(
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
priceRules: {
|
||||
where: { isActive: true },
|
||||
orderBy: [{ dayOfWeek: 'asc' }, { startTime: 'asc' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -364,10 +308,6 @@ export async function createAdminBooking(
|
||||
);
|
||||
}
|
||||
|
||||
if (isSlotInPast(bookingDate, input.startTime, court.slotDurationMinutes)) {
|
||||
throw new AdminBookingServiceError('No se pueden crear reservas en el pasado.', 400);
|
||||
}
|
||||
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
try {
|
||||
const booking = await db.$transaction(async (tx) => {
|
||||
@@ -431,9 +371,7 @@ export async function createAdminBooking(
|
||||
endTime: selectedSlot.endTime,
|
||||
customerName: input.customerName.trim(),
|
||||
customerPhone: input.customerPhone.trim(),
|
||||
customerEmail: input.customerEmail?.trim() ?? '',
|
||||
status: 'CONFIRMED',
|
||||
recurringGroupId: null,
|
||||
},
|
||||
include: {
|
||||
court: {
|
||||
@@ -459,9 +397,7 @@ export async function createAdminBooking(
|
||||
});
|
||||
});
|
||||
|
||||
const price = resolvePrice(court, dayOfWeek, selectedSlot.startTime, selectedSlot.endTime);
|
||||
|
||||
return mapBookingResponse({ ...booking, price });
|
||||
return mapBookingResponse(booking);
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
throw error;
|
||||
@@ -558,43 +494,34 @@ export async function updateAdminBookingStatus(
|
||||
);
|
||||
}
|
||||
|
||||
if (input.status === 'NOSHOW' && booking.status !== 'CONFIRMED') {
|
||||
throw new AdminBookingServiceError(
|
||||
'Solo se pueden marcar como no show las reservas confirmadas.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
await db.courtBookingLog.create({
|
||||
const updated = await db.courtBooking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
bookingCode: booking.bookingCode,
|
||||
courtId: booking.court.id,
|
||||
bookingDate: booking.bookingDate,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
previousStatus: booking.status,
|
||||
newStatus: input.status,
|
||||
changedAt: new Date(),
|
||||
status:
|
||||
input.status === 'COMPLETED' ? CourtBookingStatus.COMPLETED : CourtBookingStatus.CANCELLED,
|
||||
},
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
complexName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (input.status === 'COMPLETED' || input.status === 'NOSHOW') {
|
||||
await db.courtBooking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
status: input.status,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// if the booking is cancelled we delete it to free up the slot, but we keep a log of it with the cancelled status
|
||||
await db.courtBooking.delete({
|
||||
where: { id: booking.id },
|
||||
});
|
||||
}
|
||||
|
||||
return mapBookingResponse({ ...booking, status: input.status });
|
||||
return mapBookingResponse(updated);
|
||||
}
|
||||
|
||||
@@ -1,893 +0,0 @@
|
||||
import { randomInt } from 'node:crypto';
|
||||
import { DayOfWeek as DayOfWeekEnum } from '@/generated/prisma/enums';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { isSlotInPast } from '@/lib/slot-validator';
|
||||
import {
|
||||
evaluatePlanUsage,
|
||||
isFeatureEnabled,
|
||||
parsePlanRules,
|
||||
} from '@/modules/plan/services/plan-rules.service';
|
||||
import type {
|
||||
CreateRecurringBookingInput,
|
||||
RecurringBookingGroup,
|
||||
UpdateRecurringGroupInput,
|
||||
} from '@repo/api-contract';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
import { AdminBookingServiceError } from './admin-booking.service';
|
||||
|
||||
const BOOKING_CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
const BOOKING_CODE_LENGTH = 6;
|
||||
const MAX_RECURRING_WEEKS = 52;
|
||||
|
||||
const DAY_INDEX_BY_VALUE: Record<string, number> = {
|
||||
SUNDAY: 0,
|
||||
MONDAY: 1,
|
||||
TUESDAY: 2,
|
||||
WEDNESDAY: 3,
|
||||
THURSDAY: 4,
|
||||
FRIDAY: 5,
|
||||
SATURDAY: 6,
|
||||
};
|
||||
|
||||
const DAY_OF_WEEK_BY_INDEX = [
|
||||
DayOfWeekEnum.SUNDAY,
|
||||
DayOfWeekEnum.MONDAY,
|
||||
DayOfWeekEnum.TUESDAY,
|
||||
DayOfWeekEnum.WEDNESDAY,
|
||||
DayOfWeekEnum.THURSDAY,
|
||||
DayOfWeekEnum.FRIDAY,
|
||||
DayOfWeekEnum.SATURDAY,
|
||||
] as const;
|
||||
|
||||
type DayOfWeek = (typeof DAY_OF_WEEK_BY_INDEX)[number];
|
||||
|
||||
function toMinutes(value: string): number {
|
||||
const [hours, minutes] = value.split(':').map((part) => Number(part));
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
function minutesToTime(minutes: number): string {
|
||||
const safeMinutes = Math.max(0, minutes);
|
||||
const hours = Math.floor(safeMinutes / 60);
|
||||
const mins = safeMinutes % 60;
|
||||
return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function parseIsoDate(date: string): Date {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
||||
|
||||
if (!match) {
|
||||
throw new AdminBookingServiceError('La fecha debe tener formato YYYY-MM-DD.', 400);
|
||||
}
|
||||
|
||||
const year = Number(match[1]);
|
||||
const month = Number(match[2]);
|
||||
const day = Number(match[3]);
|
||||
|
||||
const parsed = new Date(Date.UTC(year, month - 1, day));
|
||||
|
||||
if (
|
||||
Number.isNaN(parsed.getTime()) ||
|
||||
parsed.getUTCFullYear() !== year ||
|
||||
parsed.getUTCMonth() + 1 !== month ||
|
||||
parsed.getUTCDate() !== day
|
||||
) {
|
||||
throw new AdminBookingServiceError('La fecha enviada no es valida.', 400);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function formatIsoDate(date: Date): string {
|
||||
return date.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function getDayOfWeekValue(date: Date): DayOfWeek {
|
||||
const dayOfWeek = DAY_OF_WEEK_BY_INDEX[date.getUTCDay()];
|
||||
|
||||
if (!dayOfWeek) {
|
||||
throw new AdminBookingServiceError('No se pudo resolver el dia de la semana.', 400);
|
||||
}
|
||||
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
function generateBookingCode(): string {
|
||||
let code = '';
|
||||
|
||||
for (let index = 0; index < BOOKING_CODE_LENGTH; index += 1) {
|
||||
code += BOOKING_CODE_ALPHABET[randomInt(0, BOOKING_CODE_ALPHABET.length)];
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
function buildSlots(
|
||||
availability: Array<{
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}>,
|
||||
slotDurationMinutes: number
|
||||
) {
|
||||
const slots: Array<{ startTime: string; endTime: string }> = [];
|
||||
|
||||
for (const range of availability) {
|
||||
const start = toMinutes(range.startTime);
|
||||
const end = toMinutes(range.endTime);
|
||||
|
||||
for (
|
||||
let current = start;
|
||||
current + slotDurationMinutes <= end;
|
||||
current += slotDurationMinutes
|
||||
) {
|
||||
slots.push({
|
||||
startTime: minutesToTime(current),
|
||||
endTime: minutesToTime(current + slotDurationMinutes),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
async function ensureComplexAccess(complexId: string, userId: string) {
|
||||
const complexUser = await db.complexUser.findUnique({
|
||||
where: {
|
||||
complexId_userId: {
|
||||
complexId,
|
||||
userId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
complexName: true,
|
||||
plan: {
|
||||
select: {
|
||||
rules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!complexUser) {
|
||||
throw new AdminBookingServiceError('No tienes permisos para administrar este complejo.', 403);
|
||||
}
|
||||
|
||||
return complexUser.complex;
|
||||
}
|
||||
|
||||
function* generateRecurringDates(
|
||||
startDate: Date,
|
||||
endDate: Date | null,
|
||||
dayOfWeek: number
|
||||
): Generator<Date> {
|
||||
const current = new Date(startDate);
|
||||
current.setUTCDate(current.getUTCDate() + ((dayOfWeek - current.getUTCDay() + 7) % 7));
|
||||
|
||||
if (current < startDate) {
|
||||
current.setUTCDate(current.getUTCDate() + 7);
|
||||
}
|
||||
|
||||
const maxDate = endDate ?? new Date(startDate);
|
||||
if (!endDate) {
|
||||
maxDate.setUTCDate(maxDate.getUTCDate() + MAX_RECURRING_WEEKS * 7);
|
||||
}
|
||||
|
||||
while (current <= maxDate) {
|
||||
yield new Date(current);
|
||||
current.setUTCDate(current.getUTCDate() + 7);
|
||||
}
|
||||
}
|
||||
|
||||
function mapBookingResponse(booking: {
|
||||
id: string;
|
||||
bookingCode: string;
|
||||
bookingDate: Date;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
customerName: string;
|
||||
customerPhone: string;
|
||||
customerEmail: string;
|
||||
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NOSHOW';
|
||||
recurringGroupId: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
court: {
|
||||
id: string;
|
||||
name: string;
|
||||
complex: {
|
||||
id: string;
|
||||
complexName: string;
|
||||
};
|
||||
sport: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
};
|
||||
price?: number;
|
||||
}) {
|
||||
return {
|
||||
id: booking.id,
|
||||
bookingCode: booking.bookingCode,
|
||||
complexId: booking.court.complex.id,
|
||||
complexName: booking.court.complex.complexName,
|
||||
courtId: booking.court.id,
|
||||
courtName: booking.court.name,
|
||||
sport: {
|
||||
id: booking.court.sport.id,
|
||||
name: booking.court.sport.name,
|
||||
slug: booking.court.sport.slug,
|
||||
},
|
||||
date: formatIsoDate(booking.bookingDate),
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
price: booking.price ?? 0,
|
||||
status: booking.status,
|
||||
recurringGroupId: booking.recurringGroupId,
|
||||
createdAt: booking.createdAt.toISOString(),
|
||||
updatedAt: booking.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createAdminRecurringBooking(
|
||||
userId: string,
|
||||
complexId: string,
|
||||
input: CreateRecurringBookingInput
|
||||
): Promise<RecurringBookingGroup> {
|
||||
const complex = await ensureComplexAccess(complexId, userId);
|
||||
|
||||
const adminUser = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { emailVerified: true },
|
||||
});
|
||||
|
||||
if (!adminUser?.emailVerified) {
|
||||
throw new AdminBookingServiceError('Debés verificar tu email para poder crear reservas.', 403);
|
||||
}
|
||||
|
||||
if (!complex.plan) {
|
||||
throw new AdminBookingServiceError('El complejo no tiene un plan asignado.', 403);
|
||||
}
|
||||
|
||||
const rules = parsePlanRules(complex.plan.rules);
|
||||
|
||||
if (!isFeatureEnabled(rules, 'fixedSlots')) {
|
||||
throw new AdminBookingServiceError(
|
||||
'Tu plan no permite la creación de turnos fijos. Comunicate con el administrador.',
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
const startDate = parseIsoDate(input.date);
|
||||
const dayOfWeek = getDayOfWeekValue(startDate);
|
||||
const endDate = input.recurringEndDate ? parseIsoDate(input.recurringEndDate) : null;
|
||||
|
||||
if (endDate && endDate <= startDate) {
|
||||
throw new AdminBookingServiceError(
|
||||
'La fecha de fin debe ser posterior a la fecha de inicio.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
const court = await db.court.findFirst({
|
||||
where: {
|
||||
id: input.courtId,
|
||||
complexId,
|
||||
},
|
||||
include: {
|
||||
availabilities: {
|
||||
where: {
|
||||
dayOfWeek,
|
||||
},
|
||||
orderBy: {
|
||||
startTime: 'asc',
|
||||
},
|
||||
},
|
||||
sport: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
priceRules: {
|
||||
where: { isActive: true },
|
||||
orderBy: [{ dayOfWeek: 'asc' }, { startTime: 'asc' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!court) {
|
||||
throw new AdminBookingServiceError('La cancha seleccionada no existe en el complejo.', 404);
|
||||
}
|
||||
|
||||
const validSlots = buildSlots(court.availabilities, court.slotDurationMinutes);
|
||||
const selectedEndMinutes = toMinutes(input.startTime) + court.slotDurationMinutes;
|
||||
const selectedSlot = {
|
||||
startTime: input.startTime,
|
||||
endTime: minutesToTime(selectedEndMinutes),
|
||||
};
|
||||
|
||||
const slotExists = validSlots.some(
|
||||
(slot) => slot.startTime === selectedSlot.startTime && slot.endTime === selectedSlot.endTime
|
||||
);
|
||||
|
||||
if (!slotExists) {
|
||||
throw new AdminBookingServiceError(
|
||||
'El horario seleccionado no esta disponible para esa cancha.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
if (isSlotInPast(startDate, input.startTime, court.slotDurationMinutes)) {
|
||||
throw new AdminBookingServiceError('La fecha de inicio no puede estar en el pasado.', 400);
|
||||
}
|
||||
|
||||
const recurringDates = Array.from(
|
||||
generateRecurringDates(startDate, endDate, startDate.getUTCDay())
|
||||
);
|
||||
|
||||
if (recurringDates.length === 0) {
|
||||
throw new AdminBookingServiceError(
|
||||
'No se generaron fechas para la reserva periódica. Verifica las fechas ingresadas.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
try {
|
||||
const result = await db.$transaction(async (tx) => {
|
||||
const groupId = uuidv7();
|
||||
|
||||
const bookingsForDate = await tx.courtBooking.count({
|
||||
where: {
|
||||
startTime: selectedSlot.startTime,
|
||||
bookingDate: startDate,
|
||||
status: 'CONFIRMED',
|
||||
court: {
|
||||
complexId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const courtsCount = await tx.court.count({
|
||||
where: { complexId },
|
||||
});
|
||||
|
||||
const violations = evaluatePlanUsage(rules, {
|
||||
courtsCount,
|
||||
bookingsToday: bookingsForDate,
|
||||
});
|
||||
|
||||
const maxBookingsViolation = violations.find(
|
||||
(v) => v.code === 'MAX_BOOKINGS_PER_DAY_REACHED'
|
||||
);
|
||||
|
||||
if (maxBookingsViolation) {
|
||||
throw new AdminBookingServiceError(maxBookingsViolation.message, 409);
|
||||
}
|
||||
|
||||
const group = await tx.recurringBookingGroup.create({
|
||||
data: {
|
||||
id: groupId,
|
||||
complexId,
|
||||
courtId: court.id,
|
||||
startTime: selectedSlot.startTime,
|
||||
endTime: selectedSlot.endTime,
|
||||
dayOfWeek,
|
||||
startDate,
|
||||
endDate,
|
||||
status: 'ACTIVE',
|
||||
customerName: input.customerName.trim(),
|
||||
customerPhone: input.customerPhone.trim(),
|
||||
customerEmail: input.customerEmail?.trim() ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
const createdBookings = [];
|
||||
|
||||
for (const date of recurringDates) {
|
||||
if (isSlotInPast(date, input.startTime, court.slotDurationMinutes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const overlappingBooking = await tx.courtBooking.findFirst({
|
||||
where: {
|
||||
courtId: court.id,
|
||||
bookingDate: date,
|
||||
status: 'CONFIRMED',
|
||||
startTime: {
|
||||
lt: selectedSlot.endTime,
|
||||
},
|
||||
endTime: {
|
||||
gt: selectedSlot.startTime,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (overlappingBooking) {
|
||||
throw new AdminBookingServiceError(
|
||||
`El horario seleccionado ya fue reservado para el dia ${formatIsoDate(date)}.`,
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
const booking = await tx.courtBooking.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
bookingCode: generateBookingCode(),
|
||||
courtId: court.id,
|
||||
bookingDate: date,
|
||||
startTime: selectedSlot.startTime,
|
||||
endTime: selectedSlot.endTime,
|
||||
customerName: input.customerName.trim(),
|
||||
customerPhone: input.customerPhone.trim(),
|
||||
customerEmail: input.customerEmail?.trim() ?? '',
|
||||
status: 'CONFIRMED',
|
||||
recurringGroupId: groupId,
|
||||
},
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
complexName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createdBookings.push(booking);
|
||||
}
|
||||
|
||||
return { group, bookings: createdBookings };
|
||||
});
|
||||
|
||||
return {
|
||||
id: result.group.id,
|
||||
complexId,
|
||||
courtId: court.id,
|
||||
startTime: result.group.startTime,
|
||||
endTime: result.group.endTime,
|
||||
dayOfWeek: result.group.dayOfWeek,
|
||||
startDate: formatIsoDate(result.group.startDate),
|
||||
endDate: result.group.endDate ? formatIsoDate(result.group.endDate) : null,
|
||||
status: 'ACTIVE' as const,
|
||||
customerName: result.group.customerName,
|
||||
customerPhone: result.group.customerPhone,
|
||||
customerEmail: result.group.customerEmail,
|
||||
bookings: result.bookings.map((b) => mapBookingResponse(b)),
|
||||
createdAt: result.group.createdAt.toISOString(),
|
||||
updatedAt: result.group.updatedAt.toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AdminBookingServiceError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const prismaError = error as {
|
||||
code?: string;
|
||||
meta?: {
|
||||
target?: string[] | string;
|
||||
};
|
||||
};
|
||||
|
||||
if (prismaError.code === 'P2002') {
|
||||
const targets = Array.isArray(prismaError.meta?.target)
|
||||
? prismaError.meta?.target
|
||||
: [prismaError.meta?.target];
|
||||
const isBookingCodeCollision = targets.some((target) =>
|
||||
String(target).includes('booking_code')
|
||||
);
|
||||
|
||||
if (isBookingCodeCollision) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new AdminBookingServiceError('El horario seleccionado ya fue reservado.', 409);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
throw new AdminBookingServiceError(
|
||||
'No se pudo generar un codigo de reserva unico. Intenta nuevamente.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
export async function cancelRecurringGroup(userId: string, groupId: string) {
|
||||
const group = await db.recurringBookingGroup.findUnique({
|
||||
where: { id: groupId },
|
||||
include: {
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
users: {
|
||||
where: { userId },
|
||||
select: { userId: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!group) {
|
||||
throw new AdminBookingServiceError('Grupo de reservas no encontrado.', 404);
|
||||
}
|
||||
|
||||
if (group.complex.users.length === 0) {
|
||||
throw new AdminBookingServiceError('No tienes permisos para administrar este complejo.', 403);
|
||||
}
|
||||
|
||||
if (group.status === 'CANCELLED') {
|
||||
throw new AdminBookingServiceError('El grupo ya fue cancelado anteriormente.', 409);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const todayStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
||||
|
||||
await db.$transaction(async (tx) => {
|
||||
await tx.recurringBookingGroup.update({
|
||||
where: { id: groupId },
|
||||
data: { status: 'CANCELLED' },
|
||||
});
|
||||
|
||||
const futureBookings = await tx.courtBooking.findMany({
|
||||
where: {
|
||||
recurringGroupId: groupId,
|
||||
bookingDate: { gte: todayStart },
|
||||
status: 'CONFIRMED',
|
||||
},
|
||||
});
|
||||
|
||||
for (const booking of futureBookings) {
|
||||
await tx.courtBookingLog.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
bookingCode: booking.bookingCode,
|
||||
courtId: booking.courtId,
|
||||
bookingDate: booking.bookingDate,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
previousStatus: booking.status,
|
||||
newStatus: 'CANCELLED',
|
||||
changedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
await tx.courtBooking.delete({
|
||||
where: { id: booking.id },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export async function listRecurringGroups(complexId: string): Promise<RecurringBookingGroup[]> {
|
||||
const groups = await db.recurringBookingGroup.findMany({
|
||||
where: { complexId, status: 'ACTIVE' },
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
complex: {
|
||||
select: { id: true, complexName: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
bookings: {
|
||||
orderBy: { bookingDate: 'asc' },
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
complex: {
|
||||
select: { id: true, complexName: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return groups.map((group) => ({
|
||||
id: group.id,
|
||||
complexId: group.complexId,
|
||||
courtId: group.courtId,
|
||||
startTime: group.startTime,
|
||||
endTime: group.endTime,
|
||||
dayOfWeek: group.dayOfWeek,
|
||||
startDate: formatIsoDate(group.startDate),
|
||||
endDate: group.endDate ? formatIsoDate(group.endDate) : null,
|
||||
status: group.status,
|
||||
customerName: group.customerName,
|
||||
customerPhone: group.customerPhone,
|
||||
customerEmail: group.customerEmail,
|
||||
bookings: group.bookings.map(mapBookingResponse),
|
||||
createdAt: group.createdAt.toISOString(),
|
||||
updatedAt: group.updatedAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function updateRecurringGroup(
|
||||
userId: string,
|
||||
groupId: string,
|
||||
input: UpdateRecurringGroupInput
|
||||
): Promise<RecurringBookingGroup> {
|
||||
const group = await db.recurringBookingGroup.findUnique({
|
||||
where: { id: groupId },
|
||||
include: {
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
users: {
|
||||
where: { userId },
|
||||
select: { userId: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!group) {
|
||||
throw new AdminBookingServiceError('Grupo de turnos fijos no encontrado.', 404);
|
||||
}
|
||||
|
||||
if (group.complex.users.length === 0) {
|
||||
throw new AdminBookingServiceError('No tienes permisos para administrar este complejo.', 403);
|
||||
}
|
||||
|
||||
if (group.status === 'CANCELLED') {
|
||||
throw new AdminBookingServiceError('No se puede editar un grupo cancelado.', 409);
|
||||
}
|
||||
|
||||
const courtId = input.courtId ?? group.courtId;
|
||||
const dayOfWeek = input.dayOfWeek ?? group.dayOfWeek;
|
||||
const startTime = input.startTime ?? group.startTime;
|
||||
|
||||
const scheduleChanged =
|
||||
input.courtId !== undefined || input.dayOfWeek !== undefined || input.startTime !== undefined;
|
||||
|
||||
if (scheduleChanged) {
|
||||
const court = await db.court.findFirst({
|
||||
where: { id: courtId, complexId: group.complexId },
|
||||
include: {
|
||||
availabilities: {
|
||||
where: { dayOfWeek: dayOfWeek as DayOfWeek },
|
||||
orderBy: { startTime: 'asc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!court) {
|
||||
throw new AdminBookingServiceError('La cancha seleccionada no existe en el complejo.', 404);
|
||||
}
|
||||
|
||||
const validSlots = buildSlots(court.availabilities, court.slotDurationMinutes);
|
||||
const selectedEndMinutes = toMinutes(startTime) + court.slotDurationMinutes;
|
||||
const endTime = minutesToTime(selectedEndMinutes);
|
||||
|
||||
const slotExists = validSlots.some(
|
||||
(slot) => slot.startTime === startTime && slot.endTime === endTime
|
||||
);
|
||||
|
||||
if (!slotExists) {
|
||||
throw new AdminBookingServiceError(
|
||||
'El horario seleccionado no esta disponible para esa cancha.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const todayStart = new Date(
|
||||
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
||||
);
|
||||
|
||||
await db.$transaction(async (tx) => {
|
||||
await tx.recurringBookingGroup.update({
|
||||
where: { id: groupId },
|
||||
data: {
|
||||
courtId,
|
||||
dayOfWeek: dayOfWeek as DayOfWeek,
|
||||
startTime,
|
||||
endTime,
|
||||
customerName: input.customerName?.trim() ?? group.customerName,
|
||||
customerPhone: input.customerPhone?.trim() ?? group.customerPhone,
|
||||
customerEmail: input.customerEmail?.trim() ?? group.customerEmail,
|
||||
},
|
||||
});
|
||||
|
||||
const futureBookings = await tx.courtBooking.findMany({
|
||||
where: {
|
||||
recurringGroupId: groupId,
|
||||
bookingDate: { gte: todayStart },
|
||||
status: 'CONFIRMED',
|
||||
},
|
||||
});
|
||||
|
||||
for (const booking of futureBookings) {
|
||||
await tx.courtBookingLog.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
bookingCode: booking.bookingCode,
|
||||
courtId: booking.courtId,
|
||||
bookingDate: booking.bookingDate,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
previousStatus: booking.status,
|
||||
newStatus: 'CANCELLED',
|
||||
changedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
await tx.courtBooking.delete({
|
||||
where: { id: booking.id },
|
||||
});
|
||||
}
|
||||
|
||||
const dayIndex = DAY_INDEX_BY_VALUE[dayOfWeek] ?? 0;
|
||||
const recurringDates = Array.from(
|
||||
generateRecurringDates(todayStart, group.endDate, dayIndex)
|
||||
);
|
||||
|
||||
for (const date of recurringDates) {
|
||||
const overlappingBooking = await tx.courtBooking.findFirst({
|
||||
where: {
|
||||
courtId,
|
||||
bookingDate: date,
|
||||
status: 'CONFIRMED',
|
||||
startTime: { lt: endTime },
|
||||
endTime: { gt: startTime },
|
||||
},
|
||||
});
|
||||
|
||||
if (overlappingBooking) {
|
||||
throw new AdminBookingServiceError(
|
||||
`El horario seleccionado ya fue reservado para el dia ${formatIsoDate(date)}.`,
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
await tx.courtBooking.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
bookingCode: generateBookingCode(),
|
||||
courtId,
|
||||
bookingDate: date,
|
||||
startTime,
|
||||
endTime,
|
||||
customerName: input.customerName?.trim() ?? group.customerName,
|
||||
customerPhone: input.customerPhone?.trim() ?? group.customerPhone,
|
||||
customerEmail: input.customerEmail?.trim() ?? group.customerEmail,
|
||||
status: 'CONFIRMED',
|
||||
recurringGroupId: groupId,
|
||||
},
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: { select: { id: true, name: true, slug: true } },
|
||||
complex: { select: { id: true, complexName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await db.recurringBookingGroup.update({
|
||||
where: { id: groupId },
|
||||
data: {
|
||||
...(input.customerName !== undefined && { customerName: input.customerName.trim() }),
|
||||
...(input.customerPhone !== undefined && { customerPhone: input.customerPhone.trim() }),
|
||||
...(input.customerEmail !== undefined && { customerEmail: input.customerEmail.trim() }),
|
||||
},
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
const todayStart = new Date(
|
||||
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
||||
);
|
||||
|
||||
const updateData: Record<string, string> = {};
|
||||
if (input.customerName !== undefined) updateData.customerName = input.customerName.trim();
|
||||
if (input.customerPhone !== undefined) updateData.customerPhone = input.customerPhone.trim();
|
||||
if (input.customerEmail !== undefined) updateData.customerEmail = input.customerEmail.trim();
|
||||
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await db.courtBooking.updateMany({
|
||||
where: { recurringGroupId: groupId, bookingDate: { gte: todayStart } },
|
||||
data: updateData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await db.recurringBookingGroup.findUnique({
|
||||
where: { id: groupId },
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: { select: { id: true, name: true, slug: true } },
|
||||
complex: { select: { id: true, complexName: true } },
|
||||
},
|
||||
},
|
||||
bookings: {
|
||||
orderBy: { bookingDate: 'asc' },
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sport: { select: { id: true, name: true, slug: true } },
|
||||
complex: { select: { id: true, complexName: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
throw new AdminBookingServiceError('Error al actualizar el grupo.', 409);
|
||||
}
|
||||
|
||||
return {
|
||||
id: updated.id,
|
||||
complexId: updated.complexId,
|
||||
courtId: updated.courtId,
|
||||
startTime: updated.startTime,
|
||||
endTime: updated.endTime,
|
||||
dayOfWeek: updated.dayOfWeek,
|
||||
startDate: formatIsoDate(updated.startDate),
|
||||
endDate: updated.endDate ? formatIsoDate(updated.endDate) : null,
|
||||
status: updated.status,
|
||||
customerName: updated.customerName,
|
||||
customerPhone: updated.customerPhone,
|
||||
customerEmail: updated.customerEmail,
|
||||
bookings: updated.bookings.map(mapBookingResponse),
|
||||
createdAt: updated.createdAt.toISOString(),
|
||||
updatedAt: updated.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { validate } from '@/lib/http/validate';
|
||||
import { requireAuth } from '@/middlewares/require-auth.middleware';
|
||||
import { requireSuperAdmin } from '@/middlewares/require-super-admin.middleware';
|
||||
import { blockUserHandler } from '@/modules/admin/handlers/block-user.handler';
|
||||
import { createPlanHandler } from '@/modules/admin/handlers/create-plan.handler';
|
||||
import { deletePlanHandler } from '@/modules/admin/handlers/delete-plan.handler';
|
||||
import { getGeoStatsHandler } from '@/modules/admin/handlers/get-geo-stats.handler';
|
||||
import { getUserSessionsHandler } from '@/modules/admin/handlers/get-user-sessions.handler';
|
||||
import { listComplexesHandler } from '@/modules/admin/handlers/list-complexes.handler';
|
||||
import { listPlansAdminHandler } from '@/modules/admin/handlers/list-plans-admin.handler';
|
||||
import { listUsersHandler } from '@/modules/admin/handlers/list-users.handler';
|
||||
import { revokeAllSessionsHandler } from '@/modules/admin/handlers/revoke-all-sessions.handler';
|
||||
import { unblockUserHandler } from '@/modules/admin/handlers/unblock-user.handler';
|
||||
import { updatePlanHandler } from '@/modules/admin/handlers/update-plan.handler';
|
||||
import type { AppEnv } from '@/types/hono';
|
||||
import {
|
||||
adminBlockUserSchema,
|
||||
adminCreatePlanSchema,
|
||||
adminUpdatePlanSchema,
|
||||
} from '@repo/api-contract';
|
||||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const adminRoutes = new Hono<AppEnv>();
|
||||
|
||||
adminRoutes.use('*', requireAuth, requireSuperAdmin);
|
||||
|
||||
adminRoutes.get('/complexes', listComplexesHandler);
|
||||
|
||||
adminRoutes.get('/plans', listPlansAdminHandler);
|
||||
adminRoutes.post('/plans', validate.json(adminCreatePlanSchema), createPlanHandler);
|
||||
adminRoutes.patch(
|
||||
'/plans/:code',
|
||||
validate.param(z.object({ code: z.string().min(1).max(10) })),
|
||||
validate.json(adminUpdatePlanSchema),
|
||||
updatePlanHandler
|
||||
);
|
||||
adminRoutes.delete(
|
||||
'/plans/:code',
|
||||
validate.param(z.object({ code: z.string().min(1).max(10) })),
|
||||
deletePlanHandler
|
||||
);
|
||||
|
||||
adminRoutes.get('/geo-stats', getGeoStatsHandler);
|
||||
|
||||
adminRoutes.get('/users', listUsersHandler);
|
||||
adminRoutes.post(
|
||||
'/users/:id/block',
|
||||
validate.param(z.object({ id: z.string().min(1) })),
|
||||
validate.json(adminBlockUserSchema),
|
||||
blockUserHandler
|
||||
);
|
||||
adminRoutes.post(
|
||||
'/users/:id/unblock',
|
||||
validate.param(z.object({ id: z.string().min(1) })),
|
||||
unblockUserHandler
|
||||
);
|
||||
adminRoutes.get(
|
||||
'/users/:id/sessions',
|
||||
validate.param(z.object({ id: z.string().min(1) })),
|
||||
getUserSessionsHandler
|
||||
);
|
||||
adminRoutes.post(
|
||||
'/users/:id/sessions/revoke-all',
|
||||
validate.param(z.object({ id: z.string().min(1) })),
|
||||
revokeAllSessionsHandler
|
||||
);
|
||||
@@ -1,20 +0,0 @@
|
||||
import { AdminServiceError, blockUser } from '@/modules/admin/services/admin-users.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { AdminBlockUserInput } from '@repo/api-contract';
|
||||
|
||||
type BlockUserParams = { id: string };
|
||||
|
||||
export async function blockUserHandler(c: AppContext) {
|
||||
const { id } = c.req.valid('param' as never) as BlockUserParams;
|
||||
const body = c.req.valid('json' as never) as AdminBlockUserInput;
|
||||
|
||||
try {
|
||||
await blockUser(id, body.banReason);
|
||||
return c.json({ message: 'Usuario bloqueado correctamente.' });
|
||||
} catch (error) {
|
||||
if (error instanceof AdminServiceError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { AdminCreatePlanInput } from '@repo/api-contract';
|
||||
|
||||
export async function createPlanHandler(c: AppContext) {
|
||||
const body = c.req.valid('json' as never) as AdminCreatePlanInput;
|
||||
|
||||
const existing = await db.plan.findUnique({
|
||||
where: { code: body.code },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return c.json({ message: 'Ya existe un plan con ese código.' }, 409);
|
||||
}
|
||||
|
||||
const plan = await db.plan.create({
|
||||
data: {
|
||||
code: body.code,
|
||||
name: body.name,
|
||||
price: body.price,
|
||||
rules: body.rules,
|
||||
},
|
||||
});
|
||||
|
||||
return c.json(
|
||||
{
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: Number(plan.price),
|
||||
rules: plan.rules,
|
||||
},
|
||||
201
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
type DeletePlanParams = { code: string };
|
||||
|
||||
export async function deletePlanHandler(c: AppContext) {
|
||||
const { code } = c.req.valid('param' as never) as DeletePlanParams;
|
||||
|
||||
const existing = await db.plan.findUnique({
|
||||
where: { code },
|
||||
include: {
|
||||
_count: {
|
||||
select: { complexes: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return c.json({ message: 'Plan no encontrado.' }, 404);
|
||||
}
|
||||
|
||||
if (existing._count.complexes > 0) {
|
||||
return c.json({ message: 'No se puede eliminar un plan que tiene complejos asignados.' }, 409);
|
||||
}
|
||||
|
||||
await db.plan.delete({
|
||||
where: { code },
|
||||
});
|
||||
|
||||
return c.json({ message: 'Plan eliminado correctamente.' });
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { getGeoStats } from '@/modules/admin/services/admin-geo.service';
|
||||
import type { AppEnv } from '@/types/hono';
|
||||
import type { Handler } from 'hono';
|
||||
|
||||
export const getGeoStatsHandler: Handler<AppEnv> = async (c) => {
|
||||
const stats = await getGeoStats();
|
||||
return c.json(stats);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { getUserSessions } from '@/modules/admin/services/admin-users.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
type UserSessionsParams = { id: string };
|
||||
|
||||
export async function getUserSessionsHandler(c: AppContext) {
|
||||
const { id } = c.req.valid('param' as never) as UserSessionsParams;
|
||||
|
||||
const sessions = await getUserSessions(id);
|
||||
|
||||
return c.json(sessions);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { getComplexStatsList } from '@/modules/admin/services/complex-stats.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
export async function listComplexesHandler(c: AppContext) {
|
||||
const stats = await getComplexStatsList();
|
||||
return c.json(stats);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
export async function listPlansAdminHandler(c: AppContext) {
|
||||
const plans = await db.plan.findMany({
|
||||
orderBy: { price: 'asc' },
|
||||
include: {
|
||||
_count: {
|
||||
select: { complexes: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return c.json(
|
||||
plans.map((plan) => ({
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: Number(plan.price),
|
||||
rules: plan.rules,
|
||||
lastUpdatedAt: plan.lastUpdatedAt.toISOString(),
|
||||
complexCount: plan._count.complexes,
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { listAdminUsers } from '@/modules/admin/services/admin-users.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
export async function listUsersHandler(c: AppContext) {
|
||||
const search = c.req.query('search');
|
||||
const users = await listAdminUsers(search);
|
||||
return c.json(users);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { revokeAllUserSessions } from '@/modules/admin/services/admin-users.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
type RevokeSessionsParams = { id: string };
|
||||
|
||||
export async function revokeAllSessionsHandler(c: AppContext) {
|
||||
const { id } = c.req.valid('param' as never) as RevokeSessionsParams;
|
||||
|
||||
const count = await revokeAllUserSessions(id);
|
||||
|
||||
return c.json({ message: `${count} sesiones cerradas correctamente.` });
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { unblockUser } from '@/modules/admin/services/admin-users.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
type UnblockUserParams = { id: string };
|
||||
|
||||
export async function unblockUserHandler(c: AppContext) {
|
||||
const { id } = c.req.valid('param' as never) as UnblockUserParams;
|
||||
|
||||
await unblockUser(id);
|
||||
|
||||
return c.json({ message: 'Usuario desbloqueado correctamente.' });
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { AdminUpdatePlanInput } from '@repo/api-contract';
|
||||
|
||||
type UpdatePlanParams = { code: string };
|
||||
|
||||
export async function updatePlanHandler(c: AppContext) {
|
||||
const { code } = c.req.valid('param' as never) as UpdatePlanParams;
|
||||
const body = c.req.valid('json' as never) as AdminUpdatePlanInput;
|
||||
|
||||
const existing = await db.plan.findUnique({
|
||||
where: { code },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return c.json({ message: 'Plan no encontrado.' }, 404);
|
||||
}
|
||||
|
||||
const plan = await db.plan.update({
|
||||
where: { code },
|
||||
data: {
|
||||
...(body.name !== undefined && { name: body.name }),
|
||||
...(body.price !== undefined && { price: body.price }),
|
||||
...(body.rules !== undefined && { rules: body.rules }),
|
||||
lastUpdatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return c.json({
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: Number(plan.price),
|
||||
rules: plan.rules,
|
||||
});
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
|
||||
type GeoStatsCountry = {
|
||||
country: string;
|
||||
countryCode: string;
|
||||
totalUsers: number;
|
||||
cities: { city: string; userCount: number }[];
|
||||
};
|
||||
|
||||
export async function getGeoStats(): Promise<{
|
||||
countries: GeoStatsCountry[];
|
||||
totalUniqueCountries: number;
|
||||
}> {
|
||||
const sessions = await db.session.findMany({
|
||||
where: {
|
||||
country: { not: null },
|
||||
expiresAt: { gt: new Date() },
|
||||
},
|
||||
select: {
|
||||
country: true,
|
||||
countryCode: true,
|
||||
city: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
const countryMap = new Map<
|
||||
string,
|
||||
{ country: string; countryCode: string; users: Set<string>; cities: Map<string, Set<string>> }
|
||||
>();
|
||||
|
||||
for (const s of sessions) {
|
||||
const code = s.countryCode || 'XX';
|
||||
let entry = countryMap.get(code);
|
||||
if (!entry) {
|
||||
entry = {
|
||||
country: s.country || 'Desconocido',
|
||||
countryCode: code,
|
||||
users: new Set(),
|
||||
cities: new Map(),
|
||||
};
|
||||
countryMap.set(code, entry);
|
||||
}
|
||||
entry.users.add(s.userId);
|
||||
|
||||
const cityName = s.city || 'Desconocida';
|
||||
let cityUsers = entry.cities.get(cityName);
|
||||
if (!cityUsers) {
|
||||
cityUsers = new Set();
|
||||
entry.cities.set(cityName, cityUsers);
|
||||
}
|
||||
cityUsers.add(s.userId);
|
||||
}
|
||||
|
||||
const countries = Array.from(countryMap.values())
|
||||
.map((entry) => ({
|
||||
country: entry.country,
|
||||
countryCode: entry.countryCode,
|
||||
totalUsers: entry.users.size,
|
||||
cities: Array.from(entry.cities.entries())
|
||||
.map(([city, users]) => ({ city, userCount: users.size }))
|
||||
.sort((a, b) => b.userCount - a.userCount),
|
||||
}))
|
||||
.sort((a, b) => b.totalUsers - a.totalUsers);
|
||||
|
||||
return { countries, totalUniqueCountries: countries.length };
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
import { fetchGeoInfo } from '@/lib/geoip';
|
||||
import { db } from '@/lib/prisma';
|
||||
|
||||
type AdminUser = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
banned: boolean;
|
||||
bannedAt: string | null;
|
||||
banReason: string | null;
|
||||
createdAt: string;
|
||||
complexCount: number;
|
||||
activeSessions: number;
|
||||
};
|
||||
|
||||
export async function listAdminUsers(search?: string): Promise<AdminUser[]> {
|
||||
const users = await db.user.findMany({
|
||||
where: search
|
||||
? {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ email: { contains: search, mode: 'insensitive' } },
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
include: {
|
||||
_count: {
|
||||
select: { complexes: true, sessions: true },
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return users.map((user) => ({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
banned: user.banned ?? false,
|
||||
bannedAt: user.bannedAt?.toISOString() ?? null,
|
||||
banReason: user.banReason ?? null,
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
complexCount: user._count.complexes,
|
||||
activeSessions: user._count.sessions,
|
||||
}));
|
||||
}
|
||||
|
||||
type AdminUserSession = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
expiresAt: string;
|
||||
ipAddress: string | null;
|
||||
userAgent: string | null;
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
countryCode: string | null;
|
||||
};
|
||||
|
||||
export async function getUserSessions(userId: string): Promise<AdminUserSession[]> {
|
||||
const sessions = await db.session.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
const uniqueIps = [...new Set(sessions.map((s) => s.ipAddress).filter(Boolean))] as string[];
|
||||
const geoResults = await Promise.all(uniqueIps.map((ip) => fetchGeoInfo(ip)));
|
||||
const geoMap = new Map<
|
||||
string,
|
||||
{ city: string | null; country: string | null; countryCode: string | null }
|
||||
>();
|
||||
for (let i = 0; i < uniqueIps.length; i++) {
|
||||
const info = geoResults[i];
|
||||
geoMap.set(uniqueIps[i], {
|
||||
city: info?.city ?? null,
|
||||
country: info?.country ?? null,
|
||||
countryCode: info?.countryCode ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
return sessions.map((s) => {
|
||||
const geo = s.ipAddress ? geoMap.get(s.ipAddress) : null;
|
||||
return {
|
||||
id: s.id,
|
||||
createdAt: s.createdAt.toISOString(),
|
||||
expiresAt: s.expiresAt.toISOString(),
|
||||
ipAddress: s.ipAddress,
|
||||
userAgent: s.userAgent,
|
||||
city: geo?.city ?? null,
|
||||
country: geo?.country ?? null,
|
||||
countryCode: geo?.countryCode ?? null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export class AdminServiceError extends Error {
|
||||
status: 400 | 403 | 404;
|
||||
constructor(message: string, status: 400 | 403 | 404 = 400) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.name = 'AdminServiceError';
|
||||
}
|
||||
}
|
||||
|
||||
export async function blockUser(userId: string, banReason?: string): Promise<void> {
|
||||
const target = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { role: true },
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
throw new AdminServiceError('Usuario no encontrado.', 404);
|
||||
}
|
||||
|
||||
if (target.role === 'super_admin') {
|
||||
throw new AdminServiceError('No se puede bloquear un super_admin.', 403);
|
||||
}
|
||||
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
banned: true,
|
||||
bannedAt: new Date(),
|
||||
banReason: banReason ?? null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function unblockUser(userId: string): Promise<void> {
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
banned: false,
|
||||
bannedAt: null,
|
||||
banReason: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function revokeAllUserSessions(userId: string): Promise<number> {
|
||||
const result = await db.session.deleteMany({
|
||||
where: { userId },
|
||||
});
|
||||
|
||||
return result.count;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { db } from '@/lib/prisma';
|
||||
|
||||
type ComplexStats = {
|
||||
id: string;
|
||||
complexName: string;
|
||||
complexSlug: string;
|
||||
city: string | null;
|
||||
planCode: string | null;
|
||||
planName: string | null;
|
||||
userCount: number;
|
||||
courtCount: number;
|
||||
totalBookings: number;
|
||||
avgBookingsPerDay: number;
|
||||
bookingsByStatus: {
|
||||
confirmed: number;
|
||||
cancelled: number;
|
||||
completed: number;
|
||||
noshow: number;
|
||||
};
|
||||
paymentStatus: 'active' | 'no_plan' | 'expired';
|
||||
};
|
||||
|
||||
export async function getComplexStatsList(): Promise<ComplexStats[]> {
|
||||
const complexes = await db.complex.findMany({
|
||||
include: {
|
||||
plan: true,
|
||||
users: true,
|
||||
courts: {
|
||||
include: {
|
||||
bookings: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return complexes.map((complex) => {
|
||||
const courtCount = complex.courts.length;
|
||||
const allBookings = complex.courts.flatMap((c) => c.bookings);
|
||||
|
||||
const bookingsByStatus = {
|
||||
confirmed: allBookings.filter((b) => b.status === 'CONFIRMED').length,
|
||||
cancelled: allBookings.filter((b) => b.status === 'CANCELLED').length,
|
||||
completed: allBookings.filter((b) => b.status === 'COMPLETED').length,
|
||||
noshow: allBookings.filter((b) => b.status === 'NOSHOW').length,
|
||||
};
|
||||
|
||||
const totalBookings = allBookings.length;
|
||||
|
||||
let avgBookingsPerDay = 0;
|
||||
if (allBookings.length > 0) {
|
||||
const dates = allBookings.map((b) => b.bookingDate);
|
||||
const minDate = new Date(Math.min(...dates.map((d) => d.getTime())));
|
||||
const daysDiff = Math.max(
|
||||
1,
|
||||
Math.ceil((Date.now() - minDate.getTime()) / (1000 * 60 * 60 * 24))
|
||||
);
|
||||
avgBookingsPerDay = Math.round((totalBookings / daysDiff) * 100) / 100;
|
||||
}
|
||||
|
||||
let paymentStatus: 'active' | 'no_plan' | 'expired' = 'no_plan';
|
||||
if (complex.plan) {
|
||||
paymentStatus = 'active';
|
||||
}
|
||||
|
||||
return {
|
||||
id: complex.id,
|
||||
complexName: complex.complexName,
|
||||
complexSlug: complex.complexSlug,
|
||||
city: complex.city,
|
||||
planCode: complex.planCode,
|
||||
planName: complex.plan?.name ?? null,
|
||||
userCount: complex.users.length,
|
||||
courtCount,
|
||||
totalBookings,
|
||||
avgBookingsPerDay,
|
||||
bookingsByStatus,
|
||||
paymentStatus,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -40,11 +40,6 @@ export async function createComplexHandler(c: AppContext) {
|
||||
city: payload.city,
|
||||
state: payload.state,
|
||||
country: payload.country,
|
||||
setupCourts: payload.setupCourts,
|
||||
courtSportId: payload.courtSportId,
|
||||
courtStartTime: payload.courtStartTime,
|
||||
courtEndTime: payload.courtEndTime,
|
||||
courtDaysOfWeek: payload.courtDaysOfWeek,
|
||||
});
|
||||
|
||||
return c.json(complex, 201);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Prisma } from '@/generated/prisma/client';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { parsePlanRules } from '@/modules/plan/services/plan-rules.service';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
|
||||
export type CreateComplexInput = {
|
||||
@@ -12,47 +11,8 @@ export type CreateComplexInput = {
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
setupCourts?: boolean;
|
||||
courtSportId?: string;
|
||||
courtStartTime?: string;
|
||||
courtEndTime?: string;
|
||||
courtDaysOfWeek?: string[];
|
||||
};
|
||||
|
||||
type DayOfWeek = 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY';
|
||||
|
||||
function toMinutes(value: string): number {
|
||||
const [hours, minutes] = value.split(':').map((part) => Number(part));
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
function assertAvailabilityRanges(
|
||||
availability: Array<{
|
||||
dayOfWeek: DayOfWeek;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
}>
|
||||
) {
|
||||
for (const range of availability) {
|
||||
const start = toMinutes(range.startTime);
|
||||
const end = toMinutes(range.endTime);
|
||||
if (start >= end) {
|
||||
throw new Error(`El rango ${range.startTime}-${range.endTime} es inválido.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureActiveSport(sportId: string) {
|
||||
const sport = await db.sport.findFirst({
|
||||
where: { id: sportId, isActive: true },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
if (!sport) {
|
||||
throw new Error('El deporte seleccionado no existe o está inactivo.');
|
||||
}
|
||||
return sport;
|
||||
}
|
||||
|
||||
export type UpdateComplexInput = {
|
||||
complexName?: string;
|
||||
physicalAddress?: string | null;
|
||||
@@ -134,38 +94,6 @@ export async function createComplex(input: CreateComplexInput) {
|
||||
});
|
||||
}
|
||||
|
||||
if (input.setupCourts && input.courtSportId) {
|
||||
const sport = await ensureActiveSport(input.courtSportId);
|
||||
const availability = (input.courtDaysOfWeek ?? []).map((day) => ({
|
||||
dayOfWeek: day as DayOfWeek,
|
||||
startTime: input.courtStartTime ?? '08:00',
|
||||
endTime: input.courtEndTime ?? '22:00',
|
||||
}));
|
||||
|
||||
assertAvailabilityRanges(availability);
|
||||
|
||||
const court = await tx.court.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
complexId: complex.id,
|
||||
sportId: input.courtSportId,
|
||||
name: `${sport.name} 1`,
|
||||
slotDurationMinutes: 60,
|
||||
basePrice: 0,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.courtAvailability.createMany({
|
||||
data: availability.map((avail) => ({
|
||||
id: uuidv7(),
|
||||
courtId: court.id,
|
||||
dayOfWeek: avail.dayOfWeek,
|
||||
startTime: avail.startTime,
|
||||
endTime: avail.endTime,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
return complex;
|
||||
});
|
||||
}
|
||||
@@ -186,11 +114,7 @@ export async function listMyComplexes(userId: string) {
|
||||
const complexUsers = await db.complexUser.findMany({
|
||||
where: { userId },
|
||||
include: {
|
||||
complex: {
|
||||
include: {
|
||||
plan: true,
|
||||
},
|
||||
},
|
||||
complex: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
@@ -200,9 +124,6 @@ export async function listMyComplexes(userId: string) {
|
||||
return complexUsers.map((complexUser) => ({
|
||||
...complexUser.complex,
|
||||
role: complexUser.role,
|
||||
planFeatures: complexUser.complex.plan
|
||||
? parsePlanRules(complexUser.complex.plan.rules).features
|
||||
: null,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -215,11 +136,7 @@ export async function getCurrentComplex(userId: string, complexId: string) {
|
||||
},
|
||||
},
|
||||
include: {
|
||||
complex: {
|
||||
include: {
|
||||
plan: true,
|
||||
},
|
||||
},
|
||||
complex: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -228,9 +145,6 @@ export async function getCurrentComplex(userId: string, complexId: string) {
|
||||
return {
|
||||
...complexUser.complex,
|
||||
role: complexUser.role,
|
||||
planFeatures: complexUser.complex.plan
|
||||
? parsePlanRules(complexUser.complex.plan.rules).features
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -243,11 +157,7 @@ export async function selectComplex(userId: string, complexId: string) {
|
||||
},
|
||||
},
|
||||
include: {
|
||||
complex: {
|
||||
include: {
|
||||
plan: true,
|
||||
},
|
||||
},
|
||||
complex: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -256,9 +166,6 @@ export async function selectComplex(userId: string, complexId: string) {
|
||||
return {
|
||||
...complexUser.complex,
|
||||
role: complexUser.role,
|
||||
planFeatures: complexUser.complex.plan
|
||||
? parsePlanRules(complexUser.complex.plan.rules).features
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
OnboardingError,
|
||||
completeOnboarding,
|
||||
} from '@/modules/onboarding/services/onboarding.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { OnboardingCompleteInput } from '@repo/api-contract';
|
||||
|
||||
export async function completeOnboardingHandler(c: AppContext) {
|
||||
const payload = c.req.valid('json' as never) as OnboardingCompleteInput;
|
||||
|
||||
try {
|
||||
const result = await completeOnboarding(payload);
|
||||
return c.json({
|
||||
message: 'Onboarding completado correctamente.',
|
||||
...result,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof OnboardingError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return c.json({ message: error.message }, 400);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
OnboardingError,
|
||||
resendOnboardingOtp,
|
||||
} from '@/modules/onboarding/services/onboarding.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { OnboardingResendOtpInput } from '@repo/api-contract';
|
||||
|
||||
export async function resendOtpHandler(c: AppContext) {
|
||||
const payload = c.req.valid('json' as never) as OnboardingResendOtpInput;
|
||||
|
||||
try {
|
||||
const result = await resendOnboardingOtp(payload);
|
||||
return c.json(result);
|
||||
} catch (error) {
|
||||
if (error instanceof OnboardingError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return c.json({ message: error.message }, 400);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { startOnboarding } from '@/modules/onboarding/services/onboarding.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { OnboardingStartInput } from '@repo/api-contract';
|
||||
|
||||
export async function startOnboardingHandler(c: AppContext) {
|
||||
const payload = c.req.valid('json' as never) as OnboardingStartInput;
|
||||
|
||||
const result = await startOnboarding(payload);
|
||||
return c.json(result, 202);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { verifyOnboardingOtp } from '@/modules/onboarding/services/onboarding.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { OnboardingVerifyOtpInput } from '@repo/api-contract';
|
||||
|
||||
export async function verifyOtpHandler(c: AppContext) {
|
||||
const payload = c.req.valid('json' as never) as OnboardingVerifyOtpInput;
|
||||
const result = await verifyOnboardingOtp(payload);
|
||||
|
||||
return c.json(result);
|
||||
}
|
||||
35
apps/backend/src/modules/onboarding/onboarding.routes.ts
Normal file
35
apps/backend/src/modules/onboarding/onboarding.routes.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { completeOnboardingHandler } from '@/modules/onboarding/handlers/complete-onboarding.handler';
|
||||
import { resendOtpHandler } from '@/modules/onboarding/handlers/resend-otp.handler';
|
||||
import { startOnboardingHandler } from '@/modules/onboarding/handlers/start-onboarding.handler';
|
||||
import { verifyOtpHandler } from '@/modules/onboarding/handlers/verify-otp.handler';
|
||||
import type { AppEnv } from '@/types/hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import {
|
||||
onboardingCompleteSchema,
|
||||
onboardingResendOtpSchema,
|
||||
onboardingStartSchema,
|
||||
onboardingVerifyOtpSchema,
|
||||
} from '@repo/api-contract';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
export const onboardingRoutes = new Hono<AppEnv>();
|
||||
|
||||
onboardingRoutes.post('/start', zValidator('json', onboardingStartSchema), startOnboardingHandler);
|
||||
|
||||
onboardingRoutes.post(
|
||||
'/verify-otp',
|
||||
zValidator('json', onboardingVerifyOtpSchema),
|
||||
verifyOtpHandler
|
||||
);
|
||||
|
||||
onboardingRoutes.post(
|
||||
'/resend-otp',
|
||||
zValidator('json', onboardingResendOtpSchema),
|
||||
resendOtpHandler
|
||||
);
|
||||
|
||||
onboardingRoutes.post(
|
||||
'/complete',
|
||||
zValidator('json', onboardingCompleteSchema),
|
||||
completeOnboardingHandler
|
||||
);
|
||||
@@ -0,0 +1,415 @@
|
||||
import { createHash, randomInt } from 'node:crypto';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { sendMail } from '@/lib/mailer';
|
||||
import { db } from '@/lib/prisma';
|
||||
import type {
|
||||
OnboardingCompleteInput,
|
||||
OnboardingResendOtpInput,
|
||||
OnboardingStartInput,
|
||||
OnboardingVerifyOtpInput,
|
||||
} from '@repo/api-contract';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
|
||||
const OTP_LENGTH = 6;
|
||||
const OTP_TTL_MINUTES = Number(Bun.env.ONBOARDING_OTP_TTL_MINUTES ?? 10);
|
||||
const OTP_MAX_ATTEMPTS = Number(Bun.env.ONBOARDING_OTP_MAX_ATTEMPTS ?? 5);
|
||||
const OTP_RESEND_COOLDOWN_SECONDS = Number(Bun.env.ONBOARDING_OTP_RESEND_COOLDOWN_SECONDS ?? 30);
|
||||
|
||||
type VerifyOtpResult = {
|
||||
message: string;
|
||||
verified: boolean;
|
||||
requestId: string;
|
||||
email: string | null;
|
||||
expiresAt: string | null;
|
||||
remainingAttempts: number;
|
||||
cooldownSeconds: number;
|
||||
};
|
||||
|
||||
type ResendOtpResult = {
|
||||
message: string;
|
||||
requestId: string;
|
||||
email: string;
|
||||
expiresAt: string;
|
||||
cooldownSeconds: number;
|
||||
remainingAttempts: number;
|
||||
};
|
||||
|
||||
type StartOnboardingResult = {
|
||||
message: string;
|
||||
requestId: string;
|
||||
email: string;
|
||||
expiresAt: string;
|
||||
cooldownSeconds: number;
|
||||
};
|
||||
|
||||
export class OnboardingError extends Error {
|
||||
status: 400 | 404 | 409 | 429;
|
||||
|
||||
constructor(message: string, status: 400 | 404 | 409 | 429 = 400) {
|
||||
super(message);
|
||||
this.name = 'OnboardingError';
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
function nowPlusMinutes(minutes: number): Date {
|
||||
const date = new Date();
|
||||
date.setMinutes(date.getMinutes() + minutes);
|
||||
return date;
|
||||
}
|
||||
|
||||
function normalizeEmail(email: string): string {
|
||||
return email.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function createOtpCode(): string {
|
||||
return randomInt(0, 10 ** OTP_LENGTH)
|
||||
.toString()
|
||||
.padStart(OTP_LENGTH, '0');
|
||||
}
|
||||
|
||||
function hashValue(value: string): string {
|
||||
return createHash('sha256').update(value).digest('hex');
|
||||
}
|
||||
|
||||
function getRemainingAttempts(otpAttempts: number): number {
|
||||
return Math.max(0, OTP_MAX_ATTEMPTS - otpAttempts);
|
||||
}
|
||||
|
||||
function getCooldownSeconds(otpLastSentAt: Date): number {
|
||||
const elapsedMs = Date.now() - otpLastSentAt.getTime();
|
||||
const remainingMs = OTP_RESEND_COOLDOWN_SECONDS * 1000 - elapsedMs;
|
||||
return Math.max(0, Math.ceil(remainingMs / 1000));
|
||||
}
|
||||
|
||||
function slugify(value: string): string {
|
||||
return value
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-');
|
||||
}
|
||||
|
||||
async function buildUniqueComplexSlug(complexName: string): Promise<string> {
|
||||
const base = slugify(complexName);
|
||||
const fallback = base.length > 0 ? base : `complex-${uuidv7().slice(0, 8)}`;
|
||||
|
||||
let candidate = fallback;
|
||||
let index = 1;
|
||||
|
||||
while (true) {
|
||||
const existing = await db.complex.findFirst({
|
||||
where: { complexSlug: candidate },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
candidate = `${fallback}-${index}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendOtpEmail(email: string, otpCode: string) {
|
||||
await sendMail({
|
||||
to: email,
|
||||
subject: 'Codigo OTP para validar tu email',
|
||||
text: `Tu codigo OTP es ${otpCode}. Vence en ${OTP_TTL_MINUTES} minutos.`,
|
||||
html: `<p>Tu codigo OTP es <strong>${otpCode}</strong>.</p><p>Vence en ${OTP_TTL_MINUTES} minutos.</p>`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function startOnboarding(input: OnboardingStartInput): Promise<StartOnboardingResult> {
|
||||
const email = normalizeEmail(input.email);
|
||||
const otpCode = createOtpCode();
|
||||
const expiresAt = nowPlusMinutes(OTP_TTL_MINUTES);
|
||||
const now = new Date();
|
||||
|
||||
const request = await db.onboardingRequest.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
fullName: input.fullName.trim(),
|
||||
email,
|
||||
otpHash: hashValue(otpCode),
|
||||
otpExpiresAt: expiresAt,
|
||||
otpAttempts: 0,
|
||||
otpLastSentAt: now,
|
||||
otpResendCount: 0,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
otpExpiresAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
await sendOtpEmail(email, otpCode);
|
||||
|
||||
return {
|
||||
message: 'Te enviamos un codigo OTP para validar tu direccion.',
|
||||
requestId: request.id,
|
||||
email: request.email,
|
||||
expiresAt: request.otpExpiresAt.toISOString(),
|
||||
cooldownSeconds: OTP_RESEND_COOLDOWN_SECONDS,
|
||||
};
|
||||
}
|
||||
|
||||
export async function verifyOnboardingOtp(
|
||||
input: OnboardingVerifyOtpInput
|
||||
): Promise<VerifyOtpResult> {
|
||||
const request = await db.onboardingRequest.findUnique({
|
||||
where: { id: input.requestId },
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
return {
|
||||
message: 'Solicitud de onboarding invalida o expirada.',
|
||||
verified: false,
|
||||
requestId: input.requestId,
|
||||
email: null,
|
||||
expiresAt: null,
|
||||
remainingAttempts: 0,
|
||||
cooldownSeconds: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (request.completedAt) {
|
||||
return {
|
||||
message: 'Este onboarding ya fue completado.',
|
||||
verified: true,
|
||||
requestId: request.id,
|
||||
email: request.email,
|
||||
expiresAt: request.otpExpiresAt.toISOString(),
|
||||
remainingAttempts: getRemainingAttempts(request.otpAttempts),
|
||||
cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
|
||||
};
|
||||
}
|
||||
|
||||
if (request.otpExpiresAt < new Date()) {
|
||||
return {
|
||||
message: 'El codigo OTP vencio. Solicita un nuevo codigo.',
|
||||
verified: false,
|
||||
requestId: request.id,
|
||||
email: request.email,
|
||||
expiresAt: request.otpExpiresAt.toISOString(),
|
||||
remainingAttempts: getRemainingAttempts(request.otpAttempts),
|
||||
cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
|
||||
};
|
||||
}
|
||||
|
||||
if (request.otpAttempts >= OTP_MAX_ATTEMPTS) {
|
||||
return {
|
||||
message: 'Se agotaron los intentos de OTP. Solicita un nuevo codigo.',
|
||||
verified: false,
|
||||
requestId: request.id,
|
||||
email: request.email,
|
||||
expiresAt: request.otpExpiresAt.toISOString(),
|
||||
remainingAttempts: 0,
|
||||
cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
|
||||
};
|
||||
}
|
||||
|
||||
const otpMatches = hashValue(input.otp) === request.otpHash;
|
||||
|
||||
if (!otpMatches) {
|
||||
const updated = await db.onboardingRequest.update({
|
||||
where: { id: request.id },
|
||||
data: {
|
||||
otpAttempts: {
|
||||
increment: 1,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
otpAttempts: true,
|
||||
otpExpiresAt: true,
|
||||
otpLastSentAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
const remainingAttempts = getRemainingAttempts(updated.otpAttempts);
|
||||
|
||||
return {
|
||||
message:
|
||||
remainingAttempts > 0
|
||||
? 'Codigo OTP invalido.'
|
||||
: 'Se agotaron los intentos de OTP. Solicita un nuevo codigo.',
|
||||
verified: false,
|
||||
requestId: updated.id,
|
||||
email: updated.email,
|
||||
expiresAt: updated.otpExpiresAt.toISOString(),
|
||||
remainingAttempts,
|
||||
cooldownSeconds: getCooldownSeconds(updated.otpLastSentAt),
|
||||
};
|
||||
}
|
||||
|
||||
if (!request.emailVerifiedAt) {
|
||||
await db.onboardingRequest.update({
|
||||
where: { id: request.id },
|
||||
data: { emailVerifiedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Email verificado correctamente.',
|
||||
verified: true,
|
||||
requestId: request.id,
|
||||
email: request.email,
|
||||
expiresAt: request.otpExpiresAt.toISOString(),
|
||||
remainingAttempts: getRemainingAttempts(request.otpAttempts),
|
||||
cooldownSeconds: getCooldownSeconds(request.otpLastSentAt),
|
||||
};
|
||||
}
|
||||
|
||||
export async function resendOnboardingOtp(
|
||||
input: OnboardingResendOtpInput
|
||||
): Promise<ResendOtpResult> {
|
||||
const request = await db.onboardingRequest.findUnique({
|
||||
where: { id: input.requestId },
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
throw new OnboardingError('Solicitud de onboarding invalida o expirada.', 404);
|
||||
}
|
||||
|
||||
if (request.completedAt) {
|
||||
throw new OnboardingError('Este onboarding ya fue completado.', 400);
|
||||
}
|
||||
|
||||
if (request.emailVerifiedAt) {
|
||||
throw new OnboardingError('El email ya fue verificado.', 400);
|
||||
}
|
||||
|
||||
const cooldownSeconds = getCooldownSeconds(request.otpLastSentAt);
|
||||
if (cooldownSeconds > 0) {
|
||||
throw new OnboardingError(`Debes esperar ${cooldownSeconds}s antes de reenviar el OTP.`, 429);
|
||||
}
|
||||
|
||||
const otpCode = createOtpCode();
|
||||
const expiresAt = nowPlusMinutes(OTP_TTL_MINUTES);
|
||||
const now = new Date();
|
||||
|
||||
const updated = await db.onboardingRequest.update({
|
||||
where: { id: request.id },
|
||||
data: {
|
||||
otpHash: hashValue(otpCode),
|
||||
otpExpiresAt: expiresAt,
|
||||
otpAttempts: 0,
|
||||
otpLastSentAt: now,
|
||||
otpResendCount: {
|
||||
increment: 1,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
otpExpiresAt: true,
|
||||
otpAttempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
await sendOtpEmail(updated.email, otpCode);
|
||||
|
||||
return {
|
||||
message: 'Te enviamos un nuevo codigo OTP.',
|
||||
requestId: updated.id,
|
||||
email: updated.email,
|
||||
expiresAt: updated.otpExpiresAt.toISOString(),
|
||||
cooldownSeconds: OTP_RESEND_COOLDOWN_SECONDS,
|
||||
remainingAttempts: getRemainingAttempts(updated.otpAttempts),
|
||||
};
|
||||
}
|
||||
|
||||
export async function completeOnboarding(input: OnboardingCompleteInput) {
|
||||
const onboardingRequest = await db.onboardingRequest.findUnique({
|
||||
where: { id: input.onboardingRequestId },
|
||||
});
|
||||
|
||||
if (!onboardingRequest) {
|
||||
throw new OnboardingError('Solicitud de onboarding inválida o expirada.', 400);
|
||||
}
|
||||
|
||||
if (onboardingRequest.otpExpiresAt < new Date()) {
|
||||
throw new OnboardingError('La sesión de onboarding expiró. Solicita un nuevo OTP.', 400);
|
||||
}
|
||||
|
||||
if (!onboardingRequest.emailVerifiedAt) {
|
||||
throw new OnboardingError('Debes verificar el email antes de continuar.', 400);
|
||||
}
|
||||
|
||||
if (onboardingRequest.completedAt) {
|
||||
throw new OnboardingError('Este onboarding ya fue completado.', 400);
|
||||
}
|
||||
|
||||
const plan = await db.plan.findUnique({
|
||||
where: { code: input.planCode },
|
||||
select: { code: true },
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
throw new OnboardingError('El plan seleccionado no existe.', 400);
|
||||
}
|
||||
|
||||
const { user } = await auth.api.signUpEmail({
|
||||
asResponse: false,
|
||||
body: {
|
||||
email: onboardingRequest.email,
|
||||
password: input.password,
|
||||
name: onboardingRequest.fullName,
|
||||
},
|
||||
});
|
||||
|
||||
const complexSlug = await buildUniqueComplexSlug(input.complexName);
|
||||
|
||||
const result = await db.$transaction(async (tx) => {
|
||||
await tx.user.update({
|
||||
where: { id: user.id },
|
||||
data: { emailVerified: true },
|
||||
});
|
||||
|
||||
const complex = await tx.complex.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
complexName: input.complexName.trim(),
|
||||
physicalAddress: input.physicalAddress.trim(),
|
||||
city: input.city?.trim() || null,
|
||||
state: input.state?.trim() || null,
|
||||
country: input.country?.trim() || null,
|
||||
complexSlug,
|
||||
adminEmail: onboardingRequest.email,
|
||||
planCode: input.planCode,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.complexUser.create({
|
||||
data: {
|
||||
complexId: complex.id,
|
||||
userId: user.id,
|
||||
role: 'ADMIN',
|
||||
},
|
||||
});
|
||||
|
||||
await tx.onboardingRequest.update({
|
||||
where: { id: onboardingRequest.id },
|
||||
data: {
|
||||
completedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
complexId: complex.id,
|
||||
complexSlug: complex.complexSlug,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createHash, randomInt } from 'node:crypto';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { type IpGeoInfo, fetchGeoInfo } from '@/lib/geoip';
|
||||
import { sendMail } from '@/lib/mailer';
|
||||
import { db } from '@/lib/prisma';
|
||||
import type {
|
||||
@@ -83,6 +82,13 @@ type UserAgentInfo = {
|
||||
device: string;
|
||||
};
|
||||
|
||||
type IpGeoInfo = {
|
||||
ip: string;
|
||||
city: string;
|
||||
country: string;
|
||||
countryCode: string;
|
||||
};
|
||||
|
||||
function parseUserAgent(ua: string | undefined): UserAgentInfo {
|
||||
if (!ua) {
|
||||
return { browser: 'Desconocido', os: 'Desconocido', device: 'Desconocido' };
|
||||
@@ -112,6 +118,31 @@ function parseUserAgent(ua: string | undefined): UserAgentInfo {
|
||||
return { browser, os, device };
|
||||
}
|
||||
|
||||
async function fetchGeoInfo(ip: string): Promise<IpGeoInfo | null> {
|
||||
if (ip === '127.0.0.1' || ip === '::1' || ip.startsWith('192.168.') || ip.startsWith('10.')) {
|
||||
return { ip, city: 'Red local', country: 'Red local', countryCode: 'LOCAL' };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://ip-api.com/json/${ip}?fields=status,country,countryCode,city,query`
|
||||
);
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'success') return null;
|
||||
|
||||
return {
|
||||
ip: data.query,
|
||||
city: data.city || 'Desconocida',
|
||||
country: data.country || 'Desconocido',
|
||||
countryCode: data.countryCode || '',
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendResetOtpEmail(email: string, otpCode: string) {
|
||||
await sendMail({
|
||||
to: email,
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
import { fetchGeoInfo } from '@/lib/geoip';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { resolvePlanPrice } from '@/modules/plan/services/plan-pricing.service';
|
||||
import { parsePlanRules } from '@/modules/plan/services/plan-rules.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
export async function listPlansHandler(c: AppContext) {
|
||||
const plans = await db.plan.findMany({
|
||||
select: {
|
||||
code: true,
|
||||
name: true,
|
||||
price: true,
|
||||
},
|
||||
orderBy: {
|
||||
price: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
const countryParam = c.req.query('country');
|
||||
let countryCode: string | undefined;
|
||||
|
||||
if (countryParam) {
|
||||
countryCode = countryParam;
|
||||
} else {
|
||||
const ip = c.req.header('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1';
|
||||
const geo = await fetchGeoInfo(ip);
|
||||
countryCode = geo?.countryCode || undefined;
|
||||
}
|
||||
|
||||
return c.json(
|
||||
plans.map((plan) => {
|
||||
const rules = parsePlanRules(plan.rules);
|
||||
const resolved = resolvePlanPrice(Number(plan.price), rules, countryCode);
|
||||
return {
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: resolved.amount,
|
||||
currency: resolved.currency,
|
||||
features: rules.features,
|
||||
limits: rules.limits,
|
||||
};
|
||||
})
|
||||
plans.map((plan) => ({
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: Number(plan.price),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { PlanRules } from '@repo/api-contract';
|
||||
|
||||
export type PlanPrice = {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
|
||||
export function resolvePlanPrice(
|
||||
defaultPrice: number,
|
||||
rules: PlanRules,
|
||||
countryCode?: string
|
||||
): PlanPrice {
|
||||
if (!countryCode) {
|
||||
return { amount: defaultPrice, currency: 'USD' };
|
||||
}
|
||||
|
||||
if (rules.version === 'v2' && rules.pricing?.overrides) {
|
||||
const override = rules.pricing.overrides[countryCode];
|
||||
if (override) {
|
||||
return { amount: override.amount, currency: override.currency };
|
||||
}
|
||||
}
|
||||
|
||||
return { amount: defaultPrice, currency: 'USD' };
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { sseManager } from '@/lib/sse';
|
||||
import {
|
||||
PublicBookingServiceError,
|
||||
cancelPublicBooking,
|
||||
} from '@/modules/public-booking/services/public-booking.service';
|
||||
import { sendBookingCancelled } from '@/services/booking-email.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { CancelPublicBookingInput } from '@repo/api-contract';
|
||||
|
||||
type ComplexSlugParams = { complexSlug: string };
|
||||
|
||||
export async function cancelPublicBookingHandler(c: AppContext) {
|
||||
const { complexSlug } = c.req.valid('param' as never) as ComplexSlugParams;
|
||||
const payload = c.req.valid('json' as never) as CancelPublicBookingInput;
|
||||
|
||||
try {
|
||||
const booking = await cancelPublicBooking(complexSlug, payload);
|
||||
|
||||
const channel = `complex-${booking.complexId}`;
|
||||
sseManager.emit(
|
||||
channel,
|
||||
JSON.stringify({
|
||||
type: 'booking_cancelled',
|
||||
booking: {
|
||||
bookingCode: booking.bookingCode,
|
||||
courtId: booking.courtId,
|
||||
date: booking.date,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
void sendBookingCancelled({
|
||||
bookingCode: booking.bookingCode,
|
||||
complexSlug: booking.complexSlug,
|
||||
complexName: booking.complexName,
|
||||
date: booking.date,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
courtName: booking.courtName,
|
||||
sportName: booking.sport.name,
|
||||
customerName: booking.customerName,
|
||||
customerEmail: booking.customerEmail,
|
||||
});
|
||||
|
||||
return c.json(booking);
|
||||
} catch (error) {
|
||||
if (error instanceof PublicBookingServiceError) {
|
||||
return c.json({ message: error.message }, error.status);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
PublicBookingServiceError,
|
||||
createPublicBooking,
|
||||
} from '@/modules/public-booking/services/public-booking.service';
|
||||
import { sendBookingConfirmation } from '@/services/booking-email.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { CreatePublicBookingInput } from '@repo/api-contract';
|
||||
|
||||
@@ -31,20 +30,6 @@ export async function createPublicBookingHandler(c: AppContext) {
|
||||
})
|
||||
);
|
||||
|
||||
void sendBookingConfirmation({
|
||||
bookingCode: booking.bookingCode,
|
||||
complexSlug: booking.complexSlug,
|
||||
complexName: booking.complexName,
|
||||
date: booking.date,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
courtName: booking.courtName,
|
||||
sportName: booking.sport.name,
|
||||
customerName: booking.customerName,
|
||||
customerEmail: booking.customerEmail,
|
||||
price: booking.price,
|
||||
});
|
||||
|
||||
return c.json(booking, 201);
|
||||
} catch (error) {
|
||||
if (error instanceof PublicBookingServiceError) {
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { cancelPublicBookingHandler } from '@/modules/public-booking/handlers/cancel-public-booking.handler';
|
||||
import { createPublicBookingHandler } from '@/modules/public-booking/handlers/create-public-booking.handler';
|
||||
import { getPublicBookingConfirmationHandler } from '@/modules/public-booking/handlers/get-public-booking-confirmation.handler';
|
||||
import { listPublicAvailabilityHandler } from '@/modules/public-booking/handlers/list-public-availability.handler';
|
||||
import type { AppEnv } from '@/types/hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import {
|
||||
cancelPublicBookingSchema,
|
||||
createPublicBookingSchema,
|
||||
publicAvailabilityQuerySchema,
|
||||
} from '@repo/api-contract';
|
||||
import { createPublicBookingSchema, publicAvailabilityQuerySchema } from '@repo/api-contract';
|
||||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -38,10 +33,3 @@ publicBookingRoutes.get(
|
||||
zValidator('param', confirmationParamsSchema),
|
||||
getPublicBookingConfirmationHandler
|
||||
);
|
||||
|
||||
publicBookingRoutes.post(
|
||||
'/complex/:complexSlug/cancel',
|
||||
zValidator('param', complexSlugParamsSchema),
|
||||
zValidator('json', cancelPublicBookingSchema),
|
||||
cancelPublicBookingHandler
|
||||
);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { randomInt } from 'node:crypto';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { isSlotInPast } from '@/lib/slot-validator';
|
||||
import { evaluatePlanUsage, parsePlanRules } from '@/modules/plan/services/plan-rules.service';
|
||||
import type {
|
||||
CancelPublicBookingInput,
|
||||
CreatePublicBookingInput,
|
||||
DayOfWeek,
|
||||
PublicAvailabilityQuery,
|
||||
@@ -15,16 +13,6 @@ type Slot = {
|
||||
endTime: string;
|
||||
};
|
||||
|
||||
type PriceableCourt = {
|
||||
basePrice: unknown;
|
||||
priceRules: Array<{
|
||||
dayOfWeek: DayOfWeek | null;
|
||||
startTime: string | null;
|
||||
endTime: string | null;
|
||||
price: unknown;
|
||||
}>;
|
||||
};
|
||||
|
||||
type ComplexWithPublicBookingData = Awaited<ReturnType<typeof getComplexWithBookingData>>;
|
||||
|
||||
const DAY_OF_WEEK_BY_INDEX: DayOfWeek[] = [
|
||||
@@ -44,6 +32,7 @@ export class PublicBookingServiceError extends Error {
|
||||
|
||||
constructor(message: string, status: 400 | 403 | 404 | 409) {
|
||||
super(message);
|
||||
this.name = 'PublicBookingServiceError';
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -241,33 +230,6 @@ async function getComplexWithBookingData(complexSlug: string) {
|
||||
return complex;
|
||||
}
|
||||
|
||||
function resolveSlotPrice(court: PriceableCourt, dayOfWeek: DayOfWeek, slot: Slot): number {
|
||||
const slotStart = toMinutes(slot.startTime);
|
||||
const slotEnd = toMinutes(slot.endTime);
|
||||
const matchingRules = court.priceRules
|
||||
.filter((rule) => {
|
||||
if (rule.dayOfWeek && rule.dayOfWeek !== dayOfWeek) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rule.startTime || !rule.endTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return slotStart >= toMinutes(rule.startTime) && slotEnd <= toMinutes(rule.endTime);
|
||||
})
|
||||
.sort((first, second) => {
|
||||
const firstSpecificity =
|
||||
(first.dayOfWeek ? 2 : 0) + (first.startTime && first.endTime ? 1 : 0);
|
||||
const secondSpecificity =
|
||||
(second.dayOfWeek ? 2 : 0) + (second.startTime && second.endTime ? 1 : 0);
|
||||
|
||||
return secondSpecificity - firstSpecificity;
|
||||
});
|
||||
|
||||
return Number(matchingRules[0]?.price ?? court.basePrice);
|
||||
}
|
||||
|
||||
function mapBookingResponse(input: {
|
||||
bookingId: string;
|
||||
bookingCode: string;
|
||||
@@ -275,11 +237,10 @@ function mapBookingResponse(input: {
|
||||
createdAt: Date;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
price: number;
|
||||
customerName: string;
|
||||
customerPhone: string;
|
||||
customerEmail: string;
|
||||
price: number;
|
||||
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NOSHOW';
|
||||
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED';
|
||||
court: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -305,11 +266,10 @@ function mapBookingResponse(input: {
|
||||
date: formatIsoDate(input.bookingDate),
|
||||
startTime: input.startTime,
|
||||
endTime: input.endTime,
|
||||
price: input.price,
|
||||
customerName: input.customerName,
|
||||
customerPhone: input.customerPhone,
|
||||
customerEmail: input.customerEmail,
|
||||
status: input.status,
|
||||
price: input.price,
|
||||
createdAt: input.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
@@ -330,17 +290,12 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
|
||||
bookingDate: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
customerEmail: true,
|
||||
price: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
court: {
|
||||
select: {
|
||||
name: true,
|
||||
basePrice: true,
|
||||
priceRules: {
|
||||
where: { isActive: true },
|
||||
orderBy: [{ dayOfWeek: 'asc' }, { startTime: 'asc' }],
|
||||
},
|
||||
sport: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -351,7 +306,6 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
|
||||
complex: {
|
||||
select: {
|
||||
complexName: true,
|
||||
physicalAddress: true,
|
||||
complexSlug: true,
|
||||
},
|
||||
},
|
||||
@@ -364,22 +318,13 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
|
||||
throw new PublicBookingServiceError('Reserva no encontrada.', 404);
|
||||
}
|
||||
|
||||
const date = formatIsoDate(booking.bookingDate);
|
||||
const { dayOfWeek } = parseIsoDate(date);
|
||||
const price = resolveSlotPrice(booking.court, dayOfWeek, {
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
});
|
||||
|
||||
return {
|
||||
bookingCode: booking.bookingCode,
|
||||
complexName: booking.court.complex.complexName,
|
||||
complexAddress: booking.court.complex.physicalAddress ?? undefined,
|
||||
complexSlug: booking.court.complex.complexSlug,
|
||||
date,
|
||||
date: formatIsoDate(booking.bookingDate),
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
price,
|
||||
courtName: booking.court.name,
|
||||
sport: {
|
||||
id: booking.court.sport.id,
|
||||
@@ -387,39 +332,14 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
|
||||
slug: booking.court.sport.slug,
|
||||
},
|
||||
status: booking.status,
|
||||
customerEmail: booking.customerEmail,
|
||||
price: Number(booking.price),
|
||||
createdAt: booking.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async function hasVerifiedAdmin(complexId: string): Promise<boolean> {
|
||||
const verifiedAdmin = await db.complexUser.findFirst({
|
||||
where: {
|
||||
complexId,
|
||||
role: 'ADMIN',
|
||||
user: { emailVerified: true },
|
||||
},
|
||||
});
|
||||
return verifiedAdmin !== null;
|
||||
}
|
||||
|
||||
export async function listPublicAvailability(complexSlug: string, query: PublicAvailabilityQuery) {
|
||||
const { bookingDate, dayOfWeek } = parseIsoDate(query.date);
|
||||
const complex = await getComplexWithBookingData(complexSlug);
|
||||
|
||||
if (!(await hasVerifiedAdmin(complex.id))) {
|
||||
return {
|
||||
complexId: complex.id,
|
||||
complexName: complex.complexName,
|
||||
complexAddress: complex.physicalAddress ?? null,
|
||||
complexSlug: complex.complexSlug,
|
||||
date: query.date,
|
||||
sportSelectionRequired: false,
|
||||
sports: [],
|
||||
courts: [],
|
||||
};
|
||||
}
|
||||
|
||||
const sports = resolveSports(complex);
|
||||
const sportSelectionRequired = sports.length > 1;
|
||||
|
||||
@@ -485,6 +405,50 @@ export async function listPublicAvailability(complexSlug: string, query: PublicA
|
||||
(slot) => !occupiedSlots.some((occupied) => hasOverlap(slot, occupied))
|
||||
);
|
||||
|
||||
const basePrice = Number(court.basePrice);
|
||||
const priceRuleMap = new Map<string, number>();
|
||||
|
||||
for (const rule of court.priceRules) {
|
||||
const ruleDay = rule.dayOfWeek;
|
||||
const ruleStart = rule.startTime;
|
||||
const ruleEnd = rule.endTime;
|
||||
const price = Number(rule.price);
|
||||
|
||||
if (ruleStart && ruleEnd && ruleDay) {
|
||||
const startMin = toMinutes(ruleStart);
|
||||
const endMin = toMinutes(ruleEnd);
|
||||
for (let mins = startMin; mins < endMin; mins += 30) {
|
||||
priceRuleMap.set(`${ruleDay}-${minutesToTime(mins)}`, price);
|
||||
}
|
||||
} else if (ruleDay && !ruleStart && !ruleEnd) {
|
||||
priceRuleMap.set(`day-${ruleDay}`, price);
|
||||
}
|
||||
}
|
||||
|
||||
const calcPrice = (startTime: string) => {
|
||||
if (!basePrice || basePrice <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const timeKey = `${dayOfWeek}-${startTime}`;
|
||||
const dayKey = `day-${dayOfWeek}`;
|
||||
return priceRuleMap.get(timeKey) ?? priceRuleMap.get(dayKey) ?? basePrice;
|
||||
};
|
||||
|
||||
const price =
|
||||
availableSlots.length > 0
|
||||
? (() => {
|
||||
const prices = availableSlots.map((slot) => calcPrice(slot.startTime));
|
||||
const validPrices = prices.filter((p) => p > 0);
|
||||
if (validPrices.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
min: Math.min(...validPrices),
|
||||
max: Math.max(...validPrices),
|
||||
};
|
||||
})()
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
courtId: court.id,
|
||||
courtName: court.name,
|
||||
@@ -495,10 +459,8 @@ export async function listPublicAvailability(complexSlug: string, query: PublicA
|
||||
},
|
||||
slotDurationMinutes: court.slotDurationMinutes,
|
||||
availabilityDay: dayOfWeek,
|
||||
availableSlots: availableSlots.map((slot) => ({
|
||||
...slot,
|
||||
price: resolveSlotPrice(court, dayOfWeek, slot),
|
||||
})),
|
||||
availableSlots,
|
||||
price,
|
||||
};
|
||||
})
|
||||
.filter((court) => court.availableSlots.length > 0);
|
||||
@@ -518,14 +480,6 @@ export async function listPublicAvailability(complexSlug: string, query: PublicA
|
||||
export async function createPublicBooking(complexSlug: string, input: CreatePublicBookingInput) {
|
||||
const { bookingDate, dayOfWeek } = parseIsoDate(input.date);
|
||||
const complex = await getComplexWithBookingData(complexSlug);
|
||||
|
||||
if (!(await hasVerifiedAdmin(complex.id))) {
|
||||
throw new PublicBookingServiceError(
|
||||
'El complejo no puede recibir reservas hasta que un administrador verifique su email.',
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
const sports = resolveSports(complex);
|
||||
const { sportSelectionRequired } = validateSportSelection(sports, input.sportId);
|
||||
|
||||
@@ -575,10 +529,60 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
|
||||
);
|
||||
}
|
||||
|
||||
if (isSlotInPast(bookingDate, input.startTime, selectedCourt.slotDurationMinutes)) {
|
||||
throw new PublicBookingServiceError('No se pueden crear reservas en el pasado.', 400);
|
||||
function calcBookingPrice(
|
||||
court: {
|
||||
basePrice: number | null;
|
||||
priceRules: Array<{
|
||||
dayOfWeek: string | null;
|
||||
startTime: string | null;
|
||||
endTime: string | null;
|
||||
price: number;
|
||||
}>;
|
||||
},
|
||||
dayOfWeek: string,
|
||||
startTime: string
|
||||
): number {
|
||||
if (!court.basePrice) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const slotMinutes = toMinutes(startTime);
|
||||
|
||||
const exactMatch = court.priceRules.find((rule) => {
|
||||
if (!rule.dayOfWeek || rule.dayOfWeek !== dayOfWeek) {
|
||||
return false;
|
||||
}
|
||||
if (!rule.startTime || !rule.endTime) {
|
||||
return false;
|
||||
}
|
||||
const ruleStart = toMinutes(rule.startTime);
|
||||
const ruleEnd = toMinutes(rule.endTime);
|
||||
return slotMinutes >= ruleStart && slotMinutes < ruleEnd;
|
||||
});
|
||||
|
||||
if (exactMatch) {
|
||||
return Number(exactMatch.price);
|
||||
}
|
||||
|
||||
const dayMatch = court.priceRules.find((rule) => {
|
||||
if (!rule.dayOfWeek || rule.dayOfWeek !== dayOfWeek) {
|
||||
return false;
|
||||
}
|
||||
if (rule.startTime || rule.endTime) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (dayMatch) {
|
||||
return Number(dayMatch.price);
|
||||
}
|
||||
|
||||
return Number(court.basePrice);
|
||||
}
|
||||
|
||||
const bookingPrice = calcBookingPrice(selectedCourt, dayOfWeek, selectedSlot.startTime);
|
||||
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
try {
|
||||
const booking = await db.$transaction(async (tx) => {
|
||||
@@ -634,9 +638,9 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
|
||||
bookingDate,
|
||||
startTime: selectedSlot.startTime,
|
||||
endTime: selectedSlot.endTime,
|
||||
price: bookingPrice,
|
||||
customerName: input.customerName.trim(),
|
||||
customerPhone: input.customerPhone.trim(),
|
||||
customerEmail: input.customerEmail.trim(),
|
||||
status: 'CONFIRMED',
|
||||
},
|
||||
select: {
|
||||
@@ -646,16 +650,14 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
|
||||
createdAt: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
price: true,
|
||||
customerName: true,
|
||||
customerPhone: true,
|
||||
customerEmail: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const price = resolveSlotPrice(selectedCourt, dayOfWeek, selectedSlot);
|
||||
|
||||
return mapBookingResponse({
|
||||
bookingId: booking.id,
|
||||
bookingCode: booking.bookingCode,
|
||||
@@ -663,10 +665,9 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
|
||||
createdAt: booking.createdAt,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
price: Number(booking.price),
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
price,
|
||||
status: booking.status,
|
||||
court: {
|
||||
id: selectedCourt.id,
|
||||
@@ -717,108 +718,3 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
export async function cancelPublicBooking(complexSlug: string, input: CancelPublicBookingInput) {
|
||||
const normalizedBookingCode = input.bookingCode.toUpperCase();
|
||||
const booking = await db.courtBooking.findFirst({
|
||||
where: {
|
||||
bookingCode: normalizedBookingCode,
|
||||
court: {
|
||||
complex: {
|
||||
complexSlug,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
court: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
basePrice: true,
|
||||
priceRules: {
|
||||
where: { isActive: true },
|
||||
orderBy: [{ dayOfWeek: 'asc' }, { startTime: 'asc' }],
|
||||
},
|
||||
sport: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
complexName: true,
|
||||
complexSlug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!booking) {
|
||||
throw new PublicBookingServiceError('Reserva no encontrada.', 404);
|
||||
}
|
||||
|
||||
if (booking.status !== 'CONFIRMED') {
|
||||
throw new PublicBookingServiceError(
|
||||
'Solo se pueden cancelar reservas en estado confirmada.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
if (booking.customerPhone !== input.customerPhone.trim()) {
|
||||
throw new PublicBookingServiceError('Los datos ingresados no coinciden con la reserva.', 400);
|
||||
}
|
||||
|
||||
const date = formatIsoDate(booking.bookingDate);
|
||||
const { dayOfWeek } = parseIsoDate(date);
|
||||
const price = resolveSlotPrice(booking.court, dayOfWeek, {
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
});
|
||||
|
||||
await db.courtBookingLog.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
bookingCode: booking.bookingCode,
|
||||
courtId: booking.courtId,
|
||||
bookingDate: booking.bookingDate,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
previousStatus: booking.status,
|
||||
newStatus: 'CANCELLED',
|
||||
},
|
||||
});
|
||||
|
||||
await db.courtBooking.delete({
|
||||
where: { id: booking.id },
|
||||
});
|
||||
|
||||
return mapBookingResponse({
|
||||
bookingId: booking.id,
|
||||
bookingCode: booking.bookingCode,
|
||||
bookingDate: booking.bookingDate,
|
||||
createdAt: booking.createdAt,
|
||||
startTime: booking.startTime,
|
||||
endTime: booking.endTime,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
customerEmail: booking.customerEmail,
|
||||
price,
|
||||
status: 'CANCELLED',
|
||||
court: {
|
||||
id: booking.court.id,
|
||||
name: booking.court.name,
|
||||
complexId: booking.court.complex.id,
|
||||
complexName: booking.court.complex.complexName,
|
||||
complexSlug: booking.court.complex.complexSlug,
|
||||
sport: booking.court.sport,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { Prisma } from '@/generated/prisma/client';
|
||||
import { handleResult } from '@/lib/http/handle-result';
|
||||
import { createSport } from '@/modules/sport/services/sport.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
import type { CreateSportInput } from '@repo/api-contract';
|
||||
|
||||
export async function createSportHandler(c: AppContext) {
|
||||
const payload = c.req.valid('json' as never) as CreateSportInput;
|
||||
return handleResult(c, await createSport(payload), 201);
|
||||
|
||||
try {
|
||||
const sport = await createSport(payload);
|
||||
return c.json(sport, 201);
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return c.json({ message: 'No se pudo crear el deporte.' }, 409);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { handleResult } from '@/lib/http/handle-result';
|
||||
import { listSports } from '@/modules/sport/services/sport.service';
|
||||
import type { AppContext } from '@/types/hono';
|
||||
|
||||
export async function listSportsHandler(c: AppContext) {
|
||||
const sports = await listSports();
|
||||
return handleResult(c, sports);
|
||||
return c.json(sports);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Prisma, Sport } from '@/generated/prisma/client';
|
||||
import { Errors } from '@/lib/errors';
|
||||
import { Prisma } from '@/generated/prisma/client';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { Result, err, ok } from '@/lib/result';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
|
||||
export type CreateSportInput = {
|
||||
@@ -53,25 +51,16 @@ async function buildUniqueSlug(source: string, excludeSportId?: string): Promise
|
||||
}
|
||||
}
|
||||
|
||||
export async function listSports(): Promise<Result<Sport[]>> {
|
||||
const sports = await db.sport.findMany({
|
||||
export async function listSports() {
|
||||
return db.sport.findMany({
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
return ok(sports);
|
||||
}
|
||||
|
||||
export async function createSport(input: CreateSportInput) {
|
||||
const slug = await buildUniqueSlug(input.name);
|
||||
|
||||
const existing = await db.sport.count({
|
||||
where: { slug },
|
||||
});
|
||||
|
||||
if (existing > 0) {
|
||||
return err(Errors.conflict('Ya existe un deporte con ese nombre.'));
|
||||
}
|
||||
|
||||
const created = await db.sport.create({
|
||||
return db.sport.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
name: input.name.trim(),
|
||||
@@ -79,8 +68,6 @@ export async function createSport(input: CreateSportInput) {
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
return ok(created);
|
||||
}
|
||||
|
||||
export async function getSportById(id: string) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { validate } from '@/lib/http/validate';
|
||||
import { requireAuth } from '@/middlewares/require-auth.middleware';
|
||||
import { requireSuperAdmin } from '@/middlewares/require-super-admin.middleware';
|
||||
import { createSportHandler } from '@/modules/sport/handlers/create-sport.handler';
|
||||
@@ -9,17 +8,18 @@ import { zValidator } from '@hono/zod-validator';
|
||||
import { createSportSchema, updateSportSchema } from '@repo/api-contract';
|
||||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const sportRoutes = new Hono<AppEnv>();
|
||||
const sportIdParamsSchema = z.object({ id: z.uuid() });
|
||||
|
||||
sportRoutes.use('*', requireAuth);
|
||||
|
||||
sportRoutes.get('/', listSportsHandler);
|
||||
sportRoutes.post('/', requireSuperAdmin, validate.json(createSportSchema), createSportHandler);
|
||||
sportRoutes.post('/', requireSuperAdmin, zValidator('json', createSportSchema), createSportHandler);
|
||||
sportRoutes.patch(
|
||||
'/:id',
|
||||
requireSuperAdmin,
|
||||
validate.param(sportIdParamsSchema),
|
||||
validate.json(updateSportSchema),
|
||||
zValidator('param', sportIdParamsSchema),
|
||||
zValidator('json', updateSportSchema),
|
||||
updateSportHandler
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import path from 'node:path';
|
||||
import { adminBookingRoutes } from '@/modules/admin-booking/admin-booking.routes';
|
||||
import { adminRoutes } from '@/modules/admin/admin.routes';
|
||||
import { authRoutes } from '@/modules/auth/auth.routes';
|
||||
import { complexRoutes } from '@/modules/complex/complex.routes';
|
||||
import { courtRoutes } from '@/modules/court/court.routes';
|
||||
import { onboardingRoutes } from '@/modules/onboarding/onboarding.routes';
|
||||
import { passwordResetRoutes } from '@/modules/password-reset/password-reset.routes';
|
||||
import { planRoutes } from '@/modules/plan/plan.routes';
|
||||
import { publicBookingRoutes } from '@/modules/public-booking/public-booking.routes';
|
||||
@@ -18,9 +18,9 @@ import { healthCheckRoutes } from './modules/health-check/health-check.routes';
|
||||
export function registerRoutes(app: Hono<AppEnv>) {
|
||||
app
|
||||
.route('', authRoutes)
|
||||
.route('/api/admin', adminRoutes)
|
||||
.route('/api/user', userRoutes)
|
||||
.route('/api/plans', planRoutes)
|
||||
.route('/api/onboarding', onboardingRoutes)
|
||||
.route('/api/password-reset', passwordResetRoutes)
|
||||
.route('/api/complexes', complexRoutes)
|
||||
.route('/api/sports', sportRoutes)
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import {
|
||||
bookingCancelledHtml,
|
||||
bookingConfirmationHtml,
|
||||
bookingNoShowHtml,
|
||||
} from '@/emails/booking-confirmation';
|
||||
import { sendMail } from '@/lib/mailer';
|
||||
|
||||
type BookingEmailInput = {
|
||||
bookingCode: string;
|
||||
complexSlug?: string;
|
||||
complexName: string;
|
||||
date: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
courtName: string;
|
||||
sportName: string;
|
||||
customerName: string;
|
||||
customerEmail: string;
|
||||
price?: number;
|
||||
};
|
||||
|
||||
export async function sendBookingConfirmation(data: BookingEmailInput) {
|
||||
if (!data.customerEmail) return;
|
||||
|
||||
const html = bookingConfirmationHtml({
|
||||
bookingCode: data.bookingCode,
|
||||
complexSlug: data.complexSlug,
|
||||
complexName: data.complexName,
|
||||
date: data.date,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
courtName: data.courtName,
|
||||
sportName: data.sportName,
|
||||
customerName: data.customerName,
|
||||
price: data.price,
|
||||
});
|
||||
|
||||
await sendMail({
|
||||
to: data.customerEmail,
|
||||
subject: `Reserva confirmada en ${data.complexName} | Playzer`,
|
||||
html,
|
||||
text: `Reserva confirmada en ${data.complexName}. Codigo: ${data.bookingCode}. Cancha: ${data.courtName}. Fecha: ${data.date}. Horario: ${data.startTime} - ${data.endTime}.`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendBookingCancelled(data: BookingEmailInput) {
|
||||
if (!data.customerEmail) return;
|
||||
|
||||
const html = bookingCancelledHtml({
|
||||
bookingCode: data.bookingCode,
|
||||
complexName: data.complexName,
|
||||
date: data.date,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
courtName: data.courtName,
|
||||
sportName: data.sportName,
|
||||
customerName: data.customerName,
|
||||
});
|
||||
|
||||
await sendMail({
|
||||
to: data.customerEmail,
|
||||
subject: `Reserva cancelada en ${data.complexName} | Playzer`,
|
||||
html,
|
||||
text: `Reserva cancelada en ${data.complexName}. Codigo: ${data.bookingCode}. Fecha: ${data.date}. Horario: ${data.startTime} - ${data.endTime}.`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendBookingNoShow(data: BookingEmailInput) {
|
||||
if (!data.customerEmail) return;
|
||||
|
||||
const html = bookingNoShowHtml({
|
||||
bookingCode: data.bookingCode,
|
||||
complexName: data.complexName,
|
||||
date: data.date,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
courtName: data.courtName,
|
||||
sportName: data.sportName,
|
||||
customerName: data.customerName,
|
||||
});
|
||||
|
||||
await sendMail({
|
||||
to: data.customerEmail,
|
||||
subject: `Reserva no concretada en ${data.complexName} | Playzer`,
|
||||
html,
|
||||
text: `Reserva no concretada en ${data.complexName}. Codigo: ${data.bookingCode}. Fecha: ${data.date}. Horario: ${data.startTime} - ${data.endTime}.`,
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { beforeEach, expect, mock, test } from 'bun:test';
|
||||
|
||||
import { createInviteComplexUserHandler } from '@/modules/complex/handlers/invite-complex-user.handler';
|
||||
import type { InviteComplexUserResponse } from '@repo/api-contract';
|
||||
|
||||
const inviteComplexUserMock = mock(async () => undefined as unknown as InviteComplexUserResponse);
|
||||
@@ -14,10 +15,6 @@ class MockComplexMembersError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const { createInviteComplexUserHandler } = await import(
|
||||
'@/modules/complex/handlers/invite-complex-user.handler'
|
||||
);
|
||||
|
||||
const inviteComplexUserHandler = createInviteComplexUserHandler({
|
||||
inviteComplexUser: inviteComplexUserMock,
|
||||
ComplexMembersError: MockComplexMembersError,
|
||||
@@ -62,6 +59,7 @@ function createContext(input: {
|
||||
|
||||
beforeEach(() => {
|
||||
inviteComplexUserMock.mockReset();
|
||||
mock.clearAllMocks();
|
||||
});
|
||||
|
||||
test('returns 201 with the service result', async () => {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { beforeEach, expect, test } from 'bun:test';
|
||||
import { beforeEach, expect, mock, test } from 'bun:test';
|
||||
|
||||
import {
|
||||
ComplexMembersError,
|
||||
inviteComplexUser,
|
||||
} from '@/modules/complex/services/complex-members.service';
|
||||
import { prismaMock, sendMailMock, transactionMock } from '../support/prisma.mock';
|
||||
|
||||
const { ComplexMembersError, inviteComplexUser } = await import(
|
||||
'@/modules/complex/services/complex-members.service'
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
prismaMock._reset();
|
||||
sendMailMock.mockClear();
|
||||
transactionMock.mockClear();
|
||||
mock.clearAllMocks();
|
||||
});
|
||||
|
||||
test('creates a pending invitation and sends the email', async () => {
|
||||
@@ -22,10 +21,10 @@ test('creates a pending invitation and sends the email', async () => {
|
||||
complexId: 'complex-1',
|
||||
email: 'new.member@example.com',
|
||||
tokenHash: 'token-hash',
|
||||
expiresAt: new Date('2027-01-01T00:00:00.000Z'),
|
||||
expiresAt: new Date('2026-04-29T00:00:00.000Z'),
|
||||
acceptedAt: null,
|
||||
revokedAt: null,
|
||||
createdAt: new Date('2026-12-01T00:00:00.000Z'),
|
||||
createdAt: new Date('2026-04-22T00:00:00.000Z'),
|
||||
} as never);
|
||||
|
||||
const result = await inviteComplexUser('admin-1', 'complex-1', {
|
||||
@@ -80,7 +79,7 @@ test('rejects when the invite target already belongs to the complex', async () =
|
||||
}
|
||||
|
||||
expect(caught).toBeInstanceOf(ComplexMembersError);
|
||||
expect((caught as InstanceType<typeof ComplexMembersError>).status).toBe(409);
|
||||
expect((caught as ComplexMembersError).status).toBe(409);
|
||||
expect(transactionMock).not.toHaveBeenCalled();
|
||||
expect(sendMailMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import { expect, test } from 'bun:test';
|
||||
|
||||
import type { PlanRules } from '@repo/api-contract';
|
||||
|
||||
const DEFAULT_PRICE = 29.99;
|
||||
|
||||
function makeV1Rules(): PlanRules {
|
||||
return {
|
||||
version: 'v1',
|
||||
limits: { maxCourts: 3, maxBookingsPerDay: 50 },
|
||||
policies: { allowOverbooking: false },
|
||||
features: {
|
||||
onlinePayments: false,
|
||||
publicBookingPage: false,
|
||||
advancedReports: false,
|
||||
whatsappReminders: false,
|
||||
fixedSlots: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeV2Rules(overrides?: Record<string, { amount: number; currency: string }>): PlanRules {
|
||||
return {
|
||||
version: 'v2',
|
||||
limits: { maxCourts: 5, maxBookingsPerDay: 100 },
|
||||
policies: { allowOverbooking: false },
|
||||
features: {
|
||||
onlinePayments: true,
|
||||
publicBookingPage: true,
|
||||
advancedReports: true,
|
||||
whatsappReminders: true,
|
||||
fixedSlots: true,
|
||||
},
|
||||
...(overrides ? { pricing: { overrides } } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
const { resolvePlanPrice } = await import('@/modules/plan/services/plan-pricing.service');
|
||||
|
||||
test('returns default price in USD when no country code is provided', () => {
|
||||
const result = resolvePlanPrice(
|
||||
DEFAULT_PRICE,
|
||||
makeV2Rules({ AR: { amount: 14999, currency: 'ARS' } })
|
||||
);
|
||||
expect(result).toEqual({ amount: DEFAULT_PRICE, currency: 'USD' });
|
||||
});
|
||||
|
||||
test('returns overridden price for matching country code', () => {
|
||||
const rules = makeV2Rules({ AR: { amount: 14999, currency: 'ARS' } });
|
||||
const result = resolvePlanPrice(DEFAULT_PRICE, rules, 'AR');
|
||||
expect(result).toEqual({ amount: 14999, currency: 'ARS' });
|
||||
});
|
||||
|
||||
test('returns default price for country without override', () => {
|
||||
const rules = makeV2Rules({ AR: { amount: 14999, currency: 'ARS' } });
|
||||
const result = resolvePlanPrice(DEFAULT_PRICE, rules, 'CL');
|
||||
expect(result).toEqual({ amount: DEFAULT_PRICE, currency: 'USD' });
|
||||
});
|
||||
|
||||
test('returns default price for v1 rules (no pricing config)', () => {
|
||||
const result = resolvePlanPrice(DEFAULT_PRICE, makeV1Rules(), 'AR');
|
||||
expect(result).toEqual({ amount: DEFAULT_PRICE, currency: 'USD' });
|
||||
});
|
||||
|
||||
test('returns default price for v2 rules with empty overrides', () => {
|
||||
const rules = makeV2Rules({});
|
||||
const result = resolvePlanPrice(DEFAULT_PRICE, rules, 'AR');
|
||||
expect(result).toEqual({ amount: DEFAULT_PRICE, currency: 'USD' });
|
||||
});
|
||||
|
||||
test('returns default price when v2 rules has no pricing section', () => {
|
||||
const rules = makeV2Rules();
|
||||
const result = resolvePlanPrice(DEFAULT_PRICE, rules, 'AR');
|
||||
expect(result).toEqual({ amount: DEFAULT_PRICE, currency: 'USD' });
|
||||
});
|
||||
|
||||
test('selects correct override among multiple countries', () => {
|
||||
const rules = makeV2Rules({
|
||||
AR: { amount: 24999, currency: 'ARS' },
|
||||
BR: { amount: 59.99, currency: 'USD' },
|
||||
});
|
||||
|
||||
expect(resolvePlanPrice(DEFAULT_PRICE, rules, 'AR')).toEqual({ amount: 24999, currency: 'ARS' });
|
||||
expect(resolvePlanPrice(DEFAULT_PRICE, rules, 'BR')).toEqual({ amount: 59.99, currency: 'USD' });
|
||||
expect(resolvePlanPrice(DEFAULT_PRICE, rules, 'US')).toEqual({
|
||||
amount: DEFAULT_PRICE,
|
||||
currency: 'USD',
|
||||
});
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
import { expect, test } from 'bun:test';
|
||||
|
||||
import { isSlotInPast } from '@/lib/slot-validator';
|
||||
|
||||
/**
|
||||
* Helper to create a bookingDate like parseIsoDate does (UTC midnight).
|
||||
*/
|
||||
function bookingDate(year: number, month: number, day: number): Date {
|
||||
return new Date(Date.UTC(year, month - 1, day));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a "now" date at a specific local time.
|
||||
* Using local date constructor guarantees the date parts
|
||||
* (getFullYear, getMonth, getDate) match the arguments
|
||||
* regardless of the test runner's timezone.
|
||||
*/
|
||||
function localDate(year: number, month: number, day: number, hours: number, minutes: number): Date {
|
||||
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
||||
}
|
||||
|
||||
test('future date — always allowed', () => {
|
||||
const future = bookingDate(2027, 6, 10); // 2027-06-10
|
||||
const now = localDate(2026, 6, 8, 12, 0); // 2026-06-08 12:00
|
||||
|
||||
expect(isSlotInPast(future, '10:00', 60, now)).toBe(false);
|
||||
});
|
||||
|
||||
test('past date — rejected', () => {
|
||||
const past = bookingDate(2025, 6, 8); // 2025-06-08
|
||||
const now = localDate(2026, 6, 8, 12, 0); // 2026-06-08 12:00
|
||||
|
||||
expect(isSlotInPast(past, '10:00', 60, now)).toBe(true);
|
||||
});
|
||||
|
||||
test('today, slot not started yet — allowed', () => {
|
||||
const today = bookingDate(2026, 6, 8); // 2026-06-08
|
||||
const now = localDate(2026, 6, 8, 9, 0); // 09:00, slot empieza a las 10:00
|
||||
|
||||
expect(isSlotInPast(today, '10:00', 60, now)).toBe(false);
|
||||
});
|
||||
|
||||
test('today, slot in progress less than half elapsed — allowed', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 10, 10); // 10:10, slot 10:00-11:00 (60 min), 10 min elapsed
|
||||
|
||||
expect(isSlotInPast(today, '10:00', 60, now)).toBe(false);
|
||||
});
|
||||
|
||||
test('today, slot in progress more than half elapsed — rejected', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 10, 40); // 10:40, slot 10:00-11:00 (60 min), 40 min elapsed
|
||||
|
||||
expect(isSlotInPast(today, '10:00', 60, now)).toBe(true);
|
||||
});
|
||||
|
||||
test('today, slot already ended — rejected', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 11, 1); // 11:01, slot 10:00-11:00 terminó
|
||||
|
||||
expect(isSlotInPast(today, '10:00', 60, now)).toBe(true);
|
||||
});
|
||||
|
||||
test('today, exactly half elapsed — allowed (inclusive boundary)', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 10, 30); // 10:30, slot 10:00-11:00 (60 min), exactamente 30 min
|
||||
|
||||
expect(isSlotInPast(today, '10:00', 60, now)).toBe(false);
|
||||
});
|
||||
|
||||
test('today, slot starts exactly now — allowed', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 10, 0); // 10:00 exacto
|
||||
|
||||
expect(isSlotInPast(today, '10:00', 60, now)).toBe(false);
|
||||
});
|
||||
|
||||
test('90 min slot with 10 min elapsed — allowed', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 12, 10); // 12:10, slot 12:00-13:30, 10/90 elapsed
|
||||
|
||||
expect(isSlotInPast(today, '12:00', 90, now)).toBe(false);
|
||||
});
|
||||
|
||||
test('90 min slot with 50 min elapsed — rejected', () => {
|
||||
const today = bookingDate(2026, 6, 8);
|
||||
const now = localDate(2026, 6, 8, 12, 50); // 12:50, slot 12:00-13:30, 50/90 elapsed
|
||||
|
||||
expect(isSlotInPast(today, '12:00', 90, now)).toBe(true);
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user