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[] phoneFrequencyStates PhoneFrequencyState[] phoneFrequencyHits PhoneFrequencyHit[] smsBatchTasks SmsBatchTask[] smsMessageRecords SmsMessageRecord[] smsApiRequests SmsApiRequest[] smsSubmitRecords SmsSubmitRecord[] smsReceiptRecords SmsReceiptRecord[] smsMessageSegmentAudits SmsMessageSegmentAudit[] smsUplinkMessages SmsUplinkMessage[] smsUplinkMatchCandidates SmsUplinkMatchCandidate[] cmppDownstreamDeliveries CmppDownstreamDelivery[] downstreamRequeueTasks DownstreamRequeueTask[] cmppDownstreamConnections CmppDownstreamConnection[] cmppConnectionStates CmppConnectionState[] gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[] gatewaySubmitDeadLetters GatewaySubmitDeadLetter[] smsReceiptAnomalies SmsReceiptAnomaly[] openApiRequests OpenApiRequest[] httpWebhookEvents HttpWebhookEvent[] cmppInboundLongMessages CmppInboundLongMessage[] cmppInboundSubmissionInboxes CmppInboundSubmissionInbox[] smsApplicationDailyReservations SmsApplicationDailyReservation[] phoneFrequencyReservations PhoneFrequencyReservation[] } 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 email String? phone String? 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") createdDownstreamRequeueTasks DownstreamRequeueTask[] @relation("DownstreamRequeueTaskCreator") releasedPhoneFrequencyHits PhoneFrequencyHit[] @relation("PhoneFrequencyHitReleaser") createdPhoneFrequencyWhitelistEntries PhoneFrequencyWhitelist[] @relation("PhoneFrequencyWhitelistCreator") updatedPhoneFrequencyWhitelistEntries PhoneFrequencyWhitelist[] @relation("PhoneFrequencyWhitelistUpdater") infrastructureAlertReads InfrastructureAlertRead[] } 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 ProtocolInteractionLog { id String @id @default(cuid()) protocol String direction String eventType String status String tenantId String? applicationId String? channelId String? account String? messageId String? gatewayMessageId String? traceId String? requestId String? phoneMasked String? phoneNumber String? resultCode String? durationMs Int? payloadBytes Int? retryCount Int? detail Json? createdAt DateTime @default(now()) @@index([createdAt]) @@index([protocol, direction, createdAt]) @@index([eventType, createdAt]) @@index([status, createdAt]) @@index([channelId, createdAt]) @@index([applicationId, createdAt]) @@index([messageId]) @@index([gatewayMessageId]) @@index([requestId]) } 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[] commonReportFields CommonReportField[] } model DrainageDetectionRule { id String @id @default(cuid()) code String @unique name String category String pattern String flags String @default("giu") priority Int @default(100) status String @default("active") description String? version Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, priority]) } model TenantAccount { id String @id @default(cuid()) tenantId String balanceCents BigInt @default(0) creditCents BigInt @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 idempotencyKey String? @unique amountCents BigInt @default(0) balanceAfter BigInt @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 BigInt status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model RechargeOrder { id String @id @default(cuid()) tenantId String orderNo String @unique amountCents BigInt 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]) @@index([payMethod, createdAt]) } model SmsBillingRecord { id String @id @default(cuid()) tenantId String applicationId String? taskId String? messageId String? phoneNumber String? contentLength Int billingUnits Int unitPrice BigInt amountCents BigInt 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 cmppApplicationExtension String? cmppAccessNumberFillEnabled Boolean @default(false) cmppAccessNumberFillPrefix String? cmppClientSrcId String? @unique secretHash String interfaceEnabled Boolean @default(true) interfaceType String @default("cmpp20") cmppMaxConnections Int @default(1) cmppWindowSize Int @default(16) dailyLimit Int @default(100000) customerUnitPrice BigInt @default(0) queuePriority String @default("normal") templateMismatchMode String @default("reject") downstreamReceiptRetryEnabled Boolean @default(true) downstreamUplinkRetryEnabled Boolean @default(true) status String @default("active") disablingAt DateTime? autoDisableAt DateTime? disableReason String? 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[] downstreamRequeueTasks DownstreamRequeueTask[] downstreamConnections CmppDownstreamConnection[] connectionStates CmppConnectionState[] gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[] gatewaySubmitDeadLetters GatewaySubmitDeadLetter[] receiptAnomalies SmsReceiptAnomaly[] httpConfig SmsApplicationHttpConfig? httpIpAllowlist SmsApplicationHttpIpAllowlist[] httpApiCredentials HttpApiCredential[] openApiRequests OpenApiRequest[] httpWebhookEndpoints HttpWebhookEndpoint[] httpWebhookEvents HttpWebhookEvent[] dailyUsages SmsApplicationDailyUsage[] dailyReservations SmsApplicationDailyReservation[] inboundLongMessages CmppInboundLongMessage[] inboundSubmissionInboxes CmppInboundSubmissionInbox[] riskRules RiskRule[] phoneFrequencyStates PhoneFrequencyState[] phoneFrequencyHits PhoneFrequencyHit[] phoneFrequencyReservations PhoneFrequencyReservation[] @@index([tenantId, status]) @@index([status, createdAt]) @@index([status, autoDisableAt]) } 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 SmsApplicationHttpConfig { id String @id @default(cuid()) applicationId String @unique enabled Boolean @default(false) sendEnabled Boolean @default(true) messageQueryEnabled Boolean @default(true) receiptWebhookEnabled Boolean @default(true) uplinkWebhookEnabled Boolean @default(true) uplinkQueryEnabled Boolean @default(true) credentialSelfServiceEnabled Boolean @default(true) qpsLimit Int @default(10) timestampToleranceSeconds Int @default(300) maxCredentialCount Int @default(2) uplinkRetentionDays Int @default(90) maxQueryRangeDays Int @default(31) maxPageSize Int @default(100) receiptDeliveryMode String @default("http") uplinkDeliveryMode String @default("http") webhookRetryEnabled Boolean @default(true) webhookMaxAttempts Int @default(7) webhookTimeoutSeconds Int @default(10) requireHttps Boolean @default(true) allowClientManualRetry Boolean @default(true) allowClientTest Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) } model SmsApplicationDailyUsage { id String @id @default(cuid()) applicationId String usageDate DateTime @db.Date usedCount Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) @@unique([applicationId, usageDate]) @@index([usageDate]) } model SmsApplicationDailyReservation { id String @id @default(cuid()) reservationKey String @unique tenantId String applicationId String usageDate DateTime @db.Date requestedCount Int dailyLimit Int usedCount Int? reserved Boolean createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Restrict) @@index([applicationId, usageDate]) @@index([tenantId, createdAt]) } model SmsApplicationHttpIpAllowlist { 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 HttpApiCredential { id String @id @default(cuid()) applicationId String name String accessKey String @unique secretEncrypted String secretLast4 String status String @default("active") expiresAt DateTime? lastUsedAt DateTime? lastUsedIp String? createdById String? revokedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) requests OpenApiRequest[] @@index([applicationId, status]) } model OpenApiRequest { id String @id @default(cuid()) tenantId String applicationId String credentialId String requestId String @unique idempotencyKey String bodyHash String clientMessageId String? messageRecordId String? httpStatus Int? businessCode String? responseBody Json? sourceIp String? userAgent String? durationMs Int? status String @default("processing") completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) credential HttpApiCredential @relation(fields: [credentialId], references: [id]) @@unique([applicationId, idempotencyKey]) @@index([applicationId, createdAt]) } model HttpWebhookEndpoint { id String @id @default(cuid()) applicationId String eventType String url String secretEncrypted String secretLast4 String status String @default("active") lastTestAt DateTime? lastTestStatus String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) deliveries HttpWebhookDelivery[] @@unique([applicationId, eventType]) } model HttpWebhookEvent { id String @id @default(cuid()) eventId String @unique tenantId String applicationId String eventType String messageRecordId String? messageId String? uplinkMessageId String? payload Json createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) deliveries HttpWebhookDelivery[] @@index([applicationId, createdAt]) } model HttpWebhookDelivery { id String @id @default(cuid()) eventId String endpointId String status String @default("pending") attemptCount Int @default(0) nextRetryAt DateTime? lastHttpStatus Int? lastError String? deliveredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt event HttpWebhookEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) endpoint HttpWebhookEndpoint @relation(fields: [endpointId], references: [id], onDelete: Cascade) attempts HttpWebhookAttempt[] @@unique([eventId, endpointId]) @@index([status, nextRetryAt]) } model HttpWebhookAttempt { id String @id @default(cuid()) deliveryId String attemptNo Int requestHeaders Json? responseStatus Int? responseSummary String? errorMessage String? durationMs Int? createdAt DateTime @default(now()) delivery HttpWebhookDelivery @relation(fields: [deliveryId], references: [id], onDelete: Cascade) @@unique([deliveryId, attemptNo]) } 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? materialVersion Int @default(1) pendingReport Boolean @default(true) reportChangedAt DateTime @default(now()) 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[] reportBatchItems ReportMaterialBatchItem[] @@index([tenantId, auditStatus]) @@index([tenantId, updatedAt]) @@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? materialVersion Int @default(1) pendingReport Boolean @default(true) reportChangedAt DateTime @default(now()) 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[] messageRecords SmsMessageRecord[] reportBatchItems ReportMaterialBatchItem[] @@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, createdAt]) @@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? carriers String[] @default([]) 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 BigInt @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[] receiptAnomalies SmsReceiptAnomaly[] @@index([status]) @@index([status, createdAt]) } 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) lastReconnectAttemptAt DateTime? nextReconnectAt DateTime? lastErrorCategory String? 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]) @@index([status, nextReconnectAt]) } 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[] submitRecords SmsSubmitRecord[] } 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) 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 exportName String? fieldType String required Boolean @default(false) description String? sortOrder Int @default(100) columnWidth Int @default(18) imageWidth Int @default(120) imageHeight Int @default(80) defaultValue String? transform String? 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, reportType]) @@index([drainageFieldId, reportType]) } model CommonReportField { id String @id @default(cuid()) drainageFieldId String reportType String required Boolean @default(false) sortOrder Int @default(100) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt drainageField DrainageField @relation(fields: [drainageFieldId], references: [id], onDelete: Restrict) @@unique([drainageFieldId, reportType]) @@index([reportType, status, sortOrder]) } 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 carrier String? approvedAt DateTime? approvalScope String @default("legacy_channel") 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[] exportItems ReportExportFileItem[] @@index([tenantId, status]) @@index([status, createdAt]) @@index([signatureId, channelId]) @@index([signatureId, channelId, carrier]) @@index([signatureId, drainageItemId, channelId]) @@index([reportType, status]) } model SignatureRetirementRule { id String @id @default(cuid()) ruleType String targetId String? targetKey String @default("") enabled Boolean @default(true) mobileWindowDays Int @default(30) mobileThreshold Int @default(1) unicomWindowDays Int @default(30) unicomThreshold Int @default(1) telecomWindowDays Int @default(30) telecomThreshold Int @default(1) messageTemplate String? version Int @default(1) createdById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([ruleType, targetKey]) @@index([ruleType, enabled]) } model SignatureRetirementWebhook { id String @id @default(cuid()) name String platform String urlEncrypted String urlMasked String status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, createdAt]) } model SignatureRetirementCycle { id String @id @default(cuid()) dimensionType String signatureId String channelId String? channelKey String @default("") carrier String status String @default("open") startedOn DateTime @db.Date lastDetectedOn DateTime @db.Date resolvedOn DateTime? @db.Date createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([dimensionType, signatureId, channelKey, carrier, status]) @@index([status, lastDetectedOn]) } model SignatureRetirementDetection { id String @id @default(cuid()) detectionDate DateTime @db.Date dimensionType String tenantId String applicationId String? signatureId String channelId String? channelKey String @default("") carrier String windowDays Int threshold Int submittedAttempts Int @default(0) acceptedBusinessCount Int @default(0) deliveredBusinessCount Int @default(0) approvedAt DateTime ruleId String? ruleVersion Int @default(1) status String cycleId String? suppressed Boolean @default(false) notificationTitle String? notificationContent String? createdAt DateTime @default(now()) @@unique([detectionDate, dimensionType, signatureId, channelKey, carrier]) @@index([detectionDate, dimensionType, status]) @@index([signatureId, carrier, detectionDate]) @@index([channelId, carrier, detectionDate]) } model SignatureRetirementMessage { id String @id @default(cuid()) detectionId String @unique cycleId String tenantId String title String content String isRead Boolean @default(false) suppressed Boolean @default(false) readAt DateTime? createdAt DateTime @default(now()) @@index([createdAt, isRead, suppressed]) @@index([tenantId, createdAt]) } model SignatureRetirementSuppression { id String @id @default(cuid()) dimensionType String signatureId String channelId String? channelKey String @default("") carrier String mode String muteUntil DateTime? @db.Date active Boolean @default(true) reason String? operatorId String? cancelledAt DateTime? cancelledById String? cancelReason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([dimensionType, signatureId, channelKey, carrier]) @@index([active, muteUntil]) } model SignatureRetirementWebhookDelivery { id String @id @default(cuid()) detectionDate DateTime @db.Date webhookId String groupKey String payload Json status String @default("pending") attemptCount Int @default(0) nextRetryAt DateTime? lastHttpStatus Int? lastError String? deliveredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([webhookId, detectionDate, groupKey]) @@index([status, nextRetryAt]) } 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]) @@index([createdAt]) } model ReportExportFile { id String @id @default(cuid()) taskId String? batchId String? channelId 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) batch ReportMaterialBatch? @relation(fields: [batchId], references: [id], onDelete: Cascade) items ReportExportFileItem[] @@index([batchId, channelId]) } model ReportMaterialBatch { id String @id @default(cuid()) batchNo String @unique status String @default("generating") createdById String? selectedCount Int @default(0) channelCount Int @default(0) fileCount Int @default(0) errorMessage String? createdAt DateTime @default(now()) completedAt DateTime? items ReportMaterialBatchItem[] exportFiles ReportExportFile[] @@index([status, createdAt]) } model ReportMaterialBatchItem { id String @id @default(cuid()) batchId String signatureId String drainageItemId String? reportType String materialVersion Int snapshot Json createdAt DateTime @default(now()) batch ReportMaterialBatch @relation(fields: [batchId], references: [id], onDelete: Cascade) signature SmsSignature @relation(fields: [signatureId], references: [id]) drainageInfo SmsDrainageInfo? @relation(fields: [drainageItemId], references: [id]) exportItems ReportExportFileItem[] @@index([batchId, reportType]) @@index([signatureId, drainageItemId]) } model ReportExportFileItem { id String @id @default(cuid()) exportFileId String batchItemId String taskId String rowNumber Int exportFile ReportExportFile @relation(fields: [exportFileId], references: [id], onDelete: Cascade) batchItem ReportMaterialBatchItem @relation(fields: [batchItemId], references: [id], onDelete: Cascade) task ChannelSignatureReportTask @relation(fields: [taskId], references: [id], onDelete: Cascade) @@unique([exportFileId, batchItemId]) @@index([taskId]) } model ReportMaterialImportProfile { id String @id @default(cuid()) name String reportType String tenantId String? applicationId String? sheetName String? headerRowCount Int @default(1) dataStartRow Int @default(2) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt columns ReportMaterialImportProfileColumn[] @@index([reportType, status]) @@index([tenantId, applicationId]) } model ReportMaterialImportProfileColumn { id String @id @default(cuid()) profileId String sourceHeader String sourceHeaderPath String? sourceColumnIndex Int targetFieldCode String targetKind String @default("dynamic") fieldType String @default("string") required Boolean @default(false) transform String? sortOrder Int @default(100) createdAt DateTime @default(now()) profile ReportMaterialImportProfile @relation(fields: [profileId], references: [id], onDelete: Cascade) @@unique([profileId, sourceColumnIndex]) @@index([profileId, sortOrder]) } model ReportMaterialImportBatch { id String @id @default(cuid()) tenantId String applicationId String? profileId String? fileObjectId String fileName String reportType String status String @default("analyzed") progress Int @default(100) progressStage String? errorMessage String? startedAt DateTime? heartbeatAt DateTime? sheetName String headerRowCount Int @default(1) dataStartRow Int @default(2) mapping Json preview Json? result Json? rowCount Int @default(0) successCount Int @default(0) failedCount Int @default(0) reviewedById String? reviewedAt DateTime? createdAt DateTime @default(now()) completedAt DateTime? items ReportMaterialImportItem[] @@index([tenantId, createdAt]) @@index([status, createdAt]) @@index([status, heartbeatAt]) } model ReportMaterialImportItem { id String @id @default(cuid()) batchId String rowNumber Int reportType String operation String targetId String? status String @default("pending_review") payload Json originalSnapshot Json? errorMessage String? reviewReason String? reviewedById String? reviewedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt batch ReportMaterialImportBatch @relation(fields: [batchId], references: [id], onDelete: Cascade) @@unique([batchId, rowNumber]) @@index([batchId, status]) @@index([reportType, status, createdAt]) @@index([targetId]) } 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? applicationId 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]) application SmsApplication? @relation(fields: [applicationId], references: [id], onDelete: Cascade) hits RiskHitRecord[] phoneFrequencyStates PhoneFrequencyState[] phoneFrequencyHits PhoneFrequencyHit[] @@unique([applicationId, code]) @@index([tenantId, applicationId, 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 PhoneFrequencyState { id String @id @default(cuid()) tenantId String applicationId String ruleId String? ruleCode String phoneNumber String windowStartedAt DateTime windowEndsAt DateTime count Int @default(0) generation Int @default(0) activeHitId String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) rule RiskRule? @relation(fields: [ruleId], references: [id], onDelete: SetNull) activeHit PhoneFrequencyHit? @relation("ActivePhoneFrequencyHit", fields: [activeHitId], references: [id], onDelete: SetNull) @@unique([applicationId, ruleCode, phoneNumber]) @@index([tenantId, applicationId, windowEndsAt]) @@index([ruleId]) } model PhoneFrequencyHit { id String @id @default(cuid()) tenantId String applicationId String ruleId String? ruleCode String ruleName String phoneNumber String thresholdValue Int actualValue Int windowStartedAt DateTime windowEndsAt DateTime generation Int @default(0) action String @default("block") sourceType String? releasedAt DateTime? releasedById String? releaseReason String? createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) rule RiskRule? @relation(fields: [ruleId], references: [id], onDelete: SetNull) releasedBy User? @relation("PhoneFrequencyHitReleaser", fields: [releasedById], references: [id]) activeForState PhoneFrequencyState? @relation("ActivePhoneFrequencyHit") @@unique([applicationId, ruleCode, phoneNumber, windowStartedAt, generation]) @@index([tenantId, applicationId, createdAt]) @@index([applicationId, phoneNumber, createdAt]) @@index([windowEndsAt, releasedAt]) @@index([ruleId]) @@index([releasedById]) } model PhoneFrequencyReservation { id String @id @default(cuid()) reservationKey String @unique tenantId String applicationId String result Json createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Restrict) @@index([applicationId, createdAt]) @@index([tenantId, createdAt]) } model PhoneFrequencyWhitelist { id String @id @default(cuid()) phoneNumber String @unique status String @default("active") reason String remark String? createdById String updatedById String deletedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy User @relation("PhoneFrequencyWhitelistCreator", fields: [createdById], references: [id]) updatedBy User @relation("PhoneFrequencyWhitelistUpdater", fields: [updatedById], references: [id]) @@index([status, updatedAt]) @@index([createdById]) @@index([updatedById]) } 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? drainageInfoId String? reviewTaskId String? messageId String @unique clientMessageId String? phoneNumber String carrier String? province String? content String hasDrainageContent Boolean? drainageDetection Json? drainageDetectionVersion String? drainageEvaluatedAt DateTime? billingUnits Int @default(1) unitPrice BigInt @default(0) amountCents BigInt @default(0) queuePriority String @default("normal") channelId String? submitId String? gatewayMessageId String? cmppSubmitSequenceId String? cmppSubmitGroupMessageId String? cmppRegisteredDelivery Boolean? clientSrcId String? applicationExtension String? status String @default("queued") submitStatus String? receiptStatus String? receiptRawStatus String? errorCode String? errorMessage String? queuedAt DateTime @default(now()) submittedAt DateTime? deliveredAt DateTime? timeoutAt DateTime? timeoutReceiptQueuedAt 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]) drainageInfo SmsDrainageInfo? @relation(fields: [drainageInfoId], references: [id]) reviewTask SmsSendTask? @relation(fields: [reviewTaskId], references: [id]) channel SmsChannel? @relation(fields: [channelId], references: [id]) submitRecords SmsSubmitRecord[] receiptRecords SmsReceiptRecord[] segmentAudits SmsMessageSegmentAudit[] receiptAnomalies SmsReceiptAnomaly[] matchedUplinks SmsUplinkMessage[] @relation("SmsUplinkMatchedMessage") uplinkMatchCandidates SmsUplinkMatchCandidate[] downstreamDeliveries CmppDownstreamDelivery[] @@unique([applicationId, clientMessageId]) @@index([tenantId, status, queuedAt]) @@index([queuedAt]) @@index([batchTaskId, status]) @@index([reviewTaskId, status]) @@index([phoneNumber]) @@index([gatewayMessageId]) @@index([drainageInfoId, queuedAt]) @@index([hasDrainageContent, queuedAt]) } 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 channelGroupId String? channelGroupName String? sessionId String? retryOfSubmitRecordId String? @unique submitId String @unique sequenceId Int? gatewayMessageId String? submitStatus String @default("queued") resultEventId String? @unique resultProcessedAt DateTime? costUnitPrice BigInt @default(0) costAmountCents BigInt @default(0) 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]) channelGroup SmsChannelGroup? @relation(fields: [channelGroupId], references: [id], onDelete: SetNull) session CmppSubmitSession? @relation(fields: [sessionId], references: [id]) retryOfSubmitRecord SmsSubmitRecord? @relation("SmsSubmitRetry", fields: [retryOfSubmitRecordId], references: [id]) retrySubmit SmsSubmitRecord? @relation("SmsSubmitRetry") segmentAudits SmsMessageSegmentAudit[] receiptAnomalies SmsReceiptAnomaly[] @@index([tenantId, createdAt]) @@index([messageRecordId]) @@index([gatewayMessageId]) @@index([channelId, gatewayMessageId]) @@index([channelGroupId]) } model GatewaySubmitOutbox { id String @id @default(cuid()) submitId String @unique messageRecordId String channelId String payload Json schemaVersion String @default("v1") status String @default("pending") attemptCount Int @default(0) nextAttemptAt DateTime @default(now()) leaseOwner String? leaseExpiresAt DateTime? streamEntryId String? publishedAt DateTime? lastError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([submitId, status]) @@index([messageRecordId]) } model DailyReconciliationReport { id String @id @default(cuid()) reportDate DateTime @db.Date tenantId String tenantName String applicationId String applicationName String submittedUnits Int @default(0) sentUnits Int @default(0) unknownUnits Int @default(0) successUnits Int @default(0) failedUnits Int @default(0) generatedAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([reportDate, tenantId, applicationId]) @@index([reportDate]) @@index([tenantId, reportDate]) @@index([applicationId, reportDate]) } model DailyProfitReport { id String @id @default(cuid()) reportDate DateTime @db.Date dimensionType String dimensionId String dimensionName String tenantId String? tenantName String? applicationId String? channelId String? submittedUnits Int @default(0) sentUnits Int @default(0) unknownUnits Int @default(0) successUnits Int @default(0) failedUnits Int @default(0) revenueCents BigInt @default(0) refundCents BigInt @default(0) costCents BigInt @default(0) profitCents BigInt @default(0) profitRateBps Int @default(0) generatedAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([reportDate, dimensionType, dimensionId]) @@index([dimensionType, reportDate]) @@index([tenantId, reportDate]) @@index([applicationId, reportDate]) @@index([channelId, reportDate]) } model DailyQualityReport { id String @id @default(cuid()) reportDate DateTime @db.Date dimensionType String dimensionId String dimensionName String tenantId String? tenantName String? applicationId String? channelId String? signatureId String? drainageInfoId String? submittedUnits Int @default(0) sentUnits Int @default(0) unknownUnits Int @default(0) successUnits Int @default(0) failedUnits Int @default(0) successRateBps Int @default(0) avgArrivalMs Int? generatedAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([reportDate, dimensionType, dimensionId]) @@index([dimensionType, reportDate, sentUnits]) @@index([tenantId, reportDate]) @@index([applicationId, reportDate]) @@index([channelId, reportDate]) @@index([signatureId, reportDate]) @@index([drainageInfoId, reportDate]) } 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 CmppInboundLongMessage { id String @id @default(cuid()) tenantId String applicationId String groupKey String account String srcId String? phoneNumbers Json concatReference Int segmentTotal Int msgFmt Int messageId String @unique status String @default("collecting") response Json? expiresAt DateTime completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade) segments CmppInboundLongMessageSegment[] @@index([groupKey, status, createdAt]) @@index([applicationId, status, expiresAt]) } model CmppInboundLongMessageSegment { id String @id @default(cuid()) groupId String segmentIndex Int sequenceId String? registeredDelivery Boolean @default(true) content String contentHash String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt group CmppInboundLongMessage @relation(fields: [groupId], references: [id], onDelete: Cascade) @@unique([groupId, segmentIndex]) @@index([sequenceId]) } model CmppInboundSubmissionInbox { id String @id @default(cuid()) requestKey String @unique payloadHash String tenantId String applicationId String queuePriority String @default("normal") payload Json response Json status String @default("pending") attempts Int @default(0) nextAttemptAt DateTime @default(now()) lockedAt DateTime? lockedBy String? lastError String? result Json? completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) application SmsApplication @relation(fields: [applicationId], references: [id]) @@index([status, nextAttemptAt, createdAt]) @@index([status, lockedAt]) @@index([applicationId, createdAt]) @@index([queuePriority, status, createdAt]) } model SmsReceiptRecord { id String @id @default(cuid()) tenantId String? batchTaskId String? messageRecordId String? receiptKey String @unique channelId String? messageId String gatewayMessageId String phoneNumber String? sequenceId Int? receiptStatus String rawStatus String errorCode String? errorMessage 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]) anomalies SmsReceiptAnomaly[] @@index([tenantId, createdAt]) @@index([messageId]) @@index([gatewayMessageId]) @@index([channelId, gatewayMessageId]) @@index([channelId, gatewayMessageId, phoneNumber]) } model SmsReceiptAnomaly { id String @id @default(cuid()) anomalyKey String @unique tenantId String? applicationId String? channelId String? messageRecordId String? submitRecordId String? receiptRecordId String? anomalyType String status String @default("pending") previousStatus String? incomingStatus String? rawStatus String? errorCode String? detail Json? occurrenceCount Int @default(1) firstOccurredAt DateTime @default(now()) lastOccurredAt DateTime @default(now()) resolvedAt DateTime? resolutionNote String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: SetNull) application SmsApplication? @relation(fields: [applicationId], references: [id], onDelete: SetNull) channel SmsChannel? @relation(fields: [channelId], references: [id], onDelete: SetNull) messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id], onDelete: SetNull) submitRecord SmsSubmitRecord? @relation(fields: [submitRecordId], references: [id], onDelete: SetNull) receiptRecord SmsReceiptRecord? @relation(fields: [receiptRecordId], references: [id], onDelete: SetNull) @@index([status, lastOccurredAt]) @@index([lastOccurredAt]) @@index([anomalyType, lastOccurredAt]) @@index([tenantId, status, lastOccurredAt]) @@index([applicationId, status, lastOccurredAt]) @@index([channelId, status, lastOccurredAt]) @@index([messageRecordId]) @@index([submitRecordId]) } model SmsUplinkMessage { id String @id @default(cuid()) eventId String? @unique tenantId String? applicationId String? channelId String messageRecordId String? messageId String? gatewayMessageId 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([receivedAt]) @@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? dedupeKey String? @unique deliveryType String status String @default("pending") payload Json retryEnabled Boolean @default(true) retryCount Int @default(0) manualRetryCount Int @default(0) lastRetriedAt DateTime? 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]) attempts CmppDownstreamDeliveryAttempt[] requeueItems DownstreamRequeueTaskItem[] @@index([tenantId, status, createdAt]) @@index([applicationId, status, createdAt]) @@index([messageId]) @@index([messageRecordId]) @@index([deliveryType, status]) @@index([status, ackDeadlineAt]) } model DownstreamRequeueTask { id String @id @default(cuid()) taskNo String @unique tenantId String? applicationId String? status String @default("queued") filterSnapshot Json snapshotAt DateTime reason String ratePerSecond Int @default(10) consecutiveFailureLimit Int @default(10) totalCount Int @default(0) successCount Int @default(0) failedCount Int @default(0) skippedCount Int @default(0) waitingCount Int @default(0) consecutiveFailures Int @default(0) applicationFailures Json @default("{}") lastError String? scanLeaseOwner String? scanLeaseUntil DateTime? createdById String? startedAt DateTime? pausedAt DateTime? finishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant? @relation(fields: [tenantId], references: [id]) application SmsApplication? @relation(fields: [applicationId], references: [id]) createdBy User? @relation("DownstreamRequeueTaskCreator", fields: [createdById], references: [id]) items DownstreamRequeueTaskItem[] @@index([status, createdAt]) @@index([applicationId, status, createdAt]) @@index([tenantId, createdAt]) } model DownstreamRequeueRateWindow { id String @id @default(cuid()) applicationId String windowStartedAt DateTime consumed Int @default(0) updatedAt DateTime @updatedAt createdAt DateTime @default(now()) @@unique([applicationId, windowStartedAt]) @@index([windowStartedAt]) } model DownstreamRequeueTaskItem { id String @id @default(cuid()) taskId String deliveryId String applicationId String status String @default("queued") previousStatus String skipReason String? errorMessage String? claimedAt DateTime? completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt task DownstreamRequeueTask @relation(fields: [taskId], references: [id], onDelete: Cascade) delivery CmppDownstreamDelivery @relation(fields: [deliveryId], references: [id], onDelete: Restrict) @@unique([taskId, deliveryId]) @@index([taskId, status, createdAt]) @@index([applicationId, status, createdAt]) @@index([deliveryId, status]) } model CmppDownstreamDeliveryAttempt { id String @id @default(cuid()) deliveryId String attemptKey String @unique attemptNo Int connectionId String? sequenceId String? messageId String? status String sentAt DateTime? ackDeadlineAt DateTime? acknowledgedAt DateTime? ackResult Int? failureType String? errorMessage String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt delivery CmppDownstreamDelivery @relation(fields: [deliveryId], references: [id], onDelete: Cascade) @@index([deliveryId, attemptNo]) @@index([status, ackDeadlineAt]) @@index([connectionId, sequenceId]) } model UpstreamReceiptInbox { id String @id @default(cuid()) receiptKey String @unique incomingChannelId String incomingConnectionId String? upstreamAccount String upstreamHost String upstreamPort Int protocol String protocolVersion String provisionalMessageId String? sequenceId Int? gatewayMessageId String phoneNumber String? receiptStatus String rawStatus String errorCode String? errorMessage String? deliveredAt DateTime receivedAt DateTime @default(now()) status String @default("pending") matchedMessageRecordId String? matchedSubmitRecordId String? matchedChannelId String? attemptCount Int @default(0) nextRetryAt DateTime? lastError String? processedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, nextRetryAt, receivedAt]) @@index([gatewayMessageId, phoneNumber]) @@index([incomingChannelId, receivedAt]) @@index([matchedMessageRecordId]) } 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]) } model SecurityDetectionRule { id String @id @default(cuid()) code String @unique name String sourceType String enabled Boolean @default(true) threshold Int windowSeconds Int cooldownSeconds Int severity String defaultBlockSeconds Int maximumBlockSeconds Int configVersion Int @default(1) effectiveVersion Int @default(0) applyStatus String @default("pending") lastApplyError String? pendingConfig Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt events SecurityDetectionEvent[] alerts SecurityAlert[] @@index([enabled, sourceType]) } model SecurityDetectionEvent { id String @id @default(cuid()) eventKey String @unique ruleId String sourceIp String sourcePort Int? accountHash String? path String? protocol String? resultCode String? evidence Json? occurredAt DateTime createdAt DateTime @default(now()) rule SecurityDetectionRule @relation(fields: [ruleId], references: [id], onDelete: Restrict) @@index([ruleId, occurredAt]) @@index([sourceIp, occurredAt]) } model SecurityAlert { id String @id @default(cuid()) fingerprint String @unique ruleId String sourceIp String severity String status String @default("open") eventCount Int @default(0) windowStartedAt DateTime firstOccurredAt DateTime lastOccurredAt DateTime acknowledgedAt DateTime? acknowledgedById String? ignoredAt DateTime? ignoredById String? ignoreReason String? blockId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt rule SecurityDetectionRule @relation(fields: [ruleId], references: [id], onDelete: Restrict) @@index([status, severity, lastOccurredAt]) @@index([sourceIp, status, lastOccurredAt]) @@index([ruleId, status, lastOccurredAt]) } model SecurityBlock { id String @id @default(cuid()) operationKey String @unique alertId String? sourceIp String executor String status String @default("requested") durationSeconds Int reason String requestedById String requestedAt DateTime @default(now()) appliedAt DateTime? expiresAt DateTime? releasedAt DateTime? releasedById String? executorReference String? lastError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, expiresAt]) @@index([sourceIp, status, requestedAt]) @@index([alertId]) } model SecurityProtectedNetwork { id String @id @default(cuid()) network String @unique name String reason String enabled Boolean @default(true) createdById String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([enabled, createdAt]) } model InfrastructureAlertSetting { id String @id @default("global") configVersion Int @default(1) effectiveVersion Int @default(1) thresholds Json effectiveThresholds Json applyStatus String @default("effective") lastError String? updatedById String? appliedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model InfrastructureAlertRead { id String @id @default(cuid()) fingerprint String activeAt DateTime userId String readAt DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([fingerprint, userId]) @@index([userId, readAt]) }