diff --git a/src/payments/card-to-card-deposit.service.ts b/src/payments/card-to-card-deposit.service.ts index c5c751a..cb73c43 100644 --- a/src/payments/card-to-card-deposit.service.ts +++ b/src/payments/card-to-card-deposit.service.ts @@ -27,6 +27,8 @@ import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions. import { UploaderService } from '../uploader/uploader.service'; import { UserRole } from '../users/enums/user-role.enum'; import { ShortLinksService } from '../short-links/short-links.service'; +import { DepositListItemDto } from './dto/deposit-list-item.dto'; +import { FindDepositsQueryDto } from './dto/find-deposits-query.dto'; import { PublicDepositPaymentDto } from './dto/public-deposit-payment.dto'; import { ReviewDepositDto } from './dto/review-deposit.dto'; import { Payment } from './entities/payment.entity'; @@ -253,6 +255,43 @@ export class CardToCardDepositService { return this.getPublicPaymentByToken(token); } + async findForSalon( + user: AuthUser, + query: FindDepositsQueryDto, + ): Promise { + if (user.role !== UserRole.SALON && user.role !== UserRole.ADMIN) { + throw new ForbiddenException(); + } + + const qb = this.paymentsRepository + .createQueryBuilder('payment') + .innerJoinAndSelect('payment.reserve', 'reserve') + .innerJoinAndSelect('reserve.salon', 'salon') + .leftJoinAndSelect('reserve.customer', 'customer') + .leftJoinAndSelect('customer.user', 'customerUser') + .where('payment.type = :type', { type: PaymentType.DEPOSIT }) + .andWhere('payment.method = :method', { + method: PaymentMethod.CARD_TO_CARD, + }) + .orderBy('payment.createdAt', 'DESC'); + + if (query.status) { + qb.andWhere('payment.status = :status', { status: query.status }); + } + + if (!this.authorizationService.isAdmin(user)) { + qb.andWhere('salon.ownerId = :ownerId', { ownerId: user.id }); + } + + const limit = query.limit ?? 50; + const offset = query.offset ?? 0; + qb.take(limit).skip(offset); + + const payments = await qb.getMany(); + + return payments.map((payment) => this.toDepositListItem(payment)); + } + async findForReview(paymentId: string, user: AuthUser): Promise { const payment = await this.loadPaymentWithRelations(paymentId); this.assertCanReview(user, payment); @@ -391,4 +430,23 @@ export class CardToCardDepositService { private rialToToman(amountRial: number): number { return Math.round(amountRial / 10); } + + private toDepositListItem(payment: Payment): DepositListItemDto { + const user = payment.reserve?.customer?.user; + const customerName = user + ? `${user.firstName} ${user.lastName}`.trim() + : 'مشتری'; + + return { + id: payment.id, + status: payment.status, + amountToman: this.rialToToman(payment.amountRial), + receiptUrl: payment.receiptUrl, + createdAt: payment.createdAt, + customerName, + customerMobile: user?.mobile ?? null, + appointmentDate: payment.reserve?.appointmentDate ?? null, + startTime: payment.reserve?.startTime ?? null, + }; + } } diff --git a/src/payments/deposits.controller.ts b/src/payments/deposits.controller.ts index 135e96b..6d02a0a 100644 --- a/src/payments/deposits.controller.ts +++ b/src/payments/deposits.controller.ts @@ -5,6 +5,7 @@ import { Param, ParseUUIDPipe, Post, + Query, UploadedFile, UseGuards, UseInterceptors, @@ -25,6 +26,8 @@ import { ApiPublicEndpoint } from '../swagger/decorators/swagger-auth.decorator' import { SWAGGER_JWT_AUTH } from '../swagger/swagger.setup'; import { UserRole } from '../users/enums/user-role.enum'; import { CardToCardDepositService } from './card-to-card-deposit.service'; +import { DepositListItemDto } from './dto/deposit-list-item.dto'; +import { FindDepositsQueryDto } from './dto/find-deposits-query.dto'; import { PublicDepositPaymentDto } from './dto/public-deposit-payment.dto'; import { ReviewDepositDto } from './dto/review-deposit.dto'; import { Payment } from './entities/payment.entity'; @@ -84,6 +87,18 @@ export class DepositsController { return this.cardToCardDepositService.submitReceipt(token, file); } + @ApiBearerAuth(SWAGGER_JWT_AUTH) + @UseGuards(RolesGuard) + @Roles(UserRole.ADMIN, UserRole.SALON) + @ApiStandardOkResponse(DepositListItemDto, { isArray: true }) + @Get() + findAll( + @Query() query: FindDepositsQueryDto, + @CurrentUser() user: AuthUser, + ) { + return this.cardToCardDepositService.findForSalon(user, query); + } + @ApiBearerAuth(SWAGGER_JWT_AUTH) @UseGuards(RolesGuard) @Roles(UserRole.ADMIN, UserRole.SALON) diff --git a/src/payments/dto/deposit-list-item.dto.ts b/src/payments/dto/deposit-list-item.dto.ts new file mode 100644 index 0000000..18b635d --- /dev/null +++ b/src/payments/dto/deposit-list-item.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaymentStatus } from '../enums/payment-status.enum'; + +export class DepositListItemDto { + @ApiProperty() + id: string; + + @ApiProperty({ enum: PaymentStatus }) + status: PaymentStatus; + + @ApiProperty({ example: 500000 }) + amountToman: number; + + @ApiProperty({ required: false, nullable: true }) + receiptUrl: string | null; + + @ApiProperty() + createdAt: Date; + + @ApiProperty({ example: 'مریم احمدی' }) + customerName: string; + + @ApiProperty({ required: false, nullable: true, example: '09121234567' }) + customerMobile: string | null; + + @ApiProperty({ required: false, nullable: true }) + appointmentDate: string | null; + + @ApiProperty({ required: false, nullable: true }) + startTime: string | null; +} diff --git a/src/payments/dto/find-deposits-query.dto.ts b/src/payments/dto/find-deposits-query.dto.ts new file mode 100644 index 0000000..447c561 --- /dev/null +++ b/src/payments/dto/find-deposits-query.dto.ts @@ -0,0 +1,22 @@ +import { Type } from 'class-transformer'; +import { IsEnum, IsInt, IsOptional, Max, Min } from 'class-validator'; +import { PaymentStatus } from '../enums/payment-status.enum'; + +export class FindDepositsQueryDto { + @IsOptional() + @IsEnum(PaymentStatus) + status?: PaymentStatus; + + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(100) + limit?: number; + + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(0) + offset?: number; +}