File

src/orders/orders.controller.ts

Prefix

orders

Index

Methods

Methods

Async assignVehicle
assignVehicle(id: string, orgId: string, userId: string, body: literal type)
Decorators :
@Patch(':id/assign-vehicle')
@Roles(undefined)
@ApiOperation({summary: 'Manually assign a vehicle to an order and create a trip'})
Parameters :
Name Type Optional
id string No
orgId string No
userId string No
body literal type No
Returns : unknown
Async bulkCreate
bulkCreate(orgId: string, userId: string, orders: CreateOrderDto[])
Decorators :
@Post('bulk')
@Roles(undefined)
@ApiOperation({summary: 'Bulk create orders'})
Parameters :
Name Type Optional
orgId string No
userId string No
orders CreateOrderDto[] No
Returns : unknown
Async bulkSendToJobs
bulkSendToJobs(orgId: string, userId: string)
Decorators :
@Post('bulk-send-to-jobs')
@Roles(undefined)
@ApiOperation({summary: 'Send all eligible imported orders to Jobs — rejects orders with unresolved missing data'})
Parameters :
Name Type Optional
orgId string No
userId string No
Returns : unknown
Async confirmLoading
confirmLoading(id: string, userId: string, orgId: string, body: literal type)
Decorators :
@Patch(':id/confirm-loading')
@Roles(undefined, 'DRIVER')
@ApiOperation({summary: 'Confirm loading — requires GPS location at the loading bay'})
Parameters :
Name Type Optional
id string No
userId string No
orgId string No
body literal type No
Returns : unknown
Async create
create(orgId: string, userId: string, dto: CreateOrderDto)
Decorators :
@Post()
@Roles(undefined)
@ApiOperation({summary: 'Create a new order'})
Parameters :
Name Type Optional
orgId string No
userId string No
dto CreateOrderDto No
Returns : unknown
Async findAll
findAll(orgId: string, filters: OrderFilterDto)
Decorators :
@Get()
@ApiOperation({summary: 'List orders with filtering and pagination'})
Parameters :
Name Type Optional
orgId string No
filters OrderFilterDto No
Returns : unknown
Async findOne
findOne(orgId: string, id: string)
Decorators :
@Get(':id')
@ApiOperation({summary: 'Get order by ID (org-scoped)'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async getJobs
getJobs(orgId: string)
Decorators :
@Get('jobs/list')
@ApiOperation({summary: 'List orders in job stage (VALIDATED or READY)'})
Parameters :
Name Type Optional
orgId string No
Returns : unknown
Async getStats
getStats(orgId: string)
Decorators :
@Get('stats')
@ApiOperation({summary: 'Get order statistics by status'})
Parameters :
Name Type Optional
orgId string No
Returns : unknown
Async remove
remove(orgId: string, id: string)
Decorators :
@Delete(':id')
@Roles(undefined)
@ApiOperation({summary: 'Delete an order (blocked when on an active trip)'})
Parameters :
Name Type Optional
orgId string No
id string No
Returns : unknown
Async sendToJobs
sendToJobs(orgId: string, id: string, userId: string)
Decorators :
@Post(':id/send-to-jobs')
@Roles(undefined)
@ApiOperation({summary: 'Send order to Jobs board — only if all missing fields are filled or waived'})
Parameters :
Name Type Optional
orgId string No
id string No
userId string No
Returns : unknown
Async update
update(orgId: string, id: string, userId: string, role: string, dto: UpdateOrderDto)
Decorators :
@Patch(':id')
@Roles(undefined, 'DRIVER')
@ApiOperation({summary: 'Update an order (org-scoped, drivers can change own status)'})
Parameters :
Name Type Optional
orgId string No
id string No
userId string No
role string No
dto UpdateOrderDto No
Returns : unknown
Async updateJobStatus
updateJobStatus(orgId: string, id: string, userId: string, body: literal type)
Decorators :
@Patch(':id/job-status')
@Roles(undefined)
@ApiOperation({summary: 'Update job status: PENDING -> VALIDATED -> READY -> ALLOCATED'})
Parameters :
Name Type Optional
orgId string No
id string No
userId string No
body literal type No
Returns : unknown
Async waiveField
waiveField(orgId: string, id: string, userId: string, firstName: string, lastName: string, body: literal type)
Decorators :
@Patch(':id/waive-field')
@Roles(undefined)
@ApiOperation({summary: 'Waive a missing field — mark it as intentionally skipped'})
Parameters :
Name Type Optional
orgId string No
id string No
userId string No
firstName string No
lastName string No
body literal type No
Returns : unknown
import {
  Controller,
  Get,
  Post,
  Put,
  Patch,
  Delete,
  Body,
  Param,
  Query,
  UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
import { UpdateOrderDto } from './dto/update-order.dto';
import { OrderFilterDto } from './dto/order-filter.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';

/**
 * Roles allowed to MUTATE orders. Reads are open to all authenticated
 * staff (drivers see only their own via the trips endpoint, not /orders).
 */
const ORDER_WRITERS = [
  'SUPER_ADMIN',
  'ADMIN',
  'OPERATIONS_MANAGER',
  'PLANNER',
  'DISPATCHER',
  'CUSTOMER_SERVICE',
] as const;

const ORDER_DELETERS = ['SUPER_ADMIN', 'ADMIN', 'OPERATIONS_MANAGER'] as const;

@ApiTags('Orders')
@ApiBearerAuth()
@UseGuards(CombinedAuthGuard, RolesGuard)
@Controller('orders')
export class OrdersController {
  constructor(private readonly ordersService: OrdersService) {}

  @Post()
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Create a new order' })
  async create(
    @CurrentUser('organizationId') orgId: string,
    @CurrentUser('id') userId: string,
    @Body() dto: CreateOrderDto,
  ) {
    return this.ordersService.create(orgId, dto, userId);
  }

  @Post('bulk')
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Bulk create orders' })
  async bulkCreate(
    @CurrentUser('organizationId') orgId: string,
    @CurrentUser('id') userId: string,
    @Body() orders: CreateOrderDto[],
  ) {
    return this.ordersService.bulkCreate(orgId, orders, userId);
  }

  @Get('stats')
  @ApiOperation({ summary: 'Get order statistics by status' })
  async getStats(@CurrentUser('organizationId') orgId: string) {
    return this.ordersService.getStats(orgId);
  }

  @Get()
  @ApiOperation({ summary: 'List orders with filtering and pagination' })
  async findAll(
    @CurrentUser('organizationId') orgId: string,
    @Query() filters: OrderFilterDto,
  ) {
    return this.ordersService.findAll(orgId, filters);
  }

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

  @Patch(':id')
  // Drivers update their assigned orders (status changes) from the
  // mobile app. The service enforces driver-self-service rules:
  // they can only update orders linked to their own trip and may
  // only change a small whitelist of fields.
  @Roles(...ORDER_WRITERS, 'DRIVER')
  @ApiOperation({ summary: 'Update an order (org-scoped, drivers can change own status)' })
  async update(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
    @CurrentUser('id') userId: string,
    @CurrentUser('role') role: string,
    @Body() dto: UpdateOrderDto,
  ) {
    return this.ordersService.update(orgId, id, dto, userId, role);
  }

  @Delete(':id')
  @Roles(...ORDER_DELETERS)
  @ApiOperation({ summary: 'Delete an order (blocked when on an active trip)' })
  async remove(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
  ) {
    return this.ordersService.remove(orgId, id);
  }

  @Patch(':id/confirm-loading')
  // Drivers need to confirm loading from the mobile app, so they're
  // included alongside the writer set.
  @Roles(...ORDER_WRITERS, 'DRIVER')
  @ApiOperation({ summary: 'Confirm loading — requires GPS location at the loading bay' })
  async confirmLoading(
    @Param('id') id: string,
    @CurrentUser('id') userId: string,
    @CurrentUser('organizationId') orgId: string,
    @Body() body: { latitude?: number; longitude?: number },
  ) {
    return this.ordersService.confirmLoading(id, userId, orgId, body.latitude, body.longitude);
  }

  @Get('jobs/list')
  @ApiOperation({ summary: 'List orders in job stage (VALIDATED or READY)' })
  async getJobs(@CurrentUser('organizationId') orgId: string) {
    return this.ordersService.getJobs(orgId);
  }

  @Patch(':id/job-status')
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Update job status: PENDING -> VALIDATED -> READY -> ALLOCATED' })
  async updateJobStatus(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
    @CurrentUser('id') userId: string,
    @Body() body: { jobStatus: string },
  ) {
    return this.ordersService.updateJobStatus(orgId, id, body.jobStatus, userId);
  }

  @Patch(':id/assign-vehicle')
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Manually assign a vehicle to an order and create a trip' })
  async assignVehicle(
    @Param('id') id: string,
    @CurrentUser('organizationId') orgId: string,
    @CurrentUser('id') userId: string,
    @Body() body: { vehicleId: string },
  ) {
    return this.ordersService.assignVehicle(id, body.vehicleId, orgId, userId);
  }

  @Patch(':id/waive-field')
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Waive a missing field — mark it as intentionally skipped' })
  async waiveField(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
    @CurrentUser('id') userId: string,
    @CurrentUser('firstName') firstName: string,
    @CurrentUser('lastName') lastName: string,
    @Body() body: { field: string; reason?: string },
  ) {
    return this.ordersService.waiveField(
      orgId,
      id,
      body.field,
      userId,
      `${firstName || ''} ${lastName || ''}`.trim(),
      body.reason,
    );
  }

  @Post(':id/send-to-jobs')
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Send order to Jobs board — only if all missing fields are filled or waived' })
  async sendToJobs(
    @CurrentUser('organizationId') orgId: string,
    @Param('id') id: string,
    @CurrentUser('id') userId: string,
  ) {
    return this.ordersService.sendToJobs(orgId, id, userId);
  }

  @Post('bulk-send-to-jobs')
  @Roles(...ORDER_WRITERS)
  @ApiOperation({ summary: 'Send all eligible imported orders to Jobs — rejects orders with unresolved missing data' })
  async bulkSendToJobs(
    @CurrentUser('organizationId') orgId: string,
    @CurrentUser('id') userId: string,
  ) {
    return this.ordersService.bulkSendToJobs(orgId, userId);
  }
}

results matching ""

    No results matching ""