mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
1655 lines
48 KiB
Plaintext
1655 lines
48 KiB
Plaintext
generator client {
|
||
provider = "prisma-client-js"
|
||
previewFeatures = ["multiSchema"]
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
schemas = ["passenger"]
|
||
}
|
||
|
||
enum UserRole {
|
||
PASSENGER
|
||
AGENT
|
||
SUPERVISOR
|
||
ADMIN
|
||
STAFF
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum TripStatus {
|
||
SCHEDULED
|
||
BOARDING
|
||
EN_ROUTE
|
||
ARRIVED
|
||
CANCELLED
|
||
DELAYED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum SeatKind {
|
||
STANDARD
|
||
PREMIUM
|
||
ACCESSIBLE
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum SeatStatus {
|
||
AVAILABLE
|
||
HELD
|
||
BOOKED
|
||
BLOCKED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum PassengerCategory {
|
||
ADULT
|
||
CHILD
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum IdDocumentType {
|
||
NATIONAL_ID
|
||
PASSPORT
|
||
DRIVING_LICENSE
|
||
OTHER
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum Currency {
|
||
ETB
|
||
DJF
|
||
USD
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model CoachType {
|
||
id String @id @default(uuid())
|
||
code String
|
||
name String
|
||
type String @default("passenger") // 'passenger', 'sleeper', 'dining', 'baggage'
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
coaches Coach[]
|
||
seatClasses SeatClass[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SeatClass {
|
||
id String @id @default(uuid())
|
||
coachTypeId String
|
||
name String
|
||
description String?
|
||
nationalityType String? // 'LOCAL' | 'INTERNATIONAL'
|
||
bedPosition String? // 'UPPER' | 'MIDDLE' | 'LOWER' | null for regular seat
|
||
baseFareMinor Int @default(0) // per-km rate (tariff decimal × 100000)
|
||
premiumMinor Int @default(0) // flat fee per passenger
|
||
insuranceFeeMinor Int @default(0) // flat fee per passenger
|
||
isActive Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
coachType CoachType @relation(fields: [coachTypeId], references: [id])
|
||
fareRules FareRule[]
|
||
routeFareRules RouteFareRule[]
|
||
segmentFares SegmentFareRule[]
|
||
packagePriceTiers PackagePriceTier[]
|
||
@@unique([coachTypeId, name])
|
||
@@index([coachTypeId])
|
||
@@index([coachTypeId, nationalityType, bedPosition])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum BookingStatus {
|
||
DRAFT
|
||
PENDING_PAYMENT
|
||
CONFIRMED
|
||
CANCELLED
|
||
BOARDED
|
||
NO_SHOW
|
||
REFUNDED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum ReturnLegStatus {
|
||
NOT_APPLICABLE
|
||
BOTH_USED
|
||
OUTBOUND_ONLY
|
||
INBOUND_ONLY
|
||
NEITHER_USED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum PaymentRegion {
|
||
ETHIOPIA
|
||
DJIBOUTI
|
||
INTERNATIONAL
|
||
GLOBAL
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum PaymentMethodType {
|
||
TELEBIRR
|
||
CBE_BIRR
|
||
EBIRR
|
||
CARD
|
||
WALLET
|
||
WAAFI
|
||
DMONEY
|
||
CAC_BANK
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum PaymentIntentStatus {
|
||
REQUIRES_ACTION
|
||
PROCESSING
|
||
SUCCEEDED
|
||
FAILED
|
||
CANCELLED
|
||
REFUNDED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum WalletLedgerType {
|
||
CREDIT
|
||
DEBIT
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum NotificationCategory {
|
||
BOOKING
|
||
PAYMENT
|
||
DISRUPTION
|
||
PROMOTION
|
||
SYSTEM
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum StopStatus {
|
||
OPEN
|
||
CHECKIN_CLOSED
|
||
BOARDED
|
||
COMPLETED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum SupportConversationStatus {
|
||
OPEN
|
||
RESOLVED
|
||
CLOSED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum SupportSender {
|
||
USER
|
||
BOT
|
||
AGENT
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum LoyaltyTier {
|
||
BRONZE
|
||
SILVER
|
||
GOLD
|
||
PLATINUM
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum LoyaltyLedgerReason {
|
||
TRIP_COMPLETED
|
||
REWARD_REDEEMED
|
||
PROMO_BONUS
|
||
MANUAL_ADJUSTMENT
|
||
EXPIRY
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum FoodOrderStatus {
|
||
PENDING
|
||
PREPARING
|
||
READY
|
||
DELIVERED
|
||
CANCELLED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum DevicePlatform {
|
||
IOS
|
||
ANDROID
|
||
WEB
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model User {
|
||
id String @id @default(uuid())
|
||
email String @unique
|
||
phone String @unique
|
||
fullName String
|
||
passwordHash String
|
||
role UserRole @default(PASSENGER)
|
||
nationality String?
|
||
nationalityCode String?
|
||
gender String? // Male, Female, Other
|
||
dateOfBirth DateTime?
|
||
passportNumber String?
|
||
nationalId String?
|
||
failedLoginAttempts Int @default(0)
|
||
lockedUntil DateTime?
|
||
blockedUntil DateTime?
|
||
lastLoginAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
faydaVerified Boolean @default(false)
|
||
faydaVerifiedAt DateTime?
|
||
faydaSub String? @unique
|
||
|
||
sessions Session[]
|
||
passenger Passenger?
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Session {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
token String @unique
|
||
expiresAt DateTime
|
||
ipAddress String?
|
||
userAgent String?
|
||
lastActivityAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Passenger {
|
||
id String @id @default(uuid())
|
||
userId String? @unique
|
||
iamUserId String? @unique
|
||
defaultTravelerProfileId String?
|
||
preferredLanguage String?
|
||
blockedUntil DateTime?
|
||
createdAt DateTime @default(now())
|
||
user User? @relation(fields: [userId], references: [id])
|
||
bookings Booking[]
|
||
loyalty LoyaltyAccount?
|
||
wallet WalletAccount?
|
||
notifications Notification[]
|
||
travelerProfiles TravelerProfile[]
|
||
savedRoutes SavedRoute[]
|
||
packageBookings PackageBooking[]
|
||
supportConversations SupportConversation[]
|
||
@@index([userId])
|
||
@@index([iamUserId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model TravelerProfile {
|
||
id String @id @default(uuid())
|
||
passengerId String
|
||
fullName String
|
||
gender String?
|
||
relationship String
|
||
dateOfBirth DateTime?
|
||
nationalId String?
|
||
notes String?
|
||
createdAt DateTime @default(now())
|
||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Station {
|
||
id String @id @default(uuid())
|
||
code String @unique
|
||
name String
|
||
city String
|
||
countryCode String?
|
||
sequence Int @default(0)
|
||
isOperational Boolean @default(true)
|
||
lat Decimal? @db.Decimal(9, 6)
|
||
lng Decimal? @db.Decimal(9, 6)
|
||
originSchedules TrainSchedule[] @relation("OriginTrips")
|
||
destinationSchedules TrainSchedule[] @relation("DestinationTrips")
|
||
stopTimes TripStopTime[]
|
||
crowdSignals StationCrowdSignal[]
|
||
bookingDepartures Booking[] @relation("BookingPackageDepartureStation")
|
||
packageBookingDepartures PackageBooking[] @relation("PackageBookingDepartureStation")
|
||
@@index([city, countryCode])
|
||
@@index([sequence])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Train {
|
||
id String @id @default(uuid())
|
||
number String @unique
|
||
name String
|
||
operatorId String @default("op_edr")
|
||
operatorName String?
|
||
description String?
|
||
isActive Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
schedules TrainSchedule[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model TrainSchedule {
|
||
id String @id @default(uuid())
|
||
trainId String
|
||
routeId String?
|
||
originStationId String
|
||
destinationStationId String
|
||
departureAt DateTime
|
||
arrivalAt DateTime
|
||
durationMinutes Int
|
||
status TripStatus @default(SCHEDULED)
|
||
stopsCount Int @default(0)
|
||
reservedCount Int @default(0)
|
||
onTimePercent Int @default(100)
|
||
carbonRating String @default("A")
|
||
notes String?
|
||
isPackageOnly Boolean @default(false)
|
||
train Train @relation(fields: [trainId], references: [id])
|
||
route Route? @relation(fields: [routeId], references: [id])
|
||
originStation Station @relation("OriginTrips", fields: [originStationId], references: [id])
|
||
destinationStation Station @relation("DestinationTrips", fields: [destinationStationId], references: [id])
|
||
coachAssignments CoachAssignment[]
|
||
bookings Booking[] @relation("OutboundSchedule")
|
||
returnBookings Booking[] @relation("ReturnSchedule")
|
||
stopTimes TripStopTime[]
|
||
liveStatus TripLiveStatus?
|
||
menuItems MenuItem[]
|
||
journeySegments JourneySegment[]
|
||
outboundPackages TravelPackage[] @relation("PackageOutbound")
|
||
returnPackages TravelPackage[] @relation("PackageReturn")
|
||
|
||
@@index([departureAt, originStationId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model TripStopTime {
|
||
id String @id @default(uuid())
|
||
scheduleId String
|
||
stationId String
|
||
sequence Int
|
||
plannedArrivalAt DateTime?
|
||
plannedDepartureAt DateTime?
|
||
actualArrivalAt DateTime?
|
||
status StopStatus @default(OPEN)
|
||
schedule TrainSchedule @relation(fields: [scheduleId], references: [id])
|
||
station Station @relation(fields: [stationId], references: [id])
|
||
|
||
@@unique([scheduleId, sequence])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model TripLiveStatus {
|
||
id String @id @default(uuid())
|
||
scheduleId String @unique
|
||
state String
|
||
currentLocationLabel String?
|
||
progressPercent Int @default(0)
|
||
delayMinutes Int @default(0)
|
||
currentSpeedKph Int?
|
||
platformLabel String?
|
||
updatedAt DateTime @updatedAt
|
||
schedule TrainSchedule @relation(fields: [scheduleId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Coach {
|
||
id String @id @default(uuid())
|
||
coachTypeId String
|
||
number String @unique
|
||
arrangement String @default("2+2") // e.g., '2+2', '3+2', '2+2+2'
|
||
capacity Int @default(0) // Total seats/beds
|
||
sequence Int @default(0)
|
||
status String @default("ACTIVE") // 'ACTIVE', 'MAINTENANCE', 'INACTIVE'
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
coachType CoachType @relation(fields: [coachTypeId], references: [id])
|
||
seats Seat[]
|
||
assignments CoachAssignment[]
|
||
routeTemplates RouteCoachTemplate[]
|
||
@@index([coachTypeId])
|
||
@@index([sequence])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model CoachAssignment {
|
||
id String @id @default(uuid())
|
||
scheduleId String
|
||
coachId String
|
||
positionNumber Int
|
||
isOperational Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
schedule TrainSchedule @relation(fields: [scheduleId], references: [id])
|
||
coach Coach @relation(fields: [coachId], references: [id])
|
||
|
||
@@unique([scheduleId, positionNumber])
|
||
@@index([scheduleId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Seat {
|
||
id String @id @default(uuid())
|
||
coachId String
|
||
seatNumber String // Auto-generated: e.g., '1', '2', '3' (unique per coach)
|
||
row Int
|
||
col String
|
||
kind SeatKind @default(STANDARD)
|
||
status SeatStatus @default(AVAILABLE)
|
||
heldUntil DateTime?
|
||
isWindow Boolean @default(false)
|
||
isAisle Boolean @default(false)
|
||
bedPosition String? // 'lower', 'middle', 'upper'
|
||
premiumFeeMinor Int @default(0)
|
||
coach Coach @relation(fields: [coachId], references: [id])
|
||
bookingSeats BookingSeat[]
|
||
blocks SeatBlock[]
|
||
tickets Ticket[]
|
||
|
||
@@unique([coachId, seatNumber])
|
||
@@unique([coachId, row, col])
|
||
@@index([coachId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SeatHold {
|
||
id String @id @default(uuid())
|
||
scheduleId String
|
||
seatIds String[]
|
||
fareQuoteId String?
|
||
passengerId String
|
||
createdBy String?
|
||
expiresAt DateTime
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([expiresAt])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FareRule {
|
||
id String @id @default(uuid())
|
||
tripId String?
|
||
route String?
|
||
nationality String? // Ethiopian, Djiboutian, Other
|
||
seatClassId String
|
||
baseFareMinor Int
|
||
seatClass SeatClass @relation(fields: [seatClassId], references: [id])
|
||
currency String @default("ETB")
|
||
refundable Boolean @default(true)
|
||
validFrom DateTime
|
||
validUntil DateTime?
|
||
createdAt DateTime @default(now())
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Booking {
|
||
id String @id @default(uuid())
|
||
bookingRef String @unique
|
||
passengerId String
|
||
scheduleId String
|
||
packageId String?
|
||
priceTierId String?
|
||
bookingType String @default("ONE_WAY")
|
||
status BookingStatus @default(DRAFT)
|
||
currency String @default("ETB")
|
||
totalMinor Int
|
||
adultCount Int @default(1)
|
||
childCount Int @default(0)
|
||
displayCurrency Currency?
|
||
displayTotalMinor Int?
|
||
returnScheduleId String?
|
||
returnOriginStationId String?
|
||
returnDestinationStationId String?
|
||
returnHoldId String?
|
||
returnSeatClassId String?
|
||
returnLegStatus ReturnLegStatus @default(NOT_APPLICABLE)
|
||
// Transit leg-2 fields (single-booking transit)
|
||
leg2ScheduleId String?
|
||
leg2OriginStationId String?
|
||
leg2DestinationStationId String?
|
||
leg2SeatClassId String?
|
||
// Round-trip transit: return journey transit fields
|
||
returnLeg2ScheduleId String?
|
||
returnLeg2OriginStationId String?
|
||
returnLeg2DestStationId String?
|
||
returnLeg2SeatClassId String?
|
||
originStationId String?
|
||
destinationStationId String?
|
||
outboundBoardedAt DateTime?
|
||
returnBoardedAt DateTime?
|
||
contactEmail String?
|
||
contactPhone String?
|
||
userAgent String?
|
||
source String @default("WEB")
|
||
promoCode String?
|
||
paidAt DateTime?
|
||
paymentReminderSentAt DateTime?
|
||
packageDepartureStationId String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||
schedule TrainSchedule @relation("OutboundSchedule", fields: [scheduleId], references: [id])
|
||
returnSchedule TrainSchedule? @relation("ReturnSchedule", fields: [returnScheduleId], references: [id])
|
||
package TravelPackage? @relation(fields: [packageId], references: [id])
|
||
priceTier PackagePriceTier? @relation(fields: [priceTierId], references: [id])
|
||
departureStation Station? @relation("BookingPackageDepartureStation", fields: [packageDepartureStationId], references: [id])
|
||
seats BookingSeat[]
|
||
paymentIntent PaymentIntent?
|
||
tickets Ticket[]
|
||
foodOrders FoodOrder[]
|
||
agentBooking AgentBooking?
|
||
modifications BookingModification[]
|
||
cancellation BookingCancellation?
|
||
baggage BaggageBooking[]
|
||
excessBaggageCharges ExcessBaggageCharge[]
|
||
supplementaryCharges SupplementaryCharge[]
|
||
journey Journey?
|
||
|
||
@@index([passengerId, status])
|
||
@@index([bookingType])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model BookingSeat {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
seatId String
|
||
leg Int @default(1) // 1=outbound/leg-1, 2=return/leg-2
|
||
scheduleId String // which schedule this seat belongs to
|
||
passengerName String
|
||
dateOfBirth DateTime?
|
||
passengerCategory PassengerCategory @default(ADULT)
|
||
idDocumentType IdDocumentType?
|
||
idDocumentNumber String?
|
||
passportNumber String?
|
||
passportCountry String?
|
||
verifaydaVerified Boolean @default(false)
|
||
verifaydaData Json?
|
||
faydaVerifiedAt DateTime?
|
||
faydaSub String?
|
||
faydaVerifiedName String?
|
||
seatLabelSnapshot String?
|
||
fareMinor Int?
|
||
displayCurrency Currency?
|
||
displayFareMinor Int?
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
seat Seat @relation(fields: [seatId], references: [id])
|
||
@@unique([scheduleId, seatId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PaymentMethod {
|
||
id String @id @default(uuid())
|
||
type PaymentMethodType @unique
|
||
displayName String
|
||
region PaymentRegion @default(GLOBAL)
|
||
currency String @default("ETB")
|
||
providerId String?
|
||
isDefault Boolean @default(false)
|
||
enabled Boolean @default(true)
|
||
sortOrder Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PaymentIntent {
|
||
id String @id @default(uuid())
|
||
bookingId String @unique
|
||
amountMinor Int
|
||
currency String @default("ETB")
|
||
method PaymentMethodType
|
||
provider String?
|
||
status PaymentIntentStatus @default(REQUIRES_ACTION)
|
||
providerRef String?
|
||
clientAction Json?
|
||
merchantOrderId String? @unique
|
||
providerOrderId String?
|
||
providerTxnId String?
|
||
rawInitiation Json?
|
||
paidAt DateTime?
|
||
refundedAt DateTime?
|
||
captureMethod String?
|
||
failureCode String?
|
||
failureMessage String?
|
||
expiresAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
refunds PaymentRefund[]
|
||
|
||
@@index([providerOrderId])
|
||
@@index([providerTxnId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PaymentWebhookEvent {
|
||
id String @id @default(uuid())
|
||
provider PaymentMethodType
|
||
externalEventId String
|
||
merchantOrderId String?
|
||
providerTxnId String?
|
||
signatureValid Boolean
|
||
status String
|
||
payload Json
|
||
receivedAt DateTime @default(now())
|
||
processedAt DateTime?
|
||
processingError String?
|
||
|
||
@@unique([provider, externalEventId])
|
||
@@index([merchantOrderId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PaymentRefund {
|
||
id String @id @default(uuid())
|
||
paymentIntentId String
|
||
amountMinor Int
|
||
reason String?
|
||
providerRefundId String?
|
||
status String
|
||
createdAt DateTime @default(now())
|
||
paymentIntent PaymentIntent @relation(fields: [paymentIntentId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Ticket {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
bookingRef String
|
||
passengerName String
|
||
seatId String
|
||
leg Int @default(1)
|
||
scheduleId String?
|
||
status String @default("ACTIVE")
|
||
qrPayload String
|
||
barcodePayload String?
|
||
pdfUrl String?
|
||
deliveryChannel String @default("EMAIL")
|
||
issuedAt DateTime @default(now())
|
||
validatedAt DateTime?
|
||
validatorId String?
|
||
boardedAt DateTime?
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
seat Seat @relation(fields: [seatId], references: [id])
|
||
validationLogs GateValidationLog[]
|
||
@@index([bookingId])
|
||
@@index([seatId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model LoyaltyAccount {
|
||
id String @id @default(uuid())
|
||
passengerId String @unique
|
||
pointsBalance Int @default(0)
|
||
lifetimePoints Int @default(0)
|
||
tier LoyaltyTier @default(BRONZE)
|
||
tierUpdatedAt DateTime?
|
||
updatedAt DateTime @updatedAt
|
||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||
ledger LoyaltyLedgerEntry[]
|
||
rewards LoyaltyReward[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model LoyaltyLedgerEntry {
|
||
id String @id @default(uuid())
|
||
accountId String
|
||
delta Int
|
||
reason LoyaltyLedgerReason
|
||
bookingId String?
|
||
balanceAfter Int
|
||
createdAt DateTime @default(now())
|
||
account LoyaltyAccount @relation(fields: [accountId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model LoyaltyReward {
|
||
id String @id @default(uuid())
|
||
accountId String
|
||
title String
|
||
costPoints Int
|
||
available Boolean @default(true)
|
||
description String?
|
||
account LoyaltyAccount @relation(fields: [accountId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model WalletAccount {
|
||
id String @id @default(uuid())
|
||
passengerId String @unique
|
||
balanceMinor Int @default(0)
|
||
status String @default("ACTIVE")
|
||
holdMinor Int @default(0)
|
||
currency String @default("ETB")
|
||
updatedAt DateTime @updatedAt
|
||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||
ledger WalletLedgerEntry[]
|
||
|
||
@@index([passengerId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model WalletLedgerEntry {
|
||
id String @id @default(uuid())
|
||
walletId String
|
||
type WalletLedgerType
|
||
amountMinor Int
|
||
balanceAfterMinor Int
|
||
description String
|
||
relatedBookingId String?
|
||
createdAt DateTime @default(now())
|
||
wallet WalletAccount @relation(fields: [walletId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Notification {
|
||
id String @id @default(uuid())
|
||
passengerId String
|
||
title String
|
||
body String
|
||
category NotificationCategory
|
||
read Boolean @default(false)
|
||
deepLink String?
|
||
metadata Json?
|
||
createdAt DateTime @default(now())
|
||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Promotion {
|
||
id String @id @default(uuid())
|
||
title String
|
||
subtitle String?
|
||
code String @unique
|
||
percentOff Int?
|
||
amountOffMinor Int?
|
||
validUntil DateTime
|
||
ctaLabel String?
|
||
deepLink String?
|
||
active Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model StationCrowdSignal {
|
||
id String @id @default(uuid())
|
||
stationId String
|
||
level String
|
||
label String
|
||
statusLabel String
|
||
confidence Int?
|
||
observedAt DateTime?
|
||
updatedAt DateTime @updatedAt
|
||
station Station @relation(fields: [stationId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model WeatherAlert {
|
||
id String @id @default(uuid())
|
||
region String
|
||
severity String
|
||
title String
|
||
message String
|
||
validUntil DateTime
|
||
createdAt DateTime @default(now())
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model MenuCategory {
|
||
id String @id @default(uuid())
|
||
name String
|
||
items MenuItem[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model MenuItem {
|
||
id String @id @default(uuid())
|
||
scheduleId String
|
||
categoryId String
|
||
name String
|
||
priceMinor Int
|
||
currency String @default("ETB")
|
||
available Boolean @default(true)
|
||
availableUntil DateTime?
|
||
schedule TrainSchedule @relation(fields: [scheduleId], references: [id])
|
||
category MenuCategory @relation(fields: [categoryId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FoodOrder {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
status FoodOrderStatus @default(PENDING)
|
||
totalMinor Int
|
||
currency String @default("ETB")
|
||
specialInstructions String?
|
||
estimatedReadyAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
items FoodOrderItem[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FoodOrderItem {
|
||
id String @id @default(uuid())
|
||
orderId String
|
||
menuItemId String
|
||
name String
|
||
quantity Int
|
||
unitPriceMinor Int?
|
||
lineTotalMinor Int
|
||
order FoodOrder @relation(fields: [orderId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FaqCategory {
|
||
id String @id @default(uuid())
|
||
title String
|
||
iconKey String?
|
||
articles FaqArticle[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FaqArticle {
|
||
id String @id @default(uuid())
|
||
categoryId String
|
||
question String
|
||
answerMarkdown String
|
||
rank Int @default(0)
|
||
category FaqCategory @relation(fields: [categoryId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SupportConversation {
|
||
id String @id @default(uuid())
|
||
userId String?
|
||
guestId String?
|
||
guestName String?
|
||
guestEmail String?
|
||
guestPhone String?
|
||
passengerId String?
|
||
passengerName String?
|
||
subject String?
|
||
assignedAgentId String?
|
||
status SupportConversationStatus @default(OPEN)
|
||
lastMessageAt DateTime?
|
||
lastMessagePreview String?
|
||
lastMessageSender SupportSender?
|
||
userLastReadAt DateTime?
|
||
agentLastReadAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt @default(now())
|
||
messages SupportMessage[]
|
||
passenger Passenger? @relation(fields: [passengerId], references: [id])
|
||
@@index([userId])
|
||
@@index([guestId])
|
||
@@index([status, lastMessageAt])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SupportMessage {
|
||
id String @id @default(uuid())
|
||
conversationId String
|
||
sender SupportSender
|
||
/// NULL for an attachment-only message — absence of text is representable
|
||
/// rather than smuggled through "". The DTO maps NULL -> "".
|
||
text String?
|
||
createdAt DateTime @default(now())
|
||
conversation SupportConversation @relation(fields: [conversationId], references: [id])
|
||
attachments SupportAttachment[]
|
||
/// Backs keyset pagination of a thread (newest-first over (createdAt, id)).
|
||
/// Without it, paging a long thread degrades to a scan per page.
|
||
@@index([conversationId, createdAt])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
/// A file posted on a support message.
|
||
///
|
||
/// The freight side stores the equivalent in its polymorphic `freight.files`
|
||
/// table; this app has no such table (and no TypeORM), so chat attachments get a
|
||
/// purpose-built model rather than a shared one. Bytes live in MinIO — `url` is
|
||
/// the unsigned object path, signed on read for preview.
|
||
model SupportAttachment {
|
||
id String @id @default(uuid())
|
||
messageId String
|
||
name String
|
||
mimeType String
|
||
/// Bytes.
|
||
size Int
|
||
/// Unsigned MinIO object URL. Not directly fetchable by a browser — the API
|
||
/// mints a short-lived signed URL per response.
|
||
url String
|
||
createdAt DateTime @default(now())
|
||
message SupportMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
||
@@index([messageId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model UserPreferences {
|
||
id String @id @default(uuid())
|
||
iamUserId String @unique
|
||
pushEnabled Boolean @default(true)
|
||
emailEnabled Boolean @default(true)
|
||
smsEnabled Boolean @default(false)
|
||
promosEnabled Boolean @default(true)
|
||
biometricEnabled Boolean @default(false)
|
||
twoFactorEnabled Boolean @default(false)
|
||
defaultPaymentMethodId String?
|
||
autoDownloadTickets Boolean @default(true)
|
||
dataSharing Boolean @default(false)
|
||
locale String @default("en")
|
||
darkMode Boolean @default(false)
|
||
language String @default("en")
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Device {
|
||
id String @id @default(uuid())
|
||
iamUserId String
|
||
platform DevicePlatform
|
||
name String
|
||
pushToken String?
|
||
trusted Boolean @default(false)
|
||
lastSeenAt DateTime @default(now())
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SavedRoute {
|
||
id String @id @default(uuid())
|
||
passengerId String
|
||
fromStationId String
|
||
toStationId String
|
||
fromName String
|
||
toName String
|
||
tripCount Int @default(0)
|
||
createdAt DateTime @default(now())
|
||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Journey {
|
||
id String @id @default(uuid())
|
||
passengerId String
|
||
bookingId String? @unique
|
||
status String
|
||
totalMinor Int
|
||
currency String @default("ETB")
|
||
createdAt DateTime @default(now())
|
||
booking Booking? @relation(fields: [bookingId], references: [id])
|
||
journeySegments JourneySegment[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model JourneySegment {
|
||
id String @id @default(uuid())
|
||
journeyId String
|
||
scheduleId String
|
||
segmentOrder Int
|
||
seatId String?
|
||
coachId String?
|
||
departureStationId String
|
||
arrivalStationId String
|
||
journey Journey @relation(fields: [journeyId], references: [id])
|
||
schedule TrainSchedule @relation(fields: [scheduleId], references: [id])
|
||
|
||
// Prevents two confirmed bookings from occupying the same seat on the same
|
||
// schedule hop — the hard DB backstop against application-level race conditions.
|
||
@@unique([scheduleId, seatId, departureStationId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model OtpCode {
|
||
id String @id @default(uuid())
|
||
userId String?
|
||
email String?
|
||
phone String?
|
||
code String
|
||
purpose String
|
||
expiresAt DateTime
|
||
verified Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([email, phone])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PasswordResetToken {
|
||
id String @id @default(uuid())
|
||
userId String
|
||
token String @unique
|
||
expiresAt DateTime
|
||
used Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([userId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Route {
|
||
id String @id @default(uuid())
|
||
code String @unique
|
||
name String
|
||
description String?
|
||
effectiveFrom DateTime
|
||
effectiveUntil DateTime?
|
||
active Boolean @default(true)
|
||
checkinMinutesBefore Int @default(30)
|
||
createdAt DateTime @default(now())
|
||
stops RouteStop[]
|
||
fareRules RouteFareRule[]
|
||
segmentFares SegmentFareRule[]
|
||
schedules TrainSchedule[]
|
||
coachTemplates RouteCoachTemplate[]
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model RouteStop {
|
||
id String @id @default(uuid())
|
||
routeId String
|
||
stationId String
|
||
sequence Int
|
||
distanceKm Float?
|
||
checkinMinutesBefore Int?
|
||
plannedArrivalTime DateTime?
|
||
plannedDepartureTime DateTime?
|
||
createdAt DateTime @default(now())
|
||
route Route @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([routeId, sequence])
|
||
@@index([routeId, stationId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model RouteCoachTemplate {
|
||
id String @id @default(uuid())
|
||
routeId String
|
||
coachId String
|
||
positionNumber Int
|
||
createdAt DateTime @default(now())
|
||
route Route @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
||
coach Coach @relation(fields: [coachId], references: [id])
|
||
|
||
@@unique([routeId, positionNumber])
|
||
@@index([routeId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model RouteFareRule {
|
||
id String @id @default(uuid())
|
||
routeId String
|
||
seatClassId String
|
||
passengerCategory PassengerCategory @default(ADULT)
|
||
baseFareMinor Int
|
||
discountPercent Int?
|
||
taxPercent Int?
|
||
surchargeMinor Int?
|
||
currency String @default("ETB")
|
||
validFrom DateTime
|
||
validUntil DateTime?
|
||
createdAt DateTime @default(now())
|
||
route Route @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
||
seatClass SeatClass @relation(fields: [seatClassId], references: [id])
|
||
|
||
@@index([routeId, seatClassId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SegmentFareRule {
|
||
id String @id @default(uuid())
|
||
routeId String
|
||
originStopSequence Int
|
||
destinationStopSequence Int
|
||
seatClassId String
|
||
baseFareMinor Int
|
||
nationality String? // Optional: Ethiopian, Djiboutian, Other
|
||
currency String @default("ETB")
|
||
validFrom DateTime
|
||
validUntil DateTime?
|
||
createdAt DateTime @default(now())
|
||
route Route @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
||
seatClass SeatClass @relation(fields: [seatClassId], references: [id])
|
||
|
||
@@unique([routeId, originStopSequence, destinationStopSequence, seatClassId, nationality])
|
||
@@index([routeId, seatClassId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model Agent {
|
||
id String @id @default(uuid())
|
||
iamUserId String? @unique
|
||
agentCode String @unique
|
||
stationId String?
|
||
commissionRate Int @default(5)
|
||
active Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
bookings AgentBooking[]
|
||
shifts AgentShift[]
|
||
commissions AgentCommission[]
|
||
@@index([iamUserId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model AgentBooking {
|
||
id String @id @default(uuid())
|
||
agentId String
|
||
bookingId String @unique
|
||
paymentMethod String
|
||
cashReceived Int?
|
||
changeGiven Int?
|
||
paperTicket Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
agent Agent @relation(fields: [agentId], references: [id])
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model AgentShift {
|
||
id String @id @default(uuid())
|
||
agentId String
|
||
openedAt DateTime @default(now())
|
||
closedAt DateTime?
|
||
openingBalance Int @default(0)
|
||
closingBalance Int?
|
||
reconciled Boolean @default(false)
|
||
notes String?
|
||
agent Agent @relation(fields: [agentId], references: [id])
|
||
|
||
@@index([agentId, openedAt])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model AgentCommission {
|
||
id String @id @default(uuid())
|
||
agentId String
|
||
bookingId String
|
||
amountMinor Int
|
||
rate Int
|
||
paidAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
agent Agent @relation(fields: [agentId], references: [id])
|
||
|
||
@@index([agentId, paidAt])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model BookingModification {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
modifiedBy String
|
||
modificationType String
|
||
oldData Json
|
||
newData Json
|
||
fareAdjustment Int @default(0)
|
||
reason String?
|
||
createdAt DateTime @default(now())
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
|
||
@@index([bookingId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model BookingCancellation {
|
||
id String @id @default(uuid())
|
||
bookingId String @unique
|
||
cancelledBy String
|
||
reason String?
|
||
refundAmount Int
|
||
refundMethod String
|
||
refundStatus String
|
||
processedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model GateValidationLog {
|
||
id String @id @default(uuid())
|
||
ticketId String
|
||
validatorId String
|
||
gateId String?
|
||
leg String? // 'OUTBOUND' | 'RETURN' — for round-trip tickets
|
||
status String
|
||
reason String?
|
||
validatedAt DateTime @default(now())
|
||
ticket Ticket @relation(fields: [ticketId], references: [id])
|
||
|
||
@@index([ticketId])
|
||
@@index([validatorId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model BaggageAllowance {
|
||
id String @id @default(uuid())
|
||
seatClassId String
|
||
maxWeightKg Int
|
||
maxPiecesCount Int
|
||
excessFeePerKg Int
|
||
currency String @default("ETB")
|
||
createdAt DateTime @default(now())
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model BaggageBooking {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
weightKg Int
|
||
piecesCount Int
|
||
excessFeeMinor Int @default(0)
|
||
paid Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
|
||
@@index([bookingId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SupplementaryCharge {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
reason String // e.g. "UNDERPAYMENT", "FARE_CORRECTION"
|
||
amountMinor Int
|
||
currency String @default("ETB")
|
||
status String @default("PENDING") // PENDING | PAID | WAIVED | EXPIRED
|
||
paymentToken String @unique @default(uuid())
|
||
providerTxnId String?
|
||
notes String?
|
||
createdBy String
|
||
paidAt DateTime?
|
||
expiresAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
|
||
@@index([bookingId])
|
||
@@index([paymentToken])
|
||
@@index([status])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model ExcessBaggageCharge {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
agentId String
|
||
excessWeightKg Int
|
||
feePerKgMinor Int
|
||
totalMinor Int
|
||
currency String @default("ETB")
|
||
status String @default("PENDING") // PENDING | PAID | EXPIRED | WAIVED | CASH_COLLECTED
|
||
paymentToken String @unique @default(uuid())
|
||
expiresAt DateTime
|
||
paidAt DateTime?
|
||
waivedBy String?
|
||
waivedReason String?
|
||
contactPhone String?
|
||
contactEmail String?
|
||
createdAt DateTime @default(now())
|
||
booking Booking @relation(fields: [bookingId], references: [id])
|
||
|
||
@@index([bookingId])
|
||
@@index([paymentToken])
|
||
@@index([status])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model AuditLog {
|
||
id String @id @default(uuid())
|
||
iamUserId String?
|
||
action String
|
||
entityType String
|
||
entityId String?
|
||
oldData Json?
|
||
newData Json?
|
||
ipAddress String?
|
||
userAgent String?
|
||
createdAt DateTime @default(now())
|
||
@@index([iamUserId, createdAt])
|
||
@@index([entityType, entityId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model NotificationTemplate {
|
||
id String @id @default(uuid())
|
||
code String @unique
|
||
channel String
|
||
subject String?
|
||
bodyTemplate String
|
||
active Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SeatBlock {
|
||
id String @id @default(uuid())
|
||
seatId String
|
||
scheduleId String?
|
||
reason String
|
||
blockedBy String
|
||
approvedBy String?
|
||
blockedAt DateTime @default(now())
|
||
unblockAt DateTime?
|
||
seat Seat @relation(fields: [seatId], references: [id])
|
||
|
||
@@index([seatId])
|
||
@@index([scheduleId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model OperationalReport {
|
||
id String @id @default(uuid())
|
||
reportType String
|
||
dateFrom DateTime
|
||
dateTo DateTime
|
||
data Json
|
||
generatedBy String?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([reportType, dateFrom])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FraudRule {
|
||
id String @id @default(uuid())
|
||
type String @unique
|
||
enabled Boolean @default(true)
|
||
threshold Float
|
||
config Json?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FraudAlert {
|
||
id String @id @default(uuid())
|
||
iamUserId String
|
||
eventType String
|
||
triggeredRules String[]
|
||
context Json
|
||
severity String @default("MEDIUM")
|
||
acknowledged Boolean @default(false)
|
||
acknowledgedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
@@index([iamUserId, createdAt])
|
||
@@index([acknowledged])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model CurrencyExchangeRate {
|
||
id String @id @default(uuid())
|
||
fromCurrency Currency
|
||
toCurrency Currency
|
||
rate Decimal @db.Decimal(18, 6)
|
||
effectiveDate DateTime @default(now())
|
||
source String @default("MANUAL")
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([fromCurrency, toCurrency, effectiveDate])
|
||
@@index([fromCurrency, toCurrency])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model VerifaydaVerification {
|
||
id String @id @default(uuid())
|
||
bookingId String?
|
||
nationalId String
|
||
requestPayload Json
|
||
responsePayload Json?
|
||
verified Boolean @default(false)
|
||
failureReason String?
|
||
verifiedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@index([nationalId])
|
||
@@index([bookingId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SavedPassengerProfile {
|
||
id String @id @default(uuid())
|
||
userId String?
|
||
deviceId String?
|
||
passengerName String
|
||
dateOfBirth DateTime
|
||
idDocumentType IdDocumentType
|
||
passportNumber String?
|
||
passportCountry String?
|
||
nationality String?
|
||
phone String?
|
||
email String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([userId])
|
||
@@index([deviceId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model FaydaVerificationSession {
|
||
id String @id @default(uuid())
|
||
state String @unique
|
||
codeVerifier String
|
||
purpose String @default("VERIFY") // VERIFY | LOGIN
|
||
platform String @default("WEB") // WEB | MOBILE — recorded for audit
|
||
redirectUri String? // exact OAuth redirect_uri sent to eSignet at start; reused verbatim at token exchange (domain-aware for WEB)
|
||
saveToAccount Boolean @default(false)
|
||
status String @default("PENDING")
|
||
errorCode String?
|
||
errorDescription String?
|
||
authCode String?
|
||
createdAt DateTime @default(now())
|
||
expiresAt DateTime
|
||
completedAt DateTime?
|
||
|
||
iamUserId String?
|
||
bookingId String?
|
||
|
||
@@index([iamUserId])
|
||
@@index([bookingId])
|
||
@@index([state])
|
||
@@index([expiresAt])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model SystemConfig {
|
||
id String @id @default(uuid())
|
||
key String @unique
|
||
value String
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
enum PackageStatus {
|
||
DRAFT
|
||
ACTIVE
|
||
SOLD_OUT
|
||
EXPIRED
|
||
CANCELLED
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model TravelPackage {
|
||
id String @id @default(uuid())
|
||
code String @unique
|
||
name String
|
||
description String?
|
||
status PackageStatus @default(DRAFT)
|
||
outboundScheduleId String
|
||
returnScheduleId String
|
||
originStationId String
|
||
destinationStationId String
|
||
boardingTime DateTime
|
||
departureTime DateTime
|
||
arrivalTime DateTime
|
||
totalCapacity Int
|
||
bookedCount Int @default(0)
|
||
includedServices Json
|
||
coachConfiguration String?
|
||
busTransferIncluded Boolean @default(false)
|
||
busTransferRoute String?
|
||
validFrom DateTime
|
||
validUntil DateTime
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
outboundSchedule TrainSchedule @relation("PackageOutbound", fields: [outboundScheduleId], references: [id])
|
||
returnSchedule TrainSchedule @relation("PackageReturn", fields: [returnScheduleId], references: [id])
|
||
priceTiers PackagePriceTier[]
|
||
bookings Booking[]
|
||
packageBookings PackageBooking[]
|
||
inquiries PackageInquiry[]
|
||
|
||
@@index([status, validFrom])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PackagePriceTier {
|
||
id String @id @default(uuid())
|
||
packageId String
|
||
seatClassId String?
|
||
seatType String
|
||
label String
|
||
priceMinor Int
|
||
currency String @default("ETB")
|
||
availableSeats Int @default(0)
|
||
bookedSeats Int @default(0)
|
||
|
||
package TravelPackage @relation(fields: [packageId], references: [id])
|
||
seatClass SeatClass? @relation(fields: [seatClassId], references: [id])
|
||
bookings Booking[]
|
||
packageBookings PackageBooking[]
|
||
inquiries PackageInquiry[]
|
||
|
||
@@unique([packageId, seatType])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PackageBooking {
|
||
id String @id @default(uuid())
|
||
bookingRef String @unique
|
||
packageId String
|
||
priceTierId String
|
||
passengerId String?
|
||
contactEmail String?
|
||
contactPhone String?
|
||
status BookingStatus @default(PENDING_PAYMENT)
|
||
passengerCount Int @default(1)
|
||
adultCount Int @default(1)
|
||
childCount Int @default(0)
|
||
totalMinor Int
|
||
currency String @default("ETB")
|
||
displayCurrency Currency?
|
||
displayTotalMinor Int?
|
||
promoCode String?
|
||
source String @default("WEB")
|
||
paidAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
packageDepartureStationId String?
|
||
|
||
package TravelPackage @relation(fields: [packageId], references: [id])
|
||
priceTier PackagePriceTier @relation(fields: [priceTierId], references: [id])
|
||
passenger Passenger? @relation(fields: [passengerId], references: [id])
|
||
departureStation Station? @relation("PackageBookingDepartureStation", fields: [packageDepartureStationId], references: [id])
|
||
passengers PackageBookingPassenger[]
|
||
paymentIntent PackagePaymentIntent?
|
||
|
||
@@index([packageId, status])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PackageBookingPassenger {
|
||
id String @id @default(uuid())
|
||
bookingId String
|
||
passengerName String
|
||
dateOfBirth DateTime?
|
||
idDocumentType IdDocumentType?
|
||
idDocumentNumber String?
|
||
passportNumber String?
|
||
passportCountry String?
|
||
seatLabel String?
|
||
|
||
booking PackageBooking @relation(fields: [bookingId], references: [id])
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PackagePaymentIntent {
|
||
id String @id @default(uuid())
|
||
packageBookingId String @unique
|
||
amountMinor Int
|
||
currency String @default("ETB")
|
||
method PaymentMethodType
|
||
status PaymentIntentStatus @default(REQUIRES_ACTION)
|
||
providerRef String?
|
||
paidAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
packageBooking PackageBooking @relation(fields: [packageBookingId], references: [id])
|
||
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model PackageInquiry {
|
||
id String @id @default(uuid())
|
||
packageId String
|
||
priceTierId String?
|
||
travelerCount Int
|
||
contactName String
|
||
contactEmail String?
|
||
contactPhone String?
|
||
notes String?
|
||
status String @default("NEW")
|
||
enquiredAt DateTime @default(now())
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
package TravelPackage @relation(fields: [packageId], references: [id])
|
||
priceTier PackagePriceTier? @relation(fields: [priceTierId], references: [id])
|
||
|
||
@@index([packageId])
|
||
@@schema("passenger")
|
||
}
|
||
|
||
model AppRelease {
|
||
id String @id @default(uuid())
|
||
os String // "android" | "ios"
|
||
version String
|
||
forceUpdate Boolean @default(false)
|
||
storeLink String?
|
||
notes String?
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@unique([os, version])
|
||
@@schema("passenger")
|
||
}
|