From 3cc402d6f8efc36c7e612506221df6525d0db118 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 30 Apr 2025 11:42:22 +0300 Subject: [PATCH] PM-1155 - refactor code --- .../admin.controller.ts} | 39 +-- src/api/admin/admin.module.ts | 19 ++ .../admin.service.ts} | 215 +----------- src/api/admin/dto/audit.dto.ts | 49 +++ src/api/admin/dto/winnings.dto.ts | 64 ++++ src/api/api.module.ts | 28 +- .../payment-providers/trolley.controller.ts | 4 +- src/api/repository/paymentMethod.repo.ts | 42 +-- src/api/repository/winnings.repo.ts | 220 ++++++++++++ src/{ => api/user}/dto/user.dto.ts | 16 +- .../user.controller.ts} | 20 +- src/api/user/user.module.ts | 10 + src/api/wallet/wallet.controller.ts | 4 +- src/api/wallet/wallet.module.ts | 12 + src/api/wallet/wallet.service.ts | 3 +- src/api/webhooks/trolley/trolley.service.ts | 4 +- .../winnings.controller.ts} | 24 +- src/api/winnings/winnings.module.ts | 20 ++ .../winnings.service.ts} | 33 +- src/dto/api-response.dto.ts | 30 ++ src/dto/date-filter.type.ts | 5 + src/dto/payment.dto.ts | 67 ++++ src/dto/paymentMethod.dto.ts | 15 - src/dto/sort-pagination.dto.ts | 64 ++++ src/dto/user.type.ts | 5 + .../{adminWinning.dto.ts => winning.dto.ts} | 314 ++---------------- 26 files changed, 682 insertions(+), 644 deletions(-) rename src/api/{admin-winning/adminWinning.controller.ts => admin/admin.controller.ts} (87%) create mode 100644 src/api/admin/admin.module.ts rename src/api/{admin-winning/adminWinning.service.ts => admin/admin.service.ts} (73%) create mode 100644 src/api/admin/dto/audit.dto.ts create mode 100644 src/api/admin/dto/winnings.dto.ts create mode 100644 src/api/repository/winnings.repo.ts rename src/{ => api/user}/dto/user.dto.ts (80%) rename src/api/{user-winning/userWinning.controller.ts => user/user.controller.ts} (75%) create mode 100644 src/api/user/user.module.ts create mode 100644 src/api/wallet/wallet.module.ts rename src/api/{winning/winning.controller.ts => winnings/winnings.controller.ts} (79%) create mode 100644 src/api/winnings/winnings.module.ts rename src/api/{winning/winning.service.ts => winnings/winnings.service.ts} (81%) create mode 100644 src/dto/api-response.dto.ts create mode 100644 src/dto/date-filter.type.ts create mode 100644 src/dto/payment.dto.ts delete mode 100644 src/dto/paymentMethod.dto.ts create mode 100644 src/dto/sort-pagination.dto.ts create mode 100644 src/dto/user.type.ts rename src/dto/{adminWinning.dto.ts => winning.dto.ts} (62%) diff --git a/src/api/admin-winning/adminWinning.controller.ts b/src/api/admin/admin.controller.ts similarity index 87% rename from src/api/admin-winning/adminWinning.controller.ts rename to src/api/admin/admin.controller.ts index c4ea525..284a63e 100644 --- a/src/api/admin-winning/adminWinning.controller.ts +++ b/src/api/admin/admin.controller.ts @@ -24,25 +24,23 @@ import { TopcoderMembersService } from 'src/shared/topcoder/members.service'; import { Role } from 'src/core/auth/auth.constants'; import { Roles, User } from 'src/core/auth/decorators'; -import { AdminWinningService } from './adminWinning.service'; -import { UserInfo } from 'src/dto/user.dto'; +import { UserInfo } from 'src/dto/user.type'; -import { - ResponseStatusType, - ResponseDto, - WinningAuditDto, - WinningRequestDto, - SearchWinningResult, - WinningUpdateRequestDto, - AuditPayoutDto, -} from 'src/dto/adminWinning.dto'; - -@ApiTags('AdminWinning') +import { AdminService } from './admin.service'; +import { ResponseDto, ResponseStatusType } from 'src/dto/api-response.dto'; +import { WinningAuditDto, AuditPayoutDto } from './dto/audit.dto'; + +import { WinningRequestDto, SearchWinningResult } from 'src/dto/winning.dto'; +import { WinningsRepository } from '../repository/winnings.repo'; +import { WinningUpdateRequestDto } from './dto/winnings.dto'; + +@ApiTags('AdminWinnings') @Controller('/admin') @ApiBearerAuth() -export class AdminWinningController { +export class AdminController { constructor( - private readonly adminWinningService: AdminWinningService, + private readonly adminService: AdminService, + private readonly winningsRepo: WinningsRepository, private readonly tcMembersService: TopcoderMembersService, ) {} @@ -65,7 +63,7 @@ export class AdminWinningController { async searchWinnings( @Body() body: WinningRequestDto, ): Promise> { - const result = await this.adminWinningService.searchWinnings(body); + const result = await this.winningsRepo.searchWinnings(body); if (result.error) { result.status = ResponseStatusType.ERROR; } @@ -94,7 +92,7 @@ export class AdminWinningController { @Header('Content-Type', 'text/csv') @Header('Content-Disposition', 'attachment; filename="winnings.csv"') async exportWinnings(@Body() body: WinningRequestDto) { - const result = await this.adminWinningService.searchWinnings({ + const result = await this.winningsRepo.searchWinnings({ ...body, limit: 999, }); @@ -172,7 +170,7 @@ export class AdminWinningController { ); } - const result = await this.adminWinningService.updateWinnings(body, user.id); + const result = await this.adminService.updateWinnings(body, user.id); if (result.error) { result.status = ResponseStatusType.ERROR; } @@ -201,7 +199,7 @@ export class AdminWinningController { async getWinningAudit( @Param('winningID') winningId: string, ): Promise> { - const result = await this.adminWinningService.getWinningAudit(winningId); + const result = await this.adminService.getWinningAudit(winningId); if (result.error) { result.status = ResponseStatusType.ERROR; } @@ -231,8 +229,7 @@ export class AdminWinningController { async getWinningAuditPayout( @Param('winningID') winningId: string, ): Promise> { - const result = - await this.adminWinningService.getWinningAuditPayout(winningId); + const result = await this.adminService.getWinningAuditPayout(winningId); if (result.error) { result.status = ResponseStatusType.ERROR; } diff --git a/src/api/admin/admin.module.ts b/src/api/admin/admin.module.ts new file mode 100644 index 0000000..3f70141 --- /dev/null +++ b/src/api/admin/admin.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { AdminController } from './admin.controller'; +import { AdminService } from './admin.service'; +import { TaxFormRepository } from '../repository/taxForm.repo'; +import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; +import { WinningsRepository } from '../repository/winnings.repo'; +import { TopcoderModule } from 'src/shared/topcoder/topcoder.module'; + +@Module({ + imports: [TopcoderModule], + controllers: [AdminController], + providers: [ + AdminService, + TaxFormRepository, + PaymentMethodRepository, + WinningsRepository, + ], +}) +export class AdminModule {} diff --git a/src/api/admin-winning/adminWinning.service.ts b/src/api/admin/admin.service.ts similarity index 73% rename from src/api/admin-winning/adminWinning.service.ts rename to src/api/admin/admin.service.ts index b848bc9..4653caa 100644 --- a/src/api/admin-winning/adminWinning.service.ts +++ b/src/api/admin/admin.service.ts @@ -8,27 +8,18 @@ import { import { PrismaPromise } from '@prisma/client'; import { PrismaService } from 'src/shared/global/prisma.service'; -import { - DateFilterType, - ResponseDto, - WinningAuditDto, - WinningRequestDto, - SearchWinningResult, - WinningUpdateRequestDto, - PaymentStatus, - AuditPayoutDto, - WinningsCategory, -} from 'src/dto/adminWinning.dto'; import { TaxFormRepository } from '../repository/taxForm.repo'; import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; - -const ONE_DAY = 24 * 60 * 60 * 1000; +import { ResponseDto } from 'src/dto/api-response.dto'; +import { PaymentStatus } from 'src/dto/payment.dto'; +import { WinningAuditDto, AuditPayoutDto } from './dto/audit.dto'; +import { WinningUpdateRequestDto } from './dto/winnings.dto'; /** * The admin winning service. */ @Injectable() -export class AdminWinningService { +export class AdminService { /** * Constructs the admin winning service with the given dependencies. * @param prisma the prisma service. @@ -39,202 +30,6 @@ export class AdminWinningService { private readonly paymentMethodRepo: PaymentMethodRepository, ) {} - /** - * Search winnings with parameters - * @param body the request body - * @returns the Promise with response result - */ - async searchWinnings( - body: WinningRequestDto, - ): Promise> { - const result = new ResponseDto(); - - try { - let winnerIds: string[] | undefined; - let externalIds: string[] | undefined; - if (body.winnerId) { - winnerIds = [body.winnerId]; - } else if (body.winnerIds) { - winnerIds = [...body.winnerIds]; - } else if (body.externalIds?.length > 0) { - externalIds = body.externalIds; - } - - const queryWhere = this.getQueryByWinnerId(body, winnerIds, externalIds); - const orderBy = this.getOrderByWithWinnerId( - body, - !winnerIds && !!externalIds?.length, - ); - - const [winnings, count] = await this.prisma.$transaction([ - this.prisma.winnings.findMany({ - ...queryWhere, - include: { - payment: { - where: { - installment_number: 1, - }, - orderBy: [ - { - created_at: 'desc', - }, - ], - }, - origin: true, - }, - orderBy, - skip: body.offset, - take: body.limit, - }), - this.prisma.winnings.count({ where: queryWhere.where }), - ]); - - result.data = { - winnings: winnings.map((item) => ({ - id: item.winning_id, - type: item.type, - winnerId: item.winner_id, - origin: item.origin?.origin_name, - category: (item.category ?? '') as WinningsCategory, - title: item.title as string, - description: item.description as string, - externalId: item.external_id as string, - attributes: (item.attributes ?? {}) as object, - details: item.payment?.map((paymentItem) => ({ - id: paymentItem.payment_id, - netAmount: Number(paymentItem.net_amount), - grossAmount: Number(paymentItem.gross_amount), - totalAmount: Number(paymentItem.total_amount), - installmentNumber: paymentItem.installment_number as number, - datePaid: (paymentItem.date_paid ?? undefined) as Date, - status: paymentItem.payment_status as PaymentStatus, - currency: paymentItem.currency as string, - releaseDate: paymentItem.release_date as Date, - category: item.category as string, - billingAccount: paymentItem.billing_account, - })), - createdAt: item.created_at as Date, - updatedAt: (item.payment?.[0].date_paid ?? - item.payment?.[0].updated_at ?? - undefined) as Date, - releaseDate: item.payment?.[0]?.release_date as Date, - })), - pagination: { - totalItems: count, - totalPages: Math.ceil(count / body.limit), - pageSize: body.limit, - currentPage: Math.ceil(body.offset / body.limit) + 1, - }, - }; - // response.data = winnings as any - } catch (error) { - console.error('Searching winnings failed', error); - const message = 'Searching winnings failed. ' + error; - result.error = { - code: HttpStatus.INTERNAL_SERVER_ERROR, - message, - }; - } - - return result; - } - - private generateFilterDate(body: WinningRequestDto) { - let filterDate: object | undefined; - const currentDay = new Date(new Date().setHours(0, 0, 0, 0)); - switch (body.date) { - case DateFilterType.LAST7DAYS: - // eslint-disable-next-line no-case-declarations - const last7days = new Date(currentDay.getTime() - 6 * ONE_DAY); - filterDate = { - gte: last7days, - }; - break; - case DateFilterType.LAST30DAYS: - // eslint-disable-next-line no-case-declarations - const last30days = new Date(currentDay.getTime() - 29 * ONE_DAY); - filterDate = { - gte: last30days, - }; - break; - case DateFilterType.ALL: - filterDate = undefined; - break; - default: - break; - } - return filterDate; - } - - private getQueryByWinnerId( - body: WinningRequestDto, - winnerIds: string[] | undefined, - externalIds: string[] | undefined, - ) { - const filterDate: object | undefined = this.generateFilterDate(body); - - const query = { - where: { - winner_id: winnerIds - ? { - in: winnerIds, - } - : undefined, - external_id: externalIds - ? { - in: body.externalIds, - } - : undefined, - category: body.type - ? { - equals: body.type, - } - : undefined, - created_at: filterDate, - payment: body.status - ? { - some: { - payment_status: { - equals: body.status, - }, - installment_number: { - equals: 1, - }, - }, - } - : { - some: { - installment_number: { - equals: 1, - }, - }, - }, - }, - }; - - return query; - } - - private getOrderByWithWinnerId( - body: WinningRequestDto, - externalIds?: boolean, - ) { - const orderBy: object = [ - { - created_at: 'desc', - }, - ...(externalIds ? [{ external_id: 'asc' }] : []), - ]; - - if (body.sortBy && body.sortOrder) { - orderBy[0] = { - [body.sortBy]: body.sortOrder.toString(), - }; - } - - return orderBy; - } - private getPaymentsByWinningsId(winningsId: string, paymentId?: string) { return this.prisma.payment.findMany({ where: { diff --git a/src/api/admin/dto/audit.dto.ts b/src/api/admin/dto/audit.dto.ts new file mode 100644 index 0000000..13f7c85 --- /dev/null +++ b/src/api/admin/dto/audit.dto.ts @@ -0,0 +1,49 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class WinningAuditDto { + @ApiProperty({ + description: 'The ID of the audit', + example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fb', + }) + id: string; + + @ApiProperty({ + description: 'The ID of the winning', + example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fc', + }) + winningsId: string; + + @ApiProperty({ + description: 'The ID of the user', + example: '123', + }) + userId: string; + + @ApiProperty({ + description: 'The audit action', + example: 'create payment', + }) + action: string; + + @ApiProperty({ + description: 'The audit note', + example: 'note 01', + }) + note: string | null; + + @ApiProperty({ + description: 'The creation timestamp', + example: '2023-10-01T00:00:00Z', + }) + createdAt: Date; +} + +export class AuditPayoutDto { + externalTransactionId: string; + status: string; + totalNetAmount: number; + createdAt: Date; + metadata: string; + paymentMethodUsed: string; + externalTransactionDetails: object; +} diff --git a/src/api/admin/dto/winnings.dto.ts b/src/api/admin/dto/winnings.dto.ts new file mode 100644 index 0000000..a768d8f --- /dev/null +++ b/src/api/admin/dto/winnings.dto.ts @@ -0,0 +1,64 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsString, + IsUUID, + IsOptional, + IsEnum, + IsNumber, + Min, + IsDateString, +} from 'class-validator'; +import { PaymentStatus } from 'src/dto/payment.dto'; + +export class WinningUpdateRequestDto { + @ApiProperty({ + description: 'The ID of the winnings', + example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fb', + }) + @IsString() + @IsUUID() + winningsId: string; + + @ApiProperty({ + description: 'The audit note', + example: 'audit note', + }) + @IsOptional() + @IsString() + auditNote: string; + + @ApiProperty({ + description: 'The ID of the payment', + example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fb', + }) + @IsOptional() + @IsString() + @IsUUID() + paymentId: string; + + @ApiProperty({ + description: 'The payment status', + enum: PaymentStatus, + example: PaymentStatus.PAID, + }) + @IsOptional() + @IsEnum(PaymentStatus) + paymentStatus: PaymentStatus; + + @ApiProperty({ + description: 'The payment amount', + example: 0, + }) + @IsOptional() + @IsNumber() + @Min(0) + paymentAmount: number; + + @ApiProperty({ + description: 'The filter date', + example: '2025-03-05T01:58:05.726Z', + }) + @IsOptional() + @IsDateString() + releaseDate: string; +} diff --git a/src/api/api.module.ts b/src/api/api.module.ts index 1dc3615..ace4b5a 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -1,12 +1,5 @@ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { HealthCheckController } from './health-check/healthCheck.controller'; -import { AdminWinningController } from './admin-winning/adminWinning.controller'; -import { AdminWinningService } from './admin-winning/adminWinning.service'; -import { UserWinningController } from './user-winning/userWinning.controller'; -import { WinningController } from './winning/winning.controller'; -import { WinningService } from './winning/winning.service'; -import { WalletController } from './wallet/wallet.controller'; -import { WalletService } from './wallet/wallet.service'; import { GlobalProvidersModule } from 'src/shared/global/globalProviders.module'; import { APP_GUARD } from '@nestjs/core'; import { TokenValidatorMiddleware } from 'src/core/auth/middleware/tokenValidator.middleware'; @@ -15,8 +8,13 @@ import { TopcoderModule } from 'src/shared/topcoder/topcoder.module'; import { OriginRepository } from './repository/origin.repo'; import { TaxFormRepository } from './repository/taxForm.repo'; import { PaymentMethodRepository } from './repository/paymentMethod.repo'; +import { WinningsRepository } from './repository/winnings.repo'; import { WebhooksModule } from './webhooks/webhooks.module'; import { PaymentProvidersModule } from './payment-providers/payment-providers.module'; +import { AdminModule } from './admin/admin.module'; +import { WinningsModule } from './winnings/winnings.module'; +import { UserModule } from './user/user.module'; +import { WalletModule } from './wallet/wallet.module'; @Module({ imports: [ @@ -24,14 +22,12 @@ import { PaymentProvidersModule } from './payment-providers/payment-providers.mo TopcoderModule, PaymentProvidersModule, WebhooksModule, + AdminModule, + WinningsModule, + UserModule, + WalletModule, ], - controllers: [ - HealthCheckController, - AdminWinningController, - UserWinningController, - WinningController, - WalletController, - ], + controllers: [HealthCheckController], providers: [ { provide: APP_GUARD, @@ -41,12 +37,10 @@ import { PaymentProvidersModule } from './payment-providers/payment-providers.mo provide: APP_GUARD, useClass: RolesGuard, }, - AdminWinningService, - WinningService, - WalletService, OriginRepository, TaxFormRepository, PaymentMethodRepository, + WinningsRepository, ], }) export class ApiModule implements NestModule { diff --git a/src/api/payment-providers/trolley.controller.ts b/src/api/payment-providers/trolley.controller.ts index 66e4856..a0477c7 100644 --- a/src/api/payment-providers/trolley.controller.ts +++ b/src/api/payment-providers/trolley.controller.ts @@ -7,9 +7,9 @@ import { } from '@nestjs/swagger'; import { TrolleyService } from './trolley.service'; import { Roles, User } from 'src/core/auth/decorators'; -import { UserInfo } from 'src/dto/user.dto'; +import { UserInfo } from 'src/dto/user.type'; import { Role } from 'src/core/auth/auth.constants'; -import { ResponseDto } from 'src/dto/adminWinning.dto'; +import { ResponseDto } from 'src/dto/api-response.dto'; @ApiTags('PaymentProviders') @Controller('/trolley') diff --git a/src/api/repository/paymentMethod.repo.ts b/src/api/repository/paymentMethod.repo.ts index 4b15d0c..fea2f04 100644 --- a/src/api/repository/paymentMethod.repo.ts +++ b/src/api/repository/paymentMethod.repo.ts @@ -1,8 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - PaymentMethodQueryResult, - UserPaymentMethodStatus, -} from 'src/dto/paymentMethod.dto'; +import { payment_method_status } from '@prisma/client'; import { PrismaService } from 'src/shared/global/prisma.service'; @Injectable() @@ -15,34 +12,15 @@ export class PaymentMethodRepository { * @param userId user id * @param tx transaction */ - async hasVerifiedPaymentMethod(userId: string, tx?): Promise { - const methods = await this.findPaymentMethodByUserId(userId, tx); - for (const method of methods) { - if ( - method.status === - UserPaymentMethodStatus.UserPaymentMethodStatusConnected.toString() - ) { - return true; - } - } - return false; - } + async hasVerifiedPaymentMethod(userId: string): Promise { + const connectedUserPaymentMethod = + await this.prisma.user_payment_methods.findFirst({ + where: { + user_id: userId, + status: payment_method_status.CONNECTED, + }, + }); - /** - * Get user payment methods - * - * @param userId user id - * @param tx transaction - * @returns payment methods - */ - private async findPaymentMethodByUserId(userId: string, tx?) { - const db = tx || this.prisma; - const ret = await db.$queryRaw` - SELECT pm.payment_method_id, pm.payment_method_type, pm.name, pm.description, upm.status, upm.id - FROM payment_method pm - JOIN user_payment_methods upm ON pm.payment_method_id = upm.payment_method_id - WHERE upm.user_id=${userId} - `; - return (ret || []) as PaymentMethodQueryResult[]; + return !!connectedUserPaymentMethod; } } diff --git a/src/api/repository/winnings.repo.ts b/src/api/repository/winnings.repo.ts new file mode 100644 index 0000000..dce1a2a --- /dev/null +++ b/src/api/repository/winnings.repo.ts @@ -0,0 +1,220 @@ +import { HttpStatus, Injectable } from '@nestjs/common'; +import { payment_status, Prisma, winnings_category } from '@prisma/client'; +import { ResponseDto } from 'src/dto/api-response.dto'; +import { DateFilterType } from 'src/dto/date-filter.type'; +import { PaymentStatus } from 'src/dto/payment.dto'; +import { + WinningRequestDto, + SearchWinningResult, + WinningsCategory, +} from 'src/dto/winning.dto'; +import { PrismaService } from 'src/shared/global/prisma.service'; + +const ONE_DAY = 24 * 60 * 60 * 1000; + +@Injectable() +export class WinningsRepository { + constructor(private readonly prisma: PrismaService) {} + + private generateFilterDate(date: DateFilterType) { + let filterDate: object | undefined; + const currentDay = new Date(new Date().setHours(0, 0, 0, 0)); + + switch (date) { + case DateFilterType.LAST7DAYS: + // eslint-disable-next-line no-case-declarations + const last7days = new Date(currentDay.getTime() - 6 * ONE_DAY); + filterDate = { + gte: last7days, + }; + break; + case DateFilterType.LAST30DAYS: + // eslint-disable-next-line no-case-declarations + const last30days = new Date(currentDay.getTime() - 29 * ONE_DAY); + filterDate = { + gte: last30days, + }; + break; + case DateFilterType.ALL: + filterDate = undefined; + break; + default: + break; + } + return filterDate; + } + + private getWinningsQueryFilters( + type: string, + status: string, + winnerIds: string[] | undefined, + externalIds: string[] | undefined, + date: DateFilterType, + ): Prisma.winningsFindManyArgs['where'] { + return { + winner_id: winnerIds + ? { + in: winnerIds, + } + : undefined, + external_id: externalIds + ? { + in: externalIds, + } + : undefined, + category: type + ? { + equals: type as winnings_category, + } + : undefined, + payment: status + ? { + some: { + payment_status: { + equals: status as payment_status, + }, + installment_number: { + equals: 1, + }, + }, + } + : { + some: { + installment_number: { + equals: 1, + }, + }, + }, + created_at: this.generateFilterDate(date), + }; + } + + private getOrderByWithWinnerId( + sortBy: string, + sortOrder: 'asc' | 'desc', + externalIds?: boolean, + ) { + const orderBy: object = [ + { + created_at: 'desc', + }, + ...(externalIds ? [{ external_id: 'asc' }] : []), + ]; + + if (sortBy && sortOrder) { + orderBy[0] = { + [sortBy]: sortOrder.toString(), + }; + } + + return orderBy; + } + + /** + * Search winnings with parameters + * @param searchProps the request body + * @returns the Promise with response result + */ + async searchWinnings( + searchProps: WinningRequestDto, + ): Promise> { + const result = new ResponseDto(); + + try { + let winnerIds: string[] | undefined; + let externalIds: string[] | undefined; + if (searchProps.winnerId) { + winnerIds = [searchProps.winnerId]; + } else if (searchProps.winnerIds) { + winnerIds = [...searchProps.winnerIds]; + } else if (searchProps.externalIds?.length > 0) { + externalIds = searchProps.externalIds; + } + + const queryWhere = this.getWinningsQueryFilters( + searchProps.type, + searchProps.status, + winnerIds, + externalIds, + searchProps.date, + ); + + const orderBy = this.getOrderByWithWinnerId( + searchProps.sortBy, + searchProps.sortOrder, + !winnerIds && !!externalIds?.length, + ); + + const [winnings, count] = await this.prisma.$transaction([ + this.prisma.winnings.findMany({ + where: queryWhere, + include: { + payment: { + where: { + installment_number: 1, + }, + orderBy: [ + { + created_at: 'desc', + }, + ], + }, + origin: true, + }, + orderBy, + skip: searchProps.offset, + take: searchProps.limit, + }), + this.prisma.winnings.count({ where: queryWhere }), + ]); + + result.data = { + winnings: winnings.map((item) => ({ + id: item.winning_id, + type: item.type, + winnerId: item.winner_id, + origin: item.origin?.origin_name, + category: (item.category ?? '') as WinningsCategory, + title: item.title as string, + description: item.description as string, + externalId: item.external_id as string, + attributes: (item.attributes ?? {}) as object, + details: item.payment?.map((paymentItem) => ({ + id: paymentItem.payment_id, + netAmount: Number(paymentItem.net_amount), + grossAmount: Number(paymentItem.gross_amount), + totalAmount: Number(paymentItem.total_amount), + installmentNumber: paymentItem.installment_number as number, + datePaid: (paymentItem.date_paid ?? undefined) as Date, + status: paymentItem.payment_status as PaymentStatus, + currency: paymentItem.currency as string, + releaseDate: paymentItem.release_date as Date, + category: item.category as string, + billingAccount: paymentItem.billing_account, + })), + createdAt: item.created_at as Date, + updatedAt: (item.payment?.[0].date_paid ?? + item.payment?.[0].updated_at ?? + undefined) as Date, + releaseDate: item.payment?.[0]?.release_date as Date, + })), + pagination: { + totalItems: count, + totalPages: Math.ceil(count / searchProps.limit), + pageSize: searchProps.limit, + currentPage: Math.ceil(searchProps.offset / searchProps.limit) + 1, + }, + }; + // response.data = winnings as any + } catch (error) { + console.error('Searching winnings failed', error); + const message = 'Searching winnings failed. ' + error; + result.error = { + code: HttpStatus.INTERNAL_SERVER_ERROR, + message, + }; + } + + return result; + } +} diff --git a/src/dto/user.dto.ts b/src/api/user/dto/user.dto.ts similarity index 80% rename from src/dto/user.dto.ts rename to src/api/user/dto/user.dto.ts index afad6d9..2b000c9 100644 --- a/src/dto/user.dto.ts +++ b/src/api/user/dto/user.dto.ts @@ -1,17 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString, IsEnum } from 'class-validator'; -import { - WinningsCategory, - PaymentStatus, - DateFilterType, - SortPagination, -} from './adminWinning.dto'; - -export class UserInfo { - id: string; - handle: string; - email: string; -} +import { DateFilterType } from 'src/dto/date-filter.type'; +import { PaymentStatus } from 'src/dto/payment.dto'; +import { SortPagination } from 'src/dto/sort-pagination.dto'; +import { WinningsCategory } from 'src/dto/winning.dto'; export class UserWinningRequestDto extends SortPagination { @ApiProperty({ diff --git a/src/api/user-winning/userWinning.controller.ts b/src/api/user/user.controller.ts similarity index 75% rename from src/api/user-winning/userWinning.controller.ts rename to src/api/user/user.controller.ts index 24d6f63..f4fa123 100644 --- a/src/api/user-winning/userWinning.controller.ts +++ b/src/api/user/user.controller.ts @@ -18,21 +18,17 @@ import { import { Role } from 'src/core/auth/auth.constants'; import { Roles, User } from 'src/core/auth/decorators'; -import { UserInfo, UserWinningRequestDto } from 'src/dto/user.dto'; -import { - ResponseStatusType, - ResponseDto, - WinningRequestDto, - SearchWinningResult, -} from 'src/dto/adminWinning.dto'; - -import { AdminWinningService } from '../admin-winning/adminWinning.service'; +import { WinningsRepository } from '../repository/winnings.repo'; +import { ResponseDto, ResponseStatusType } from 'src/dto/api-response.dto'; +import { SearchWinningResult, WinningRequestDto } from 'src/dto/winning.dto'; +import { UserInfo } from 'src/dto/user.type'; +import { UserWinningRequestDto } from './dto/user.dto'; @ApiTags('UserWinning') @Controller('/user') @ApiBearerAuth() -export class UserWinningController { - constructor(private readonly adminWinningService: AdminWinningService) {} +export class UserController { + constructor(private readonly winningsRepo: WinningsRepository) {} @Post('/winnings') @Roles(Role.User) @@ -63,7 +59,7 @@ export class UserWinningController { throw new ForbiddenException('insufficient permissions'); } - const result = await this.adminWinningService.searchWinnings( + const result = await this.winningsRepo.searchWinnings( body as WinningRequestDto, ); if (result.error) { diff --git a/src/api/user/user.module.ts b/src/api/user/user.module.ts new file mode 100644 index 0000000..831da2f --- /dev/null +++ b/src/api/user/user.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { UserController } from './user.controller'; +import { WinningsRepository } from '../repository/winnings.repo'; + +@Module({ + imports: [], + controllers: [UserController], + providers: [WinningsRepository], +}) +export class UserModule {} diff --git a/src/api/wallet/wallet.controller.ts b/src/api/wallet/wallet.controller.ts index c294fa8..4ed9bd4 100644 --- a/src/api/wallet/wallet.controller.ts +++ b/src/api/wallet/wallet.controller.ts @@ -8,8 +8,8 @@ import { import { Role } from 'src/core/auth/auth.constants'; import { Roles, User } from 'src/core/auth/decorators'; -import { UserInfo } from 'src/dto/user.dto'; -import { ResponseStatusType, ResponseDto } from 'src/dto/adminWinning.dto'; +import { UserInfo } from 'src/dto/user.type'; +import { ResponseDto, ResponseStatusType } from 'src/dto/api-response.dto'; import { WalletDetailDto } from 'src/dto/wallet.dto'; import { WalletService } from './wallet.service'; diff --git a/src/api/wallet/wallet.module.ts b/src/api/wallet/wallet.module.ts new file mode 100644 index 0000000..6e6aaf4 --- /dev/null +++ b/src/api/wallet/wallet.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { WalletController } from './wallet.controller'; +import { WalletService } from './wallet.service'; +import { TaxFormRepository } from '../repository/taxForm.repo'; +import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; + +@Module({ + imports: [], + controllers: [WalletController], + providers: [WalletService, TaxFormRepository, PaymentMethodRepository], +}) +export class WalletModule {} diff --git a/src/api/wallet/wallet.service.ts b/src/api/wallet/wallet.service.ts index 9a92a8b..63d623e 100644 --- a/src/api/wallet/wallet.service.ts +++ b/src/api/wallet/wallet.service.ts @@ -2,8 +2,9 @@ import { Injectable, HttpStatus } from '@nestjs/common'; import { PrismaService } from 'src/shared/global/prisma.service'; -import { ResponseDto, WinningsType } from 'src/dto/adminWinning.dto'; import { WalletDetailDto } from 'src/dto/wallet.dto'; +import { ResponseDto } from 'src/dto/api-response.dto'; +import { WinningsType } from 'src/dto/winning.dto'; import { TaxFormRepository } from '../repository/taxForm.repo'; import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; diff --git a/src/api/webhooks/trolley/trolley.service.ts b/src/api/webhooks/trolley/trolley.service.ts index fc21a17..b508e76 100644 --- a/src/api/webhooks/trolley/trolley.service.ts +++ b/src/api/webhooks/trolley/trolley.service.ts @@ -43,7 +43,7 @@ export class TrolleyService { const t = headerSignatureValues[0].split('=')[1]; const v1 = headerSignatureValues[1].split('=')[1]; - const hmac = crypto.createHmac('sha256', trolleyWhHmac as string); + const hmac = crypto.createHmac('sha256', trolleyWhHmac); hmac.update(`${t}${bodyPayload}`); const digest = hmac.digest('hex'); @@ -128,7 +128,7 @@ export class TrolleyService { return; } - await handler(body); + await handler(body[model]); await this.setEventState(requestId, webhook_status.processed); } catch (e) { await this.setEventState(requestId, webhook_status.error, void 0, { diff --git a/src/api/winning/winning.controller.ts b/src/api/winnings/winnings.controller.ts similarity index 79% rename from src/api/winning/winning.controller.ts rename to src/api/winnings/winnings.controller.ts index d4a8e8c..15a373b 100644 --- a/src/api/winning/winning.controller.ts +++ b/src/api/winnings/winnings.controller.ts @@ -9,25 +9,23 @@ import { import { M2mScope } from 'src/core/auth/auth.constants'; import { M2M, AllowedM2mScope, User } from 'src/core/auth/decorators'; -import { UserInfo } from 'src/dto/user.dto'; +import { ResponseDto, ResponseStatusType } from 'src/dto/api-response.dto'; +import { UserInfo } from 'src/dto/user.type'; import { - ResponseStatusType, - ResponseDto, - WinningRequestDto, WinningCreateRequestDto, + WinningRequestDto, WinningDto, -} from 'src/dto/adminWinning.dto'; - -import { AdminWinningService } from '../admin-winning/adminWinning.service'; -import { WinningService } from './winning.service'; +} from 'src/dto/winning.dto'; +import { WinningsService } from './winnings.service'; +import { WinningsRepository } from '../repository/winnings.repo'; @ApiTags('Winning') @Controller('/winnings') @ApiBearerAuth() -export class WinningController { +export class WinningsController { constructor( - private readonly winningService: WinningService, - private readonly adminWinningService: AdminWinningService, + private readonly winningsService: WinningsService, + private readonly winningsRepo: WinningsRepository, ) {} @Post() @@ -51,7 +49,7 @@ export class WinningController { @Body() body: WinningCreateRequestDto, @User() user: UserInfo, ): Promise> { - const result = await this.winningService.createWinningWithPayments( + const result = await this.winningsService.createWinningWithPayments( body, user.id, ); @@ -84,7 +82,7 @@ export class WinningController { async searchWinnings( @Body() body: WinningRequestDto, ): Promise> { - const result = await this.adminWinningService.searchWinnings(body); + const result = await this.winningsRepo.searchWinnings(body); result.status = result.error ? ResponseStatusType.ERROR diff --git a/src/api/winnings/winnings.module.ts b/src/api/winnings/winnings.module.ts new file mode 100644 index 0000000..02c9ae5 --- /dev/null +++ b/src/api/winnings/winnings.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { WinningsController } from './winnings.controller'; +import { WinningsService } from './winnings.service'; +import { OriginRepository } from '../repository/origin.repo'; +import { WinningsRepository } from '../repository/winnings.repo'; +import { TaxFormRepository } from '../repository/taxForm.repo'; +import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; + +@Module({ + imports: [], + controllers: [WinningsController], + providers: [ + WinningsService, + OriginRepository, + TaxFormRepository, + WinningsRepository, + PaymentMethodRepository, + ], +}) +export class WinningsModule {} diff --git a/src/api/winning/winning.service.ts b/src/api/winnings/winnings.service.ts similarity index 81% rename from src/api/winning/winning.service.ts rename to src/api/winnings/winnings.service.ts index 1bd1e54..8872def 100644 --- a/src/api/winning/winning.service.ts +++ b/src/api/winnings/winnings.service.ts @@ -3,11 +3,9 @@ import { Prisma, payment, payment_status } from '@prisma/client'; import { PrismaService } from 'src/shared/global/prisma.service'; -import { - PaymentStatus, - ResponseDto, - WinningCreateRequestDto, -} from 'src/dto/adminWinning.dto'; +import { WinningCreateRequestDto } from 'src/dto/winning.dto'; +import { ResponseDto } from 'src/dto/api-response.dto'; +import { PaymentStatus } from 'src/dto/payment.dto'; import { OriginRepository } from '../repository/origin.repo'; import { TaxFormRepository } from '../repository/taxForm.repo'; import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; @@ -16,7 +14,7 @@ import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; * The winning service. */ @Injectable() -export class WinningService { +export class WinningsService { /** * Constructs the admin winning service with the given dependencies. * @param prisma the prisma service. @@ -44,13 +42,11 @@ export class WinningService { const originId = await this.originRepo.getOriginIdByName(body.origin, tx); if (!originId) { - return { - ...result, - error: { - code: HttpStatus.BAD_REQUEST, - message: 'Origin name does not exist', - }, + result.error = { + code: HttpStatus.BAD_REQUEST, + message: 'Origin name does not exist', }; + return result; } const winningModel = { @@ -70,11 +66,11 @@ export class WinningService { const payrollPayment = (body.attributes || {})['payroll'] === true; + const hasActiveTaxForm = await this.taxFormRepo.hasActiveTaxForm( + body.winnerId, + ); const hasPaymentMethod = - await this.paymentMethodRepo.hasVerifiedPaymentMethod( - body.winnerId, - tx, - ); + await this.paymentMethodRepo.hasVerifiedPaymentMethod(body.winnerId); for (const detail of body.details || []) { const paymentModel = { @@ -89,7 +85,10 @@ export class WinningService { }; paymentModel.net_amount = Prisma.Decimal(detail.grossAmount); - paymentModel.payment_status = PaymentStatus.ON_HOLD; + paymentModel.payment_status = + hasPaymentMethod && hasActiveTaxForm + ? PaymentStatus.OWED + : PaymentStatus.ON_HOLD; if (payrollPayment) { paymentModel.payment_status = PaymentStatus.PAID; diff --git a/src/dto/api-response.dto.ts b/src/dto/api-response.dto.ts new file mode 100644 index 0000000..68ddaa3 --- /dev/null +++ b/src/dto/api-response.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export enum ResponseStatusType { + SUCCESS = 'success', + ERROR = 'error', +} + +export class Error { + code: number; + message: string; +} + +export class ResponseDto { + @ApiProperty({ + description: 'Type of the response', + enum: ResponseStatusType, + example: ResponseStatusType.SUCCESS, + }) + status: ResponseStatusType; + + @ApiProperty({ + description: 'The response data', + }) + data: T; + + @ApiProperty({ + description: 'The error message', + }) + error: Error; +} diff --git a/src/dto/date-filter.type.ts b/src/dto/date-filter.type.ts new file mode 100644 index 0000000..329e327 --- /dev/null +++ b/src/dto/date-filter.type.ts @@ -0,0 +1,5 @@ +export enum DateFilterType { + LAST7DAYS = 'last7days', + LAST30DAYS = 'last30days', + ALL = 'all', +} diff --git a/src/dto/payment.dto.ts b/src/dto/payment.dto.ts new file mode 100644 index 0000000..22ba515 --- /dev/null +++ b/src/dto/payment.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, Min, IsInt, IsString, IsNotEmpty } from 'class-validator'; + +export enum PaymentStatus { + PAID = 'PAID', + ON_HOLD = 'ON_HOLD', + ON_HOLD_ADMIN = 'ON_HOLD_ADMIN', + OWED = 'OWED', + PROCESSING = 'PROCESSING', + CANCELLED = 'CANCELLED', +} + +export class PaymentDetailDto { + id: string; + netAmount: number; + grossAmount: number; + totalAmount: number; + installmentNumber: number; + datePaid: Date; + status: PaymentStatus; + currency: string; + releaseDate: Date; + category: string; + billingAccount: string; +} + +export class PaymentCreateRequestDto { + @ApiProperty({ + description: 'The total amount of the payment', + example: 12.3, + }) + @IsNumber() + @Min(0) + totalAmount: number; + + @ApiProperty({ + description: 'The gross amount of the payment', + example: 12.3, + }) + @IsNumber() + @Min(0) + grossAmount: number; + + @ApiProperty({ + description: 'The installment number of the payment', + example: 1, + }) + @IsInt() + @Min(0) + installmentNumber: number; + + @ApiProperty({ + description: 'The currency of the payment', + example: 12.3, + }) + @IsString() + @IsNotEmpty() + currency: string; + + @ApiProperty({ + description: 'Billing Account number for the payment', + example: '1231231', + }) + @IsString() + @IsNotEmpty() + billingAccount: string; +} diff --git a/src/dto/paymentMethod.dto.ts b/src/dto/paymentMethod.dto.ts deleted file mode 100644 index 2920536..0000000 --- a/src/dto/paymentMethod.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -export enum UserPaymentMethodStatus { - UserPaymentMethodStatusOtpVerified = 'OTP_VERIFIED', - UserPaymentMethodStatusOtpPending = 'OTP_PENDING', - UserPaymentMethodStatusConnected = 'CONNECTED', - UserPaymentMethodStatusInactive = 'INACTIVE', -} - -export class PaymentMethodQueryResult { - payment_method_id: string; - payment_method_type: string; - name: string; - description: string | null; - status: string; - id: string; -} diff --git a/src/dto/sort-pagination.dto.ts b/src/dto/sort-pagination.dto.ts new file mode 100644 index 0000000..8fad58e --- /dev/null +++ b/src/dto/sort-pagination.dto.ts @@ -0,0 +1,64 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsInt, Min, IsIn, IsEnum, IsOptional } from 'class-validator'; + +export const OrderBy = [ + 'winning_id', + 'winner_id', + 'type', + 'category', + 'title', + 'external_id', + 'created_at', + 'updated_at', + 'created_by', + 'updated_by', +] as const; + +export enum SortOrder { + ASC = 'asc', + DESC = 'desc', +} + +export class PaginationInfo { + totalItems: number; + totalPages: number; + pageSize: number; + currentPage: number; +} + +export class SortPagination { + @ApiProperty({ + description: 'The limit parameter for pagination', + example: 10, + }) + @IsOptional() + @IsInt() + @Min(1) + limit: number = 10; + + @ApiProperty({ + description: 'The offset parameter for pagination', + example: 0, + }) + @IsOptional() + @IsInt() + @Min(0) + offset: number = 0; + + @ApiProperty({ + description: 'The sortBy parameter for sorting', + example: 'type', + }) + @IsOptional() + @IsIn(OrderBy) + sortBy: string; + + @ApiProperty({ + description: 'The sort order', + enum: SortOrder, + example: SortOrder.ASC, + }) + @IsOptional() + @IsEnum(SortOrder) + sortOrder: SortOrder = SortOrder.ASC; +} diff --git a/src/dto/user.type.ts b/src/dto/user.type.ts new file mode 100644 index 0000000..8713d61 --- /dev/null +++ b/src/dto/user.type.ts @@ -0,0 +1,5 @@ +export class UserInfo { + id: string; + handle: string; + email: string; +} diff --git a/src/dto/adminWinning.dto.ts b/src/dto/winning.dto.ts similarity index 62% rename from src/dto/adminWinning.dto.ts rename to src/dto/winning.dto.ts index 063b83b..fbfe66f 100644 --- a/src/dto/adminWinning.dto.ts +++ b/src/dto/winning.dto.ts @@ -1,21 +1,27 @@ import { ApiProperty } from '@nestjs/swagger'; import { - IsDateString, - IsNotEmpty, IsOptional, + IsString, + IsNotEmpty, IsArray, ArrayNotEmpty, - IsString, IsEnum, - IsInt, - Min, - IsIn, - IsUUID, - IsNumber, IsObject, ValidateNested, } from 'class-validator'; +import { PaginationInfo, SortPagination } from './sort-pagination.dto'; +import { DateFilterType } from './date-filter.type'; import { Type } from 'class-transformer'; +import { + PaymentStatus, + PaymentCreateRequestDto, + PaymentDetailDto, +} from './payment.dto'; + +export enum WinningsType { + PAYMENT = 'PAYMENT', + REWARD = 'REWARD', +} export enum WinningsCategory { ALGORITHM_CONTEST_PAYMENT = 'ALGORITHM_CONTEST_PAYMENT', @@ -92,146 +98,20 @@ export enum WinningsCategory { TASK_COPILOT_PAYMENT = 'TASK_COPILOT_PAYMENT', } -export enum PaymentStatus { - PAID = 'PAID', - ON_HOLD = 'ON_HOLD', - ON_HOLD_ADMIN = 'ON_HOLD_ADMIN', - OWED = 'OWED', - PROCESSING = 'PROCESSING', - CANCELLED = 'CANCELLED', -} - -export enum DateFilterType { - LAST7DAYS = 'last7days', - LAST30DAYS = 'last30days', - ALL = 'all', -} - -export enum WinningsType { - PAYMENT = 'PAYMENT', - REWARD = 'REWARD', -} - -export enum SortOrder { - ASC = 'asc', - DESC = 'desc', -} - -export enum ResponseStatusType { - SUCCESS = 'success', - ERROR = 'error', -} - -export const OrderBy = [ - 'winning_id', - 'winner_id', - 'type', - 'category', - 'title', - 'external_id', - 'created_at', - 'updated_at', - 'created_by', - 'updated_by', -] as const; - -export class Error { - code: number; - message: string; -} - -export class ResponseDto { - @ApiProperty({ - description: 'Type of the response', - enum: ResponseStatusType, - example: ResponseStatusType.SUCCESS, - }) - status: ResponseStatusType; - - @ApiProperty({ - description: 'The response data', - }) - data: T; - - @ApiProperty({ - description: 'The error message', - }) - error: Error; -} - -export class SortPagination { - @ApiProperty({ - description: 'The limit parameter for pagination', - example: 10, - }) - @IsOptional() - @IsInt() - @Min(1) - limit: number = 10; - - @ApiProperty({ - description: 'The offset parameter for pagination', - example: 0, - }) - @IsOptional() - @IsInt() - @Min(0) - offset: number = 0; - - @ApiProperty({ - description: 'The sortBy parameter for sorting', - example: 'type', - }) - @IsOptional() - @IsIn(OrderBy) - sortBy: string; - - @ApiProperty({ - description: 'The sort order', - enum: SortOrder, - example: SortOrder.ASC, - }) - @IsOptional() - @IsEnum(SortOrder) - sortOrder: SortOrder = SortOrder.ASC; -} - -export class WinningAuditDto { - @ApiProperty({ - description: 'The ID of the audit', - example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fb', - }) +export class WinningDto { id: string; - - @ApiProperty({ - description: 'The ID of the winning', - example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fc', - }) - winningsId: string; - - @ApiProperty({ - description: 'The ID of the user', - example: '123', - }) - userId: string; - - @ApiProperty({ - description: 'The audit action', - example: 'create payment', - }) - action: string; - - @ApiProperty({ - description: 'The audit note', - example: 'note 01', - }) - note: string | null; - - @ApiProperty({ - description: 'The creation timestamp', - example: '2023-10-01T00:00:00Z', - }) + type: string; + winnerId: string; + origin?: string; + category: WinningsCategory; + title: string; + description: string; + externalId: string; + attributes: object; + details: PaymentDetailDto[]; createdAt: Date; + updatedAt: Date; + releaseDate: Date; } export class WinningRequestDto extends SortPagination { @@ -294,101 +174,6 @@ export class WinningRequestDto extends SortPagination { date: DateFilterType; } -export class WinningUpdateRequestDto { - @ApiProperty({ - description: 'The ID of the winnings', - example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fb', - }) - @IsString() - @IsUUID() - winningsId: string; - - @ApiProperty({ - description: 'The audit note', - example: 'audit note', - }) - @IsOptional() - @IsString() - auditNote: string; - - @ApiProperty({ - description: 'The ID of the payment', - example: '2ccba36d-8db7-49da-94c9-b6c5b7bf47fb', - }) - @IsOptional() - @IsString() - @IsUUID() - paymentId: string; - - @ApiProperty({ - description: 'The payment status', - enum: PaymentStatus, - example: PaymentStatus.PAID, - }) - @IsOptional() - @IsEnum(PaymentStatus) - paymentStatus: PaymentStatus; - - @ApiProperty({ - description: 'The payment amount', - example: 0, - }) - @IsOptional() - @IsNumber() - @Min(0) - paymentAmount: number; - - @ApiProperty({ - description: 'The filter date', - example: '2025-03-05T01:58:05.726Z', - }) - @IsOptional() - @IsDateString() - releaseDate: string; -} - -export class PaymentCreateRequestDto { - @ApiProperty({ - description: 'The total amount of the payment', - example: 12.3, - }) - @IsNumber() - @Min(0) - totalAmount: number; - - @ApiProperty({ - description: 'The gross amount of the payment', - example: 12.3, - }) - @IsNumber() - @Min(0) - grossAmount: number; - - @ApiProperty({ - description: 'The installment number of the payment', - example: 1, - }) - @IsInt() - @Min(0) - installmentNumber: number; - - @ApiProperty({ - description: 'The currency of the payment', - example: 12.3, - }) - @IsString() - @IsNotEmpty() - currency: string; - - @ApiProperty({ - description: 'Billing Account number for the payment', - example: '1231231', - }) - @IsString() - @IsNotEmpty() - billingAccount: string; -} - export class WinningCreateRequestDto { @ApiProperty({ description: 'The ID of the winner', @@ -474,54 +259,7 @@ export class WinningCreateRequestDto { details: PaymentCreateRequestDto[]; } -export class PaginationInfo { - totalItems: number; - totalPages: number; - pageSize: number; - currentPage: number; -} - -export class PaymentDetailDto { - id: string; - netAmount: number; - grossAmount: number; - totalAmount: number; - installmentNumber: number; - datePaid: Date; - status: PaymentStatus; - currency: string; - releaseDate: Date; - category: string; - billingAccount: string; -} - -export class WinningDto { - id: string; - type: string; - winnerId: string; - origin?: string; - category: WinningsCategory; - title: string; - description: string; - externalId: string; - attributes: object; - details: PaymentDetailDto[]; - createdAt: Date; - updatedAt: Date; - releaseDate: Date; -} - export class SearchWinningResult { winnings: WinningDto[]; pagination: PaginationInfo; } - -export class AuditPayoutDto { - externalTransactionId: string; - status: string; - totalNetAmount: number; - createdAt: Date; - metadata: string; - paymentMethodUsed: string; - externalTransactionDetails: object; -}