mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
Initial commit of edr-passenger-api alpha version
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||
import { SearchService } from './search.service';
|
||||
import { SearchTripsDto, FareQuoteDto } from './search.dto';
|
||||
|
||||
@ApiTags('Search')
|
||||
@Controller('search')
|
||||
export class SearchController {
|
||||
constructor(private service: SearchService) {}
|
||||
@Post() @ApiOperation({ summary: 'Search trips' }) searchTrips(@Body() dto: SearchTripsDto) { return this.service.searchTrips(dto); }
|
||||
@Post('fare-quote')@ApiOperation({ summary: 'Get fare quote' }) getFareQuote(@Body() dto: FareQuoteDto) { return this.service.getFareQuote(dto); }
|
||||
}
|
||||
18
apps/edr-passenger-api/src/modules/search/search.dto.ts
Normal file
18
apps/edr-passenger-api/src/modules/search/search.dto.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { IsString, IsDateString, IsInt, IsOptional, Min } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class SearchTripsDto {
|
||||
@ApiProperty({ example: 'st_ADD' }) @IsString() originStationId: string;
|
||||
@ApiProperty({ example: 'st_DJI' }) @IsString() destinationStationId: string;
|
||||
@ApiProperty({ example: '2026-05-11' }) @IsDateString() date: string;
|
||||
@ApiPropertyOptional({ example: 1 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) passengers?: number;
|
||||
}
|
||||
|
||||
export class FareQuoteDto {
|
||||
@ApiProperty() @IsString() tripId: string;
|
||||
@ApiProperty({ example: 'ECONOMY' }) @IsString() serviceClass: string;
|
||||
@ApiPropertyOptional({ example: 1 }) @IsOptional() @Type(() => Number) @IsInt() @Min(1) passengerCount?: number;
|
||||
@ApiPropertyOptional({ example: 'WEEKEND15' }) @IsOptional() @IsString() promoCode?: string;
|
||||
@ApiPropertyOptional({ example: 450 }) @IsOptional() @Type(() => Number) @IsInt() loyaltyRedemptionPoints?: number;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SearchController } from './search.controller';
|
||||
import { SearchService } from './search.service';
|
||||
|
||||
@Module({ controllers: [SearchController], providers: [SearchService], exports: [SearchService] })
|
||||
export class SearchModule {}
|
||||
50
apps/edr-passenger-api/src/modules/search/search.service.ts
Normal file
50
apps/edr-passenger-api/src/modules/search/search.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
import { SearchTripsDto, FareQuoteDto } from './search.dto';
|
||||
|
||||
const POINTS_TO_MINOR = 10;
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async searchTrips(dto: SearchTripsDto) {
|
||||
const date = new Date(dto.date), nextDay = new Date(date.getTime() + 86400000);
|
||||
const trips = await this.prisma.trip.findMany({
|
||||
where: { originStationId: dto.originStationId, destinationStationId: dto.destinationStationId, departureAt: { gte: date, lt: nextDay }, status: { in: ['SCHEDULED', 'BOARDING'] } },
|
||||
include: { service: true, originStation: true, destinationStation: true, coaches: { include: { seats: true } } },
|
||||
});
|
||||
return trips.map((trip) => {
|
||||
const seatsByClass = (cls: string) => trip.coaches.filter((c) => c.serviceClass === cls).flatMap((c) => c.seats);
|
||||
const avail = (cls: string) => seatsByClass(cls).filter((s) => s.status === 'AVAILABLE').length;
|
||||
return {
|
||||
id: trip.id,
|
||||
number: trip.service.number,
|
||||
origin: { id: trip.originStation.id, code: trip.originStation.code, name: trip.originStation.name, city: trip.originStation.city },
|
||||
destination: { id: trip.destinationStation.id, code: trip.destinationStation.code, name: trip.destinationStation.name, city: trip.destinationStation.city },
|
||||
departureAt: trip.departureAt, arrivalAt: trip.arrivalAt, status: trip.status,
|
||||
availability: { ECONOMY: avail('ECONOMY'), BUSINESS: avail('BUSINESS'), FIRST: avail('FIRST') },
|
||||
fares: { ECONOMY: this.defaultFare('ECONOMY') / 100, BUSINESS: this.defaultFare('BUSINESS') / 100, FIRST: this.defaultFare('FIRST') / 100 },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getFareQuote(dto: FareQuoteDto) {
|
||||
const trip = await this.prisma.trip.findUnique({ where: { id: dto.tripId } });
|
||||
if (!trip) throw new NotFoundException('Trip not found');
|
||||
const count = dto.passengerCount ?? 1;
|
||||
const baseFareMinor = this.defaultFare(dto.serviceClass) * count;
|
||||
let discountMinor = 0;
|
||||
if (dto.promoCode) {
|
||||
const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } });
|
||||
if (promo?.active && promo.validUntil > new Date()) discountMinor = promo.percentOff ? Math.round(baseFareMinor * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
|
||||
}
|
||||
const loyaltyMinor = (dto.loyaltyRedemptionPoints ?? 0) * POINTS_TO_MINOR;
|
||||
const taxesMinor = Math.round(baseFareMinor * 0.05);
|
||||
return { tripId: dto.tripId, serviceClass: dto.serviceClass, passengerCount: count, baseFareMinor, discountMinor, loyaltyRedemptionMinor: loyaltyMinor, taxesFeesMinor: taxesMinor, totalMinor: Math.max(0, baseFareMinor - discountMinor - loyaltyMinor + taxesMinor), currency: 'ETB' };
|
||||
}
|
||||
|
||||
private defaultFare(serviceClass: string): number {
|
||||
return ({ ECONOMY: 45000, BUSINESS: 90000, FIRST: 135000 } as any)[serviceClass] ?? 45000;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user