745 lines
27 KiB
Plaintext
745 lines
27 KiB
Plaintext
// VA-P28b / M040b: tenant-less platform sales workspace.
|
|
//
|
|
// These records belong to HiveGrid's own sales operation. They never carry a
|
|
// tenantId and are protected by the platform-admin RLS contract. A won deal may
|
|
// later reference a Tenant explicitly, but that does not turn sales data into
|
|
// tenant-owned CRM data.
|
|
|
|
enum AdminContactChannelKind {
|
|
email
|
|
phone
|
|
linkedin
|
|
website
|
|
other
|
|
}
|
|
|
|
enum AdminPipelineStageKind {
|
|
open
|
|
won
|
|
lost
|
|
}
|
|
|
|
enum AdminDealStatus {
|
|
open
|
|
won
|
|
lost
|
|
}
|
|
|
|
enum AdminActivityType {
|
|
configuration_changed
|
|
data_job_requested
|
|
data_job_updated
|
|
company_created
|
|
company_updated
|
|
contact_created
|
|
contact_updated
|
|
note_added
|
|
lead_updated
|
|
lead_converted
|
|
lead_assigned
|
|
lead_scored
|
|
lead_enriched
|
|
lead_merged
|
|
deal_created
|
|
deal_updated
|
|
deal_stage_changed
|
|
deal_won
|
|
deal_lost
|
|
task_created
|
|
task_completed
|
|
email
|
|
call
|
|
meeting
|
|
sequence
|
|
quote
|
|
signature
|
|
tenant_linked
|
|
}
|
|
|
|
enum AdminLeadAssignmentStrategy {
|
|
fixed
|
|
round_robin
|
|
}
|
|
|
|
enum AdminLeadRuleOperator {
|
|
equals
|
|
not_equals
|
|
contains
|
|
present
|
|
}
|
|
|
|
enum AdminLeadScoreField {
|
|
source
|
|
region
|
|
country
|
|
company
|
|
has_email
|
|
has_phone
|
|
}
|
|
|
|
enum AdminDuplicateCandidateStatus {
|
|
open
|
|
dismissed
|
|
merged
|
|
}
|
|
|
|
enum AdminLeadEnrichmentStatus {
|
|
pending
|
|
complete
|
|
unavailable
|
|
failed
|
|
}
|
|
|
|
enum AdminTaskPriority {
|
|
low
|
|
normal
|
|
high
|
|
urgent
|
|
}
|
|
|
|
enum AdminTaskStatus {
|
|
open
|
|
completed
|
|
cancelled
|
|
}
|
|
|
|
enum AdminTaskSource {
|
|
manual
|
|
email
|
|
call
|
|
}
|
|
|
|
enum AdminTaskRecurrenceFrequency {
|
|
daily
|
|
weekly
|
|
monthly
|
|
}
|
|
|
|
enum AdminSalesCustomFieldTarget {
|
|
contact
|
|
deal
|
|
}
|
|
|
|
enum AdminSalesCustomFieldType {
|
|
text
|
|
number
|
|
boolean
|
|
date
|
|
single_select
|
|
}
|
|
|
|
enum AdminSalesDataJobStatus {
|
|
pending
|
|
running
|
|
completed
|
|
completed_with_errors
|
|
failed
|
|
}
|
|
|
|
model AdminCompany {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
name String
|
|
normalizedName String
|
|
legalName String?
|
|
domain String?
|
|
website String?
|
|
phone String?
|
|
normalizedPhone String?
|
|
region String?
|
|
countryCode String?
|
|
industry String?
|
|
employeeRange String?
|
|
ownerAdminUserId String?
|
|
sourceLeadId String? @unique
|
|
enrichmentMetadata Json?
|
|
|
|
sourceLead AdminLead? @relation("AdminLeadConvertedCompany", fields: [sourceLeadId], references: [id], onDelete: SetNull)
|
|
contacts AdminContact[]
|
|
deals AdminDeal[]
|
|
activities AdminActivity[]
|
|
|
|
@@index([normalizedName, archivedAt], name: "admin_company_name_archive_idx")
|
|
@@index([domain, archivedAt], name: "admin_company_domain_archive_idx")
|
|
@@index([ownerAdminUserId, archivedAt], name: "admin_company_owner_archive_idx")
|
|
}
|
|
|
|
model AdminContact {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
companyId String?
|
|
firstName String
|
|
lastName String
|
|
jobTitle String?
|
|
email String?
|
|
normalizedEmail String?
|
|
phone String?
|
|
normalizedPhone String?
|
|
locale String?
|
|
timeZone String?
|
|
ownerAdminUserId String?
|
|
sourceLeadId String? @unique
|
|
enrichmentMetadata Json?
|
|
|
|
sourceLead AdminLead? @relation("AdminLeadConvertedContact", fields: [sourceLeadId], references: [id], onDelete: SetNull)
|
|
company AdminCompany? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
|
channels AdminContactChannel[]
|
|
deals AdminDealContact[]
|
|
tasks AdminTask[]
|
|
tags AdminContactTag[]
|
|
customFieldValues AdminCustomFieldValue[]
|
|
notes AdminContactNote[]
|
|
activities AdminActivity[]
|
|
emailMessages AdminEmailMessage[]
|
|
callSessions AdminCallSession[]
|
|
callMatchCandidates AdminCallMatchCandidate[]
|
|
dialerEntries AdminDialerEntry[]
|
|
outboundCallRequests AdminOutboundCallRequest[]
|
|
bookings AdminBooking[]
|
|
doNotCall Boolean @default(false)
|
|
doNotCallReason String?
|
|
doNotCallAt DateTime?
|
|
|
|
@@index([companyId, archivedAt, lastName, firstName], name: "admin_contact_company_name_idx")
|
|
@@index([normalizedEmail, archivedAt], name: "admin_contact_email_archive_idx")
|
|
@@index([normalizedPhone, archivedAt], name: "admin_contact_phone_archive_idx")
|
|
@@index([ownerAdminUserId, archivedAt], name: "admin_contact_owner_archive_idx")
|
|
}
|
|
|
|
model AdminContactChannel {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
contactId String
|
|
kind AdminContactChannelKind
|
|
label String?
|
|
value String
|
|
normalizedValue String?
|
|
isPrimary Boolean @default(false)
|
|
|
|
contact AdminContact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([contactId, kind, normalizedValue], name: "admin_contact_channel_normalized_key")
|
|
@@index([kind, normalizedValue, archivedAt], name: "admin_contact_channel_lookup_idx")
|
|
@@index([contactId, isPrimary, archivedAt], name: "admin_contact_channel_primary_idx")
|
|
}
|
|
|
|
model AdminPipeline {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
name String
|
|
description String?
|
|
isDefault Boolean @default(false)
|
|
|
|
stages AdminPipelineStage[]
|
|
deals AdminDeal[]
|
|
lossReasons AdminDealLossReason[]
|
|
activities AdminActivity[]
|
|
|
|
@@index([archivedAt, name], name: "admin_pipeline_archive_name_idx")
|
|
@@index([isDefault, archivedAt], name: "admin_pipeline_default_idx")
|
|
}
|
|
|
|
model AdminPipelineStage {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
pipelineId String
|
|
name String
|
|
position Int
|
|
kind AdminPipelineStageKind @default(open)
|
|
probabilityBasisPoints Int
|
|
agingThresholdDays Int?
|
|
color String?
|
|
|
|
pipeline AdminPipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
deals AdminDeal[]
|
|
requiredFields AdminStageRequiredField[]
|
|
historyFrom AdminDealStageHistory[] @relation("AdminDealStageHistoryFrom")
|
|
historyTo AdminDealStageHistory[] @relation("AdminDealStageHistoryTo")
|
|
|
|
@@unique([pipelineId, position], name: "admin_pipeline_stage_position_key")
|
|
@@unique([pipelineId, name], name: "admin_pipeline_stage_name_key")
|
|
@@index([pipelineId, kind, archivedAt], name: "admin_pipeline_stage_kind_idx")
|
|
}
|
|
|
|
model AdminStageRequiredField {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
stageId String
|
|
fieldKey String
|
|
|
|
stage AdminPipelineStage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([stageId, fieldKey], name: "admin_stage_required_field_key")
|
|
}
|
|
|
|
model AdminDealLossReason {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
pipelineId String
|
|
label String
|
|
position Int
|
|
|
|
pipeline AdminPipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
deals AdminDeal[]
|
|
|
|
@@unique([pipelineId, label], name: "admin_deal_loss_reason_label_key")
|
|
@@unique([pipelineId, position], name: "admin_deal_loss_reason_position_key")
|
|
}
|
|
|
|
model AdminDeal {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
title String
|
|
pipelineId String
|
|
stageId String
|
|
companyId String?
|
|
sourceLeadId String? @unique
|
|
ownerAdminUserId String
|
|
status AdminDealStatus @default(open)
|
|
amountCents Int?
|
|
currencyCode String @default("EUR")
|
|
probabilityBasisPoints Int
|
|
expectedCloseDate DateTime?
|
|
nextStep String?
|
|
nextStepDueAt DateTime?
|
|
stageEnteredAt DateTime @default(now())
|
|
wonAt DateTime?
|
|
lostAt DateTime?
|
|
lossReasonId String?
|
|
lossReasonNote String?
|
|
promotedTenantId String?
|
|
promotedAt DateTime?
|
|
|
|
sourceLead AdminLead? @relation("AdminLeadConvertedDeal", fields: [sourceLeadId], references: [id], onDelete: SetNull)
|
|
pipeline AdminPipeline @relation(fields: [pipelineId], references: [id], onDelete: Restrict)
|
|
stage AdminPipelineStage @relation(fields: [stageId], references: [id], onDelete: Restrict)
|
|
company AdminCompany? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
|
lossReason AdminDealLossReason? @relation(fields: [lossReasonId], references: [id], onDelete: SetNull)
|
|
contacts AdminDealContact[]
|
|
history AdminDealStageHistory[]
|
|
tasks AdminTask[]
|
|
customFieldValues AdminCustomFieldValue[]
|
|
activities AdminActivity[]
|
|
emailMessages AdminEmailMessage[]
|
|
callSessions AdminCallSession[]
|
|
outboundCallRequests AdminOutboundCallRequest[]
|
|
bookings AdminBooking[]
|
|
|
|
@@index([pipelineId, stageId, stageEnteredAt], name: "admin_deal_pipeline_stage_age_idx")
|
|
@@index([ownerAdminUserId, status, expectedCloseDate], name: "admin_deal_owner_status_close_idx")
|
|
@@index([companyId, status], name: "admin_deal_company_status_idx")
|
|
@@index([promotedTenantId], name: "admin_deal_promoted_tenant_idx")
|
|
}
|
|
|
|
model AdminDealContact {
|
|
dealId String
|
|
contactId String
|
|
createdAt DateTime @default(now())
|
|
role String?
|
|
isPrimary Boolean @default(false)
|
|
|
|
deal AdminDeal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
contact AdminContact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([dealId, contactId])
|
|
@@index([contactId, createdAt], name: "admin_deal_contact_contact_idx")
|
|
@@index([dealId, isPrimary], name: "admin_deal_contact_primary_idx")
|
|
}
|
|
|
|
/// Immutable record of every deal stage transition. The first history row has
|
|
/// no fromStageId and represents entry into the pipeline.
|
|
model AdminDealStageHistory {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
dealId String
|
|
fromStageId String?
|
|
toStageId String
|
|
actorAdminUserId String
|
|
enteredAt DateTime
|
|
exitedAt DateTime?
|
|
reason String?
|
|
|
|
deal AdminDeal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
fromStage AdminPipelineStage? @relation("AdminDealStageHistoryFrom", fields: [fromStageId], references: [id], onDelete: Restrict)
|
|
toStage AdminPipelineStage @relation("AdminDealStageHistoryTo", fields: [toStageId], references: [id], onDelete: Restrict)
|
|
|
|
@@index([dealId, enteredAt(sort: Desc)], name: "admin_deal_stage_history_deal_time_idx")
|
|
@@index([toStageId, enteredAt], name: "admin_deal_stage_history_stage_time_idx")
|
|
@@index([actorAdminUserId, enteredAt(sort: Desc)], name: "admin_deal_stage_history_actor_time_idx")
|
|
}
|
|
|
|
model AdminTaskRecurrence {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
createdByAdminUserId String
|
|
frequency AdminTaskRecurrenceFrequency
|
|
interval Int @default(1)
|
|
timeZone String
|
|
weekDays Int[] @default([])
|
|
dayOfMonth Int?
|
|
reminderOffsetMinutes Int?
|
|
endsAt DateTime?
|
|
|
|
tasks AdminTask[]
|
|
|
|
@@index([archivedAt, frequency, endsAt], name: "admin_task_recurrence_active_idx")
|
|
@@index([createdByAdminUserId, archivedAt], name: "admin_task_recurrence_creator_idx")
|
|
}
|
|
|
|
model AdminTask {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
title String
|
|
description String?
|
|
contactId String?
|
|
dealId String?
|
|
assignedToAdminUserId String
|
|
createdByAdminUserId String
|
|
dueAt DateTime
|
|
timeZone String
|
|
reminderAt DateTime?
|
|
reminderSentAt DateTime?
|
|
priority AdminTaskPriority @default(normal)
|
|
status AdminTaskStatus @default(open)
|
|
completedAt DateTime?
|
|
source AdminTaskSource @default(manual)
|
|
sourceActivityId String? @unique
|
|
recurrenceId String?
|
|
recurrenceSequence Int @default(0)
|
|
|
|
contact AdminContact? @relation(fields: [contactId], references: [id], onDelete: SetNull)
|
|
deal AdminDeal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
|
|
recurrence AdminTaskRecurrence? @relation(fields: [recurrenceId], references: [id], onDelete: Restrict)
|
|
sourceActivity AdminActivity? @relation("AdminTaskSourceActivity", fields: [sourceActivityId], references: [id], onDelete: SetNull)
|
|
activities AdminActivity[] @relation("AdminActivityTask")
|
|
callbackCall AdminCallSession?
|
|
|
|
@@unique([recurrenceId, recurrenceSequence], name: "admin_task_recurrence_sequence_key")
|
|
@@index([assignedToAdminUserId, status, dueAt], name: "admin_task_owner_status_due_idx")
|
|
@@index([status, reminderAt, reminderSentAt], name: "admin_task_reminder_due_idx")
|
|
@@index([contactId, status, dueAt], name: "admin_task_contact_due_idx")
|
|
@@index([dealId, status, dueAt], name: "admin_task_deal_due_idx")
|
|
}
|
|
|
|
model AdminTag {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
name String
|
|
normalizedName String @unique
|
|
color String?
|
|
createdByAdminUserId String
|
|
|
|
contacts AdminContactTag[]
|
|
|
|
@@index([archivedAt, name], name: "admin_tag_archive_name_idx")
|
|
}
|
|
|
|
model AdminContactTag {
|
|
contactId String
|
|
tagId String
|
|
createdAt DateTime @default(now())
|
|
createdByAdminUserId String
|
|
|
|
contact AdminContact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
tag AdminTag @relation(fields: [tagId], references: [id], onDelete: Restrict)
|
|
|
|
@@id([contactId, tagId])
|
|
@@index([tagId, createdAt], name: "admin_contact_tag_tag_time_idx")
|
|
}
|
|
|
|
model AdminCustomFieldDefinition {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
target AdminSalesCustomFieldTarget
|
|
key String
|
|
label String
|
|
type AdminSalesCustomFieldType
|
|
options String[] @default([])
|
|
required Boolean @default(false)
|
|
position Int
|
|
createdByAdminUserId String
|
|
|
|
values AdminCustomFieldValue[]
|
|
|
|
@@unique([target, key], name: "admin_custom_field_target_key")
|
|
@@unique([target, position], name: "admin_custom_field_target_position")
|
|
@@index([target, archivedAt, position], name: "admin_custom_field_target_active_idx")
|
|
}
|
|
|
|
model AdminCustomFieldValue {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
definitionId String
|
|
contactId String?
|
|
dealId String?
|
|
textValue String?
|
|
numberValue Float?
|
|
booleanValue Boolean?
|
|
dateValue DateTime?
|
|
optionValue String?
|
|
updatedByAdminUserId String
|
|
|
|
definition AdminCustomFieldDefinition @relation(fields: [definitionId], references: [id], onDelete: Restrict)
|
|
contact AdminContact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
deal AdminDeal? @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([definitionId, contactId], name: "admin_custom_field_contact_value")
|
|
@@unique([definitionId, dealId], name: "admin_custom_field_deal_value")
|
|
@@index([contactId, definitionId], name: "admin_custom_field_contact_idx")
|
|
@@index([dealId, definitionId], name: "admin_custom_field_deal_idx")
|
|
}
|
|
|
|
model AdminContactNote {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
contactId String
|
|
body String
|
|
createdByAdminUserId String
|
|
|
|
contact AdminContact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
activities AdminActivity[]
|
|
|
|
@@index([contactId, createdAt(sort: Desc)], name: "admin_contact_note_contact_time_idx")
|
|
@@index([createdByAdminUserId, createdAt(sort: Desc)], name: "admin_contact_note_actor_time_idx")
|
|
}
|
|
|
|
model AdminImportJob {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
requestedByAdminUserId String
|
|
status AdminSalesDataJobStatus @default(pending)
|
|
fileName String
|
|
sourceBucket String
|
|
sourceKey String
|
|
sourceHeaders String[] @default([])
|
|
fieldMapping String[] @default([])
|
|
totalRows Int @default(0)
|
|
acceptedRows Int @default(0)
|
|
rejectedRows Int @default(0)
|
|
errorReportBucket String?
|
|
errorReportKey String?
|
|
queuedAt DateTime?
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
errorCode String?
|
|
errorMessage String?
|
|
idempotencyKey String @unique
|
|
|
|
errors AdminImportRowError[]
|
|
|
|
@@index([status, createdAt], name: "admin_import_job_status_time_idx")
|
|
@@index([requestedByAdminUserId, createdAt(sort: Desc)], name: "admin_import_job_requester_time_idx")
|
|
}
|
|
|
|
model AdminImportRowError {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
importJobId String
|
|
rowNumber Int
|
|
fieldKey String?
|
|
code String
|
|
message String
|
|
rawValues String[] @default([])
|
|
|
|
importJob AdminImportJob @relation(fields: [importJobId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([importJobId, rowNumber], name: "admin_import_row_error_job_row_idx")
|
|
}
|
|
|
|
model AdminExportJob {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
requestedByAdminUserId String
|
|
status AdminSalesDataJobStatus @default(pending)
|
|
selectedContactIds String[] @default([])
|
|
artifactBucket String?
|
|
artifactKey String?
|
|
rowCount Int @default(0)
|
|
queuedAt DateTime?
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
expiresAt DateTime?
|
|
errorCode String?
|
|
errorMessage String?
|
|
idempotencyKey String @unique
|
|
|
|
@@index([status, createdAt], name: "admin_export_job_status_time_idx")
|
|
@@index([requestedByAdminUserId, createdAt(sort: Desc)], name: "admin_export_job_requester_time_idx")
|
|
}
|
|
|
|
/// Append-only business timeline shared by leads, contacts, companies and
|
|
/// deals. At least one context id is required by the repository contract.
|
|
model AdminActivity {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
type AdminActivityType
|
|
actorAdminUserId String
|
|
leadId String?
|
|
pipelineId String?
|
|
companyId String?
|
|
contactId String?
|
|
dealId String?
|
|
taskId String?
|
|
noteId String? @unique
|
|
emailMessageId String?
|
|
callSessionId String?
|
|
bookingId String?
|
|
summary String
|
|
payload Json
|
|
occurredAt DateTime @default(now())
|
|
|
|
lead AdminLead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
|
pipeline AdminPipeline? @relation(fields: [pipelineId], references: [id], onDelete: SetNull)
|
|
company AdminCompany? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
|
contact AdminContact? @relation(fields: [contactId], references: [id], onDelete: SetNull)
|
|
deal AdminDeal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
|
|
task AdminTask? @relation("AdminActivityTask", fields: [taskId], references: [id], onDelete: SetNull)
|
|
sourcedTask AdminTask? @relation("AdminTaskSourceActivity")
|
|
note AdminContactNote? @relation(fields: [noteId], references: [id], onDelete: SetNull)
|
|
emailMessage AdminEmailMessage? @relation(fields: [emailMessageId], references: [id], onDelete: SetNull)
|
|
callSession AdminCallSession? @relation(fields: [callSessionId], references: [id], onDelete: SetNull)
|
|
booking AdminBooking? @relation(fields: [bookingId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([leadId, occurredAt(sort: Desc)], name: "admin_activity_lead_time_idx")
|
|
@@index([pipelineId, occurredAt(sort: Desc)], name: "admin_activity_pipeline_time_idx")
|
|
@@index([companyId, occurredAt(sort: Desc)], name: "admin_activity_company_time_idx")
|
|
@@index([contactId, occurredAt(sort: Desc)], name: "admin_activity_contact_time_idx")
|
|
@@index([dealId, occurredAt(sort: Desc)], name: "admin_activity_deal_time_idx")
|
|
@@index([taskId, occurredAt(sort: Desc)], name: "admin_activity_task_time_idx")
|
|
@@index([actorAdminUserId, occurredAt(sort: Desc)], name: "admin_activity_actor_time_idx")
|
|
@@index([type, occurredAt(sort: Desc)], name: "admin_activity_type_time_idx")
|
|
@@index([emailMessageId, occurredAt(sort: Desc)], name: "admin_activity_email_message_time_idx")
|
|
@@index([callSessionId, occurredAt(sort: Desc)], name: "admin_activity_call_session_time_idx")
|
|
@@index([bookingId, occurredAt(sort: Desc)], name: "admin_activity_booking_time_idx")
|
|
}
|
|
|
|
/// Ordered, deterministic assignment rule. `sourceId` and `region` are ANDed;
|
|
/// a null criterion is a wildcard. The mutable cursor is locked together with
|
|
/// the lead so concurrent round-robin assignments cannot select the same slot.
|
|
model AdminLeadAssignmentRule {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
name String
|
|
active Boolean @default(true)
|
|
priority Int
|
|
sourceId String?
|
|
region String?
|
|
strategy AdminLeadAssignmentStrategy
|
|
fixedAssigneeAdminUserId String?
|
|
roundRobinAdminUserIds String[] @default([])
|
|
nextAssigneeIndex Int @default(0)
|
|
lastAssignedAt DateTime?
|
|
|
|
@@unique([priority, name], name: "admin_lead_assignment_rule_priority_name_key")
|
|
@@index([active, archivedAt, priority, id], name: "admin_lead_assignment_rule_resolution_idx")
|
|
@@index([sourceId, region, active], name: "admin_lead_assignment_rule_match_idx")
|
|
}
|
|
|
|
/// Versionable score rule. Rules are additive and evaluated in stable
|
|
/// priority/id order. The score snapshot on AdminLeadQualification records the
|
|
/// rule-set version so historical decisions stay explainable.
|
|
model AdminLeadScoreRule {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
name String
|
|
active Boolean @default(true)
|
|
priority Int
|
|
field AdminLeadScoreField
|
|
operator AdminLeadRuleOperator
|
|
value String?
|
|
points Int
|
|
version Int @default(1)
|
|
|
|
@@unique([priority, name], name: "admin_lead_score_rule_priority_name_key")
|
|
@@index([active, archivedAt, priority, id], name: "admin_lead_score_rule_resolution_idx")
|
|
}
|
|
|
|
model AdminLeadQualification {
|
|
leadId String @id
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
score Int @default(0)
|
|
scoreBand String
|
|
scoreVersion Int @default(1)
|
|
scoreBreakdown Json
|
|
enrichmentStatus AdminLeadEnrichmentStatus @default(pending)
|
|
enrichmentProvider String?
|
|
enrichmentData Json?
|
|
enrichedAt DateTime?
|
|
|
|
lead AdminLead @relation(fields: [leadId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([score, updatedAt(sort: Desc)], name: "admin_lead_qualification_score_idx")
|
|
@@index([enrichmentStatus, updatedAt], name: "admin_lead_qualification_enrichment_idx")
|
|
}
|
|
|
|
model AdminDuplicateCandidate {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
primaryLeadId String
|
|
duplicateLeadId String
|
|
status AdminDuplicateCandidateStatus @default(open)
|
|
matchKeys String[] @default([])
|
|
confidenceBasisPoints Int
|
|
resolvedAt DateTime?
|
|
resolvedByAdminUserId String?
|
|
resolutionReason String?
|
|
|
|
primaryLead AdminLead @relation("AdminDuplicatePrimaryLead", fields: [primaryLeadId], references: [id], onDelete: Cascade)
|
|
duplicateLead AdminLead @relation("AdminDuplicateSecondaryLead", fields: [duplicateLeadId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([primaryLeadId, duplicateLeadId], name: "admin_duplicate_candidate_pair_key")
|
|
@@index([status, confidenceBasisPoints(sort: Desc), createdAt], name: "admin_duplicate_candidate_queue_idx")
|
|
@@index([duplicateLeadId, status], name: "admin_duplicate_candidate_duplicate_idx")
|
|
}
|
|
|
|
/// Append-only record of a controlled lead merge. The losing lead is archived,
|
|
/// never deleted, and its original activity stream remains intact.
|
|
model AdminMergeRecord {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
survivorLeadId String
|
|
mergedLeadId String
|
|
actorAdminUserId String
|
|
reason String
|
|
survivorSnapshot Json
|
|
mergedSnapshot Json
|
|
|
|
survivorLead AdminLead @relation("AdminMergeSurvivorLead", fields: [survivorLeadId], references: [id], onDelete: Restrict)
|
|
mergedLead AdminLead @relation("AdminMergeMergedLead", fields: [mergedLeadId], references: [id], onDelete: Restrict)
|
|
|
|
@@unique([mergedLeadId], name: "admin_merge_record_merged_lead_key")
|
|
@@index([survivorLeadId, createdAt(sort: Desc)], name: "admin_merge_record_survivor_idx")
|
|
@@index([actorAdminUserId, createdAt(sort: Desc)], name: "admin_merge_record_actor_idx")
|
|
}
|