generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model AdminUser { id String @id @default(uuid()) email String @unique name String passwordHash String role String @default("admin") isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt auditLogs AuditLog[] assignedLeads Lead[] @relation("LeadAssignee") } model MediaAsset { id String @id @default(uuid()) url String @unique name String? mimeType String? sizeBytes Int? group String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model HeroSlide { id String @id @default(uuid()) title String kicker String? actionLabel String? image String targetType String? targetValue String? sortOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Destination { id String @id @default(uuid()) name String @unique slug String @unique region String? image String? isHot Boolean @default(false) sortOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt aliases DestinationAlias[] products Product[] } model DestinationAlias { id String @id @default(uuid()) alias String destinationId String destination Destination @relation(fields: [destinationId], references: [id], onDelete: Cascade) @@unique([alias, destinationId]) } model ThemeCard { id String @id @default(uuid()) label String image String targetType String? targetValue String? sortOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CtaBanner { id String @id @default(uuid()) alt String image String targetType String targetValue String? sortOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model DestinationPageConfig { id String @id @default("destination-home") recommendedProductIds Json createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model SearchPageConfig { id String @id @default("search-page") title String subtitle String placeholder String modules Json @default("{}") popularKeywords Json groups Json createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model HomeModule { id String @id label String templateType String @default("explore") content Json? publishedConfig Json? sortOrder Int @default(0) isActive Boolean @default(true) isSystem Boolean @default(false) isDeleted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Product { id String @id @default(uuid()) sourceId Int? @unique title String subtitle String? destinationId String? destination Destination? @relation(fields: [destinationId], references: [id], onDelete: SetNull) priceAmount Int? priceUnit String @default("起/人") pricingTiers Json? tags String[] coverImage String? summary String? durationDays Int? durationNights Int? departureDates String[] @default([]) remainingSpots Int? recommendation String? keyFacts Json? contentBlocks Json? detailSections Json? status String @default("published") sortWeight Int @default(0) publishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt images ProductImage[] campaignLinks CampaignProduct[] leads Lead[] favorites MiniProgramFavorite[] histories MiniProgramHistory[] orders Order[] } model ProductImage { id String @id @default(uuid()) productId String product Product @relation(fields: [productId], references: [id], onDelete: Cascade) url String alt String? sortOrder Int @default(0) } model Campaign { id String @id @default(uuid()) slug String @unique title String description String? coverImage String? status String @default("published") startsAt DateTime? endsAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt products CampaignProduct[] } model CampaignProduct { campaignId String productId String sortOrder Int @default(0) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) @@id([campaignId, productId]) } model Lead { id String @id @default(uuid()) requestType String @default("custom") userId String? user MiniProgramUser? @relation(fields: [userId], references: [id], onDelete: SetNull) wecomContactId String? wecomContact WecomExternalContact? @relation(fields: [wecomContactId], references: [id], onDelete: SetNull) destination String? phone String contactName String? wechat String? travelDate DateTime? peopleCount Int? adultCount Int? childCount Int? roomType String? plan String? addOns String[] @default([]) budgetMin Int? budgetMax Int? note String? sourcePage String? sourceTheme String? sourceProductId String? sourceProduct Product? @relation(fields: [sourceProductId], references: [id], onDelete: SetNull) adminNote String? processed Boolean @default(false) status String @default("new") assignedUserId String? assignedUser AdminUser? @relation("LeadAssignee", fields: [assignedUserId], references: [id], onDelete: SetNull) followups LeadFollowup[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId, createdAt]) @@index([phone]) @@index([requestType, status, createdAt]) @@index([wecomContactId]) } model LeadFollowup { id String @id @default(uuid()) leadId String lead Lead @relation(fields: [leadId], references: [id], onDelete: Cascade) content String nextAt DateTime? createdAt DateTime @default(now()) } model MiniProgramUser { id String @id @default(uuid()) appId String openId String unionId String? nickname String? avatarUrl String? phone String? phoneAuthorizedAt DateTime? note String? source String @default("mini_program") status String @default("active") customerId String? @unique customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull) firstSeenAt DateTime @default(now()) lastLoginAt DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt leads Lead[] favorites MiniProgramFavorite[] histories MiniProgramHistory[] wecomContacts WecomExternalContact[] h5AuthTickets H5AuthTicket[] @@unique([appId, openId]) @@index([phone]) @@index([status, lastLoginAt]) } model H5AuthTicket { id String @id @default(uuid()) tokenHash String @unique userId String user MiniProgramUser @relation(fields: [userId], references: [id], onDelete: Cascade) expiresAt DateTime consumedAt DateTime? createdAt DateTime @default(now()) @@index([expiresAt]) @@index([userId, createdAt]) } model MiniProgramFavorite { id String @id @default(uuid()) userId String productId String user MiniProgramUser @relation(fields: [userId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([userId, productId]) @@index([productId]) } model MiniProgramHistory { id String @id @default(uuid()) userId String productId String viewCount Int @default(1) lastViewedAt DateTime @default(now()) user MiniProgramUser @relation(fields: [userId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) @@unique([userId, productId]) @@index([userId, lastViewedAt]) } model Customer { id String @id @default(uuid()) phone String? @unique name String? wechat String? note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt miniProgramUser MiniProgramUser? orders Order[] wecomContacts WecomExternalContact[] } model WecomContactWay { id String @id @default(uuid()) configId String @unique type Int @default(1) scene Int @default(1) style Int @default(1) remark String? state String? wecomUserId String? enabled Boolean @default(true) lastSyncedAt DateTime? lastSyncError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt externalContacts WecomExternalContact[] } model WecomExternalContact { id String @id @default(uuid()) externalUserId String wecomUserId String contactWayId String? contactWay WecomContactWay? @relation(fields: [contactWayId], references: [id], onDelete: SetNull) miniProgramUserId String? miniProgramUser MiniProgramUser? @relation(fields: [miniProgramUserId], references: [id], onDelete: SetNull) customerId String? customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull) name String? avatarUrl String? type Int? gender Int? unionId String? corpName String? position String? remark String? description String? profile Json? status String @default("active") addedAt DateTime? lastEventAt DateTime @default(now()) deletedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt leads Lead[] @@unique([externalUserId, wecomUserId]) @@index([status, lastEventAt]) @@index([miniProgramUserId]) @@index([customerId]) @@index([contactWayId]) } model Order { id String @id @default(uuid()) customerId String? customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull) productId String? product Product? @relation(fields: [productId], references: [id], onDelete: SetNull) status String @default("draft") travelDate DateTime? amount Int? note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model SiteVersion { id String @id @default(uuid()) title String status String @default("draft") snapshot Json publishedAt DateTime? createdAt DateTime @default(now()) } model AuditLog { id String @id @default(uuid()) actorId String? actor AdminUser? @relation(fields: [actorId], references: [id], onDelete: SetNull) action String entity String entityId String? before Json? after Json? createdAt DateTime @default(now()) }