mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 07:08:18 +00:00
Boarding, payment methods, journey direction on seat hold, and more updates
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "passenger"."TicketSeat" DROP CONSTRAINT IF EXISTS "TicketSeat_seatId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "passenger"."TicketSeat" DROP CONSTRAINT IF EXISTS "TicketSeat_ticketId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX IF EXISTS "passenger"."Ticket_bookingId_key";
|
||||
|
||||
-- AlterTable: Station
|
||||
ALTER TABLE "passenger"."Station" DROP COLUMN IF EXISTS "timezone";
|
||||
|
||||
-- AlterTable: Ticket — add columns with safe defaults
|
||||
ALTER TABLE "passenger"."Ticket"
|
||||
ADD COLUMN IF NOT EXISTS "leg" INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS "passengerName" TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS "scheduleId" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "seatId" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE IF EXISTS "passenger"."TicketSeat";
|
||||
|
||||
-- Remove GateValidationLog rows referencing orphan tickets first
|
||||
DELETE FROM "passenger"."GateValidationLog"
|
||||
WHERE "ticketId" IN (
|
||||
SELECT "id" FROM "passenger"."Ticket"
|
||||
WHERE "seatId" = ''
|
||||
OR "seatId" NOT IN (SELECT "id" FROM "passenger"."Seat")
|
||||
);
|
||||
|
||||
-- Remove orphan ticket rows
|
||||
DELETE FROM "passenger"."Ticket"
|
||||
WHERE "seatId" = ''
|
||||
OR "seatId" NOT IN (SELECT "id" FROM "passenger"."Seat");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "Ticket_bookingId_idx" ON "passenger"."Ticket"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "Ticket_seatId_idx" ON "passenger"."Ticket"("seatId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "passenger"."Ticket"
|
||||
ADD CONSTRAINT "Ticket_seatId_fkey"
|
||||
FOREIGN KEY ("seatId") REFERENCES "passenger"."Seat"("id")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Drop temporary defaults that were only needed for the backfill
|
||||
ALTER TABLE "passenger"."Ticket" ALTER COLUMN "passengerName" DROP DEFAULT;
|
||||
ALTER TABLE "passenger"."Ticket" ALTER COLUMN "seatId" DROP DEFAULT;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Remove timezone column if it still exists
|
||||
ALTER TABLE "passenger"."Station" DROP COLUMN IF EXISTS "timezone";
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "passenger"."TravelerProfile" ADD COLUMN "gender" TEXT;
|
||||
@@ -0,0 +1,117 @@
|
||||
-- Migration: Add Configurable Fare Management System
|
||||
|
||||
-- Main fare configuration table
|
||||
CREATE TABLE "fare_configurations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"effective_date" TIMESTAMP(3) NOT NULL,
|
||||
"expiry_date" TIMESTAMP(3),
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT false,
|
||||
"is_default" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_by" TEXT,
|
||||
"approved_by" TEXT,
|
||||
"approved_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "fare_configurations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Rate structure by nationality and coach/position
|
||||
CREATE TABLE "fare_rate_rules" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fare_config_id" TEXT NOT NULL,
|
||||
"nationality_type" TEXT NOT NULL, -- 'LOCAL' or 'INTERNATIONAL'
|
||||
"coach_type" TEXT NOT NULL, -- 'REGULAR_SEAT', 'ECONOMY_BED', 'VIP_BED'
|
||||
"bed_position" TEXT, -- 'UPPER', 'MIDDLE', 'LOWER', NULL for seats
|
||||
"rate_per_km_minor" INTEGER NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "fare_rate_rules_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Configurable fare components (insurance, premiums, service charges, taxes)
|
||||
CREATE TABLE "fare_components" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fare_config_id" TEXT NOT NULL,
|
||||
"component_type" TEXT NOT NULL, -- 'INSURANCE', 'PREMIUM', 'SERVICE_CHARGE', 'TAX', 'DEMAND'
|
||||
"component_name" TEXT NOT NULL,
|
||||
"calculation_method" TEXT NOT NULL, -- 'MULTIPLIER', 'PERCENTAGE', 'FIXED_AMOUNT'
|
||||
"value_minor" INTEGER, -- For fixed amounts
|
||||
"percentage_value" DECIMAL(10,6), -- For percentages (e.g., 0.02 for 2%)
|
||||
"applies_to" TEXT NOT NULL DEFAULT 'SUBTOTAL', -- 'BASE_FARE', 'SUBTOTAL', 'TOTAL'
|
||||
"apply_order" INTEGER NOT NULL DEFAULT 1, -- Order of application
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "fare_components_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Age-based pricing rules
|
||||
CREATE TABLE "age_pricing_rules" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fare_config_id" TEXT NOT NULL,
|
||||
"rule_name" TEXT NOT NULL,
|
||||
"min_age" INTEGER NOT NULL,
|
||||
"max_age" INTEGER,
|
||||
"pricing_type" TEXT NOT NULL, -- 'FREE', 'FULL_FARE', 'DISCOUNTED'
|
||||
"discount_percentage" DECIMAL(5,4), -- For discounted fares
|
||||
"max_free_passengers" INTEGER, -- For free fares (e.g., 1 free child)
|
||||
"applies_to_components" BOOLEAN NOT NULL DEFAULT false, -- Whether discount applies to components too
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "age_pricing_rules_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Audit trail for configuration changes
|
||||
CREATE TABLE "fare_configuration_audit" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fare_config_id" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL, -- 'CREATED', 'UPDATED', 'ACTIVATED', 'DEACTIVATED'
|
||||
"changed_by" TEXT,
|
||||
"changes" JSONB, -- Store the actual changes made
|
||||
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "fare_configuration_audit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Foreign key constraints
|
||||
ALTER TABLE "fare_rate_rules" ADD CONSTRAINT "fare_rate_rules_fare_config_id_fkey" FOREIGN KEY ("fare_config_id") REFERENCES "fare_configurations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "fare_components" ADD CONSTRAINT "fare_components_fare_config_id_fkey" FOREIGN KEY ("fare_config_id") REFERENCES "fare_configurations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "age_pricing_rules" ADD CONSTRAINT "age_pricing_rules_fare_config_id_fkey" FOREIGN KEY ("fare_config_id") REFERENCES "fare_configurations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "fare_configuration_audit" ADD CONSTRAINT "fare_configuration_audit_fare_config_id_fkey" FOREIGN KEY ("fare_config_id") REFERENCES "fare_configurations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX "fare_configurations_effective_date_idx" ON "fare_configurations"("effective_date");
|
||||
CREATE INDEX "fare_configurations_is_active_idx" ON "fare_configurations"("is_active");
|
||||
CREATE UNIQUE INDEX "fare_configurations_default_unique_idx" ON "fare_configurations"("is_default") WHERE "is_default" = true;
|
||||
|
||||
CREATE INDEX "fare_rate_rules_config_lookup_idx" ON "fare_rate_rules"("fare_config_id", "nationality_type", "coach_type", "bed_position");
|
||||
CREATE INDEX "fare_components_config_order_idx" ON "fare_components"("fare_config_id", "apply_order");
|
||||
CREATE INDEX "age_pricing_rules_age_lookup_idx" ON "age_pricing_rules"("fare_config_id", "min_age", "max_age");
|
||||
|
||||
-- Add legacy mode flag to existing fare tables for gradual migration
|
||||
ALTER TABLE "FareRule" ADD COLUMN "migrated_to_config_id" TEXT;
|
||||
ALTER TABLE "SegmentFareRule" ADD COLUMN "migrated_to_config_id" TEXT;
|
||||
|
||||
-- Add feature flag support
|
||||
CREATE TABLE "system_features" (
|
||||
"id" TEXT NOT NULL,
|
||||
"feature_name" TEXT NOT NULL UNIQUE,
|
||||
"is_enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"config" JSONB,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "system_features_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Insert the configurable fares feature flag
|
||||
INSERT INTO "system_features" ("id", "feature_name", "is_enabled", "config")
|
||||
VALUES ('cf-001', 'USE_CONFIGURABLE_FARES', false, '{"rollout_percentage": 0}');
|
||||
@@ -304,6 +304,7 @@ model TravelerProfile {
|
||||
id String @id @default(uuid())
|
||||
passengerId String
|
||||
fullName String
|
||||
gender String?
|
||||
relationship String
|
||||
dateOfBirth DateTime?
|
||||
nationalId String?
|
||||
@@ -321,7 +322,6 @@ model Station {
|
||||
countryCode String?
|
||||
sequence Int @default(0)
|
||||
isOperational Boolean @default(true)
|
||||
timezone String @default("Africa/Addis_Ababa")
|
||||
lat Decimal? @db.Decimal(9, 6)
|
||||
lng Decimal? @db.Decimal(9, 6)
|
||||
originSchedules TrainSchedule[] @relation("OriginTrips")
|
||||
@@ -459,7 +459,7 @@ model Seat {
|
||||
coach Coach @relation(fields: [coachId], references: [id])
|
||||
bookingSeats BookingSeat[]
|
||||
blocks SeatBlock[]
|
||||
ticketSeats TicketSeat[]
|
||||
tickets Ticket[]
|
||||
|
||||
@@unique([coachId, seatNumber])
|
||||
@@unique([coachId, row, col])
|
||||
@@ -542,7 +542,7 @@ model Booking {
|
||||
returnSchedule TrainSchedule? @relation("ReturnSchedule", fields: [returnScheduleId], references: [id])
|
||||
seats BookingSeat[]
|
||||
paymentIntent PaymentIntent?
|
||||
ticket Ticket?
|
||||
tickets Ticket[]
|
||||
foodOrders FoodOrder[]
|
||||
agentBooking AgentBooking?
|
||||
modifications BookingModification[]
|
||||
@@ -660,8 +660,12 @@ model PaymentRefund {
|
||||
|
||||
model Ticket {
|
||||
id String @id @default(uuid())
|
||||
bookingId String @unique
|
||||
bookingId String
|
||||
bookingRef String
|
||||
passengerName String
|
||||
seatId String
|
||||
leg Int @default(1)
|
||||
scheduleId String?
|
||||
status String @default("ACTIVE")
|
||||
qrPayload String
|
||||
barcodePayload String?
|
||||
@@ -672,20 +676,9 @@ model Ticket {
|
||||
validatorId String?
|
||||
boardedAt DateTime?
|
||||
booking Booking @relation(fields: [bookingId], references: [id])
|
||||
seat Seat @relation(fields: [seatId], references: [id])
|
||||
validationLogs GateValidationLog[]
|
||||
seats TicketSeat[]
|
||||
@@schema("passenger")
|
||||
}
|
||||
|
||||
model TicketSeat {
|
||||
id String @id @default(uuid())
|
||||
ticketId String
|
||||
seatId String
|
||||
seatIndex Int @default(0)
|
||||
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||
seat Seat @relation(fields: [seatId], references: [id])
|
||||
|
||||
@@index([ticketId])
|
||||
@@index([bookingId])
|
||||
@@index([seatId])
|
||||
@@schema("passenger")
|
||||
}
|
||||
@@ -1013,7 +1006,7 @@ model RouteStop {
|
||||
routeId String
|
||||
stationId String
|
||||
sequence Int
|
||||
distanceKm Int?
|
||||
distanceKm Float?
|
||||
createdAt DateTime @default(now())
|
||||
route Route @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ async function seedRoute() {
|
||||
const station = await prisma.station.findUnique({ where: { code: stationCodes[i] } });
|
||||
await prisma.routeStop.upsert({
|
||||
where: { routeId_sequence: { routeId: route.id, sequence: i + 1 } },
|
||||
update: {},
|
||||
update: { distanceKm: routeDistancesKm[i] },
|
||||
create: { routeId: route.id, stationId: station!.id, sequence: i + 1, distanceKm: routeDistancesKm[i] },
|
||||
});
|
||||
}
|
||||
@@ -227,12 +227,13 @@ async function seedRoute() {
|
||||
});
|
||||
|
||||
const returnStationCodes = ['DRE', 'BIK', 'MIS', 'MTE', 'ADM', 'MOJ', 'BSH', 'LEB', 'SBT'];
|
||||
// Cumulative distances from origin (Dire Dawa), mirroring the outbound route in reverse
|
||||
const returnRouteDistancesKm = [0, 119.4, 181.4, 232.8, 306.3, 323.1, 345.8, 401.5, 413.0];
|
||||
for (let i = 0; i < returnStationCodes.length; i++) {
|
||||
const station = await prisma.station.findUnique({ where: { code: returnStationCodes[i] } });
|
||||
await prisma.routeStop.upsert({
|
||||
where: { routeId_sequence: { routeId: returnRoute!.id, sequence: i + 1 } },
|
||||
update: {},
|
||||
update: { distanceKm: returnRouteDistancesKm[i] },
|
||||
create: { routeId: returnRoute!.id, stationId: station!.id, sequence: i + 1, distanceKm: returnRouteDistancesKm[i] },
|
||||
});
|
||||
}
|
||||
@@ -757,13 +758,7 @@ async function runStep(name: string, step: () => Promise<unknown>): Promise<bool
|
||||
async function main() {
|
||||
console.log('🌱 Comprehensive EDR Seed Starting...\n');
|
||||
|
||||
const steps: Array<[string, () => Promise<unknown>]> = [
|
||||
['system users', seedSystemUsers],
|
||||
['fare rules', seedFareRules],
|
||||
['segment fares', seedSegmentFares],
|
||||
['currency', seedCurrency],
|
||||
['notification templates', seedNotificationTemplates],
|
||||
['kulubbi package', seedKulubbiPackage],
|
||||
const steps: Array<[string, () => Promise<unknown>]> = [
|
||||
];
|
||||
|
||||
let failed = 0;
|
||||
|
||||
Reference in New Issue
Block a user