added cancellation worklfow

This commit is contained in:
Rajpreet0 2026-08-26 15:56:11 +02:00
parent 0fe79f4e9b
commit cffca6b2dd
10 changed files with 620 additions and 148 deletions

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Course" ADD COLUMN "cancelledAt" TIMESTAMP(3);

View file

@ -11,143 +11,145 @@ generator client {
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
model User {
id String @id @default(uuid())
name String?
complaints String?
birthday DateTime?
plz String?
city String?
street String?
houseNumber String?
id String @id @default(uuid())
name String?
complaints String?
birthday DateTime?
plz String?
city String?
street String?
houseNumber String?
// @unique is Prisma-managed and stays case-sensitive at the schema level —
// Prisma has no functional-index syntax. The actual enforced constraint is
// a case-insensitive unique index on lower(email), added via raw SQL in
// migration 20260817000000_case_insensitive_user_email. Do not remove
// @unique here; it keeps Prisma's drift detection from trying to recreate
// a duplicate case-sensitive constraint.
email String @unique
tel String?
gender String?
is2FA Boolean @default(false)
isSendingMail Boolean @default(false)
isNewsletter Boolean @default(false)
pbSrc String?
isValid Boolean @default(false)
email String @unique
tel String?
gender String?
is2FA Boolean @default(false)
isSendingMail Boolean @default(false)
isNewsletter Boolean @default(false)
pbSrc String?
isValid Boolean @default(false)
trainings UserTraining[]
courseBookings CourseBooking[]
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
}
model Employee {
id String @id @default(uuid())
firstName String?
lastName String?
email String @unique
is2FA Boolean @default(false)
isSendingMail Boolean @default(false)
pbSrc String?
status String?
roles String[] @default([])
locations String[] @default([])
permissions Json?
affiliation String?
label String?
qualification String?
authorization Json?
onboardingToken String? @unique
id String @id @default(uuid())
firstName String?
lastName String?
email String @unique
is2FA Boolean @default(false)
isSendingMail Boolean @default(false)
pbSrc String?
status String?
roles String[] @default([])
locations String[] @default([])
permissions Json?
affiliation String?
label String?
qualification String?
authorization Json?
onboardingToken String? @unique
onboardingTokenExpiry DateTime?
isOnboarded Boolean @default(false)
createdBy String? // Manager ID who created the employee
trainings UserTraining[]
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
isOnboarded Boolean @default(false)
createdBy String? // Manager ID who created the employee
trainings UserTraining[]
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
@@index([createdBy])
}
model UserTraining {
userId String
userId String
employeeId String
employee Employee @relation(fields: [employeeId], references: [id])
user User @relation(fields: [userId], references: [id])
@@id([userId, employeeId])
}
model Manager {
id String @id @default(uuid())
firstName String
lastName String
tel String?
email String @unique
is2FA Boolean @default(false)
isSendingMail Boolean @default(false)
pbSrc String?
businesses Business[]
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
id String @id @default(uuid())
firstName String
lastName String
tel String?
email String @unique
is2FA Boolean @default(false)
isSendingMail Boolean @default(false)
pbSrc String?
businesses Business[]
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
}
model Business {
id String @id @default(uuid())
managerId String
name String
address String
email String
title String
ustId String?
banking String?
plan String?
subscription Json?
slug String? @unique
isPublic Boolean @default(false)
id String @id @default(uuid())
managerId String
name String
address String
email String
title String
ustId String?
banking String?
plan String?
subscription Json?
slug String? @unique
isPublic Boolean @default(false)
stripeAccountId String? @unique // Stripe Connect Account ID
stripeAccountStatus String? // "pending" | "active" | "restricted"
stripeAccountStatus String? // "pending" | "active" | "restricted"
invoiceCounter Int @default(0) // Fortlaufender Zähler für Rechnungsnummern
cancellationPolicy Json? // { tiers: [{ hoursBeforeStart: number, refundPercent: number }, ...] }, absteigend sortiert. null = immer 100% Rückerstattung.
manager Manager @relation(fields: [managerId], references: [id])
courses Course[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cancellationPolicy Json? // { tiers: [{ hoursBeforeStart: number, refundPercent: number }, ...] }, absteigend sortiert. null = immer 100% Rückerstattung.
manager Manager @relation(fields: [managerId], references: [id])
courses Course[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([managerId])
}
model Course {
id String @id @default(uuid())
name String
sport String[]
level String @default("any")
date DateTime
timeFrom String // Anfangszeit
timeTo String // Endzeit
trainers String[] // Array of trainer IDs
room String
description String // Rich text HTML content
id String @id @default(uuid())
name String
sport String[]
level String @default("any")
date DateTime
timeFrom String // Anfangszeit
timeTo String // Endzeit
trainers String[] // Array of trainer IDs
room String
description String // Rich text HTML content
maxParticipants Int // Maximum number of participants
price Float @default(10.0) // Preis in Euro
isStandingOrder Boolean @default(false)
frequency String? // daily, weekly, biweekly, monthly, custom
weekdays String[] // For custom frequency
weekdayTimings Json? // { "monday": { "timeFrom": "08:00", "timeTo": "09:00" }, ... }
endDate DateTime? // Required when isStandingOrder = true
parentCourseId String? // Null on template; set on every child instance
parentCourse Course? @relation("StandingOrderInstances", fields: [parentCourseId], references: [id], onDelete: Cascade)
children Course[] @relation("StandingOrderInstances")
status String @default("draft") // draft, published
createdBy String // Manager ID who created the course
businessId String?
coverImage String?
business Business? @relation(fields: [businessId], references: [id], onDelete: SetNull)
bookings CourseBooking[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
price Float @default(10.0) // Preis in Euro
isStandingOrder Boolean @default(false)
frequency String? // daily, weekly, biweekly, monthly, custom
weekdays String[] // For custom frequency
weekdayTimings Json? // { "monday": { "timeFrom": "08:00", "timeTo": "09:00" }, ... }
endDate DateTime? // Required when isStandingOrder = true
parentCourseId String? // Null on template; set on every child instance
parentCourse Course? @relation("StandingOrderInstances", fields: [parentCourseId], references: [id], onDelete: Cascade)
children Course[] @relation("StandingOrderInstances")
status String @default("draft") // draft, published, cancelled
cancelledAt DateTime? // Zeitpunkt der Kursabsage (nur gesetzt wenn status = "cancelled")
createdBy String // Manager ID who created the course
businessId String?
coverImage String?
business Business? @relation(fields: [businessId], references: [id], onDelete: SetNull)
bookings CourseBooking[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([createdBy])
@@index([date])
@ -158,8 +160,8 @@ model Course {
}
model Room {
id String @id @default(uuid())
name String
id String @id @default(uuid())
name String
createdBy String // Manager ID who created the room
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@ -176,28 +178,28 @@ model GuestLead {
}
model CourseBooking {
id String @id @default(uuid())
courseId String
userId String?
id String @id @default(uuid())
courseId String
userId String?
guestLeadId String?
paymentStatus String @default("pending") // "pending" | "paid" | "failed" | "refunded" | "partially_refunded" | "refunding" | "cancelled" (cancelled = storniert ohne Geldbewegung, 0% Erstattung)
stripeSessionId String? @unique
stripePaymentIntentId String? @unique
amountPaid Int? // tatsächlich gezahlter Betrag in Cents
platformFee Int? // unsere Service-Fee in Cents
invoiceNumber String? @unique // Rechnungsnummer, wird lazy beim ersten PDF-Download vergeben
paymentStatus String @default("pending") // "pending" | "paid" | "failed" | "refunded" | "partially_refunded" | "refunding" | "cancelled" (cancelled = storniert ohne Geldbewegung, 0% Erstattung)
stripeSessionId String? @unique
stripePaymentIntentId String? @unique
amountPaid Int? // tatsächlich gezahlter Betrag in Cents
platformFee Int? // unsere Service-Fee in Cents
invoiceNumber String? @unique // Rechnungsnummer, wird lazy beim ersten PDF-Download vergeben
invoiceIssuedAt DateTime? // Zeitpunkt der Rechnungsnummer-Vergabe, damit das Rechnungsdatum bei jedem Download gleich bleibt
refundedAmount Int? // tatsächlich erstatteter Betrag in Cents
refundedAmount Int? // tatsächlich erstatteter Betrag in Cents
refundedAt DateTime? // Zeitpunkt der erfolgreichen Stripe-Rückerstattung
cancelledBy String? // "user" | "manager" | "system" (system = automatischer Überbuchungs-Refund)
cancelledBy String? // "user" | "manager" | "system" (system = automatischer Überbuchungs-Refund)
cancellationReason String?
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
guestLead GuestLead? @relation(fields: [guestLeadId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
guestLead GuestLead? @relation(fields: [guestLeadId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@unique([courseId, userId]) // Ein User kann sich nur einmal pro Kurs anmelden
@@index([courseId])
@@index([userId])
@@index([guestLeadId])
}
}