feat: implement partner licenses module with pagination and filtering capabilities
This commit is contained in:
@@ -37,12 +37,12 @@ export class PartnerConsumersService {
|
||||
partner_id,
|
||||
})
|
||||
|
||||
async findAll(partner_id: string, page = 1, pageSize = 10) {
|
||||
const [consumers, count] = await this.prisma.$transaction(async tx => [
|
||||
async findAll(partner_id: string, page = 1, perPage = 10) {
|
||||
const [consumers, total] = await this.prisma.$transaction(async tx => [
|
||||
await tx.consumer.findMany({
|
||||
where: this.defaultWhere(partner_id),
|
||||
select: this.defaultSelect,
|
||||
skip: (page - 1) * pageSize,
|
||||
skip: (page - 1) * perPage,
|
||||
take: 10,
|
||||
}),
|
||||
await tx.consumer.count({
|
||||
@@ -50,9 +50,9 @@ export class PartnerConsumersService {
|
||||
}),
|
||||
])
|
||||
return ResponseMapper.paginate(consumers.map(mapConsumerWithLicenseUtil), {
|
||||
count,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
perPage,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { Paginated } from 'common/response/response-mapper'
|
||||
|
||||
export interface PartnerLicenseActivationConsumerResponseDto {
|
||||
id: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
mobile_number: string
|
||||
}
|
||||
|
||||
export interface PartnerLicenseActivationBusinessActivityResponseDto {
|
||||
id: string
|
||||
name: string
|
||||
consumer: PartnerLicenseActivationConsumerResponseDto
|
||||
}
|
||||
|
||||
export interface PartnerLicenseActivationChargeTransactionResponseDto {
|
||||
id: string
|
||||
tracking_code: string
|
||||
activation_expires_at: Date
|
||||
}
|
||||
|
||||
export interface PartnerLicenseActivationLicenseResponseDto {
|
||||
id: string
|
||||
accounts_limit: number
|
||||
charge_transaction: PartnerLicenseActivationChargeTransactionResponseDto
|
||||
}
|
||||
|
||||
export interface PartnerLicenseActivationCountResponseDto {
|
||||
account_allocations: number
|
||||
}
|
||||
|
||||
export interface PartnerLicenseActivationResponseDto {
|
||||
id: string
|
||||
starts_at: Date
|
||||
expires_at: Date
|
||||
created_at: Date
|
||||
updated_at: Date
|
||||
business_activity: PartnerLicenseActivationBusinessActivityResponseDto
|
||||
license: PartnerLicenseActivationLicenseResponseDto
|
||||
_count: PartnerLicenseActivationCountResponseDto
|
||||
}
|
||||
|
||||
export type PartnerLicenseActivationPaginatedResponseDto =
|
||||
Paginated<PartnerLicenseActivationResponseDto>
|
||||
|
||||
export class PartnerLicenseActivationConsumerResponseSchema {
|
||||
@ApiProperty()
|
||||
id: string
|
||||
|
||||
@ApiProperty()
|
||||
first_name: string
|
||||
|
||||
@ApiProperty()
|
||||
last_name: string
|
||||
|
||||
@ApiProperty()
|
||||
mobile_number: string
|
||||
}
|
||||
|
||||
export class PartnerLicenseActivationBusinessActivityResponseSchema {
|
||||
@ApiProperty()
|
||||
id: string
|
||||
|
||||
@ApiProperty()
|
||||
name: string
|
||||
|
||||
@ApiProperty({ type: () => PartnerLicenseActivationConsumerResponseSchema })
|
||||
consumer: PartnerLicenseActivationConsumerResponseSchema
|
||||
}
|
||||
|
||||
export class PartnerLicenseActivationChargeTransactionResponseSchema {
|
||||
@ApiProperty()
|
||||
id: string
|
||||
|
||||
@ApiProperty()
|
||||
tracking_code: string
|
||||
|
||||
@ApiProperty()
|
||||
activation_expires_at: Date
|
||||
}
|
||||
|
||||
export class PartnerLicenseActivationLicenseResponseSchema {
|
||||
@ApiProperty()
|
||||
id: string
|
||||
|
||||
@ApiProperty()
|
||||
accounts_limit: number
|
||||
|
||||
@ApiProperty({ type: () => PartnerLicenseActivationChargeTransactionResponseSchema })
|
||||
charge_transaction: PartnerLicenseActivationChargeTransactionResponseSchema
|
||||
}
|
||||
|
||||
export class PartnerLicenseActivationCountResponseSchema {
|
||||
@ApiProperty()
|
||||
account_allocations: number
|
||||
}
|
||||
|
||||
export class PartnerLicenseActivationResponseSchema {
|
||||
@ApiProperty()
|
||||
id: string
|
||||
|
||||
@ApiProperty()
|
||||
starts_at: Date
|
||||
|
||||
@ApiProperty()
|
||||
expires_at: Date
|
||||
|
||||
@ApiProperty()
|
||||
created_at: Date
|
||||
|
||||
@ApiProperty()
|
||||
updated_at: Date
|
||||
|
||||
@ApiProperty({ type: () => PartnerLicenseActivationBusinessActivityResponseSchema })
|
||||
business_activity: PartnerLicenseActivationBusinessActivityResponseSchema
|
||||
|
||||
@ApiProperty({ type: () => PartnerLicenseActivationLicenseResponseSchema })
|
||||
license: PartnerLicenseActivationLicenseResponseSchema
|
||||
|
||||
@ApiProperty({ type: () => PartnerLicenseActivationCountResponseSchema })
|
||||
_count: PartnerLicenseActivationCountResponseSchema
|
||||
}
|
||||
|
||||
export class PartnerLicenseActivationPaginatedResponseSchema {
|
||||
@ApiProperty({ example: 'paginate' })
|
||||
__mapped: string
|
||||
|
||||
@ApiProperty({ type: () => [PartnerLicenseActivationResponseSchema] })
|
||||
items: PartnerLicenseActivationResponseSchema[]
|
||||
|
||||
@ApiProperty()
|
||||
total: number
|
||||
|
||||
@ApiProperty()
|
||||
page: number
|
||||
|
||||
@ApiProperty()
|
||||
perPage: number
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { PartnerInfo } from '@/common/decorators/partnerInfo.decorator'
|
||||
import { Controller, Get, Query } from '@nestjs/common'
|
||||
import { ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger'
|
||||
import { PartnerLicenseActivationPaginatedResponseSchema } from './dto/licenses-response.dto'
|
||||
import { PartnerLicensesService } from './licenses.service'
|
||||
|
||||
@ApiTags('PartnerLicenses')
|
||||
@Controller('partner/licenses')
|
||||
export class PartnerLicensesController {
|
||||
constructor(private readonly service: PartnerLicensesService) {}
|
||||
|
||||
@Get()
|
||||
@ApiQuery({ name: 'page', required: false, type: Number, example: 1 })
|
||||
@ApiQuery({
|
||||
name: 'perPage',
|
||||
required: false,
|
||||
type: Number,
|
||||
example: 10,
|
||||
description: 'Max value is 50.',
|
||||
})
|
||||
@ApiQuery({ name: 'consumer_name', required: false, type: String })
|
||||
@ApiQuery({ name: 'consumer_mobile', required: false, type: String })
|
||||
@ApiQuery({
|
||||
name: 'expires_from',
|
||||
required: false,
|
||||
type: String,
|
||||
example: '2026-01-01',
|
||||
})
|
||||
@ApiQuery({ name: 'expires_to', required: false, type: String, example: '2026-12-31' })
|
||||
@ApiOkResponse({ type: PartnerLicenseActivationPaginatedResponseSchema })
|
||||
async findAll(
|
||||
@PartnerInfo('id') partnerId: string,
|
||||
@Query('page') page = '1',
|
||||
@Query('perPage') perPage = '10',
|
||||
@Query('consumer_name') consumer_name?: string,
|
||||
@Query('consumer_mobile') consumer_mobile?: string,
|
||||
@Query('expires_from') expires_from?: string,
|
||||
@Query('expires_to') expires_to?: string,
|
||||
) {
|
||||
return this.service.findAll(partnerId, {
|
||||
page: Number(page) || 1,
|
||||
perPage: Number(perPage) || 10,
|
||||
consumer_name,
|
||||
consumer_mobile,
|
||||
expires_from,
|
||||
expires_to,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { PrismaModule } from '@/prisma/prisma.module'
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PartnerLicensesController } from './licenses.controller'
|
||||
import { PartnerLicensesService } from './licenses.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [PartnerLicensesController],
|
||||
providers: [PartnerLicensesService],
|
||||
})
|
||||
export class PartnerLicensesModule {}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { PrismaService } from '@/prisma/prisma.service'
|
||||
import { BadRequestException, Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from 'common/response/response-mapper'
|
||||
import {
|
||||
PartnerLicenseActivationPaginatedResponseDto,
|
||||
PartnerLicenseActivationResponseDto,
|
||||
} from './dto/licenses-response.dto'
|
||||
|
||||
type FindPartnerLicensesParams = {
|
||||
perPage?: number
|
||||
page?: number
|
||||
consumer_name?: string
|
||||
consumer_mobile?: string
|
||||
expires_from?: string
|
||||
expires_to?: string
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PartnerLicensesService {
|
||||
private readonly defaultPerPage = 10
|
||||
private readonly maxPerPage = 50
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findAll(
|
||||
partner_id: string,
|
||||
params: FindPartnerLicensesParams = {},
|
||||
): Promise<PartnerLicenseActivationPaginatedResponseDto> {
|
||||
const { perPage, page, consumer_name, consumer_mobile, expires_from, expires_to } =
|
||||
params
|
||||
|
||||
const normalizedPage = Number.isFinite(page)
|
||||
? Math.max(1, Math.floor(page as number))
|
||||
: 1
|
||||
const requestedPerPage = Number.isFinite(perPage)
|
||||
? Math.max(1, Math.floor(perPage as number))
|
||||
: this.defaultPerPage
|
||||
const normalizedPerPage = Math.min(requestedPerPage, this.maxPerPage)
|
||||
|
||||
const andWhere: any[] = [
|
||||
{
|
||||
license: {
|
||||
charge_transaction: {
|
||||
partner_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const consumerName = consumer_name?.trim()
|
||||
const consumerMobile = consumer_mobile?.trim()
|
||||
|
||||
if (consumerName || consumerMobile) {
|
||||
andWhere.push({
|
||||
business_activity: {
|
||||
consumer: {
|
||||
OR: [
|
||||
{ first_name: { contains: consumerName } },
|
||||
{ last_name: { contains: consumerName } },
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (consumerMobile) {
|
||||
andWhere.push({
|
||||
business_activity: {
|
||||
consumer: {
|
||||
mobile_number: {
|
||||
contains: consumerMobile,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (expires_from || expires_to) {
|
||||
const expiresAtFilter: {
|
||||
gte?: Date
|
||||
lte?: Date
|
||||
} = {}
|
||||
|
||||
if (expires_from) {
|
||||
const fromDate = new Date(expires_from)
|
||||
if (Number.isNaN(fromDate.getTime())) {
|
||||
throw new BadRequestException('مقدار expires_from نامعتبر است.')
|
||||
}
|
||||
expiresAtFilter.gte = fromDate
|
||||
}
|
||||
|
||||
if (expires_to) {
|
||||
const toDate = new Date(expires_to)
|
||||
if (Number.isNaN(toDate.getTime())) {
|
||||
throw new BadRequestException('مقدار expires_to نامعتبر است.')
|
||||
}
|
||||
expiresAtFilter.lte = toDate
|
||||
}
|
||||
|
||||
andWhere.push({
|
||||
expires_at: expiresAtFilter,
|
||||
})
|
||||
}
|
||||
|
||||
const where = { AND: andWhere }
|
||||
|
||||
const [licenseActivations, total] = await this.prisma.$transaction(async tx => [
|
||||
await tx.licenseActivation.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
skip: (normalizedPage - 1) * normalizedPerPage,
|
||||
take: normalizedPerPage,
|
||||
select: {
|
||||
id: true,
|
||||
starts_at: true,
|
||||
expires_at: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
business_activity: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
consumer: {
|
||||
select: {
|
||||
id: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
mobile_number: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
license: {
|
||||
select: {
|
||||
id: true,
|
||||
accounts_limit: true,
|
||||
charge_transaction: {
|
||||
select: {
|
||||
id: true,
|
||||
tracking_code: true,
|
||||
activation_expires_at: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
account_allocations: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
await tx.licenseActivation.count({ where }),
|
||||
])
|
||||
|
||||
return ResponseMapper.paginate<PartnerLicenseActivationResponseDto>(
|
||||
licenseActivations,
|
||||
{
|
||||
total,
|
||||
page: normalizedPage,
|
||||
perPage: normalizedPerPage,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,14 @@ import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/c
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { PartnerAccountsModule } from './accounts/accounts.module'
|
||||
import { PartnerCustomersModule } from './consumers/consumers.module'
|
||||
import { PartnerLicensesModule } from './licenses/licenses.module'
|
||||
import { PartnerController } from './partners.controller'
|
||||
import { PartnerMiddleware } from './partners.middleware'
|
||||
import { PartnerService } from './partners.service'
|
||||
|
||||
@Module({
|
||||
controllers: [PartnerController],
|
||||
imports: [PartnerAccountsModule, PartnerCustomersModule],
|
||||
imports: [PartnerAccountsModule, PartnerCustomersModule, PartnerLicensesModule],
|
||||
providers: [JwtService, PartnerService],
|
||||
})
|
||||
export class PartnerModule implements NestModule {
|
||||
|
||||
Reference in New Issue
Block a user