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[] smsTemplates SmsTemplate[] auditRecords AuditRecord[] riskRules RiskRule[] smsSendTasks SmsSendTask[] riskHitRecords RiskHitRecord[] smsBatchTasks SmsBatchTask[] smsMessageRecords SmsMessageRecord[] smsApiRequests SmsApiRequest[] smsSubmitRecords SmsSubmitRecord[] smsReceiptRecords SmsReceiptRecord[] smsUplinkMessages SmsUplinkMessage[] cmppConnectionStates CmppConnectionState[] } 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") 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]) } 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 phoneNumber String reason String? status String @default("active") createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) @@unique([tenantId, 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 } model BillingPlan { id String @id @default(cuid()) name String priceCents Int smsUnits Int validDays Int status String @default("active") description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt rechargeOrders RechargeOrder[] } model TenantAccount { id String @id @default(cuid()) tenantId String balanceCents Int @default(0) smsUnits 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) smsUnits 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 planId String? orderNo String @unique amountCents Int smsUnits Int @default(0) 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]) plan BillingPlan? @relation(fields: [planId], 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 secretHash String dailyLimit Int? customerUnitPrice Int @default(0) queuePriority String @default("normal") maxPhonesPerTask Int @default(1000000) templateMismatchMode String @default("reject") status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) ipAllowlist SmsApplicationIpAllowlist[] signatures SmsSignature[] templates SmsTemplate[] sendTasks SmsSendTask[] batchTasks SmsBatchTask[] messageRecords SmsMessageRecord[] connectionStates CmppConnectionState[] @@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[] reportTasks ChannelSignatureReportTask[] @@index([tenantId, auditStatus]) @@index([tenantId, reportStatus]) } 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("3.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[] reportTasks ChannelSignatureReportTask[] reportRecords ChannelSignatureReportRecord[] messageRecords SmsMessageRecord[] submitSessions CmppSubmitSession[] submitRecords SmsSubmitRecord[] receiptRecords SmsReceiptRecord[] uplinkMessages SmsUplinkMessage[] connectionStates CmppConnectionState[] @@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 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 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) @@unique([channelId, code]) } 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 ChannelSignatureReportTask { id String @id @default(cuid()) tenantId String signatureId String channelId 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]) records ChannelSignatureReportRecord[] exportFiles ReportExportFile[] receiptImports ReportReceiptImport[] @@index([tenantId, status]) @@index([signatureId, channelId]) } model ChannelSignatureReportRecord { id String @id @default(cuid()) taskId String channelId String action String statusBefore String? statusAfter String reason String? operatorId String? 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 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[] @@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[] @@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? messageId String @unique phoneNumber 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? 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]) channel SmsChannel? @relation(fields: [channelId], references: [id]) submitRecords SmsSubmitRecord[] receiptRecords SmsReceiptRecord[] @@index([tenantId, status, queuedAt]) @@index([batchTaskId, 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]) @@index([tenantId, createdAt]) @@index([messageRecordId]) @@index([gatewayMessageId]) } 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? channelId String messageId String? sequenceId Int? phoneNumber String destId String content String receivedAt DateTime createdAt DateTime @default(now()) tenant Tenant? @relation(fields: [tenantId], references: [id]) channel SmsChannel @relation(fields: [channelId], references: [id]) @@index([tenantId, createdAt]) @@index([channelId, receivedAt]) @@index([phoneNumber]) }