File

src/trips/trips.controller.ts

Prefix

trips

Index

Methods

Methods

Async autoSuggest
autoSuggest()
Decorators :
@Post('auto-suggest')
@Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'PLANNER', 'DISPATCHER')
@ApiOperation({summary: 'Generate auto-assignment suggestions for unassigned orders'})
Returns : unknown
Async cancel
cancel(orgId: string, id: string)
Decorators :
@Post(':id/cancel')
@Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'DISPATCHER')
@ApiOperation({summary: 'Cancel a trip and release vehicle/bay'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async complete
complete(orgId: string, id: string)
Decorators :
@Post(':id/complete')
@Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'DISPATCHER', 'DRIVER')
@ApiOperation({summary: 'Complete a trip'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async create
create(orgId: string, dto: CreateTripDto)
Decorators :
@Post()
@Roles(undefined)
@ApiOperation({summary: 'Create a new trip'})
Parameters :
Name Type Optional
orgId string No
dto CreateTripDto No
Returns : unknown
Async dispatch
dispatch(orgId: string, id: string)
Decorators :
@Post(':id/dispatch')
@Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'DISPATCHER', 'DRIVER')
@ApiOperation({summary: 'Dispatch a trip'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async findAll
findAll(orgId: string, role: string, userId: string, query: PaginationParams)
Decorators :
@Get()
@ApiPagination()
@ApiOperation({summary: 'List all trips (drivers only see their own)'})
Parameters :
Name Type Optional
orgId string No
role string No
userId string No
query PaginationParams No
Returns : unknown
Async findOne
findOne(orgId: string, id: string)
Decorators :
@Get(':id')
@ApiOperation({summary: 'Get trip by ID (org-scoped)'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async optimizeRoute
optimizeRoute(orgId: string, tripId: string)
Decorators :
@Post('optimize-route/:tripId')
@Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'PLANNER', 'DISPATCHER')
@ApiOperation({summary: 'Optimize stop sequence for a trip'})
Parameters :
Name Type Optional
orgId string No
tripId string No
Returns : unknown
Async remove
remove(orgId: string, id: string)
Decorators :
@Delete(':id')
@Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER')
@ApiOperation({summary: 'Delete a trip'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async update
update(orgId: string, userId: string, role: string, id: string, dto: UpdateTripDto)
Decorators :
@Patch(':id')
@Roles(undefined, 'DRIVER')
@ApiOperation({summary: 'Update a trip (org-scoped, drivers can change own status)'})
Parameters :
Name Type Optional
orgId string No
userId string No
role string No
id string No
dto UpdateTripDto No
Returns : unknown
import {
  Controller,
  Get,
  Post,
  Patch,
  Delete,
  Body,
  Param,
  Query,
  UseGuards,
  NotFoundException,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { TripsService } from './trips.service';
import { AutoAssignService } from './optimizer/auto-assign.service';
import { RouteOptimizer } from './optimizer/route.optimizer';
import { CreateTripDto } from './dto/create-trip.dto';
import { UpdateTripDto } from './dto/update-trip.dto';
import { CombinedAuthGuard } from '../auth/guards/combined-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { ApiPagination } from '../common/decorators/api-pagination.decorator';
import { PaginationParams } from '../common/utils/pagination.util';

const TRIP_WRITERS = [
  'SUPER_ADMIN',
  'ADMIN',
  'OPERATIONS_MANAGER',
  'PLANNER',
  'DISPATCHER',
] as const;

@ApiTags('Trips')
@ApiBearerAuth()
@UseGuards(CombinedAuthGuard, RolesGuard)
@Controller('trips')
export class TripsController {
  constructor(
    private readonly tripsService: TripsService,
    private readonly autoAssignService: AutoAssignService,
    private readonly routeOptimizer: RouteOptimizer,
  ) {}

  @Post()
  @Roles(...TRIP_WRITERS)
  @ApiOperation({ summary: 'Create a new trip' })
  async create(
    @CurrentUser('organizationId') orgId: string,
    @Body() dto: CreateTripDto,
  ) {
    return this.tripsService.create(orgId, dto);
  }

  @Get()
  @ApiPagination()
  @ApiOperation({ summary: 'List all trips (drivers only see their own)' })
  async findAll(
    @CurrentUser('organizationId') orgId: string,
    @CurrentUser('role') role: string,
    @CurrentUser('id') userId: string,
    @Query() query: PaginationParams,
  ) {
    return this.tripsService.findAll(orgId, query, role === 'DRIVER' ? userId : undefined);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get trip by ID (org-scoped)' })
  async findOne(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
  ) {
    return this.tripsService.findOne(orgId, id);
  }

  @Patch(':id')
  // Drivers also need PATCH access so they can flip the trip status
  // when they tap "Start Driving" on the mobile app. The service
  // enforces that a driver can only update their own trip and may only
  // change the `status` field — never reassign vehicles or dates.
  @Roles(...TRIP_WRITERS, 'DRIVER')
  @ApiOperation({ summary: 'Update a trip (org-scoped, drivers can change own status)' })
  async update(
    @CurrentUser('organizationId') orgId: string,
    @CurrentUser('id') userId: string,
    @CurrentUser('role') role: string,
    @Param('id') id: string,
    @Body() dto: UpdateTripDto,
  ) {
    return this.tripsService.update(orgId, id, dto, { userId, role });
  }

  @Post(':id/dispatch')
  @Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'DISPATCHER', 'DRIVER')
  @ApiOperation({ summary: 'Dispatch a trip' })
  async dispatch(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
  ) {
    return this.tripsService.dispatch(orgId, id);
  }

  @Post(':id/complete')
  @Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'DISPATCHER', 'DRIVER')
  @ApiOperation({ summary: 'Complete a trip' })
  async complete(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
  ) {
    return this.tripsService.complete(orgId, id);
  }

  @Post(':id/cancel')
  @Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'DISPATCHER')
  @ApiOperation({ summary: 'Cancel a trip and release vehicle/bay' })
  async cancel(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
  ) {
    return this.tripsService.cancel(orgId, id);
  }

  @Delete(':id')
  @Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER')
  @ApiOperation({ summary: 'Delete a trip' })
  async remove(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
  ) {
    return this.tripsService.remove(orgId, id);
  }

  @Post('auto-suggest')
  @Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'PLANNER', 'DISPATCHER')
  @ApiOperation({ summary: 'Generate auto-assignment suggestions for unassigned orders' })
  async autoSuggest() {
    return this.autoAssignService.generateSuggestions();
  }

  @Post('optimize-route/:tripId')
  @Roles('SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER', 'PLANNER', 'DISPATCHER')
  @ApiOperation({ summary: 'Optimize stop sequence for a trip' })
  async optimizeRoute(
    @CurrentUser('organizationId') orgId: string,
    @Param('tripId') tripId: string,
  ) {
    const trip = await this.tripsService.findOne(orgId, tripId);
    if (!trip) {
      throw new NotFoundException(`Trip ${tripId} not found`);
    }

    // Build stops list from trip stops that have coordinates
    const stops = trip.stops
      .filter((s) => s.lat != null && s.lng != null)
      .map((s) => ({
        id: s.id,
        lat: s.lat!,
        lng: s.lng!,
        type: s.stopType,
        address: s.address || '',
      }));

    if (stops.length === 0) {
      return { stops: [], totalDistanceKm: 0, estimatedDurationMin: 0 };
    }

    // Use vehicle's current position as start if available
    const startLat = trip.vehicle?.currentLat ?? undefined;
    const startLng = trip.vehicle?.currentLng ?? undefined;

    return this.routeOptimizer.optimizeRoute(stops, startLat, startLng);
  }
}

results matching ""

    No results matching ""