generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } model Tenant { id String @id @default(cuid()) name String code String @unique status String @default("active") certificationStatus String @default("approved") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User[] enterpriseCertifications EnterpriseCertification[] operationLogs OperationLog[] fileObjects FileObject[] enterpriseBlacklists EnterpriseBlacklist[] accounts TenantAccount[] accountTransactions AccountTransaction[] rechargeOrders RechargeOrder[] smsBillingRecords SmsBillingRecord[] smsApplications SmsApplication[] smsSignatures SmsSignature[] smsDrainageInfos SmsDrainageInfo[] smsTemplates SmsTemplate[] auditRecords AuditRecord[] riskRules RiskRule[] smsSendTasks SmsSendTask[] riskHitRecords RiskHitRecord[] smsBatchTasks SmsBatchTask[] smsMessageRecords SmsMessageRecord[] smsApiRequests SmsApiRequest[] smsSubmitRecords SmsSubmitRecord[] smsReceiptRecords SmsReceiptRecord[] smsMessageSegmentAudits SmsMessageSegmentAudit[] smsUplinkMessages SmsUplinkMessage[] smsUplinkMatchCandidates SmsUplinkMatchCandidate[] cmppDownstreamDeliveries CmppDownstreamDelivery[] cmppDownstreamConnections CmppDownstreamConnection[] cmppConnectionStates CmppConnectionState[] gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[] gatewaySubmitDeadLetters GatewaySubmitDeadLetter[] } model EnterpriseCertification { id String @id @default(cuid()) tenantId String companyName String licenseNo String? contactName String? contactPhone String? materials Json? status String @default("pending") rejectReason String? reviewerId String? submittedAt DateTime @default(now()) reviewedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) @@index([tenantId, status, createdAt]) } model User { id String @id @default(cuid()) tenantId String? username String @unique email String? @unique phone String? @unique displayName String passwordHash String status String @default("active") sessionVersion Int @default(0) failedLoginCount Int @default(0) lockedUntil DateTime? lastLoginAt DateTime? deletedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) roles UserRole[] operationLogs OperationLog[] auditRecords AuditRecord[] createdSmsSendTasks SmsSendTask[] @relation("SmsSendTaskCreator") reviewedSmsSendTasks SmsSendTask[] @relation("SmsSendTaskReviewer") createdSmsBatchTasks SmsBatchTask[] @relation("SmsBatchTaskCreator") } model Role { id String @id @default(cuid()) code String @unique name String scope String @default("platform") description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users UserRole[] permissions RolePermission[] } model Permission { id String @id @default(cuid()) code String @unique name String description String? createdAt DateTime @default(now()) roles RolePermission[] } model UserRole { userId String roleId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@id([userId, roleId]) } model RolePermission { roleId String permissionId String role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade) @@id([roleId, permissionId]) } model OperationLog { id String @id @default(cuid()) tenantId String? userId String? action String resource String resourceId String? ipAddress String? userAgent String? detail Json? createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) user User? @relation(fields: [userId], references: [id]) @@index([tenantId, createdAt]) @@index([userId, createdAt]) @@index([createdAt]) @@index([resource, createdAt]) } model OperationLogArchive { originalId String @id tenantId String? userId String? action String resource String resourceId String? ipAddress String? userAgent String? detail Json? createdAt DateTime archiveMonth String archivedAt DateTime @default(now()) @@index([tenantId, createdAt]) @@index([userId, createdAt]) @@index([createdAt]) @@index([resource, createdAt]) @@index([archiveMonth]) } model FileObject { id String @id @default(cuid()) tenantId String? bucket String objectKey String @unique fileName String contentType String sizeBytes BigInt checksum String? purpose String createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) } model PhoneSegment { id String @id @default(cuid()) prefix String @unique carrier String province String? city String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model PhoneCarrierRule { id String @id @default(cuid()) carrier String pattern String priority Int @default(100) status String @default("active") remark String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, priority]) } model SensitiveWord { id String @id @default(cuid()) word String @unique level String @default("block") status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model EnterpriseBlacklist { id String @id @default(cuid()) tenantId String applicationId String phoneNumber String reason String? status String @default("active") createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) @@unique([applicationId, phoneNumber]) } model GlobalBlacklist { id String @id @default(cuid()) phoneNumber String @unique reason String? status String @default("active") createdAt DateTime @default(now()) } model DrainageField { id String @id @default(cuid()) code String @unique name String fieldType String required Boolean @default(false) status String @default("active") description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt channelReportFields ChannelReportField[] } model TenantAccount { id String @id @default(cuid()) tenantId String balanceCents Int @default(0) creditCents Int @default(0) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) @@unique([tenantId]) } model AccountTransaction { id String @id @default(cuid()) tenantId String transactionType String amountCents Int @default(0) balanceAfter Int @default(0) relatedType String? relatedId String? remark String? createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) @@index([tenantId, createdAt]) @@index([relatedType, relatedId]) } model BillingRule { id String @id @default(cuid()) code String @unique name String chargeBasis String @default("submit_success") unitPrice Int status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model RechargeOrder { id String @id @default(cuid()) tenantId String orderNo String @unique amountCents Int status String @default("created") payMethod String? paidAt DateTime? operatorId String? remark String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) @@index([tenantId, createdAt]) } model SmsBillingRecord { id String @id @default(cuid()) tenantId String applicationId String? taskId String? messageId String? phoneNumber String? contentLength Int billingUnits Int unitPrice Int amountCents Int billingStatus String @default("estimated") transactionId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) @@index([tenantId, createdAt]) @@index([taskId]) @@index([messageId]) } model SmsApplication { id String @id @default(cuid()) tenantId String name String scene String? callbackUrl String? cmppAccount String @unique cmppEnterpriseCode String secretHash String interfaceEnabled Boolean @default(true) interfaceType String @default("cmpp20") cmppMaxConnections Int @default(1) cmppWindowSize Int @default(16) dailyLimit Int? customerUnitPrice Int @default(0) queuePriority String @default("normal") maxPhonesPerTask Int @default(1000000) templateMismatchMode String @default("reject") downstreamReceiptRetryEnabled Boolean @default(true) downstreamUplinkRetryEnabled Boolean @default(true) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) ipAllowlist SmsApplicationIpAllowlist[] signatures SmsSignature[] drainageInfos SmsDrainageInfo[] templates SmsTemplate[] enterpriseBlacklists EnterpriseBlacklist[] sendTasks SmsSendTask[] batchTasks SmsBatchTask[] messageRecords SmsMessageRecord[] uplinkMessages SmsUplinkMessage[] uplinkMatchCandidates SmsUplinkMatchCandidate[] downstreamDeliveries CmppDownstreamDelivery[] downstreamConnections CmppDownstreamConnection[] connectionStates CmppConnectionState[] gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[] gatewaySubmitDeadLetters GatewaySubmitDeadLetter[] @@index([tenantId, status]) } model SmsApplicationIpAllowlist { id String @id @default(cuid()) applicationId String ipCidr String remark String? createdAt DateTime @default(now()) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) @@unique([applicationId, ipCidr]) } model SmsSignature { id String @id @default(cuid()) tenantId String applicationId String? name String purpose String? drainageInfo Json? auditStatus String @default("draft") reportStatus String @default("waiting_material") rejectReason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) materials SignatureMaterial[] templates SmsTemplate[] reportMaterials SignatureReportMaterial[] drainageReportMaterials DrainageReportMaterial[] drainageItems SmsDrainageInfo[] reportTasks ChannelSignatureReportTask[] messageRecords SmsMessageRecord[] @@index([tenantId, auditStatus]) @@index([tenantId, reportStatus]) } model SmsDrainageInfo { id String @id @default(cuid()) tenantId String signatureId String applicationId String? siteName String url String remark String? reportValues Json? auditStatus String @default("pending") rejectReason String? submittedAt DateTime @default(now()) reviewedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) signature SmsSignature @relation(fields: [signatureId], references: [id], onDelete: Cascade) application SmsApplication? @relation(fields: [applicationId], references: [id]) reportTasks ChannelSignatureReportTask[] @@index([tenantId, auditStatus, updatedAt]) @@index([signatureId, auditStatus]) @@index([applicationId]) } model SignatureMaterial { id String @id @default(cuid()) signatureId String fileObjectId String? materialType String title String description String? createdAt DateTime @default(now()) signature SmsSignature @relation(fields: [signatureId], references: [id], onDelete: Cascade) } model SmsTemplate { id String @id @default(cuid()) tenantId String applicationId String signatureId String? name String content String category String? auditStatus String @default("draft") rejectReason String? billingUnits Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) signature SmsSignature? @relation(fields: [signatureId], references: [id]) variables TemplateVariable[] sendTasks SmsSendTask[] batchTasks SmsBatchTask[] messageRecords SmsMessageRecord[] @@index([tenantId, auditStatus]) @@index([applicationId]) } model TemplateVariable { id String @id @default(cuid()) templateId String name String example String? required Boolean @default(true) createdAt DateTime @default(now()) template SmsTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade) @@unique([templateId, name]) } model AuditRecord { id String @id @default(cuid()) tenantId String? targetType String targetId String action String statusBefore String? statusAfter String reason String? reviewerId String? createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) reviewer User? @relation(fields: [reviewerId], references: [id]) @@index([targetType, targetId]) @@index([tenantId, createdAt]) } model SmsChannel { id String @id @default(cuid()) code String @unique name String carrier String? sendRegion String @default("全国") protocol String @default("CMPP") gatewayHost String gatewayPort Int enterpriseCode String? account String passwordCipher String srcId String cmppVersion String @default("2.0") rateLimitPerSecond Int @default(100) unitPrice Int @default(0) status String @default("active") config Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt groupItems SmsChannelGroupItem[] routeRules ChannelRouteRule[] healthMetrics ChannelHealthMetric[] reportFields ChannelReportField[] drainageReportMaterials DrainageReportMaterial[] reportTasks ChannelSignatureReportTask[] reportRecords ChannelSignatureReportRecord[] messageRecords SmsMessageRecord[] submitSessions CmppSubmitSession[] submitRecords SmsSubmitRecord[] receiptRecords SmsReceiptRecord[] segmentAudits SmsMessageSegmentAudit[] uplinkMessages SmsUplinkMessage[] connectionStates CmppConnectionState[] gatewaySubmitDeadLetters GatewaySubmitDeadLetter[] @@index([status]) } model CmppConnectionState { id String @id @default(cuid()) tenantId String? applicationId String? channelId String connectionId String status String @default("disconnected") desiredConnections Int @default(1) currentConnections Int @default(0) lastConnectedAt DateTime? lastDisconnectedAt DateTime? lastHeartbeatAt DateTime? reconnectCount Int @default(0) lastError String? updatedAt DateTime @updatedAt createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) channel SmsChannel @relation(fields: [channelId], references: [id]) @@unique([applicationId, channelId, connectionId]) @@index([tenantId, status]) @@index([applicationId, status]) @@index([channelId, status]) } model CmppDownstreamConnection { id String @id @default(cuid()) tenantId String applicationId String account String enterpriseCode String connectionId String @unique remoteIp String? protocol String? status String @default("connected") connectedAt DateTime @default(now()) lastHeartbeatAt DateTime? lastSubmitAt DateTime? lastDeliverAt DateTime? disconnectedAt DateTime? lastError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) @@index([applicationId, status, updatedAt]) @@index([tenantId, status, updatedAt]) @@index([account, status]) } model SmsChannelGroup { id String @id @default(cuid()) code String @unique name String description String? carrier String @default("mobile") status String @default("active") retryEnabled Boolean @default(true) retryTimeLimitHours Int @default(72) retryTimeLimitMinutes Int @default(720) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt items SmsChannelGroupItem[] routeRules ChannelRouteRule[] } model SmsChannelGroupItem { id String @id @default(cuid()) groupId String channelId String carrier String? province String? priority Int @default(100) weight Int @default(1) isBackup Boolean @default(false) rateLimitPerSecond Int? createdAt DateTime @default(now()) group SmsChannelGroup @relation(fields: [groupId], references: [id], onDelete: Cascade) channel SmsChannel @relation(fields: [channelId], references: [id]) @@unique([groupId, channelId, carrier, province]) @@unique([groupId, province]) @@index([channelId]) } model ChannelRouteRule { id String @id @default(cuid()) tenantId String? applicationId String? groupId String channelId String? carrier String? province String? priority Int @default(100) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt group SmsChannelGroup @relation(fields: [groupId], references: [id]) channel SmsChannel? @relation(fields: [channelId], references: [id]) @@index([tenantId, applicationId, status]) @@index([groupId, priority]) } model ChannelHealthMetric { id String @id @default(cuid()) channelId String submitTotal Int @default(0) submitSuccess Int @default(0) submitFailed Int @default(0) receiptSuccess Int @default(0) receiptFailed Int @default(0) unknownTotal Int @default(0) windowStart DateTime windowEnd DateTime createdAt DateTime @default(now()) channel SmsChannel @relation(fields: [channelId], references: [id]) @@index([channelId, windowStart]) } model ChannelReportField { id String @id @default(cuid()) channelId String drainageFieldId String? reportType String @default("both") code String name String fieldType String required Boolean @default(false) description String? sortOrder Int @default(100) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt channel SmsChannel @relation(fields: [channelId], references: [id], onDelete: Cascade) drainageField DrainageField? @relation(fields: [drainageFieldId], references: [id]) @@unique([channelId, code]) @@index([drainageFieldId, reportType]) } model SignatureReportMaterial { id String @id @default(cuid()) signatureId String channelId String fieldCode String fieldValue String? fileObjectId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt signature SmsSignature @relation(fields: [signatureId], references: [id], onDelete: Cascade) @@unique([signatureId, channelId, fieldCode]) @@index([channelId]) } model DrainageReportMaterial { id String @id @default(cuid()) signatureId String drainageItemId String channelId String fieldCode String fieldValue String? fileObjectId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt signature SmsSignature @relation(fields: [signatureId], references: [id], onDelete: Cascade) channel SmsChannel @relation(fields: [channelId], references: [id], onDelete: Cascade) @@unique([signatureId, drainageItemId, channelId, fieldCode]) @@index([channelId, fieldCode]) } model ChannelSignatureReportTask { id String @id @default(cuid()) tenantId String signatureId String channelId String reportType String @default("signature") drainageItemId String? status String @default("pending") reason String? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt signature SmsSignature @relation(fields: [signatureId], references: [id]) channel SmsChannel @relation(fields: [channelId], references: [id]) drainageInfo SmsDrainageInfo? @relation(fields: [drainageItemId], references: [id]) records ChannelSignatureReportRecord[] exportFiles ReportExportFile[] receiptImports ReportReceiptImport[] @@index([tenantId, status]) @@index([signatureId, channelId]) @@index([signatureId, drainageItemId, channelId]) @@index([reportType, status]) } model ChannelSignatureReportRecord { id String @id @default(cuid()) taskId String channelId String action String statusBefore String? statusAfter String reason String? operatorId String? sourceEntry String @default("system") createdAt DateTime @default(now()) task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade) channel SmsChannel @relation(fields: [channelId], references: [id]) @@index([taskId, createdAt]) @@index([channelId, createdAt]) } model ReportExportFile { id String @id @default(cuid()) taskId String fileObjectId String? fileName String rowCount Int @default(0) status String @default("generated") createdAt DateTime @default(now()) task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade) } model ReportReceiptImport { id String @id @default(cuid()) taskId String fileObjectId String? fileName String rowCount Int @default(0) successCount Int @default(0) failedCount Int @default(0) status String @default("imported") result Json? createdAt DateTime @default(now()) task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade) } model RiskRule { id String @id @default(cuid()) tenantId String? code String name String description String? metric String thresholdValue Float action String @default("manual_review") status String @default("active") priority Int @default(100) config Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) hits RiskHitRecord[] @@unique([tenantId, code]) @@index([tenantId, status, priority]) } model SmsSendTask { id String @id @default(cuid()) tenantId String applicationId String? templateId String? taskNo String @unique sourceType String @default("risk") aggregationKey String? @unique contentHash String? windowStartedAt DateTime? windowEndsAt DateTime? content String category String? phoneTotal Int uniquePhoneTotal Int duplicateRatio Float @default(0) illegalRatio Float @default(0) blacklistHitRatio Float @default(0) variableIssues Json? status String @default("created") riskDecision String @default("allow") reviewReason String? rejectReason String? createdById String? reviewedById String? reviewedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) template SmsTemplate? @relation(fields: [templateId], references: [id]) createdBy User? @relation("SmsSendTaskCreator", fields: [createdById], references: [id]) reviewedBy User? @relation("SmsSendTaskReviewer", fields: [reviewedById], references: [id]) riskHits RiskHitRecord[] messageRecords SmsMessageRecord[] @@index([tenantId, status, createdAt]) @@index([applicationId, createdAt]) } model RiskHitRecord { id String @id @default(cuid()) tenantId String taskId String ruleId String? ruleCode String ruleName String thresholdValue Float actualValue Float action String reason String createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) task SmsSendTask @relation(fields: [taskId], references: [id], onDelete: Cascade) rule RiskRule? @relation(fields: [ruleId], references: [id]) @@index([tenantId, createdAt]) @@index([taskId]) @@index([ruleCode]) } model SmsBatchTask { id String @id @default(cuid()) tenantId String applicationId String? templateId String? taskNo String @unique sourceType String @default("client") content String category String? phoneTotal Int status String @default("created") riskTaskId String? auditStatus String @default("approved") reviewReason String? rejectReason String? progressTotal Int @default(0) submittedTotal Int @default(0) successTotal Int @default(0) failedTotal Int @default(0) unknownTotal Int @default(0) timeoutTotal Int @default(0) scheduledAt DateTime? canceledAt DateTime? createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) template SmsTemplate? @relation(fields: [templateId], references: [id]) createdBy User? @relation("SmsBatchTaskCreator", fields: [createdById], references: [id]) apiRequests SmsApiRequest[] messages SmsMessageRecord[] submitRecords SmsSubmitRecord[] receiptRecords SmsReceiptRecord[] segmentAudits SmsMessageSegmentAudit[] @@index([tenantId, status, createdAt]) @@index([status, scheduledAt]) @@index([applicationId, createdAt]) } model SmsApiRequest { id String @id @default(cuid()) tenantId String batchTaskId String requestId String @unique sourceIp String? userAgent String? payloadSummary Json? status String @default("accepted") createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) batchTask SmsBatchTask @relation(fields: [batchTaskId], references: [id], onDelete: Cascade) @@index([tenantId, createdAt]) @@index([batchTaskId]) } model SmsMessageRecord { id String @id @default(cuid()) tenantId String? batchTaskId String? applicationId String? templateId String? signatureId String? reviewTaskId String? messageId String @unique phoneNumber String carrier String? province String? content String billingUnits Int @default(1) unitPrice Int @default(0) amountCents Int @default(0) queuePriority String @default("normal") channelId String? submitId String? gatewayMessageId String? cmppSubmitSequenceId String? status String @default("queued") submitStatus String? receiptStatus String? errorCode String? errorMessage String? queuedAt DateTime @default(now()) submittedAt DateTime? deliveredAt DateTime? timeoutAt DateTime? updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) batchTask SmsBatchTask? @relation(fields: [batchTaskId], references: [id], onDelete: Cascade) application SmsApplication? @relation(fields: [applicationId], references: [id]) template SmsTemplate? @relation(fields: [templateId], references: [id]) signature SmsSignature? @relation(fields: [signatureId], references: [id]) reviewTask SmsSendTask? @relation(fields: [reviewTaskId], references: [id]) channel SmsChannel? @relation(fields: [channelId], references: [id]) submitRecords SmsSubmitRecord[] receiptRecords SmsReceiptRecord[] segmentAudits SmsMessageSegmentAudit[] matchedUplinks SmsUplinkMessage[] @relation("SmsUplinkMatchedMessage") uplinkMatchCandidates SmsUplinkMatchCandidate[] downstreamDeliveries CmppDownstreamDelivery[] @@index([tenantId, status, queuedAt]) @@index([batchTaskId, status]) @@index([reviewTaskId, status]) @@index([phoneNumber]) @@index([gatewayMessageId]) } model CmppSubmitSession { id String @id @default(cuid()) channelId String sessionNo String @unique status String @default("open") submitTotal Int @default(0) acceptedTotal Int @default(0) rejectedTotal Int @default(0) startedAt DateTime @default(now()) endedAt DateTime? channel SmsChannel @relation(fields: [channelId], references: [id]) submitRecords SmsSubmitRecord[] @@index([channelId, status]) } model SmsSubmitRecord { id String @id @default(cuid()) tenantId String? batchTaskId String? messageRecordId String channelId String sessionId String? submitId String @unique sequenceId Int? gatewayMessageId String? submitStatus String @default("queued") errorCode String? errorMessage String? submittedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) batchTask SmsBatchTask? @relation(fields: [batchTaskId], references: [id], onDelete: Cascade) messageRecord SmsMessageRecord @relation(fields: [messageRecordId], references: [id], onDelete: Cascade) channel SmsChannel @relation(fields: [channelId], references: [id]) session CmppSubmitSession? @relation(fields: [sessionId], references: [id]) segmentAudits SmsMessageSegmentAudit[] @@index([tenantId, createdAt]) @@index([messageRecordId]) @@index([gatewayMessageId]) } model SmsMessageSegmentAudit { id String @id @default(cuid()) tenantId String? batchTaskId String? messageRecordId String submitRecordId String? channelId String? submitId String attempt Int @default(0) segmentTotal Int @default(1) segmentIndex Int @default(1) sequenceId Int? gatewayMessageId String? submitStatus String @default("queued") receiptStatus String? rawStatus String? compensationType String? errorCode String? errorMessage String? submittedAt DateTime? deliveredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) batchTask SmsBatchTask? @relation(fields: [batchTaskId], references: [id]) messageRecord SmsMessageRecord @relation(fields: [messageRecordId], references: [id], onDelete: Cascade) submitRecord SmsSubmitRecord? @relation(fields: [submitRecordId], references: [id]) channel SmsChannel? @relation(fields: [channelId], references: [id]) @@unique([messageRecordId, submitId, segmentIndex]) @@index([tenantId, createdAt]) @@index([messageRecordId, segmentIndex]) @@index([submitRecordId]) @@index([gatewayMessageId]) @@index([submitStatus, receiptStatus]) } model SmsReceiptRecord { id String @id @default(cuid()) tenantId String? batchTaskId String? messageRecordId String? channelId String? messageId String gatewayMessageId String sequenceId Int? receiptStatus String rawStatus String errorCode String? deliveredAt DateTime createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) batchTask SmsBatchTask? @relation(fields: [batchTaskId], references: [id]) messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id]) channel SmsChannel? @relation(fields: [channelId], references: [id]) @@index([tenantId, createdAt]) @@index([messageId]) @@index([gatewayMessageId]) } model SmsUplinkMessage { id String @id @default(cuid()) tenantId String? applicationId String? channelId String messageRecordId String? messageId String? sequenceId Int? phoneNumber String destId String content String matchStatus String @default("unmatched") matchReason String? receivedAt DateTime createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) messageRecord SmsMessageRecord? @relation("SmsUplinkMatchedMessage", fields: [messageRecordId], references: [id]) channel SmsChannel @relation(fields: [channelId], references: [id]) matchCandidates SmsUplinkMatchCandidate[] @@index([tenantId, createdAt]) @@index([applicationId, receivedAt]) @@index([channelId, receivedAt]) @@index([messageRecordId]) @@index([matchStatus]) @@index([phoneNumber]) } model SmsUplinkMatchCandidate { id String @id @default(cuid()) uplinkMessageId String tenantId String applicationId String messageRecordId String? matchSource String confidence Int @default(50) reason String? status String @default("pending") claimedAt DateTime? claimedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt uplinkMessage SmsUplinkMessage @relation(fields: [uplinkMessageId], references: [id], onDelete: Cascade) tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id]) @@unique([uplinkMessageId, applicationId, messageRecordId]) @@index([uplinkMessageId, status]) @@index([tenantId, status, createdAt]) @@index([applicationId, status, createdAt]) @@index([messageRecordId]) } model CmppDownstreamDelivery { id String @id @default(cuid()) tenantId String applicationId String messageRecordId String? messageId String? deliveryType String status String @default("pending") payload Json retryEnabled Boolean @default(true) retryCount Int @default(0) nextRetryAt DateTime? sentAt DateTime? acknowledgedAt DateTime? ackDeadlineAt DateTime? ackResult Int? ackSequenceId String? ackMessageId String? connectionId String? deliveredAt DateTime? lastError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id]) @@index([tenantId, status, createdAt]) @@index([applicationId, status, createdAt]) @@index([messageId]) @@index([messageRecordId]) @@index([deliveryType, status]) @@index([status, ackDeadlineAt]) } model GatewaySubmitDeadLetter { id String @id @default(cuid()) streamMessageId String @unique tenantId String? applicationId String? channelId String? traceId String? messageId String? submitId String? status String @default("pending") failureCode String failureMessage String attempts Int @default(1) maxAttempts Int @default(3) commandPayload Json? rawPayload String? manualRetryCount Int @default(0) lastRetryStreamId String? lastRetriedAt DateTime? resolvedAt DateTime? resolvedStatus String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) channel SmsChannel? @relation(fields: [channelId], references: [id]) @@index([status, createdAt]) @@index([tenantId, status, createdAt]) @@index([applicationId, status, createdAt]) @@index([channelId, status, createdAt]) @@index([messageId]) @@index([submitId]) } model GatewayDownstreamRecoveryStatus { id String @id @default(cuid()) account String @unique tenantId String? applicationId String? gatewayInstanceId String? state String lockOwner String? lockExpiresAt DateTime? lastAttemptAt DateTime? lastSuccessAt DateTime? lastFailureAt DateTime? nextRetryAt DateTime? attemptCount Int @default(0) failureCategory String? lastError String? lastSkipReason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) @@index([tenantId, state, updatedAt]) @@index([applicationId, state, updatedAt]) @@index([failureCategory, updatedAt]) @@index([lockOwner, lockExpiresAt]) @@index([state, updatedAt]) @@index([nextRetryAt]) }