generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } // ===== Enums ===== enum UserRole { MEMBER ADMIN } enum CardTypeCategory { TIMES DURATION TRIAL } enum MembershipStatus { ACTIVE EXPIRED USED_UP } enum TimeSlotStatus { OPEN FULL CLOSED } enum TimeSlotSource { TEMPLATE MANUAL } enum BookingStatus { PENDING_CONFIRMATION CONFIRMED CANCELLED COMPLETED NO_SHOW } enum OrderStatus { PENDING PAID REFUNDED } enum InviteReferralStatus { REGISTERED TRIAL_PURCHASED QUALIFIED } // ===== Models ===== model User { id String @id @default(uuid()) inviteCode String? @unique @map("invite_code") @db.VarChar(6) openid String @unique unionid String? phone String? nickname String @default("") avatarUrl String? @map("avatar_url") role UserRole @default(MEMBER) adminBookingSubscriptionCount Int @default(0) @map("admin_booking_subscription_count") lastLoginAt DateTime? @map("last_login_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") bodyMetrics BodyMetric[] memberNotes MemberNote[] progressPhotos ProgressPhoto[] lessonSupplements LessonSupplement[] memberships Membership[] bookings Booking[] orders Order[] subscriptionMessageConsents SubscriptionMessageConsent[] sentInviteReferrals InviteReferral[] @relation("InviteReferralInviter") receivedInviteReferral InviteReferral[] @relation("InviteReferralInvitee") inviteRewardGrants InviteRewardGrant[] @relation("InviteRewardGrantInviter") @@map("users") } model SubscriptionMessageConsent { id String @id @default(uuid()) userId String @map("user_id") templateId String @map("template_id") scene String totalRequestCount Int @default(0) @map("total_request_count") acceptCount Int @default(0) @map("accept_count") rejectCount Int @default(0) @map("reject_count") banCount Int @default(0) @map("ban_count") filterCount Int @default(0) @map("filter_count") sentCount Int @default(0) @map("sent_count") lastResult String @map("last_result") lastRequestedAt DateTime @default(now()) @map("last_requested_at") lastSentAt DateTime? @map("last_sent_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id]) @@unique([userId, templateId, scene]) @@index([userId]) @@index([scene]) @@map("subscription_message_consents") } model CardType { id String @id @default(uuid()) name String type CardTypeCategory totalTimes Int? @map("total_times") durationDays Int @map("duration_days") price Decimal @db.Decimal(10, 0) originalPrice Decimal? @map("original_price") @db.Decimal(10, 0) description String? coverUrl String? @map("cover_url") isActive Boolean @default(true) @map("is_active") sortOrder Int @default(0) @map("sort_order") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") memberships Membership[] orders Order[] @@map("card_types") } model Membership { id String @id @default(uuid()) userId String @map("user_id") cardTypeId String @map("card_type_id") remainingTimes Int? @map("remaining_times") totalTimes Int? @map("total_times") startDate DateTime @map("start_date") expireDate DateTime @map("expire_date") status MembershipStatus @default(ACTIVE) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") lessonSupplements LessonSupplement[] user User @relation(fields: [userId], references: [id]) cardType CardType @relation(fields: [cardTypeId], references: [id]) bookings Booking[] orders Order[] inviteRewardGrants InviteRewardGrant[] @@index([userId]) @@index([userId, cardTypeId]) @@index([status]) @@map("memberships") } model WeekTemplate { id String @id @default(uuid()) dayOfWeek Int @map("day_of_week") startTime String @map("start_time") endTime String @map("end_time") capacity Int @default(1) isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") timeSlots TimeSlot[] @@map("week_templates") } model TimeSlot { id String @id @default(uuid()) date DateTime @db.Date startTime String @map("start_time") endTime String @map("end_time") capacity Int @default(1) bookedCount Int @default(0) @map("booked_count") status TimeSlotStatus @default(OPEN) source TimeSlotSource @default(TEMPLATE) templateId String? @map("template_id") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") template WeekTemplate? @relation(fields: [templateId], references: [id]) bookings Booking[] @@unique([date, startTime, endTime]) @@index([date]) @@index([status]) @@map("time_slots") } model Booking { id String @id @default(uuid()) userId String @map("user_id") timeSlotId String @map("time_slot_id") membershipId String @map("membership_id") membershipTimesDeducted Boolean? @map("membership_times_deducted") status BookingStatus @default(PENDING_CONFIRMATION) cancelledAt DateTime? @map("cancelled_at") confirmedAt DateTime? @map("confirmed_at") completedAt DateTime? @map("completed_at") operatorId String? @map("operator_id") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id]) timeSlot TimeSlot @relation(fields: [timeSlotId], references: [id]) membership Membership @relation(fields: [membershipId], references: [id]) qualifiedInviteReferrals InviteReferral[] review BookingReview? memberNotes MemberNote[] reviewReminderDueAt DateTime? @map("review_reminder_due_at") reviewReminderClaimedAt DateTime? @map("review_reminder_claimed_at") reviewReminderSentAt DateTime? @map("review_reminder_sent_at") classReminderDueAt DateTime? @map("class_reminder_due_at") classReminderClaimedAt DateTime? @map("class_reminder_claimed_at") classReminderSentAt DateTime? @map("class_reminder_sent_at") statusHistory BookingStatusHistory[] @@unique([userId, timeSlotId]) @@index([userId]) @@index([status]) @@map("bookings") } model BookingStatusHistory { id String @id @default(uuid()) bookingId String @map("booking_id") fromStatus String? @map("from_status") toStatus String @map("to_status") operatorId String? @map("operator_id") remark String? createdAt DateTime @default(now()) @map("created_at") booking Booking @relation(fields: [bookingId], references: [id]) @@index([bookingId]) @@map("booking_status_history") } model Order { id String @id @default(uuid()) userId String @map("user_id") cardTypeId String @map("card_type_id") membershipId String? @map("membership_id") orderNo String @unique @map("order_no") inviteInviterId String? @map("invite_inviter_id") purchasedCategory String? @map("purchased_category") amount Decimal @db.Decimal(10, 0) status OrderStatus @default(PENDING) wxTransactionId String? @map("wx_transaction_id") paidAt DateTime? @map("paid_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id]) cardType CardType @relation(fields: [cardTypeId], references: [id]) membership Membership? @relation(fields: [membershipId], references: [id]) inviteReferrals InviteReferral[] @@index([userId]) @@index([status]) @@index([membershipId]) @@map("orders") } model InviteReferral { id String @id @default(uuid()) inviterId String @map("inviter_id") inviteeId String @unique @map("invitee_id") status InviteReferralStatus @default(REGISTERED) trialOrderId String? @unique @map("trial_order_id") qualifiedBookingId String? @unique @map("qualified_booking_id") invitedAt DateTime @default(now()) @map("invited_at") trialPurchasedAt DateTime? @map("trial_purchased_at") qualifiedAt DateTime? @map("qualified_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") inviter User @relation("InviteReferralInviter", fields: [inviterId], references: [id]) invitee User @relation("InviteReferralInvitee", fields: [inviteeId], references: [id]) trialOrder Order? @relation(fields: [trialOrderId], references: [id]) qualifiedBooking Booking? @relation(fields: [qualifiedBookingId], references: [id]) @@unique([inviterId, inviteeId]) @@index([inviterId, status]) @@index([status]) @@map("invite_referrals") } model InviteRewardGrant { id String @id @default(uuid()) inviterId String @map("inviter_id") membershipId String? @map("membership_id") qualifiedReferralCount Int @map("qualified_referral_count") rewardTimes Int @default(1) @map("reward_times") grantedAt DateTime @default(now()) @map("granted_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") inviter User @relation("InviteRewardGrantInviter", fields: [inviterId], references: [id]) membership Membership? @relation(fields: [membershipId], references: [id], onDelete: Restrict) @@index([inviterId, grantedAt]) @@map("invite_reward_grants") } model StudioConfig { id String @id @default(uuid()) name String logo String? bannerUrl String? @map("banner_url") address String @default("") phone String @default("") latitude Decimal? @db.Decimal(10, 7) longitude Decimal? @db.Decimal(10, 7) cancelHoursLimit Int @default(2) @map("cancel_hours_limit") photos Json @default("[]") updatedAt DateTime @updatedAt @map("updated_at") @@map("studio_config") } // Historical totals without fabricated scheduled dates. model LessonSupplement { id String @id @default(uuid()) userId String @map("user_id") requestId String @map("request_id") @db.VarChar(80) quantity Int membershipId String? @map("membership_id") deductedTimes Int @default(0) @map("deducted_times") cardName String? @map("card_name") remark String? @db.VarChar(200) operatorId String @map("operator_id") operatorName String @map("operator_name") createdAt DateTime @default(now()) @map("created_at") revokedAt DateTime? @map("revoked_at") revokedBy String? @map("revoked_by") user User @relation(fields: [userId], references: [id]) membership Membership? @relation(fields: [membershipId], references: [id], onDelete: Restrict) @@unique([userId, requestId]) @@index([userId, revokedAt, createdAt]) @@map("lesson_supplements") } model BookingReview { id String @id @default(uuid()) bookingId String @unique @map("booking_id") rating Int recommendation Int? tags Json comment String @db.VarChar(200) createdAt DateTime @default(now()) @map("created_at") booking Booking @relation(fields: [bookingId], references: [id]) @@index([createdAt]) @@map("booking_reviews") } model BodyMetric { id String @id @default(uuid()) userId String @map("user_id") recordedAt DateTime @db.Date @map("recorded_at") weight Float? bodyFat Float? @map("body_fat") waist Float? hip Float? flexibility Float? remark String @default("") @db.VarChar(200) operatorId String @map("operator_id") createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id]) @@index([userId, recordedAt]) @@map("body_metrics") } model MemberNote { id String @id @default(uuid()) userId String @map("user_id") bookingId String? @map("booking_id") content String @db.VarChar(1000) shared Boolean @default(false) operatorId String @map("operator_id") createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id]) booking Booking? @relation(fields: [bookingId], references: [id]) @@index([userId, createdAt]) @@map("member_notes") } model ProgressPhoto { id String @id @default(uuid()) userId String @map("user_id") objectKey String @unique @map("object_key") caption String @default("") @db.VarChar(200) recordedAt DateTime @db.Date @map("recorded_at") uploadedAt DateTime? @map("uploaded_at") consentedAt DateTime? @map("consented_at") revokedAt DateTime? @map("revoked_at") operatorId String @map("operator_id") createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id]) @@index([userId, recordedAt]) @@map("progress_photos") }