generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

//////////////////// ENUMS ////////////////////

// 유저 역할
enum UserType {
  CUSTOMER // 일반 고객
  MOVER // 기사님
}

// 로그인 제공자
enum AuthProvider {
  LOCAL // 자체 가입
  GOOGLE // 구글
  NAVER // 네이버
  KAKAO // 카카오
}

// 이사 유형
enum MoveType {
  SMALL // 소형 이사
  HOME // 가정집 이사
  OFFICE // 사무실 이사
}

// 견적 요청 상태
enum RequestStatus {
  PENDING // 대기중 (견적 기다리는 상태)
  APPROVED // 승인됨 (고객이 견적 수락)
  COMPLETED // 완료됨 (이사 완료)
  REJECTED // 거절됨 (기사님이 요청 거절)
  CANCELLED // 취소됨 (고객이 취소)
  EXPIRED // 만료됨 (기한 초과)
}

// 견적 응답 상태
enum EstimateStatus {
  PROPOSED // 제안됨 (기사님이 견적 제출)
  ACCEPTED // 수락됨 (고객이 견적 수락)
  REJECTED // 거절됨 (기사님이 견적 거절)
  AUTO_REJECTED // 자동 거절됨 (다른 견적 수락으로 인한 자동 거절)
}

// 주소 역할
enum AddressRole {
  FROM // 출발지
  TO // 도착지
  EXTRA // 기타 주소 (예: 짐 보관처)
}

// 전국 광역시/도
enum RegionType {
  SEOUL // 서울특별시
  BUSAN // 부산광역시
  DAEGU // 대구광역시
  INCHEON // 인천광역시
  GWANGJU // 광주광역시
  DAEJEON // 대전광역시
  ULSAN // 울산광역시
  SEJONG // 세종특별자치시
  GYEONGGI // 경기도
  GANGWON // 강원도
  CHUNGBUK // 충청북도
  CHUNGNAM // 충청남도
  JEONBUK // 전라북도
  JEONNAM // 전라남도
  GYEONGBUK // 경상북도
  GYEONGNAM // 경상남도
  JEJU // 제주특별자치도
}

// 알림 종류
enum NotificationType {
  WELCOME // 회원가입 환영
  ESTIMATE_REQUEST_ARRIVED // 견적 요청 도착  
  ESTIMATE_ARRIVED // 견적 도착
  ESTIMATE_STATUS_UPDATED // 견적 수락/거절
  DESIGNATED_ESTIMATE_REQUEST_STATUS_UPDATED // 지정 견적 요청 관련 상태 변경
  DESIGNATED_ESTIMATE_STATUS_UPDATED // 지정 견적 관련 상태 변경
  REVIEW_EVENT // 리뷰 관련
  FAVORITE_EVENT // 찜 관련
  MOVE_DAY_REMINDER // 이사 리마인더
}

// 사용자 활동 타입
enum ActionType {
  WELCOME // 회원가입 환영
  ESTIMATE_REQUEST_CREATE // 견적 요청 생성 
  ESTIMATE_SUBMITTED // 견적 제출
  ESTIMATE_ACCEPTED // 견적 수락
  ESTIMATE_REJECTED // 견적 거절
  DESIGNATED_ESTIMATE_REQUEST_SUBMITTED // 지정 요청 발송 
  DESIGNATED_ESTIMATE_REQUEST_REJECTED // 지정 요청 거절  
  DESIGNATED_ESTIMATE_SUBMITTED // 지정 견적 제출
  DESIGNATED_ESTIMATE_ACCEPTED // 지정 견적 수락
  DESIGNATED_ESTIMATE_REJECTED // 지정 견적 거절
  REVIEW_SUBMITTED // 리뷰 작성
  FAVORITE_ADDED // 찜 추가
  FAVORITE_REMOVED // 찜 제거
  MOVE_DAY_REMINDER_TOMORROW // 이사 전날
  MOVE_DAY_REMINDER_TODAY // 이사 당일
  MOVE_DAY_REVIEW_REQUEST // 이사 다음날
}

enum ReviewStatus {
  PENDING
  COMPLETED
  EXPIRED
}

//////////////////// MODELS ////////////////////

// 통합 사용자 모델 (일반 고객과 기사님 모두)
model User {
  id                   String       @id @default(cuid())
  email                String       @unique // 이메일 (고유)
  encryptedPassword    String? // 암호화된 비밀번호 (소셜 로그인은 null)
  refreshToken         String? // 리프레시 토큰 (JWT, 암호화 저장 권장)
  encryptedPhoneNumber String? // 암호화된 전화번호
  name                 String // 실명
  userType             UserType[] // 유저 타입 배열 (CUSTOMER, MOVER 둘 다 가능)
  provider             AuthProvider @default(LOCAL) // 로그인 제공자
  providerId           String? // 소셜 로그인 ID
  customerImage        String? // 고객 프로필 이미지
  moverImage           String? // 기사님 프로필 이미지
  nickname             String?      @unique // 닉네임 (모든 유저 사용 가능)
  createdAt            DateTime     @default(now())
  updatedAt            DateTime     @updatedAt
  deletedAt            DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)
  totalFavoriteCount   Int          @default(0) // 받은 찜 개수

  // Customer 전용 필드
  currentArea       RegionType? // 현재 거주 지역
  preferredServices MoveType[] // 이용하고 싶은 서비스 타입들
  isCustomer        Boolean? // 일반 유저 프로필 등록 여부 (옵셔널)

  // Mover 전용 필드
  shortIntro       String? // 간단 소개
  detailIntro      String? // 상세 소개
  career           Int? // 경력 (년)
  isMover          Boolean? // 기사님 프로필 등록 여부 (옵셔널)
  isVeteran        Boolean? // 베테랑 여부 (옵셔널)
  workedCount      Int?         @default(0) // 완료된 이사 건수
  averageRating    Float?       @default(0) // 평균 평점
  totalReviewCount Int?         @default(0) // 총 리뷰 수
  currentAreas     RegionType[] // 서비스 가능 지역들
  serviceTypes     MoveType[] // 제공 가능한 서비스 타입들

  // 관계
  addresses          UserAddress[] // 등록한 주소들
  serviceAreas       MoverServiceArea[] // 기사님 활동 가능 지역
  requests           EstimateRequest[] // 고객이 생성한 견적 요청들
  estimates          Estimate[] // 기사님이 작성한 견적들
  reviewsWritten     Review[]           @relation("ReviewWriter") // 작성한 리뷰들
  reviewsReceived    Review[]           @relation("ReviewReceiver") // 받은 리뷰들
  favorites          Favorite[]         @relation("UserFavorites") // 찜한 기사님들
  designatedRequests DesignatedMover[] // 지정 요청받은 견적들
  actions            Action[] // 사용자 활동들
  notifications      Notification[] // 받은 알림들
  Favorite           Favorite[]

  @@index([email])
  @@index([deletedAt])
  @@map("users")
}

// 주소 정보 테이블
model Address {
  id        String     @id @default(cuid())
  zoneCode  String // 우편번호
  city      String // 도시 (시/군)
  district  String // 구/동
  detail    String? // 상세주소 (동, 호 등)
  region    RegionType // 광역시/도
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
  deletedAt DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  requestsFrom  EstimateRequest[] @relation("RequestFrom") // 출발지로 사용된 견적 요청들
  requestsTo    EstimateRequest[] @relation("RequestTo") // 도착지로 사용된 견적 요청들
  userAddresses UserAddress[] // 사용자별 주소 연결

  @@index([region])
  @@index([city])
  @@index([district])
  @@index([deletedAt])
  @@map("addresses")
}

// 사용자별 주소 연결 테이블
model UserAddress {
  id          String      @id @default(cuid())
  userId      String // 사용자 ID
  addressId   String // 주소 ID
  role        AddressRole // 주소 역할 (FROM, TO, EXTRA)
  customLabel String? // 사용자 정의 라벨 (예: "회사", "창고")
  createdAt   DateTime    @default(now())
  updatedAt   DateTime    @updatedAt
  deletedAt   DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  user    User    @relation(fields: [userId], references: [id])
  address Address @relation(fields: [addressId], references: [id])

  @@unique([userId, addressId, role]) // 같은 사용자가 동일 주소를 같은 역할로 중복 등록 불가
  @@index([deletedAt])
  @@index([userId])
  @@index([addressId])
  @@map("user_addresses")
}

// 기사님 활동 가능 지역 정보
model MoverServiceArea {
  id        String     @id @default(cuid())
  userId    String // 기사님 ID
  region    RegionType // 광역시/도
  district  String? // 구/동 (선택사항)
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
  deletedAt DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  user User @relation(fields: [userId], references: [id])

  @@unique([userId, region, district]) // 같은 기사님이 동일 지역 중복 등록 불가
  @@index([region])
  @@index([district])
  @@index([deletedAt])
  @@map("mover_service_areas")
}

// 견적 요청 테이블
model EstimateRequest {
  id            String        @id @default(cuid())
  customerId    String // 고객 ID
  moveType      MoveType // 이사 유형
  moveDate      DateTime // 이사 날짜
  fromAddressId String // 출발지 주소 ID
  toAddressId   String // 도착지 주소 ID
  status        RequestStatus // 요청 상태
  description   String? // 추가 설명
  createdAt     DateTime      @default(now())
  updatedAt     DateTime      @updatedAt
  deletedAt     DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  customer         User              @relation(fields: [customerId], references: [id])
  fromAddress      Address           @relation("RequestFrom", fields: [fromAddressId], references: [id])
  toAddress        Address           @relation("RequestTo", fields: [toAddressId], references: [id])
  estimates        Estimate[] // 받은 견적들
  review           Review? // 작성된 리뷰 (1개만)
  designatedMovers DesignatedMover[] // 지정 요청한 기사님들

  @@index([customerId])
  @@index([status])
  @@index([moveDate])
  @@index([moveType])
  @@index([deletedAt])
  @@map("estimate_requests")
}

// 지정 기사 요청 테이블
model DesignatedMover {
  id                String        @id @default(cuid())
  estimateRequestId String // 견적 요청 ID
  moverId           String // 기사님 ID
  message           String? // 요청 메시지
  status            RequestStatus @default(PENDING) // 요청 상태
  expiresAt         DateTime // 만료일
  createdAt         DateTime      @default(now())
  updatedAt         DateTime      @updatedAt
  deletedAt         DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  estimateRequest EstimateRequest @relation(fields: [estimateRequestId], references: [id])
  mover           User            @relation(fields: [moverId], references: [id])

  @@unique([estimateRequestId, moverId]) // 한 요청에 동일 기사님 중복 지정 불가
  @@index([estimateRequestId])
  @@index([moverId])
  @@index([status])
  @@index([deletedAt])
  @@map("designated_movers")
}

// 견적 응답 테이블
model Estimate {
  id                String         @id @default(cuid())
  moverId           String // 기사님 ID
  estimateRequestId String // 견적 요청 ID
  price             Int? // 제안 가격
  comment           String? // 코멘트
  status            EstimateStatus @default(PROPOSED) // 견적 상태
  rejectReason      String? // 거절 사유
  isDesignated      Boolean        @default(false) // 지정 기사 여부
  workingHours      String? // 작업 소요 시간 (예: "4-6시간")
  includesPackaging Boolean        @default(false) // 포장 서비스 포함 여부
  insuranceAmount   Int? // 보험 금액
  validUntil        DateTime? // 견적 유효기간
  createdAt         DateTime       @default(now())
  updatedAt         DateTime       @updatedAt
  deletedAt         DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  mover           User            @relation(fields: [moverId], references: [id])
  estimateRequest EstimateRequest @relation(fields: [estimateRequestId], references: [id])

  @@unique([estimateRequestId, moverId]) // 한 요청에 동일 기사님 중복 견적 불가
  @@index([moverId])
  @@index([status])
  @@index([price])
  @@index([isDesignated])
  @@index([deletedAt])
  @@map("estimates")
}

// 리뷰 테이블 (1개의 견적 요청당 1개만)
model Review {
  id                String       @id @default(cuid())
  customerId        String // 작성자 ID
  moverId           String // 대상 기사님 ID
  estimateRequestId String // 견적 요청 ID
  rating            Int          @default(0)
  content           String       @default("")
  status            ReviewStatus @default(PENDING)
  createdAt         DateTime     @default(now())
  updatedAt         DateTime     @updatedAt
  deletedAt         DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  writer  User            @relation("ReviewWriter", fields: [customerId], references: [id])
  mover   User            @relation("ReviewReceiver", fields: [moverId], references: [id])
  request EstimateRequest @relation(fields: [estimateRequestId], references: [id])

  @@unique([estimateRequestId]) // 한 견적 요청당 리뷰 1개만
  @@index([moverId])
  @@index([rating])
  @@index([deletedAt])
  @@map("reviews")
}

// 찜 기능 테이블
model Favorite {
  id         String    @id @default(cuid())
  customerId String // 찜한 고객 ID
  moverId    String // 찜받은 기사님 ID
  createdAt  DateTime  @default(now())
  updatedAt  DateTime  @updatedAt
  deletedAt  DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  customer User @relation("UserFavorites", fields: [customerId], references: [id])
  mover    User @relation(fields: [moverId], references: [id])

  @@unique([customerId, moverId]) // 중복 찜 방지
  @@index([customerId])
  @@index([moverId])
  @@index([deletedAt])
  @@map("favorites")
}

// 사용자 활동 추적 테이블 (알림 생성의 기반)
model Action {
  id          String     @id @default(cuid())
  userId      String // 활동한 사용자 ID
  type        ActionType // 활동 타입
  entityId    String // 관련 엔티티 ID (견적요청, 견적 등)
  entityType  String // 엔티티 타입 ("EstimateRequest", "Estimate", "Review" 등)
  description String? // 활동 설명
  metadata    Json? // 추가 데이터
  createdAt   DateTime   @default(now())
  deletedAt   DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  user          User           @relation(fields: [userId], references: [id])
  notifications Notification[] // 이 활동으로 생성된 알림들

  @@index([userId])
  @@index([type])
  @@index([entityId, entityType])
  @@index([createdAt])
  @@index([deletedAt])
  @@map("actions")
}

// 알림 테이블 (Action 기반)
model Notification {
  id        String           @id @default(cuid())
  actionId  String // 연결된 Action ID
  userId    String // 수신자 ID
  type      NotificationType // 알림 종류
  title     String // 알림 제목
  content   String // 알림 내용
  path      String? // 클릭 시 이동 경로
  isRead    Boolean          @default(false) // 읽음 여부
  createdAt DateTime         @default(now())
  updatedAt DateTime         @updatedAt
  deletedAt DateTime? // 소프트 삭제 (API에서 YYYY-MM-DD 형식으로 가공)

  // 관계
  action Action @relation(fields: [actionId], references: [id])
  user   User   @relation(fields: [userId], references: [id])

  @@index([userId, isRead])
  @@index([actionId])
  @@index([type])
  @@index([createdAt])
  @@index([deletedAt])
  @@map("notifications")
}

prisma-erd.svg