diff --git a/apps/edr-passenger-api/prisma/migrations/20260101000000_add_configurable_fare_system/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260101000000_add_configurable_fare_system/migration.sql index 15b2502f5..aa6cd855a 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260101000000_add_configurable_fare_system/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260101000000_add_configurable_fare_system/migration.sql @@ -107,7 +107,7 @@ CREATE TABLE "system_features" ( "is_enabled" BOOLEAN NOT NULL DEFAULT false, "config" JSONB, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "system_features_pkey" PRIMARY KEY ("id") ); diff --git a/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts b/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts index 8805bc3c6..b2b7ae631 100644 --- a/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts +++ b/apps/edr-passenger-api/src/modules/passengers/passengers.service.ts @@ -115,25 +115,35 @@ export class PassengersService { const guestBooking = (passenger as any)?.bookings?.[0] ?? null; const guestSeat = guestBooking?.seats?.[0] ?? null; + // Parse notes JSON to extract phone and other data + let notesData: any = null; + if (profile.notes) { + try { + notesData = typeof profile.notes === 'string' ? JSON.parse(profile.notes) : profile.notes; + } catch { + notesData = null; + } + } + return { id: profile.id, fullName: profile.fullName, - email: localUser?.email ?? iam?.email ?? guestBooking?.contactEmail ?? null, - phone: localUser?.phone ?? iam?.phone_number ?? guestBooking?.contactPhone ?? null, + email: localUser?.email ?? iam?.email ?? notesData?.email ?? guestBooking?.contactEmail ?? null, + phone: localUser?.phone ?? iam?.phone_number ?? notesData?.phone ?? guestBooking?.contactPhone ?? null, gender: profile.gender ?? localUser?.gender ?? iam?.metadata?.gender ?? null, dateOfBirth: profile.dateOfBirth ? new Date(profile.dateOfBirth).toISOString().split('T')[0] : (localUser?.dateOfBirth ? (localUser.dateOfBirth instanceof Date ? localUser.dateOfBirth.toISOString().split('T')[0] : localUser.dateOfBirth) : iam?.metadata?.dateOfBirth ?? null), - nationality: localUser?.nationality ?? iam?.metadata?.nationality ?? (guestSeat?.passportCountry ? (guestSeat.passportCountry === 'Ethiopia' ? 'Ethiopian' : guestSeat.passportCountry) : null), + nationality: localUser?.nationality ?? iam?.metadata?.nationality ?? notesData?.nationality ?? (guestSeat?.passportCountry ? (guestSeat.passportCountry === 'Ethiopia' ? 'Ethiopian' : guestSeat.passportCountry) : null), nationalityCode: localUser?.nationalityCode ?? iam?.metadata?.nationalityCode ?? null, faydaVerified, faydaVerifiedAt: localUser?.faydaVerifiedAt ?? iam?.metadata?.faydaVerifiedAt ?? null, - passportNumber: localUser?.passportNumber ?? iam?.metadata?.passportNumber ?? guestSeat?.passportNumber ?? null, - passportCountry: localUser?.passportCountry ?? iam?.metadata?.passportCountry ?? guestSeat?.passportCountry ?? null, + passportNumber: localUser?.passportNumber ?? iam?.metadata?.passportNumber ?? notesData?.passportNumber ?? guestSeat?.passportNumber ?? null, + passportCountry: localUser?.passportCountry ?? iam?.metadata?.passportCountry ?? notesData?.passportCountry ?? guestSeat?.passportCountry ?? null, passportExpiryDate: localUser?.passportExpiryDate ?? iam?.metadata?.passportExpiryDate ?? null, - idDocumentType: profile.nationalId ? 'NATIONAL_ID' : null, + idDocumentType: profile.nationalId ? 'NATIONAL_ID' : (notesData?.idDocumentType ?? null), verified: faydaVerified, lastLoginAt: localUser?.lastLoginAt ?? null, role: localUser?.role ?? null, diff --git a/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts b/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts index 8e78d710b..3c07d8c40 100644 --- a/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts +++ b/apps/edr-passenger-api/src/modules/tickets/tickets.service.ts @@ -54,11 +54,11 @@ export class TicketsService { ...(filters.dateTo ? { lte: new Date(new Date(filters.dateTo).setHours(23, 59, 59, 999)) } : {}), }; } - // if (filters.coachId) { - // where.seat = { - // coachId: filters.coachId - // }; - // } + if (filters.coachId) { + where.seat = { + coachId: filters.coachId + }; + } const [tickets, total] = await Promise.all([ this.prisma.ticket.findMany({ @@ -67,8 +67,8 @@ export class TicketsService { booking: { include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, - returnSchedule: { select: { departureAt: true, arrivalAt: true, originStation: true, destinationStation: true } }, - passenger: { select: { id: true, iamUserId: true } }, + returnSchedule: { include: { originStation: true, destinationStation: true } }, + passenger: { include: { travelerProfiles: true } }, seats: { include: { seat: { include: { coach: true } } } }, }, }, @@ -93,9 +93,41 @@ export class TicketsService { return { items: tickets.map((t: any) => { const iam = t.booking?.passenger?.iamUserId ? iamMap.get(t.booking.passenger.iamUserId) : undefined; + + // Extract phone from TravelerProfile notes JSON + let guestPhone = null; + let guestEmail = null; + const matchingProfile = t.booking?.passenger?.travelerProfiles?.find((tp: any) => tp.fullName === t.passengerName); + + // DEBUG: Log to see what we're getting + this.logger.debug(`Ticket ${t.id}: passengerName=${t.passengerName}, profiles count=${t.booking?.passenger?.travelerProfiles?.length || 0}, matchingProfile=${!!matchingProfile}`); + if (matchingProfile) { + this.logger.debug(`Matching profile notes: ${matchingProfile.notes}`); + } + + if (matchingProfile?.notes) { + try { + const notesData = JSON.parse(matchingProfile.notes); + guestPhone = notesData.phone || null; + guestEmail = notesData.email || null; + this.logger.debug(`Extracted from notes: phone=${guestPhone}, email=${guestEmail}`); + } catch (err) { + this.logger.error(`Failed to parse notes JSON: ${err}`); + } + } + + // Fallback to booking contact info if no match in TravelerProfile + if (!guestPhone) guestPhone = t.booking?.contactPhone; + if (!guestEmail) guestEmail = t.booking?.contactEmail; + + this.logger.debug(`Final values: phone=${guestPhone}, email=${guestEmail}`); + const passengerInfo = iam ? { fullName: iam.name?.en ?? iam.name?.am ?? null, email: iam.email, phone: iam.phone_number } - : { fullName: 'Guest', email: t.booking?.contactEmail, phone: null }; + : { fullName: 'Guest', email: guestEmail, phone: guestPhone }; + + this.logger.debug(`Final passenger info: ${JSON.stringify(passengerInfo)}`); + return { id: t.id, ticketNumber: t.barcodePayload, diff --git a/apps/edr-passenger-api/src/seed/edr-passenger.seed.ts b/apps/edr-passenger-api/src/seed/edr-passenger.seed.ts index 8413cadb2..cd178139a 100644 --- a/apps/edr-passenger-api/src/seed/edr-passenger.seed.ts +++ b/apps/edr-passenger-api/src/seed/edr-passenger.seed.ts @@ -11,7 +11,7 @@ export type PassengerSeedRole = { }; export const EDR_PASSENGER_APPLICATION = { - id: 'd2000001-0001-4000-8000-000000000001', + id: '921cd1a4-98a7-4601-bfb1-6fe19518be52', key: 'edr_passenger_app', name: { am: 'EDR Passenger App', diff --git a/apps/edr-passenger-api/src/seed/passenger-permissions.registry.ts b/apps/edr-passenger-api/src/seed/passenger-permissions.registry.ts index 9b06c812b..d6a804b3a 100644 --- a/apps/edr-passenger-api/src/seed/passenger-permissions.registry.ts +++ b/apps/edr-passenger-api/src/seed/passenger-permissions.registry.ts @@ -15,26 +15,26 @@ const perm = (id: string, key: string, en: string): PassengerPermissionSeed => ( }); export const PASSENGER_PERMISSIONS: PassengerPermissionSeed[] = [ - perm('c1000001-0001-4000-8000-000000000001', 'edr_passenger_app:bookings:view', 'View bookings'), - perm('c1000001-0001-4000-8000-000000000002', 'edr_passenger_app:bookings:manage', 'Manage bookings'), - perm('c1000001-0001-4000-8000-000000000003', 'edr_passenger_app:bookings:cancel', 'Cancel bookings'), - perm('c1000001-0001-4000-8000-000000000004', 'edr_passenger_app:passengers:view', 'View passengers'), - perm('c1000001-0001-4000-8000-000000000005', 'edr_passenger_app:passengers:manage', 'Manage passengers'), - perm('c1000001-0001-4000-8000-000000000006', 'edr_passenger_app:tickets:view', 'View tickets'), - perm('c1000001-0001-4000-8000-000000000007', 'edr_passenger_app:tickets:manage', 'Manage tickets'), - perm('c1000001-0001-4000-8000-000000000008', 'edr_passenger_app:payments:view_all', 'View all payments'), - perm('c1000001-0001-4000-8000-000000000009', 'edr_passenger_app:payments:refund', 'Refund payments'), - perm('c1000001-0001-4000-8000-00000000000a', 'edr_passenger_app:payments:manage_methods', 'Manage payment methods'), - perm('c1000001-0001-4000-8000-00000000000b', 'edr_passenger_app:reports:view', 'View reports'), - perm('c1000001-0001-4000-8000-00000000000c', 'edr_passenger_app:fraud:view', 'View fraud alerts'), - perm('c1000001-0001-4000-8000-00000000000d', 'edr_passenger_app:fraud:manage', 'Manage fraud rules'), - perm('c1000001-0001-4000-8000-00000000000e', 'edr_passenger_app:audit:view', 'View audit logs'), - perm('c1000001-0001-4000-8000-00000000000f', 'edr_passenger_app:agents:view', 'View agents'), - perm('c1000001-0001-4000-8000-000000000010', 'edr_passenger_app:agents:manage', 'Manage agents'), - perm('c1000001-0001-4000-8000-000000000011', 'edr_passenger_app:currencies:manage', 'Manage currencies'), - perm('c1000001-0001-4000-8000-000000000012', 'edr_passenger_app:notifications:send', 'Send notifications'), - perm('c1000001-0001-4000-8000-000000000013', 'edr_passenger_app:dashboard:view', 'View dashboard'), - perm('c1000001-0001-4000-8000-000000000014', 'edr_passenger_app:admin', 'Full admin access'), + perm('40f1b49c-c33d-4563-a6bb-9373eabbde9b', 'edr_passenger_app:bookings:view', 'View bookings'), + perm('62810ae5-315e-4ae5-8ed1-33cead51b95a', 'edr_passenger_app:bookings:manage', 'Manage bookings'), + perm('b593adf3-2060-48b0-b35d-ff9ff5d72bc4', 'edr_passenger_app:bookings:cancel', 'Cancel bookings'), + perm('566c968f-71f1-462d-9824-4b7cd33cecbb', 'edr_passenger_app:passengers:view', 'View passengers'), + perm('ff5d33a0-0fe7-427f-a065-46dd14ac1da0', 'edr_passenger_app:passengers:manage', 'Manage passengers'), + perm('326ec767-1da8-4c7e-b557-d4d2f9dd6d2c', 'edr_passenger_app:tickets:view', 'View tickets'), + perm('8ec5697f-d2d4-40a2-a365-ad624991a2ab', 'edr_passenger_app:tickets:manage', 'Manage tickets'), + perm('736aca18-6660-4865-9773-81a636f51fa0', 'edr_passenger_app:payments:view_all', 'View all payments'), + perm('44065042-b4af-4af2-b213-34a823f78be1', 'edr_passenger_app:payments:refund', 'Refund payments'), + perm('558f0172-ab9f-4d13-9477-4ca247d94f3c', 'edr_passenger_app:payments:manage_methods', 'Manage payment methods'), + perm('bf6cbd48-7a4a-46ec-91f3-0a23245da0a4', 'edr_passenger_app:reports:view', 'View reports'), + perm('53b49280-a272-4688-8345-e14fbedce50e', 'edr_passenger_app:fraud:view', 'View fraud alerts'), + perm('834f576c-afe2-41f4-9e6e-5f87b155fbf4', 'edr_passenger_app:fraud:manage', 'Manage fraud rules'), + perm('988b680d-df5a-4e79-9c01-c9441753ce1a', 'edr_passenger_app:audit:view', 'View audit logs'), + perm('38736897-545f-47dc-9531-7fc381478a1f', 'edr_passenger_app:agents:view', 'View agents'), + perm('30ada94a-cd15-4588-97bd-cd2f9d33ad7d', 'edr_passenger_app:agents:manage', 'Manage agents'), + perm('75b5ff62-a8e4-4331-b6e6-d53e1456d10e', 'edr_passenger_app:currencies:manage', 'Manage currencies'), + perm('4a47da9b-cf6e-4240-aff8-aadf01641c54', 'edr_passenger_app:notifications:send', 'Send notifications'), + perm('bfe3428f-8b85-4a36-87c6-33063b084bf3', 'edr_passenger_app:dashboard:view', 'View dashboard'), + perm('49fd28cd-5b58-4403-8e53-1df4b93cbbd2', 'edr_passenger_app:admin', 'Full admin access'), ]; export const PASSENGER_PERMISSION_KEYS = PASSENGER_PERMISSIONS.map((p) => p.key); diff --git a/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx b/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx index b4cf005e8..d841d144d 100644 --- a/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx @@ -185,7 +185,7 @@ function BookingsPageContent() { }, }, { - key: 'contact', label: 'Contact', + key: 'contact', label: 'Primary contact', render: (booking: any) => (