Files
edr-platform/apps/edr-passenger-api/prisma/schema.prisma

1593 lines
45 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
COMPLETED
APPROACHING
CURRENT
UPCOMING
@@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(UPCOMING)
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[]
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])
@@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
text String
attachments Json?
createdAt DateTime @default(now())
conversation SupportConversation @relation(fields: [conversationId], references: [id])
@@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])
@@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)
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?
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 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
reason String
blockedBy String
approvedBy String?
blockedAt DateTime @default(now())
unblockAt DateTime?
seat Seat @relation(fields: [seatId], references: [id])
@@index([seatId])
@@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
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")
}