update license structures and setup file storage services

This commit is contained in:
2026-04-16 22:19:20 +03:30
parent d098ef10e1
commit ca494ee82a
75 changed files with 4550 additions and 1255 deletions
@@ -0,0 +1,36 @@
import { Controller, Get, Param } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { PartnerActivatedLicensesService } from './activatedLicenses.service'
@ApiTags('AdminPartnerActivatedLicenses')
@Controller('admin/partners/:partnerId/activated-licenses')
export class PartnerLicensesController {
constructor(private readonly service: PartnerActivatedLicensesService) {}
@Get()
async findAll(@Param('partnerId') partnerId: string) {
return this.service.findAll(partnerId)
}
// @Get(':id')
// async findOne(@Param('partnerId') partnerId: string, @Param('id') id: string) {
// return this.service.findOne(partnerId, id)
// }
// @Post()
// async create(
// @Param('partnerId') partnerId: string,
// @Body() data: CreatePartnerLicenseDto,
// ) {
// return this.licensesService.create(partnerId, data)
// }
// @Patch(':id')
// async update(
// @Param('partnerId') partnerId: string,
// @Param('id') id: string,
// @Body() data: UpdateLicenseDto,
// ) {
// return this.licensesService.update(partnerId, id, data)
// }
}
@@ -0,0 +1,12 @@
import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common'
import { PartnerLicensesController } from './activatedLicenses.controller'
import { PartnerActivatedLicensesService } from './activatedLicenses.service'
@Module({
imports: [PrismaModule],
controllers: [PartnerLicensesController],
providers: [PartnerActivatedLicensesService],
exports: [PartnerActivatedLicensesService],
})
export class AdminPartnerActivatedLicensesModule {}
@@ -0,0 +1,62 @@
import { ActivatedLicenseWhereInput } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
@Injectable()
export class PartnerActivatedLicensesService {
constructor(private readonly prisma: PrismaService) {}
async findAll(partner_id: string, page = 1, pageSize = 10) {
const defaultWhere: ActivatedLicenseWhereInput = {
license: {
charged_license_transaction: {
partner_id,
},
},
}
const [licenses, count] = await this.prisma.$transaction(async tx => [
await tx.activatedLicense.findMany({
where: defaultWhere,
skip: (page - 1) * pageSize,
take: pageSize,
select: {
id: true,
starts_at: true,
expires_at: true,
license_id: true,
created_at: true,
consumer: {
select: {
id: true,
first_name: true,
last_name: true,
},
},
},
}),
await tx.activatedLicense.count({
where: defaultWhere,
}),
])
return ResponseMapper.paginate(licenses, {
page,
pageSize,
count,
})
}
// async update(partnerId: string, id: string, data: UpdateLicenseDto) {
// const license = await this.prisma.license.update({
// where: { id },
// data: { ...data, partner_id: partnerId },
// })
// return ResponseMapper.update(license)
// }
async delete(partnerId: string, id: string) {
await this.prisma.license.delete({ where: { id } })
return ResponseMapper.delete()
}
}
@@ -0,0 +1,3 @@
export class CreatePartnerLicenseDto {}
export class UpdateLicenseDto {}
@@ -0,0 +1,34 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { PartnerChargedLicenseTransactionsService } from './chargedLicenseTransactions.service'
import { ChargeLicenseDto } from './dto/chargedLicenseTransactions.dto'
@ApiTags('AdminPartnerChargedLicenseTransactions')
@Controller('admin/partners/:partnerId/charge-license-transactions')
export class PartnerChargedLicenseTransactionsController {
constructor(private readonly service: PartnerChargedLicenseTransactionsService) {}
@Get()
async findAll(@Param('partnerId') partnerId: string) {
return this.service.findAll(partnerId)
}
@Get(':id')
async findOne(@Param('partnerId') partnerId: string, @Param('id') id: string) {
return this.service.findOne(partnerId, id)
}
@Post()
async create(@Param('partnerId') partnerId: string, @Body() data: ChargeLicenseDto) {
return this.service.create(partnerId, data)
}
// @Patch(':id')
// async update(
// @Param('partnerId') partnerId: string,
// @Param('id') id: string,
// @Body() data: UpdateLicenseDto,
// ) {
// return this.licensesService.update(partnerId, id, data)
// }
}
@@ -0,0 +1,12 @@
import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common'
import { PartnerChargedLicenseTransactionsController } from './chargedLicenseTransactions.controller'
import { PartnerChargedLicenseTransactionsService } from './chargedLicenseTransactions.service'
@Module({
imports: [PrismaModule],
controllers: [PartnerChargedLicenseTransactionsController],
providers: [PartnerChargedLicenseTransactionsService],
exports: [PartnerChargedLicenseTransactionsService],
})
export class AdminPartnerChargedLicenseTransactionsModule {}
@@ -0,0 +1,122 @@
import {
ChargedLicenseTransactionsWhereInput,
LicenseCreateInput,
} from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { ChargeLicenseDto } from './dto/chargedLicenseTransactions.dto'
@Injectable()
export class PartnerChargedLicenseTransactionsService {
constructor(private readonly prisma: PrismaService) {}
private readonly mappedTransaction = (transaction: any) => {
const { licenses, ...rest } = transaction
return {
...rest,
charged_license_count: licenses.length,
remained_license_count: licenses.filter(license => !license?.activated_license)
.length,
}
}
async findAll(partner_id: string, page = 1, pageSize = 10) {
const defaultWhere: ChargedLicenseTransactionsWhereInput = {
partner_id,
}
const [transactions, count] = await this.prisma.$transaction(async tx => [
await tx.chargedLicenseTransactions.findMany({
where: defaultWhere,
skip: (page - 1) * pageSize,
take: pageSize,
select: {
id: true,
created_at: true,
activation_expires_at: true,
licenses: {
select: {
activated_license: {
select: {
id: true,
},
},
},
},
},
}),
await tx.chargedLicenseTransactions.count({
where: defaultWhere,
}),
])
const mappedTransactions = transactions.map(this.mappedTransaction)
return ResponseMapper.paginate(mappedTransactions, {
page,
pageSize,
count,
})
}
async findOne(partner_id: string, id: string) {
const transaction = this.prisma.chargedLicenseTransactions.findUniqueOrThrow({
where: {
id,
partner_id,
},
select: {
id: true,
created_at: true,
activation_expires_at: true,
licenses: {
select: {
id: true,
activated_license: {
select: {
id: true,
},
},
},
},
},
})
return ResponseMapper.single(this.mappedTransaction(transaction))
}
async create(partner_id: string, data: ChargeLicenseDto) {
try {
return await this.prisma.$transaction(async tx => {
const transaction = await tx.chargedLicenseTransactions.create({
data: {
activation_expires_at: data.activated_expires_at,
partner: { connect: { id: partner_id } },
},
})
if (!transaction) {
throw new BadRequestException('متاسفانه مشکلی پیش آمده است.')
}
const licenseCreationPromises: any[] = []
for (let i = 0; i < data.quantity; i++) {
const license: LicenseCreateInput = {
charged_license_transaction: {
connect: {
id: transaction.id,
},
},
}
licenseCreationPromises.push(tx.license.create({ data: license }))
}
const createdLicenses = await Promise.all(licenseCreationPromises)
return { transaction, createdLicenses }
})
} catch (error) {
throw error
}
}
}
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsDateString, IsNumber } from 'class-validator'
export class CreatePartnerLicenseDto {}
export class UpdateLicenseDto {}
export class ChargeLicenseDto {
@ApiProperty({
required: true,
minimum: 1,
maximum: 10_000,
})
@IsNumber({
maxDecimalPlaces: 0,
})
quantity: number
@ApiProperty({
required: true,
})
@IsDateString()
activated_expires_at: string
}
@@ -1,71 +0,0 @@
import { IsDateString, IsEnum, IsOptional, IsString } from 'class-validator'
import { AccountStatus, LicenseStatus, POSType } from 'generated/prisma/enums'
export class CreatePartnerLicenseDto {
@IsString()
first_name: string
@IsString()
last_name: string
@IsString()
mobile_number: string
@IsString()
national_code: string
@IsString()
username: string
@IsString()
password: string
@IsEnum(AccountStatus)
account_status: AccountStatus
@IsString()
guild: string
@IsString()
business_activity_name: string
@IsString()
tax_id: string
@IsString()
complex_name: string
@IsString()
pos_serial: string
@IsString()
pos_status: string
@IsEnum(POSType)
pos_type: POSType
@IsString()
pos_device: string
@IsDateString()
@IsOptional()
starts_at: string
@IsDateString()
@IsOptional()
expires_at: string
}
export class UpdateLicenseDto {
@IsString()
pos_id?: string
@IsDateString()
starts_at?: string
@IsDateString()
expires_at?: string
@IsEnum(LicenseStatus)
status?: LicenseStatus
}
@@ -1,35 +0,0 @@
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'
import { CreatePartnerLicenseDto, UpdateLicenseDto } from './dto/license.dto'
import { PartnerLicensesService } from './licenses.service'
@Controller('admin/partners/:partnerId/licenses')
export class PartnerLicensesController {
constructor(private readonly licensesService: PartnerLicensesService) {}
@Get()
async findAll(@Param('partnerId') partnerId: string) {
return this.licensesService.findAll(partnerId)
}
@Get(':id')
async findOne(@Param('partnerId') partnerId: string, @Param('id') id: string) {
return this.licensesService.findOne(partnerId, id)
}
@Post()
async create(
@Param('partnerId') partnerId: string,
@Body() data: CreatePartnerLicenseDto,
) {
return this.licensesService.create(partnerId, data)
}
@Patch(':id')
async update(
@Param('partnerId') partnerId: string,
@Param('id') id: string,
@Body() data: UpdateLicenseDto,
) {
return this.licensesService.update(partnerId, id, data)
}
}
@@ -1,12 +0,0 @@
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],
exports: [PartnerLicensesService],
})
export class AdminPartnerLicensesModule {}
@@ -1,183 +0,0 @@
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { CreatePartnerLicenseDto, UpdateLicenseDto } from './dto/license.dto'
@Injectable()
export class PartnerLicensesService {
constructor(private readonly prisma: PrismaService) {}
async findAll(partner_id: string, page = 1, pageSize = 10) {
const [licenses, count] = await this.prisma.$transaction(async tx => [
await tx.license.findMany({
where: {
partner_id,
},
skip: (page - 1) * pageSize,
take: pageSize,
select: {
id: true,
starts_at: true,
expires_at: true,
consumer: {
select: {
id: true,
first_name: true,
last_name: true,
},
},
},
}),
await tx.license.count({
where: {
partner_id,
},
}),
])
return ResponseMapper.paginate(licenses, {
page,
pageSize,
count,
})
}
async findOne(partnerId: string, id: string) {
// const license = await this.prisma.license.findFirst({
// where: { id, partner_id: partnerId },
// include: {
// pos: {
// include: {
// complex: true,
// provider: true,
// accounts: {
// include: {
// user: true,
// },
// omit: {
// business_id: true,
// partner_id: true,
// provider_id: true,
// pos_id: true,
// },
// },
// device: {
// include: {
// brand: true,
// },
// omit: {
// brand_id: true,
// },
// },
// },
// },
// },
// omit: {
// pos_id: true,
// partner_id: true,
// },
// })
// if (license) {
// const { pos, ...rest } = license
// const { accounts, ...posRest } = pos
// return ResponseMapper.single({
// account: accounts[0],
// pos: posRest,
// ...rest,
// })
// }
return ResponseMapper.single({})
}
async create(partnerId: string, data: CreatePartnerLicenseDto) {
// const license = await this.prisma.$transaction(async tx => {
// const user = await tx.user.upsert({
// where: {
// mobile_number: data.mobile_number,
// },
// update: {},
// create: {
// first_name: data.first_name,
// last_name: data.last_name,
// mobile_number: data.mobile_number,
// national_code: data.national_code,
// },
// })
// const business = await tx.businessActivity.create({
// data: {
// name: data.business_activity_name,
// guild_id: data.guild,
// user: {
// connect: {
// id: user.id,
// },
// },
// },
// })
// const complex = await tx.complex.create({
// data: {
// name: data.complex_name,
// tax_id: data.tax_id,
// business_activity_id: business.id,
// },
// })
// const pos = await tx.pos.create({
// data: {
// pos_type: data.pos_type,
// serial: data.pos_serial,
// device_id: data.pos_device,
// complex_id: complex.id,
// },
// })
// const date = new Date()
// const expiresAt = new Date(date.setFullYear(date.getFullYear() + 1))
// const license = await tx.license.create({
// data: {
// partner_id: partnerId,
// pos_id: pos.id,
// starts_at: data.starts_at || new Date(),
// expires_at: data.expires_at || expiresAt,
// status: LicenseStatus.ACTIVE,
// },
// })
// const hashedPassword = await PasswordUtil.hash(data.password)
// const account = await tx.account.create({
// data: {
// username: data.username,
// password: hashedPassword,
// type: AccountType.POS,
// user_id: user.id,
// pos_id: pos.id,
// status: data.account_status,
// },
// })
// return {
// license,
// }
// })
return ResponseMapper.create('license')
}
async update(partnerId: string, id: string, data: UpdateLicenseDto) {
const license = await this.prisma.license.update({
where: { id },
data: { ...data, partner_id: partnerId },
})
return ResponseMapper.update(license)
}
async delete(partnerId: string, id: string) {
await this.prisma.license.delete({ where: { id } })
return ResponseMapper.delete()
}
}
@@ -1,5 +1,5 @@
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
import { CreatePartnerDto, LicenseChargeDto, UpdatePartnerDto } from './dto/partner.dto'
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'
import { CreatePartnerDto, UpdatePartnerDto } from './dto/partner.dto'
import { PartnersService } from './partners.service'
@Controller('admin/partners')
@@ -26,13 +26,8 @@ export class PartnersController {
return this.partnersService.update(id, data)
}
@Post(':id/charge-license')
async licenseCharge(@Param('id') id: string, @Body() data: LicenseChargeDto) {
return this.partnersService.licenseCharge(id, data)
}
@Delete(':id')
async delete(@Param('id') id: string) {
return this.partnersService.delete(id)
}
// @Delete(':id')
// async delete(@Param('id') id: string) {
// return this.partnersService.delete(id)
// }
}
@@ -1,13 +1,19 @@
import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common'
import { AdminPartnerLicensesModule } from './licenses/licenses.module'
import { AdminPartnerActivatedLicensesModule } from './activatedLicenses/activatedLicenses.module'
import { AdminPartnerChargedLicenseTransactionsModule } from './chargedLicenseTransactions/chargedLicenseTransactions.module'
import { AdminPartnerAccountsModule } from './partnerAccounts/accounts.module'
import { PartnersController } from './partners.controller'
import { PartnersService } from './partners.service'
@Module({
imports: [PrismaModule, AdminPartnerLicensesModule, AdminPartnerAccountsModule],
imports: [
PrismaModule,
AdminPartnerChargedLicenseTransactionsModule,
AdminPartnerActivatedLicensesModule,
AdminPartnerAccountsModule,
],
controllers: [PartnersController],
providers: [PartnersService],
exports: [PartnersService],
+91 -30
View File
@@ -1,43 +1,111 @@
import { PasswordUtil } from '@/common/utils/password.util'
import { ChargedLicenseTransactions } from '@/generated/prisma/client'
import {
AccountStatus,
AccountType,
PartnerRole,
PartnerStatus,
} from '@/generated/prisma/enums'
import { PartnerSelect } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { CreatePartnerDto, LicenseChargeDto, UpdatePartnerDto } from './dto/partner.dto'
import { CreatePartnerDto, UpdatePartnerDto } from './dto/partner.dto'
@Injectable()
export class PartnersService {
constructor(private readonly prisma: PrismaService) {}
private readonly defaultSelect: PartnerSelect = {
id: true,
name: true,
code: true,
status: true,
created_at: true,
chargedLicenseTransactions: {
select: {
activation_expires_at: true,
licenses: {
select: {
activated_license: {
select: {
id: true,
},
},
},
},
},
},
}
private readonly separateLicenseCount = (
transactions: ChargedLicenseTransactions[] | null,
) => {
function toDateOnlyString(date) {
return date.toISOString().slice(0, 10)
}
const startOfTodayDate = toDateOnlyString(new Date())
const used = transactions?.reduce((sum, cur) => {
const license = cur as any
return (
sum + license.licenses.filter(license => license.activated_license).length || 0
)
}, 0)
const total = transactions?.reduce((sum, cur) => {
const license = cur as any
return sum + license.licenses.length || 0
}, 0)
const expired = transactions?.reduce((sum, cur) => {
const license = cur as any
const activationExpiresDate = toDateOnlyString(license.activation_expires_at)
if (startOfTodayDate > activationExpiresDate)
return (
sum + license.licenses.filter(license => !license.activated_license).length || 0
)
return sum
}, 0)
return {
total,
used,
expired,
}
}
async findAll() {
const partners = await this.prisma.partner.findMany()
return ResponseMapper.list(partners)
const partners = await this.prisma.partner.findMany({
select: this.defaultSelect,
})
const mappedPartners = partners.map(partner => {
const { chargedLicenseTransactions, ...rest } = partner
return {
...rest,
licenses_status: this.separateLicenseCount(chargedLicenseTransactions),
}
})
return ResponseMapper.list(mappedPartners)
}
async findOne(id: string) {
const [partner, relatedLicenseCount] = await this.prisma.$transaction([
this.prisma.partner.findUnique({
where: { id },
}),
this.prisma.license.count({
where: {
partner_id: id,
},
}),
])
return ResponseMapper.single({
...partner,
remained_license: Math.max(
(partner?.license_quota || 0) - (relatedLicenseCount || 0),
0,
),
const partner = await this.prisma.partner.findUniqueOrThrow({
where: { id },
select: this.defaultSelect,
})
const { chargedLicenseTransactions, ...rest } = partner
const mappedPartner = {
...rest,
licenses_status: this.separateLicenseCount(chargedLicenseTransactions),
}
return ResponseMapper.single(mappedPartner)
}
async create(data: CreatePartnerDto) {
@@ -60,23 +128,16 @@ export class PartnersService {
},
},
},
select: this.defaultSelect,
})
return ResponseMapper.create(partner)
}
async update(id: string, data: UpdatePartnerDto) {
const partner = await this.prisma.partner.update({ where: { id }, data })
return ResponseMapper.update(partner)
}
async licenseCharge(id: string, data: LicenseChargeDto) {
const partner = await this.prisma.partner.update({
where: { id },
data: {
license_quota: {
increment: data.count,
},
},
data,
select: this.defaultSelect,
})
return ResponseMapper.update(partner)
}