generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model MailboxAccount { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt name String @db.VarChar(128) host String @default("imap.qq.com") @db.VarChar(255) port Int @default(993) @db.UnsignedInt username String @db.VarChar(320) /// AES-GCM ciphertext (base64); empty when OAuth-only passwordEnc String @default("") @map("password_enc") @db.VarChar(1024) /// PASSWORD = 授权码;OAUTH = XOAUTH2 authType String @default("PASSWORD") @map("auth_type") @db.VarChar(16) oauthProvider String? @map("oauth_provider") @db.VarChar(32) oauthRefreshEnc String? @map("oauth_refresh_enc") @db.VarChar(2048) oauthAccessEnc String? @map("oauth_access_enc") @db.VarChar(2048) oauthExpiresAt DateTime? @map("oauth_expires_at") @db.DateTime(3) idleEnabled Boolean @default(true) @map("idle_enabled") folder String @default("INBOX") @db.VarChar(64) enabled Boolean @default(true) lastTestAt DateTime? @map("last_test_at") @db.DateTime(3) lastTestOk Boolean? @map("last_test_ok") lastTestError String? @map("last_test_error") @db.VarChar(500) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) mails MailMessage[] @@unique([username], map: "uk_mailbox_username") @@index([enabled], map: "idx_mailbox_enabled") @@map("mailbox_account") } model MailMessage { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt // utf8mb4 unique key limit: keep message_id ≤ 191 chars for UNIQUE messageId String? @unique @map("message_id") @db.VarChar(191) mailboxAccountId BigInt? @map("mailbox_account_id") @db.UnsignedBigInt imapUid BigInt? @map("imap_uid") @db.UnsignedBigInt folder String @default("INBOX") @db.VarChar(64) subject String @default("(无主题)") @db.VarChar(512) fromAddr String @default("") @map("from_addr") @db.VarChar(320) receivedAt DateTime? @map("received_at") @db.DateTime(3) bodyText String? @map("body_text") @db.MediumText /// OCR 抽取文本(权威解析辅助) ocrText String? @map("ocr_text") @db.MediumText /// 线程根 Message-ID(规范化) threadId String? @map("thread_id") @db.VarChar(191) inReplyTo String? @map("in_reply_to") @db.VarChar(191) referencesHeader String? @map("references_header") @db.Text isThreadRoot Boolean @default(true) @map("is_thread_root") mailType String @default("UNKNOWN") @map("mail_type") @db.VarChar(32) status String @default("FETCHED") @db.VarChar(32) typeEvidence Json? @map("type_evidence") snapshotPath String? @map("snapshot_path") @db.VarChar(512) rawHash String? @unique @map("raw_hash") @db.Char(64) lastError String? @map("last_error") @db.VarChar(1000) version Int @default(1) @db.UnsignedInt createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) mailboxAccount MailboxAccount? @relation(fields: [mailboxAccountId], references: [id], onDelete: SetNull) attachments MailAttachment[] parseResult ParseResult? imports ContainerImport[] @@unique([mailboxAccountId, folder, imapUid], map: "uk_mailbox_folder_uid") @@index([status, receivedAt], map: "idx_status_received") @@index([mailType], map: "idx_mail_type") @@index([mailboxAccountId], map: "idx_mailbox") @@index([threadId], map: "idx_thread") @@map("mail_message") } model MailAttachment { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt mailId BigInt @map("mail_id") @db.UnsignedBigInt filename String @db.VarChar(512) contentType String? @map("content_type") @db.VarChar(128) sha256 String @db.Char(64) path String @db.VarChar(512) size Int @db.UnsignedInt rejected Boolean @default(false) templateId String? @map("template_id") @db.VarChar(64) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) mail MailMessage @relation(fields: [mailId], references: [id]) @@index([mailId], map: "idx_mail") @@map("mail_attachment") } model ParseResult { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt mailId BigInt @unique @map("mail_id") @db.UnsignedBigInt containerHeader Json @map("container_header") shipments Json lineage Json? createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) mail MailMessage @relation(fields: [mailId], references: [id]) @@map("parse_result") } model ContainerImport { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt mailId BigInt @map("mail_id") @db.UnsignedBigInt containerNo String @map("container_no") @db.VarChar(50) externalId String? @map("external_id") @db.VarChar(64) requestBody String? @map("request_body") @db.MediumText responseBody String? @map("response_body") @db.MediumText status String @db.VarChar(32) lastError String? @map("last_error") @db.VarChar(1000) shipmentsHash String @map("shipments_hash") @db.Char(64) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) mail MailMessage @relation(fields: [mailId], references: [id]) compensations ImportCompensation[] @@unique([mailId, containerNo, shipmentsHash], map: "uk_mail_container_shiphash") @@index([mailId], map: "idx_mail") @@index([containerNo], map: "idx_container") @@map("container_import") } model ContainerActiveLock { containerNo String @id @map("container_no") @db.VarChar(50) mailId BigInt @map("mail_id") @db.UnsignedBigInt importId BigInt? @map("import_id") @db.UnsignedBigInt lockedAt DateTime @map("locked_at") @db.DateTime(3) @@map("container_active_lock") } model ImportCompensation { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt importId BigInt @map("import_id") @db.UnsignedBigInt retryCount Int @default(0) @map("retry_count") @db.UnsignedInt maxRetry Int @default(3) @map("max_retry") @db.UnsignedInt nextRetryAt DateTime? @map("next_retry_at") @db.DateTime(3) reason String @db.VarChar(64) status String @default("OPEN") @db.VarChar(32) retryToken String @unique @map("retry_token") @db.Char(36) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) import ContainerImport @relation(fields: [importId], references: [id]) @@index([status, nextRetryAt], map: "idx_open") @@map("import_compensation") } model CcTokenCache { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt loginMark String @unique @map("login_mark") @db.Char(36) token String @db.VarChar(128) expireAt DateTime @map("expire_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) @@map("cc_token_cache") } /// Singleton CC 连接配置(id 固定为 1);优先于 .env,密码 AES 加密 model CcSettings { id Int @id @default(1) mock Boolean @default(true) apiBase String @map("api_base") @db.VarChar(512) saasHeader String @map("saas_header") @db.VarChar(64) loginMark String @map("login_mark") @db.Char(36) username String @default("") @db.VarChar(128) /// AES-GCM;空表示沿用 .env 密码或未配置 passwordEnc String? @map("password_enc") @db.VarChar(1024) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) @@map("cc_settings") } /// CC 写能力门禁:live | mock | off model CcWriteCapability { id String @id @db.VarChar(32) mode String @default("mock") @db.VarChar(16) endpoint String? @db.VarChar(512) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) @@map("cc_write_capability") } /// Singleton OAuth 应用凭证(id=1);优先于 .env,secret AES 加密 model OauthSettings { id Int @id @default(1) publicBaseUrl String @default("http://localhost:3000") @map("public_base_url") @db.VarChar(512) googleClientId String @default("") @map("google_client_id") @db.VarChar(256) googleClientSecretEnc String? @map("google_client_secret_enc") @db.VarChar(1024) msClientId String @default("") @map("ms_client_id") @db.VarChar(256) msClientSecretEnc String? @map("ms_client_secret_enc") @db.VarChar(1024) msTenant String @default("common") @map("ms_tenant") @db.VarChar(64) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) @@map("oauth_settings") } model SenderCustomerMap { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt matchType String @map("match_type") @db.VarChar(32) matchValue String @map("match_value") @db.VarChar(320) customerCode String @map("customer_code") @db.VarChar(64) enabled Boolean @default(true) @@unique([matchType, matchValue], map: "uk_match") @@map("sender_customer_map") } model AppUser { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt username String @unique @db.VarChar(64) passwordHash String @map("password_hash") @db.VarChar(128) role String @default("ops") @db.VarChar(16) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) @@map("app_user") } /// Singleton IMAP 自动拉取配置(id=1);poll_interval_ms 优先于 .env POLL_INTERVAL_MS model ImapSettings { id Int @id @default(1) /// 默认 30min;合法范围 3min~7d pollIntervalMs Int @default(1800000) @map("poll_interval_ms") updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) @@map("imap_settings") } /// 拉取过滤规则:关键词 / 发件人白名单 / 发件人黑名单 / 关键词黑名单 model ImapFilterRule { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt kind String @db.VarChar(32) value String @db.VarChar(256) enabled Boolean @default(true) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) @@unique([kind, value], map: "uk_imap_filter_kind_value") @@index([kind, enabled], map: "idx_imap_filter_kind") @@map("imap_filter_rule") } /// 拉取记录(每封一条;最多保留 1000,超则删最旧) model ImapPullLog { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt pulledAt DateTime @default(now()) @map("pulled_at") @db.DateTime(3) mailboxAccountId BigInt? @map("mailbox_account_id") @db.UnsignedBigInt mailboxName String @default("") @map("mailbox_name") @db.VarChar(128) mailboxUsername String @default("") @map("mailbox_username") @db.VarChar(320) fromAddr String @default("") @map("from_addr") @db.VarChar(320) subject String @default("") @db.VarChar(512) mailType String? @map("mail_type") @db.VarChar(32) mailId BigInt? @map("mail_id") @db.UnsignedBigInt result String @db.VarChar(16) reason String? @db.VarChar(64) @@index([pulledAt], map: "idx_imap_pull_at") @@map("imap_pull_log") }