Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/prisma/schema/adminSales.prisma
T

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")
}