Some checks failed
Sprint 9: Полная реализация CRM-модуля на базе Entity Factory Pattern. Backend (45 файлов): - Entity Factory Core: EntityManager<T> с lifecycle-хуками, событиями, правами - Pipelines & Stages: CRUD, дефолтные B2B/B2C воронки с 7-8 стадиями - Deals: создание, перемещение по стадиям, win/lose, cursor-пагинация, kanban view - Timeline: лента событий (комментарии, звонки, стадии, формы), pin/unpin - Activities: дела с планированием, завершением, просроченные через BullMQ scheduler - Custom Fields: 8 типов (STRING/INTEGER/FLOAT/BOOLEAN/DATE/TIME/EMAIL/PHONE), CRUD - Webhooks: антифрод (honeypot/timing/disposable/phone/fingerprint/IP), Smart Field Detection - Trainings: entity manager с timeline-интеграцией - CRM Scheduler: BullMQ processor (overdue activities, stale deals, unprocessed leads) Frontend — Platform Admin + Club Admin: - Kanban-доска с HTML5 drag-and-drop между стадиями - Табличный вид со всеми фильтрами (pipeline, source, search) - Карточка сделки: контакт, реквизиты, таймлайн, дела, тренировки - Настройки CRM: 4 вкладки (воронки, кастомные поля, вебхуки, причины проигрыша) - Форма лендинга: honeypot, timing, UTM, POST на /crm/deals/from-form E2E тесты: Pipelines, Deals CRUD, Timeline, Activities, Form spam, RBAC, Lost Reasons, Custom Fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1531 lines
42 KiB
Plaintext
1531 lines
42 KiB
Plaintext
// MyFitCRM - Prisma Schema
|
|
// Multi-tenant CRM for fitness clubs
|
|
// Every table has club_id (except Club itself)
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Enums
|
|
// ---------------------------------------------------------------------------
|
|
|
|
enum UserRole {
|
|
TRAINER
|
|
COORDINATOR
|
|
MANAGER
|
|
RECEPTIONIST
|
|
CLUB_ADMIN
|
|
SUPER_ADMIN
|
|
}
|
|
|
|
enum Gender {
|
|
MALE
|
|
FEMALE
|
|
}
|
|
|
|
enum TrainingStatus {
|
|
PLANNED
|
|
CONFIRMED
|
|
COMPLETED
|
|
PAID
|
|
NOT_PAID
|
|
NO_SHOW
|
|
CANCELLED
|
|
}
|
|
|
|
enum CancelReason {
|
|
CLIENT_CANCEL
|
|
NO_DEDUCTION
|
|
SALE_RETURN
|
|
SICK
|
|
}
|
|
|
|
enum FunnelStage {
|
|
NEW
|
|
ASSIGNED
|
|
APPOINTMENT_SET
|
|
COMPLETED
|
|
REGULAR
|
|
TRANSFERRED
|
|
REFUSED
|
|
}
|
|
|
|
enum SaleStatus {
|
|
PENDING
|
|
PAID
|
|
CANCELLED
|
|
REFUNDED
|
|
}
|
|
|
|
enum CallDirection {
|
|
OUTBOUND
|
|
}
|
|
|
|
enum CallStatus {
|
|
INITIATED
|
|
RINGING
|
|
ANSWERED
|
|
COMPLETED
|
|
FAILED
|
|
NO_ANSWER
|
|
BUSY
|
|
}
|
|
|
|
enum NotificationType {
|
|
CLIENT_ASSIGNED
|
|
SERVICE_BOOKED
|
|
SERVICE_PAID
|
|
PACKAGE_TOPUP
|
|
BOOKING_CANCELLED
|
|
TRAINING_DEDUCTED
|
|
TRAININGS_ENDING
|
|
CLIENT_RETURNED
|
|
CLIENT_BIRTHDAY
|
|
}
|
|
|
|
enum LicenseType {
|
|
STARTER
|
|
STANDARD
|
|
PREMIUM
|
|
}
|
|
|
|
enum ReportStatus {
|
|
PENDING
|
|
GENERATING
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
enum SipTransport {
|
|
UDP
|
|
TCP
|
|
TLS
|
|
}
|
|
|
|
enum Platform {
|
|
IOS
|
|
ANDROID
|
|
}
|
|
|
|
enum EmailType {
|
|
WELCOME
|
|
TRAINING_REMINDER
|
|
MEMBERSHIP_EXPIRING
|
|
PROMO
|
|
BIRTHDAY
|
|
}
|
|
|
|
enum EmailStatus {
|
|
PENDING
|
|
SENT
|
|
DELIVERED
|
|
OPENED
|
|
CLICKED
|
|
BOUNCED
|
|
FAILED
|
|
}
|
|
|
|
enum ClientServiceStatus {
|
|
ACTIVE
|
|
EXPIRED
|
|
CANCELLED
|
|
FROZEN
|
|
}
|
|
|
|
// CRM Enums
|
|
|
|
enum CrmDealSource {
|
|
FORM
|
|
MANUAL
|
|
IMPORT
|
|
API
|
|
WEBHOOK
|
|
}
|
|
|
|
enum CrmStageType {
|
|
OPEN
|
|
WON
|
|
LOST
|
|
}
|
|
|
|
enum CrmActivityType {
|
|
CALL
|
|
MEETING
|
|
EMAIL
|
|
MESSAGE
|
|
CUSTOM
|
|
}
|
|
|
|
enum CrmTimelineType {
|
|
FORM_SUBMISSION
|
|
CALL_INCOMING
|
|
CALL_OUTGOING
|
|
COMMENT
|
|
MESSAGE_TG
|
|
MESSAGE_WA
|
|
MESSAGE_VK
|
|
MESSAGE_EMAIL
|
|
STAGE_CHANGE
|
|
ACTIVITY_CREATED
|
|
ACTIVITY_DONE
|
|
TRAINING_CREATED
|
|
SYSTEM
|
|
}
|
|
|
|
enum CrmTrainingType {
|
|
PERSONAL
|
|
GROUP
|
|
TRIAL
|
|
SPLIT
|
|
}
|
|
|
|
enum CrmTrainingStatus {
|
|
CRM_PLANNED
|
|
CRM_COMPLETED
|
|
CRM_CANCELLED
|
|
CRM_NO_SHOW
|
|
}
|
|
|
|
enum CrmUserFieldType {
|
|
STRING
|
|
INTEGER
|
|
FLOAT
|
|
LIST
|
|
BOOLEAN
|
|
DATE
|
|
DATETIME
|
|
TIME
|
|
}
|
|
|
|
enum CrmActivityPriority {
|
|
LOW
|
|
MEDIUM
|
|
HIGH
|
|
URGENT
|
|
}
|
|
|
|
enum CrmMessageChannel {
|
|
TG
|
|
WHATSAPP
|
|
VK
|
|
EMAIL
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 1. Club
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Club {
|
|
id String @id @default(uuid())
|
|
name String
|
|
slug String @unique
|
|
address String?
|
|
phone String?
|
|
email String?
|
|
logoUrl String?
|
|
timezone String @default("Europe/Moscow")
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
users User[]
|
|
apiKeys ApiKey[]
|
|
departments Department[]
|
|
rooms Room[]
|
|
services Service[]
|
|
serviceCategories ServiceCategory[]
|
|
packages Package[]
|
|
clients Client[]
|
|
clientMemberships ClientMembership[]
|
|
clientGoals ClientGoal[]
|
|
clientInjuries ClientInjury[]
|
|
clientServices ClientService[]
|
|
trainings Training[]
|
|
funnelEntries FunnelEntry[]
|
|
sales Sale[]
|
|
workSchedules WorkSchedule[]
|
|
sipCalls SipCall[]
|
|
sipConfig SipConfig?
|
|
sipAccounts SipAccount[]
|
|
notifications Notification[]
|
|
pushTokens PushToken[]
|
|
webhookSubscriptions WebhookSubscription[]
|
|
webhookLogs WebhookLog[]
|
|
clubModules ClubModule[]
|
|
license License?
|
|
usageRecords UsageRecord[]
|
|
auditLogs AuditLog[]
|
|
integrationConfig IntegrationConfig?
|
|
syncLogs SyncLog[]
|
|
notificationSettings NotificationSetting[]
|
|
reports Report[]
|
|
ratingPeriods RatingPeriod[]
|
|
emailTemplates EmailTemplate[]
|
|
emailLogs EmailLog[]
|
|
|
|
// CRM relations
|
|
crmPipelines CrmPipeline[]
|
|
crmDeals CrmDeal[]
|
|
crmTrainings CrmTraining[]
|
|
crmLostReasons CrmLostReason[]
|
|
crmUserFields CrmUserField[]
|
|
crmWebhookEndpoints CrmWebhookEndpoint[]
|
|
|
|
@@map("clubs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. User (all 6 roles)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
phone String
|
|
passwordHash String
|
|
firstName String
|
|
lastName String
|
|
patronymic String?
|
|
role UserRole
|
|
departmentId String?
|
|
avatarUrl String?
|
|
isActive Boolean @default(true)
|
|
totpSecret String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
department Department? @relation(fields: [departmentId], references: [id], onDelete: SetNull)
|
|
|
|
refreshTokens RefreshToken[]
|
|
trainingsAsTrainer Training[] @relation("TrainerTrainings")
|
|
assignedClients Client[] @relation("AssignedTrainer")
|
|
funnelEntries FunnelEntry[] @relation("FunnelTrainer")
|
|
sales Sale[]
|
|
workSchedules WorkSchedule[]
|
|
sipCalls SipCall[]
|
|
sipAccount SipAccount?
|
|
notifications Notification[]
|
|
pushTokens PushToken[]
|
|
auditLogs AuditLog[]
|
|
reports Report[]
|
|
ratingPeriods RatingPeriod[]
|
|
|
|
// CRM relations
|
|
crmDealsAssigned CrmDeal[] @relation("CrmDealAssignee")
|
|
crmDealsCreated CrmDeal[] @relation("CrmDealCreator")
|
|
crmTrainingsAsTrainer CrmTraining[] @relation("CrmTrainingTrainer")
|
|
crmTimelineEntries CrmTimeline[] @relation("CrmTimelineAuthor")
|
|
crmActivitiesAssigned CrmActivity[] @relation("CrmActivityAssignee")
|
|
crmActivitiesCompleted CrmActivity[] @relation("CrmActivityCompleter")
|
|
crmStageHistoryMoves CrmStageHistory[] @relation("CrmStageHistoryMover")
|
|
|
|
@@unique([clubId, phone])
|
|
@@index([clubId])
|
|
@@index([departmentId])
|
|
@@map("users")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 3. RefreshToken
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model RefreshToken {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
token String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@map("refresh_tokens")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 4. ApiKey
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ApiKey {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
key String @unique
|
|
name String
|
|
scopes String[]
|
|
isActive Boolean @default(true)
|
|
lastUsedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@map("api_keys")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 5. Department
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Department {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
name String
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
users User[]
|
|
|
|
@@index([clubId])
|
|
@@map("departments")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 6. Room
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Room {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
name String
|
|
capacity Int
|
|
description String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
trainings Training[]
|
|
|
|
@@index([clubId])
|
|
@@map("rooms")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7. Service
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Service {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
name String
|
|
description String?
|
|
price Decimal @db.Decimal(12, 2)
|
|
duration Int
|
|
categoryId String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
category ServiceCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
|
|
|
packages Package[]
|
|
trainings Training[]
|
|
sales Sale[]
|
|
clientServices ClientService[]
|
|
|
|
@@index([clubId])
|
|
@@index([categoryId])
|
|
@@map("services")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 8. ServiceCategory
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ServiceCategory {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
name String
|
|
sortOrder Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
services Service[]
|
|
|
|
@@index([clubId])
|
|
@@map("service_categories")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 9. Package
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Package {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
name String
|
|
description String?
|
|
price Decimal @db.Decimal(12, 2)
|
|
totalSessions Int
|
|
validDays Int
|
|
serviceId String
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
|
sales Sale[]
|
|
|
|
@@index([clubId])
|
|
@@index([serviceId])
|
|
@@map("packages")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 10. Client
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Client {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
externalId String?
|
|
firstName String
|
|
lastName String
|
|
patronymic String?
|
|
phone String
|
|
email String?
|
|
birthDate DateTime?
|
|
gender Gender?
|
|
avatarUrl String?
|
|
notes String?
|
|
source String?
|
|
assignedTrainerId String?
|
|
lastActivityAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
assignedTrainer User? @relation("AssignedTrainer", fields: [assignedTrainerId], references: [id], onDelete: SetNull)
|
|
|
|
memberships ClientMembership[]
|
|
goals ClientGoal[]
|
|
injuries ClientInjury[]
|
|
clientServices ClientService[]
|
|
trainings Training[]
|
|
funnelEntries FunnelEntry[]
|
|
sales Sale[]
|
|
sipCalls SipCall[]
|
|
emailLogs EmailLog[]
|
|
|
|
@@index([clubId])
|
|
@@index([assignedTrainerId])
|
|
@@index([clubId, phone])
|
|
@@index([clubId, externalId])
|
|
@@map("clients")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 11. ClientMembership
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ClientMembership {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
clientId String
|
|
name String
|
|
startDate DateTime
|
|
endDate DateTime
|
|
isActive Boolean @default(true)
|
|
externalId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([clientId])
|
|
@@map("client_memberships")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 12. Training
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Training {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
trainerId String
|
|
clientId String
|
|
serviceId String?
|
|
roomId String?
|
|
scheduledAt DateTime
|
|
duration Int
|
|
status TrainingStatus @default(PLANNED)
|
|
cancelReason CancelReason?
|
|
cancelComment String?
|
|
isRepeating Boolean @default(false)
|
|
repeatParentId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
trainer User @relation("TrainerTrainings", fields: [trainerId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
|
|
room Room? @relation(fields: [roomId], references: [id], onDelete: SetNull)
|
|
repeatParent Training? @relation("TrainingRepeat", fields: [repeatParentId], references: [id], onDelete: SetNull)
|
|
repeatChildren Training[] @relation("TrainingRepeat")
|
|
|
|
@@index([clubId])
|
|
@@index([trainerId])
|
|
@@index([clientId])
|
|
@@index([status])
|
|
@@index([clubId, scheduledAt])
|
|
@@index([clubId, status, scheduledAt])
|
|
@@map("trainings")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 13. FunnelEntry
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model FunnelEntry {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
clientId String
|
|
trainerId String?
|
|
stage FunnelStage @default(NEW)
|
|
previousStage FunnelStage?
|
|
comment String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
trainer User? @relation("FunnelTrainer", fields: [trainerId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([clubId])
|
|
@@index([clientId])
|
|
@@index([trainerId])
|
|
@@index([clubId, stage])
|
|
@@map("funnel_entries")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 14. Sale
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Sale {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
trainerId String
|
|
clientId String
|
|
packageId String?
|
|
serviceId String?
|
|
amount Decimal @db.Decimal(12, 2)
|
|
description String?
|
|
status SaleStatus @default(PENDING)
|
|
paidAt DateTime?
|
|
externalId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
trainer User @relation(fields: [trainerId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
package Package? @relation(fields: [packageId], references: [id], onDelete: SetNull)
|
|
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([clubId])
|
|
@@index([trainerId])
|
|
@@index([clientId])
|
|
@@index([clubId, status])
|
|
@@index([clubId, createdAt])
|
|
@@map("sales")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 15. WorkSchedule
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model WorkSchedule {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
trainerId String
|
|
date DateTime
|
|
startTime String
|
|
endTime String
|
|
isAvailable Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
trainer User @relation(fields: [trainerId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([trainerId])
|
|
@@index([clubId, trainerId, date])
|
|
@@map("work_schedules")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 16. SipCall
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model SipCall {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
callerId String
|
|
clientId String?
|
|
callerNumber String
|
|
calleeNumber String
|
|
direction CallDirection @default(OUTBOUND)
|
|
status CallStatus @default(INITIATED)
|
|
startedAt DateTime
|
|
answeredAt DateTime?
|
|
endedAt DateTime?
|
|
duration Int?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
caller User @relation(fields: [callerId], references: [id], onDelete: Cascade)
|
|
client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([clubId])
|
|
@@index([callerId])
|
|
@@index([clientId])
|
|
@@index([clubId, startedAt])
|
|
@@map("sip_calls")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 17. Notification
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Notification {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
userId String
|
|
type NotificationType
|
|
title String
|
|
body String
|
|
data Json?
|
|
isRead Boolean @default(false)
|
|
sentAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([userId])
|
|
@@index([userId, isRead])
|
|
@@index([userId, createdAt])
|
|
@@map("notifications")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 18. WebhookSubscription
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model WebhookSubscription {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
url String
|
|
secret String
|
|
events String[]
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
logs WebhookLog[]
|
|
|
|
@@index([clubId])
|
|
@@map("webhook_subscriptions")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 19. WebhookLog
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model WebhookLog {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
subscriptionId String
|
|
event String
|
|
payload Json
|
|
statusCode Int?
|
|
response String?
|
|
attempt Int @default(1)
|
|
deliveredAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
subscription WebhookSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([subscriptionId])
|
|
@@map("webhook_logs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 20. ClubModule
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ClubModule {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
moduleId String
|
|
enabled Boolean @default(false)
|
|
limitsJson Json?
|
|
usageJson Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clubId, moduleId])
|
|
@@index([clubId])
|
|
@@map("club_modules")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 21. License
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model License {
|
|
id String @id @default(uuid())
|
|
clubId String @unique
|
|
type LicenseType @default(STARTER)
|
|
startDate DateTime
|
|
endDate DateTime
|
|
isActive Boolean @default(true)
|
|
gracePeriodEnd DateTime?
|
|
maxUsers Int
|
|
maxClients Int
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("licenses")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 22. UsageRecord
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model UsageRecord {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
period DateTime
|
|
activeUsers Int @default(0)
|
|
clients Int @default(0)
|
|
callStorageGb Decimal @default(0) @db.Decimal(10, 4)
|
|
webhooksSent Int @default(0)
|
|
pushSent Int @default(0)
|
|
apiRequests Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clubId, period])
|
|
@@index([clubId])
|
|
@@map("usage_records")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 23. AuditLog
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model AuditLog {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
userId String?
|
|
action String
|
|
entity String
|
|
entityId String?
|
|
oldData Json?
|
|
newData Json?
|
|
ipAddress String?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: SetNull)
|
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([clubId])
|
|
@@index([userId])
|
|
@@index([entity, entityId])
|
|
@@index([createdAt])
|
|
@@map("audit_logs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 24. IntegrationConfig
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model IntegrationConfig {
|
|
id String @id @default(uuid())
|
|
clubId String @unique
|
|
provider String @default("1c")
|
|
apiUrl String?
|
|
apiKey String?
|
|
syncEnabled Boolean @default(false)
|
|
lastSyncAt DateTime?
|
|
settings Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
syncLogs SyncLog[]
|
|
|
|
@@map("integration_configs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 25. NotificationSetting
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model NotificationSetting {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
notificationType NotificationType
|
|
enabled Boolean @default(true)
|
|
customText String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clubId, notificationType])
|
|
@@index([clubId])
|
|
@@map("notification_settings")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 26. ClientGoal
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ClientGoal {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
clientId String
|
|
title String
|
|
isAchieved Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([clientId])
|
|
@@map("client_goals")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 27. ClientInjury
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ClientInjury {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
clientId String
|
|
description String
|
|
severity String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([clientId])
|
|
@@map("client_injuries")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 28. ClientService (purchased services/packages)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model ClientService {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
clientId String
|
|
serviceId String
|
|
sessionsTotal Int?
|
|
sessionsUsed Int @default(0)
|
|
startsAt DateTime?
|
|
expiresAt DateTime?
|
|
status ClientServiceStatus @default(ACTIVE)
|
|
externalId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([clientId])
|
|
@@index([serviceId])
|
|
@@index([expiresAt])
|
|
@@index([clientId, status])
|
|
@@map("client_services")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 29. SipConfig (per-club SIP settings)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model SipConfig {
|
|
id String @id @default(uuid())
|
|
clubId String @unique
|
|
sipServer String
|
|
sipPort Int @default(5060)
|
|
transport SipTransport @default(TLS)
|
|
realm String?
|
|
stunServer String?
|
|
turnServer String?
|
|
turnUsername String?
|
|
turnPassword String?
|
|
callerId String
|
|
codecs String[] @default(["opus", "g711_alaw", "g711_ulaw"])
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("sip_configs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 30. SipAccount (per-user SIP credentials)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model SipAccount {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
userId String @unique
|
|
sipUsername String
|
|
sipPassword String
|
|
displayName String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@map("sip_accounts")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 31. PushToken (device push tokens)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model PushToken {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
userId String
|
|
token String
|
|
platform Platform
|
|
deviceId String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([userId])
|
|
@@map("push_tokens")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 32. SyncLog (1C sync history)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model SyncLog {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
integrationId String
|
|
direction String
|
|
entityType String
|
|
recordsProcessed Int @default(0)
|
|
recordsFailed Int @default(0)
|
|
errorDetails String?
|
|
startedAt DateTime
|
|
completedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
integration IntegrationConfig @relation(fields: [integrationId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([integrationId])
|
|
@@map("sync_logs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 33. Report (generated PDF reports)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model Report {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
generatedById String
|
|
type String
|
|
title String
|
|
parametersJson Json?
|
|
fileUrl String?
|
|
status ReportStatus @default(PENDING)
|
|
generatedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
generatedBy User @relation(fields: [generatedById], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@index([generatedById])
|
|
@@map("reports")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 34. RatingPeriod (trainer rating snapshots)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model RatingPeriod {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
userId String
|
|
period DateTime
|
|
conversionNewToAssigned Decimal? @db.Decimal(5, 2)
|
|
conversionAssignedToCompleted Decimal? @db.Decimal(5, 2)
|
|
conversionCompletedToRegular Decimal? @db.Decimal(5, 2)
|
|
totalTrainings Int @default(0)
|
|
totalSalesAmount Decimal @default(0) @db.Decimal(12, 2)
|
|
totalClients Int @default(0)
|
|
newClients Int @default(0)
|
|
rank Int?
|
|
score Decimal? @db.Decimal(5, 2)
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clubId, userId, period])
|
|
@@index([clubId])
|
|
@@index([userId])
|
|
@@map("rating_periods")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 35. EmailTemplate
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model EmailTemplate {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
type EmailType
|
|
subject String
|
|
bodyHtml String
|
|
bodyText String?
|
|
variables String[] @default([])
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
emailLogs EmailLog[]
|
|
|
|
@@unique([clubId, type])
|
|
@@index([clubId])
|
|
@@map("email_templates")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 36. EmailLog
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model EmailLog {
|
|
id String @id @default(uuid())
|
|
clubId String
|
|
templateId String?
|
|
recipientEmail String
|
|
recipientClientId String?
|
|
subject String
|
|
type EmailType
|
|
status EmailStatus @default(PENDING)
|
|
providerMessageId String?
|
|
errorMessage String?
|
|
sentAt DateTime?
|
|
deliveredAt DateTime?
|
|
openedAt DateTime?
|
|
clickedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
// Relations
|
|
club Club @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
template EmailTemplate? @relation(fields: [templateId], references: [id], onDelete: SetNull)
|
|
client Client? @relation(fields: [recipientClientId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([clubId])
|
|
@@index([templateId])
|
|
@@index([recipientClientId])
|
|
@@index([clubId, type])
|
|
@@index([clubId, status])
|
|
@@map("email_logs")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 37. PlatformTheme (platform-level active theme — single row, no clubId)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
model PlatformTheme {
|
|
id String @id @default(uuid())
|
|
themeId String
|
|
setById String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([createdAt])
|
|
|
|
@@map("platform_themes")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CRM Module
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// 38. CrmPipeline
|
|
model CrmPipeline {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
name String
|
|
isDefault Boolean @default(false)
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
stages CrmStage[]
|
|
deals CrmDeal[]
|
|
|
|
@@index([clubId])
|
|
@@map("crm_pipelines")
|
|
}
|
|
|
|
// 39. CrmStage
|
|
model CrmStage {
|
|
id String @id @default(uuid())
|
|
pipelineId String
|
|
name String
|
|
position Int
|
|
color String @default("#3B82F6")
|
|
type CrmStageType @default(OPEN)
|
|
autoActivityType CrmActivityType?
|
|
autoActivityDays Int?
|
|
staleDays Int?
|
|
isDefault Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
pipeline CrmPipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
deals CrmDeal[]
|
|
stageHistoryFrom CrmStageHistory[] @relation("StageHistoryFrom")
|
|
stageHistoryTo CrmStageHistory[] @relation("StageHistoryTo")
|
|
|
|
@@index([pipelineId])
|
|
@@index([pipelineId, position])
|
|
@@map("crm_stages")
|
|
}
|
|
|
|
// 40. CrmDeal
|
|
model CrmDeal {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
pipelineId String
|
|
stageId String
|
|
assigneeId String?
|
|
|
|
title String
|
|
amount Decimal? @db.Decimal(12, 2)
|
|
currency String @default("RUB")
|
|
|
|
// Contact info
|
|
contactName String
|
|
contactPhone String?
|
|
contactEmail String?
|
|
contactTelegram String?
|
|
contactWhatsapp String?
|
|
contactVk String?
|
|
|
|
// Company requisites
|
|
companyName String?
|
|
companyInn String?
|
|
companyKpp String?
|
|
companyOgrn String?
|
|
companyLegalAddress String?
|
|
companyBankAccount String?
|
|
companyBik String?
|
|
companyBankName String?
|
|
|
|
// Meta
|
|
source CrmDealSource @default(MANUAL)
|
|
probability Int?
|
|
expectedCloseDate DateTime?
|
|
closedAt DateTime?
|
|
lostReasonId String?
|
|
lostComment String?
|
|
metadata Json?
|
|
deletedAt DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
createdById String?
|
|
|
|
// Relations
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
pipeline CrmPipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
stage CrmStage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
|
assignee User? @relation("CrmDealAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
|
createdBy User? @relation("CrmDealCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
|
lostReason CrmLostReason? @relation(fields: [lostReasonId], references: [id], onDelete: SetNull)
|
|
|
|
timeline CrmTimeline[]
|
|
activities CrmActivity[]
|
|
trainings CrmTraining[]
|
|
stageHistory CrmStageHistory[]
|
|
|
|
@@index([clubId])
|
|
@@index([pipelineId])
|
|
@@index([stageId])
|
|
@@index([assigneeId])
|
|
@@index([clubId, stageId])
|
|
@@index([clubId, createdAt])
|
|
@@index([clubId, pipelineId, stageId])
|
|
@@map("crm_deals")
|
|
}
|
|
|
|
// 41. CrmTraining
|
|
model CrmTraining {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
dealId String
|
|
trainerId String?
|
|
clientName String
|
|
type CrmTrainingType @default(PERSONAL)
|
|
status CrmTrainingStatus @default(CRM_PLANNED)
|
|
scheduledAt DateTime
|
|
duration Int @default(60)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
deal CrmDeal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
trainer User? @relation("CrmTrainingTrainer", fields: [trainerId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([clubId])
|
|
@@index([dealId])
|
|
@@index([trainerId])
|
|
@@map("crm_trainings")
|
|
}
|
|
|
|
// 42. CrmTimeline
|
|
model CrmTimeline {
|
|
id String @id @default(uuid())
|
|
dealId String
|
|
userId String?
|
|
type CrmTimelineType
|
|
subject String?
|
|
content String?
|
|
metadata Json?
|
|
pinnedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
deal CrmDeal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
user User? @relation("CrmTimelineAuthor", fields: [userId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([dealId])
|
|
@@index([dealId, createdAt])
|
|
@@map("crm_timeline")
|
|
}
|
|
|
|
// 43. CrmActivity
|
|
model CrmActivity {
|
|
id String @id @default(uuid())
|
|
dealId String?
|
|
assigneeId String
|
|
type CrmActivityType @default(CALL)
|
|
subject String
|
|
description String?
|
|
scheduledAt DateTime
|
|
deadline DateTime?
|
|
priority CrmActivityPriority @default(MEDIUM)
|
|
completedAt DateTime?
|
|
completedById String?
|
|
result String?
|
|
channel CrmMessageChannel?
|
|
isOverdue Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
deal CrmDeal? @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
assignee User @relation("CrmActivityAssignee", fields: [assigneeId], references: [id], onDelete: Cascade)
|
|
completedBy User? @relation("CrmActivityCompleter", fields: [completedById], references: [id], onDelete: SetNull)
|
|
|
|
@@index([dealId])
|
|
@@index([assigneeId])
|
|
@@index([assigneeId, scheduledAt])
|
|
@@index([assigneeId, completedAt])
|
|
@@map("crm_activities")
|
|
}
|
|
|
|
// 44. CrmStageHistory
|
|
model CrmStageHistory {
|
|
id String @id @default(uuid())
|
|
dealId String
|
|
fromStageId String?
|
|
toStageId String
|
|
movedById String?
|
|
duration Int?
|
|
movedAt DateTime @default(now())
|
|
|
|
deal CrmDeal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
fromStage CrmStage? @relation("StageHistoryFrom", fields: [fromStageId], references: [id], onDelete: SetNull)
|
|
toStage CrmStage @relation("StageHistoryTo", fields: [toStageId], references: [id], onDelete: Cascade)
|
|
movedBy User? @relation("CrmStageHistoryMover", fields: [movedById], references: [id], onDelete: SetNull)
|
|
|
|
@@index([dealId])
|
|
@@index([dealId, movedAt])
|
|
@@map("crm_stage_history")
|
|
}
|
|
|
|
// 45. CrmLostReason
|
|
model CrmLostReason {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
name String
|
|
position Int @default(0)
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
deals CrmDeal[]
|
|
|
|
@@index([clubId])
|
|
@@map("crm_lost_reasons")
|
|
}
|
|
|
|
// 46. CrmUserField
|
|
model CrmUserField {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
entityType String
|
|
name String
|
|
fieldName String
|
|
type CrmUserFieldType @default(STRING)
|
|
listOptions Json?
|
|
isRequired Boolean @default(false)
|
|
isMultiple Boolean @default(false)
|
|
showToTrainer Boolean @default(false)
|
|
position Int @default(0)
|
|
defaultValue String?
|
|
description String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
values CrmUserFieldValue[]
|
|
|
|
@@unique([clubId, entityType, fieldName])
|
|
@@index([clubId, entityType])
|
|
@@map("crm_user_fields")
|
|
}
|
|
|
|
// 47. CrmUserFieldValue
|
|
model CrmUserFieldValue {
|
|
id String @id @default(uuid())
|
|
fieldId String
|
|
entityId String
|
|
entityType String
|
|
value String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
field CrmUserField @relation(fields: [fieldId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([fieldId, entityId])
|
|
@@index([fieldId])
|
|
@@index([entityId, entityType])
|
|
@@map("crm_user_field_values")
|
|
}
|
|
|
|
// 48. CrmWebhookEndpoint
|
|
model CrmWebhookEndpoint {
|
|
id String @id @default(uuid())
|
|
clubId String?
|
|
pipelineId String?
|
|
stageId String?
|
|
token String @unique
|
|
name String
|
|
isActive Boolean @default(true)
|
|
fieldMappings Json?
|
|
listMappings Json?
|
|
defaultAssigneeId String?
|
|
antifraudEnabled Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
club Club? @relation(fields: [clubId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([clubId])
|
|
@@map("crm_webhook_endpoints")
|
|
}
|