Package passenger numbers and pricing updates

This commit is contained in:
Stephanos A
2026-07-05 18:14:42 +03:00
parent 9e77ca7865
commit afd30c36a0
20 changed files with 537 additions and 105 deletions

View File

@@ -0,0 +1,49 @@
import {
Controller, Get, Post, Put, Delete,
Param, Body, Query, UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery } from '@nestjs/swagger';
import { IamGuard } from '../../common/iam-adapter';
import { Roles } from '../../common/roles.decorator';
import { SegmentFareService } from './segment-fare.service';
import { CreateSegmentFareDto, UpdateSegmentFareDto } from './segment-fare.dto';
@ApiTags('Admin Segment Fares')
@Controller('admin/segment-fares')
@ApiBearerAuth('IAM-auth')
@UseGuards(IamGuard)
@Roles('ADMIN', 'SUPERVISOR')
export class SegmentFareController {
constructor(private readonly service: SegmentFareService) {}
@Get()
@ApiOperation({ summary: 'List all segment fare rules, optionally filtered by route' })
@ApiQuery({ name: 'routeId', required: false })
findAll(@Query('routeId') routeId?: string) {
return this.service.findAll(routeId);
}
@Get(':id')
@ApiOperation({ summary: 'Get a single segment fare rule' })
findOne(@Param('id') id: string) {
return this.service.findOne(id);
}
@Post()
@ApiOperation({ summary: 'Create a segment fare rule' })
create(@Body() dto: CreateSegmentFareDto) {
return this.service.create(dto);
}
@Put(':id')
@ApiOperation({ summary: 'Update a segment fare rule' })
update(@Param('id') id: string, @Body() dto: UpdateSegmentFareDto) {
return this.service.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a segment fare rule' })
remove(@Param('id') id: string) {
return this.service.remove(id);
}
}

View File

@@ -0,0 +1,67 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsString, IsInt, IsOptional, IsDateString, IsIn, Min,
} from 'class-validator';
export class CreateSegmentFareDto {
@ApiProperty({ example: 'route-uuid' })
@IsString()
routeId: string;
@ApiProperty({ example: 1 })
@IsInt() @Min(0)
originStopSequence: number;
@ApiProperty({ example: 5 })
@IsInt() @Min(1)
destinationStopSequence: number;
@ApiProperty({ example: 'seat-class-uuid' })
@IsString()
seatClassId: string;
@ApiProperty({ example: 35000, description: 'Base fare in minor units (e.g. 350.00 ETB = 35000)' })
@IsInt() @Min(0)
baseFareMinor: number;
@ApiPropertyOptional({ example: 'LOCAL', enum: ['LOCAL', 'INTERNATIONAL'] })
@IsOptional()
@IsIn(['LOCAL', 'INTERNATIONAL'])
nationality?: string;
@ApiPropertyOptional({ example: 'ETB', default: 'ETB' })
@IsOptional()
@IsString()
currency?: string;
@ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
@IsDateString()
validFrom: string;
@ApiPropertyOptional({ example: '2026-12-31T23:59:59.000Z' })
@IsOptional()
@IsDateString()
validUntil?: string;
}
export class UpdateSegmentFareDto {
@ApiPropertyOptional({ example: 40000 })
@IsOptional()
@IsInt() @Min(0)
baseFareMinor?: number;
@ApiPropertyOptional({ example: 'ETB' })
@IsOptional()
@IsString()
currency?: string;
@ApiPropertyOptional({ example: '2025-06-01T00:00:00.000Z' })
@IsOptional()
@IsDateString()
validFrom?: string;
@ApiPropertyOptional({ example: '2026-12-31T23:59:59.000Z' })
@IsOptional()
@IsDateString()
validUntil?: string;
}

View File

@@ -0,0 +1,62 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CreateSegmentFareDto, UpdateSegmentFareDto } from './segment-fare.dto';
@Injectable()
export class SegmentFareService {
constructor(private readonly prisma: PrismaService) {}
findAll(routeId?: string) {
return this.prisma.segmentFareRule.findMany({
where: routeId ? { routeId } : undefined,
include: { seatClass: true, route: { select: { id: true, code: true, name: true } } },
orderBy: [{ routeId: 'asc' }, { originStopSequence: 'asc' }, { destinationStopSequence: 'asc' }],
});
}
async findOne(id: string) {
const rule = await this.prisma.segmentFareRule.findUnique({
where: { id },
include: { seatClass: true, route: { select: { id: true, code: true, name: true } } },
});
if (!rule) throw new NotFoundException(`SegmentFareRule ${id} not found`);
return rule;
}
create(dto: CreateSegmentFareDto) {
return this.prisma.segmentFareRule.create({
data: {
routeId: dto.routeId,
originStopSequence: dto.originStopSequence,
destinationStopSequence: dto.destinationStopSequence,
seatClassId: dto.seatClassId,
baseFareMinor: dto.baseFareMinor,
nationality: dto.nationality ?? null,
currency: dto.currency ?? 'ETB',
validFrom: new Date(dto.validFrom),
validUntil: dto.validUntil ? new Date(dto.validUntil) : null,
},
include: { seatClass: true },
});
}
async update(id: string, dto: UpdateSegmentFareDto) {
await this.findOne(id);
return this.prisma.segmentFareRule.update({
where: { id },
data: {
...(dto.baseFareMinor !== undefined && { baseFareMinor: dto.baseFareMinor }),
...(dto.currency !== undefined && { currency: dto.currency }),
...(dto.validFrom !== undefined && { validFrom: new Date(dto.validFrom) }),
...(dto.validUntil !== undefined && { validUntil: new Date(dto.validUntil) }),
},
include: { seatClass: true },
});
}
async remove(id: string) {
await this.findOne(id);
await this.prisma.segmentFareRule.delete({ where: { id } });
return { deleted: true };
}
}

View File

@@ -3,20 +3,24 @@ import { SegmentsService } from './segments.service';
import { EnhancedSeatsService } from './enhanced-seats.service';
import { TripProgressService } from './trip-progress.service';
import { SegmentSeatsController } from './segments.controller';
import { SegmentFareController } from './segment-fare.controller';
import { SegmentFareService } from './segment-fare.service';
import { PrismaService } from '../../common/prisma.service';
@Module({
controllers: [SegmentSeatsController],
controllers: [SegmentSeatsController, SegmentFareController],
providers: [
SegmentsService,
EnhancedSeatsService,
TripProgressService,
PrismaService
SegmentFareService,
PrismaService,
],
exports: [
SegmentsService,
EnhancedSeatsService,
TripProgressService
]
TripProgressService,
SegmentFareService,
],
})
export class SegmentsModule {}