mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
Implemened verifayda and currency modules
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
-- Language field already exists in UserPreferences table
|
||||
-- No migration needed
|
||||
@@ -1,74 +0,0 @@
|
||||
-- Migration: Add Fraud Detection Tables and User.blockedUntil field
|
||||
-- Date: 2026-05-21
|
||||
-- Description: Adds FraudRule and FraudAlert tables, and blockedUntil field to User table
|
||||
|
||||
-- Add blockedUntil field to User table if not exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'User' AND column_name = 'blockedUntil'
|
||||
) THEN
|
||||
ALTER TABLE "User" ADD COLUMN "blockedUntil" TIMESTAMP(3);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- CreateTable FraudRule
|
||||
CREATE TABLE IF NOT EXISTS "FraudRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"threshold" DOUBLE PRECISION NOT NULL,
|
||||
"config" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FraudRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable FraudAlert
|
||||
CREATE TABLE IF NOT EXISTS "FraudAlert" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"triggeredRules" TEXT[],
|
||||
"context" JSONB NOT NULL,
|
||||
"severity" TEXT NOT NULL DEFAULT 'MEDIUM',
|
||||
"acknowledged" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FraudAlert_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "FraudRule_type_key" ON "FraudRule"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "FraudAlert_userId_createdAt_idx" ON "FraudAlert"("userId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "FraudAlert_acknowledged_idx" ON "FraudAlert"("acknowledged");
|
||||
|
||||
-- AddForeignKey
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'FraudAlert_userId_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "FraudAlert" ADD CONSTRAINT "FraudAlert_userId_fkey"
|
||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Insert default fraud rules
|
||||
INSERT INTO "FraudRule" ("id", "type", "enabled", "threshold", "config", "createdAt", "updatedAt")
|
||||
VALUES
|
||||
(gen_random_uuid(), 'VELOCITY', true, 5, '{"timeWindowMinutes": 30, "blockDurationMinutes": 30}'::jsonb, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(gen_random_uuid(), 'HIGH_VALUE', true, 10000, '{"blockDurationMinutes": 60}'::jsonb, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(gen_random_uuid(), 'FAILED_PAYMENTS', true, 3, '{"timeWindowMinutes": 60, "blockDurationMinutes": 30}'::jsonb, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (type) DO NOTHING;
|
||||
|
||||
-- Add comments
|
||||
COMMENT ON TABLE "FraudRule" IS 'Fraud detection rules for monitoring suspicious activities';
|
||||
COMMENT ON TABLE "FraudAlert" IS 'Fraud alerts triggered by rule violations';
|
||||
COMMENT ON COLUMN "User"."blockedUntil" IS 'Timestamp until which the user is blocked due to fraud or security reasons';
|
||||
@@ -13,6 +13,15 @@ CREATE TYPE "SeatStatus" AS ENUM ('AVAILABLE', 'HELD', 'BOOKED', 'BLOCKED');
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ServiceClass" AS ENUM ('ECONOMY_REGULAR', 'ECONOMY_BED_LOWER', 'ECONOMY_BED_MIDDLE', 'ECONOMY_BED_UPPER', 'VIP_BED_LOWER', 'VIP_BED_UPPER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PassengerCategory" AS ENUM ('ADULT', 'CHILD');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "IdDocumentType" AS ENUM ('NATIONAL_ID', 'PASSPORT', 'DRIVING_LICENSE', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Currency" AS ENUM ('ETB', 'DJF', 'USD');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BookingStatus" AS ENUM ('DRAFT', 'PENDING_PAYMENT', 'CONFIRMED', 'CANCELLED', 'COMPLETED', 'NO_SHOW', 'REFUNDED');
|
||||
|
||||
@@ -58,10 +67,13 @@ CREATE TABLE "User" (
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"role" "UserRole" NOT NULL DEFAULT 'PASSENGER',
|
||||
"nationality" TEXT,
|
||||
"nationalityCode" TEXT,
|
||||
"passportNumber" TEXT,
|
||||
"nationalId" TEXT,
|
||||
"failedLoginAttempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"lockedUntil" TIMESTAMP(3),
|
||||
"blockedUntil" TIMESTAMP(3),
|
||||
"lastLoginAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
@@ -86,6 +98,8 @@ CREATE TABLE "Session" (
|
||||
CREATE TABLE "Passenger" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"defaultTravelerProfileId" TEXT,
|
||||
"preferredLanguage" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Passenger_pkey" PRIMARY KEY ("id")
|
||||
@@ -111,6 +125,8 @@ CREATE TABLE "Station" (
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL,
|
||||
"countryCode" TEXT,
|
||||
"isOperational" BOOLEAN NOT NULL DEFAULT true,
|
||||
"timezone" TEXT NOT NULL DEFAULT 'Africa/Addis_Ababa',
|
||||
"lat" DECIMAL(9,6) NOT NULL,
|
||||
"lng" DECIMAL(9,6) NOT NULL,
|
||||
@@ -124,6 +140,7 @@ CREATE TABLE "TrainService" (
|
||||
"number" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"operatorId" TEXT NOT NULL DEFAULT 'op_edr',
|
||||
"operatorName" TEXT,
|
||||
|
||||
CONSTRAINT "TrainService_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -132,6 +149,7 @@ CREATE TABLE "TrainService" (
|
||||
CREATE TABLE "Trip" (
|
||||
"id" TEXT NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"routeId" TEXT,
|
||||
"originStationId" TEXT NOT NULL,
|
||||
"destinationStationId" TEXT NOT NULL,
|
||||
"departureAt" TIMESTAMP(3) NOT NULL,
|
||||
@@ -139,8 +157,10 @@ CREATE TABLE "Trip" (
|
||||
"durationMinutes" INTEGER NOT NULL,
|
||||
"status" "TripStatus" NOT NULL DEFAULT 'SCHEDULED',
|
||||
"stopsCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"reservedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"onTimePercent" INTEGER NOT NULL DEFAULT 100,
|
||||
"carbonRating" TEXT NOT NULL DEFAULT 'A',
|
||||
"notes" TEXT,
|
||||
|
||||
CONSTRAINT "Trip_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -180,6 +200,10 @@ CREATE TABLE "Coach" (
|
||||
"tripId" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"serviceClass" "ServiceClass" NOT NULL,
|
||||
"capacity" INTEGER,
|
||||
"sequence" INTEGER,
|
||||
"coachType" TEXT,
|
||||
"amenities" JSONB,
|
||||
|
||||
CONSTRAINT "Coach_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -191,9 +215,12 @@ CREATE TABLE "Seat" (
|
||||
"row" INTEGER NOT NULL,
|
||||
"col" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"seatNumber" TEXT,
|
||||
"kind" "SeatKind" NOT NULL DEFAULT 'STANDARD',
|
||||
"status" "SeatStatus" NOT NULL DEFAULT 'AVAILABLE',
|
||||
"heldUntil" TIMESTAMP(3),
|
||||
"isWindow" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAisle" BOOLEAN NOT NULL DEFAULT false,
|
||||
"premiumFeeMinor" INTEGER NOT NULL DEFAULT 0,
|
||||
"eligibility" TEXT,
|
||||
|
||||
@@ -207,6 +234,7 @@ CREATE TABLE "SeatHold" (
|
||||
"seatIds" TEXT[],
|
||||
"fareQuoteId" TEXT,
|
||||
"passengerId" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
@@ -238,7 +266,15 @@ CREATE TABLE "Booking" (
|
||||
"status" "BookingStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"currency" TEXT NOT NULL DEFAULT 'ETB',
|
||||
"totalMinor" INTEGER NOT NULL,
|
||||
"adultCount" INTEGER NOT NULL DEFAULT 1,
|
||||
"childCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"displayCurrency" "Currency",
|
||||
"displayTotalMinor" INTEGER,
|
||||
"bookingType" TEXT NOT NULL DEFAULT 'ONE_WAY',
|
||||
"userAgent" TEXT,
|
||||
"source" TEXT NOT NULL DEFAULT 'WEB',
|
||||
"promoCode" TEXT,
|
||||
"paidAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
@@ -251,8 +287,18 @@ CREATE TABLE "BookingSeat" (
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"seatId" TEXT NOT NULL,
|
||||
"passengerName" TEXT NOT NULL,
|
||||
"idDocumentType" TEXT,
|
||||
"dateOfBirth" TIMESTAMP(3),
|
||||
"passengerCategory" "PassengerCategory" NOT NULL DEFAULT 'ADULT',
|
||||
"idDocumentType" "IdDocumentType",
|
||||
"idDocumentNumber" TEXT,
|
||||
"passportNumber" TEXT,
|
||||
"passportCountry" TEXT,
|
||||
"verifaydaVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"verifaydaData" JSONB,
|
||||
"seatLabelSnapshot" TEXT,
|
||||
"fareMinor" INTEGER,
|
||||
"displayCurrency" "Currency",
|
||||
"displayFareMinor" INTEGER,
|
||||
|
||||
CONSTRAINT "BookingSeat_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -264,6 +310,7 @@ CREATE TABLE "PaymentMethod" (
|
||||
"type" "PaymentMethodType" NOT NULL,
|
||||
"displayName" TEXT NOT NULL,
|
||||
"maskedHint" TEXT,
|
||||
"providerId" TEXT,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
@@ -277,6 +324,7 @@ CREATE TABLE "PaymentIntent" (
|
||||
"amountMinor" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'ETB',
|
||||
"method" "PaymentMethodType" NOT NULL,
|
||||
"provider" TEXT,
|
||||
"status" "PaymentIntentStatus" NOT NULL DEFAULT 'REQUIRES_ACTION',
|
||||
"providerRef" TEXT,
|
||||
"clientAction" JSONB,
|
||||
@@ -285,6 +333,8 @@ CREATE TABLE "PaymentIntent" (
|
||||
"providerTxnId" TEXT,
|
||||
"rawInitiation" JSONB,
|
||||
"paidAt" TIMESTAMP(3),
|
||||
"refundedAt" TIMESTAMP(3),
|
||||
"captureMethod" TEXT,
|
||||
"failureCode" TEXT,
|
||||
"failureMessage" TEXT,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
@@ -346,7 +396,9 @@ CREATE TABLE "LoyaltyAccount" (
|
||||
"id" TEXT NOT NULL,
|
||||
"passengerId" TEXT NOT NULL,
|
||||
"pointsBalance" INTEGER NOT NULL DEFAULT 0,
|
||||
"lifetimePoints" INTEGER NOT NULL DEFAULT 0,
|
||||
"tier" "LoyaltyTier" NOT NULL DEFAULT 'BRONZE',
|
||||
"tierUpdatedAt" TIMESTAMP(3),
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "LoyaltyAccount_pkey" PRIMARY KEY ("id")
|
||||
@@ -382,6 +434,8 @@ CREATE TABLE "WalletAccount" (
|
||||
"id" TEXT NOT NULL,
|
||||
"passengerId" TEXT NOT NULL,
|
||||
"balanceMinor" INTEGER NOT NULL DEFAULT 0,
|
||||
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
"holdMinor" INTEGER NOT NULL DEFAULT 0,
|
||||
"currency" TEXT NOT NULL DEFAULT 'ETB',
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
@@ -441,6 +495,8 @@ CREATE TABLE "StationCrowdSignal" (
|
||||
"level" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"statusLabel" TEXT NOT NULL,
|
||||
"confidence" INTEGER,
|
||||
"observedAt" TIMESTAMP(3),
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "StationCrowdSignal_pkey" PRIMARY KEY ("id")
|
||||
@@ -476,6 +532,7 @@ CREATE TABLE "MenuItem" (
|
||||
"priceMinor" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'ETB',
|
||||
"available" BOOLEAN NOT NULL DEFAULT true,
|
||||
"availableUntil" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "MenuItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -487,6 +544,8 @@ CREATE TABLE "FoodOrder" (
|
||||
"status" "FoodOrderStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"totalMinor" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'ETB',
|
||||
"specialInstructions" TEXT,
|
||||
"estimatedReadyAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FoodOrder_pkey" PRIMARY KEY ("id")
|
||||
@@ -499,6 +558,7 @@ CREATE TABLE "FoodOrderItem" (
|
||||
"menuItemId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"quantity" INTEGER NOT NULL,
|
||||
"unitPriceMinor" INTEGER,
|
||||
"lineTotalMinor" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "FoodOrderItem_pkey" PRIMARY KEY ("id")
|
||||
@@ -528,6 +588,7 @@ CREATE TABLE "FaqArticle" (
|
||||
CREATE TABLE "SupportConversation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"assignedAgentId" TEXT,
|
||||
"status" "SupportConversationStatus" NOT NULL DEFAULT 'OPEN',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
@@ -540,6 +601,7 @@ CREATE TABLE "SupportMessage" (
|
||||
"conversationId" TEXT NOT NULL,
|
||||
"sender" "SupportSender" NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"attachments" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "SupportMessage_pkey" PRIMARY KEY ("id")
|
||||
@@ -676,9 +738,11 @@ CREATE TABLE "RouteFareRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"routeId" TEXT NOT NULL,
|
||||
"serviceClass" "ServiceClass" NOT NULL,
|
||||
"passengerCategory" TEXT NOT NULL DEFAULT 'ADULT',
|
||||
"passengerCategory" "PassengerCategory" NOT NULL DEFAULT 'ADULT',
|
||||
"baseFareMinor" INTEGER NOT NULL,
|
||||
"discountPercent" INTEGER,
|
||||
"taxPercent" INTEGER,
|
||||
"surchargeMinor" INTEGER,
|
||||
"currency" TEXT NOT NULL DEFAULT 'ETB',
|
||||
"validFrom" TIMESTAMP(3) NOT NULL,
|
||||
"validUntil" TIMESTAMP(3),
|
||||
@@ -865,6 +929,61 @@ CREATE TABLE "OperationalReport" (
|
||||
CONSTRAINT "OperationalReport_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FraudRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"threshold" DOUBLE PRECISION NOT NULL,
|
||||
"config" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FraudRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FraudAlert" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"triggeredRules" TEXT[],
|
||||
"context" JSONB NOT NULL,
|
||||
"severity" TEXT NOT NULL DEFAULT 'MEDIUM',
|
||||
"acknowledged" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FraudAlert_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CurrencyExchangeRate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fromCurrency" "Currency" NOT NULL,
|
||||
"toCurrency" "Currency" NOT NULL,
|
||||
"rate" DECIMAL(18,6) NOT NULL,
|
||||
"effectiveDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"source" TEXT NOT NULL DEFAULT 'MANUAL',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "CurrencyExchangeRate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VerifaydaVerification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT,
|
||||
"nationalId" TEXT NOT NULL,
|
||||
"requestPayload" JSONB NOT NULL,
|
||||
"responsePayload" JSONB,
|
||||
"verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"failureReason" TEXT,
|
||||
"verifiedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "VerifaydaVerification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
@@ -877,12 +996,21 @@ CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token");
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Passenger_userId_key" ON "Passenger"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Passenger_userId_idx" ON "Passenger"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Station_code_key" ON "Station"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Station_city_countryCode_idx" ON "Station"("city", "countryCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TrainService_number_key" ON "TrainService"("number");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Trip_departureAt_originStationId_idx" ON "Trip"("departureAt", "originStationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TripStopTime_tripId_sequence_key" ON "TripStopTime"("tripId", "sequence");
|
||||
|
||||
@@ -895,9 +1023,21 @@ CREATE UNIQUE INDEX "Coach_tripId_label_key" ON "Coach"("tripId", "label");
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Seat_coachId_row_col_key" ON "Seat"("coachId", "row", "col");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Seat_coachId_seatNumber_key" ON "Seat"("coachId", "seatNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SeatHold_expiresAt_idx" ON "SeatHold"("expiresAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Booking_bookingRef_key" ON "Booking"("bookingRef");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_passengerId_status_idx" ON "Booking"("passengerId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PaymentMethod_userId_isDefault_idx" ON "PaymentMethod"("userId", "isDefault");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PaymentIntent_bookingId_key" ON "PaymentIntent"("bookingId");
|
||||
|
||||
@@ -925,6 +1065,9 @@ CREATE UNIQUE INDEX "LoyaltyAccount_passengerId_key" ON "LoyaltyAccount"("passen
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "WalletAccount_passengerId_key" ON "WalletAccount"("passengerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WalletAccount_passengerId_idx" ON "WalletAccount"("passengerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Promotion_code_key" ON "Promotion"("code");
|
||||
|
||||
@@ -997,6 +1140,27 @@ CREATE INDEX "SeatBlock_seatId_idx" ON "SeatBlock"("seatId");
|
||||
-- CreateIndex
|
||||
CREATE INDEX "OperationalReport_reportType_dateFrom_idx" ON "OperationalReport"("reportType", "dateFrom");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FraudRule_type_key" ON "FraudRule"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FraudAlert_userId_createdAt_idx" ON "FraudAlert"("userId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FraudAlert_acknowledged_idx" ON "FraudAlert"("acknowledged");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CurrencyExchangeRate_fromCurrency_toCurrency_idx" ON "CurrencyExchangeRate"("fromCurrency", "toCurrency");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CurrencyExchangeRate_fromCurrency_toCurrency_effectiveDate_key" ON "CurrencyExchangeRate"("fromCurrency", "toCurrency", "effectiveDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerifaydaVerification_nationalId_idx" ON "VerifaydaVerification"("nationalId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerifaydaVerification_bookingId_idx" ON "VerifaydaVerification"("bookingId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -1143,3 +1307,6 @@ ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_userId_fkey" FOREIGN KEY ("userI
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SeatBlock" ADD CONSTRAINT "SeatBlock_seatId_fkey" FOREIGN KEY ("seatId") REFERENCES "Seat"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FraudAlert" ADD CONSTRAINT "FraudAlert_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,3 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
|
||||
@@ -46,6 +46,24 @@ enum ServiceClass {
|
||||
VIP_BED_UPPER
|
||||
}
|
||||
|
||||
enum PassengerCategory {
|
||||
ADULT
|
||||
CHILD
|
||||
}
|
||||
|
||||
enum IdDocumentType {
|
||||
NATIONAL_ID
|
||||
PASSPORT
|
||||
DRIVING_LICENSE
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum Currency {
|
||||
ETB
|
||||
DJF
|
||||
USD
|
||||
}
|
||||
|
||||
enum BookingStatus {
|
||||
DRAFT
|
||||
PENDING_PAYMENT
|
||||
@@ -142,11 +160,13 @@ model User {
|
||||
passwordHash String
|
||||
role UserRole @default(PASSENGER)
|
||||
nationality String?
|
||||
nationalityCode String?
|
||||
passportNumber String?
|
||||
nationalId String?
|
||||
failedLoginAttempts Int @default(0)
|
||||
lockedUntil DateTime?
|
||||
blockedUntil DateTime?
|
||||
lastLoginAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
passenger Passenger?
|
||||
@@ -173,6 +193,8 @@ model Session {
|
||||
model Passenger {
|
||||
id String @id @default(uuid())
|
||||
userId String @unique
|
||||
defaultTravelerProfileId String?
|
||||
preferredLanguage String?
|
||||
createdAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
bookings Booking[]
|
||||
@@ -181,6 +203,7 @@ model Passenger {
|
||||
notifications Notification[]
|
||||
travelerProfiles TravelerProfile[]
|
||||
savedRoutes SavedRoute[]
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model TravelerProfile {
|
||||
@@ -200,6 +223,8 @@ model Station {
|
||||
code String @unique
|
||||
name String
|
||||
city String
|
||||
countryCode String?
|
||||
isOperational Boolean @default(true)
|
||||
timezone String @default("Africa/Addis_Ababa")
|
||||
lat Decimal @db.Decimal(9, 6)
|
||||
lng Decimal @db.Decimal(9, 6)
|
||||
@@ -207,6 +232,7 @@ model Station {
|
||||
destinationTrips Trip[] @relation("DestinationTrips")
|
||||
stopTimes TripStopTime[]
|
||||
crowdSignals StationCrowdSignal[]
|
||||
@@index([city, countryCode])
|
||||
}
|
||||
|
||||
model TrainService {
|
||||
@@ -214,12 +240,14 @@ model TrainService {
|
||||
number String @unique
|
||||
name String
|
||||
operatorId String @default("op_edr")
|
||||
operatorName String?
|
||||
trips Trip[]
|
||||
}
|
||||
|
||||
model Trip {
|
||||
id String @id @default(uuid())
|
||||
serviceId String
|
||||
routeId String?
|
||||
originStationId String
|
||||
destinationStationId String
|
||||
departureAt DateTime
|
||||
@@ -227,8 +255,10 @@ model Trip {
|
||||
durationMinutes Int
|
||||
status TripStatus @default(SCHEDULED)
|
||||
stopsCount Int @default(0)
|
||||
reservedCount Int @default(0)
|
||||
onTimePercent Int @default(100)
|
||||
carbonRating String @default("A")
|
||||
notes String?
|
||||
service TrainService @relation(fields: [serviceId], references: [id])
|
||||
originStation Station @relation("OriginTrips", fields: [originStationId], references: [id])
|
||||
destinationStation Station @relation("DestinationTrips", fields: [destinationStationId], references: [id])
|
||||
@@ -238,6 +268,7 @@ model Trip {
|
||||
liveStatus TripLiveStatus?
|
||||
menuItems MenuItem[]
|
||||
journeySegments JourneySegment[]
|
||||
@@index([departureAt, originStationId])
|
||||
}
|
||||
|
||||
model TripStopTime {
|
||||
@@ -272,6 +303,10 @@ model Coach {
|
||||
tripId String
|
||||
label String
|
||||
serviceClass ServiceClass
|
||||
capacity Int?
|
||||
sequence Int?
|
||||
coachType String?
|
||||
amenities Json?
|
||||
trip Trip @relation(fields: [tripId], references: [id])
|
||||
seats Seat[]
|
||||
@@unique([tripId, label])
|
||||
@@ -283,15 +318,19 @@ model Seat {
|
||||
row Int
|
||||
col String
|
||||
label String
|
||||
seatNumber String?
|
||||
kind SeatKind @default(STANDARD)
|
||||
status SeatStatus @default(AVAILABLE)
|
||||
heldUntil DateTime?
|
||||
isWindow Boolean @default(false)
|
||||
isAisle Boolean @default(false)
|
||||
premiumFeeMinor Int @default(0)
|
||||
eligibility String?
|
||||
coach Coach @relation(fields: [coachId], references: [id])
|
||||
bookingSeats BookingSeat[]
|
||||
blocks SeatBlock[]
|
||||
@@unique([coachId, row, col])
|
||||
@@unique([coachId, seatNumber])
|
||||
}
|
||||
|
||||
model SeatHold {
|
||||
@@ -300,8 +339,10 @@ model SeatHold {
|
||||
seatIds String[]
|
||||
fareQuoteId String?
|
||||
passengerId String
|
||||
createdBy String?
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
model FareRule {
|
||||
@@ -318,16 +359,24 @@ model FareRule {
|
||||
}
|
||||
|
||||
model Booking {
|
||||
id String @id @default(uuid())
|
||||
bookingRef String @unique
|
||||
passengerId String
|
||||
tripId String
|
||||
status BookingStatus @default(DRAFT)
|
||||
currency String @default("ETB")
|
||||
totalMinor Int
|
||||
bookingType String @default("ONE_WAY")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid())
|
||||
bookingRef String @unique
|
||||
passengerId String
|
||||
tripId String
|
||||
status BookingStatus @default(DRAFT)
|
||||
currency String @default("ETB")
|
||||
totalMinor Int
|
||||
adultCount Int @default(1)
|
||||
childCount Int @default(0)
|
||||
displayCurrency Currency?
|
||||
displayTotalMinor Int?
|
||||
bookingType String @default("ONE_WAY")
|
||||
userAgent String?
|
||||
source String @default("WEB")
|
||||
promoCode String?
|
||||
paidAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
passenger Passenger @relation(fields: [passengerId], references: [id])
|
||||
trip Trip @relation(fields: [tripId], references: [id])
|
||||
seats BookingSeat[]
|
||||
@@ -338,15 +387,26 @@ model Booking {
|
||||
modifications BookingModification[]
|
||||
cancellation BookingCancellation?
|
||||
baggage BaggageBooking[]
|
||||
@@index([passengerId, status])
|
||||
}
|
||||
|
||||
model BookingSeat {
|
||||
id String @id @default(uuid())
|
||||
bookingId String
|
||||
seatId String
|
||||
passengerName String
|
||||
idDocumentType String?
|
||||
idDocumentNumber String?
|
||||
id String @id @default(uuid())
|
||||
bookingId String
|
||||
seatId String
|
||||
passengerName String
|
||||
dateOfBirth DateTime?
|
||||
passengerCategory PassengerCategory @default(ADULT)
|
||||
idDocumentType IdDocumentType?
|
||||
idDocumentNumber String?
|
||||
passportNumber String?
|
||||
passportCountry String?
|
||||
verifaydaVerified Boolean @default(false)
|
||||
verifaydaData Json?
|
||||
seatLabelSnapshot String?
|
||||
fareMinor Int?
|
||||
displayCurrency Currency?
|
||||
displayFareMinor Int?
|
||||
booking Booking @relation(fields: [bookingId], references: [id])
|
||||
seat Seat @relation(fields: [seatId], references: [id])
|
||||
}
|
||||
@@ -357,8 +417,10 @@ model PaymentMethod {
|
||||
type PaymentMethodType
|
||||
displayName String
|
||||
maskedHint String?
|
||||
providerId String?
|
||||
isDefault Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
@@index([userId, isDefault])
|
||||
}
|
||||
|
||||
model PaymentIntent {
|
||||
@@ -367,6 +429,7 @@ model PaymentIntent {
|
||||
amountMinor Int
|
||||
currency String @default("ETB")
|
||||
method PaymentMethodType
|
||||
provider String?
|
||||
status PaymentIntentStatus @default(REQUIRES_ACTION)
|
||||
providerRef String?
|
||||
clientAction Json?
|
||||
@@ -375,6 +438,8 @@ model PaymentIntent {
|
||||
providerTxnId String?
|
||||
rawInitiation Json?
|
||||
paidAt DateTime?
|
||||
refundedAt DateTime?
|
||||
captureMethod String?
|
||||
failureCode String?
|
||||
failureMessage String?
|
||||
expiresAt DateTime?
|
||||
@@ -433,7 +498,9 @@ 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[]
|
||||
@@ -465,10 +532,13 @@ 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])
|
||||
}
|
||||
|
||||
model WalletLedgerEntry {
|
||||
@@ -516,6 +586,8 @@ model StationCrowdSignal {
|
||||
level String
|
||||
label String
|
||||
statusLabel String
|
||||
confidence Int?
|
||||
observedAt DateTime?
|
||||
updatedAt DateTime @updatedAt
|
||||
station Station @relation(fields: [stationId], references: [id])
|
||||
}
|
||||
@@ -544,6 +616,7 @@ model MenuItem {
|
||||
priceMinor Int
|
||||
currency String @default("ETB")
|
||||
available Boolean @default(true)
|
||||
availableUntil DateTime?
|
||||
trip Trip @relation(fields: [tripId], references: [id])
|
||||
category MenuCategory @relation(fields: [categoryId], references: [id])
|
||||
}
|
||||
@@ -554,6 +627,8 @@ model FoodOrder {
|
||||
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[]
|
||||
@@ -565,6 +640,7 @@ model FoodOrderItem {
|
||||
menuItemId String
|
||||
name String
|
||||
quantity Int
|
||||
unitPriceMinor Int?
|
||||
lineTotalMinor Int
|
||||
order FoodOrder @relation(fields: [orderId], references: [id])
|
||||
}
|
||||
@@ -588,6 +664,7 @@ model FaqArticle {
|
||||
model SupportConversation {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
assignedAgentId String?
|
||||
status SupportConversationStatus @default(OPEN)
|
||||
createdAt DateTime @default(now())
|
||||
messages SupportMessage[]
|
||||
@@ -598,6 +675,7 @@ model SupportMessage {
|
||||
conversationId String
|
||||
sender SupportSender
|
||||
text String
|
||||
attachments Json?
|
||||
createdAt DateTime @default(now())
|
||||
conversation SupportConversation @relation(fields: [conversationId], references: [id])
|
||||
}
|
||||
@@ -715,17 +793,19 @@ model RouteStop {
|
||||
}
|
||||
|
||||
model RouteFareRule {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
routeId String
|
||||
serviceClass ServiceClass
|
||||
passengerCategory String @default("ADULT")
|
||||
passengerCategory PassengerCategory @default(ADULT)
|
||||
baseFareMinor Int
|
||||
discountPercent Int?
|
||||
currency String @default("ETB")
|
||||
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)
|
||||
createdAt DateTime @default(now())
|
||||
route Route @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
||||
@@index([routeId, serviceClass])
|
||||
}
|
||||
|
||||
@@ -915,3 +995,29 @@ model FraudAlert {
|
||||
@@index([userId, createdAt])
|
||||
@@index([acknowledged])
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient, ServiceClass, UserRole, LoyaltyTier, SeatKind } from '@prisma/client';
|
||||
import { PrismaClient, ServiceClass, UserRole, LoyaltyTier, SeatKind, PassengerCategory, Currency } from '@prisma/client';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
@@ -6,28 +6,28 @@ const prisma = new PrismaClient();
|
||||
async function main() {
|
||||
console.log('🌱 Starting comprehensive seed...');
|
||||
|
||||
// All 18 Stations (Ethiopian-Djibouti Railway)
|
||||
const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa Central', city: 'Addis Ababa', lat: 9.0054, lng: 38.7636 } });
|
||||
const sebeta = await prisma.station.upsert({ where: { code: 'SBT' }, update: {}, create: { code: 'SBT', name: 'Sebeta', city: 'Sebeta', lat: 8.9167, lng: 38.6167 } });
|
||||
const labu = await prisma.station.upsert({ where: { code: 'LBU' }, update: {}, create: { code: 'LBU', name: 'Labu', city: 'Labu', lat: 8.8500, lng: 38.8500 } });
|
||||
const indode = await prisma.station.upsert({ where: { code: 'IND' }, update: {}, create: { code: 'IND', name: 'Indode', city: 'Indode', lat: 8.7833, lng: 39.0167 } });
|
||||
const bishoftu = await prisma.station.upsert({ where: { code: 'BSH' }, update: {}, create: { code: 'BSH', name: 'Bishoftu', city: 'Bishoftu', lat: 8.7500, lng: 38.9833 } });
|
||||
const mojo = await prisma.station.upsert({ where: { code: 'MJO' }, update: {}, create: { code: 'MJO', name: 'Mojo', city: 'Mojo', lat: 8.5833, lng: 39.1167 } });
|
||||
const adama = await prisma.station.upsert({ where: { code: 'ADM' }, update: {}, create: { code: 'ADM', name: 'Adama', city: 'Adama', lat: 8.5400, lng: 39.2675 } });
|
||||
const feto = await prisma.station.upsert({ where: { code: 'FTO' }, update: {}, create: { code: 'FTO', name: 'Feto', city: 'Feto', lat: 8.7167, lng: 39.5833 } });
|
||||
const metahara = await prisma.station.upsert({ where: { code: 'MTH' }, update: {}, create: { code: 'MTH', name: 'Metahara', city: 'Metahara', lat: 8.9000, lng: 39.9167 } });
|
||||
const awash = await prisma.station.upsert({ where: { code: 'AWS' }, update: {}, create: { code: 'AWS', name: 'Awash', city: 'Awash', lat: 8.9833, lng: 40.1667 } });
|
||||
const mieso = await prisma.station.upsert({ where: { code: 'MSO' }, update: {}, create: { code: 'MSO', name: 'Mieso', city: 'Mieso', lat: 9.2333, lng: 40.7500 } });
|
||||
const bike = await prisma.station.upsert({ where: { code: 'BKE' }, update: {}, create: { code: 'BKE', name: 'Bike', city: 'Bike', lat: 9.4167, lng: 41.2500 } });
|
||||
const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', lat: 9.5931, lng: 41.8661 } });
|
||||
const arawa = await prisma.station.upsert({ where: { code: 'ARW' }, update: {}, create: { code: 'ARW', name: 'Arawa', city: 'Arawa', lat: 10.0833, lng: 42.2500 } });
|
||||
const adigala = await prisma.station.upsert({ where: { code: 'ADG' }, update: {}, create: { code: 'ADG', name: 'Adigala', city: 'Adigala', lat: 10.5000, lng: 42.5833 } });
|
||||
const aysha = await prisma.station.upsert({ where: { code: 'AYS' }, update: {}, create: { code: 'AYS', name: 'Aysha', city: 'Aysha', lat: 11.5500, lng: 42.7167 } });
|
||||
const dawanle = await prisma.station.upsert({ where: { code: 'DWN' }, update: {}, create: { code: 'DWN', name: 'Dawanle', city: 'Dawanle', timezone: 'Africa/Djibouti', lat: 11.3833, lng: 42.8500 } });
|
||||
const alisabieh = await prisma.station.upsert({ where: { code: 'ALI' }, update: {}, create: { code: 'ALI', name: 'Alisabieh', city: 'Alisabieh', timezone: 'Africa/Djibouti', lat: 11.1667, lng: 42.7167 } });
|
||||
const holhol = await prisma.station.upsert({ where: { code: 'HLH' }, update: {}, create: { code: 'HLH', name: 'Holhol', city: 'Holhol', timezone: 'Africa/Djibouti', lat: 11.4167, lng: 43.0000 } });
|
||||
const nagad = await prisma.station.upsert({ where: { code: 'NGD' }, update: {}, create: { code: 'NGD', name: 'Nagad', city: 'Nagad', timezone: 'Africa/Djibouti', lat: 11.5167, lng: 43.1000 } });
|
||||
const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } });
|
||||
// All 21 Stations (Ethiopian-Djibouti Railway)
|
||||
const addis = await prisma.station.upsert({ where: { code: 'ADD' }, update: {}, create: { code: 'ADD', name: 'Addis Ababa Central', city: 'Addis Ababa', countryCode: 'ET', lat: 9.0054, lng: 38.7636 } });
|
||||
const sebeta = await prisma.station.upsert({ where: { code: 'SBT' }, update: {}, create: { code: 'SBT', name: 'Sebeta', city: 'Sebeta', countryCode: 'ET', lat: 8.9167, lng: 38.6167 } });
|
||||
const labu = await prisma.station.upsert({ where: { code: 'LBU' }, update: {}, create: { code: 'LBU', name: 'Labu', city: 'Labu', countryCode: 'ET', lat: 8.8500, lng: 38.8500 } });
|
||||
const indode = await prisma.station.upsert({ where: { code: 'IND' }, update: {}, create: { code: 'IND', name: 'Indode', city: 'Indode', countryCode: 'ET', lat: 8.7833, lng: 39.0167 } });
|
||||
const bishoftu = await prisma.station.upsert({ where: { code: 'BSH' }, update: {}, create: { code: 'BSH', name: 'Bishoftu', city: 'Bishoftu', countryCode: 'ET', lat: 8.7500, lng: 38.9833 } });
|
||||
const mojo = await prisma.station.upsert({ where: { code: 'MJO' }, update: {}, create: { code: 'MJO', name: 'Mojo', city: 'Mojo', countryCode: 'ET', lat: 8.5833, lng: 39.1167 } });
|
||||
const adama = await prisma.station.upsert({ where: { code: 'ADM' }, update: {}, create: { code: 'ADM', name: 'Adama', city: 'Adama', countryCode: 'ET', lat: 8.5400, lng: 39.2675 } });
|
||||
const feto = await prisma.station.upsert({ where: { code: 'FTO' }, update: {}, create: { code: 'FTO', name: 'Feto', city: 'Feto', countryCode: 'ET', lat: 8.7167, lng: 39.5833 } });
|
||||
const metahara = await prisma.station.upsert({ where: { code: 'MTH' }, update: {}, create: { code: 'MTH', name: 'Metahara', city: 'Metahara', countryCode: 'ET', lat: 8.9000, lng: 39.9167 } });
|
||||
const awash = await prisma.station.upsert({ where: { code: 'AWS' }, update: {}, create: { code: 'AWS', name: 'Awash', city: 'Awash', countryCode: 'ET', lat: 8.9833, lng: 40.1667 } });
|
||||
const mieso = await prisma.station.upsert({ where: { code: 'MSO' }, update: {}, create: { code: 'MSO', name: 'Mieso', city: 'Mieso', countryCode: 'ET', lat: 9.2333, lng: 40.7500 } });
|
||||
const bike = await prisma.station.upsert({ where: { code: 'BKE' }, update: {}, create: { code: 'BKE', name: 'Bike', city: 'Bike', countryCode: 'ET', lat: 9.4167, lng: 41.2500 } });
|
||||
const direDawa = await prisma.station.upsert({ where: { code: 'DDW' }, update: {}, create: { code: 'DDW', name: 'Dire Dawa', city: 'Dire Dawa', countryCode: 'ET', lat: 9.5931, lng: 41.8661 } });
|
||||
const arawa = await prisma.station.upsert({ where: { code: 'ARW' }, update: {}, create: { code: 'ARW', name: 'Arawa', city: 'Arawa', countryCode: 'ET', lat: 10.0833, lng: 42.2500 } });
|
||||
const adigala = await prisma.station.upsert({ where: { code: 'ADG' }, update: {}, create: { code: 'ADG', name: 'Adigala', city: 'Adigala', countryCode: 'ET', lat: 10.5000, lng: 42.5833 } });
|
||||
const aysha = await prisma.station.upsert({ where: { code: 'AYS' }, update: {}, create: { code: 'AYS', name: 'Aysha', city: 'Aysha', countryCode: 'ET', lat: 11.5500, lng: 42.7167 } });
|
||||
const dawanle = await prisma.station.upsert({ where: { code: 'DWN' }, update: {}, create: { code: 'DWN', name: 'Dawanle', city: 'Dawanle', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.3833, lng: 42.8500 } });
|
||||
const alisabieh = await prisma.station.upsert({ where: { code: 'ALI' }, update: {}, create: { code: 'ALI', name: 'Alisabieh', city: 'Alisabieh', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.1667, lng: 42.7167 } });
|
||||
const holhol = await prisma.station.upsert({ where: { code: 'HLH' }, update: {}, create: { code: 'HLH', name: 'Holhol', city: 'Holhol', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.4167, lng: 43.0000 } });
|
||||
const nagad = await prisma.station.upsert({ where: { code: 'NGD' }, update: {}, create: { code: 'NGD', name: 'Nagad', city: 'Nagad', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.5167, lng: 43.1000 } });
|
||||
const djibouti = await prisma.station.upsert({ where: { code: 'DJI' }, update: {}, create: { code: 'DJI', name: 'Djibouti', city: 'Djibouti', countryCode: 'DJ', timezone: 'Africa/Djibouti', lat: 11.5720, lng: 43.1456 } });
|
||||
|
||||
// Routes
|
||||
const route1 = await prisma.route.upsert({
|
||||
@@ -62,12 +62,15 @@ async function main() {
|
||||
{ routeId: route1.id, stationId: djibouti.id, sequence: 21, distanceKm: 756 },
|
||||
]});
|
||||
|
||||
// Route Fare Rules
|
||||
// Route Fare Rules (with passenger categories)
|
||||
await prisma.routeFareRule.deleteMany({ where: { routeId: route1.id } });
|
||||
await prisma.routeFareRule.createMany({ data: [
|
||||
{ routeId: route1.id, serviceClass: 'ECONOMY_REGULAR', baseFareMinor: 45000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'ECONOMY_BED_LOWER', baseFareMinor: 65000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'VIP_BED_LOWER', baseFareMinor: 95000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'ECONOMY_REGULAR', passengerCategory: 'ADULT', baseFareMinor: 45000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'ECONOMY_BED_LOWER', passengerCategory: 'ADULT', baseFareMinor: 65000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'VIP_BED_LOWER', passengerCategory: 'ADULT', baseFareMinor: 95000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'ECONOMY_REGULAR', passengerCategory: 'CHILD', baseFareMinor: 45000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'ECONOMY_BED_LOWER', passengerCategory: 'CHILD', baseFareMinor: 65000, validFrom: new Date('2026-01-01') },
|
||||
{ routeId: route1.id, serviceClass: 'VIP_BED_LOWER', passengerCategory: 'CHILD', baseFareMinor: 95000, validFrom: new Date('2026-01-01') },
|
||||
]});
|
||||
|
||||
// Train Services
|
||||
@@ -217,12 +220,24 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Currency Exchange Rates
|
||||
await prisma.currencyExchangeRate.deleteMany({});
|
||||
await prisma.currencyExchangeRate.createMany({ data: [
|
||||
{ fromCurrency: 'ETB', toCurrency: 'ETB', rate: 1.0, effectiveDate: new Date() },
|
||||
{ fromCurrency: 'ETB', toCurrency: 'DJF', rate: 3.25, effectiveDate: new Date() },
|
||||
{ fromCurrency: 'ETB', toCurrency: 'USD', rate: 0.018, effectiveDate: new Date() },
|
||||
{ fromCurrency: 'DJF', toCurrency: 'ETB', rate: 0.3077, effectiveDate: new Date() },
|
||||
{ fromCurrency: 'USD', toCurrency: 'ETB', rate: 55.56, effectiveDate: new Date() },
|
||||
]});
|
||||
|
||||
console.log('✅ Comprehensive seed complete');
|
||||
console.log('\n📋 Seed Summary:');
|
||||
console.log(' - 18 Stations (Complete Ethiopian-Djibouti Railway)');
|
||||
console.log(' - 21 Stations (Complete Ethiopian-Djibouti Railway with country codes)');
|
||||
console.log(' - 1 Route with 21 stops');
|
||||
console.log(' - 2 Train services, 4 trips');
|
||||
console.log(' - 3 Coaches per trip (Economy, Bed, VIP)');
|
||||
console.log(' - Fare rules for ADULT and CHILD categories');
|
||||
console.log(' - Currency exchange rates (ETB ↔ DJF, USD)');
|
||||
console.log(' - 3 Users: Admin, Passenger (Silver tier + wallet), Agent');
|
||||
console.log(' - 3 Fraud detection rules');
|
||||
console.log(' - 3 Loyalty rewards');
|
||||
@@ -232,6 +247,9 @@ async function main() {
|
||||
console.log(' Passenger: kelemu@email.com / password123');
|
||||
console.log(' Agent: agent@edr-platform.com / agent123');
|
||||
console.log('\n🚉 Stations: Addis Ababa → Sebeta → Labu → Indode → Bishoftu → Mojo → Adama → Feto → Metahara → Awash → Mieso → Bike → Dire Dawa → Arawa → Adigala → Aysha → Dawanle → Alisabieh → Holhol → Nagad → Djibouti');
|
||||
console.log('\n💰 Pricing: ADULT (≥5 years) = 100% fare | CHILD (<5 years) = First free, subsequent 100%');
|
||||
console.log('\n💱 Currencies: ETB (transaction) | DJF, USD (display) | Rates: ETB→DJF=3.25, ETB→USD=0.018');
|
||||
console.log('\n🔐 Verifayda: DISABLED (set VERIFAYDA_ENABLED=true in production)');
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
|
||||
Reference in New Issue
Block a user