356 lines
13 KiB
Plaintext
356 lines
13 KiB
Plaintext
// VA-P28d: tenant-less telephony for HiveGrid's own admin sales workspace.
|
|
//
|
|
// Aircall credentials remain in AdminDataConnection. Every operator-owned row
|
|
// carries adminUserId so Postgres can enforce the platform-admin user context;
|
|
// worker writes use the reviewed worker branch of the same policies.
|
|
|
|
enum AdminCallDirection {
|
|
inbound
|
|
outbound
|
|
}
|
|
|
|
enum AdminCallStatus {
|
|
ringing
|
|
answered
|
|
completed
|
|
missed
|
|
failed
|
|
}
|
|
|
|
enum AdminCallMatchStatus {
|
|
unmatched
|
|
unique
|
|
ambiguous
|
|
confirmed
|
|
}
|
|
|
|
enum AdminCallParticipantKind {
|
|
admin
|
|
contact
|
|
lead
|
|
external
|
|
}
|
|
|
|
enum AdminCallRecordingStatus {
|
|
pending
|
|
processing
|
|
clean
|
|
rejected
|
|
expired
|
|
deleted
|
|
}
|
|
|
|
enum AdminDialerListStatus {
|
|
draft
|
|
active
|
|
paused
|
|
completed
|
|
}
|
|
|
|
enum AdminDialerEntryStatus {
|
|
pending
|
|
active
|
|
completed
|
|
skipped
|
|
blocked
|
|
}
|
|
|
|
enum AdminOutboundCallRequestStatus {
|
|
pending
|
|
processing
|
|
accepted
|
|
failed
|
|
cancelled
|
|
}
|
|
|
|
model AdminPhoneConnection {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
connectionId String @unique
|
|
providerCompanyId String?
|
|
providerUserId String?
|
|
providerUserEmail String?
|
|
defaultPhoneNumberId String?
|
|
webhookTokenHash String? @unique
|
|
webhookVerifiedAt DateTime?
|
|
lastEventAt DateTime?
|
|
|
|
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Restrict)
|
|
connection AdminDataConnection @relation(fields: [connectionId], references: [id], onDelete: Restrict)
|
|
defaultPhoneNumber AdminPhoneNumber? @relation("AdminDefaultPhoneNumber", fields: [defaultPhoneNumberId], references: [id], onDelete: SetNull)
|
|
phoneNumbers AdminPhoneNumber[] @relation("AdminPhoneConnectionNumbers")
|
|
calls AdminCallSession[]
|
|
outboundRequests AdminOutboundCallRequest[]
|
|
|
|
@@index([adminUserId, archivedAt], name: "admin_phone_connection_owner_archive_idx")
|
|
@@index([providerCompanyId, archivedAt], name: "admin_phone_connection_company_idx")
|
|
}
|
|
|
|
model AdminPhoneNumber {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
phoneConnectionId String
|
|
providerNumberId String
|
|
label String
|
|
e164 String
|
|
countryCode String?
|
|
selected Boolean @default(true)
|
|
|
|
phoneConnection AdminPhoneConnection @relation("AdminPhoneConnectionNumbers", fields: [phoneConnectionId], references: [id], onDelete: Cascade)
|
|
defaultFor AdminPhoneConnection[] @relation("AdminDefaultPhoneNumber")
|
|
|
|
@@unique([phoneConnectionId, providerNumberId], name: "admin_phone_number_provider_key")
|
|
@@index([adminUserId, selected, archivedAt], name: "admin_phone_number_owner_selected_idx")
|
|
@@index([e164, archivedAt], name: "admin_phone_number_e164_idx")
|
|
}
|
|
|
|
model AdminTelephonyPolicy {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String @unique
|
|
outboundEnabled Boolean @default(false)
|
|
recordingEnabled Boolean @default(false)
|
|
recordingConsentText String?
|
|
recordingConsentVersion String?
|
|
recordingConsentAt DateTime?
|
|
recordingRetentionDays Int @default(90)
|
|
dncEnforced Boolean @default(true)
|
|
dailyCallLimit Int?
|
|
workDayStartMinutes Int?
|
|
workDayEndMinutes Int?
|
|
workDays Int[] @default([])
|
|
timeZone String?
|
|
|
|
@@index([outboundEnabled, adminUserId], name: "admin_telephony_policy_outbound_idx")
|
|
@@index([recordingEnabled, adminUserId], name: "admin_telephony_policy_recording_idx")
|
|
}
|
|
|
|
model AdminCallDisposition {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
code String @unique
|
|
label String
|
|
position Int @unique
|
|
active Boolean @default(true)
|
|
isSystem Boolean @default(true)
|
|
|
|
calls AdminCallSession[]
|
|
|
|
@@index([active, archivedAt, position], name: "admin_call_disposition_active_idx")
|
|
}
|
|
|
|
model AdminCallSession {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
phoneConnectionId String
|
|
providerCallId String
|
|
direction AdminCallDirection
|
|
status AdminCallStatus
|
|
providerUserId String?
|
|
fromNumber String
|
|
toNumber String
|
|
externalNumber String
|
|
startedAt DateTime
|
|
answeredAt DateTime?
|
|
endedAt DateTime?
|
|
durationSeconds Int?
|
|
contactId String?
|
|
leadId String?
|
|
dealId String?
|
|
matchStatus AdminCallMatchStatus @default(unmatched)
|
|
matchConfidenceBasisPoints Int?
|
|
note String?
|
|
dispositionId String?
|
|
dispositionCodeSnapshot String?
|
|
dispositionLabelSnapshot String?
|
|
callbackTaskId String? @unique
|
|
recordingOffered Boolean @default(false)
|
|
recordingConsentConfirmedAt DateTime?
|
|
|
|
phoneConnection AdminPhoneConnection @relation(fields: [phoneConnectionId], references: [id], onDelete: Restrict)
|
|
contact AdminContact? @relation(fields: [contactId], references: [id], onDelete: SetNull)
|
|
lead AdminLead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
|
deal AdminDeal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
|
|
disposition AdminCallDisposition? @relation(fields: [dispositionId], references: [id], onDelete: SetNull)
|
|
callbackTask AdminTask? @relation(fields: [callbackTaskId], references: [id], onDelete: SetNull)
|
|
participants AdminCallParticipant[]
|
|
events AdminCallEvent[]
|
|
matchCandidates AdminCallMatchCandidate[]
|
|
recording AdminCallRecording?
|
|
dialerEntry AdminDialerEntry?
|
|
activities AdminActivity[]
|
|
|
|
@@unique([phoneConnectionId, providerCallId], name: "admin_call_provider_key")
|
|
@@index([adminUserId, startedAt(sort: Desc)], name: "admin_call_owner_started_idx")
|
|
@@index([contactId, startedAt(sort: Desc)], name: "admin_call_contact_started_idx")
|
|
@@index([leadId, startedAt(sort: Desc)], name: "admin_call_lead_started_idx")
|
|
@@index([dealId, startedAt(sort: Desc)], name: "admin_call_deal_started_idx")
|
|
@@index([matchStatus, startedAt(sort: Desc)], name: "admin_call_match_queue_idx")
|
|
@@index([status, startedAt], name: "admin_call_status_started_idx")
|
|
}
|
|
|
|
model AdminCallParticipant {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
callSessionId String
|
|
kind AdminCallParticipantKind
|
|
externalId String?
|
|
displayName String?
|
|
phone String
|
|
|
|
callSession AdminCallSession @relation(fields: [callSessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([callSessionId, kind, phone], name: "admin_call_participant_identity_key")
|
|
@@index([adminUserId, createdAt(sort: Desc)], name: "admin_call_participant_owner_idx")
|
|
}
|
|
|
|
model AdminCallEvent {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
callSessionId String
|
|
dedupeKey String @unique
|
|
providerEvent String
|
|
status AdminCallStatus
|
|
occurredAt DateTime
|
|
durationSeconds Int?
|
|
|
|
callSession AdminCallSession @relation(fields: [callSessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([adminUserId, occurredAt(sort: Desc)], name: "admin_call_event_owner_time_idx")
|
|
@@index([callSessionId, occurredAt], name: "admin_call_event_session_time_idx")
|
|
}
|
|
|
|
model AdminCallMatchCandidate {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
callSessionId String
|
|
contactId String?
|
|
leadId String?
|
|
confidenceBasisPoints Int
|
|
matchReason String
|
|
|
|
callSession AdminCallSession @relation(fields: [callSessionId], references: [id], onDelete: Cascade)
|
|
contact AdminContact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
lead AdminLead? @relation(fields: [leadId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([callSessionId, contactId], name: "admin_call_match_contact_key")
|
|
@@unique([callSessionId, leadId], name: "admin_call_match_lead_key")
|
|
@@index([adminUserId, createdAt(sort: Desc)], name: "admin_call_match_candidate_owner_idx")
|
|
}
|
|
|
|
model AdminCallRecording {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
callSessionId String @unique
|
|
status AdminCallRecordingStatus @default(pending)
|
|
providerUrlWrappedDek Bytes?
|
|
providerUrlCiphertext String?
|
|
providerUrlKeyVersion Int?
|
|
storageBucket String?
|
|
storageKey String?
|
|
encryptedSizeBytes Int?
|
|
checksumSha256 String?
|
|
wrappedDek Bytes?
|
|
iv Bytes?
|
|
authTag Bytes?
|
|
processingAt DateTime?
|
|
attemptCount Int @default(0)
|
|
lastErrorCode String?
|
|
scannedAt DateTime?
|
|
retainedUntil DateTime
|
|
expiredAt DateTime?
|
|
deletedAt DateTime?
|
|
rejectionCode String?
|
|
|
|
callSession AdminCallSession @relation(fields: [callSessionId], references: [id], onDelete: Restrict)
|
|
|
|
@@index([adminUserId, createdAt(sort: Desc)], name: "admin_call_recording_owner_idx")
|
|
@@index([status, retainedUntil], name: "admin_call_recording_retention_idx")
|
|
}
|
|
|
|
model AdminDialerList {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
name String
|
|
status AdminDialerListStatus @default(draft)
|
|
currentPosition Int?
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
|
|
entries AdminDialerEntry[]
|
|
|
|
@@index([adminUserId, status, updatedAt(sort: Desc)], name: "admin_dialer_list_owner_status_idx")
|
|
}
|
|
|
|
model AdminDialerEntry {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
dialerListId String
|
|
contactId String
|
|
position Int
|
|
status AdminDialerEntryStatus @default(pending)
|
|
callSessionId String? @unique
|
|
blockedReason String?
|
|
activatedAt DateTime?
|
|
completedAt DateTime?
|
|
|
|
dialerList AdminDialerList @relation(fields: [dialerListId], references: [id], onDelete: Cascade)
|
|
contact AdminContact @relation(fields: [contactId], references: [id], onDelete: Restrict)
|
|
callSession AdminCallSession? @relation(fields: [callSessionId], references: [id], onDelete: SetNull)
|
|
outboundRequests AdminOutboundCallRequest[]
|
|
|
|
@@unique([dialerListId, position], name: "admin_dialer_entry_position_key")
|
|
@@unique([dialerListId, contactId], name: "admin_dialer_entry_contact_key")
|
|
@@index([adminUserId, status, createdAt], name: "admin_dialer_entry_owner_status_idx")
|
|
}
|
|
|
|
model AdminOutboundCallRequest {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
phoneConnectionId String
|
|
contactId String
|
|
dealId String?
|
|
dialerEntryId String?
|
|
idempotencyKey String @unique
|
|
destinationE164 String
|
|
status AdminOutboundCallRequestStatus @default(pending)
|
|
requestedAt DateTime @default(now())
|
|
processingAt DateTime?
|
|
acceptedAt DateTime?
|
|
failedAt DateTime?
|
|
attemptCount Int @default(0)
|
|
errorCode String?
|
|
|
|
phoneConnection AdminPhoneConnection @relation(fields: [phoneConnectionId], references: [id], onDelete: Restrict)
|
|
contact AdminContact @relation(fields: [contactId], references: [id], onDelete: Restrict)
|
|
deal AdminDeal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
|
|
dialerEntry AdminDialerEntry? @relation(fields: [dialerEntryId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([adminUserId, status, requestedAt], name: "admin_outbound_call_owner_status_idx")
|
|
@@index([status, requestedAt], name: "admin_outbound_call_worker_queue_idx")
|
|
@@index([contactId, requestedAt(sort: Desc)], name: "admin_outbound_call_contact_idx")
|
|
}
|