From 8336e7f81a26c67a96331186fffdfb9c2d95c73b Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 6 Dec 2025 10:36:51 +0330 Subject: [PATCH] orders pagination --- src/modules/orders/dto/find-orders.dto.ts | 127 +++++++++++++++++++ src/modules/orders/interface/order-status.ts | 3 + src/modules/orders/orders.controller.ts | 29 +++-- src/modules/orders/orders.module.ts | 4 +- src/modules/orders/orders.service.ts | 39 ++++-- 5 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 src/modules/orders/dto/find-orders.dto.ts diff --git a/src/modules/orders/dto/find-orders.dto.ts b/src/modules/orders/dto/find-orders.dto.ts new file mode 100644 index 0000000..a2d33ce --- /dev/null +++ b/src/modules/orders/dto/find-orders.dto.ts @@ -0,0 +1,127 @@ +import { IsOptional, IsString, IsNumber, Min, IsIn, IsEnum, IsDateString } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { OrderStatus } from '../interface/order-status'; +import { PaymentStatusEnum } from '../../payments/interface/payment'; + +// Define the valid sort directions +const sortOrderOptions = ['asc', 'desc'] as const; +type SortOrder = (typeof sortOrderOptions)[number]; + +export class FindOrdersDto { + /** + * The page number to retrieve. + * @default 1 + */ + @ApiPropertyOptional({ + description: 'Page number', + type: Number, + default: 1, + minimum: 1, + }) + @IsOptional() + @IsNumber() + @Min(1) + @Type(() => Number) + page?: number = 1; + + /** + * The number of items per page. + * @default 10 + */ + @ApiPropertyOptional({ + description: 'Number of items per page', + type: Number, + default: 10, + minimum: 1, + }) + @IsOptional() + @IsNumber() + @Min(1) + @Type(() => Number) + limit?: number = 10; + + /** + * Filter by order status + */ + @ApiPropertyOptional({ + description: 'Filter by order status', + enum: OrderStatus, + }) + @IsOptional() + @IsEnum(OrderStatus) + status?: OrderStatus; + + /** + * Filter by payment status + */ + @ApiPropertyOptional({ + description: 'Filter by payment status', + enum: PaymentStatusEnum, + }) + @IsOptional() + @IsEnum(PaymentStatusEnum) + paymentStatus?: PaymentStatusEnum; + + /** + * Search by order number or user information + */ + @ApiPropertyOptional({ + description: 'Search by order number or user information', + type: String, + }) + @IsOptional() + @IsString() + search?: string; + + /** + * Filter orders from this date (ISO date string) + */ + @ApiPropertyOptional({ + description: 'Filter orders from this date (ISO date string)', + type: String, + format: 'date-time', + }) + @IsOptional() + @IsDateString() + startDate?: string; + + /** + * Filter orders until this date (ISO date string) + */ + @ApiPropertyOptional({ + description: 'Filter orders until this date (ISO date string)', + type: String, + format: 'date-time', + }) + @IsOptional() + @IsDateString() + endDate?: string; + + /** + * The field to sort the results by. + * @default "createdAt" + */ + @ApiPropertyOptional({ + description: 'Field to sort by (createdAt, total, orderNumber)', + type: String, + default: 'createdAt', + }) + @IsOptional() + @IsString() + orderBy?: string = 'createdAt'; + + /** + * The direction to sort the results. + * @default "desc" + */ + @ApiPropertyOptional({ + description: 'Sort direction (asc or desc)', + enum: sortOrderOptions, + default: 'desc', + }) + @IsOptional() + @IsIn(sortOrderOptions) + order?: SortOrder = 'desc'; +} + diff --git a/src/modules/orders/interface/order-status.ts b/src/modules/orders/interface/order-status.ts index befe14e..8c4079e 100644 --- a/src/modules/orders/interface/order-status.ts +++ b/src/modules/orders/interface/order-status.ts @@ -1,5 +1,8 @@ export enum OrderStatus { Pending = 'pending', + + // PendingConfirmation = 'pendingConfirmation', + Confirmed = 'confirmed', RejectedByRestaurant = 'rejectedByRestaurant', CancelledByUser = 'cancelledByUser', diff --git a/src/modules/orders/orders.controller.ts b/src/modules/orders/orders.controller.ts index 7c73d4a..c3b257b 100644 --- a/src/modules/orders/orders.controller.ts +++ b/src/modules/orders/orders.controller.ts @@ -1,11 +1,12 @@ -import { Controller, Get, Post, Param, UseGuards, Patch } from '@nestjs/common'; +import { Controller, Get, Post, Param, UseGuards, Patch, Query } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; import { OrdersService } from './orders.service'; import { AuthGuard } from '../auth/guards/auth.guard'; import { UserId } from '../../common/decorators/user-id.decorator'; import { RestId } from 'src/common/decorators/rest-id.decorator'; import { AdminAuthGuard } from '../auth/guards/adminAuth.guard'; - +import { FindOrdersDto } from './dto/find-orders.dto'; + @ApiTags('orders') @Controller() export class OrdersController { @@ -22,9 +23,9 @@ export class OrdersController { @UseGuards(AuthGuard) @ApiBearerAuth() @Get('public/orders') - @ApiOperation({ summary: 'Get all orders' }) - findAll(@RestId() restId: string) { - return this.ordersService.findAll(restId); + @ApiOperation({ summary: 'Get all orders with pagination and filters' }) + findAll(@RestId() restId: string, @Query() dto: FindOrdersDto) { + return this.ordersService.findAll(restId, dto); } @UseGuards(AuthGuard) @@ -34,12 +35,19 @@ export class OrdersController { return this.ordersService.findOne(+id); } + @UseGuards(AuthGuard) + @ApiBearerAuth() + @Patch('public/orders/:id/cancel') + cancelOrder(@Param('id') id: string) { + return this.ordersService.findOne(+id); + } + @UseGuards(AdminAuthGuard) @ApiBearerAuth() @Get('admin/orders') - @ApiOperation({ summary: 'Get all orders' }) - findAllAdmin(@RestId() restId: string) { - return this.ordersService.findAll(restId); + @ApiOperation({ summary: 'Get all orders with pagination and filters' }) + findAllAdmin(@RestId() restId: string, @Query() dto: FindOrdersDto) { + return this.ordersService.findAll(restId, dto); } @UseGuards(AdminAuthGuard) @@ -68,9 +76,4 @@ export class OrdersController { readyForPickup(@Param('orderId') orderId: string) { return this.ordersService.readyForDelivery(orderId); } - - // @Delete(':id') - // remove(@Param('id') id: string) { - // return this.ordersService.remove(+id); - // } } diff --git a/src/modules/orders/orders.module.ts b/src/modules/orders/orders.module.ts index 7c6eb05..73a8b56 100644 --- a/src/modules/orders/orders.module.ts +++ b/src/modules/orders/orders.module.ts @@ -14,6 +14,7 @@ import { UtilsModule } from '../utils/utils.module'; import { AuthModule } from '../auth/auth.module'; import { PaymentsModule } from '../payments/payments.module'; import { JwtModule } from '@nestjs/jwt'; +import { OrderRepository } from './repositories/order.repository'; @Module({ imports: [ @@ -25,6 +26,7 @@ import { JwtModule } from '@nestjs/jwt'; JwtModule, ], controllers: [OrdersController], - providers: [OrdersService], + providers: [OrdersService, OrderRepository], + exports: [OrderRepository], }) export class OrdersModule {} diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index 61ddf67..55a3807 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -16,6 +16,9 @@ import { PaymentsService } from '../payments/services/payments.service'; import { DeliveryMethodEnum } from '../delivery/interface/delivery'; import { Delivery } from '../delivery/entities/delivery.entity'; import { Cron, CronExpression } from '@nestjs/schedule'; +import { OrderRepository } from './repositories/order.repository'; +import { FindOrdersDto } from './dto/find-orders.dto'; +import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; @Injectable() export class OrdersService { @@ -25,6 +28,7 @@ export class OrdersService { private readonly em: EntityManager, private readonly cartService: CartService, private readonly paymentsService: PaymentsService, + private readonly orderRepository: OrderRepository, // private readonly paymentGatewayService: PaymentGatewayService, ) {} @@ -234,16 +238,18 @@ export class OrdersService { }; } - async findAll(restId: string) { - const orders = await this.em.find( - Order, - { restaurant: { id: restId } }, - { populate: ['user', 'restaurant', 'deliveryMethod', 'address', 'paymentMethod'] }, - ); - if (!orders) { - throw new NotFoundException('No orders found'); - } - return orders; + async findAll(restId: string, dto: FindOrdersDto): Promise> { + return this.orderRepository.findAllPaginated(restId, { + page: dto.page, + limit: dto.limit, + status: dto.status, + paymentStatus: dto.paymentStatus, + search: dto.search, + startDate: dto.startDate, + endDate: dto.endDate, + orderBy: dto.orderBy, + order: dto.order, + }); } findOne(id: number) { @@ -299,8 +305,17 @@ export class OrdersService { return order; } - remove(id: number) { - return `This action removes a #${id} order`; + async cancelOrder(orderId: string) { + const order = await this.em.findOne(Order, { id: orderId }); + if (!order) { + throw new NotFoundException('Order not found'); + } + if (order.status !== OrderStatus.Pending) { + throw new BadRequestException('Order is not pending'); + } + order.status = OrderStatus.CancelledByUser; + await this.em.persistAndFlush(order); + return order; } /**