2026-04-27 10:45:39 +03:30
import { QUERY_CONSTANTS } from '@/common/queryConstants'
import mapConsumerWithLicenseUtil from '@/common/utils/mappers/consumer_mappers.util'
2026-04-23 20:59:39 +03:30
import { PasswordUtil } from '@/common/utils/password.util'
2026-05-19 15:40:45 +03:30
import { RedisKeyMaker } from '@/common/utils/redisKeyMaker'
2026-04-23 20:59:39 +03:30
import {
AccountStatus ,
AccountType ,
ConsumerRole ,
ConsumerStatus ,
2026-04-27 10:45:39 +03:30
ConsumerType ,
2026-04-23 20:59:39 +03:30
PartnerStatus ,
} from '@/generated/prisma/enums'
2026-04-27 10:45:39 +03:30
import {
ConsumerCreateInput ,
ConsumerSelect ,
ConsumerWhereInput ,
} from '@/generated/prisma/models'
2026-04-23 20:59:39 +03:30
import { PrismaService } from '@/prisma/prisma.service'
2026-05-19 15:40:45 +03:30
import { RedisService } from '@/redis/redis.service'
2026-04-23 20:59:39 +03:30
import { BadRequestException , Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
2026-05-19 15:40:45 +03:30
import { PartnersCacheInvalidationService } from '../cache/partners-cache-invalidation.service'
2026-04-23 20:59:39 +03:30
import { CreateConsumerDto , UpdateConsumerDto } from './dto/create-consumers.dto'
@Injectable ( )
export class PartnerConsumersService {
2026-05-19 15:40:45 +03:30
constructor (
private readonly prisma : PrismaService ,
private readonly redisService : RedisService ,
private readonly partnersCacheInvalidationService : PartnersCacheInvalidationService ,
) { }
private readonly infoCacheTtlSeconds = 300
2026-04-23 20:59:39 +03:30
private readonly setExpireDate = ( starts_at : Date ) = > {
return new Date (
new Date ( starts_at ) . setFullYear ( new Date ( starts_at ) . getFullYear ( ) + 1 ) ,
) . toISOString ( )
}
defaultSelect : ConsumerSelect = {
id : true ,
status : true ,
created_at : true ,
2026-05-05 10:14:48 +03:30
type : true ,
2026-04-27 22:11:05 +03:30
. . . QUERY_CONSTANTS . CONSUMER . infoSelect ,
. . . QUERY_CONSTANTS . CONSUMER . activeBusinessCount ,
2026-04-23 20:59:39 +03:30
}
private defaultWhere = ( partner_id : string ) : ConsumerWhereInput = > ( {
2026-04-27 10:45:39 +03:30
OR : [
{
legal : {
partner_id ,
} ,
} ,
{
individual : {
partner_id ,
} ,
} ,
] ,
2026-04-23 20:59:39 +03:30
} )
2026-04-26 09:54:48 +03:30
async findAll ( partner_id : string , page = 1 , perPage = 10 ) {
2026-05-19 15:40:45 +03:30
const cacheKey = RedisKeyMaker . partnerConsumersList ( partner_id , page , perPage )
2026-05-21 17:27:37 +03:30
return await this . redisService . getAndSet ( cacheKey , 'paginate' , async ( ) = > {
const [ consumers , total ] = await this . prisma . $transaction ( async tx = > [
await tx . consumer . findMany ( {
where : this.defaultWhere ( partner_id ) ,
select : this.defaultSelect ,
skip : ( page - 1 ) * perPage ,
take : 10 ,
} ) ,
await tx . consumer . count ( {
where : this.defaultWhere ( partner_id ) ,
} ) ,
] )
const mappedConsumers = consumers . map ( mapConsumerWithLicenseUtil )
2026-05-19 15:40:45 +03:30
2026-05-21 17:27:37 +03:30
return {
items : mappedConsumers ,
total ,
page ,
perPage ,
}
2026-04-23 20:59:39 +03:30
} )
}
async findOne ( partner_id : string , consumer_id : string ) {
2026-05-19 15:40:45 +03:30
const cacheKey = RedisKeyMaker . partnerConsumerInfo ( partner_id , consumer_id )
2026-05-21 17:27:37 +03:30
return await this . redisService . getAndSet ( cacheKey , 'single' , async ( ) = > {
const consumer = await this . prisma . consumer . findUniqueOrThrow ( {
where : {
. . . ( this . defaultWhere ( partner_id ) as any ) ,
id : consumer_id ,
} ,
select : this.defaultSelect ,
} )
2026-05-19 15:40:45 +03:30
2026-05-21 17:27:37 +03:30
return mapConsumerWithLicenseUtil ( consumer )
2026-04-23 20:59:39 +03:30
} )
}
async create ( partner_id : string , data : CreateConsumerDto ) {
const consumer = await this . prisma . $transaction ( async tx = > {
const partnerLicense = await tx . license . findFirst ( {
where : {
charge_transaction : {
partner : {
id : partner_id ,
status : PartnerStatus.ACTIVE ,
} ,
} ,
activation : null ,
} ,
orderBy : {
charge_transaction : {
activation_expires_at : 'asc' ,
} ,
} ,
select : {
id : true ,
charge_transaction : {
select : {
partner : {
select : {
id : true ,
name : true ,
status : true ,
} ,
} ,
} ,
} ,
} ,
} )
if ( ! partnerLicense ) {
throw new BadRequestException ( ` تعداد لایسنسهای فعلی شما به پایان رسیده است. ` )
}
2026-04-27 22:11:05 +03:30
const { username , password , individual , legal , . . . rest } = data
2026-04-23 20:59:39 +03:30
2026-04-27 10:45:39 +03:30
const createConsumerData : ConsumerCreateInput = {
. . . rest ,
accounts : {
create : {
role : ConsumerRole.OWNER ,
account : {
create : {
username ,
password : await PasswordUtil . hash ( password ) ,
type : AccountType . CONSUMER ,
status : AccountStatus.ACTIVE ,
} ,
} ,
} ,
2026-04-23 20:59:39 +03:30
} ,
2026-04-27 10:45:39 +03:30
status : ConsumerStatus.ACTIVE ,
}
2026-04-23 20:59:39 +03:30
2026-04-27 10:45:39 +03:30
const partner = {
partner : { connect : { id : partner_id } } ,
}
if ( ! legal && ! individual ) {
throw new BadRequestException (
` برای ایجاد مصرف کننده باید یکی از اطلاعات فردی یا حقوقی وارد شود. ` ,
)
}
if ( data . type === ConsumerType . INDIVIDUAL && ! individual ) {
2026-04-23 20:59:39 +03:30
throw new BadRequestException (
2026-04-27 10:45:39 +03:30
` برای ایجاد مصرف کننده با نوع حقیقی باید اطلاعات فردی وارد شود. ` ,
)
}
if ( data . type === ConsumerType . LEGAL && ! legal ) {
throw new BadRequestException (
` برای ایجاد مصرف کننده با نوع حقوقی باید اطلاعات حقوقی وارد شود. ` ,
2026-04-23 20:59:39 +03:30
)
}
2026-04-27 10:45:39 +03:30
let consumerExistError = ''
if ( legal ) {
const existingConsumer = await tx . consumer . findFirst ( {
where : {
legal : { registration_code : legal.registration_code , partner_id } ,
} ,
select : {
id : true ,
legal : {
select : {
name : true ,
} ,
2026-04-23 20:59:39 +03:30
} ,
} ,
2026-04-27 10:45:39 +03:30
} )
createConsumerData . legal = {
create : {
. . . legal ,
. . . partner ,
} ,
}
if ( existingConsumer ) {
consumerExistError = ` مشتری با نام ${ existingConsumer . legal ! . name } با این شماره ثبت، از قبل توسط شما ایجاد شده است. `
}
} else if ( individual ) {
const existingConsumer = await tx . consumer . findFirst ( {
where : {
individual : { national_code : individual.national_code , partner_id } ,
} ,
select : {
id : true ,
individual : {
select : {
first_name : true ,
last_name : true ,
2026-04-23 20:59:39 +03:30
} ,
} ,
} ,
2026-04-27 10:45:39 +03:30
} )
createConsumerData . individual = {
create : {
. . . individual ,
. . . partner ,
} ,
}
if ( existingConsumer ) {
2026-05-07 20:30:24 +03:30
consumerExistError = ` مشتری با نام ${ existingConsumer . individual ! . first_name } ${ existingConsumer . individual ! . last_name } یا این کد ملی، از قبل توسط شما ایجاد شده است. `
2026-04-27 10:45:39 +03:30
}
}
2026-05-07 20:30:24 +03:30
if ( consumerExistError ) {
throw new BadRequestException ( consumerExistError )
}
2026-04-27 10:45:39 +03:30
return await tx . consumer . create ( {
data : createConsumerData ,
2026-04-23 20:59:39 +03:30
select : this.defaultSelect ,
} )
} )
2026-05-19 15:40:45 +03:30
await this . partnersCacheInvalidationService . invalidatePartnerConsumerReadModels (
partner_id ,
consumer . id ,
)
2026-04-23 20:59:39 +03:30
return ResponseMapper . single ( mapConsumerWithLicenseUtil ( consumer ) )
}
async update ( id : string , partner_id : string , data : UpdateConsumerDto ) {
2026-05-19 15:40:45 +03:30
const updatedConsumer = await this . prisma . consumer . update ( {
2026-04-23 20:59:39 +03:30
where : {
. . . ( this . defaultWhere ( partner_id ) as any ) ,
id ,
} ,
2026-04-27 22:11:05 +03:30
data : {
status : data.status ,
legal : {
update : data.legal ,
} ,
individual : {
update : data.individual ,
} ,
} ,
2026-04-23 20:59:39 +03:30
} )
2026-05-19 15:40:45 +03:30
await this . partnersCacheInvalidationService . invalidatePartnerConsumerReadModels (
partner_id ,
id ,
)
await this . redisService . delete ( RedisKeyMaker . consumerInfo ( id ) )
return updatedConsumer
2026-04-23 20:59:39 +03:30
}
}