* Integrate creation/rescheduling booking audit * fix: add missing hostUserUuid to booking audit test data Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix-ci * feat: enhance booking audit with seat reference - Added support for seat reference in booking audit actions. - Updated localization for booking creation to include seat information. - Modified relevant services to pass attendee seat ID during booking creation. * fix: update test data to match schema requirements - Add seatReferenceUid: null to default mock audit log data - Add seatReferenceUid: null to multiple audit logs test case - Convert SEAT_RESCHEDULED test data to use numeric timestamps instead of ISO strings Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Allow nullish seatReferenceUid * feat: enhance booking audit to support rescheduledBy information - Updated booking audit actions to include rescheduledBy details, allowing tracking of who rescheduled a booking. - Refactored related services to accommodate the new rescheduledBy parameter in booking events. - Adjusted type definitions and function signatures to reflect the changes in the booking audit context. * Avoid possible run time issue * Fix imoport path * fix failing test due to merge from main\ * Pass useruuid * chore: retrigger CI (flaky unit test) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: add context parameter to bulk audit methods for impersonation support - Add context parameter to queueBulkCreatedAudit and queueBulkRescheduledAudit in BookingAuditProducerService interface - Add context parameter to BookingAuditTaskerProducerService implementation - Add context parameter to onBulkBookingsCreated and onBulkBookingsRescheduled in BookingEventHandlerService - Update RegularBookingService.fireBookingEvents to accept and pass impersonation context - Update RecurringBookingService.fireBookingEvents to accept and pass impersonation context - Update handleSeats to accept and pass impersonation context Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * chore: Integrate mark-no-show booking audit Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Simplify host no-show audit and add comment for attendee actor Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: make impersonatedByUserUuid required with explicit null for non-impersonation cases Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: integrate impersonatedByUserUuid into booking cancellation audit flow - Add impersonatedByUserUuid to CancelBookingInput and CancelBookingMeta types - Pass audit context with impersonatedBy to onBookingCancelled and onBulkBookingsCancelled - Update all cancel booking call sites to pass impersonatedByUserUuid: - Web app cancel route: uses session impersonatedBy - API v1 and v2: explicitly set to null (no impersonation support) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: make impersonatedByUserUuid optional and remove explicit null assignments - Changed impersonatedByUserUuid from required (string | null) to optional (string?) - Removed explicit null assignments from API v1 and v2 endpoints - Keep impersonatedByUserUuid only where impersonation actually occurs (web app) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Apply suggestions from code review Rfemove unnecessary comment * fix: restore bookingMeta variable in createBookingForApiV1 Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Make actionSource required with ValidActionSource type and remove unnecessary comments Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Remove merge conflict markers from bookings.service.ts Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: implement SYSTEM as a booking audit source for background tasks, including no-show triggers. * refactor: Move prisma query to BookingRepository for audit logging Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Conflict resolution * refactor: introduce `handleMarkHostNoShow` for public viewer and standardize actor and action source parameters for no-show actions. * refactor: Combine HostNoShowUpdatedAuditActionService and AttendeeNoShowUpdatedAuditActionService into NoShowUpdatedActionService - Create new NoShowUpdatedAuditActionService with combined schema supporting both noShowHost and noShowAttendee as optional fields - Update BookingAuditActionServiceRegistry to use combined service with NO_SHOW_UPDATED action type - Update BookingAuditTaskerProducerService with single queueNoShowUpdatedAudit method - Update BookingAuditProducerService.interface.ts with combined method - Update BookingEventHandlerService with single onNoShowUpdated method - Update handleMarkNoShow.ts to use combined audit service - Update tasker tasks (triggerGuestNoShow, triggerHostNoShow) to use combined service - Add NO_SHOW_UPDATED to Prisma BookingAuditAction enum - Remove old separate HostNoShowUpdatedAuditActionService and AttendeeNoShowUpdatedAuditActionService files This allows a single API action (e.g., API V2 markAbsent) that updates both host and attendee no-show status to be logged as a single audit event. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * chore: Add migration for NO_SHOW_UPDATED audit action enum value Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Update no-show audit schema to use host and attendees array - Rename noShowHost to host and noShowAttendee to attendees (remove redundant prefix) - Change attendees from single value to array to support multiple attendees in single audit entry - Update handleMarkNoShow.ts to create single audit entry for all attendees - Update tasker tasks (triggerGuestNoShow, triggerHostNoShow) to use new schema - Update display data types to reflect new schema structure Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Update schema to be more clear. Call onNoShowUpdated immediately after DB update as audit only cares about DB update, and this would avoid any accidental Audit update on DB change * chore: accommodate schema changes and fix type errors for no-show audit Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Update migration to remove old no-show enum values Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Use updated attendees in webhook payload for guest no-show Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Remove duplicate imports and fix actor parameter in bookings.service.ts Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Move booking query to BookingRepository and remove unused method Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: centralize no-show audit event firing and attendee fetching within `handleMarkNoShow` for improved clarity. * fix: Build attendeesNoShow as Record with attendee IDs as keys Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Fix and add tests for handleMarkBoShow * refactor: Move prisma queries to AttendeeRepository and BookingRepository Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Correct return type for updateNoShow method (noShow can be null) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Update handleMarkNoShow tests to mock new repository methods Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix triggerGuestNoShow * refactor: Move triggerGuestNoShow queries to AttendeeRepository and add unit tests - Add new methods to AttendeeRepository: - findByBookingId: Get attendees with id, email, noShow - findByBookingIdWithDetails: Get full attendee details - updateManyNoShowByBookingIdAndEmails: Update specific attendees - updateManyNoShowByBookingIdExcludingEmails: Update all except specific emails - Refactor triggerGuestNoShow.ts to use AttendeeRepository instead of direct Prisma calls - Add comprehensive unit tests for triggerGuestNoShow following handleMarkNoShow.test.ts pattern: - Mock repositories and external services - In-memory DB simulation - Test core functionality, audit logging, webhook payload, error handling, and edge cases Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Move triggerHostNoShow queries to repositories and delete unit test file Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Convert repository methods to use named parameters objects Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Enhance no-show handling by consolidating audit logging and updating repository interactions - Refactor `buildResultPayload` to accept a structured argument for better clarity. - Update `updateAttendees` to use a named parameter object for improved readability. - Introduce `fireNoShowUpdatedEvent` to centralize no-show audit logging for both hosts and guests. - Remove redundant methods and streamline attendee retrieval in `AttendeeRepository`. - Add comprehensive unit tests for no-show event handling, ensuring accurate audit logging and webhook triggering. * test: Add tests for handleMarkHostNoShow guest actor and unmarking host no-show Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: update attendeesNoShow validation to handle numeric keys - Changed attendeesNoShow schema to use z.coerce.number() for key validation, ensuring string keys are correctly coerced to numbers. - Updated test to validate attendeesNoShow data using numeric key comparison, improving robustness of the audit data checks. * test: Add integration tests for NoShowUpdatedAuditActionService coerce fix Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: add graceful error handling for audit log enrichment - Add hasError field to EnrichedAuditLog type - Create buildFallbackAuditLog() method for failed enrichments - Wrap enrichAuditLog() in try-catch to handle errors gracefully - Add booking_audit_action.error_processing translation key - Update BookingHistory.tsx to show warning icon for error logs - Hide 'Show details' button for logs with hasError - Add comprehensive test cases for error handling scenarios Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: Enhance booking audit action services with new display fields and improved validation - Added "attendee_no_show_updated" and "no_show_updated" translations to common.json. - Updated IAuditActionService to include new methods for handling display fields and migration. - Enhanced NoShowUpdatedAuditActionService to differentiate between host and attendee no-show updates. - Improved ReassignmentAuditActionService to ensure consistent handling of audit data. - Refactored BookingAuditViewerService for better clarity and maintainability. * refactor: Use attendeeEmail instead of attendeeId as key in audit data and store host's userUuid - Updated schema to use attendee email as key instead of attendee ID because attendee records can be reused with different person's data - Store host's userUuid in audit data with format: host: { userUuid, noShow: { old, new } } - Updated fireNoShowUpdated to accept hostUserUuid parameter - Added uuid field to BookingRepository.findByUidIncludeEventTypeAttendeesAndUser - Updated NoShowUpdatedAuditActionService getDisplayFields to work with email-based keys - Added attendeeRepository to DI modules for BookingAuditTaskConsumer - Updated tests to match new schema format Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * test: Update integration tests to use new schema format with host.userUuid and email keys Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: correct audit schema for SYSTEM source and ensure attendee lookup by bookingId - Fix schema inconsistency: use host.userUuid format instead of hostNoShow - Add user.uuid to getBooking select for audit logging - Log warning when hostUserUuid is undefined but host no-show is being updated - Use email as key for attendeesNoShow (not attendee ID) to match schema - Fetch attendees by bookingId before updating to ensure correct scoping - Remove unused safeStringify import * refactor: Change attendeesNoShow schema from Record to Array format - Update NoShowUpdatedAuditActionService schema to use array format: attendeesNoShow: Array<{attendeeEmail: string, noShow: {old, new}}> - Update handleMarkNoShow.ts to build attendeesNoShow as array - Update common.ts fireNoShowUpdatedEvent to convert Map to array - Update all tests to use new array format with attendeeEmail property Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Update attendeesNoShow schema to use array format in triggerNoShow tasks - Refactor `triggerGuestNoShow` and `triggerHostNoShow` to replace Map with array for `attendeesNoShowAudit`. - Update `fireNoShowUpdatedEvent` to handle the new array format for attendees. - Ensure consistent data structure across no-show audit logging for better clarity and maintainability. * fix: Update ReassignmentAuditActionService to use GetDisplayFieldsParams interface Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Update ReassignmentAuditActionService tests to use GetDisplayFieldsParams interface Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Use handleMarkAttendeeNoShow for API v2 mark absent endpoint - Export handleMarkAttendeeNoShow from platform-libraries - Update API v2 bookings service to use handleMarkAttendeeNoShow with actionSource - Add validation for userUuid before calling handleMarkAttendeeNoShow Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Enhance display fields structure in booking audit components - Updated `BookingHistory.tsx` to allow for more flexible display fields, including optional raw values and arrays of values. - Introduced `DisplayFieldValue` component for rendering display fields, improving clarity and maintainability. - Adjusted `IAuditActionService` and `NoShowUpdatedAuditActionService` to align with the new display fields structure. - Ensured consistent handling of display fields across the booking audit service for better data representation. * fix: Remove debug code that duplicated first attendee in display fields Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Update attendee repository methods to use structured parameters - Modified `updateNoShow`, `updateManyNoShowByBookingIdAndEmails`, and `updateManyNoShowByBookingIdExcludingEmails` methods to accept structured `where` and `data` parameters for better clarity and maintainability. - Updated calls to these methods throughout the codebase to reflect the new parameter structure. - Removed unused `findByIdWithNoShow` method and adjusted related logic to streamline attendee data retrieval. * feat: Add infrastructure for no-show audit integration - Add Prisma migrations for SYSTEM source and NO_SHOW_UPDATED audit action - Add NoShowUpdatedAuditActionService with array-based attendeesNoShow schema - Update BookingAuditActionServiceRegistry to include NO_SHOW_UPDATED - Update BookingAuditTaskConsumer and BookingAuditViewerService - Add AttendeeRepository methods for no-show queries - Update IAuditActionService interface with values array support - Update locales with no-show audit translation keys Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Add NO_SHOW_UPDATED to BookingAuditAction and SYSTEM to ActionSource types Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Remove HOST_NO_SHOW_UPDATED and ATTENDEE_NO_SHOW_UPDATED from BookingAuditAction type to match Prisma schema Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Update BookingAuditActionSchema to use NO_SHOW_UPDATED instead of HOST_NO_SHOW_UPDATED and ATTENDEE_NO_SHOW_UPDATED Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: Remove deprecated no-show audit services and unify to NoShowUpdatedAuditActionService - Delete HostNoShowUpdatedAuditActionService and AttendeeNoShowUpdatedAuditActionService - Update BookingAuditProducerService.interface.ts to use queueNoShowUpdatedAudit - Update BookingAuditTaskerProducerService.ts to use queueNoShowUpdatedAudit - Update BookingEventHandlerService.ts to use onNoShowUpdated - Add integration tests for NoShowUpdatedAuditActionService Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: Update test mock to match new AttendeeRepository.updateNoShow signature Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: rename handleMarkAttendeeNoShow to handleMarkNoShow and update related types - Renamed `handleMarkAttendeeNoShow` to `handleMarkNoShow` for clarity. - Updated type definitions to reflect the new naming convention. - Modified the `markNoShow` handler to require `userUuid` and adjusted related logic. - Enhanced the `fireNoShowUpdatedEvent` to accommodate changes in event type structure. - Updated references across various files to ensure consistency with the new function name. * handle null value * fix: add explicit parentheses for operator precedence clarity Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Fix cubic reported bug * feat: add impersonation audit support to no-show flow (#27601) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat: add impersonatedByUserUuid context to all BookingEventHandler calls (#27602) - Add context parameter to confirm.handler.ts (onBulkBookingsRejected, onBookingRejected) - Add context parameter to requestReschedule.handler.ts (onRescheduleRequested) - Add context parameter to editLocation.handler.ts (onLocationChanged) - Add context parameter to addGuests.handler.ts (onAttendeeAdded) - Add context parameter to handleConfirmation.ts (onBulkBookingsAccepted, onBookingAccepted) - Update _router.tsx to pass impersonatedByUserUuid from session to handlers This ensures all BookingEventHandler method calls that have loggedInUser context now properly support impersonation tracking for audit logging. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: remove stray merge artifact in RegularBookingService Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: remove duplicate imports in BookingRepository Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: resolve type errors and duplicate declarations from merge Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: remove duplicate test assertions and duplicate test from merge artifacts Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: remove redundant ?? undefined from impersonatedByUserUuid Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: update impersonatedByUserUuid type to string | null for consistency Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: make impersonatedByUserUuid required in booking audit flows Changes impersonatedByUserUuid from optional (?: string | null) to required (: string | null) in all booking-related type definitions. This ensures audit-critical parameters are never accidentally omitted. Fixed all call sites to explicitly pass the value or null. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add impersonatedByUserUuid to remaining callers and tests Adds impersonatedByUserUuid: null to API v1, API v2, and test callers of handleCancelBooking that were missing the now-required parameter. Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: require impersonatedByUserUuid in DTOs and pass it across all booking flows (coalesce to null) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add impersonatedByUserUuid to remaining call sites (confirm, markNoShow, webhook, tests) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add impersonatedByUserUuid to confirm handler test and API v2 confirm/decline calls Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: derive impersonatedByUserUuid from session in reportBooking handler instead of hardcoding null Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: reorder spread to ensure impersonatedByUserUuid null fallback is not overwritten Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: add missing impersonatedByUserUuid to CalendarSyncService cancel and reschedule calls Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: update CalendarSyncService tests to expect impersonatedByUserUuid Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * ci: retrigger failed checks (attempt 1) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * ci: retrigger failed checks (attempt 2) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * ci: retrigger failed checks (attempt 3) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * chore: bust prisma cache with comment change Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * ci: retrigger after cache bust (attempt 2) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * ci: retrigger after cache bust (attempt 3) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Volnei Munhoz <volnei@cal.com>
3342 lines
119 KiB
Plaintext
3342 lines
119 KiB
Plaintext
// This is your Prisma Schema file
|
|
// Learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
directUrl = env("DATABASE_DIRECT_URL")
|
|
}
|
|
|
|
generator client {
|
|
provider = "prisma-client"
|
|
previewFeatures = ["views"]
|
|
output = "./generated/prisma"
|
|
engineType = "client"
|
|
moduleFormat = "cjs"
|
|
importFileExtension = ""
|
|
}
|
|
|
|
generator zod {
|
|
provider = "zod-prisma-types"
|
|
output = "./zod"
|
|
useMultipleFiles = true
|
|
createInputTypes = false
|
|
addIncludeType = false
|
|
addSelectType = false
|
|
validateWhereUniqueInput = false
|
|
prismaClientPath = "../../generated/prisma/client"
|
|
moduleFormat = "cjs"
|
|
/// createRelationValuesTypes = true
|
|
}
|
|
|
|
generator kysely {
|
|
provider = "prisma-kysely"
|
|
output = "../kysely"
|
|
fileName = "types.ts"
|
|
}
|
|
|
|
generator enums {
|
|
provider = "prisma-enum-generator"
|
|
}
|
|
|
|
enum SchedulingType {
|
|
ROUND_ROBIN @map("roundRobin")
|
|
COLLECTIVE @map("collective")
|
|
MANAGED @map("managed")
|
|
}
|
|
|
|
enum PeriodType {
|
|
UNLIMITED @map("unlimited")
|
|
ROLLING @map("rolling")
|
|
ROLLING_WINDOW @map("rolling_window")
|
|
RANGE @map("range")
|
|
}
|
|
|
|
enum CreationSource {
|
|
API_V1 @map("api_v1")
|
|
API_V2 @map("api_v2")
|
|
WEBAPP @map("webapp")
|
|
}
|
|
|
|
model Host {
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
eventTypeId Int
|
|
isFixed Boolean @default(false)
|
|
priority Int?
|
|
weight Int?
|
|
// weightAdjustment is deprecated. We not calculate the calibratino value on the spot. Plan to drop this column.
|
|
weightAdjustment Int?
|
|
schedule Schedule? @relation(fields: [scheduleId], references: [id])
|
|
scheduleId Int?
|
|
createdAt DateTime @default(now())
|
|
group HostGroup? @relation(fields: [groupId], references: [id])
|
|
groupId String?
|
|
memberId Int?
|
|
member Membership? @relation(fields: [memberId], references: [id], onDelete: Cascade)
|
|
location HostLocation?
|
|
|
|
@@id([userId, eventTypeId])
|
|
@@index([memberId])
|
|
@@index([userId])
|
|
@@index([eventTypeId])
|
|
@@index([scheduleId])
|
|
}
|
|
|
|
model HostGroup {
|
|
id String @id @default(uuid())
|
|
name String
|
|
hosts Host[]
|
|
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
eventTypeId Int?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([name])
|
|
@@index([eventTypeId])
|
|
}
|
|
|
|
model HostLocation {
|
|
id String @id @default(uuid())
|
|
userId Int
|
|
eventTypeId Int
|
|
host Host @relation(fields: [userId, eventTypeId], references: [userId, eventTypeId], onDelete: Cascade)
|
|
type String
|
|
credentialId Int?
|
|
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull)
|
|
link String?
|
|
address String?
|
|
phoneNumber String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, eventTypeId])
|
|
@@index([credentialId])
|
|
@@index([eventTypeId])
|
|
}
|
|
|
|
model CalVideoSettings {
|
|
eventTypeId Int @id
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
|
|
disableRecordingForOrganizer Boolean @default(false)
|
|
disableRecordingForGuests Boolean @default(false)
|
|
enableAutomaticTranscription Boolean @default(false)
|
|
enableAutomaticRecordingForOrganizer Boolean @default(false)
|
|
redirectUrlOnExit String?
|
|
disableTranscriptionForGuests Boolean @default(false)
|
|
disableTranscriptionForOrganizer Boolean @default(false)
|
|
requireEmailForGuests Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model VideoCallGuest {
|
|
id String @id @default(uuid())
|
|
bookingUid String
|
|
email String
|
|
name String
|
|
joinedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([bookingUid, email])
|
|
@@index([bookingUid])
|
|
@@index([email])
|
|
}
|
|
|
|
enum CancellationReasonRequirement {
|
|
MANDATORY_BOTH
|
|
MANDATORY_HOST_ONLY
|
|
MANDATORY_ATTENDEE_ONLY
|
|
OPTIONAL_BOTH
|
|
}
|
|
|
|
model EventType {
|
|
id Int @id @default(autoincrement())
|
|
/// @zod.string.min(1)
|
|
title String
|
|
/// @zod.import(["import { eventTypeSlug } from '../../zod-utils'"]).custom.use(eventTypeSlug)
|
|
slug String
|
|
description String?
|
|
interfaceLanguage String?
|
|
position Int @default(0)
|
|
/// @zod.import(["import { eventTypeLocations } from '../../zod-utils'"]).custom.use(eventTypeLocations)
|
|
locations Json?
|
|
/// @zod.number.min(1)
|
|
length Int
|
|
offsetStart Int @default(0)
|
|
hidden Boolean @default(false)
|
|
hosts Host[]
|
|
|
|
users User[] @relation("user_eventtype")
|
|
owner User? @relation("owner", fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int?
|
|
|
|
profileId Int?
|
|
profile Profile? @relation(fields: [profileId], references: [id])
|
|
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
hashedLink HashedLink[]
|
|
bookings Booking[]
|
|
availability Availability[]
|
|
webhooks Webhook[]
|
|
destinationCalendar DestinationCalendar?
|
|
useEventLevelSelectedCalendars Boolean @default(false)
|
|
eventName String?
|
|
customInputs EventTypeCustomInput[]
|
|
parentId Int?
|
|
parent EventType? @relation("managed_eventtype", fields: [parentId], references: [id], onDelete: Cascade)
|
|
children EventType[] @relation("managed_eventtype")
|
|
/// @zod.import(["import { eventTypeBookingFields } from '../../zod-utils'"]).custom.use(eventTypeBookingFields)
|
|
bookingFields Json?
|
|
timeZone String?
|
|
periodType PeriodType @default(UNLIMITED)
|
|
/// @zod.import(["import { coerceToDate } from '../../zod-utils'"]).custom.use(coerceToDate)
|
|
periodStartDate DateTime?
|
|
/// @zod.import(["import { coerceToDate } from '../../zod-utils'"]).custom.use(coerceToDate)
|
|
periodEndDate DateTime?
|
|
periodDays Int?
|
|
periodCountCalendarDays Boolean?
|
|
lockTimeZoneToggleOnBookingPage Boolean @default(false)
|
|
lockedTimeZone String?
|
|
requiresConfirmation Boolean @default(false)
|
|
requiresConfirmationWillBlockSlot Boolean @default(false)
|
|
requiresConfirmationForFreeEmail Boolean @default(false)
|
|
requiresBookerEmailVerification Boolean @default(false)
|
|
canSendCalVideoTranscriptionEmails Boolean @default(true)
|
|
|
|
autoTranslateDescriptionEnabled Boolean @default(false)
|
|
autoTranslateInstantMeetingTitleEnabled Boolean @default(true)
|
|
/// @zod.import(["import { recurringEventType } from '../../zod-utils'"]).custom.use(recurringEventType)
|
|
recurringEvent Json?
|
|
disableGuests Boolean @default(false)
|
|
hideCalendarNotes Boolean @default(false)
|
|
hideCalendarEventDetails Boolean @default(false)
|
|
/// @zod.number.min(0)
|
|
minimumBookingNotice Int @default(120)
|
|
beforeEventBuffer Int @default(0)
|
|
afterEventBuffer Int @default(0)
|
|
seatsPerTimeSlot Int?
|
|
onlyShowFirstAvailableSlot Boolean @default(false)
|
|
showOptimizedSlots Boolean? @default(false)
|
|
disableCancelling Boolean? @default(false)
|
|
disableRescheduling Boolean? @default(false)
|
|
/// @zod.number.min(0)
|
|
minimumRescheduleNotice Int?
|
|
seatsShowAttendees Boolean? @default(false)
|
|
seatsShowAvailabilityCount Boolean? @default(true)
|
|
schedulingType SchedulingType?
|
|
schedule Schedule? @relation(fields: [scheduleId], references: [id])
|
|
scheduleId Int?
|
|
allowReschedulingCancelledBookings Boolean? @default(false)
|
|
// price is deprecated. It has now moved to metadata.apps.stripe.price. Plan to drop this column.
|
|
price Int @default(0)
|
|
// currency is deprecated. It has now moved to metadata.apps.stripe.currency. Plan to drop this column.
|
|
currency String @default("usd")
|
|
slotInterval Int?
|
|
/// @zod.import(["import { EventTypeMetaDataSchema } from '../../zod-utils'"]).custom.use(EventTypeMetaDataSchema)
|
|
metadata Json?
|
|
/// @zod.import(["import { successRedirectUrl } from '../../zod-utils'"]).custom.use(successRedirectUrl)
|
|
successRedirectUrl String?
|
|
forwardParamsSuccessRedirect Boolean? @default(true)
|
|
/// @zod.import(["import { successRedirectUrl } from '../../zod-utils'"]).custom.use(successRedirectUrl)
|
|
redirectUrlOnNoRoutingFormResponse String?
|
|
workflows WorkflowsOnEventTypes[]
|
|
/// @zod.import(["import { intervalLimitsType } from '../../zod-utils'"]).custom.use(intervalLimitsType)
|
|
bookingLimits Json?
|
|
/// @zod.import(["import { intervalLimitsType } from '../../zod-utils'"]).custom.use(intervalLimitsType)
|
|
durationLimits Json?
|
|
isInstantEvent Boolean @default(false)
|
|
instantMeetingExpiryTimeOffsetInSeconds Int @default(90)
|
|
instantMeetingScheduleId Int?
|
|
instantMeetingSchedule Schedule? @relation("InstantMeetingSchedule", fields: [instantMeetingScheduleId], references: [id])
|
|
instantMeetingParameters String[]
|
|
assignAllTeamMembers Boolean @default(false)
|
|
// It is applicable only when assignAllTeamMembers is true and it filters out all the team members using rrSegmentQueryValue
|
|
assignRRMembersUsingSegment Boolean @default(false)
|
|
/// @zod.import(["import { rrSegmentQueryValueSchema } from '../../zod-utils'"]).custom.use(rrSegmentQueryValueSchema)
|
|
rrSegmentQueryValue Json?
|
|
useEventTypeDestinationCalendarEmail Boolean @default(false)
|
|
aiPhoneCallConfig AIPhoneCallConfiguration?
|
|
isRRWeightsEnabled Boolean @default(false)
|
|
fieldTranslations EventTypeTranslation[]
|
|
maxLeadThreshold Int?
|
|
includeNoShowInRRCalculation Boolean @default(false)
|
|
selectedCalendars SelectedCalendar[]
|
|
allowReschedulingPastBookings Boolean @default(false)
|
|
hideOrganizerEmail Boolean @default(false)
|
|
maxActiveBookingsPerBooker Int?
|
|
maxActiveBookingPerBookerOfferReschedule Boolean @default(false)
|
|
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
|
|
customReplyToEmail String?
|
|
calVideoSettings CalVideoSettings?
|
|
|
|
/// @zod.import(["import { eventTypeColor } from '../../zod-utils'"]).custom.use(eventTypeColor)
|
|
eventTypeColor Json?
|
|
rescheduleWithSameRoundRobinHost Boolean @default(false)
|
|
|
|
secondaryEmailId Int?
|
|
secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id], onDelete: Cascade)
|
|
|
|
useBookerTimezone Boolean @default(false)
|
|
restrictionScheduleId Int?
|
|
restrictionSchedule Schedule? @relation("restrictionSchedule", fields: [restrictionScheduleId], references: [id])
|
|
hostGroups HostGroup[]
|
|
|
|
bookingRequiresAuthentication Boolean @default(false)
|
|
rrHostSubsetEnabled Boolean @default(false)
|
|
requiresCancellationReason CancellationReasonRequirement? @default(MANDATORY_HOST_ONLY)
|
|
enablePerHostLocations Boolean @default(false)
|
|
|
|
createdAt DateTime? @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
|
|
// @@partial_index([instantMeetingScheduleId])
|
|
|
|
@@unique([userId, slug])
|
|
@@unique([teamId, slug])
|
|
@@unique([userId, parentId])
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
@@index([profileId])
|
|
@@index([scheduleId])
|
|
@@index([secondaryEmailId])
|
|
@@index([parentId])
|
|
@@index([parentId, teamId])
|
|
@@index([restrictionScheduleId])
|
|
}
|
|
|
|
model Credential {
|
|
id Int @id @default(autoincrement())
|
|
// @@type is deprecated
|
|
type String
|
|
key Json
|
|
encryptedKey String?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
|
|
// How to make it a required column?
|
|
appId String?
|
|
|
|
// paid apps
|
|
subscriptionId String?
|
|
paymentStatus String?
|
|
billingCycleStart Int?
|
|
|
|
destinationCalendars DestinationCalendar[]
|
|
selectedCalendars SelectedCalendar[]
|
|
invalid Boolean? @default(false)
|
|
CalendarCache CalendarCache[]
|
|
references BookingReference[]
|
|
delegationCredentialId String?
|
|
delegationCredential DelegationCredential? @relation(fields: [delegationCredentialId], references: [id], onDelete: Cascade)
|
|
integrationAttributeSyncs IntegrationAttributeSync[]
|
|
hostLocations HostLocation[]
|
|
|
|
@@index([appId])
|
|
@@index([subscriptionId])
|
|
@@index([invalid])
|
|
@@index([userId, delegationCredentialId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
enum IdentityProvider {
|
|
CAL
|
|
GOOGLE
|
|
SAML
|
|
AZUREAD
|
|
}
|
|
|
|
model DestinationCalendar {
|
|
id Int @id @default(autoincrement())
|
|
integration String
|
|
externalId String
|
|
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
|
|
primaryEmail String?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int? @unique
|
|
booking Booking[]
|
|
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
eventTypeId Int? @unique
|
|
credentialId Int?
|
|
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
|
|
createdAt DateTime? @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
delegationCredential DelegationCredential? @relation(fields: [delegationCredentialId], references: [id], onDelete: Cascade)
|
|
delegationCredentialId String?
|
|
domainWideDelegation DomainWideDelegation? @relation(fields: [domainWideDelegationCredentialId], references: [id], onDelete: Cascade)
|
|
domainWideDelegationCredentialId String?
|
|
customCalendarReminder Int?
|
|
|
|
@@index([userId])
|
|
@@index([eventTypeId])
|
|
@@index([credentialId])
|
|
}
|
|
|
|
enum UserPermissionRole {
|
|
USER
|
|
ADMIN
|
|
}
|
|
|
|
// It holds the password of a User, separate from the User model to avoid leaking the password hash
|
|
model UserPassword {
|
|
hash String
|
|
userId Int @unique
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model TravelSchedule {
|
|
id Int @id @default(autoincrement())
|
|
userId Int
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
timeZone String
|
|
startDate DateTime
|
|
endDate DateTime?
|
|
prevTimeZone String?
|
|
|
|
@@index([startDate])
|
|
@@index([endDate])
|
|
}
|
|
|
|
// It holds Personal Profiles of a User plus it has email, password and other core things..
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
uuid String @unique @default(uuid()) @db.Uuid
|
|
username String?
|
|
name String?
|
|
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
|
|
email String
|
|
emailVerified DateTime?
|
|
password UserPassword?
|
|
bio String?
|
|
avatarUrl String?
|
|
timeZone String @default("Europe/London")
|
|
travelSchedules TravelSchedule[]
|
|
weekStart String @default("Sunday")
|
|
bufferTime Int @default(0)
|
|
hideBranding Boolean @default(false)
|
|
// TODO: should be renamed since it only affects the booking page
|
|
theme String?
|
|
appTheme String?
|
|
createdDate DateTime @default(now()) @map(name: "created")
|
|
trialEndsAt DateTime?
|
|
lastActiveAt DateTime?
|
|
eventTypes EventType[] @relation("user_eventtype")
|
|
credentials Credential[]
|
|
teams Membership[]
|
|
bookings Booking[]
|
|
schedules Schedule[]
|
|
defaultScheduleId Int?
|
|
selectedCalendars SelectedCalendar[]
|
|
completedOnboarding Boolean @default(false)
|
|
locale String?
|
|
timeFormat Int? @default(12)
|
|
twoFactorSecret String?
|
|
twoFactorEnabled Boolean @default(false)
|
|
backupCodes String?
|
|
identityProvider IdentityProvider @default(CAL)
|
|
identityProviderId String?
|
|
availability Availability[]
|
|
invitedTo Int?
|
|
webhooks Webhook[]
|
|
brandColor String?
|
|
darkBrandColor String?
|
|
// the location where the events will end up
|
|
destinationCalendar DestinationCalendar?
|
|
// participate in dynamic group booking or not
|
|
allowDynamicBooking Boolean? @default(true)
|
|
|
|
// participate in SEO indexing or not
|
|
allowSEOIndexing Boolean? @default(true)
|
|
|
|
// receive monthly digest email for teams or not
|
|
receiveMonthlyDigestEmail Boolean? @default(true)
|
|
// require email verification when someone books using your email
|
|
requiresBookerEmailVerification Boolean? @default(false)
|
|
|
|
/// @zod.import(["import { userMetadata } from '../../zod-utils'"]).custom.use(userMetadata)
|
|
metadata Json?
|
|
verified Boolean? @default(false)
|
|
role UserPermissionRole @default(USER)
|
|
disableImpersonation Boolean @default(false)
|
|
impersonatedUsers Impersonations[] @relation("impersonated_user")
|
|
impersonatedBy Impersonations[] @relation("impersonated_by_user")
|
|
apiKeys ApiKey[]
|
|
accounts Account[]
|
|
sessions Session[]
|
|
Feedback Feedback[]
|
|
ownedEventTypes EventType[] @relation("owner")
|
|
workflows Workflow[]
|
|
routingForms App_RoutingForms_Form[] @relation("routing-form")
|
|
updatedRoutingForms App_RoutingForms_Form[] @relation("updated-routing-form")
|
|
verifiedNumbers VerifiedNumber[]
|
|
verifiedEmails VerifiedEmail[]
|
|
hosts Host[]
|
|
// organizationId is deprecated. Instead, rely on the Profile to search profiles by organizationId and then get user from the profile.
|
|
organizationId Int?
|
|
organization Team? @relation("scope", fields: [organizationId], references: [id], onDelete: SetNull)
|
|
accessCodes AccessCode[]
|
|
bookingRedirects OutOfOfficeEntry[]
|
|
bookingRedirectsTo OutOfOfficeEntry[] @relation(name: "toUser")
|
|
|
|
// Used to lock the user account
|
|
locked Boolean @default(false)
|
|
platformOAuthClients PlatformOAuthClient[]
|
|
AccessToken AccessToken[]
|
|
RefreshToken RefreshToken[]
|
|
PlatformAuthorizationToken PlatformAuthorizationToken[]
|
|
profiles Profile[]
|
|
movedToProfileId Int?
|
|
movedToProfile Profile? @relation("moved_to_profile", fields: [movedToProfileId], references: [id], onDelete: SetNull)
|
|
secondaryEmails SecondaryEmail[]
|
|
isPlatformManaged Boolean @default(false)
|
|
OutOfOfficeReasons OutOfOfficeReason[]
|
|
smsLockState SMSLockState @default(UNLOCKED)
|
|
smsLockReviewedByAdmin Boolean @default(false)
|
|
NotificationsSubscriptions NotificationsSubscriptions[]
|
|
referralLinkId String?
|
|
features UserFeatures[]
|
|
reassignedBookings Booking[] @relation("reassignByUser")
|
|
createdAttributeToUsers AttributeToUser[] @relation("createdBy")
|
|
updatedAttributeToUsers AttributeToUser[] @relation("updatedBy")
|
|
createdTranslations EventTypeTranslation[] @relation("CreatedEventTypeTranslations")
|
|
updatedTranslations EventTypeTranslation[] @relation("UpdatedEventTypeTranslations")
|
|
reportedBookings BookingReport[] @relation("ReportedBy")
|
|
wrongAssignmentReports WrongAssignmentReport[] @relation("WrongAssignmentReportedBy")
|
|
reviewedWrongAssignmentReports WrongAssignmentReport[] @relation("WrongAssignmentReviewedBy")
|
|
BookingInternalNote BookingInternalNote[]
|
|
creationSource CreationSource?
|
|
createdOrganizationOnboardings OrganizationOnboarding[] @relation("CreatedOrganizationOnboardings")
|
|
filterSegments FilterSegment[]
|
|
filterSegmentPreferences UserFilterSegmentPreference[]
|
|
creditBalance CreditBalance?
|
|
whitelistWorkflows Boolean @default(false)
|
|
calAiPhoneNumbers CalAiPhoneNumber[]
|
|
agents Agent[]
|
|
oAuthClients OAuthClient[]
|
|
holidaySettings UserHolidaySettings?
|
|
|
|
autoOptInFeatures Boolean @default(false)
|
|
|
|
@@unique([email])
|
|
@@unique([email, username])
|
|
@@unique([username, organizationId])
|
|
@@unique([movedToProfileId])
|
|
@@index([username])
|
|
@@index([emailVerified])
|
|
@@index([identityProvider])
|
|
@@index([identityProviderId])
|
|
@@map(name: "users")
|
|
}
|
|
|
|
model NotificationsSubscriptions {
|
|
id Int @id @default(autoincrement())
|
|
userId Int
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
subscription String
|
|
|
|
@@index([userId, subscription])
|
|
}
|
|
|
|
// It holds Organization Profiles as well as User Profiles for users that have been added to an organization
|
|
model Profile {
|
|
id Int @id @default(autoincrement())
|
|
// uid allows us to set an identifier chosen by us which is helpful in migration when we create the Profile from User directly.
|
|
uid String
|
|
userId Int
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
organizationId Int
|
|
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
username String
|
|
eventTypes EventType[]
|
|
movedFromUser User? @relation("moved_to_profile")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// A user can have multiple profiles in different organizations
|
|
@@unique([userId, organizationId])
|
|
// Allow username reuse only across different organizations
|
|
@@unique([username, organizationId])
|
|
@@index([uid])
|
|
@@index([userId])
|
|
@@index([organizationId])
|
|
}
|
|
|
|
model Team {
|
|
id Int @id @default(autoincrement())
|
|
/// @zod.string.min(1)
|
|
name String
|
|
// It is unique across teams and organizations. We don't have a strong reason for organization and team slug to be conflicting, could be fixed.
|
|
// Sub-teams could have same slug across different organizations but not within the same organization.
|
|
/// @zod.string.min(1)
|
|
slug String?
|
|
logoUrl String?
|
|
calVideoLogo String?
|
|
appLogo String?
|
|
appIconLogo String?
|
|
bio String?
|
|
hideBranding Boolean @default(false)
|
|
hideTeamProfileLink Boolean @default(false)
|
|
isPrivate Boolean @default(false)
|
|
hideBookATeamMember Boolean @default(false)
|
|
members Membership[]
|
|
eventTypes EventType[]
|
|
workflows Workflow[]
|
|
createdAt DateTime @default(now())
|
|
/// @zod.import(["import { teamMetadataSchema } from '../../zod-utils'"]).custom.use(teamMetadataSchema)
|
|
metadata Json?
|
|
theme String?
|
|
rrResetInterval RRResetInterval? @default(MONTH)
|
|
rrTimestampBasis RRTimestampBasis @default(CREATED_AT)
|
|
brandColor String?
|
|
darkBrandColor String?
|
|
verifiedNumbers VerifiedNumber[]
|
|
verifiedEmails VerifiedEmail[]
|
|
bannerUrl String?
|
|
parentId Int?
|
|
parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade)
|
|
children Team[] @relation("organization")
|
|
orgUsers User[] @relation("scope")
|
|
inviteTokens VerificationToken[]
|
|
webhooks Webhook[]
|
|
timeFormat Int?
|
|
timeZone String @default("Europe/London")
|
|
weekStart String @default("Sunday")
|
|
routingForms App_RoutingForms_Form[]
|
|
apiKeys ApiKey[]
|
|
credentials Credential[]
|
|
accessCodes AccessCode[]
|
|
isOrganization Boolean @default(false)
|
|
organizationSettings OrganizationSettings?
|
|
instantMeetingTokens InstantMeetingToken[]
|
|
orgProfiles Profile[]
|
|
pendingPayment Boolean @default(false)
|
|
dsyncTeamGroupMapping DSyncTeamGroupMapping[]
|
|
isPlatform Boolean @default(false)
|
|
// Organization's OAuth clients. Organization has them but a team does not.
|
|
platformOAuthClient PlatformOAuthClient[]
|
|
// OAuth client used to create team of an organization. Team has it but organization does not.
|
|
createdByOAuthClient PlatformOAuthClient? @relation("CreatedByOAuthClient", fields: [createdByOAuthClientId], references: [id], onDelete: Cascade)
|
|
createdByOAuthClientId String?
|
|
smsLockState SMSLockState @default(UNLOCKED)
|
|
platformBilling PlatformBilling?
|
|
activeOrgWorkflows WorkflowsOnTeams[]
|
|
attributes Attribute[]
|
|
smsLockReviewedByAdmin Boolean @default(false)
|
|
// Available for Organization only
|
|
delegationCredentials DelegationCredential[]
|
|
domainWideDelegations DomainWideDelegation[]
|
|
roles Role[] // Added for Role relation
|
|
calAiPhoneNumbers CalAiPhoneNumber[]
|
|
agents Agent[]
|
|
bookingReports BookingReport[]
|
|
wrongAssignmentReports WrongAssignmentReport[]
|
|
|
|
features TeamFeatures[]
|
|
|
|
/// @zod.import(["import { intervalLimitsType } from '../../zod-utils'"]).custom.use(intervalLimitsType)
|
|
bookingLimits Json?
|
|
includeManagedEventsInLimits Boolean @default(false)
|
|
internalNotePresets InternalNotePreset[]
|
|
creditBalance CreditBalance?
|
|
organizationOnboarding OrganizationOnboarding?
|
|
|
|
// note(Lauris): if a Team has parentId it is a team, if parentId is null it is an organization, but if parentId is null and managedOrganization is set,
|
|
// it means that it is an organization managed by another organization.
|
|
managedOrganization ManagedOrganization? @relation("ManagedOrganization")
|
|
managedOrganizations ManagedOrganization[] @relation("ManagerOrganization")
|
|
filterSegments FilterSegment[]
|
|
|
|
teamBilling TeamBilling? @relation("TeamBilling")
|
|
organizationBilling OrganizationBilling? @relation("OrganizationBilling")
|
|
integrationAttributeSyncs IntegrationAttributeSync[]
|
|
|
|
autoOptInFeatures Boolean @default(false)
|
|
|
|
// Monthly proration tracking
|
|
seatChangeLogs SeatChangeLog[]
|
|
monthlyProrations MonthlyProration[]
|
|
|
|
@@unique([slug, parentId])
|
|
@@index([parentId])
|
|
}
|
|
|
|
model CreditBalance {
|
|
id String @id @default(uuid())
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int? @unique
|
|
// user credit balances will be supported in the future
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int? @unique
|
|
additionalCredits Int @default(0)
|
|
limitReachedAt DateTime?
|
|
warningSentAt DateTime?
|
|
expenseLogs CreditExpenseLog[]
|
|
purchaseLogs CreditPurchaseLog[]
|
|
}
|
|
|
|
model CreditPurchaseLog {
|
|
id String @id @default(uuid())
|
|
creditBalanceId String
|
|
creditBalance CreditBalance @relation(fields: [creditBalanceId], references: [id], onDelete: Cascade)
|
|
credits Int
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
enum CreditUsageType {
|
|
SMS
|
|
CAL_AI_PHONE_CALL
|
|
}
|
|
|
|
model CreditExpenseLog {
|
|
id String @id @default(uuid())
|
|
creditBalanceId String
|
|
creditBalance CreditBalance @relation(fields: [creditBalanceId], references: [id], onDelete: Cascade)
|
|
bookingUid String?
|
|
booking Booking? @relation(fields: [bookingUid], references: [uid], onDelete: Cascade)
|
|
credits Int?
|
|
creditType CreditType
|
|
date DateTime
|
|
smsSid String?
|
|
smsSegments Int?
|
|
phoneNumber String?
|
|
email String?
|
|
callDuration Int?
|
|
creditFor CreditUsageType?
|
|
externalRef String? @unique
|
|
|
|
// @@partial_index([bookingUid])
|
|
}
|
|
|
|
enum CreditType {
|
|
MONTHLY
|
|
ADDITIONAL
|
|
}
|
|
|
|
model OrganizationSettings {
|
|
id Int @id @default(autoincrement())
|
|
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
organizationId Int @unique
|
|
isOrganizationConfigured Boolean @default(false)
|
|
// It decides if new organization members can be auto-accepted or not
|
|
isOrganizationVerified Boolean @default(false)
|
|
// It is a domain e.g "acme.com". Any email with this domain might be auto-accepted
|
|
// Also, it is the domain to which the organization profile is redirected.
|
|
orgAutoAcceptEmail String
|
|
lockEventTypeCreationForUsers Boolean @default(false)
|
|
adminGetsNoSlotsNotification Boolean @default(false)
|
|
// It decides if instance ADMIN has reviewed the organization or not.
|
|
// It is used to allow super sensitive operations like 'impersonation of Org members by Org admin'
|
|
isAdminReviewed Boolean @default(false)
|
|
dSyncData DSyncData?
|
|
isAdminAPIEnabled Boolean @default(false)
|
|
allowSEOIndexing Boolean @default(false)
|
|
orgProfileRedirectsToVerifiedDomain Boolean @default(false)
|
|
disablePhoneOnlySMSNotifications Boolean @default(false)
|
|
disableAutofillOnBookingPage Boolean @default(false)
|
|
orgAutoJoinOnSignup Boolean @default(true)
|
|
|
|
disableAttendeeConfirmationEmail Boolean @default(false)
|
|
disableAttendeeCancellationEmail Boolean @default(false)
|
|
disableAttendeeRescheduledEmail Boolean @default(false)
|
|
disableAttendeeRequestEmail Boolean @default(false)
|
|
disableAttendeeReassignedEmail Boolean @default(false)
|
|
disableAttendeeAwaitingPaymentEmail Boolean @default(false)
|
|
disableAttendeeRescheduleRequestEmail Boolean @default(false)
|
|
disableAttendeeLocationChangeEmail Boolean @default(false)
|
|
disableAttendeeNewEventEmail Boolean @default(false)
|
|
}
|
|
|
|
enum MembershipRole {
|
|
MEMBER
|
|
ADMIN
|
|
OWNER
|
|
}
|
|
|
|
model Membership {
|
|
id Int @id @default(autoincrement())
|
|
teamId Int
|
|
userId Int
|
|
accepted Boolean @default(false)
|
|
role MembershipRole
|
|
customRoleId String?
|
|
customRole Role? @relation(fields: [customRoleId], references: [id])
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
disableImpersonation Boolean @default(false)
|
|
AttributeToUser AttributeToUser[]
|
|
createdAt DateTime? @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
Host Host[]
|
|
|
|
@@unique([userId, teamId])
|
|
@@index([teamId])
|
|
@@index([userId])
|
|
@@index([accepted])
|
|
@@index([role])
|
|
@@index([customRoleId])
|
|
}
|
|
|
|
model VerificationToken {
|
|
id Int @id @default(autoincrement())
|
|
identifier String
|
|
token String @unique
|
|
expires DateTime
|
|
expiresInDays Int?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id])
|
|
secondaryEmailId Int?
|
|
secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id])
|
|
|
|
@@unique([identifier, token])
|
|
@@index([token])
|
|
@@index([teamId])
|
|
@@index([secondaryEmailId])
|
|
}
|
|
|
|
model InstantMeetingToken {
|
|
id Int @id @default(autoincrement())
|
|
token String @unique
|
|
expires DateTime
|
|
teamId Int
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
bookingId Int? @unique
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([token])
|
|
}
|
|
|
|
model BookingReference {
|
|
id Int @id @default(autoincrement())
|
|
/// @zod.string.min(1)
|
|
type String
|
|
/// @zod.string.min(1)
|
|
uid String
|
|
meetingId String?
|
|
thirdPartyRecurringEventId String?
|
|
meetingPassword String?
|
|
meetingUrl String?
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
bookingId Int?
|
|
externalCalendarId String?
|
|
deleted Boolean?
|
|
|
|
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: SetNull)
|
|
credentialId Int?
|
|
delegationCredential DelegationCredential? @relation(fields: [delegationCredentialId], references: [id], onDelete: SetNull)
|
|
delegationCredentialId String?
|
|
domainWideDelegation DomainWideDelegation? @relation(fields: [domainWideDelegationCredentialId], references: [id], onDelete: SetNull)
|
|
domainWideDelegationCredentialId String?
|
|
|
|
@@index([bookingId])
|
|
@@index([type])
|
|
@@index([uid])
|
|
}
|
|
|
|
model Attendee {
|
|
id Int @id @default(autoincrement())
|
|
email String
|
|
name String
|
|
timeZone String
|
|
phoneNumber String?
|
|
locale String? @default("en")
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
bookingId Int?
|
|
bookingSeat BookingSeat?
|
|
noShow Boolean? @default(false)
|
|
|
|
@@index([email])
|
|
@@index([bookingId])
|
|
@@index([email, bookingId])
|
|
}
|
|
|
|
enum BookingStatus {
|
|
CANCELLED @map("cancelled")
|
|
ACCEPTED @map("accepted")
|
|
REJECTED @map("rejected")
|
|
PENDING @map("pending")
|
|
AWAITING_HOST @map("awaiting_host")
|
|
}
|
|
|
|
model Booking {
|
|
id Int @id @default(autoincrement())
|
|
uid String @unique
|
|
// (optional) UID based on slot start/end time & email against duplicates
|
|
idempotencyKey String? @unique
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int?
|
|
// User's email at the time of booking
|
|
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
|
|
userPrimaryEmail String?
|
|
references BookingReference[]
|
|
eventType EventType? @relation(fields: [eventTypeId], references: [id])
|
|
eventTypeId Int?
|
|
title String
|
|
description String?
|
|
customInputs Json?
|
|
/// @zod.import(["import { bookingResponses } from '../../zod-utils'"]).custom.use(bookingResponses)
|
|
responses Json?
|
|
startTime DateTime
|
|
endTime DateTime
|
|
attendees Attendee[]
|
|
location String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
status BookingStatus @default(ACCEPTED)
|
|
paid Boolean @default(false)
|
|
payment Payment[]
|
|
destinationCalendar DestinationCalendar? @relation(fields: [destinationCalendarId], references: [id])
|
|
destinationCalendarId Int?
|
|
cancellationReason String?
|
|
rejectionReason String?
|
|
reassignReason String?
|
|
reassignBy User? @relation("reassignByUser", fields: [reassignById], references: [id])
|
|
reassignById Int?
|
|
dynamicEventSlugRef String?
|
|
dynamicGroupSlugRef String?
|
|
rescheduled Boolean?
|
|
fromReschedule String?
|
|
recurringEventId String?
|
|
smsReminderNumber String?
|
|
workflowReminders WorkflowReminder[]
|
|
scheduledJobs String[] // scheduledJobs is deprecated, please use scheduledTriggers instead
|
|
seatsReferences BookingSeat[]
|
|
/// @zod.import(["import { bookingMetadataSchema } from '../../zod-utils'"]).custom.use(bookingMetadataSchema)
|
|
metadata Json?
|
|
isRecorded Boolean @default(false)
|
|
iCalUID String? @default("")
|
|
iCalSequence Int @default(0)
|
|
instantMeetingToken InstantMeetingToken?
|
|
rating Int?
|
|
ratingFeedback String?
|
|
noShowHost Boolean? @default(false)
|
|
scheduledTriggers WebhookScheduledTriggers[]
|
|
oneTimePassword String? @unique @default(uuid())
|
|
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
|
|
cancelledBy String?
|
|
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
|
|
rescheduledBy String?
|
|
// Ah, made a typo here. Should have been routedFromRoutingFormRe"s"ponse. Live with it :(
|
|
routedFromRoutingFormReponse App_RoutingForms_FormResponse?
|
|
assignmentReason AssignmentReason[]
|
|
internalNote BookingInternalNote[]
|
|
creationSource CreationSource?
|
|
tracking Tracking?
|
|
routingFormResponses RoutingFormResponseDenormalized[]
|
|
expenseLogs CreditExpenseLog[]
|
|
report BookingReport?
|
|
wrongAssignmentReports WrongAssignmentReport[]
|
|
routingTrace RoutingTrace?
|
|
|
|
// @@partial_index([reassignById])
|
|
|
|
@@index([eventTypeId])
|
|
@@index([userId])
|
|
@@index([destinationCalendarId])
|
|
@@index([recurringEventId])
|
|
@@index([uid])
|
|
@@index([status])
|
|
@@index([startTime, endTime, status])
|
|
@@index([fromReschedule])
|
|
@@index([userId, endTime])
|
|
@@index([userId, status, startTime])
|
|
@@index([eventTypeId, status])
|
|
@@index([userId, createdAt])
|
|
}
|
|
|
|
model Tracking {
|
|
id Int @id @default(autoincrement())
|
|
bookingId Int
|
|
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
utm_source String?
|
|
utm_medium String?
|
|
utm_campaign String?
|
|
utm_term String?
|
|
utm_content String?
|
|
|
|
@@unique([bookingId])
|
|
}
|
|
|
|
model Schedule {
|
|
id Int @id @default(autoincrement())
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int
|
|
eventType EventType[]
|
|
instantMeetingEvents EventType[] @relation("InstantMeetingSchedule")
|
|
restrictionSchedule EventType[] @relation("restrictionSchedule")
|
|
name String
|
|
timeZone String?
|
|
availability Availability[]
|
|
Host Host[]
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model Availability {
|
|
id Int @id @default(autoincrement())
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int?
|
|
eventType EventType? @relation(fields: [eventTypeId], references: [id])
|
|
eventTypeId Int?
|
|
days Int[]
|
|
startTime DateTime @db.Time
|
|
endTime DateTime @db.Time
|
|
date DateTime? @db.Date
|
|
Schedule Schedule? @relation(fields: [scheduleId], references: [id])
|
|
scheduleId Int?
|
|
|
|
@@index([userId])
|
|
@@index([eventTypeId])
|
|
@@index([scheduleId])
|
|
}
|
|
|
|
model SelectedCalendar {
|
|
id String @id @default(uuid())
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int
|
|
integration String
|
|
externalId String
|
|
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
|
|
credentialId Int?
|
|
createdAt DateTime? @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
// Used to identify a watched calendar channel in Google Calendar
|
|
// @deprecated use channelId instead
|
|
googleChannelId String?
|
|
|
|
// @deprecated use channelKind instead
|
|
googleChannelKind String?
|
|
|
|
// @deprecated use channelResourceId instead
|
|
googleChannelResourceId String?
|
|
|
|
// @deprecated use channelResourceUri instead
|
|
googleChannelResourceUri String?
|
|
|
|
// @deprecated use channelExpoiration instead
|
|
googleChannelExpiration String?
|
|
|
|
// Used to identify a watched calendar
|
|
channelId String?
|
|
channelKind String?
|
|
channelResourceId String?
|
|
channelResourceUri String?
|
|
channelExpiration DateTime? @db.Timestamp(3)
|
|
|
|
// Used to calendar cache and sync
|
|
syncSubscribedAt DateTime? @db.Timestamp(3)
|
|
syncSubscribedErrorAt DateTime? @db.Timestamp(3)
|
|
syncSubscribedErrorCount Int @default(0)
|
|
syncToken String?
|
|
syncedAt DateTime?
|
|
syncErrorAt DateTime?
|
|
syncErrorCount Int? @default(0)
|
|
|
|
delegationCredential DelegationCredential? @relation(fields: [delegationCredentialId], references: [id], onDelete: Cascade)
|
|
delegationCredentialId String?
|
|
|
|
// Deprecated and unused: Use delegationCredential instead
|
|
domainWideDelegationCredential DomainWideDelegation? @relation(fields: [domainWideDelegationCredentialId], references: [id], onDelete: Cascade)
|
|
domainWideDelegationCredentialId String?
|
|
error String?
|
|
lastErrorAt DateTime?
|
|
watchAttempts Int @default(0)
|
|
unwatchAttempts Int @default(0)
|
|
maxAttempts Int @default(3)
|
|
|
|
eventTypeId Int?
|
|
eventType EventType? @relation(fields: [eventTypeId], references: [id])
|
|
|
|
calendarCacheEvents CalendarCacheEvent[]
|
|
|
|
// It could still allow multiple user-level(eventTypeId is null) selected calendars for same userId, integration, externalId because NULL is not equal to NULL
|
|
// We currently ensure uniqueness by checking for the existence of the record before creating a new one
|
|
// Think about introducing a generated unique key ${userId}_${integration}_${externalId}_${eventTypeId}
|
|
@@unique([userId, integration, externalId, eventTypeId])
|
|
@@unique([googleChannelId, eventTypeId])
|
|
@@index([userId])
|
|
@@index([externalId])
|
|
@@index([eventTypeId])
|
|
@@index([credentialId])
|
|
@@index([channelId])
|
|
// Composite indices to optimize calendar-cache queries
|
|
@@index([integration, googleChannelExpiration, error, watchAttempts, maxAttempts], name: "SelectedCalendar_watch_idx")
|
|
@@index([integration, googleChannelExpiration, error, unwatchAttempts, maxAttempts], name: "SelectedCalendar_unwatch_idx")
|
|
}
|
|
|
|
enum EventTypeCustomInputType {
|
|
TEXT @map("text")
|
|
TEXTLONG @map("textLong")
|
|
NUMBER @map("number")
|
|
BOOL @map("bool")
|
|
RADIO @map("radio")
|
|
PHONE @map("phone")
|
|
}
|
|
|
|
model EventTypeCustomInput {
|
|
id Int @id @default(autoincrement())
|
|
eventTypeId Int
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
label String
|
|
type EventTypeCustomInputType
|
|
/// @zod.import(["import { customInputOptionSchema } from '../../zod-utils'"]).custom.use(customInputOptionSchema)
|
|
options Json?
|
|
required Boolean
|
|
placeholder String @default("")
|
|
|
|
@@index([eventTypeId])
|
|
}
|
|
|
|
model ResetPasswordRequest {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
email String
|
|
expires DateTime
|
|
}
|
|
|
|
enum ReminderType {
|
|
PENDING_BOOKING_CONFIRMATION
|
|
}
|
|
|
|
model ReminderMail {
|
|
id Int @id @default(autoincrement())
|
|
referenceId Int
|
|
reminderType ReminderType
|
|
elapsedMinutes Int
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([referenceId])
|
|
@@index([reminderType])
|
|
}
|
|
|
|
model Payment {
|
|
id Int @id @default(autoincrement())
|
|
uid String @unique
|
|
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
|
|
appId String?
|
|
bookingId Int
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
amount Int
|
|
fee Int
|
|
currency String
|
|
success Boolean
|
|
refunded Boolean
|
|
data Json
|
|
externalId String @unique
|
|
paymentOption PaymentOption? @default(ON_BOOKING)
|
|
|
|
@@index([bookingId])
|
|
@@index([externalId])
|
|
}
|
|
|
|
enum PaymentOption {
|
|
ON_BOOKING
|
|
HOLD
|
|
}
|
|
|
|
enum WebhookTriggerEvents {
|
|
BOOKING_CREATED
|
|
BOOKING_PAYMENT_INITIATED
|
|
BOOKING_PAID
|
|
BOOKING_RESCHEDULED
|
|
BOOKING_REQUESTED
|
|
BOOKING_CANCELLED
|
|
BOOKING_REJECTED
|
|
BOOKING_NO_SHOW_UPDATED
|
|
FORM_SUBMITTED
|
|
MEETING_ENDED
|
|
MEETING_STARTED
|
|
RECORDING_READY
|
|
INSTANT_MEETING
|
|
RECORDING_TRANSCRIPTION_GENERATED
|
|
OOO_CREATED
|
|
AFTER_HOSTS_CAL_VIDEO_NO_SHOW
|
|
AFTER_GUESTS_CAL_VIDEO_NO_SHOW
|
|
FORM_SUBMITTED_NO_EVENT
|
|
ROUTING_FORM_FALLBACK_HIT
|
|
DELEGATION_CREDENTIAL_ERROR
|
|
WRONG_ASSIGNMENT_REPORT
|
|
}
|
|
|
|
model Webhook {
|
|
id String @id @unique
|
|
userId Int?
|
|
teamId Int?
|
|
eventTypeId Int?
|
|
platformOAuthClientId String?
|
|
/// @zod.string.url()
|
|
subscriberUrl String
|
|
payloadTemplate String?
|
|
createdAt DateTime @default(now())
|
|
active Boolean @default(true)
|
|
eventTriggers WebhookTriggerEvents[]
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
platformOAuthClient PlatformOAuthClient? @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
|
|
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
|
|
appId String?
|
|
secret String?
|
|
platform Boolean @default(false)
|
|
scheduledTriggers WebhookScheduledTriggers[]
|
|
time Int?
|
|
timeUnit TimeUnit?
|
|
version String @default("2021-10-20")
|
|
|
|
@@unique([userId, subscriberUrl], name: "courseIdentifier")
|
|
@@unique([platformOAuthClientId, subscriberUrl], name: "oauthclientwebhook")
|
|
@@index([active])
|
|
}
|
|
|
|
model Impersonations {
|
|
id Int @id @default(autoincrement())
|
|
createdAt DateTime @default(now())
|
|
impersonatedUser User @relation("impersonated_user", fields: [impersonatedUserId], references: [id], onDelete: Cascade)
|
|
impersonatedBy User @relation("impersonated_by_user", fields: [impersonatedById], references: [id], onDelete: Cascade)
|
|
impersonatedUserId Int
|
|
impersonatedById Int
|
|
|
|
@@index([impersonatedUserId])
|
|
@@index([impersonatedById])
|
|
}
|
|
|
|
model ApiKey {
|
|
id String @id @unique @default(cuid())
|
|
userId Int
|
|
teamId Int?
|
|
note String?
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime?
|
|
lastUsedAt DateTime?
|
|
hashedKey String @unique()
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
app App? @relation(fields: [appId], references: [slug], onDelete: Cascade)
|
|
appId String?
|
|
rateLimits RateLimit[]
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model RateLimit {
|
|
id String @id @default(uuid())
|
|
name String
|
|
apiKeyId String
|
|
ttl Int
|
|
limit Int
|
|
blockDuration Int
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([apiKeyId])
|
|
}
|
|
|
|
model HashedLink {
|
|
id Int @id @default(autoincrement())
|
|
link String @unique
|
|
eventTypeId Int
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
expiresAt DateTime?
|
|
maxUsageCount Int @default(1)
|
|
usageCount Int @default(0)
|
|
|
|
@@index([eventTypeId])
|
|
}
|
|
|
|
model Account {
|
|
id String @id @default(cuid())
|
|
userId Int
|
|
type String
|
|
provider String
|
|
providerAccountId String
|
|
providerEmail String?
|
|
refresh_token String? @db.Text
|
|
access_token String? @db.Text
|
|
expires_at Int?
|
|
token_type String?
|
|
scope String?
|
|
id_token String? @db.Text
|
|
session_state String?
|
|
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([provider, providerAccountId])
|
|
@@index([userId])
|
|
@@index([type])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(cuid())
|
|
sessionToken String @unique
|
|
userId Int
|
|
expires DateTime
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
enum AppCategories {
|
|
calendar
|
|
messaging
|
|
other
|
|
payment
|
|
video // deprecated, please use 'conferencing' instead
|
|
web3 // deprecated, we should no longer have any web3 apps
|
|
automation
|
|
analytics
|
|
// Wherever video is in use, conferencing should also be used for legacy apps can have it.
|
|
conferencing
|
|
crm
|
|
}
|
|
|
|
model App {
|
|
// The slug for the app store public page inside `/apps/[slug]`
|
|
slug String @id @unique
|
|
// The directory name for `/packages/app-store/[dirName]`
|
|
dirName String @unique
|
|
// Needed API Keys
|
|
keys Json?
|
|
// One or multiple categories to which this app belongs
|
|
categories AppCategories[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
credentials Credential[]
|
|
payments Payment[]
|
|
Webhook Webhook[]
|
|
ApiKey ApiKey[]
|
|
enabled Boolean @default(false)
|
|
|
|
@@index([enabled])
|
|
}
|
|
|
|
model App_RoutingForms_Form {
|
|
id String @id @default(cuid())
|
|
description String?
|
|
position Int @default(0)
|
|
routes Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
name String
|
|
fields Json?
|
|
user User @relation("routing-form", fields: [userId], references: [id], onDelete: Cascade)
|
|
updatedBy User? @relation("updated-routing-form", fields: [updatedById], references: [id], onDelete: SetNull)
|
|
updatedById Int?
|
|
// This is the user who created the form and also the user who has read-write access to the form
|
|
// If teamId is set, the members of the team would also have access to form readOnly or read-write depending on their permission level as team member.
|
|
userId Int
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
responses App_RoutingForms_FormResponse[]
|
|
queuedResponses App_RoutingForms_QueuedFormResponse[]
|
|
disabled Boolean @default(false)
|
|
/// @zod.import(["import { RoutingFormSettings } from '../../zod-utils'"]).custom.use(RoutingFormSettings)
|
|
settings Json?
|
|
incompleteBookingActions App_RoutingForms_IncompleteBookingActions[]
|
|
workflows WorkflowsOnRoutingForms[]
|
|
wrongAssignmentReports WrongAssignmentReport[]
|
|
|
|
@@index([userId])
|
|
@@index([disabled])
|
|
}
|
|
|
|
model App_RoutingForms_FormResponse {
|
|
id Int @id @default(autoincrement())
|
|
uuid String? @default(uuid())
|
|
formFillerId String @default(cuid())
|
|
form App_RoutingForms_Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
|
formId String
|
|
response Json
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
|
|
routedToBookingUid String? @unique
|
|
// We should not cascade delete the booking, because we want to keep the form response even if the routedToBooking is deleted
|
|
routedToBooking Booking? @relation(fields: [routedToBookingUid], references: [uid])
|
|
chosenRouteId String?
|
|
routingFormResponseFields RoutingFormResponseField[]
|
|
routingFormResponses RoutingFormResponseDenormalized[]
|
|
queuedFormResponse App_RoutingForms_QueuedFormResponse?
|
|
pendingRoutingTrace PendingRoutingTrace?
|
|
routingTrace RoutingTrace?
|
|
|
|
@@unique([formFillerId, formId])
|
|
@@index([formFillerId])
|
|
@@index([formId])
|
|
@@index([formId, createdAt])
|
|
@@index([routedToBookingUid])
|
|
}
|
|
|
|
model App_RoutingForms_QueuedFormResponse {
|
|
id String @id @default(cuid())
|
|
form App_RoutingForms_Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
|
formId String
|
|
response Json
|
|
chosenRouteId String?
|
|
fallbackAction Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime? @updatedAt
|
|
actualResponseId Int? @unique
|
|
actualResponse App_RoutingForms_FormResponse? @relation(fields: [actualResponseId], references: [id], onDelete: Cascade)
|
|
pendingRoutingTrace PendingRoutingTrace?
|
|
routingTrace RoutingTrace?
|
|
}
|
|
|
|
model RoutingFormResponseField {
|
|
id Int @id @default(autoincrement())
|
|
responseId Int
|
|
fieldId String
|
|
valueString String?
|
|
valueNumber Decimal?
|
|
valueStringArray String[]
|
|
response App_RoutingForms_FormResponse @relation(fields: [responseId], references: [id], map: "RoutingFormResponseField_response_fkey", onDelete: Cascade)
|
|
denormalized RoutingFormResponseDenormalized @relation("DenormalizedResponseToFields", fields: [responseId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([responseId])
|
|
@@index([fieldId])
|
|
@@index([valueNumber])
|
|
@@index([valueStringArray], type: Gin)
|
|
}
|
|
|
|
view RoutingFormResponse {
|
|
id Int @unique
|
|
response Json
|
|
responseLowercase Json
|
|
formId String
|
|
formName String
|
|
formTeamId Int?
|
|
formUserId Int?
|
|
bookingUid String?
|
|
bookingStatus BookingStatus?
|
|
bookingStatusOrder Int?
|
|
bookingCreatedAt DateTime?
|
|
bookingAttendees Json? // Array of {timeZone: string, email: string}
|
|
bookingUserId Int?
|
|
bookingUserName String?
|
|
bookingUserEmail String?
|
|
bookingUserAvatarUrl String?
|
|
bookingAssignmentReason String?
|
|
bookingAssignmentReasonLowercase String?
|
|
bookingStartTime DateTime?
|
|
bookingEndTime DateTime?
|
|
createdAt DateTime
|
|
utm_source String?
|
|
utm_medium String?
|
|
utm_campaign String?
|
|
utm_term String?
|
|
utm_content String?
|
|
}
|
|
|
|
model RoutingFormResponseDenormalized {
|
|
id Int @id
|
|
uuid String?
|
|
formId String
|
|
formName String
|
|
formTeamId Int?
|
|
formUserId Int
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: SetNull)
|
|
bookingUid String?
|
|
bookingId Int?
|
|
bookingStatus BookingStatus?
|
|
bookingStatusOrder Int?
|
|
bookingCreatedAt DateTime? @db.Timestamp(3)
|
|
bookingStartTime DateTime? @db.Timestamp(3)
|
|
bookingEndTime DateTime? @db.Timestamp(3)
|
|
bookingUserId Int?
|
|
bookingUserName String?
|
|
bookingUserEmail String?
|
|
bookingUserAvatarUrl String?
|
|
bookingAssignmentReason String?
|
|
eventTypeId Int?
|
|
eventTypeParentId Int?
|
|
eventTypeSchedulingType String?
|
|
createdAt DateTime @db.Timestamp(3)
|
|
utm_source String?
|
|
utm_medium String?
|
|
utm_campaign String?
|
|
utm_term String?
|
|
utm_content String?
|
|
response App_RoutingForms_FormResponse @relation(fields: [id], references: [id], onDelete: Cascade)
|
|
fields RoutingFormResponseField[] @relation("DenormalizedResponseToFields")
|
|
|
|
@@index([formId])
|
|
@@index([formTeamId])
|
|
@@index([formUserId])
|
|
@@index([formId, createdAt])
|
|
@@index([bookingId])
|
|
@@index([bookingUserId])
|
|
@@index([eventTypeId, eventTypeParentId])
|
|
}
|
|
|
|
model Feedback {
|
|
id Int @id @default(autoincrement())
|
|
date DateTime @default(now())
|
|
userId Int
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
rating String
|
|
comment String?
|
|
|
|
@@index([userId])
|
|
@@index([rating])
|
|
}
|
|
|
|
enum WorkflowTriggerEvents {
|
|
BEFORE_EVENT
|
|
EVENT_CANCELLED
|
|
NEW_EVENT
|
|
AFTER_EVENT
|
|
RESCHEDULE_EVENT
|
|
AFTER_HOSTS_CAL_VIDEO_NO_SHOW
|
|
AFTER_GUESTS_CAL_VIDEO_NO_SHOW
|
|
FORM_SUBMITTED
|
|
FORM_SUBMITTED_NO_EVENT
|
|
BOOKING_REJECTED
|
|
BOOKING_REQUESTED
|
|
BOOKING_PAYMENT_INITIATED
|
|
BOOKING_PAID
|
|
BOOKING_NO_SHOW_UPDATED
|
|
}
|
|
|
|
enum WorkflowActions {
|
|
EMAIL_HOST
|
|
EMAIL_ATTENDEE
|
|
SMS_ATTENDEE
|
|
SMS_NUMBER
|
|
EMAIL_ADDRESS
|
|
WHATSAPP_ATTENDEE
|
|
WHATSAPP_NUMBER
|
|
CAL_AI_PHONE_CALL
|
|
}
|
|
|
|
enum WorkflowType {
|
|
EVENT_TYPE
|
|
ROUTING_FORM
|
|
}
|
|
|
|
model WorkflowStep {
|
|
id Int @id @default(autoincrement())
|
|
stepNumber Int
|
|
action WorkflowActions
|
|
workflowId Int
|
|
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
|
sendTo String?
|
|
reminderBody String?
|
|
emailSubject String?
|
|
template WorkflowTemplates @default(REMINDER)
|
|
workflowReminders WorkflowReminder[]
|
|
numberRequired Boolean?
|
|
sender String?
|
|
numberVerificationPending Boolean @default(true)
|
|
includeCalendarEvent Boolean @default(false)
|
|
verifiedAt DateTime?
|
|
agentId String? @unique
|
|
agent Agent? @relation(fields: [agentId], references: [id], onDelete: SetNull)
|
|
|
|
inboundAgentId String? @unique
|
|
inboundAgent Agent? @relation("WorkflowStepInboundAgent", fields: [inboundAgentId], references: [id], onDelete: SetNull)
|
|
|
|
autoTranslateEnabled Boolean @default(false)
|
|
sourceLocale String?
|
|
translations WorkflowStepTranslation[]
|
|
|
|
@@index([workflowId])
|
|
}
|
|
|
|
model Workflow {
|
|
id Int @id @default(autoincrement())
|
|
position Int @default(0)
|
|
name String
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
activeOn WorkflowsOnEventTypes[]
|
|
activeOnTeams WorkflowsOnTeams[]
|
|
activeOnRoutingForms WorkflowsOnRoutingForms[]
|
|
isActiveOnAll Boolean @default(false)
|
|
trigger WorkflowTriggerEvents
|
|
time Int?
|
|
timeUnit TimeUnit?
|
|
steps WorkflowStep[]
|
|
type WorkflowType @default(EVENT_TYPE)
|
|
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model AIPhoneCallConfiguration {
|
|
id Int @id @default(autoincrement())
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
eventTypeId Int
|
|
templateType String @default("CUSTOM_TEMPLATE")
|
|
schedulerName String?
|
|
generalPrompt String?
|
|
yourPhoneNumber String
|
|
numberToCall String
|
|
guestName String?
|
|
guestEmail String?
|
|
guestCompany String?
|
|
enabled Boolean @default(false)
|
|
beginMessage String?
|
|
llmId String?
|
|
|
|
@@unique([eventTypeId])
|
|
@@index([eventTypeId])
|
|
}
|
|
|
|
model WorkflowsOnEventTypes {
|
|
id Int @id @default(autoincrement())
|
|
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
|
workflowId Int
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
eventTypeId Int
|
|
|
|
@@unique([workflowId, eventTypeId])
|
|
@@index([workflowId])
|
|
@@index([eventTypeId])
|
|
}
|
|
|
|
model WorkflowsOnRoutingForms {
|
|
id Int @id @default(autoincrement())
|
|
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
|
workflowId Int
|
|
routingForm App_RoutingForms_Form @relation(fields: [routingFormId], references: [id], onDelete: Cascade)
|
|
routingFormId String
|
|
|
|
@@unique([workflowId, routingFormId])
|
|
@@index([workflowId])
|
|
@@index([routingFormId])
|
|
}
|
|
|
|
model WorkflowsOnTeams {
|
|
id Int @id @default(autoincrement())
|
|
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
|
workflowId Int
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int
|
|
|
|
@@unique([workflowId, teamId])
|
|
@@index([workflowId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model Deployment {
|
|
/// This is a single row table, so we use a fixed id
|
|
id Int @id @default(1)
|
|
logo String?
|
|
/// @zod.import(["import { DeploymentTheme } from '../../zod-utils'"]).custom.use(DeploymentTheme)
|
|
theme Json?
|
|
licenseKey String?
|
|
// We encrypt the signature token in the deployment table with the current calendso encryption key
|
|
signatureTokenEncrypted String?
|
|
agreedLicenseAt DateTime?
|
|
}
|
|
|
|
enum TimeUnit {
|
|
DAY @map("day")
|
|
HOUR @map("hour")
|
|
MINUTE @map("minute")
|
|
}
|
|
|
|
model WorkflowReminder {
|
|
id Int @id @default(autoincrement())
|
|
uuid String? @unique @default(uuid())
|
|
bookingUid String?
|
|
booking Booking? @relation(fields: [bookingUid], references: [uid])
|
|
method WorkflowMethods
|
|
scheduledDate DateTime
|
|
referenceId String? @unique
|
|
scheduled Boolean
|
|
workflowStepId Int?
|
|
workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id], onDelete: Cascade)
|
|
cancelled Boolean?
|
|
seatReferenceId String?
|
|
isMandatoryReminder Boolean? @default(false)
|
|
retryCount Int @default(0)
|
|
|
|
@@index([bookingUid])
|
|
@@index([workflowStepId])
|
|
@@index([seatReferenceId])
|
|
@@index([method, scheduled, scheduledDate])
|
|
@@index([cancelled, scheduledDate])
|
|
}
|
|
|
|
model WebhookScheduledTriggers {
|
|
id Int @id @default(autoincrement())
|
|
jobName String? // jobName is deprecated, not needed when webhook and booking is set
|
|
subscriberUrl String
|
|
payload String
|
|
startAfter DateTime
|
|
retryCount Int @default(0)
|
|
createdAt DateTime? @default(now())
|
|
appId String?
|
|
webhookId String?
|
|
webhook Webhook? @relation(fields: [webhookId], references: [id], onDelete: Cascade)
|
|
bookingId Int?
|
|
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
|
|
// @@partial_index([bookingId])
|
|
}
|
|
|
|
enum WorkflowTemplates {
|
|
REMINDER
|
|
CUSTOM
|
|
CANCELLED
|
|
RESCHEDULED
|
|
COMPLETED
|
|
RATING
|
|
}
|
|
|
|
enum WorkflowMethods {
|
|
EMAIL
|
|
SMS
|
|
WHATSAPP
|
|
AI_PHONE_CALL
|
|
}
|
|
|
|
model BookingSeat {
|
|
id Int @id @default(autoincrement())
|
|
referenceUid String @unique
|
|
bookingId Int
|
|
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
attendeeId Int @unique
|
|
attendee Attendee @relation(fields: [attendeeId], references: [id], onDelete: Cascade)
|
|
/// @zod.import(["import { bookingSeatDataSchema } from '../../zod-utils'"]).custom.use(bookingSeatDataSchema)
|
|
data Json?
|
|
metadata Json?
|
|
|
|
@@index([bookingId])
|
|
@@index([attendeeId])
|
|
}
|
|
|
|
model VerifiedNumber {
|
|
id Int @id @default(autoincrement())
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
phoneNumber String
|
|
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model VerifiedEmail {
|
|
id Int @id @default(autoincrement())
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
email String
|
|
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model Feature {
|
|
// The feature slug, ex: 'v2-workflows'
|
|
slug String @id @unique
|
|
// If the feature is currently enabled
|
|
enabled Boolean @default(false)
|
|
// A short description of the feature
|
|
description String?
|
|
// The type of feature flag
|
|
type FeatureType? @default(RELEASE)
|
|
// If the flag is considered stale
|
|
stale Boolean? @default(false)
|
|
lastUsedAt DateTime?
|
|
createdAt DateTime? @default(now())
|
|
updatedAt DateTime? @default(now()) @updatedAt
|
|
updatedBy Int?
|
|
users UserFeatures[]
|
|
teams TeamFeatures[]
|
|
|
|
@@index([enabled])
|
|
@@index([stale])
|
|
}
|
|
|
|
model UserFeatures {
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int
|
|
feature Feature @relation(fields: [featureId], references: [slug], onDelete: Cascade)
|
|
featureId String
|
|
enabled Boolean
|
|
assignedAt DateTime @default(now())
|
|
assignedBy String
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@id([userId, featureId])
|
|
@@index([userId, featureId])
|
|
}
|
|
|
|
model TeamFeatures {
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int
|
|
feature Feature @relation(fields: [featureId], references: [slug], onDelete: Cascade)
|
|
featureId String
|
|
enabled Boolean
|
|
assignedAt DateTime @default(now())
|
|
assignedBy String
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@id([teamId, featureId])
|
|
@@index([teamId, featureId])
|
|
}
|
|
|
|
enum FeatureType {
|
|
RELEASE
|
|
EXPERIMENT
|
|
OPERATIONAL
|
|
KILL_SWITCH
|
|
PERMISSION
|
|
}
|
|
|
|
enum RRResetInterval {
|
|
MONTH
|
|
DAY
|
|
}
|
|
|
|
enum RRTimestampBasis {
|
|
CREATED_AT
|
|
START_TIME
|
|
}
|
|
|
|
model SelectedSlots {
|
|
id Int @id @default(autoincrement())
|
|
eventTypeId Int
|
|
userId Int
|
|
slotUtcStartDate DateTime
|
|
slotUtcEndDate DateTime
|
|
uid String
|
|
releaseAt DateTime
|
|
isSeat Boolean @default(false)
|
|
|
|
@@unique(fields: [userId, slotUtcStartDate, slotUtcEndDate, uid], name: "selectedSlotUnique")
|
|
}
|
|
|
|
enum OAuthClientType {
|
|
CONFIDENTIAL @map("confidential")
|
|
PUBLIC @map("public")
|
|
}
|
|
|
|
enum OAuthClientStatus {
|
|
PENDING @map("pending")
|
|
APPROVED @map("approved")
|
|
REJECTED @map("rejected")
|
|
}
|
|
|
|
model OAuthClient {
|
|
clientId String @id @unique
|
|
redirectUri String
|
|
clientSecret String?
|
|
clientType OAuthClientType @default(CONFIDENTIAL)
|
|
name String
|
|
purpose String?
|
|
logo String?
|
|
websiteUrl String?
|
|
rejectionReason String?
|
|
isTrusted Boolean @default(false)
|
|
accessCodes AccessCode[]
|
|
status OAuthClientStatus @default(APPROVED)
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model AccessCode {
|
|
id Int @id @default(autoincrement())
|
|
code String
|
|
clientId String?
|
|
client OAuthClient? @relation(fields: [clientId], references: [clientId], onDelete: Cascade)
|
|
expiresAt DateTime
|
|
scopes AccessScope[]
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
codeChallenge String?
|
|
codeChallengeMethod String? @default("S256")
|
|
}
|
|
|
|
enum AccessScope {
|
|
READ_BOOKING
|
|
READ_PROFILE
|
|
}
|
|
|
|
view BookingTimeStatus {
|
|
id Int @unique
|
|
uid String?
|
|
eventTypeId Int?
|
|
title String?
|
|
description String?
|
|
startTime DateTime?
|
|
endTime DateTime?
|
|
createdAt DateTime?
|
|
location String?
|
|
paid Boolean?
|
|
status BookingStatus?
|
|
rescheduled Boolean?
|
|
userId Int?
|
|
teamId Int?
|
|
eventLength Int?
|
|
timeStatus String?
|
|
eventParentId Int?
|
|
userEmail String?
|
|
username String?
|
|
ratingFeedback String?
|
|
rating Int?
|
|
noShowHost Boolean?
|
|
isTeamBooking Boolean
|
|
}
|
|
|
|
model BookingDenormalized {
|
|
id Int @id @unique
|
|
uid String
|
|
eventTypeId Int?
|
|
title String
|
|
description String?
|
|
startTime DateTime
|
|
endTime DateTime
|
|
createdAt DateTime
|
|
updatedAt DateTime?
|
|
location String?
|
|
paid Boolean
|
|
status BookingStatus
|
|
rescheduled Boolean?
|
|
userId Int?
|
|
teamId Int?
|
|
eventLength Int?
|
|
eventParentId Int?
|
|
userEmail String?
|
|
userName String?
|
|
userUsername String?
|
|
ratingFeedback String?
|
|
rating Int?
|
|
noShowHost Boolean?
|
|
isTeamBooking Boolean
|
|
|
|
@@index([userId])
|
|
@@index([createdAt])
|
|
@@index([eventTypeId])
|
|
@@index([eventParentId])
|
|
@@index([teamId])
|
|
@@index([startTime])
|
|
@@index([endTime])
|
|
@@index([status])
|
|
@@index([teamId, isTeamBooking])
|
|
@@index([userId, isTeamBooking])
|
|
@@index([startTime, endTime])
|
|
}
|
|
|
|
view BookingTimeStatusDenormalized {
|
|
id Int @unique
|
|
uid String
|
|
eventTypeId Int?
|
|
title String
|
|
description String?
|
|
startTime DateTime
|
|
endTime DateTime
|
|
createdAt DateTime
|
|
updatedAt DateTime?
|
|
location String?
|
|
paid Boolean
|
|
status BookingStatus
|
|
rescheduled Boolean?
|
|
userId Int?
|
|
teamId Int?
|
|
eventLength Int?
|
|
eventParentId Int?
|
|
userEmail String?
|
|
userName String?
|
|
userUsername String?
|
|
ratingFeedback String?
|
|
rating Int?
|
|
noShowHost Boolean?
|
|
isTeamBooking Boolean
|
|
timeStatus String? // this is the addition on top of BookingDenormalized
|
|
}
|
|
|
|
model CalendarCache {
|
|
// To be made required in a followup
|
|
id String? @default(uuid())
|
|
|
|
// The key would be the unique URL that is requested by the user
|
|
key String
|
|
value Json
|
|
expiresAt DateTime
|
|
// Provide an initial value for legacy rows and future raw inserts
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
credentialId Int
|
|
userId Int?
|
|
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([credentialId, key])
|
|
@@unique([credentialId, key])
|
|
@@index([userId, key])
|
|
}
|
|
|
|
enum RedirectType {
|
|
UserEventType @map("user-event-type")
|
|
TeamEventType @map("team-event-type")
|
|
User @map("user")
|
|
Team @map("team")
|
|
}
|
|
|
|
model TempOrgRedirect {
|
|
id Int @id @default(autoincrement())
|
|
// Better would be to have fromOrgId and toOrgId as well and then we should have just to instead toUrl
|
|
from String
|
|
// 0 would mean it is non org
|
|
fromOrgId Int
|
|
type RedirectType
|
|
// It doesn't have any query params
|
|
toUrl String
|
|
enabled Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([from, type, fromOrgId])
|
|
}
|
|
|
|
model Avatar {
|
|
// e.g. NULL(0), organization ID or team logo
|
|
teamId Int @default(0)
|
|
// Avatar, NULL(0) if team logo
|
|
userId Int @default(0)
|
|
// base64 string
|
|
data String
|
|
// different every time to pop the cache.
|
|
objectKey String @unique
|
|
|
|
isBanner Boolean @default(false)
|
|
|
|
@@unique([teamId, userId, isBanner])
|
|
@@map(name: "avatars")
|
|
}
|
|
|
|
model OutOfOfficeEntry {
|
|
id Int @id @default(autoincrement())
|
|
uuid String @unique
|
|
start DateTime
|
|
end DateTime
|
|
notes String?
|
|
showNotePublicly Boolean @default(false)
|
|
userId Int
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
toUserId Int?
|
|
toUser User? @relation(name: "toUser", fields: [toUserId], references: [id], onDelete: Cascade)
|
|
reasonId Int?
|
|
reason OutOfOfficeReason? @relation(fields: [reasonId], references: [id], onDelete: SetNull)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([uuid])
|
|
@@index([userId])
|
|
@@index([toUserId])
|
|
@@index([start, end])
|
|
}
|
|
|
|
model OutOfOfficeReason {
|
|
id Int @id @default(autoincrement())
|
|
emoji String
|
|
reason String @unique
|
|
enabled Boolean @default(true)
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
entries OutOfOfficeEntry[]
|
|
}
|
|
|
|
model UserHolidaySettings {
|
|
id Int @id @default(autoincrement())
|
|
userId Int @unique
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
countryCode String?
|
|
disabledIds String[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
// Holiday cache for Google Calendar public holidays
|
|
model HolidayCache {
|
|
id String @id @default(uuid())
|
|
countryCode String
|
|
calendarId String
|
|
eventId String
|
|
name String
|
|
date DateTime
|
|
year Int
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([countryCode, eventId])
|
|
@@index([countryCode, year])
|
|
@@index([countryCode, date])
|
|
}
|
|
|
|
// Platform
|
|
model PlatformOAuthClient {
|
|
id String @id @default(cuid())
|
|
name String
|
|
secret String
|
|
permissions Int
|
|
users User[]
|
|
logo String?
|
|
redirectUris String[]
|
|
organizationId Int
|
|
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
teams Team[] @relation("CreatedByOAuthClient")
|
|
|
|
accessTokens AccessToken[]
|
|
refreshToken RefreshToken[]
|
|
authorizationTokens PlatformAuthorizationToken[]
|
|
webhook Webhook[]
|
|
|
|
bookingRedirectUri String?
|
|
bookingCancelRedirectUri String?
|
|
bookingRescheduleRedirectUri String?
|
|
areEmailsEnabled Boolean @default(false)
|
|
areDefaultEventTypesEnabled Boolean @default(true)
|
|
areCalendarEventsEnabled Boolean @default(true)
|
|
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model PlatformAuthorizationToken {
|
|
id String @id @default(cuid())
|
|
|
|
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
client PlatformOAuthClient @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
|
|
|
|
platformOAuthClientId String
|
|
userId Int
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([userId, platformOAuthClientId])
|
|
}
|
|
|
|
model AccessToken {
|
|
id Int @id @default(autoincrement())
|
|
|
|
secret String @unique
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime
|
|
|
|
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
client PlatformOAuthClient @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
|
|
|
|
platformOAuthClientId String
|
|
userId Int
|
|
}
|
|
|
|
model RefreshToken {
|
|
id Int @id @default(autoincrement())
|
|
|
|
secret String @unique
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime
|
|
|
|
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
client PlatformOAuthClient @relation(fields: [platformOAuthClientId], references: [id], onDelete: Cascade)
|
|
|
|
platformOAuthClientId String
|
|
userId Int
|
|
}
|
|
|
|
model DSyncData {
|
|
id Int @id @default(autoincrement())
|
|
directoryId String @unique
|
|
tenant String
|
|
organizationId Int? @unique
|
|
org OrganizationSettings? @relation(fields: [organizationId], references: [organizationId], onDelete: Cascade)
|
|
teamGroupMapping DSyncTeamGroupMapping[]
|
|
|
|
createdAttributeToUsers AttributeToUser[] @relation("createdByDSync")
|
|
updatedAttributeToUsers AttributeToUser[] @relation("updatedByDSync")
|
|
}
|
|
|
|
model DSyncTeamGroupMapping {
|
|
id Int @id @default(autoincrement())
|
|
organizationId Int
|
|
teamId Int
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
directoryId String
|
|
directory DSyncData @relation(fields: [directoryId], references: [directoryId], onDelete: Cascade)
|
|
groupName String
|
|
|
|
@@unique([teamId, groupName])
|
|
}
|
|
|
|
model SecondaryEmail {
|
|
id Int @id @default(autoincrement())
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int
|
|
email String
|
|
emailVerified DateTime?
|
|
verificationTokens VerificationToken[]
|
|
eventTypes EventType[]
|
|
|
|
@@unique([email])
|
|
@@unique([userId, email])
|
|
@@index([userId])
|
|
@@index([email, emailVerified])
|
|
}
|
|
|
|
// Needed to store tasks that need to be processed by a background worker or Tasker
|
|
model Task {
|
|
id String @id @unique @default(uuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
// The time at which the task should be executed
|
|
scheduledAt DateTime @default(now())
|
|
// The time at which the task was successfully executed
|
|
succeededAt DateTime?
|
|
// The task type to be executed. Left it as a freeform string to avoid more migrations for now. Will be enforced at type level.
|
|
type String
|
|
// Generic payload for the task
|
|
payload String
|
|
// The number of times the task has been attempted
|
|
attempts Int @default(0)
|
|
// The maximum number of times the task can be attempted
|
|
maxAttempts Int @default(3)
|
|
lastError String?
|
|
lastFailedAttemptAt DateTime?
|
|
referenceUid String?
|
|
|
|
// unique index on referenceUid,type to avoid duplicate tasks
|
|
@@unique([referenceUid, type])
|
|
// for finding succeeded tasks
|
|
@@index([succeededAt])
|
|
// for finding tasks that are scheduled to be executed
|
|
@@index([scheduledAt, succeededAt])
|
|
}
|
|
|
|
enum SMSLockState {
|
|
LOCKED
|
|
UNLOCKED
|
|
REVIEW_NEEDED
|
|
}
|
|
|
|
model ManagedOrganization {
|
|
managedOrganizationId Int @unique
|
|
managedOrganization Team @relation("ManagedOrganization", fields: [managedOrganizationId], references: [id], onDelete: Cascade)
|
|
|
|
managerOrganizationId Int
|
|
managerOrganization Team @relation("ManagerOrganization", fields: [managerOrganizationId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([managerOrganizationId, managedOrganizationId])
|
|
@@index([managerOrganizationId])
|
|
}
|
|
|
|
model PlatformBilling {
|
|
id Int @id @unique // team id
|
|
|
|
customerId String
|
|
subscriptionId String?
|
|
priceId String?
|
|
plan String @default("none")
|
|
|
|
billingCycleStart Int?
|
|
billingCycleEnd Int?
|
|
overdue Boolean? @default(false)
|
|
|
|
// note(Lauris): in case of a platform managed organization's billing record this field points to the manager organization's billing record.
|
|
managerBillingId Int?
|
|
managerBilling PlatformBilling? @relation("PlatformManagedBilling", fields: [managerBillingId], references: [id])
|
|
// note(Lauris): in case of a manager organization's billing record this field points to billing records of its platform managed organizations.
|
|
managedBillings PlatformBilling[] @relation("PlatformManagedBilling")
|
|
|
|
team Team @relation(fields: [id], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
enum AttributeType {
|
|
TEXT
|
|
NUMBER
|
|
SINGLE_SELECT
|
|
MULTI_SELECT
|
|
}
|
|
|
|
model AttributeOption {
|
|
id String @id @default(uuid())
|
|
attribute Attribute @relation(fields: [attributeId], references: [id], onDelete: Cascade)
|
|
attributeId String
|
|
value String
|
|
slug String
|
|
isGroup Boolean @default(false)
|
|
// It is a list of AttributeOptions ids that are contained in the group option
|
|
// You could think of a person having the group option to actually have all the options in the contains list.
|
|
// We are not using relation here because it would be a many to many relation because a group option can contain many non-group options and a non-group option can be contained in many group options
|
|
// Such a relation would require its own table to be managed and we don't need it for now.
|
|
contains String[]
|
|
assignedUsers AttributeToUser[]
|
|
}
|
|
|
|
model Attribute {
|
|
id String @id @default(uuid())
|
|
|
|
// This is organization
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
// This is organizationId
|
|
teamId Int
|
|
|
|
type AttributeType
|
|
|
|
name String
|
|
slug String
|
|
enabled Boolean @default(true)
|
|
|
|
usersCanEditRelation Boolean @default(false)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
options AttributeOption[]
|
|
isWeightsEnabled Boolean @default(false)
|
|
isLocked Boolean @default(false)
|
|
attributeSyncFieldMappings AttributeSyncFieldMapping[]
|
|
|
|
@@unique([teamId, slug])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model AttributeToUser {
|
|
id String @id @default(uuid())
|
|
|
|
// This is the membership of the organization
|
|
member Membership @relation(fields: [memberId], references: [id], onDelete: Cascade)
|
|
|
|
// This is the membership id of the organization
|
|
memberId Int
|
|
|
|
attributeOption AttributeOption @relation(fields: [attributeOptionId], references: [id], onDelete: Cascade)
|
|
attributeOptionId String
|
|
|
|
weight Int?
|
|
|
|
// We don't intentionally delete assignments on deletion of a user/directory sync
|
|
createdAt DateTime @default(now())
|
|
createdById Int?
|
|
createdBy User? @relation("createdBy", fields: [createdById], references: [id], onDelete: SetNull)
|
|
createdByDSyncId String?
|
|
createdByDSync DSyncData? @relation("createdByDSync", fields: [createdByDSyncId], references: [directoryId], onDelete: SetNull)
|
|
|
|
updatedAt DateTime? @updatedAt
|
|
updatedBy User? @relation("updatedBy", fields: [updatedById], references: [id], onDelete: SetNull)
|
|
updatedById Int?
|
|
updatedByDSyncId String?
|
|
updatedByDSync DSyncData? @relation("updatedByDSync", fields: [updatedByDSyncId], references: [directoryId], onDelete: SetNull)
|
|
|
|
@@unique([memberId, attributeOptionId])
|
|
}
|
|
|
|
enum AssignmentReasonEnum {
|
|
ROUTING_FORM_ROUTING
|
|
ROUTING_FORM_ROUTING_FALLBACK
|
|
REASSIGNED
|
|
RR_REASSIGNED
|
|
REROUTED
|
|
SALESFORCE_ASSIGNMENT
|
|
}
|
|
|
|
model AssignmentReason {
|
|
id Int @id @unique @default(autoincrement())
|
|
createdAt DateTime @default(now())
|
|
bookingId Int
|
|
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
reasonEnum AssignmentReasonEnum
|
|
reasonString String
|
|
routingTrace RoutingTrace?
|
|
|
|
@@index([bookingId])
|
|
}
|
|
|
|
enum EventTypeAutoTranslatedField {
|
|
DESCRIPTION
|
|
TITLE
|
|
}
|
|
|
|
enum WorkflowStepAutoTranslatedField {
|
|
REMINDER_BODY
|
|
EMAIL_SUBJECT
|
|
}
|
|
|
|
model DelegationCredential {
|
|
id String @id @default(uuid())
|
|
workspacePlatform WorkspacePlatform @relation(fields: [workspacePlatformId], references: [id], onDelete: Cascade)
|
|
workspacePlatformId Int
|
|
// Provides possibility to have different service accounts for different organizations if the need arises, but normally they should be the same
|
|
/// @zod.import(["import { serviceAccountKeySchema } from '../../zod-utils'"]).custom.use(serviceAccountKeySchema)
|
|
serviceAccountKey Json
|
|
enabled Boolean @default(false)
|
|
// lastEnabledAt is set when the delegation credential is enabled
|
|
lastEnabledAt DateTime?
|
|
// lastDisabledAt is set when the delegation credential is disabled. So, lastDisabledAt could be earlier then lastEnabledAt if the delegation credential was enabled -> then disabled -> then enabled again.
|
|
lastDisabledAt DateTime?
|
|
organizationId Int
|
|
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
domain String
|
|
selectedCalendars SelectedCalendar[]
|
|
destinationCalendar DestinationCalendar[]
|
|
bookingReferences BookingReference[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
credentials Credential[]
|
|
|
|
// Should be fair to assume that one domain can be only on one workspace platform at a time. So, one can't have two different workspace platforms for the same domain
|
|
// Because we don't know which domain the organization might have, we couldn't make "domain" unique here as that would prevent an actual owner of the domain to be unable to use that domain if it is used by someone else.
|
|
@@unique([organizationId, domain])
|
|
@@index([enabled])
|
|
}
|
|
|
|
// Deprecated and probably unused - Use DelegationCredential instead
|
|
model DomainWideDelegation {
|
|
id String @id @default(uuid())
|
|
workspacePlatform WorkspacePlatform @relation(fields: [workspacePlatformId], references: [id], onDelete: Cascade)
|
|
workspacePlatformId Int
|
|
// Provides possibility to have different service accounts for different organizations if the need arises, but normally they should be the same
|
|
/// @zod.import(["import { serviceAccountKeySchema } from '../../zod-utils'"]).custom.use(serviceAccountKeySchema)
|
|
serviceAccountKey Json
|
|
enabled Boolean @default(false)
|
|
organizationId Int
|
|
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
domain String
|
|
selectedCalendars SelectedCalendar[]
|
|
destinationCalendar DestinationCalendar[]
|
|
bookingReferences BookingReference[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Should be fair to assume that one domain can be only on one workspace platform at a time. So, one can't have two different workspace platforms for the same domain
|
|
// Because we don't know which domain the organization might have, we couldn't make "domain" unique here as that would prevent an actual owner of the domain to be unable to use that domain if it is used by someone else.
|
|
@@unique([organizationId, domain])
|
|
}
|
|
|
|
// It is for delegation credential
|
|
model WorkspacePlatform {
|
|
id Int @id @default(autoincrement())
|
|
/// @zod.string.min(1)
|
|
slug String
|
|
/// @zod.string.min(1)
|
|
name String
|
|
description String
|
|
/// @zod.import(["import { serviceAccountKeySchema } from '../../zod-utils'"]).custom.use(serviceAccountKeySchema)
|
|
defaultServiceAccountKey Json
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
enabled Boolean @default(false)
|
|
delegationCredentials DelegationCredential[]
|
|
domainWideDelegations DomainWideDelegation[]
|
|
|
|
@@unique([slug])
|
|
}
|
|
|
|
model EventTypeTranslation {
|
|
uid String @id @default(cuid())
|
|
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
|
eventTypeId Int
|
|
field EventTypeAutoTranslatedField
|
|
sourceLocale String
|
|
targetLocale String
|
|
translatedText String @db.Text
|
|
createdAt DateTime @default(now())
|
|
createdBy Int
|
|
updatedAt DateTime @updatedAt
|
|
updatedBy Int?
|
|
creator User @relation("CreatedEventTypeTranslations", fields: [createdBy], references: [id])
|
|
updater User? @relation("UpdatedEventTypeTranslations", fields: [updatedBy], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([eventTypeId, field, targetLocale])
|
|
@@index([eventTypeId, field, targetLocale])
|
|
}
|
|
|
|
model WorkflowStepTranslation {
|
|
uid String @id @default(cuid())
|
|
workflowStep WorkflowStep @relation(fields: [workflowStepId], references: [id], onDelete: Cascade)
|
|
workflowStepId Int
|
|
field WorkflowStepAutoTranslatedField
|
|
sourceLocale String
|
|
targetLocale String
|
|
translatedText String @db.Text
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([workflowStepId, field, targetLocale])
|
|
}
|
|
|
|
enum WatchlistType {
|
|
EMAIL
|
|
DOMAIN
|
|
USERNAME
|
|
}
|
|
|
|
enum WatchlistAction {
|
|
REPORT
|
|
BLOCK
|
|
ALERT
|
|
}
|
|
|
|
enum WatchlistSource {
|
|
MANUAL
|
|
FREE_DOMAIN_POLICY
|
|
SIGNUP
|
|
}
|
|
|
|
model Watchlist {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
type WatchlistType
|
|
value String
|
|
description String?
|
|
isGlobal Boolean @default(false)
|
|
organizationId Int?
|
|
action WatchlistAction @default(REPORT)
|
|
source WatchlistSource @default(MANUAL)
|
|
lastUpdatedAt DateTime @default(now())
|
|
|
|
bookingReports BookingReport[]
|
|
globalBookingReports BookingReport[] @relation("GlobalBlocklist")
|
|
audits WatchlistAudit[]
|
|
|
|
@@unique([type, value, organizationId])
|
|
@@index([isGlobal, action, organizationId, type, value])
|
|
}
|
|
|
|
model WatchlistAudit {
|
|
id String @id @default(uuid(7)) @db.Uuid
|
|
type WatchlistType
|
|
value String
|
|
description String?
|
|
|
|
action WatchlistAction @default(REPORT)
|
|
|
|
changedAt DateTime @default(now())
|
|
changedByUserId Int?
|
|
|
|
watchlistId String? @db.Uuid
|
|
watchlist Watchlist? @relation(fields: [watchlistId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([watchlistId, changedAt])
|
|
}
|
|
|
|
model WatchlistEventAudit {
|
|
id String @id @default(uuid(7)) @db.Uuid
|
|
watchlistId String @db.Uuid
|
|
eventTypeId Int
|
|
actionTaken WatchlistAction
|
|
timestamp DateTime @default(now())
|
|
}
|
|
|
|
enum BookingReportReason {
|
|
SPAM
|
|
DONT_KNOW_PERSON @map("dont_know_person")
|
|
OTHER
|
|
}
|
|
|
|
enum BookingReportStatus {
|
|
PENDING // Initial state - awaiting review
|
|
DISMISSED // User clicked "Don't block"
|
|
BLOCKED // Added to watchlist
|
|
}
|
|
|
|
enum SystemReportStatus {
|
|
PENDING // Not yet reviewed by system admin
|
|
BLOCKED // Added to global blocklist
|
|
DISMISSED // System admin dismissed (won't see again)
|
|
}
|
|
|
|
model BookingReport {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
bookingUid String @unique
|
|
booking Booking @relation(fields: [bookingUid], references: [uid], onDelete: Cascade)
|
|
bookerEmail String
|
|
reportedById Int?
|
|
organizationId Int?
|
|
reason BookingReportReason
|
|
description String?
|
|
cancelled Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
status BookingReportStatus @default(PENDING)
|
|
systemStatus SystemReportStatus @default(PENDING)
|
|
|
|
watchlistId String? @db.Uuid
|
|
globalWatchlistId String? @db.Uuid
|
|
|
|
reportedBy User? @relation("ReportedBy", fields: [reportedById], references: [id], onDelete: SetNull)
|
|
organization Team? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
watchlist Watchlist? @relation(fields: [watchlistId], references: [id], onDelete: SetNull)
|
|
globalWatchlist Watchlist? @relation("GlobalBlocklist", fields: [globalWatchlistId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([bookerEmail])
|
|
@@index([reportedById])
|
|
@@index([organizationId])
|
|
@@index([watchlistId])
|
|
@@index([globalWatchlistId])
|
|
@@index([systemStatus])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
enum WrongAssignmentReportStatus {
|
|
PENDING // Initial state - awaiting review
|
|
REVIEWED // Admin has seen it
|
|
RESOLVED // Corrective action taken
|
|
DISMISSED // Admin decided no action needed
|
|
}
|
|
|
|
model WrongAssignmentReport {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
bookingUid String @unique
|
|
booking Booking @relation(fields: [bookingUid], references: [uid], onDelete: Cascade)
|
|
reportedById Int?
|
|
reportedBy User? @relation("WrongAssignmentReportedBy", fields: [reportedById], references: [id], onDelete: SetNull)
|
|
correctAssignee String?
|
|
additionalNotes String
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: SetNull)
|
|
routingFormId String?
|
|
routingForm App_RoutingForms_Form? @relation(fields: [routingFormId], references: [id], onDelete: SetNull)
|
|
status WrongAssignmentReportStatus @default(PENDING)
|
|
reviewedById Int?
|
|
reviewedBy User? @relation("WrongAssignmentReviewedBy", fields: [reviewedById], references: [id], onDelete: SetNull)
|
|
reviewedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([reportedById])
|
|
@@index([teamId])
|
|
@@index([routingFormId])
|
|
@@index([status])
|
|
@@index([reviewedById])
|
|
@@index([createdAt])
|
|
@@index([teamId, status, createdAt])
|
|
}
|
|
|
|
enum BillingPeriod {
|
|
MONTHLY
|
|
ANNUALLY
|
|
}
|
|
|
|
enum BillingMode {
|
|
SEATS
|
|
ACTIVE_USERS
|
|
}
|
|
|
|
model OrganizationOnboarding {
|
|
// TODO: Use uuid for id
|
|
id String @id @default(uuid())
|
|
|
|
// User who started the onboarding. It is different from orgOwnerEmail in case Cal.com admin is doing the onboarding for someone else.
|
|
createdBy User @relation("CreatedOrganizationOnboardings", fields: [createdById], references: [id], onDelete: Cascade)
|
|
createdById Int
|
|
createdAt DateTime @default(now())
|
|
|
|
// We keep the email only here and don't need to connect it with user because on User deletion, we don't delete the entry here.
|
|
// It is unique because an email can be the owner of only one organization at a time.
|
|
orgOwnerEmail String @unique
|
|
error String?
|
|
|
|
updatedAt DateTime @updatedAt
|
|
// TODO: updatedBy to be added when we support marking updatedBy using webhook too, as webhook also updates it
|
|
|
|
// Set after organization payment is done and the organization is created
|
|
organizationId Int? @unique
|
|
organization Team? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
|
|
billingPeriod BillingPeriod
|
|
pricePerSeat Float
|
|
seats Int
|
|
|
|
isPlatform Boolean @default(false)
|
|
|
|
// Organization info
|
|
name String
|
|
// We don't keep it unique because we don't want self-serve flows to block a slug if it isn't paid for yet.
|
|
slug String
|
|
logo String?
|
|
bio String?
|
|
brandColor String?
|
|
bannerUrl String?
|
|
isDomainConfigured Boolean @default(false)
|
|
|
|
// Set when payment intent is there.
|
|
stripeCustomerId String? @unique
|
|
// TODO: Can we make it required
|
|
stripeSubscriptionId String?
|
|
stripeSubscriptionItemId String?
|
|
|
|
/// @zod.import(["import { orgOnboardingInvitedMembersSchema } from '../../zod-utils'"]).custom.use(orgOnboardingInvitedMembersSchema)
|
|
invitedMembers Json @default("[]")
|
|
|
|
/// @zod.import(["import { orgOnboardingTeamsSchema } from '../../zod-utils'"]).custom.use(orgOnboardingTeamsSchema)
|
|
teams Json @default("[]")
|
|
|
|
// Completion status
|
|
isComplete Boolean @default(false)
|
|
|
|
@@index([orgOwnerEmail])
|
|
@@index([stripeCustomerId])
|
|
}
|
|
|
|
enum IncompleteBookingActionType {
|
|
SALESFORCE
|
|
}
|
|
|
|
model App_RoutingForms_IncompleteBookingActions {
|
|
id Int @id @default(autoincrement())
|
|
form App_RoutingForms_Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
|
formId String
|
|
actionType IncompleteBookingActionType
|
|
data Json
|
|
enabled Boolean @default(true)
|
|
credentialId Int?
|
|
}
|
|
|
|
model InternalNotePreset {
|
|
id Int @id @default(autoincrement())
|
|
name String
|
|
cancellationReason String?
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int
|
|
|
|
createdAt DateTime @default(now())
|
|
BookingInternalNote BookingInternalNote[]
|
|
|
|
@@unique([teamId, name])
|
|
@@index([teamId])
|
|
}
|
|
|
|
enum FilterSegmentScope {
|
|
USER
|
|
TEAM
|
|
}
|
|
|
|
model FilterSegment {
|
|
id Int @id @default(autoincrement())
|
|
name String
|
|
// Identifies which data table this segment belongs to (e.g. "organization_members", "team_members", "bookings", etc.)
|
|
tableIdentifier String
|
|
scope FilterSegmentScope
|
|
// Filter configuration
|
|
activeFilters Json?
|
|
sorting Json?
|
|
columnVisibility Json?
|
|
columnSizing Json?
|
|
perPage Int
|
|
searchTerm String? @db.Text
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
// Creator of the segment
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
userId Int
|
|
// Team scope - optional, only set when scope is TEAM
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
userPreferences UserFilterSegmentPreference[]
|
|
|
|
// For user-scoped segments: scope + userId + tableIdentifier
|
|
@@index([scope, userId, tableIdentifier])
|
|
// For team-scoped segments: scope + teamId + tableIdentifier
|
|
@@index([scope, teamId, tableIdentifier])
|
|
}
|
|
|
|
model UserFilterSegmentPreference {
|
|
id Int @id @default(autoincrement())
|
|
userId Int
|
|
tableIdentifier String
|
|
segmentId Int?
|
|
systemSegmentId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
segment FilterSegment? @relation(fields: [segmentId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, tableIdentifier])
|
|
@@index([userId])
|
|
@@index([segmentId])
|
|
}
|
|
|
|
model BookingInternalNote {
|
|
id Int @id @default(autoincrement())
|
|
|
|
notePreset InternalNotePreset? @relation(fields: [notePresetId], references: [id], onDelete: Cascade)
|
|
notePresetId Int?
|
|
text String?
|
|
|
|
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
|
bookingId Int
|
|
|
|
createdBy User @relation(fields: [createdById], references: [id])
|
|
createdById Int
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([bookingId, notePresetId])
|
|
@@index([bookingId])
|
|
}
|
|
|
|
enum WorkflowContactType {
|
|
PHONE
|
|
EMAIL
|
|
}
|
|
|
|
model WorkflowOptOutContact {
|
|
id Int @id @default(autoincrement())
|
|
type WorkflowContactType
|
|
value String
|
|
optedOut Boolean
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([type, value])
|
|
}
|
|
|
|
enum RoleType {
|
|
SYSTEM
|
|
CUSTOM
|
|
}
|
|
|
|
model Role {
|
|
id String @id @default(cuid())
|
|
name String
|
|
color String?
|
|
description String?
|
|
teamId Int? // null for global roles
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
permissions RolePermission[]
|
|
memberships Membership[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
type RoleType @default(CUSTOM)
|
|
|
|
@@unique([name, teamId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model RolePermission {
|
|
id String @id @default(uuid())
|
|
roleId String
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
resource String
|
|
action String
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([roleId, resource, action])
|
|
@@index([roleId])
|
|
// TODO: come back to this with indexs.
|
|
@@index([action])
|
|
}
|
|
|
|
enum BookingAuditType {
|
|
RECORD_CREATED @map("record_created")
|
|
RECORD_UPDATED @map("record_updated")
|
|
RECORD_DELETED @map("record_deleted")
|
|
}
|
|
|
|
enum BookingAuditAction {
|
|
// Booking lifecycle
|
|
CREATED @map("created")
|
|
|
|
// Status changes
|
|
CANCELLED @map("cancelled")
|
|
ACCEPTED @map("accepted")
|
|
REJECTED @map("rejected")
|
|
PENDING @map("pending")
|
|
AWAITING_HOST @map("awaiting_host")
|
|
RESCHEDULED @map("rescheduled")
|
|
|
|
// Attendee management
|
|
ATTENDEE_ADDED @map("attendee_added")
|
|
ATTENDEE_REMOVED @map("attendee_removed")
|
|
|
|
// Assignment/Reassignment (keep integration version - simpler)
|
|
REASSIGNMENT @map("reassignment")
|
|
|
|
// Meeting details
|
|
LOCATION_CHANGED @map("location_changed")
|
|
|
|
// No-show tracking
|
|
NO_SHOW_UPDATED @map("no_show_updated")
|
|
|
|
// Rescheduling
|
|
RESCHEDULE_REQUESTED @map("reschedule_requested")
|
|
|
|
// Seat management
|
|
SEAT_BOOKED @map("seat_booked")
|
|
SEAT_RESCHEDULED @map("seat_rescheduled")
|
|
}
|
|
|
|
enum BookingAuditSource {
|
|
API_V1 @map("api_v1")
|
|
API_V2 @map("api_v2")
|
|
WEBAPP @map("webapp")
|
|
WEBHOOK @map("webhook")
|
|
SYSTEM @map("system")
|
|
MAGIC_LINK @map("magic_link")
|
|
UNKNOWN @map("unknown")
|
|
}
|
|
|
|
enum AuditActorType {
|
|
USER @map("user") // Registered Cal.com user (stored here for audit retention even after user deletion)
|
|
GUEST @map("guest") // Non-registered user
|
|
ATTENDEE @map("attendee") // Has Attendee record with us
|
|
SYSTEM @map("system") // Automated actions
|
|
APP @map("app") // App-triggered actions (payment webhooks, integrations)
|
|
}
|
|
|
|
model AuditActor {
|
|
id String @id @default(uuid(7)) @db.Uuid
|
|
type AuditActorType
|
|
|
|
// References for different actor types (soft references, no FK constraints)
|
|
// These fields intentionally do NOT have foreign key constraints to preserve audit trail integrity:
|
|
// - When a User, Attendee, or Credential is deleted, their AuditActor record persists with these IDs intact
|
|
// - This maintains immutable audit history even after source records are removed
|
|
// - For APP actors: when credential is deleted, name/email fields are populated to preserve app identity
|
|
userUuid String? @db.Uuid // For USER type - references User.uuid without FK constraint
|
|
attendeeId Int? // For ATTENDEE type - references Attendee.id without FK constraint
|
|
credentialId Int? // For APP type - references Credential.id without FK constraint
|
|
|
|
// Identity fields - for GUEST/SYSTEM/APP types when source record is deleted
|
|
// - GUEST: email/name stored directly
|
|
// - SYSTEM: email/name for system identifier
|
|
// - APP: email/name used when using appSlug mode (fallback), or preserved when credential deleted
|
|
// - USER/ATTENDEE: derive from their tables using userUuid/attendeeId
|
|
// These fields can be anonymized when User/Attendee records are deleted to preserve audit trail
|
|
email String?
|
|
phone String?
|
|
name String?
|
|
|
|
createdAt DateTime @default(now())
|
|
bookingAudits BookingAudit[]
|
|
|
|
// TODO: Add pseudonymizedAt and related fields when we anonymize the data on deletion
|
|
@@unique([userUuid])
|
|
@@unique([attendeeId])
|
|
@@unique([credentialId]) // One actor per credential
|
|
@@unique([email]) // Prevent duplicate email actors
|
|
@@unique([phone]) // Prevent duplicate phone actors
|
|
@@index([email])
|
|
@@index([userUuid])
|
|
@@index([attendeeId])
|
|
@@index([credentialId])
|
|
}
|
|
|
|
model BookingAudit {
|
|
id String @id @default(uuid(7)) @db.Uuid
|
|
// bookingUid is stored as a plain string (not a foreign key relation) to preserve the audit trail
|
|
// even after the booking is deleted. This is intentional for audit log integrity:
|
|
// - Audit logs are immutable historical records that should persist independently
|
|
// - When a booking is deleted, we still need to know which booking the audit log belonged to
|
|
// - Using a plain string instead of a relation prevents bookingUid from becoming NULL on booking deletion
|
|
// - This allows users to view complete audit history for deleted bookings
|
|
bookingUid String
|
|
|
|
// Actor who performed the action (USER, GUEST, or SYSTEM)
|
|
// Stored in AuditActor table to maintain audit trail even after user deletion
|
|
actorId String @db.Uuid
|
|
// Restrict onDelete to prevent deletion of audit actor if there are any booking audits associated with it
|
|
actor AuditActor @relation(fields: [actorId], references: [id], onDelete: Restrict)
|
|
|
|
type BookingAuditType
|
|
action BookingAuditAction
|
|
|
|
// Timestamp of the actual booking change (business event time)
|
|
// Important: May differ from createdAt if audit is processed asynchronously
|
|
timestamp DateTime
|
|
|
|
// Database record timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
source BookingAuditSource
|
|
|
|
// Operation ID for correlating related audit logs across different audit types (BookingAudit, UserAudit, etc.) or for bulk operations
|
|
operationId String
|
|
|
|
data Json?
|
|
|
|
// Context for across action types concerns like impersonation, ip, userAgent etc.
|
|
// This is separate from `data` because it isn't related to action type but makes sense with every action
|
|
// BookingAuditContextSchema
|
|
context Json?
|
|
|
|
@@index([actorId])
|
|
@@index([bookingUid])
|
|
@@index([timestamp])
|
|
@@index([operationId])
|
|
}
|
|
|
|
enum PhoneNumberSubscriptionStatus {
|
|
ACTIVE
|
|
PAST_DUE
|
|
CANCELLED
|
|
INCOMPLETE
|
|
INCOMPLETE_EXPIRED
|
|
TRIALING
|
|
UNPAID
|
|
}
|
|
|
|
// Cal AI Voice Agent
|
|
model Agent {
|
|
id String @id @default(uuid())
|
|
name String
|
|
|
|
// Either user-owned OR team-owned
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
// Provider-specific agent ID (e.g., Retell AI agent ID)
|
|
providerAgentId String @unique
|
|
|
|
// Event type association for inbound agents
|
|
inboundEventTypeId Int?
|
|
|
|
// Event type association for outbound agents
|
|
outboundEventTypeId Int?
|
|
|
|
enabled Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
inboundPhoneNumbers CalAiPhoneNumber[] @relation("InboundAgent")
|
|
outboundPhoneNumbers CalAiPhoneNumber[] @relation("OutboundAgent")
|
|
workflowStep WorkflowStep?
|
|
inboundWorkflowStep WorkflowStep? @relation("WorkflowStepInboundAgent")
|
|
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
@@index([inboundEventTypeId])
|
|
@@index([outboundEventTypeId])
|
|
}
|
|
|
|
model CalAiPhoneNumber {
|
|
id Int @id @default(autoincrement())
|
|
|
|
// Either user-owned OR team-owned
|
|
userId Int?
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
teamId Int?
|
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
phoneNumber String @unique
|
|
provider String
|
|
|
|
// Provider-specific phone number ID (e.g., Retell phone number ID)
|
|
providerPhoneNumberId String? @unique
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
stripeCustomerId String?
|
|
stripeSubscriptionId String? @unique
|
|
subscriptionStatus PhoneNumberSubscriptionStatus?
|
|
|
|
// Agent associations
|
|
inboundAgentId String?
|
|
inboundAgent Agent? @relation("InboundAgent", fields: [inboundAgentId], references: [id], onDelete: SetNull)
|
|
outboundAgentId String?
|
|
outboundAgent Agent? @relation("OutboundAgent", fields: [outboundAgentId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
@@index([inboundAgentId])
|
|
@@index([outboundAgentId])
|
|
}
|
|
|
|
model TeamBilling {
|
|
id String @id @default(uuid())
|
|
teamId Int @unique
|
|
team Team? @relation("TeamBilling", fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
subscriptionId String @unique
|
|
subscriptionItemId String
|
|
customerId String
|
|
status String
|
|
planName String
|
|
subscriptionStart DateTime?
|
|
subscriptionTrialEnd DateTime?
|
|
subscriptionEnd DateTime?
|
|
|
|
billingPeriod BillingPeriod?
|
|
billingMode BillingMode @default(SEATS)
|
|
pricePerSeat Int?
|
|
paidSeats Int?
|
|
monthlyProrations MonthlyProration[]
|
|
seatChangeLogs SeatChangeLog[]
|
|
|
|
// High water mark tracking for monthly billing
|
|
highWaterMark Int?
|
|
highWaterMarkPeriodStart DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model OrganizationBilling {
|
|
id String @id @default(uuid())
|
|
teamId Int @unique
|
|
team Team @relation("OrganizationBilling", fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
subscriptionId String @unique
|
|
subscriptionItemId String
|
|
customerId String
|
|
status String
|
|
planName String
|
|
subscriptionStart DateTime?
|
|
subscriptionTrialEnd DateTime?
|
|
subscriptionEnd DateTime?
|
|
|
|
billingPeriod BillingPeriod?
|
|
billingMode BillingMode @default(SEATS)
|
|
pricePerSeat Int?
|
|
paidSeats Int?
|
|
monthlyProrations MonthlyProration[]
|
|
seatChangeLogs SeatChangeLog[]
|
|
|
|
// High water mark tracking for monthly billing
|
|
highWaterMark Int?
|
|
highWaterMarkPeriodStart DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model SeatChangeLog {
|
|
id String @id @default(uuid())
|
|
teamId Int
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
changeType SeatChangeType
|
|
seatCount Int
|
|
userId Int?
|
|
triggeredBy Int?
|
|
|
|
changeDate DateTime @default(now())
|
|
monthKey String
|
|
|
|
// Idempotency key to prevent duplicate seat change logs from race conditions
|
|
operationId String?
|
|
|
|
processedInProrationId String?
|
|
proration MonthlyProration? @relation(fields: [processedInProrationId], references: [id])
|
|
|
|
metadata Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
teamBillingId String?
|
|
teamBilling TeamBilling? @relation(fields: [teamBillingId], references: [id])
|
|
organizationBillingId String?
|
|
organizationBilling OrganizationBilling? @relation(fields: [organizationBillingId], references: [id])
|
|
|
|
@@unique([teamId, operationId])
|
|
@@index([teamId, monthKey])
|
|
@@index([teamId, processedInProrationId])
|
|
@@index([monthKey])
|
|
}
|
|
|
|
enum SeatChangeType {
|
|
ADDITION
|
|
REMOVAL
|
|
}
|
|
|
|
model MonthlyProration {
|
|
id String @id @default(uuid())
|
|
teamId Int
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
monthKey String
|
|
periodStart DateTime
|
|
periodEnd DateTime
|
|
|
|
seatsAtStart Int
|
|
seatsAdded Int
|
|
seatsRemoved Int
|
|
netSeatIncrease Int
|
|
seatsAtEnd Int
|
|
|
|
subscriptionId String
|
|
subscriptionItemId String
|
|
customerId String
|
|
|
|
subscriptionStart DateTime
|
|
subscriptionEnd DateTime
|
|
remainingDays Int
|
|
pricePerSeat Int
|
|
proratedAmount Int
|
|
|
|
invoiceItemId String?
|
|
invoiceId String?
|
|
invoiceUrl String?
|
|
|
|
status ProrationStatus @default(PENDING)
|
|
|
|
chargedAt DateTime?
|
|
failedAt DateTime?
|
|
failureReason String?
|
|
retryCount Int @default(0)
|
|
|
|
seatChanges SeatChangeLog[]
|
|
|
|
metadata Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
teamBillingId String?
|
|
teamBilling TeamBilling? @relation(fields: [teamBillingId], references: [id])
|
|
organizationBillingId String?
|
|
organizationBilling OrganizationBilling? @relation(fields: [organizationBillingId], references: [id])
|
|
|
|
@@unique([teamId, monthKey])
|
|
@@index([monthKey, status])
|
|
@@index([status])
|
|
@@index([teamId])
|
|
}
|
|
|
|
enum ProrationStatus {
|
|
PENDING // Created, waiting to be processed
|
|
INVOICE_CREATED // Stripe invoice item created
|
|
CHARGED // Payment succeeded
|
|
FAILED // Payment failed
|
|
CANCELLED // Cancelled (e.g., subscription ended before charge)
|
|
}
|
|
|
|
enum CalendarCacheEventStatus {
|
|
confirmed @map("confirmed")
|
|
tentative @map("tentative")
|
|
cancelled @map("cancelled")
|
|
}
|
|
|
|
model CalendarCacheEvent {
|
|
id String @id @default(uuid())
|
|
selectedCalendarId String
|
|
externalId String
|
|
externalEtag String
|
|
iCalUID String?
|
|
iCalSequence Int @default(0)
|
|
|
|
// Event details
|
|
summary String?
|
|
description String?
|
|
location String?
|
|
start DateTime
|
|
end DateTime
|
|
isAllDay Boolean @default(false)
|
|
timeZone String?
|
|
status CalendarCacheEventStatus @default(confirmed)
|
|
recurringEventId String?
|
|
originalStartTime DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
externalCreatedAt DateTime?
|
|
externalUpdatedAt DateTime?
|
|
|
|
selectedCalendar SelectedCalendar @relation(fields: [selectedCalendarId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([selectedCalendarId, externalId])
|
|
@@index([start, end, status])
|
|
@@index([selectedCalendarId, iCalUID])
|
|
}
|
|
|
|
model IntegrationAttributeSync {
|
|
id String @id @default(uuid())
|
|
organizationId Int
|
|
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
|
|
name String
|
|
integration String
|
|
credentialId Int?
|
|
credential Credential? @relation(fields: [credentialId], references: [id], onDelete: Cascade)
|
|
|
|
enabled Boolean
|
|
|
|
attributeSyncRule AttributeSyncRule?
|
|
syncFieldMappings AttributeSyncFieldMapping[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId])
|
|
@@index([credentialId])
|
|
}
|
|
|
|
model AttributeSyncRule {
|
|
id String @id @default(uuid())
|
|
|
|
integrationAttributeSyncId String @unique
|
|
integrationAttributeSync IntegrationAttributeSync @relation(fields: [integrationAttributeSyncId], references: [id], onDelete: Cascade)
|
|
|
|
rule Json
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model AttributeSyncFieldMapping {
|
|
id String @id @default(uuid())
|
|
integrationFieldName String
|
|
attributeId String
|
|
attribute Attribute @relation(fields: [attributeId], references: [id], onDelete: Cascade)
|
|
|
|
enabled Boolean
|
|
integrationAttributeSyncId String
|
|
integrationAttributeSync IntegrationAttributeSync @relation(fields: [integrationAttributeSyncId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([integrationAttributeSyncId, attributeId])
|
|
@@index([integrationAttributeSyncId])
|
|
}
|
|
|
|
model PendingRoutingTrace {
|
|
id String @id @default(uuid())
|
|
createdAt DateTime @default(now())
|
|
trace Json
|
|
|
|
formResponseId Int? @unique
|
|
formResponse App_RoutingForms_FormResponse? @relation(fields: [formResponseId], references: [id], onDelete: Cascade)
|
|
|
|
queuedFormResponseId String? @unique
|
|
queuedFormResponse App_RoutingForms_QueuedFormResponse? @relation(fields: [queuedFormResponseId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model RoutingTrace {
|
|
id String @id @default(uuid())
|
|
createdAt DateTime @default(now())
|
|
trace Json
|
|
|
|
formResponseId Int? @unique
|
|
formResponse App_RoutingForms_FormResponse? @relation(fields: [formResponseId], references: [id], onDelete: Cascade)
|
|
|
|
queuedFormResponseId String? @unique
|
|
queuedFormResponse App_RoutingForms_QueuedFormResponse? @relation(fields: [queuedFormResponseId], references: [id], onDelete: Cascade)
|
|
|
|
bookingUid String? @unique
|
|
booking Booking? @relation(fields: [bookingUid], references: [uid], onDelete: Cascade)
|
|
|
|
assignmentReasonId Int? @unique
|
|
assignmentReason AssignmentReason? @relation(fields: [assignmentReasonId], references: [id], onDelete: Cascade)
|
|
}
|