Merge branch 'freight/develop' of github.com:Tria-plc/edr-platform into freight_feature/profile

This commit is contained in:
marshal
2026-06-24 10:58:57 +03:00
32 changed files with 2342 additions and 978 deletions

View File

@@ -55,6 +55,7 @@ export class CreateFirstMileDto {
nullable: true,
})
@IsOptional()
@Transform(({ value }) => (value === '' ? undefined : value))
@IsUUID()
vehicleId?: string | null;
}

View File

@@ -55,6 +55,13 @@ export class FirstMileController {
return this.firstMileService.findById(id);
}
@Post('accept/:reference')
@TrainSchedulingManage()
@ApiOperation({ summary: 'Accept a paid booking and create a first-mile leg' })
acceptBooking(@Param('reference') reference: string) {
return this.firstMileService.acceptBooking(reference);
}
@Post()
@TrainSchedulingManage()
@ApiOperation({ summary: 'Create a first-mile leg' })

View File

@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingsModule } from '../bookings/bookings.module';
import { FirstMile } from './entities/first-mile.entity';
import { FirstMileController } from './first-mile.controller';
import { FirstMileRepository } from './first-mile.repository';
import { FirstMileService } from './first-mile.service';
@Module({
imports: [TypeOrmModule.forFeature([FirstMile])],
imports: [TypeOrmModule.forFeature([FirstMile]), BookingsModule],
controllers: [FirstMileController],
providers: [FirstMileRepository, FirstMileService],
exports: [FirstMileRepository, FirstMileService],

View File

@@ -1,6 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { FindOptionsWhere } from 'typeorm';
import { BookingsRepository } from '../bookings/bookings.repository';
import { CreateFirstMileDto } from './dto/create-first-mile.dto';
import { UpdateFirstMileDto } from './dto/update-first-mile.dto';
import { FirstMile, FirstMileStatus } from './entities/first-mile.entity';
@@ -25,7 +26,32 @@ const SORTABLE_FIELDS: (keyof FirstMile)[] = [
@Injectable()
export class FirstMileService {
constructor(private readonly firstMileRepository: FirstMileRepository) {}
constructor(
private readonly firstMileRepository: FirstMileRepository,
private readonly bookingsRepository: BookingsRepository,
) {}
/**
* Look up a booking by its human-readable reference and confirm it has been
* paid before any first-mile work proceeds. Throws if the reference is
* unknown or the booking has not reached PAID status.
*/
async acceptBooking(bookingReference: string): Promise<FirstMile | null> {
const booking = await this.bookingsRepository.findByReference(bookingReference);
if (!booking) {
return null;
}
if (booking.paymentStatus !== 'PAID') {
return null;
}
return this.create({
bookingId: booking.id,
advancedPayment: 0,
});
}
async findAll(filter: FirstMileListFilter = {}): Promise<{
data: FirstMile[];
@@ -45,7 +71,10 @@ export class FirstMileService {
const [data, total] = await this.firstMileRepository.findAndCount({
where,
relations: { booking: true, vehicle: true },
relations: {
booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true },
vehicle: true,
},
order: { [sortBy]: sortOrder },
skip: (page - 1) * pageSize,
take: pageSize,
@@ -64,7 +93,10 @@ export class FirstMileService {
async findById(id: string): Promise<FirstMile> {
const record = await this.firstMileRepository.findById(id, {
relations: { booking: true, vehicle: true },
relations: {
booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true },
vehicle: true,
},
});
if (!record) {

View File

@@ -55,6 +55,7 @@ export class CreateLastMileDto {
nullable: true,
})
@IsOptional()
@Transform(({ value }) => (value === '' ? undefined : value))
@IsUUID()
vehicleId?: string | null;
}

View File

@@ -55,6 +55,13 @@ export class LastMileController {
return this.lastMileService.findById(id);
}
@Post('accept/:reference')
@TrainSchedulingManage()
@ApiOperation({ summary: 'Accept a paid booking and create a last-mile leg' })
acceptBooking(@Param('reference') reference: string) {
return this.lastMileService.acceptBooking(reference);
}
@Post()
@TrainSchedulingManage()
@ApiOperation({ summary: 'Create a last-mile leg' })

View File

@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingsModule } from '../bookings/bookings.module';
import { LastMile } from './entities/last-mile.entity';
import { LastMileController } from './last-mile.controller';
import { LastMileRepository } from './last-mile.repository';
import { LastMileService } from './last-mile.service';
@Module({
imports: [TypeOrmModule.forFeature([LastMile])],
imports: [TypeOrmModule.forFeature([LastMile]), BookingsModule],
controllers: [LastMileController],
providers: [LastMileRepository, LastMileService],
exports: [LastMileRepository, LastMileService],

View File

@@ -1,6 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { FindOptionsWhere } from 'typeorm';
import { BookingsRepository } from '../bookings/bookings.repository';
import { CreateLastMileDto } from './dto/create-last-mile.dto';
import { UpdateLastMileDto } from './dto/update-last-mile.dto';
import { LastMile, LastMileStatus } from './entities/last-mile.entity';
@@ -25,7 +26,29 @@ const SORTABLE_FIELDS: (keyof LastMile)[] = [
@Injectable()
export class LastMileService {
constructor(private readonly lastMileRepository: LastMileRepository) {}
constructor(
private readonly lastMileRepository: LastMileRepository,
private readonly bookingsRepository: BookingsRepository,
) {}
async acceptBooking(bookingReference: string): Promise<LastMile> {
const booking = await this.bookingsRepository.findByReference(bookingReference);
if (!booking) {
throw new NotFoundException(`Booking ${bookingReference} not found`);
}
if (booking.paymentStatus !== 'PAID') {
throw new BadRequestException(
`Booking ${bookingReference} is not paid (payment status: ${booking.paymentStatus})`,
);
}
return this.create({
bookingId: booking.id,
advancedPayment: booking.totalAmount,
});
}
async findAll(filter: LastMileListFilter = {}): Promise<{
data: LastMile[];
@@ -45,7 +68,10 @@ export class LastMileService {
const [data, total] = await this.lastMileRepository.findAndCount({
where,
relations: { booking: true, vehicle: true },
relations: {
booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true },
vehicle: true,
},
order: { [sortBy]: sortOrder },
skip: (page - 1) * pageSize,
take: pageSize,
@@ -64,7 +90,10 @@ export class LastMileService {
async findById(id: string): Promise<LastMile> {
const record = await this.lastMileRepository.findById(id, {
relations: { booking: true, vehicle: true },
relations: {
booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true },
vehicle: true,
},
});
if (!record) {