From 4ac1a19d2f346941bc88a65cdb3eeaad8d6f067d Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Thu, 18 Jun 2026 22:08:10 +0330 Subject: [PATCH] order report by date --- .../orders/controllers/orders.controller.ts | 9 ++++ .../orders/dto/daily-order-report.dto.ts | 37 +++++++++++++++ .../orders/providers/orders.service.ts | 46 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/modules/orders/dto/daily-order-report.dto.ts diff --git a/src/modules/orders/controllers/orders.controller.ts b/src/modules/orders/controllers/orders.controller.ts index 17b7ac4..9a901c4 100644 --- a/src/modules/orders/controllers/orders.controller.ts +++ b/src/modules/orders/controllers/orders.controller.ts @@ -13,6 +13,7 @@ import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permission } from 'src/common/enums/permission.enum'; import { AdminCreateOrderDto } from '../dto/admin-create-order.dto'; import { FoodOrderReportDto } from '../dto/food-order-report.dto'; +import { DailyOrderReportDto } from '../dto/daily-order-report.dto'; @ApiTags('orders') @ApiBearerAuth() @@ -136,4 +137,12 @@ export class OrdersController { getFoodOrderReport(@RestId() restId: string, @Query() dto: FoodOrderReportDto) { return this.ordersService.getFoodOrderReport(restId, dto); } + + @UseGuards(AdminAuthGuard) + @Permissions(Permission.VIEW_REPORTS) + @ApiOperation({ summary: 'Get order report grouped by day' }) + @Get('admin/orders/report/by-day') + getDailyOrderReport(@RestId() restId: string, @Query() dto: DailyOrderReportDto) { + return this.ordersService.getDailyOrderReport(restId, dto); + } } diff --git a/src/modules/orders/dto/daily-order-report.dto.ts b/src/modules/orders/dto/daily-order-report.dto.ts new file mode 100644 index 0000000..30cdcbe --- /dev/null +++ b/src/modules/orders/dto/daily-order-report.dto.ts @@ -0,0 +1,37 @@ +import { IsOptional, IsDateString, IsEnum } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export enum DailyOrderReportSortBy { + Date = 'date', + TotalAmount = 'totalAmount', + OrderCount = 'orderCount', +} + +export class DailyOrderReportDto { + @ApiPropertyOptional({ + format: 'date-time', + description: 'Filter orders from this date and time', + example: '2024-01-01T08:00:00.000Z', + }) + @IsOptional() + @IsDateString() + from?: string; + + @ApiPropertyOptional({ + format: 'date-time', + description: 'Filter orders until this date and time', + example: '2024-12-31T23:59:59.000Z', + }) + @IsOptional() + @IsDateString() + to?: string; + + @ApiPropertyOptional({ + enum: DailyOrderReportSortBy, + default: DailyOrderReportSortBy.Date, + description: 'Sort results by date, total amount, or order count', + }) + @IsOptional() + @IsEnum(DailyOrderReportSortBy) + sortBy: DailyOrderReportSortBy = DailyOrderReportSortBy.Date; +} diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index 90b471e..e0eeeff 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -26,6 +26,7 @@ import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.even import { OrderMessage } from 'src/common/enums/message.enum'; import { AdminCreateOrderDto } from '../dto/admin-create-order.dto'; import { FoodOrderReportDto, FoodOrderReportSortBy } from '../dto/food-order-report.dto'; +import { DailyOrderReportDto, DailyOrderReportSortBy } from '../dto/daily-order-report.dto'; import { normalizePhone } from 'src/modules/utils/phone.util'; type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number }; @@ -817,4 +818,49 @@ console.log(cart); totalPrice: Number(row.total_price || 0), })); } + + async getDailyOrderReport(restId: string, dto: DailyOrderReportDto) { + const { from, to, sortBy = DailyOrderReportSortBy.Date } = dto; + + const params: unknown[] = [restId]; + const dateFilters: string[] = []; + + if (from) { + params.push(new Date(from)); + dateFilters.push(`AND o.created_at >= ?`); + } + + if (to) { + params.push(new Date(to)); + dateFilters.push(`AND o.created_at <= ?`); + } + + const orderColumn = { + [DailyOrderReportSortBy.Date]: `DATE_TRUNC('day', CAST(o.created_at AS timestamp))`, + [DailyOrderReportSortBy.TotalAmount]: 'total_amount', + [DailyOrderReportSortBy.OrderCount]: 'order_count', + }[sortBy]; + + const result = await this.em.execute( + ` + SELECT + TO_CHAR(DATE_TRUNC('day', CAST(o.created_at AS timestamp)), 'YYYY-MM-DD') as "date", + COUNT(o.id)::int as order_count, + COALESCE(SUM(o.total), 0)::numeric as total_amount + FROM orders o + WHERE o.restaurant_id = ? + ${dateFilters.join('\n ')} + AND o.status NOT IN ('pendingPayment', 'canceled') + GROUP BY DATE_TRUNC('day', CAST(o.created_at AS timestamp)) + ORDER BY ${orderColumn} DESC + `, + params, + ); + + return result.map((row: Record) => ({ + date: row.date, + orderCount: Number(row.order_count || 0), + totalAmount: Number(row.total_amount || 0), + })); + } }