mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
UAT issues resolution
This commit is contained in:
@@ -32,10 +32,6 @@ export class CurrenciesService {
|
||||
async createCurrency(dto: CreateCurrencyDto) {
|
||||
const { code, name, symbol, baseCurrencyCode = 'ETB', exchangeRate } = dto;
|
||||
|
||||
if (!['ETB', 'USD', 'DJF'].includes(code.toUpperCase())) {
|
||||
throw new BadRequestException('Unsupported currency code');
|
||||
}
|
||||
|
||||
if (exchangeRate <= 0) {
|
||||
throw new BadRequestException('Exchange rate must be positive');
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import { Type } from 'class-transformer';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class CreatePriceTierDto {
|
||||
@ApiPropertyOptional({ description: 'SeatClass ID to link this tier to a specific seat class' })
|
||||
@IsOptional() @IsUUID() seatClassId?: string;
|
||||
|
||||
@ApiProperty({ example: 'HSC' })
|
||||
@IsString() seatType: string;
|
||||
|
||||
@@ -31,6 +34,7 @@ export class UpdateInquiryStatusDto {
|
||||
}
|
||||
|
||||
export class UpdatePriceTierDto {
|
||||
@ApiPropertyOptional() @IsOptional() @IsUUID() seatClassId?: string;
|
||||
@ApiPropertyOptional() @IsOptional() @IsString() seatType?: string;
|
||||
@ApiPropertyOptional() @IsOptional() @IsString() label?: string;
|
||||
@ApiPropertyOptional() @IsOptional() @IsInt() @Min(0) priceMinor?: number;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { GuestBookingService } from '../bookings/guest-booking.service';
|
||||
|
||||
/** Package-specific fare rules */
|
||||
const PKG_MAX_ADULTS = 5;
|
||||
const PKG_MAX_CHILDREN = 2;
|
||||
const PKG_CHILDREN_PER_ADULT = 2; // 2 children allowed per adult
|
||||
const PKG_CHILD_FARE_RATIO = 0.1;
|
||||
|
||||
function calculatePackageFareBreakdown(
|
||||
@@ -68,7 +68,8 @@ export class PackagesService {
|
||||
|
||||
if (adultCount < 1) throw new BadRequestException('At least one adult passenger required');
|
||||
if (adultCount > PKG_MAX_ADULTS) throw new BadRequestException(`Maximum ${PKG_MAX_ADULTS} adults allowed per package booking`);
|
||||
if (childCount > PKG_MAX_CHILDREN) throw new BadRequestException(`Maximum ${PKG_MAX_CHILDREN} children allowed per package booking`);
|
||||
const maxChildren = adultCount * PKG_CHILDREN_PER_ADULT;
|
||||
if (childCount > maxChildren) throw new BadRequestException(`Maximum ${PKG_CHILDREN_PER_ADULT} children per adult (${maxChildren} for ${adultCount} adult${adultCount !== 1 ? 's' : ''}) allowed per package booking`);
|
||||
|
||||
const passengerCount = adultCount + childCount;
|
||||
const remaining = tier.availableSeats - tier.bookedSeats;
|
||||
@@ -81,14 +82,17 @@ export class PackagesService {
|
||||
);
|
||||
|
||||
// Resolve the seatClassId and coachTypeId that matches this tier's seatType from the outbound schedule coaches
|
||||
let seatClassId: string | null = null;
|
||||
let seatClassId: string | null = tier.seatClassId ?? null;
|
||||
let seatClassName: string | null = null;
|
||||
let coachTypeId: string | null = null;
|
||||
for (const a of pkg.outboundSchedule.coachAssignments) {
|
||||
const sc = a.coach.coachType?.seatClasses?.find(
|
||||
(s: any) => s.name.toLowerCase().includes(tier.seatType.toLowerCase()) ||
|
||||
tier.seatType.toLowerCase().includes(s.name.toLowerCase()),
|
||||
);
|
||||
if (sc) { seatClassId = sc.id; coachTypeId = a.coach.coachTypeId ?? a.coach.coachType?.id ?? null; break; }
|
||||
const sc = seatClassId
|
||||
? a.coach.coachType?.seatClasses?.find((s: any) => s.id === seatClassId)
|
||||
: a.coach.coachType?.seatClasses?.find(
|
||||
(s: any) => s.name.toLowerCase().includes(tier.seatType.toLowerCase()) ||
|
||||
tier.seatType.toLowerCase().includes(s.name.toLowerCase()),
|
||||
);
|
||||
if (sc) { seatClassId = sc.id; seatClassName = sc.name; coachTypeId = a.coach.coachTypeId ?? a.coach.coachType?.id ?? null; break; }
|
||||
}
|
||||
if (!coachTypeId && pkg.outboundSchedule.coachAssignments.length > 0) {
|
||||
const first = pkg.outboundSchedule.coachAssignments[0];
|
||||
@@ -102,6 +106,7 @@ export class PackagesService {
|
||||
tierLabel: tier.label,
|
||||
seatType: tier.seatType,
|
||||
seatClassId,
|
||||
seatClassName,
|
||||
coachTypeId,
|
||||
adultCount,
|
||||
childCount,
|
||||
@@ -111,7 +116,7 @@ export class PackagesService {
|
||||
pricePerChildMinor: childFareMinor,
|
||||
childFareNote: `Children pay ${PKG_CHILD_FARE_RATIO * 100}% of adult fare`,
|
||||
maxAdults: PKG_MAX_ADULTS,
|
||||
maxChildren: PKG_MAX_CHILDREN,
|
||||
maxChildren: adultCount * PKG_CHILDREN_PER_ADULT,
|
||||
totalMinor,
|
||||
currency: tier.currency,
|
||||
remainingSeats: remaining,
|
||||
@@ -203,7 +208,7 @@ export class PackagesService {
|
||||
const pkg = await this.prisma.travelPackage.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
priceTiers: true,
|
||||
priceTiers: { include: { seatClass: { include: { coachType: true } } } },
|
||||
outboundSchedule: {
|
||||
include: {
|
||||
originStation: true,
|
||||
@@ -369,7 +374,8 @@ export class PackagesService {
|
||||
|
||||
if (adultCount < 1) throw new BadRequestException('At least one adult passenger required');
|
||||
if (adultCount > PKG_MAX_ADULTS) throw new BadRequestException(`Maximum ${PKG_MAX_ADULTS} adults allowed per package booking`);
|
||||
if (childCount > PKG_MAX_CHILDREN) throw new BadRequestException(`Maximum ${PKG_MAX_CHILDREN} children allowed per package booking`);
|
||||
const maxChildrenBook = adultCount * PKG_CHILDREN_PER_ADULT;
|
||||
if (childCount > maxChildrenBook) throw new BadRequestException(`Maximum ${PKG_CHILDREN_PER_ADULT} children per adult (${maxChildrenBook} for ${adultCount} adult${adultCount !== 1 ? 's' : ''}) allowed per package booking`);
|
||||
|
||||
const passengerCount = adultCount + childCount;
|
||||
const remaining = tier.availableSeats - tier.bookedSeats;
|
||||
@@ -398,6 +404,8 @@ export class PackagesService {
|
||||
contactPhone: dto.contactPhone,
|
||||
promoCode: dto.promoCode,
|
||||
passengerCount,
|
||||
adultCount,
|
||||
childCount,
|
||||
totalMinor,
|
||||
currency: 'ETB',
|
||||
displayCurrency,
|
||||
|
||||
@@ -59,6 +59,7 @@ export class UpdateScheduleDto {
|
||||
@ApiPropertyOptional({ example: '2026-06-15T20:00:00Z', description: 'Scheduled arrival at the last stop (destination)' }) @IsOptional() @IsDateString() arrivalAt?: string;
|
||||
@ApiPropertyOptional({ enum: TripStatus, example: TripStatus.SCHEDULED }) @IsOptional() @IsEnum(TripStatus) status?: TripStatus;
|
||||
@ApiPropertyOptional({ type: Array, description: 'List of coaches to assign' }) @IsOptional() @IsArray() coaches?: Array<{ coachId: string; positionNumber: number }>;
|
||||
@ApiPropertyOptional({ example: false, description: 'Exclude from public search (reserved for packages)' }) @IsOptional() isPackageOnly?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateStopTimeDto {
|
||||
|
||||
@@ -632,6 +632,7 @@ export class SchedulesService {
|
||||
}
|
||||
|
||||
if (dto.status) updateData.status = dto.status;
|
||||
if (dto.isPackageOnly !== undefined) updateData.isPackageOnly = dto.isPackageOnly;
|
||||
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await this.prisma.trainSchedule.update({ where: { id }, data: updateData });
|
||||
|
||||
@@ -157,6 +157,7 @@ export class SearchService {
|
||||
const schedules = await this.prisma.trainSchedule.findMany({
|
||||
where: {
|
||||
status: { in: ['SCHEDULED', 'BOARDING'] },
|
||||
isPackageOnly: false,
|
||||
OR: [
|
||||
{ departureAt: { gte: windowStart, lt: requestedDate } },
|
||||
{ departureAt: { gte: requestedNextDay < now ? now : requestedNextDay, lt: windowEnd } },
|
||||
@@ -192,6 +193,7 @@ export class SearchService {
|
||||
const schedules = await this.prisma.trainSchedule.findMany({
|
||||
where: {
|
||||
status: { in: ['SCHEDULED', 'BOARDING'] },
|
||||
isPackageOnly: false,
|
||||
departureAt: { gte: date < now ? now : date, lt: nextDay },
|
||||
stopTimes: { some: { stationId: originStationId } },
|
||||
},
|
||||
@@ -230,6 +232,7 @@ export class SearchService {
|
||||
this.prisma.trainSchedule.findMany({
|
||||
where: {
|
||||
status: { in: ['SCHEDULED', 'BOARDING'] },
|
||||
isPackageOnly: false,
|
||||
departureAt: { gte: dayStart, lt: dayEnd },
|
||||
stopTimes: { some: { stationId: originStationId } },
|
||||
},
|
||||
@@ -238,6 +241,7 @@ export class SearchService {
|
||||
this.prisma.trainSchedule.findMany({
|
||||
where: {
|
||||
status: { in: ['SCHEDULED', 'BOARDING'] },
|
||||
isPackageOnly: false,
|
||||
departureAt: { gte: dayStart, lt: leg2WindowEnd },
|
||||
},
|
||||
include: SCHEDULE_INCLUDE,
|
||||
|
||||
Reference in New Issue
Block a user