order
This commit is contained in:
+1
-2
@@ -12,7 +12,6 @@ import { ScheduleModule } from '@nestjs/schedule';
|
|||||||
import { ProductModule } from './modules/product/product.module';
|
import { ProductModule } from './modules/product/product.module';
|
||||||
import { RolesModule } from './modules/roles/roles.module';
|
import { RolesModule } from './modules/roles/roles.module';
|
||||||
import { PaymentModule } from './modules/payment/payments.module';
|
import { PaymentModule } from './modules/payment/payments.module';
|
||||||
import { OrderModule } from './modules/order/order.module';
|
|
||||||
import { NotificationsModule } from './modules/notification/notifications.module';
|
import { NotificationsModule } from './modules/notification/notifications.module';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
@@ -40,7 +39,7 @@ import { InvoiceModule } from './modules/invoice/invoice.module';
|
|||||||
ProductModule,
|
ProductModule,
|
||||||
RolesModule,
|
RolesModule,
|
||||||
PaymentModule,
|
PaymentModule,
|
||||||
OrderModule,
|
// OrderModule,
|
||||||
NotificationsModule,
|
NotificationsModule,
|
||||||
EventEmitterModule.forRoot(),
|
EventEmitterModule.forRoot(),
|
||||||
PrintModule,
|
PrintModule,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class Invoice extends BaseEntity {
|
|||||||
@ManyToOne(() => Request)
|
@ManyToOne(() => Request)
|
||||||
request!: Request;
|
request!: Request;
|
||||||
|
|
||||||
@OneToMany(() => Payment, payment => payment.order, {
|
@OneToMany(() => Payment, payment => payment.invoice, {
|
||||||
cascade: [Cascade.ALL],
|
cascade: [Cascade.ALL],
|
||||||
orphanRemoval: true,
|
orphanRemoval: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
import { Controller, Get, Post, Param, UseGuards, Patch, Query, Body, Delete } from '@nestjs/common';
|
|
||||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
||||||
import { OrderService } from '../providers/order.service';
|
|
||||||
import { AuthGuard } from '../../auth/guards/auth.guard';
|
|
||||||
import { UserId } from '../../../common/decorators/user-id.decorator';
|
|
||||||
import { FindOrdersDto } from '../dto/find-orders.dto';
|
|
||||||
import {
|
|
||||||
CreateOrderAsUSerDto, CreateOrderItemAsUserDto,
|
|
||||||
CreateOrderItemDtoAsAdmin, CreateOrderAsAdminDto
|
|
||||||
} from '../dto/create-order.dto';
|
|
||||||
import { AdminId } from 'src/common/decorators/admin-id.decorator';
|
|
||||||
import { AssignDesignerDto } from '../dto/assign-designer.dto';
|
|
||||||
import { UpdateOrderItemDtoAsAdmin, UpdateOrderItemDtoAsUser } from '../dto/update-order-item.dto';
|
|
||||||
import { UpdateOrderAsAdminDto } from '../dto/update-order.dto';
|
|
||||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
|
||||||
import { CreatePrintFormDto } from '../dto/create-print-form.dto';
|
|
||||||
import { FindOrderItemsDto } from '../dto/find-order-items.dto';
|
|
||||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
|
||||||
import { PermissionEnum } from 'src/common/enums/permission.enum';
|
|
||||||
|
|
||||||
@ApiTags('orders')
|
|
||||||
@ApiBearerAuth()
|
|
||||||
@Controller()
|
|
||||||
export class OrderController {
|
|
||||||
constructor(private readonly orderService: OrderService) { }
|
|
||||||
|
|
||||||
// ================== Public ===================
|
|
||||||
|
|
||||||
@Post('public/orders')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'create order ' })
|
|
||||||
createOrder(@UserId() userId: string, @Body() body: CreateOrderAsUSerDto) {
|
|
||||||
return this.orderService.createOrderAsUser(userId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('public/orders/:id')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'Delete order ' })
|
|
||||||
deleteOrder(@Param('id') orderId: string, @UserId() userId: string) {
|
|
||||||
return this.orderService.removeOrderAsUser(userId, orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('public/orders')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'Get all orders with pagination and filters' })
|
|
||||||
findAll(@Query() dto: FindOrdersDto, @UserId() userId: string) {
|
|
||||||
return this.orderService.findOrdersAsUser(userId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('public/orders/:id')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'order detail' })
|
|
||||||
orderDetail(@Param('id') orderId: string, @UserId() userId: string) {
|
|
||||||
return this.orderService.getOrderAsUser(userId, orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('public/orders/:id/item')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'add item to order ' })
|
|
||||||
addOrderItem(@Param('id') orderId: string, @UserId() userId: string, @Body() body: CreateOrderItemAsUserDto) {
|
|
||||||
return this.orderService.addOrderItemAsUser(userId, orderId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Patch('public/orders/:id/item/:itemId')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'Update order item ' })
|
|
||||||
updateOrderItem(@Param('id') orderId: string, @UserId() userId: string, @Param('itemId') itemId: string, @Body() body: UpdateOrderItemDtoAsUser) {
|
|
||||||
return this.orderService.updateOrderItemAsUser(userId, orderId, itemId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Patch('public/orders/:orderId/items/:orderItemId/confirm')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'Confirm Invoice Item ' })
|
|
||||||
confirmOrderItem(
|
|
||||||
@Param('orderId') orderId: string,
|
|
||||||
@Param('orderItemId') orderItemId: string,
|
|
||||||
@UserId() userId: string
|
|
||||||
) {
|
|
||||||
return this.orderService.confirmOrderItem(userId, orderId, orderItemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('public/orders/:orderId/items/:orderItemId')
|
|
||||||
@UseGuards(AuthGuard)
|
|
||||||
@ApiOperation({ summary: 'Remove order Item ' })
|
|
||||||
removeOrderItem(
|
|
||||||
@Param('orderId') orderId: string,
|
|
||||||
@Param('orderItemId') orderItemId: string,
|
|
||||||
@UserId() userId: string
|
|
||||||
) {
|
|
||||||
return this.orderService.removeOrderItemAsUser(userId, orderId, orderItemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*========================== Admin Routes =====================*/
|
|
||||||
|
|
||||||
@Post('admin/orders')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.CREATE_ORDER)
|
|
||||||
@ApiOperation({ summary: 'create order ' })
|
|
||||||
createOrderAsAdmin(@AdminId() adminId: string, @Body() body: CreateOrderAsAdminDto) {
|
|
||||||
return this.orderService.createOrderAsAdmin(adminId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('admin/orders/request')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.VIEW_REQUESTS)
|
|
||||||
@ApiOperation({ summary: 'Get all order requests with pagination and filters' })
|
|
||||||
findOrderRequestAsAdmin(@Query() dto: FindOrdersDto) {
|
|
||||||
return this.orderService.findOrderRequestsAsAdmin(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this route permission is handled inside service
|
|
||||||
@Get('admin/orders/invoiced')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@ApiOperation({ summary: 'Get all invoiced orders with pagination and filters' })
|
|
||||||
findInvoicedOrdersAsAdmin(@Query() dto: FindOrdersDto, @AdminId() adminId: string) {
|
|
||||||
return this.orderService.findInvoicedOrdersAsAdmin(adminId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('admin/orders/:id')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@ApiOperation({ summary: 'order detail' })
|
|
||||||
orderDetailAsAdmin(@Param('id') orderId: string) {
|
|
||||||
return this.orderService.findOrderOrFail(orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch('admin/orders/:id')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.UPDATE_ORDER)
|
|
||||||
@ApiOperation({ summary: 'Update Order ' })
|
|
||||||
updateOrder(@Param('id') id: string, @Body() dto: UpdateOrderAsAdminDto) {
|
|
||||||
return this.orderService.updateOrderAsAdmin2(id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('admin/orders/:orderId')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.REMOVE_ORDER)
|
|
||||||
@ApiOperation({ summary: 'Hard Delete Order ' })
|
|
||||||
hardDelete(@Param('orderId') orderId: string) {
|
|
||||||
return this.orderService.hardDeleteOrder(orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============== Order Item ================
|
|
||||||
|
|
||||||
@Get('admin/orders/item/print')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.VIEW_All_ORDERS)
|
|
||||||
@ApiOperation({ summary: 'get print orders' })
|
|
||||||
getPrintOrders(@Body() dto: FindOrderItemsDto) {
|
|
||||||
return this.orderService.findPrintOrderItemsAsAdmin(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('admin/orders/item/:id')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@ApiOperation({ summary: 'order item detail' })
|
|
||||||
getOrderItem(@Param('id') itemId: string) {
|
|
||||||
return this.orderService.findOrderItemOrFailByItemId(itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Post('admin/orders/:orderId/item/:itemId/designer')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.ASSIGN_DESIGNER)
|
|
||||||
@ApiOperation({ summary: 'Assign Order Designer ' })
|
|
||||||
assignDesigner(@Param('orderId') orderId: string, @Param('itemId') itemId: string,
|
|
||||||
@Body() body: AssignDesignerDto) {
|
|
||||||
return this.orderService.assignDesigner(orderId, itemId, body.designerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch('admin/orders/:id/item/:itemId/print')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.CREATE_ORDER_PRINT)
|
|
||||||
@ApiOperation({ summary: 'Create Print form for order item' })
|
|
||||||
createPrintForm(@Param('id') id: string, @Param('itemId') itemId: string, @Body() dto: CreatePrintFormDto) {
|
|
||||||
return this.orderService.createPrintForm(id, itemId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Post('admin/orders/:orderId/invoice')
|
|
||||||
// @UseGuards(AdminAuthGuard)
|
|
||||||
// @ApiOperation({ summary: 'Create Order invoice ' })
|
|
||||||
// createInvoice(@Param('orderId') orderId: string) {
|
|
||||||
// return this.orderService.createInvoice(orderId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@Delete('admin/orders/:id/items/:itemId')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.UPDATE_ORDER)
|
|
||||||
@ApiOperation({ summary: 'Remove order Item ' })
|
|
||||||
removeOrderItemAdAdmin(
|
|
||||||
@Param('id') orderId: string,
|
|
||||||
@Param('itemId') itemId: string,
|
|
||||||
) {
|
|
||||||
return this.orderService.removeOrderItemAsAdmin(orderId, itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('admin/orders/:id/item')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.UPDATE_ORDER)
|
|
||||||
@ApiOperation({ summary: 'add item to order ' })
|
|
||||||
addOrderItemAsAdmin(@Param('id') orderId: string, @Body() body: CreateOrderItemDtoAsAdmin) {
|
|
||||||
return this.orderService.addOrderItemAsAdmin(orderId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Patch('admin/orders/:id/item/:itemId')
|
|
||||||
@UseGuards(AdminAuthGuard)
|
|
||||||
@Permissions(PermissionEnum.UPDATE_ORDER)
|
|
||||||
@ApiOperation({ summary: 'Update order item' })
|
|
||||||
updateOrderItemAsAdmin(@Param('id') orderId: string, @Param('itemId') itemId: string,
|
|
||||||
@Body() body: UpdateOrderItemDtoAsAdmin) {
|
|
||||||
return this.orderService.updateOrderItemAsAdmin(orderId, itemId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { Cron } from '@nestjs/schedule';
|
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OrdersCrone {
|
|
||||||
private readonly logger = new Logger(OrdersCrone.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly em: EntityManager,
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import {
|
|
||||||
IsString
|
|
||||||
} from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class AssignDesignerDto {
|
|
||||||
@IsString()
|
|
||||||
@ApiProperty()
|
|
||||||
designerId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
import {
|
|
||||||
IsString,
|
|
||||||
IsInt, IsArray, IsNumber,
|
|
||||||
IsNotEmpty,
|
|
||||||
ArrayMinSize,
|
|
||||||
IsOptional,
|
|
||||||
IsBoolean,
|
|
||||||
IsEnum,
|
|
||||||
IsDateString
|
|
||||||
} from 'class-validator';
|
|
||||||
import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { Type } from 'class-transformer';
|
|
||||||
import { IAttachment, IField, OrderItemStatusEnum } from '../interface/order.interface';
|
|
||||||
|
|
||||||
|
|
||||||
export class CreateOrderItemAsUserDto {
|
|
||||||
@IsString()
|
|
||||||
@ApiProperty()
|
|
||||||
productId: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsNumber()
|
|
||||||
quantity: number
|
|
||||||
|
|
||||||
@ApiProperty({ enum: OrderItemStatusEnum })
|
|
||||||
@IsEnum(() => OrderItemStatusEnum)
|
|
||||||
status: OrderItemStatusEnum
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsArray()
|
|
||||||
attributes: IField[]
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ example: 'توضیحات' })
|
|
||||||
@IsString()
|
|
||||||
description: string
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ example: [] })
|
|
||||||
@IsArray()
|
|
||||||
attachments: { url: string, type: string }[]
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateOrderItemDtoAsAdmin extends CreateOrderItemAsUserDto {
|
|
||||||
// @ApiPropertyOptional({ example: [] })
|
|
||||||
// @IsArray()
|
|
||||||
// printAttributes?: IField[]
|
|
||||||
|
|
||||||
// @ApiPropertyOptional({ example: [] })
|
|
||||||
// @IsArray()
|
|
||||||
// printAttachments?: IAttachment[]
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
@ApiProperty()
|
|
||||||
designerId: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsNumber()
|
|
||||||
unitPrice: number
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@IsNumber()
|
|
||||||
discount: number
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
@IsString()
|
|
||||||
adminDescription: string
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
@IsDateString()
|
|
||||||
confirmedAt: string
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateOrderAsUSerDto {
|
|
||||||
@IsArray()
|
|
||||||
@ApiProperty({
|
|
||||||
isArray: true, type: [CreateOrderItemAsUserDto],
|
|
||||||
example: [
|
|
||||||
{
|
|
||||||
productId: 'sd4',
|
|
||||||
quantity: 100,
|
|
||||||
attributes: [
|
|
||||||
{
|
|
||||||
attributeId: 1,
|
|
||||||
value: 'string | boolean| number'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
attachments: [
|
|
||||||
{ url: '', type: '' }
|
|
||||||
],
|
|
||||||
description: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
@IsNotEmpty()
|
|
||||||
@ArrayMinSize(1, { message: 'At least one item is required' })
|
|
||||||
@Type(() => CreateOrderItemAsUserDto)
|
|
||||||
items: CreateOrderItemAsUserDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class CreateOrderAsAdminDto {
|
|
||||||
@ApiProperty({ example: '' })
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
userId: string
|
|
||||||
|
|
||||||
@IsArray()
|
|
||||||
@ApiProperty({
|
|
||||||
isArray: true, type: [CreateOrderItemDtoAsAdmin], example: [
|
|
||||||
{
|
|
||||||
productId: 1,
|
|
||||||
designerId: '',
|
|
||||||
attachments: [{ url: '', type: '' }],
|
|
||||||
quantity: 100,
|
|
||||||
attributes: [{ fieldId: 10, value: 'blue' }],
|
|
||||||
unitPrice: 150000,
|
|
||||||
discount: 20000,
|
|
||||||
adminDescription: '',
|
|
||||||
description: 'توضیحات کاربر',
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
@IsNotEmpty()
|
|
||||||
@ArrayMinSize(1, { message: 'At least one product is required' })
|
|
||||||
@Type(() => CreateOrderItemDtoAsAdmin)
|
|
||||||
items: CreateOrderItemDtoAsAdmin[];
|
|
||||||
|
|
||||||
|
|
||||||
@ApiProperty({})
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
paymentMethod: string
|
|
||||||
|
|
||||||
@ApiProperty({})
|
|
||||||
@IsBoolean()
|
|
||||||
enableTax: boolean
|
|
||||||
|
|
||||||
@ApiProperty({})
|
|
||||||
@IsInt()
|
|
||||||
estimatedDays: number
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { IsArray, } from 'class-validator';
|
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { IField } from '../interface/order.interface';
|
|
||||||
|
|
||||||
export class CreatePrintFormDto {
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
example: [{
|
|
||||||
fieldId: 10, value: ''
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
@IsArray()
|
|
||||||
printAttributes?: IField[]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import {
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
IsNumber,
|
|
||||||
Min,
|
|
||||||
IsIn,
|
|
||||||
IsEnum,
|
|
||||||
IsDateString,
|
|
||||||
IsArray,
|
|
||||||
} from 'class-validator';
|
|
||||||
import { Type, Transform } from 'class-transformer';
|
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { OrderStatusEnum } from '../interface/order.interface';
|
|
||||||
import { PaymentStatusEnum } from '../../payment/interface/payment';
|
|
||||||
|
|
||||||
const sortOrderOptions = ['asc', 'desc'] as const;
|
|
||||||
type SortOrder = (typeof sortOrderOptions)[number];
|
|
||||||
|
|
||||||
export class FindOrderItemsDto {
|
|
||||||
@ApiPropertyOptional({ default: 1, minimum: 1 })
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
page: number = 1;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ default: 10, minimum: 1 })
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
limit: number = 10;
|
|
||||||
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
search?: string;
|
|
||||||
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ default: 'createdAt' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
orderBy: string = 'createdAt';
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
enum: sortOrderOptions,
|
|
||||||
default: 'desc',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsIn(sortOrderOptions)
|
|
||||||
order: SortOrder = 'desc';
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import {
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
IsNumber,
|
|
||||||
Min,
|
|
||||||
IsIn,
|
|
||||||
IsEnum,
|
|
||||||
IsDateString,
|
|
||||||
IsArray,
|
|
||||||
} from 'class-validator';
|
|
||||||
import { Type, Transform } from 'class-transformer';
|
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { OrderStatusEnum } from '../interface/order.interface';
|
|
||||||
import { PaymentStatusEnum } from '../../payment/interface/payment';
|
|
||||||
|
|
||||||
const sortOrderOptions = ['asc', 'desc'] as const;
|
|
||||||
type SortOrder = (typeof sortOrderOptions)[number];
|
|
||||||
|
|
||||||
export class FindOrdersDto {
|
|
||||||
@ApiPropertyOptional({ default: 1, minimum: 1 })
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
page: number = 1;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ default: 10, minimum: 1 })
|
|
||||||
@IsOptional()
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
@Min(1)
|
|
||||||
limit: number = 10;
|
|
||||||
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'Filter by order statuses',
|
|
||||||
enum: OrderStatusEnum,
|
|
||||||
isArray: true,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@Transform(({ value }) =>
|
|
||||||
Array.isArray(value) ? value : value?.split(',')
|
|
||||||
)
|
|
||||||
@IsArray()
|
|
||||||
@IsEnum(OrderStatusEnum, { each: true })
|
|
||||||
statuses?: OrderStatusEnum[];
|
|
||||||
|
|
||||||
// @ApiPropertyOptional({ enum: PaymentStatusEnum })
|
|
||||||
// @IsOptional()
|
|
||||||
// @IsEnum(PaymentStatusEnum)
|
|
||||||
// paymentStatus?: PaymentStatusEnum;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
search?: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ format: 'date-time' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsDateString()
|
|
||||||
from?: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ format: 'date-time' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsDateString()
|
|
||||||
to?: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({ default: 'createdAt' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
orderBy: string = 'createdAt';
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
enum: sortOrderOptions,
|
|
||||||
default: 'desc',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsIn(sortOrderOptions)
|
|
||||||
order: SortOrder = 'desc';
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { PartialType } from '@nestjs/swagger';
|
|
||||||
import { CreateOrderItemAsUserDto, CreateOrderItemDtoAsAdmin } from './create-order.dto';
|
|
||||||
|
|
||||||
export class UpdateOrderItemDtoAsUser extends PartialType(CreateOrderItemAsUserDto) { }
|
|
||||||
export class UpdateOrderItemDtoAsAdmin extends PartialType(CreateOrderItemDtoAsAdmin) { }
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { ApiProperty, OmitType, PartialType } from '@nestjs/swagger';
|
|
||||||
import { CreateOrderAsAdminDto, CreateOrderItemDtoAsAdmin } from './create-order.dto';
|
|
||||||
import { IsNumber } from 'class-validator';
|
|
||||||
|
|
||||||
export class UpdateOrderAsAdminDto extends PartialType(OmitType(CreateOrderAsAdminDto, 'items' as any)) {
|
|
||||||
@ApiProperty()
|
|
||||||
items: UpdateOrCreateOrderItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateOrCreateOrderItem extends CreateOrderItemDtoAsAdmin {
|
|
||||||
@ApiProperty()
|
|
||||||
@IsNumber()
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { Entity, Enum, Index, ManyToOne, OptionalProps, Property } from '@mikro-orm/core';
|
|
||||||
import { Order } from './order.entity';
|
|
||||||
import { Product } from 'src/modules/product/entities/product.entity';
|
|
||||||
import { IAttachment, IField, OrderItemStatusEnum } from '../interface/order.interface';
|
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
|
||||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
|
||||||
|
|
||||||
|
|
||||||
@Entity({ tableName: 'order_items' })
|
|
||||||
@Index({ properties: ['order'] })
|
|
||||||
export class OrderItem extends BaseEntity {
|
|
||||||
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'subTotal' | 'total';
|
|
||||||
|
|
||||||
@ManyToOne(() => Order)
|
|
||||||
order!: Order;
|
|
||||||
|
|
||||||
@ManyToOne(() => Product)
|
|
||||||
product!: Product;
|
|
||||||
|
|
||||||
@Enum(() => OrderItemStatusEnum)
|
|
||||||
status!: OrderItemStatusEnum;
|
|
||||||
|
|
||||||
@ManyToOne(() => Admin, { nullable: true })
|
|
||||||
designer?: Admin
|
|
||||||
|
|
||||||
@Property({ type: 'json', nullable: true })
|
|
||||||
attributes?: IField[]
|
|
||||||
|
|
||||||
@Property({ type: 'int' })
|
|
||||||
quantity!: number;
|
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true, })
|
|
||||||
unitPrice?: number;
|
|
||||||
|
|
||||||
// @Formula(alias => `
|
|
||||||
// COALESCE(${alias}.unit_price, 0) * ${alias}.quantity
|
|
||||||
// `)
|
|
||||||
@Property({
|
|
||||||
type: 'int',
|
|
||||||
columnType: 'int GENERATED ALWAYS AS (COALESCE(unit_price, 0) * quantity) STORED',
|
|
||||||
persist: false,
|
|
||||||
nullable: true
|
|
||||||
})
|
|
||||||
subTotal!: number;
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
type: 'int',
|
|
||||||
nullable: true
|
|
||||||
})
|
|
||||||
discount?: number;
|
|
||||||
|
|
||||||
// @Formula(alias => `
|
|
||||||
// (COALESCE(${alias}.unit_price, 0) * ${alias}.quantity)
|
|
||||||
// - COALESCE(${alias}.discount, 0)
|
|
||||||
// `)
|
|
||||||
@Property({
|
|
||||||
type: 'int',
|
|
||||||
nullable: true,
|
|
||||||
columnType: 'int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED',
|
|
||||||
persist: false
|
|
||||||
})
|
|
||||||
total!: number;
|
|
||||||
|
|
||||||
@Property({ type: 'text', nullable: true })
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@Property({ type: 'text', nullable: true })
|
|
||||||
adminDescription?: string;
|
|
||||||
|
|
||||||
@Property({ type: 'json', nullable: true })
|
|
||||||
attachments?: IAttachment[] // user attachments like voice and photos
|
|
||||||
|
|
||||||
@Property({ type: 'json', nullable: true })
|
|
||||||
printAttributes?: IField[] // print form selected attributes
|
|
||||||
|
|
||||||
@Property({ nullable: true, columnType: 'timestamptz' })
|
|
||||||
confirmedAt?: Date;
|
|
||||||
|
|
||||||
@Property({ nullable: true, columnType: 'timestamptz' })
|
|
||||||
printFormCreatedAt?: Date;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
Index,
|
|
||||||
ManyToOne,
|
|
||||||
OneToMany,
|
|
||||||
Property,
|
|
||||||
Collection,
|
|
||||||
Cascade,
|
|
||||||
Enum,
|
|
||||||
PrimaryKey,
|
|
||||||
OptionalProps,
|
|
||||||
} from '@mikro-orm/core';
|
|
||||||
import { User } from '../../user/entities/user.entity';
|
|
||||||
import { OrderItem } from './order-item.entity';
|
|
||||||
import { Payment } from 'src/modules/payment/entities/payment.entity';
|
|
||||||
import { ulid } from 'ulid';
|
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
|
||||||
import { PaymentStatusEnum } from 'src/modules/payment/interface/payment';
|
|
||||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
|
||||||
import { OrderStatusEnum } from '../interface/order.interface';
|
|
||||||
|
|
||||||
@Entity({ tableName: 'orders' })
|
|
||||||
@Index({ properties: ['user'] })
|
|
||||||
export class Order extends BaseEntity {
|
|
||||||
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'orderNumber'
|
|
||||||
|
|
||||||
@PrimaryKey({ type: 'string', columnType: 'char(26)' })
|
|
||||||
id: string = ulid()
|
|
||||||
|
|
||||||
|
|
||||||
@ManyToOne(() => User)
|
|
||||||
user!: User;
|
|
||||||
|
|
||||||
@OneToMany(() => Payment, payment => payment.order, {
|
|
||||||
cascade: [Cascade.ALL],
|
|
||||||
orphanRemoval: true,
|
|
||||||
})
|
|
||||||
payments = new Collection<Payment>(this);
|
|
||||||
|
|
||||||
@OneToMany(() => OrderItem, item => item.order, {
|
|
||||||
cascade: [Cascade.ALL],
|
|
||||||
orphanRemoval: true,
|
|
||||||
})
|
|
||||||
items = new Collection<OrderItem>(this);
|
|
||||||
|
|
||||||
@ManyToOne(() => Admin, { nullable: true })
|
|
||||||
creator?: Admin
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
type: 'int',
|
|
||||||
autoincrement: true
|
|
||||||
})
|
|
||||||
orderNumber: number;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true })
|
|
||||||
discount?: number;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true })
|
|
||||||
subTotal?: number;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true })
|
|
||||||
taxAmount?: number;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true })
|
|
||||||
total?: number;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', default: 0 })
|
|
||||||
paidAmount: number = 0;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', default: 0 })
|
|
||||||
balance: number = 0;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true })
|
|
||||||
estimatedDays?: number; // number of days to be done
|
|
||||||
|
|
||||||
|
|
||||||
@Enum(() => OrderStatusEnum)
|
|
||||||
status: OrderStatusEnum = OrderStatusEnum.REQUEST;
|
|
||||||
|
|
||||||
|
|
||||||
@Property({ type: 'string', nullable: true })
|
|
||||||
paymentMethod?: string;
|
|
||||||
|
|
||||||
@Property({ nullable: true, columnType: 'timestamptz' })
|
|
||||||
invoicedAt?: Date;
|
|
||||||
|
|
||||||
@Property({ type: 'boolean', default: false })
|
|
||||||
enableTax?: boolean = false
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import type { OrderStatusEnum } from '../interface/order.interface';
|
|
||||||
|
|
||||||
export class OrderCreatedEvent {
|
|
||||||
constructor(
|
|
||||||
public readonly orderId: string,
|
|
||||||
public readonly: string,
|
|
||||||
public readonly orderNumber: string,
|
|
||||||
public readonly total: number,
|
|
||||||
) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OrderStatusChangedEvent {
|
|
||||||
constructor(
|
|
||||||
public readonly orderId: string,
|
|
||||||
public readonly userId: string,
|
|
||||||
public readonly orderNumber: string,
|
|
||||||
public readonly: string,
|
|
||||||
public readonly previousStatus: OrderStatusEnum,
|
|
||||||
public readonly newStatus: OrderStatusEnum,
|
|
||||||
) { }
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { Admin } from "src/modules/admin/entities/admin.entity"
|
|
||||||
import { Product } from "src/modules/product/entities/product.entity"
|
|
||||||
|
|
||||||
export enum OrderStatusEnum {
|
|
||||||
REQUEST = 'request',
|
|
||||||
|
|
||||||
INVOICED = 'invoiced',
|
|
||||||
|
|
||||||
ORDER = 'order',
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum OrderItemStatusEnum {
|
|
||||||
CREATED = 'created',
|
|
||||||
|
|
||||||
INVOICED = 'invoiced',
|
|
||||||
WAITING_to_CONFIRM_INVOICE = 'waiting_to_confirm_invoice',
|
|
||||||
|
|
||||||
|
|
||||||
DESIGN_INITIATED = 'design_initiated',
|
|
||||||
DESIGN_FINISHED = 'design_finished',
|
|
||||||
DESIGN_REVISION = 'design_revision',
|
|
||||||
|
|
||||||
READY_TO_PRINTING = 'ready_to_printing',
|
|
||||||
IN_PRINTING = 'in_printing',
|
|
||||||
|
|
||||||
READY_FOR_DELIVERY = 'ready_for_delivery',
|
|
||||||
DELIVERED = 'delivered',
|
|
||||||
|
|
||||||
AFTER_PRINT_COVER = 'after_print_cover',
|
|
||||||
AFTER_PRINT_BOXING = 'after_print_boxing',
|
|
||||||
AFTER_PRINT_CARTONING = 'after_print_cartoning',
|
|
||||||
|
|
||||||
|
|
||||||
FINISHED = 'finished',
|
|
||||||
|
|
||||||
CANCELED = 'canceled',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IAttachment { type: string, url: string }
|
|
||||||
|
|
||||||
export interface CreateOrderItem {
|
|
||||||
productId: string,
|
|
||||||
quantity: number
|
|
||||||
status: OrderItemStatusEnum,
|
|
||||||
attributes?: IField[]
|
|
||||||
description?: string
|
|
||||||
attachments?: IAttachment[]
|
|
||||||
unitPrice?: number
|
|
||||||
discount?: number
|
|
||||||
printFormCreatedAt?: Date
|
|
||||||
|
|
||||||
printAttributes?: IField[]
|
|
||||||
designerId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateOrderItemPopulated = Omit<CreateOrderItem, 'productId' | 'designerId'> & {
|
|
||||||
product: Product,
|
|
||||||
designer?: Admin
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateOrder {
|
|
||||||
userId: string
|
|
||||||
// status: OrderStatusEnum
|
|
||||||
enableTax?: boolean
|
|
||||||
paymentMethod?: string
|
|
||||||
adminId?: string
|
|
||||||
estimatedDays?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateOrderWithItems extends CreateOrder {
|
|
||||||
items: CreateOrderItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UpdateOrder = Partial<CreateOrder>
|
|
||||||
|
|
||||||
|
|
||||||
export type UpdateOrderItem = Partial<CreateOrderItemPopulated>
|
|
||||||
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
|
||||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
|
||||||
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
|
|
||||||
import { PermissionEnum } from 'src/common/enums/permission.enum';
|
|
||||||
import { NotifEvent } from 'src/modules/notification/interfaces/notification.interface';
|
|
||||||
import { NotificationService } from 'src/modules/notification/services/notification.service';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { OrderRepository } from '../repositories/order.repository';
|
|
||||||
// import { OrderStatusEnum } from '../interface/order.interface';
|
|
||||||
import { PaymentMethodEnum } from 'src/modules/payment/interface/payment';
|
|
||||||
import { UserService } from 'src/modules/user/providers/user.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OrderListeners {
|
|
||||||
private readonly logger = new Logger(OrderListeners.name);
|
|
||||||
private orderCreatedSmsTemplateId: string;
|
|
||||||
private orderStatusChangedSmsTemplateId: string;
|
|
||||||
// private orderCompletedSmsTemplateId: string;
|
|
||||||
constructor(
|
|
||||||
private readonly adminService: AdminRepository,
|
|
||||||
private readonly OrderRepository: OrderRepository,
|
|
||||||
private readonly notificationService: NotificationService,
|
|
||||||
private readonly configService: ConfigService,
|
|
||||||
private readonly userService: UserService,
|
|
||||||
) {
|
|
||||||
this.orderCreatedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ORDER_CREATED') ?? '123';
|
|
||||||
this.orderStatusChangedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ORDER_STATUS_CHANGE') ?? '123';
|
|
||||||
// this.orderCompletedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ORDER_STATUS_COMPLETED') ?? '123';
|
|
||||||
}
|
|
||||||
|
|
||||||
// private getStatusFarsi(status: OrderStatus): string {
|
|
||||||
// const statusMap: Record<OrderStatus, string> = {
|
|
||||||
// [OrderStatus.PENDING_PAYMENT]: 'در انتظار پرداخت',
|
|
||||||
// [OrderStatus.PAID]: 'پرداخت شده',
|
|
||||||
// [OrderStatus.PREPARING]: 'در حال آمادهسازی',
|
|
||||||
// [OrderStatus.DELIVERED_TO_RECEPTIONIST]: 'تحویل به پذیرش',
|
|
||||||
// [OrderStatus.DELIVERED_TO_WAITER]: 'تحویل به گارسون',
|
|
||||||
// [OrderStatus.SHIPPED]: 'ارسال شده',
|
|
||||||
// [OrderStatus.COMPLETED]: 'تکمیل شده',
|
|
||||||
// [OrderStatus.CANCELED]: 'لغو شده',
|
|
||||||
// };
|
|
||||||
// return statusMap[status] || status;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @OnEvent(OrderCreatedEvent.name)
|
|
||||||
// async handleOrderCreated(event: OrderCreatedEvent) {
|
|
||||||
// try {
|
|
||||||
// this.logger.log(
|
|
||||||
// `Order created event received: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const order = await this.OrderRepository.findOne(event.orderId);
|
|
||||||
// if (order?.paymentMethod.method === PaymentMethodEnum.Online) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// // get admnin os restuaraant that have order permissuins
|
|
||||||
// const admins = await this.adminService.findAdminsWithPermission(event., Permission.MANAGE_ORDERS);
|
|
||||||
// const recipients = admins.map(admin => ({
|
|
||||||
// adminId: admin.id,
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// await this.notificationService.sendNotification({
|
|
||||||
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_CREATED,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// total: event.total.toString(),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `سفارش جدید`,
|
|
||||||
// content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
|
|
||||||
// icon: `/`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_CREATED,
|
|
||||||
// url: `/`,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// recipients,
|
|
||||||
// metadata: {
|
|
||||||
// priority: 1,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// } catch (error) {
|
|
||||||
// this.logger.error(
|
|
||||||
// `Failed to send notification for order created event: ${event.}`,
|
|
||||||
// error instanceof Error ? error.stack : String(error),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @OnEvent(OrderStatusChangedEvent.name)
|
|
||||||
// async handleOrderStatusChanged(event: OrderStatusChangedEvent) {
|
|
||||||
// try {
|
|
||||||
// this.logger.log(
|
|
||||||
// `Order status changed event received: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
|
||||||
// );
|
|
||||||
// //TODO : REFACTOR to use queue or other way to handle this
|
|
||||||
// const recipients = [
|
|
||||||
// {
|
|
||||||
// userId: event.userId,
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
// if (event.newStatus === OrderStatus.COMPLETED) {
|
|
||||||
|
|
||||||
// if (!event?.userId) {
|
|
||||||
// this.logger.log(
|
|
||||||
// `User not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // const restaurant = await this.RestaurantRepository.findOne(event.);
|
|
||||||
// // if (!restaurant) {
|
|
||||||
// // this.logger.log(
|
|
||||||
// // `Restaurant not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
|
||||||
// // );
|
|
||||||
// // return;
|
|
||||||
// // }
|
|
||||||
// // const score = restaurant.score;
|
|
||||||
// // if (!score) {
|
|
||||||
// // this.logger.log(
|
|
||||||
// // `Score not found for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
|
||||||
// // );
|
|
||||||
// // return;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // increase score for user
|
|
||||||
// // const order = await this.OrderRepository.findOne(event.orderId);
|
|
||||||
// // if (!order) {
|
|
||||||
// // this.logger.log(
|
|
||||||
// // `Order not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
|
||||||
// // );
|
|
||||||
// // return;
|
|
||||||
// // }
|
|
||||||
// // this.userService.createWalletTransaction(event.userId, event., {
|
|
||||||
// // amount: order.subTotal,
|
|
||||||
// // type: WalletTransactionType.CREDIT,
|
|
||||||
// // reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT,
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// await this.notificationService.sendNotification({
|
|
||||||
// : event.,
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `لطفابرای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderCreatedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `تغییر وضعیت سفارش`,
|
|
||||||
// content: `لطفا برای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
|
|
||||||
// icon: `/`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// recipients,
|
|
||||||
// metadata: {
|
|
||||||
// priority: 1,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// await this.notificationService.sendNotification({
|
|
||||||
// : event.,
|
|
||||||
// message: {
|
|
||||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
|
|
||||||
// sms: {
|
|
||||||
// templateId: this.orderStatusChangedSmsTemplateId,
|
|
||||||
// parameters: {
|
|
||||||
// orderNumber: event.orderNumber,
|
|
||||||
// status: this.getStatusFarsi(event.newStatus),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// pushNotif: {
|
|
||||||
// title: `تغییر وضعیت سفارش`,
|
|
||||||
// content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
|
|
||||||
// icon: `/`,
|
|
||||||
// action: {
|
|
||||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
|
||||||
// url: ``,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// recipients,
|
|
||||||
// metadata: {
|
|
||||||
// priority: 1,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// } catch (error) {
|
|
||||||
// this.logger.error(
|
|
||||||
// `Failed to send notification for order status changed event: ${event.}`,
|
|
||||||
// error instanceof Error ? error.stack : String(error),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { Module, forwardRef } from '@nestjs/common';
|
|
||||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
|
||||||
import { OrderService } from './providers/order.service';
|
|
||||||
import { OrderController } from './controllers/order.controller';
|
|
||||||
import { Order } from './entities/order.entity';
|
|
||||||
import { OrderItem } from './entities/order-item.entity';
|
|
||||||
import { AuthModule } from '../auth/auth.module';
|
|
||||||
import { PaymentModule } from '../payment/payments.module';
|
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
|
||||||
import { OrderRepository } from './repositories/order.repository';
|
|
||||||
import { OrderListeners } from './listeners/order.listeners';
|
|
||||||
import { OrdersCrone } from './crone/order.crone';
|
|
||||||
import { AdminModule } from '../admin/admin.module';
|
|
||||||
import { NotificationsModule } from '../notification/notifications.module';
|
|
||||||
import { UserModule } from '../user/user.module';
|
|
||||||
import { UtilsModule } from '../util/utils.module';
|
|
||||||
import { ProductModule } from '../product/product.module';
|
|
||||||
import { TicketModule } from '../ticket/ticket.module';
|
|
||||||
import { OrderItemRepository } from './repositories/order-item.repository';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
MikroOrmModule.forFeature([Order, OrderItem]),
|
|
||||||
AuthModule,
|
|
||||||
forwardRef(() => PaymentModule),
|
|
||||||
JwtModule,
|
|
||||||
AdminModule,
|
|
||||||
NotificationsModule,
|
|
||||||
UtilsModule,
|
|
||||||
forwardRef(() => UserModule),
|
|
||||||
ProductModule,
|
|
||||||
forwardRef(() => TicketModule)
|
|
||||||
],
|
|
||||||
controllers: [OrderController],
|
|
||||||
providers: [OrderService, OrderRepository, OrderListeners,
|
|
||||||
OrdersCrone, OrderItemRepository],
|
|
||||||
exports: [OrderRepository, OrderService, OrderItemRepository],
|
|
||||||
})
|
|
||||||
export class OrderModule { }
|
|
||||||
@@ -1,636 +0,0 @@
|
|||||||
import { Injectable, BadRequestException, Logger, BadGatewayException } from '@nestjs/common';
|
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
|
||||||
import { OrderRepository } from '../repositories/order.repository';
|
|
||||||
import { FindOrdersDto } from '../dto/find-orders.dto';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import {
|
|
||||||
CreateOrderAsAdminDto,
|
|
||||||
CreateOrderAsUSerDto, CreateOrderItemAsUserDto,
|
|
||||||
CreateOrderItemDtoAsAdmin
|
|
||||||
} from '../dto/create-order.dto';
|
|
||||||
import { UserService } from 'src/modules/user/providers/user.service';
|
|
||||||
import {
|
|
||||||
OrderItemStatusEnum,
|
|
||||||
CreateOrderWithItems,
|
|
||||||
CreateOrderItemPopulated,
|
|
||||||
UpdateOrder,
|
|
||||||
UpdateOrderItem,
|
|
||||||
OrderStatusEnum
|
|
||||||
} from '../interface/order.interface';
|
|
||||||
import { OrderItemRepository } from '../repositories/order-item.repository';
|
|
||||||
import { ProductService } from 'src/modules/product/providers/product.service';
|
|
||||||
import { ProductRepository } from 'src/modules/product/repositories/product.repository';
|
|
||||||
import { TicketService } from 'src/modules/ticket/providers/tickets.service';
|
|
||||||
import { TicketRepository } from 'src/modules/ticket/repositories/tickets.repository';
|
|
||||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
|
||||||
import { Order } from '../entities/order.entity';
|
|
||||||
import { PaymentRepository } from 'src/modules/payment/repositories/payment.repository';
|
|
||||||
import { UpdateOrderItemDtoAsAdmin, UpdateOrderItemDtoAsUser } from '../dto/update-order-item.dto';
|
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
|
||||||
import { AdminService } from 'src/modules/admin/providers/admin.service';
|
|
||||||
import { OrderItem } from '../entities/order-item.entity';
|
|
||||||
import { UpdateOrderAsAdminDto } from '../dto/update-order.dto';
|
|
||||||
import { CreatePrintFormDto } from '../dto/create-print-form.dto';
|
|
||||||
import { FindOrderItemsDto } from '../dto/find-order-items.dto';
|
|
||||||
import { PermissionService } from 'src/modules/roles/providers/permissions.service';
|
|
||||||
import { PermissionEnum } from 'src/common/enums/permission.enum';
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OrderService {
|
|
||||||
private readonly logger = new Logger(OrderService.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly em: EntityManager,
|
|
||||||
private readonly orderRepository: OrderRepository,
|
|
||||||
private readonly orderItemRepository: OrderItemRepository,
|
|
||||||
private readonly userService: UserService,
|
|
||||||
private readonly adminService: AdminService,
|
|
||||||
private readonly productService: ProductService,
|
|
||||||
private readonly productRepository: ProductRepository,
|
|
||||||
private readonly ticketRepository: TicketRepository,
|
|
||||||
private readonly ticketService: TicketService,
|
|
||||||
private readonly eventEmitter: EventEmitter2,
|
|
||||||
private readonly adminRepository: AdminRepository,
|
|
||||||
private readonly paymentRepository: PaymentRepository,
|
|
||||||
private readonly permissionService: PermissionService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
async createOrderAsAdmin(adminId: string, dto: CreateOrderAsAdminDto) {
|
|
||||||
const order = await this.createOrder({ ...dto, adminId })
|
|
||||||
// await this.calculateOrder(order)
|
|
||||||
// this.em.flush()
|
|
||||||
this.logger.log("Order created as admin")
|
|
||||||
return order
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOrderAsUser(userId: string, dto: CreateOrderAsUSerDto) {
|
|
||||||
const order = await this.createOrder({ ...dto, userId })
|
|
||||||
this.logger.log("Order created as user")
|
|
||||||
return order
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateOrderAsAdmin(orderId: string, dto: UpdateOrderAsAdminDto) {
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
// const updateOrder = await this.updateOrder(order, dto)
|
|
||||||
|
|
||||||
// const updateOrder = await this.calculateOrder(order)
|
|
||||||
|
|
||||||
// await this.em.flush()
|
|
||||||
|
|
||||||
this.logger.log("Order updated as admin")
|
|
||||||
|
|
||||||
// return updateOrder
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async updateOrderAsAdmin2(orderId: string, dto: UpdateOrderAsAdminDto) {
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
const { items, ...rest } = dto
|
|
||||||
|
|
||||||
return this.em.transactional(async (em) => {
|
|
||||||
|
|
||||||
em.assign(order, rest)
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
|
|
||||||
if (item.id) { // update
|
|
||||||
const currentItem = order.items.find(i => i.id == item.id)
|
|
||||||
|
|
||||||
if (!currentItem) {
|
|
||||||
throw new BadGatewayException("Order Item not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
const { designerId, productId, ...itemRest } = item
|
|
||||||
|
|
||||||
let newProduct = currentItem.product
|
|
||||||
|
|
||||||
if (productId && productId !== currentItem.product.id) {
|
|
||||||
|
|
||||||
newProduct = await this.productService.findOneOrFail(productId)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let newDesigner: Admin | undefined
|
|
||||||
|
|
||||||
if (designerId && designerId !== currentItem.designer?.id) {
|
|
||||||
|
|
||||||
newDesigner = await this.adminService.findOrFail(designerId)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
em.assign(currentItem, { ...itemRest, designer: newDesigner, product: newProduct })
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (!item.productId) {
|
|
||||||
throw new BadRequestException(`Product id is required for item ${item.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const product = await this.productService.findOneOrFail(item.productId)
|
|
||||||
|
|
||||||
const newOrderItem = em.create(OrderItem, {
|
|
||||||
order,
|
|
||||||
product,
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitPrice: item.unitPrice,
|
|
||||||
|
|
||||||
adminDescription: item.adminDescription,
|
|
||||||
attachments: item.attachments,
|
|
||||||
attributes: item.attributes,
|
|
||||||
description: item.description,
|
|
||||||
discount: item.discount,
|
|
||||||
confirmedAt: item.confirmedAt,
|
|
||||||
status: OrderItemStatusEnum.CREATED
|
|
||||||
})
|
|
||||||
|
|
||||||
em.persist(newOrderItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
await em.flush()
|
|
||||||
|
|
||||||
this.logger.log(`order #${order.orderNumber} was updated as Admin`)
|
|
||||||
|
|
||||||
return order
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async addOrderItemAsUser(userId: string, orderId: string, dto: CreateOrderItemAsUserDto) {
|
|
||||||
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
// if (order.status !== OrderStatusEnum.CREATED) {
|
|
||||||
// throw new BadRequestException(`You can not update when status is ${order.status}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (order.user.id !== userId) {
|
|
||||||
throw new BadRequestException(`This order doest belongs to you!`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const product = await this.productService.findOneOrFail(dto.productId)
|
|
||||||
|
|
||||||
const orderItem = this.createOrderItemEntity(order, { ...dto, product })
|
|
||||||
|
|
||||||
await this.em.flush()
|
|
||||||
|
|
||||||
return orderItem
|
|
||||||
}
|
|
||||||
|
|
||||||
async addOrderItemAsAdmin(orderId: string, dto: CreateOrderItemDtoAsAdmin) {
|
|
||||||
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
const product = await this.productService.findOneOrFail(dto.productId)
|
|
||||||
|
|
||||||
let designer: Admin | undefined
|
|
||||||
if (dto.designerId) {
|
|
||||||
designer = await this.adminService.findOrFail(dto.designerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderItem = this.createOrderItemEntity(order, { ...dto, designer, product })
|
|
||||||
|
|
||||||
await this.em.flush()
|
|
||||||
|
|
||||||
// await this.calculateOrder(undefined, orderId)
|
|
||||||
|
|
||||||
// await this.em.flush()
|
|
||||||
|
|
||||||
return orderItem
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateOrderItemAsUser(userId: string, orderId: string, itemId: string, dto: UpdateOrderItemDtoAsUser) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
|
|
||||||
|
|
||||||
if (orderItem.order.user.id !== userId) {
|
|
||||||
throw new BadRequestException(`This order doesnt belongs to you`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (orderItem.order.status !== OrderStatusEnum.CREATED) {
|
|
||||||
// throw new BadRequestException(`You can not update when status is ${orderItem.order.status}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (orderItem.confirmedAt) {
|
|
||||||
throw new BadRequestException(`You can not update when item is confirmed`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = await this.updateOrderItem(orderItem, dto)
|
|
||||||
|
|
||||||
return updated
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateOrderItemAsAdmin(orderId: string, itemId: string, dto: UpdateOrderItemDtoAsAdmin) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
|
|
||||||
|
|
||||||
const updated = await this.updateOrderItem(orderItem, dto)
|
|
||||||
|
|
||||||
// await this.calculateOrder(undefined, orderId)
|
|
||||||
|
|
||||||
// await this.em.flush()
|
|
||||||
|
|
||||||
return updated
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPrintForm(orderId: string, itemId: string, dto: CreatePrintFormDto) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
|
|
||||||
|
|
||||||
const updated = await this.updateOrderItem(orderItem, { ...dto, printFormCreatedAt: new Date() })
|
|
||||||
|
|
||||||
return updated
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeOrderItemAsAdmin(orderId: string, itemId: string) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
|
|
||||||
|
|
||||||
await this.removeOrderItem(orderItem)
|
|
||||||
|
|
||||||
// await this.calculateOrder(undefined, orderId)
|
|
||||||
|
|
||||||
// await this.em.flush()
|
|
||||||
|
|
||||||
return { message: 'success' }
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeOrderItemAsUser(userId: string, orderId: string, itemId: string) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, itemId)
|
|
||||||
|
|
||||||
if (orderItem.order.user.id !== userId) {
|
|
||||||
throw new BadRequestException(`this order is not belongs to you`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (orderItem.order.status !== OrderStatusEnum.CREATED) {
|
|
||||||
// throw new BadRequestException(`You can not delete when order status is ${orderItem.order.status}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (orderItem.confirmedAt) {
|
|
||||||
throw new BadRequestException(`You can not delete when item is confirmed`)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.removeOrderItem(orderItem)
|
|
||||||
|
|
||||||
return { message: 'success' }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async findOrdersAsUser(userId: string, dto: FindOrdersDto) {
|
|
||||||
const orders = await this.orderRepository.findAllPaginated({ userId, ...dto })
|
|
||||||
return orders
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOrderRequestsAsAdmin(dto: FindOrdersDto) {
|
|
||||||
const orders = await this.orderRepository.findAllPaginated({ ...dto, isInvoiced: false })
|
|
||||||
return orders
|
|
||||||
}
|
|
||||||
|
|
||||||
async findInvoicedOrdersAsAdmin(adminId: string, dto: FindOrdersDto) {
|
|
||||||
const canSeeAll = await this.adminService.hasPermission(adminId, PermissionEnum.VIEW_All_INVOICED_ORDERS)
|
|
||||||
|
|
||||||
let canSeeAsigned = false
|
|
||||||
|
|
||||||
if (!canSeeAll) {
|
|
||||||
canSeeAsigned = await this.adminService.hasPermission(adminId, PermissionEnum.VIEW_MY_ASSIGNED_INVOICED_REQUESTS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canSeeAsigned && !canSeeAll) {
|
|
||||||
throw new BadRequestException("You dont have Permission")
|
|
||||||
}
|
|
||||||
|
|
||||||
let designerId = canSeeAll ? undefined : adminId
|
|
||||||
|
|
||||||
const orders = await this.orderRepository.findAllPaginated({ ...dto, isInvoiced: true, designerId })
|
|
||||||
|
|
||||||
return orders
|
|
||||||
}
|
|
||||||
|
|
||||||
async findPrintOrderItemsAsAdmin(dto: FindOrderItemsDto) {
|
|
||||||
const orderItems = await this.orderItemRepository.findAllPaginated({ ...dto, isPrintFormAdded: true, })
|
|
||||||
return orderItems
|
|
||||||
}
|
|
||||||
|
|
||||||
async findPrints(dto: FindOrdersDto) {
|
|
||||||
const orders = await this.orderRepository.findAllPaginated({ ...dto, statuses: [] })
|
|
||||||
return orders
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async confirmOrderItem(userId: string, orderId: string, orderItemId: string) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, orderItemId)
|
|
||||||
|
|
||||||
|
|
||||||
if (orderItem.order.user.id !== userId) {
|
|
||||||
throw new BadRequestException("Order Item does not belong to you")
|
|
||||||
}
|
|
||||||
|
|
||||||
orderItem.confirmedAt = new Date()
|
|
||||||
|
|
||||||
await this.em.persistAndFlush(orderItem)
|
|
||||||
|
|
||||||
return orderItem
|
|
||||||
}
|
|
||||||
|
|
||||||
async assignDesigner(orderId: string, orderItemId: string, designerId: string) {
|
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderId, orderItemId)
|
|
||||||
|
|
||||||
const designer = await this.adminRepository.findOne({ id: designerId })
|
|
||||||
|
|
||||||
if (!designer) {
|
|
||||||
throw new BadRequestException("designer not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
orderItem.designer = designer
|
|
||||||
|
|
||||||
await this.em.persistAndFlush(orderItem)
|
|
||||||
|
|
||||||
return orderItem
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeOrderAsUser(userId: string, orderId: string) {
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
if (order.user.id !== userId) {
|
|
||||||
throw new BadRequestException("this order doesnt belongs to you")
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (![OrderStatusEnum.CREATED].includes(order.status)) {
|
|
||||||
// throw new BadRequestException("Order can not be deleted")
|
|
||||||
// }
|
|
||||||
|
|
||||||
return this.hardDeleteOrder(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// async updateStatus(orderId: string, newStatus: OrderStatusEnum) {
|
|
||||||
|
|
||||||
// const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
// // order.status = newStatus
|
|
||||||
|
|
||||||
// await this.em.flush()
|
|
||||||
|
|
||||||
// return order
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async createInvoice(orderId: string) {
|
|
||||||
// const order = await this.updateStatus(orderId, OrderStatusEnum.INVOICED)
|
|
||||||
// return order
|
|
||||||
// }
|
|
||||||
|
|
||||||
async getOrderAsUser(userId: string, orderId: string) {
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
if (order.user.id !== userId) {
|
|
||||||
throw new BadRequestException("This order is not belongs to you")
|
|
||||||
}
|
|
||||||
return order
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Entity Methods ====================
|
|
||||||
|
|
||||||
private async createOrderItemEntity(order: Order, dto: CreateOrderItemPopulated, em?: EntityManager) {
|
|
||||||
const { attributes, description, quantity, attachments, discount,
|
|
||||||
unitPrice, product, designer, printAttributes, status } = dto
|
|
||||||
|
|
||||||
const eM = em ?? this.em
|
|
||||||
return eM.create(
|
|
||||||
OrderItem,
|
|
||||||
{
|
|
||||||
designer,
|
|
||||||
order,
|
|
||||||
attributes,
|
|
||||||
description,
|
|
||||||
quantity,
|
|
||||||
attachments,
|
|
||||||
discount,
|
|
||||||
unitPrice,
|
|
||||||
product,
|
|
||||||
printAttributes: printAttributes,
|
|
||||||
status
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Core Logic ====================
|
|
||||||
|
|
||||||
async createOrder(dto: CreateOrderWithItems) {
|
|
||||||
const { items, userId, enableTax,
|
|
||||||
estimatedDays, paymentMethod, adminId } = dto
|
|
||||||
|
|
||||||
// Validate and fetch all required entities
|
|
||||||
const user = await this.userService.findOrFail(userId)
|
|
||||||
const admin = adminId ? await this.adminService.findOrFail(adminId) : undefined
|
|
||||||
const products = await this.productService.findProductsByIds(items.map(item => item.productId))
|
|
||||||
const desiners = await this.adminService.findByIds(items.map(item => item.designerId).filter(id => id != null))
|
|
||||||
|
|
||||||
const productMap = new Map(
|
|
||||||
products.map(p => [Number(p.id), p])
|
|
||||||
);
|
|
||||||
const designerMap = new Map(
|
|
||||||
desiners.map(d => [d.id, d])
|
|
||||||
)
|
|
||||||
|
|
||||||
return this.em.transactional(async (em) => {
|
|
||||||
|
|
||||||
const order = em.create(Order, {
|
|
||||||
creator: admin,
|
|
||||||
user,
|
|
||||||
enableTax,
|
|
||||||
estimatedDays,
|
|
||||||
paymentMethod,
|
|
||||||
status: OrderStatusEnum.REQUEST,
|
|
||||||
})
|
|
||||||
|
|
||||||
em.persist(order)
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
const product = productMap.get(Number(item.productId))
|
|
||||||
|
|
||||||
if (!product) {
|
|
||||||
throw new BadRequestException("Product not found!")
|
|
||||||
}
|
|
||||||
let designer: undefined | Admin = undefined
|
|
||||||
|
|
||||||
if (item.designerId) {
|
|
||||||
designer = designerMap.get(item.designerId)
|
|
||||||
if (!designer) {
|
|
||||||
throw new BadRequestException("designer not found!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderItem = await this.createOrderItemEntity(order, { ...item, designer, product }, em)
|
|
||||||
|
|
||||||
order.items.add(orderItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO : calculation must be done after create order
|
|
||||||
|
|
||||||
// await this.calculateOrder(order)
|
|
||||||
|
|
||||||
await em.flush()
|
|
||||||
|
|
||||||
return order
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateOrder(order: Order, param: UpdateOrder) {
|
|
||||||
const { adminId, enableTax,
|
|
||||||
estimatedDays, paymentMethod, userId } = param
|
|
||||||
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
const user = await this.userService.findOrFail(userId)
|
|
||||||
order.user = user
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (adminId != undefined && adminId !== null) {
|
|
||||||
const admin = await this.adminService.findOrFail(adminId)
|
|
||||||
order.creator = admin
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (enableTax != undefined) {
|
|
||||||
order.enableTax = enableTax
|
|
||||||
}
|
|
||||||
|
|
||||||
if (estimatedDays != undefined) {
|
|
||||||
order.estimatedDays = estimatedDays
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paymentMethod != undefined) {
|
|
||||||
order.paymentMethod = paymentMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (status != undefined) {
|
|
||||||
// order.status = status
|
|
||||||
|
|
||||||
// if (status === OrderStatusEnum.INVOICED && !order.invoicedAt) {
|
|
||||||
// order.invoicedAt = new Date()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await this.em.persistAndFlush(order)
|
|
||||||
|
|
||||||
return order
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateOrderItem(orderItem: OrderItem, dto: UpdateOrderItem) {
|
|
||||||
const { attributes, description, product, quantity, printFormCreatedAt,
|
|
||||||
attachments, discount, unitPrice, designer, printAttributes, } = dto
|
|
||||||
|
|
||||||
if (product && orderItem.product.id !== product.id) {
|
|
||||||
orderItem.product = product
|
|
||||||
}
|
|
||||||
|
|
||||||
if (designer && orderItem.designer?.id !== designer.id) {
|
|
||||||
orderItem.designer = designer
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attributes != undefined) {
|
|
||||||
orderItem.attributes = attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description != undefined) {
|
|
||||||
orderItem.description = description
|
|
||||||
}
|
|
||||||
if (quantity != undefined) {
|
|
||||||
orderItem.quantity = quantity
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachments != undefined) {
|
|
||||||
orderItem.attachments = attachments
|
|
||||||
}
|
|
||||||
if (discount != undefined) {
|
|
||||||
orderItem.discount = discount
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unitPrice != undefined) {
|
|
||||||
orderItem.unitPrice = unitPrice
|
|
||||||
}
|
|
||||||
|
|
||||||
if (printFormCreatedAt != undefined) {
|
|
||||||
orderItem.printFormCreatedAt = printFormCreatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
if (printAttributes != undefined) {
|
|
||||||
orderItem.printAttributes = printAttributes
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.em.flush()
|
|
||||||
|
|
||||||
return orderItem
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeOrderItem(orderItem: OrderItem) {
|
|
||||||
|
|
||||||
await this.em.removeAndFlush(orderItem)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async hardDeleteOrder(orderId: string) {
|
|
||||||
const order = await this.findOrderOrFail(orderId)
|
|
||||||
|
|
||||||
if (order.payments.length > 0) {
|
|
||||||
throw new BadRequestException("order with payments can not be deleted!")
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.em.transactional(async (em) => {
|
|
||||||
await this.ticketRepository.nativeDelete({ orderItem: { order: { id: orderId } } })
|
|
||||||
await this.orderRepository.nativeDelete(order)
|
|
||||||
// order item will be deleted because cascade is true
|
|
||||||
|
|
||||||
// TODO : images also must be deleted
|
|
||||||
await em.flush()
|
|
||||||
})
|
|
||||||
|
|
||||||
return { message: "Order deleted successfully" }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Helper Methods ====================
|
|
||||||
|
|
||||||
async findOrderOrFail(orderId: string) {
|
|
||||||
const order = await this.orderRepository.findOne({ id: orderId },
|
|
||||||
{ populate: ['items', 'items.product', 'payments', 'user', 'items.designer'] })
|
|
||||||
if (!order) {
|
|
||||||
throw new BadRequestException('Order not found')
|
|
||||||
}
|
|
||||||
return order
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOrderItemOrFail(orderId: string, itemId: string) {
|
|
||||||
const orderItem = await this.orderItemRepository.findOne({ id: itemId, order: { id: orderId } },
|
|
||||||
{ populate: ['order', 'order.user'] })
|
|
||||||
if (!orderItem) {
|
|
||||||
throw new BadRequestException('Order item not found')
|
|
||||||
}
|
|
||||||
return orderItem
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOrderItemOrFailByItemId(itemId: string) {
|
|
||||||
const orderItem = await this.orderItemRepository.findOne({ id: itemId },
|
|
||||||
{ populate: ['order', 'order.user'] })
|
|
||||||
if (!orderItem) {
|
|
||||||
throw new BadRequestException('Order item not found')
|
|
||||||
}
|
|
||||||
return orderItem
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { EntityManager, EntityRepository, FilterQuery } from '@mikro-orm/postgresql';
|
|
||||||
import { OrderItem } from '../entities/order-item.entity';
|
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
|
||||||
import { FindOrderItemsDto } from '../dto/find-order-items.dto';
|
|
||||||
|
|
||||||
class FindOrderItemsOpts extends FindOrderItemsDto {
|
|
||||||
userId?: string;
|
|
||||||
isPrintFormAdded?: boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OrderItemRepository extends EntityRepository<OrderItem> {
|
|
||||||
constructor(readonly em: EntityManager) {
|
|
||||||
super(em, OrderItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAllPaginated(opts: FindOrderItemsOpts): Promise<PaginatedResult<OrderItem>> {
|
|
||||||
const {
|
|
||||||
page = 1,
|
|
||||||
limit = 10,
|
|
||||||
search,
|
|
||||||
orderBy = 'createdAt',
|
|
||||||
order = 'desc',
|
|
||||||
userId,
|
|
||||||
isPrintFormAdded
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
const where: FilterQuery<OrderItem> = {};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
where.order = { user: { id: userId } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPrintFormAdded != undefined) {
|
|
||||||
if (isPrintFormAdded == true) {
|
|
||||||
where.printFormCreatedAt = { $not: null }
|
|
||||||
} else {
|
|
||||||
where.printFormCreatedAt = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Search by order number or user information
|
|
||||||
if (search) {
|
|
||||||
const searchPattern = `%${search}%`;
|
|
||||||
const searchConditions: any[] = [
|
|
||||||
{ user: { firstName: { $ilike: searchPattern } } },
|
|
||||||
{ user: { lastName: { $ilike: searchPattern } } },
|
|
||||||
{ user: { phone: { $ilike: searchPattern } } },
|
|
||||||
];
|
|
||||||
|
|
||||||
// If search is numeric, also search by orderNumber
|
|
||||||
const numericSearch = Number(search);
|
|
||||||
if (!isNaN(numericSearch) && isFinite(numericSearch)) {
|
|
||||||
searchConditions.push({ orderNumber: numericSearch });
|
|
||||||
}
|
|
||||||
|
|
||||||
where.$or = searchConditions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, fetch orders without reviews
|
|
||||||
const [data, total] = await this.findAndCount(where, {
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
|
||||||
populate: ['designer', 'product'],
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(total / limit);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
meta: {
|
|
||||||
total,
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
totalPages,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
|
||||||
import { FilterQuery } from '@mikro-orm/core';
|
|
||||||
import { Order } from '../entities/order.entity';
|
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
|
||||||
import { OrderStatusEnum } from '../interface/order.interface';
|
|
||||||
import { FindOrdersDto } from '../dto/find-orders.dto';
|
|
||||||
|
|
||||||
|
|
||||||
class FindOrdersOpts extends FindOrdersDto {
|
|
||||||
userId?: string;
|
|
||||||
isInvoiced?: boolean;
|
|
||||||
designerId?: string
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class OrderRepository extends EntityRepository<Order> {
|
|
||||||
constructor(readonly em: EntityManager) {
|
|
||||||
super(em, Order);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAllPaginated(opts: FindOrdersOpts): Promise<PaginatedResult<Order>> {
|
|
||||||
const {
|
|
||||||
page = 1,
|
|
||||||
limit = 10,
|
|
||||||
statuses,
|
|
||||||
search,
|
|
||||||
orderBy = 'createdAt',
|
|
||||||
order = 'desc',
|
|
||||||
userId,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
isInvoiced,
|
|
||||||
designerId
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
const where: FilterQuery<Order> = {};
|
|
||||||
|
|
||||||
// Filter by statuses
|
|
||||||
if (statuses) {
|
|
||||||
// Ensure statuses is always an array and normalize it
|
|
||||||
const normalizedStatuses = Array.isArray(statuses) ? statuses : [statuses];
|
|
||||||
// Filter out any empty values
|
|
||||||
const validStatuses = normalizedStatuses.filter((s): s is OrderStatusEnum => !!s);
|
|
||||||
|
|
||||||
if (validStatuses.length > 0) {
|
|
||||||
// Use direct equality for single value, $in for multiple values to avoid SQL generation issues
|
|
||||||
if (validStatuses.length === 1) {
|
|
||||||
where.status = validStatuses[0];
|
|
||||||
} else {
|
|
||||||
where.status = { $in: validStatuses };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userId) {
|
|
||||||
where.user = { id: userId };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInvoiced != undefined) {
|
|
||||||
if (isInvoiced == true) {
|
|
||||||
where.invoicedAt = { $not: null }
|
|
||||||
} else {
|
|
||||||
where.invoicedAt = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (designerId) {
|
|
||||||
where.items = { $some: { designer: { id: designerId } } }
|
|
||||||
}
|
|
||||||
// Filter by date range
|
|
||||||
if (from || to) {
|
|
||||||
where.createdAt = {};
|
|
||||||
if (from) {
|
|
||||||
where.createdAt.$gte = new Date(from);
|
|
||||||
}
|
|
||||||
if (to) {
|
|
||||||
where.createdAt.$lte = new Date(to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search by order number or user information
|
|
||||||
if (search) {
|
|
||||||
const searchPattern = `%${search}%`;
|
|
||||||
const searchConditions: any[] = [
|
|
||||||
{ user: { firstName: { $ilike: searchPattern } } },
|
|
||||||
{ user: { lastName: { $ilike: searchPattern } } },
|
|
||||||
{ user: { phone: { $ilike: searchPattern } } },
|
|
||||||
];
|
|
||||||
|
|
||||||
// If search is numeric, also search by orderNumber
|
|
||||||
const numericSearch = Number(search);
|
|
||||||
if (!isNaN(numericSearch) && isFinite(numericSearch)) {
|
|
||||||
searchConditions.push({ orderNumber: numericSearch });
|
|
||||||
}
|
|
||||||
|
|
||||||
where.$or = searchConditions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, fetch orders without reviews
|
|
||||||
const [data, total] = await this.findAndCount(where, {
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
|
||||||
populate: ['items', 'items.product', 'user'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Collect all (orderId, productId) pairs for efficient review lookup
|
|
||||||
// const orderproductPairs: Array<{ orderId: string; productId: bigint }> = [];
|
|
||||||
// for (const order of data) {
|
|
||||||
// for (const item of order.items.getItems()) {
|
|
||||||
// if (item.product?.id) {
|
|
||||||
// orderproductPairs.push({ orderId: order.id, productId: item.product.id });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Map reviewIds to products
|
|
||||||
// for (const order of data) {
|
|
||||||
// for (const item of order.items.getItems()) {
|
|
||||||
// if (item.product) {
|
|
||||||
// const key = `${order.id}-${item.product.id}`;
|
|
||||||
// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
// const product = item.product as any;
|
|
||||||
// // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(total / limit);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
meta: {
|
|
||||||
total,
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
totalPages,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
Delete,
|
Delete,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PaymentService } from '../services/payments.service';
|
// import { PaymentService } from '../services/payments.service';
|
||||||
import {
|
import {
|
||||||
ApiTags,
|
ApiTags,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
@@ -25,72 +25,72 @@ import { PermissionEnum } from 'src/common/enums/permission.enum';
|
|||||||
@Controller()
|
@Controller()
|
||||||
export class PaymentController {
|
export class PaymentController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly paymentService: PaymentService,
|
// private readonly paymentService: PaymentService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@Post('public/payments/order/:id/pay')
|
// @Post('public/payments/order/:id/pay')
|
||||||
@UseGuards(AuthGuard)
|
// @UseGuards(AuthGuard)
|
||||||
@ApiOperation({ summary: 'Pay for an order' })
|
// @ApiOperation({ summary: 'Pay for an order' })
|
||||||
payAnOrder(@Param('id') orderId: string, @Body() dto: PayOrderDto) {
|
// payAnOrder(@Param('id') orderId: string, @Body() dto: PayOrderDto) {
|
||||||
return this.paymentService.payOrder(orderId, dto);
|
// return this.paymentService.payOrder(orderId, dto);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('public/payments/online/:token/verify')
|
// @Post('public/payments/online/:token/verify')
|
||||||
@UseGuards(AuthGuard)
|
// @UseGuards(AuthGuard)
|
||||||
@ApiOperation({ summary: 'Verify online payment' })
|
// @ApiOperation({ summary: 'Verify online payment' })
|
||||||
verifyOnlinePayment(@Param('paymentId') token: string) {
|
// verifyOnlinePayment(@Param('paymentId') token: string) {
|
||||||
return this.paymentService.verifyOnlinePayment(token);
|
// return this.paymentService.verifyOnlinePayment(token);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
@Get('public/payments')
|
// @Get('public/payments')
|
||||||
@ApiOperation({ summary: 'get all payments as user' })
|
// @ApiOperation({ summary: 'get all payments as user' })
|
||||||
findAllByUser(@UserId() userId: string, @Query() dto: FindPaymentsDto) {
|
// findAllByUser(@UserId() userId: string, @Query() dto: FindPaymentsDto) {
|
||||||
return this.paymentService.findAllByUser(dto, userId);
|
// return this.paymentService.findAllByUser(dto, userId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/*=============== Admin =============== */
|
// /*=============== Admin =============== */
|
||||||
|
|
||||||
@Post('admin/payments/order/:id/pay')
|
// @Post('admin/payments/order/:id/pay')
|
||||||
@UseGuards(AdminAuthGuard)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
// @Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
||||||
@ApiOperation({ summary: 'Pay for an order as Admin' })
|
// @ApiOperation({ summary: 'Pay for an order as Admin' })
|
||||||
payOrderAsAdmin(@Param('orderId') orderId: string, @AdminId() adminId: string, @Body() dto: PayOrderDto) {
|
// payOrderAsAdmin(@Param('orderId') orderId: string, @AdminId() adminId: string, @Body() dto: PayOrderDto) {
|
||||||
return this.paymentService.payOrder(orderId, dto, adminId);
|
// return this.paymentService.payOrder(orderId, dto, adminId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('admin/payments/online/:token/verify')
|
// @Post('admin/payments/online/:token/verify')
|
||||||
@UseGuards(AdminAuthGuard)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
// @Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
||||||
@ApiOperation({ summary: 'Verify online payment as admin' })
|
// @ApiOperation({ summary: 'Verify online payment as admin' })
|
||||||
verifyOnlinePaymentAsAdmin(@Param('paymentId') token: string) {
|
// verifyOnlinePaymentAsAdmin(@Param('paymentId') token: string) {
|
||||||
return this.paymentService.verifyOnlinePayment(token);
|
// return this.paymentService.verifyOnlinePayment(token);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post('admin/payments/:paymentId/cash/verify')
|
// @Post('admin/payments/:paymentId/cash/verify')
|
||||||
@UseGuards(AdminAuthGuard)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
// @Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
||||||
@ApiOperation({ summary: 'Verify cash payment' })
|
// @ApiOperation({ summary: 'Verify cash payment' })
|
||||||
confirmPayment(@Param('paymentId') paymentId: string) {
|
// confirmPayment(@Param('paymentId') paymentId: string) {
|
||||||
return this.paymentService.confirmCashPayment(paymentId);
|
// return this.paymentService.confirmCashPayment(paymentId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Delete('admin/payments/:paymentId/cash/deny')
|
// @Delete('admin/payments/:paymentId/cash/deny')
|
||||||
@UseGuards(AdminAuthGuard)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
// @Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
||||||
@ApiOperation({ summary: 'Deny cash payment' })
|
// @ApiOperation({ summary: 'Deny cash payment' })
|
||||||
denyPayment(@Param('paymentId') paymentId: string) {
|
// denyPayment(@Param('paymentId') paymentId: string) {
|
||||||
return this.paymentService.denyCashPayment(paymentId);
|
// return this.paymentService.denyCashPayment(paymentId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
@Get('admin/payments')
|
// @Get('admin/payments')
|
||||||
@UseGuards(AdminAuthGuard)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
// @Permissions(PermissionEnum.MANAGE_PAYMENTS)
|
||||||
@ApiOperation({ summary: 'get all payments as admin' })
|
// @ApiOperation({ summary: 'get all payments as admin' })
|
||||||
findAllPayments(@Body() dto: FindPaymentsDto) {
|
// findAllPayments(@Body() dto: FindPaymentsDto) {
|
||||||
return this.paymentService.findAll(dto);
|
// return this.paymentService.findAll(dto);
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class FindPaymentsDto {
|
|||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
orderId?: string;
|
invoiceId?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ format: 'date-time' })
|
@ApiPropertyOptional({ format: 'date-time' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Entity, ManyToOne, Property, Enum, Index, PrimaryKey, OptionalProps } from '@mikro-orm/core';
|
import { Entity, ManyToOne, Property, Enum, Index, PrimaryKey, OptionalProps } from '@mikro-orm/core';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Order } from '../../order/entities/order.entity';
|
|
||||||
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||||
import { ulid } from 'ulid';
|
import { ulid } from 'ulid';
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
||||||
|
import { Invoice } from 'src/modules/invoice/entities/invoice.entity';
|
||||||
|
|
||||||
@Entity({ tableName: 'payments' })
|
@Entity({ tableName: 'payments' })
|
||||||
export class Payment extends BaseEntity {
|
export class Payment extends BaseEntity {
|
||||||
@@ -12,9 +12,9 @@ export class Payment extends BaseEntity {
|
|||||||
@PrimaryKey({ type: 'string', columnType: 'char(26)' })
|
@PrimaryKey({ type: 'string', columnType: 'char(26)' })
|
||||||
id: string = ulid()
|
id: string = ulid()
|
||||||
|
|
||||||
@ManyToOne(() => Order)
|
@ManyToOne(() => Invoice)
|
||||||
@Index()
|
@Index()
|
||||||
order!: Order;
|
invoice!: Invoice;
|
||||||
|
|
||||||
@ManyToOne(() => Admin, { nullable: true })
|
@ManyToOne(() => Admin, { nullable: true })
|
||||||
creator?: Admin
|
creator?: Admin
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
||||||
import type { Order } from 'src/modules/order/entities/order.entity';
|
import { Invoice } from 'src/modules/invoice/entities/invoice.entity';
|
||||||
import { User } from 'src/modules/user/entities/user.entity';
|
import { User } from 'src/modules/user/entities/user.entity';
|
||||||
|
|
||||||
export enum PaymentMethodEnum {
|
export enum PaymentMethodEnum {
|
||||||
@@ -28,7 +28,7 @@ export interface ICreatePayment {
|
|||||||
export interface OrderPaymentContext {
|
export interface OrderPaymentContext {
|
||||||
admin: Admin|null
|
admin: Admin|null
|
||||||
user: User;
|
user: User;
|
||||||
order: Order;
|
invoice: Invoice;
|
||||||
amount: number;
|
amount: number;
|
||||||
method: PaymentMethodEnum
|
method: PaymentMethodEnum
|
||||||
attachments?: string[]
|
attachments?: string[]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { PermissionEnum } from 'src/common/enums/permission.enum';
|
|||||||
import { NotifEvent } from 'src/modules/notification/interfaces/notification.interface';
|
import { NotifEvent } from 'src/modules/notification/interfaces/notification.interface';
|
||||||
import { NotificationService } from 'src/modules/notification/services/notification.service';
|
import { NotificationService } from 'src/modules/notification/services/notification.service';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { OrderService } from 'src/modules/order/providers/order.service';
|
// import { OrderService } from 'src/modules/order/providers/order.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PaymentListeners {
|
export class PaymentListeners {
|
||||||
@@ -17,7 +17,7 @@ export class PaymentListeners {
|
|||||||
private readonly adminService: AdminRepository,
|
private readonly adminService: AdminRepository,
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly orderService: OrderService,
|
// private readonly orderService: OrderService,
|
||||||
) {
|
) {
|
||||||
this.orderCreatedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ORDER_CREATED') ?? '123';
|
this.orderCreatedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ORDER_CREATED') ?? '123';
|
||||||
this.paymentSucceedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ONLINE_PAYMENT_SUCCEED') ?? '123';
|
this.paymentSucceedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ONLINE_PAYMENT_SUCCEED') ?? '123';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Module, forwardRef } from '@nestjs/common';
|
import { Module, forwardRef } from '@nestjs/common';
|
||||||
import { PaymentService } from './services/payments.service';
|
// import { PaymentsService } from './services/payments.service';
|
||||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||||
import { PaymentController } from './controllers/payment.controller';
|
import { PaymentController } from './controllers/payment.controller';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
@@ -11,7 +11,7 @@ import { PaymentRepository } from './repositories/payment.repository';
|
|||||||
import { PaymentListeners } from './listeners/payment.listeners';
|
import { PaymentListeners } from './listeners/payment.listeners';
|
||||||
import { AdminModule } from '../admin/admin.module';
|
import { AdminModule } from '../admin/admin.module';
|
||||||
import { NotificationsModule } from '../notification/notifications.module';
|
import { NotificationsModule } from '../notification/notifications.module';
|
||||||
import { OrderModule } from '../order/order.module';
|
// import { OrderModule } from '../order/order.module';
|
||||||
import { UserModule } from '../user/user.module';
|
import { UserModule } from '../user/user.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -21,12 +21,12 @@ import { UserModule } from '../user/user.module';
|
|||||||
JwtModule,
|
JwtModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
NotificationsModule,
|
NotificationsModule,
|
||||||
forwardRef(() => OrderModule),
|
// forwardRef(() => OrderModule),
|
||||||
UserModule
|
UserModule
|
||||||
],
|
],
|
||||||
controllers: [PaymentController],
|
controllers: [PaymentController],
|
||||||
providers: [
|
providers: [
|
||||||
PaymentService,
|
// PaymentService,
|
||||||
PaymentRepository,
|
PaymentRepository,
|
||||||
ZarinpalGateway,
|
ZarinpalGateway,
|
||||||
GatewayManager,
|
GatewayManager,
|
||||||
@@ -34,7 +34,7 @@ import { UserModule } from '../user/user.module';
|
|||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
PaymentRepository,
|
PaymentRepository,
|
||||||
PaymentService,
|
// PaymentService,
|
||||||
// PaymentGatewayService,
|
// PaymentGatewayService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class PaymentRepository extends EntityRepository<Payment> {
|
|||||||
userId,
|
userId,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
orderId
|
invoiceId
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
@@ -51,11 +51,11 @@ export class PaymentRepository extends EntityRepository<Payment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
where.order = { user: { id: userId } };
|
where.invoice = { user: { id: userId } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orderId) {
|
if (invoiceId) {
|
||||||
where.order = { id: orderId }
|
where.invoice = { id: invoiceId }
|
||||||
}
|
}
|
||||||
// Filter by date range
|
// Filter by date range
|
||||||
if (from || to) {
|
if (from || to) {
|
||||||
|
|||||||
@@ -1,302 +1,301 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
// import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
// import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
// import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
import { Order } from '../../order/entities/order.entity';
|
// import { Logger } from '@nestjs/common';
|
||||||
import { Logger } from '@nestjs/common';
|
// import { GatewayManager } from '../gateways/gateway.manager';
|
||||||
import { GatewayManager } from '../gateways/gateway.manager';
|
// import { OrderPaymentContext } from '../interface/payment';
|
||||||
import { OrderPaymentContext } from '../interface/payment';
|
// import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum';
|
||||||
import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum';
|
// import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
// import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
|
||||||
import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
|
// import { OrderService } from 'src/modules/order/providers/order.service';
|
||||||
import { OrderService } from 'src/modules/order/providers/order.service';
|
// import { PayOrderDto } from '../dto/pay-order.dto';
|
||||||
import { PayOrderDto } from '../dto/pay-order.dto';
|
// import { PaymentRepository } from '../repositories/payment.repository';
|
||||||
import { PaymentRepository } from '../repositories/payment.repository';
|
// import { CreditRepository } from 'src/modules/user/repositories/credit.repository';
|
||||||
import { CreditRepository } from 'src/modules/user/repositories/credit.repository';
|
// import { CreditService } from 'src/modules/user/providers/credit.service';
|
||||||
import { CreditService } from 'src/modules/user/providers/credit.service';
|
// import { CreditTransactionType } from 'src/modules/user/interface/user';
|
||||||
import { CreditTransactionType } from 'src/modules/user/interface/user';
|
// import { Payment } from '../entities/payment.entity';
|
||||||
import { Payment } from '../entities/payment.entity';
|
// import { FindPaymentsDto } from '../dto/find-payments.dto';
|
||||||
import { FindPaymentsDto } from '../dto/find-payments.dto';
|
// import { Admin } from 'src/modules/admin/entities/admin.entity';
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
// import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
// import { CreditTransaction } from 'src/modules/user/entities/credit-transaction.entity';
|
||||||
import { CreditTransaction } from 'src/modules/user/entities/credit-transaction.entity';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
// @Injectable()
|
||||||
export class PaymentService {
|
// export class PaymentService {
|
||||||
private readonly logger = new Logger(PaymentService.name);
|
// private readonly logger = new Logger(PaymentService.name);
|
||||||
|
|
||||||
constructor(
|
// constructor(
|
||||||
private readonly em: EntityManager,
|
// private readonly em: EntityManager,
|
||||||
private readonly orderService: OrderService,
|
// private readonly orderService: OrderService,
|
||||||
private readonly paymentRepository: PaymentRepository,
|
// private readonly paymentRepository: PaymentRepository,
|
||||||
private readonly gatewayManager: GatewayManager,
|
// private readonly gatewayManager: GatewayManager,
|
||||||
private readonly eventEmitter: EventEmitter2,
|
// private readonly eventEmitter: EventEmitter2,
|
||||||
private readonly creditService: CreditService,
|
// private readonly creditService: CreditService,
|
||||||
private readonly creditRepository: CreditRepository,
|
// private readonly creditRepository: CreditRepository,
|
||||||
private readonly adminRepository: AdminRepository,
|
// private readonly adminRepository: AdminRepository,
|
||||||
) { }
|
// ) { }
|
||||||
|
|
||||||
async payOrder(orderId: string, dto: PayOrderDto, adminId?: string): Promise<{ paymentUrl: string | null }> {
|
// async payOrder(orderId: string, dto: PayOrderDto, adminId?: string): Promise<{ paymentUrl: string | null }> {
|
||||||
|
|
||||||
const ctx = await this.loadAndValidateOrder(orderId, dto, adminId)
|
// const ctx = await this.loadAndValidateOrder(orderId, dto, adminId)
|
||||||
|
|
||||||
switch (dto.method) {
|
// switch (dto.method) {
|
||||||
case PaymentMethodEnum.Cash:
|
// case PaymentMethodEnum.Cash:
|
||||||
await this.handleCashPayment(ctx);
|
// await this.handleCashPayment(ctx);
|
||||||
return { paymentUrl: null };
|
// return { paymentUrl: null };
|
||||||
|
|
||||||
case PaymentMethodEnum.Credit:
|
// case PaymentMethodEnum.Credit:
|
||||||
await this.handleCreditPayment(ctx);
|
// await this.handleCreditPayment(ctx);
|
||||||
return { paymentUrl: null };
|
// return { paymentUrl: null };
|
||||||
|
|
||||||
case PaymentMethodEnum.Online:
|
// case PaymentMethodEnum.Online:
|
||||||
return this.handleOnlinePayment(ctx);
|
// return this.handleOnlinePayment(ctx);
|
||||||
|
|
||||||
default:
|
// default:
|
||||||
throw new BadRequestException(PaymentMessage.UNSUPPORTED_PAYMENT_METHOD);
|
// throw new BadRequestException(PaymentMessage.UNSUPPORTED_PAYMENT_METHOD);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
private async loadAndValidateOrder(orderId: string, dto: PayOrderDto, adminId?: string): Promise<OrderPaymentContext> {
|
// private async loadAndValidateOrder(orderId: string, dto: PayOrderDto, adminId?: string): Promise<OrderPaymentContext> {
|
||||||
const { amount, method, attachments, description, gateway } = dto
|
// const { amount, method, attachments, description, gateway } = dto
|
||||||
|
|
||||||
const order = await this.orderService.findOrderOrFail(orderId)
|
// const order = await this.orderService.findOrderOrFail(orderId)
|
||||||
|
|
||||||
if (!order.balance) {
|
// if (!order.balance) {
|
||||||
throw new NotFoundException('Balance is not set');
|
// throw new NotFoundException('Balance is not set');
|
||||||
}
|
// }
|
||||||
|
|
||||||
let admin: null | Admin = null
|
// let admin: null | Admin = null
|
||||||
if (adminId) {
|
// if (adminId) {
|
||||||
admin = await this.adminRepository.findOne({ id: adminId })
|
// admin = await this.adminRepository.findOne({ id: adminId })
|
||||||
if (!admin) {
|
// if (!admin) {
|
||||||
throw new BadRequestException("Admin not found")
|
// throw new BadRequestException("Admin not found")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (amount <= 0) {
|
// if (amount <= 0) {
|
||||||
throw new BadRequestException(PaymentMessage.AMOUNT_MUST_BE_GREATER_THAN_ZERO);
|
// throw new BadRequestException(PaymentMessage.AMOUNT_MUST_BE_GREATER_THAN_ZERO);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (amount > order.balance) {
|
// if (amount > order.balance) {
|
||||||
throw new BadRequestException(`You cant pay more than ${order.balance}`);
|
// throw new BadRequestException(`You cant pay more than ${order.balance}`);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
admin,
|
// admin,
|
||||||
user: order.user,
|
// user: order.user,
|
||||||
order,
|
// order,
|
||||||
amount,
|
// amount,
|
||||||
attachments,
|
// attachments,
|
||||||
description,
|
// description,
|
||||||
gateway,
|
// gateway,
|
||||||
method
|
// method
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async handleCashPayment(ctx: OrderPaymentContext): Promise<void> {
|
// private async handleCashPayment(ctx: OrderPaymentContext): Promise<void> {
|
||||||
const { order, amount, method, attachments, description, admin } = ctx
|
// const { order, amount, method, attachments, description, admin } = ctx
|
||||||
const payment = this.paymentRepository.create({
|
// const payment = this.paymentRepository.create({
|
||||||
creator: admin,
|
// creator: admin,
|
||||||
order,
|
// order,
|
||||||
amount,
|
// amount,
|
||||||
method,
|
// method,
|
||||||
status: PaymentStatusEnum.Pending,
|
// status: PaymentStatusEnum.Pending,
|
||||||
attachments,
|
// attachments,
|
||||||
description
|
// description
|
||||||
})
|
// })
|
||||||
await this.em.persistAndFlush(payment)
|
// await this.em.persistAndFlush(payment)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
private async handleCreditPayment(ctx: OrderPaymentContext): Promise<void> {
|
// private async handleCreditPayment(ctx: OrderPaymentContext): Promise<void> {
|
||||||
const { order, amount, user, admin } = ctx
|
// const { order, amount, user, admin } = ctx
|
||||||
|
|
||||||
await this.em.transactional(async em => {
|
// await this.em.transactional(async em => {
|
||||||
// 1. get User remained Credit
|
// // 1. get User remained Credit
|
||||||
const remainedCredit = await this.creditService.getCurrentCredit(user.id)
|
// const remainedCredit = await this.creditService.getCurrentCredit(user.id)
|
||||||
|
|
||||||
if (remainedCredit < amount) {
|
// if (remainedCredit < amount) {
|
||||||
throw new BadRequestException("You Dont have enough Credit")
|
// throw new BadRequestException("You Dont have enough Credit")
|
||||||
}
|
// }
|
||||||
|
|
||||||
const newBalance = remainedCredit - amount;
|
// const newBalance = remainedCredit - amount;
|
||||||
// 2. Create payment record
|
// // 2. Create payment record
|
||||||
const payment = em.create(Payment, {
|
// const payment = em.create(Payment, {
|
||||||
creator: admin,
|
// creator: admin,
|
||||||
amount: ctx.amount,
|
// amount: ctx.amount,
|
||||||
method: PaymentMethodEnum.Credit,
|
// method: PaymentMethodEnum.Credit,
|
||||||
gateway: null,
|
// gateway: null,
|
||||||
order,
|
// order,
|
||||||
status: PaymentStatusEnum.Paid,
|
// status: PaymentStatusEnum.Paid,
|
||||||
paidAt: new Date(),
|
// paidAt: new Date(),
|
||||||
});
|
// });
|
||||||
// 3. Create Credit transaction record
|
// // 3. Create Credit transaction record
|
||||||
const newCreditTransaction = em.create(CreditTransaction, {
|
// const newCreditTransaction = em.create(CreditTransaction, {
|
||||||
user: order.user,
|
// user: order.user,
|
||||||
amount,
|
// amount,
|
||||||
type: CreditTransactionType.WITHDRAW,
|
// type: CreditTransactionType.WITHDRAW,
|
||||||
balance: newBalance,
|
// balance: newBalance,
|
||||||
orderId: order.id,
|
// orderId: order.id,
|
||||||
});
|
// });
|
||||||
|
|
||||||
await em.persistAndFlush([payment, newCreditTransaction]);
|
// await em.persistAndFlush([payment, newCreditTransaction]);
|
||||||
});
|
// });
|
||||||
|
|
||||||
this.eventEmitter.emit(
|
// this.eventEmitter.emit(
|
||||||
paymentSucceedEvent.name,
|
// paymentSucceedEvent.name,
|
||||||
new paymentSucceedEvent(ctx.order.id, String(ctx.order?.orderNumber) || '', ctx.method, ctx.amount),
|
// new paymentSucceedEvent(ctx.order.id, String(ctx.order?.orderNumber) || '', ctx.method, ctx.amount),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async handleOnlinePayment(ctx: OrderPaymentContext): Promise<{ paymentUrl: string }> {
|
// private async handleOnlinePayment(ctx: OrderPaymentContext): Promise<{ paymentUrl: string }> {
|
||||||
const { amount, order, gateway: requestedGateway, admin } = ctx
|
// const { amount, order, gateway: requestedGateway, admin } = ctx
|
||||||
|
|
||||||
const gateway = this.gatewayManager.get(requestedGateway!);
|
// const gateway = this.gatewayManager.get(requestedGateway!);
|
||||||
|
|
||||||
const { token, paymentUrl } = await gateway.requestPayment({
|
// const { token, paymentUrl } = await gateway.requestPayment({
|
||||||
amount: ctx.amount,
|
// amount: ctx.amount,
|
||||||
orderId: ctx.order.id,
|
// orderId: ctx.order.id,
|
||||||
});
|
// });
|
||||||
|
|
||||||
const payment = this.paymentRepository.create({
|
// const payment = this.paymentRepository.create({
|
||||||
creator: admin,
|
// creator: admin,
|
||||||
order,
|
// order,
|
||||||
amount,
|
// amount,
|
||||||
method: PaymentMethodEnum.Online,
|
// method: PaymentMethodEnum.Online,
|
||||||
status: PaymentStatusEnum.Pending,
|
// status: PaymentStatusEnum.Pending,
|
||||||
gateway: requestedGateway,
|
// gateway: requestedGateway,
|
||||||
token,
|
// token,
|
||||||
})
|
// })
|
||||||
|
|
||||||
await this.em.persistAndFlush(payment);
|
// await this.em.persistAndFlush(payment);
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
paymentUrl,
|
// paymentUrl,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
async verifyOnlinePayment(token: string): Promise<Payment> {
|
// async verifyOnlinePayment(token: string): Promise<Payment> {
|
||||||
const payment = await this.em.transactional(async em => {
|
// const payment = await this.em.transactional(async em => {
|
||||||
const payment = await this.paymentRepository.findOne(
|
// const payment = await this.paymentRepository.findOne(
|
||||||
{ token },
|
// { token },
|
||||||
{ populate: ['order'] },
|
// { populate: ['order'] },
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (!payment) {
|
// if (!payment) {
|
||||||
throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
|
// throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (payment.status === PaymentStatusEnum.Paid) {
|
// if (payment.status === PaymentStatusEnum.Paid) {
|
||||||
return payment;
|
// return payment;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!payment.gateway) {
|
// if (!payment.gateway) {
|
||||||
throw new NotFoundException("Payment gateway not found");
|
// throw new NotFoundException("Payment gateway not found");
|
||||||
}
|
// }
|
||||||
|
|
||||||
const gateway = this.gatewayManager.get(payment.gateway);
|
// const gateway = this.gatewayManager.get(payment.gateway);
|
||||||
|
|
||||||
const { raw, referenceId, success } = await gateway.verifyPayment({
|
// const { raw, referenceId, success } = await gateway.verifyPayment({
|
||||||
amount: payment.amount,
|
// amount: payment.amount,
|
||||||
token: payment.token!,
|
// token: payment.token!,
|
||||||
});
|
// });
|
||||||
|
|
||||||
payment.verifyResponse = raw;
|
// payment.verifyResponse = raw;
|
||||||
|
|
||||||
if (!success) {
|
// if (!success) {
|
||||||
payment.status = PaymentStatusEnum.Failed;
|
// payment.status = PaymentStatusEnum.Failed;
|
||||||
payment.failedAt = new Date();
|
// payment.failedAt = new Date();
|
||||||
return payment;
|
// return payment;
|
||||||
}
|
// }
|
||||||
|
|
||||||
payment.status = PaymentStatusEnum.Paid;
|
// payment.status = PaymentStatusEnum.Paid;
|
||||||
payment.paidAt = new Date();
|
// payment.paidAt = new Date();
|
||||||
payment.transactionId = referenceId;
|
// payment.transactionId = referenceId;
|
||||||
// TODO : use decimal js
|
// // TODO : use decimal js
|
||||||
// does need persist or not
|
// // does need persist or not
|
||||||
// payment.order.paidAmount! += payment.amount
|
// // payment.order.paidAmount! += payment.amount
|
||||||
// payment.order.balance! -= payment.amount
|
// // payment.order.balance! -= payment.amount
|
||||||
|
|
||||||
await em.flush();
|
// await em.flush();
|
||||||
return payment;
|
// return payment;
|
||||||
});
|
// });
|
||||||
this.eventEmitter.emit(
|
// this.eventEmitter.emit(
|
||||||
onlinePaymentSucceedEvent.name,
|
// onlinePaymentSucceedEvent.name,
|
||||||
new onlinePaymentSucceedEvent(payment.id, payment.order.id, String(payment.order?.orderNumber) || '', payment.amount),
|
// new onlinePaymentSucceedEvent(payment.id, payment.order.id, String(payment.order?.orderNumber) || '', payment.amount),
|
||||||
);
|
// );
|
||||||
return payment;
|
// return payment;
|
||||||
}
|
// }
|
||||||
|
|
||||||
findAll(dto: FindPaymentsDto) {
|
// findAll(dto: FindPaymentsDto) {
|
||||||
return this.paymentRepository.findAllPaginated(dto)
|
// return this.paymentRepository.findAllPaginated(dto)
|
||||||
}
|
// }
|
||||||
|
|
||||||
findAllByUser(dto: FindPaymentsDto, userId: string) {
|
// findAllByUser(dto: FindPaymentsDto, userId: string) {
|
||||||
return this.paymentRepository.findAllPaginated({ ...dto, userId })
|
// return this.paymentRepository.findAllPaginated({ ...dto, userId })
|
||||||
}
|
// }
|
||||||
|
|
||||||
async confirmCashPayment(paymentId: string): Promise<Payment> {
|
// async confirmCashPayment(paymentId: string): Promise<Payment> {
|
||||||
const payment = await this.em.transactional(async em => {
|
// const payment = await this.em.transactional(async em => {
|
||||||
const payment = await em.findOne(Payment, paymentId, { populate: ['order'] });
|
// const payment = await em.findOne(Payment, paymentId, { populate: ['order'] });
|
||||||
if (!payment) {
|
// if (!payment) {
|
||||||
throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
|
// throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
|
||||||
}
|
// }
|
||||||
if (payment.method !== PaymentMethodEnum.Cash) {
|
// if (payment.method !== PaymentMethodEnum.Cash) {
|
||||||
throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
|
// throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
|
||||||
}
|
// }
|
||||||
if (payment.status === PaymentStatusEnum.Paid) {
|
// if (payment.status === PaymentStatusEnum.Paid) {
|
||||||
throw new BadRequestException(PaymentMessage.PAYMENT_ALREADY_PAID);
|
// throw new BadRequestException(PaymentMessage.PAYMENT_ALREADY_PAID);
|
||||||
}
|
// }
|
||||||
|
|
||||||
payment.status = PaymentStatusEnum.Paid;
|
// payment.status = PaymentStatusEnum.Paid;
|
||||||
payment.paidAt = new Date();
|
// payment.paidAt = new Date();
|
||||||
payment.failedAt = null;
|
// payment.failedAt = null;
|
||||||
|
|
||||||
// TODO : use decimal js
|
// // TODO : use decimal js
|
||||||
// payment.order.paidAmount! += payment.amount
|
// // payment.order.paidAmount! += payment.amount
|
||||||
// payment.order.balance! -= payment.amount
|
// // payment.order.balance! -= payment.amount
|
||||||
|
|
||||||
em.persist(payment);
|
// em.persist(payment);
|
||||||
em.persist(payment.order);
|
// em.persist(payment.order);
|
||||||
await em.flush();
|
// await em.flush();
|
||||||
return payment;
|
// return payment;
|
||||||
});
|
// });
|
||||||
return payment;
|
// return payment;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async denyCashPayment(paymentId: string): Promise<Payment> {
|
// async denyCashPayment(paymentId: string): Promise<Payment> {
|
||||||
const payment = await this.em.transactional(async em => {
|
// const payment = await this.em.transactional(async em => {
|
||||||
|
|
||||||
const payment = await em.findOne(Payment, paymentId, { populate: ['order'] });
|
// const payment = await em.findOne(Payment, paymentId, { populate: ['order'] });
|
||||||
|
|
||||||
if (!payment) {
|
// if (!payment) {
|
||||||
throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
|
// throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (payment.method !== PaymentMethodEnum.Cash) {
|
// if (payment.method !== PaymentMethodEnum.Cash) {
|
||||||
throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
|
// throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (payment.status == PaymentStatusEnum.Paid) {
|
// if (payment.status == PaymentStatusEnum.Paid) {
|
||||||
// payment.order.paidAmount! -= payment.amount
|
// // payment.order.paidAmount! -= payment.amount
|
||||||
// payment.order.balance! += payment.amount
|
// // payment.order.balance! += payment.amount
|
||||||
|
|
||||||
em.persistAndFlush(payment.order)
|
// em.persistAndFlush(payment.order)
|
||||||
}
|
// }
|
||||||
|
|
||||||
payment.status = PaymentStatusEnum.Failed;
|
// payment.status = PaymentStatusEnum.Failed;
|
||||||
payment.failedAt = new Date();
|
// payment.failedAt = new Date();
|
||||||
payment.paidAt = null
|
// payment.paidAt = null
|
||||||
|
|
||||||
em.persistAndFlush(payment);
|
// em.persistAndFlush(payment);
|
||||||
|
|
||||||
return payment;
|
// return payment;
|
||||||
});
|
// });
|
||||||
return payment;
|
// return payment;
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { AuthGuard } from "src/modules/auth/guards/auth.guard";
|
|||||||
export class TicketController {
|
export class TicketController {
|
||||||
constructor(private readonly ticketsService: TicketService) { }
|
constructor(private readonly ticketsService: TicketService) { }
|
||||||
|
|
||||||
@Post('public/ticket/order/item/:id')
|
@Post('public/ticket/ref/:id')
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@ApiOperation({ summary: "create ticket ==> user route" })
|
@ApiOperation({ summary: "create ticket ==> user route" })
|
||||||
createTicket(@Param('id') id: string, @Body() createDto: CreateTicketDto, @UserId() userId: string) {
|
createTicket(@Param('id') id: string, @Body() createDto: CreateTicketDto, @UserId() userId: string) {
|
||||||
@@ -24,7 +24,7 @@ export class TicketController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Get('public/ticket/order/item/:id')
|
@Get('public/ticket/ref/:id')
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@ApiOperation({ summary: "Get all order item tickets ==> user route" })
|
@ApiOperation({ summary: "Get all order item tickets ==> user route" })
|
||||||
getTickets(@Query() queryDto: FindTicketQueryDto, @Param('id') id: string, @UserId() userId: string) {
|
getTickets(@Query() queryDto: FindTicketQueryDto, @Param('id') id: string, @UserId() userId: string) {
|
||||||
@@ -47,7 +47,7 @@ export class TicketController {
|
|||||||
}
|
}
|
||||||
/*=========================== Admin ============================== */
|
/*=========================== Admin ============================== */
|
||||||
|
|
||||||
@Post('admin/ticket/order/item/:id')
|
@Post('admin/ticket/ref/:id')
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@ApiOperation({ summary: "create ticket ==> admin route" })
|
@ApiOperation({ summary: "create ticket ==> admin route" })
|
||||||
createTicketAsAdmin(@Param('id') itemId: string, @Body() createDto: CreateTicketDto, @AdminId() adminId: string) {
|
createTicketAsAdmin(@Param('id') itemId: string, @Body() createDto: CreateTicketDto, @AdminId() adminId: string) {
|
||||||
@@ -55,7 +55,7 @@ export class TicketController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Get('admin/ticket/order/item/:id')
|
@Get('admin/ticket/ref/:id')
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@ApiOperation({ summary: "Get all order item tickets ==> admin route" })
|
@ApiOperation({ summary: "Get all order item tickets ==> admin route" })
|
||||||
getTicketsAsAdmin(@Query() queryDto: FindTicketQueryDto, @Param('id') itemId: string) {
|
getTicketsAsAdmin(@Query() queryDto: FindTicketQueryDto, @Param('id') itemId: string) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
import { User } from "../../user/entities/user.entity";
|
import { User } from "../../user/entities/user.entity";
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
||||||
import { AttachmentType } from '../interfaces/ticket';
|
import { AttachmentType } from '../interfaces/ticket';
|
||||||
import { OrderItem } from 'src/modules/order/entities/order-item.entity';
|
|
||||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
import { BaseEntity } from 'src/common/entities/base.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@@ -28,8 +27,8 @@ export class Ticket extends BaseEntity{
|
|||||||
@Property({ type: 'json', nullable: true })
|
@Property({ type: 'json', nullable: true })
|
||||||
attachments?: AttachmentType[]
|
attachments?: AttachmentType[]
|
||||||
|
|
||||||
@ManyToOne(() => OrderItem)
|
@Property({ type: 'string' })
|
||||||
orderItem!: OrderItem
|
refId!: string
|
||||||
|
|
||||||
@ManyToOne(() => Ticket, { nullable: true })
|
@ManyToOne(() => Ticket, { nullable: true })
|
||||||
parent?: Ticket
|
parent?: Ticket
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import { Admin } from "src/modules/admin/entities/admin.entity";
|
|||||||
import { FindTicketQueryDto } from "../DTO/search-ticket-query.dto";
|
import { FindTicketQueryDto } from "../DTO/search-ticket-query.dto";
|
||||||
import { EntityManager } from "@mikro-orm/postgresql";
|
import { EntityManager } from "@mikro-orm/postgresql";
|
||||||
import { UpdateTicketDto } from "../DTO/update-ticket.dto";
|
import { UpdateTicketDto } from "../DTO/update-ticket.dto";
|
||||||
import { OrderItemRepository } from "src/modules/order/repositories/order-item.repository";
|
|
||||||
import { Ticket } from "../entities/ticket.entity";
|
import { Ticket } from "../entities/ticket.entity";
|
||||||
import { OrderItem } from "src/modules/order/entities/order-item.entity";
|
|
||||||
import { AdminService } from "src/modules/admin/providers/admin.service";
|
import { AdminService } from "src/modules/admin/providers/admin.service";
|
||||||
import { UserService } from "src/modules/user/providers/user.service";
|
import { UserService } from "src/modules/user/providers/user.service";
|
||||||
|
|
||||||
@@ -19,33 +17,33 @@ export class TicketService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly ticketsRepository: TicketRepository,
|
private readonly ticketsRepository: TicketRepository,
|
||||||
private readonly orderItemRepository: OrderItemRepository,
|
// private readonly orderItemRepository: OrderItemRepository,
|
||||||
private readonly adminService: AdminService,
|
private readonly adminService: AdminService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async createTicketAsAdmin(orderItemId: string, dto: CreateTicketDto, adminId: string) {
|
async createTicketAsAdmin(refId: string, dto: CreateTicketDto, adminId: string) {
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderItemId)
|
// const orderItem = await this.findOrderItemOrFail(refId)
|
||||||
|
|
||||||
let admin: null | Admin = null
|
let admin: null | Admin = null
|
||||||
|
|
||||||
admin = await this.adminService.findOrFail(adminId)
|
admin = await this.adminService.findOrFail(adminId)
|
||||||
|
|
||||||
const ticket = await this.createTicket(orderItem, dto, undefined, admin)
|
const ticket = await this.createTicket(refId, dto, undefined, admin)
|
||||||
|
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTicketAsUser(orderItemId: string, dto: CreateTicketDto, userId: string) {
|
async createTicketAsUser(refId: string, dto: CreateTicketDto, userId: string) {
|
||||||
|
|
||||||
const orderItem = await this.findOrderItemOrFail(orderItemId)
|
// const orderItem = await this.findOrderItemOrFail(refId)
|
||||||
|
|
||||||
const user = await this.userService.findOrFail(userId)
|
const user = await this.userService.findOrFail(userId)
|
||||||
|
|
||||||
const ticket = await this.createTicket(orderItem, dto, user, undefined)
|
const ticket = await this.createTicket(refId, dto, user, undefined)
|
||||||
|
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
@@ -97,9 +95,9 @@ export class TicketService {
|
|||||||
|
|
||||||
async deleteTicketAsUSer(ticketId: string, userId: string) {
|
async deleteTicketAsUSer(ticketId: string, userId: string) {
|
||||||
const ticket = await this.findOneOrFail(ticketId)
|
const ticket = await this.findOneOrFail(ticketId)
|
||||||
if (ticket.orderItem.order.user.id !== userId) {
|
// if (ticket.orderItem.order.user.id !== userId) {
|
||||||
throw new BadRequestException("You can not Delete this Ticket")
|
// throw new BadRequestException("You can not Delete this Ticket")
|
||||||
}
|
// }
|
||||||
|
|
||||||
await this.em.removeAndFlush(ticket)
|
await this.em.removeAndFlush(ticket)
|
||||||
|
|
||||||
@@ -108,21 +106,15 @@ export class TicketService {
|
|||||||
|
|
||||||
// ================ Core Logic ============
|
// ================ Core Logic ============
|
||||||
|
|
||||||
async getTickets(orderItemId: string, queryDto: FindTicketQueryDto, userId?: string) {
|
async getTickets(refId: string, queryDto: FindTicketQueryDto, userId?: string) {
|
||||||
const orderItem = await this.findOrderItemOrFail(orderItemId)
|
// const orderItem = await this.findOrderItemOrFail(refId)
|
||||||
|
|
||||||
if (userId) {
|
const data = await this.ticketsRepository.findAllPaginated({ ...queryDto, refId });
|
||||||
if (orderItem.order.user.id !== userId) {
|
|
||||||
throw new BadRequestException("This ticket doenst belongs to You !")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await this.ticketsRepository.findAllPaginated({ ...queryDto, orderItemId });
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTicket(orderItem: OrderItem, dto: CreateTicketDto, user?: User, admin?: Admin) {
|
async createTicket(refId:string,dto: CreateTicketDto, user?: User, admin?: Admin) {
|
||||||
const { content, attachments } = dto
|
const { content, attachments } = dto
|
||||||
|
|
||||||
if (!user && !admin) {
|
if (!user && !admin) {
|
||||||
@@ -134,7 +126,7 @@ export class TicketService {
|
|||||||
attachments,
|
attachments,
|
||||||
admin,
|
admin,
|
||||||
user,
|
user,
|
||||||
orderItem,
|
refId
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.ticketsRepository.em.persistAndFlush(ticket)
|
await this.ticketsRepository.em.persistAndFlush(ticket)
|
||||||
@@ -163,7 +155,7 @@ export class TicketService {
|
|||||||
|
|
||||||
async findOneOrFail(ticketId: string) {
|
async findOneOrFail(ticketId: string) {
|
||||||
const ticket = await this.ticketsRepository.findOne({ id: ticketId },
|
const ticket = await this.ticketsRepository.findOne({ id: ticketId },
|
||||||
{ populate: ['orderItem', 'orderItem.order', 'user'] })
|
{ populate: [ 'user'] })
|
||||||
if (!ticket) {
|
if (!ticket) {
|
||||||
throw new BadRequestException('ticket not found!')
|
throw new BadRequestException('ticket not found!')
|
||||||
}
|
}
|
||||||
@@ -190,14 +182,14 @@ export class TicketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOrderItemOrFail(orderItemId: string) {
|
// async findOrderItemOrFail(refId: string) {
|
||||||
const orderItem = await this.orderItemRepository.findOne({ id: orderItemId }, { populate: ['order', 'order.user'] })
|
// const orderItem = await this.orderItemRepository.findOne({ id: refId }, { populate: ['order', 'order.user'] })
|
||||||
|
|
||||||
if (!orderItem) {
|
// if (!orderItem) {
|
||||||
throw new BadRequestException("orderItem not found!")
|
// throw new BadRequestException("orderItem not found!")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return orderItem
|
// return orderItem
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FindTicketQueryDto } from '../DTO/search-ticket-query.dto';
|
|||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
|
|
||||||
class FindOrdersOpts extends FindTicketQueryDto {
|
class FindOrdersOpts extends FindTicketQueryDto {
|
||||||
orderItemId: string;
|
refId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -20,13 +20,13 @@ export class TicketRepository extends EntityRepository<Ticket> {
|
|||||||
const {
|
const {
|
||||||
page = 1,
|
page = 1,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
orderItemId: orderId
|
refId
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
const where: FilterQuery<Ticket> = {
|
const where: FilterQuery<Ticket> = {
|
||||||
orderItem: { id: orderId }
|
refId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { TicketService } from "./providers/tickets.service";
|
|||||||
import { TicketRepository } from "./repositories/tickets.repository";
|
import { TicketRepository } from "./repositories/tickets.repository";
|
||||||
import { TicketController } from "./controller/ticket.controller";
|
import { TicketController } from "./controller/ticket.controller";
|
||||||
import { MikroOrmModule } from "@mikro-orm/nestjs";
|
import { MikroOrmModule } from "@mikro-orm/nestjs";
|
||||||
import { OrderModule } from "../order/order.module";
|
|
||||||
import { AdminModule } from "../admin/admin.module";
|
import { AdminModule } from "../admin/admin.module";
|
||||||
import { UserModule } from "../user/user.module";
|
import { UserModule } from "../user/user.module";
|
||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
@@ -13,7 +12,7 @@ import { JwtModule } from "@nestjs/jwt";
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
forwardRef(() => OrderModule),
|
// forwardRef(() => OrderModule),
|
||||||
MikroOrmModule.forFeature([Ticket]),
|
MikroOrmModule.forFeature([Ticket]),
|
||||||
AdminModule,
|
AdminModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Entity, Index, Property, OneToMany, Collection, Cascade, PrimaryKey, OptionalProps } from '@mikro-orm/core';
|
import { Entity, Index, Property, OneToMany, Collection, Cascade, PrimaryKey, OptionalProps } from '@mikro-orm/core';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Order } from 'src/modules/order/entities/order.entity';
|
// import { Order } from 'src/modules/order/entities/order.entity';
|
||||||
import { normalizePhone } from '../../util/phone.util';
|
import { normalizePhone } from '../../util/phone.util';
|
||||||
import { ulid } from 'ulid';
|
import { ulid } from 'ulid';
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ import { ulid } from 'ulid';
|
|||||||
export class User extends BaseEntity {
|
export class User extends BaseEntity {
|
||||||
[OptionalProps]?: 'createdAt' | 'deletedAt'
|
[OptionalProps]?: 'createdAt' | 'deletedAt'
|
||||||
|
|
||||||
@OneToMany(() => Order, order => order.user)
|
// @OneToMany(() => Order, order => order.user)
|
||||||
orders = new Collection<Order>(this);
|
// orders = new Collection<Order>(this);
|
||||||
|
|
||||||
|
|
||||||
@Property({ nullable: true })
|
@Property({ nullable: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user