feat: refactor ComplexPosesService to remove unused defaultInsert method and improve findAll logic

fix: update PartnersService to use 'isNot' instead of 'not' for allocation checks

refactor: enhance BusinessActivityComplexesService to validate license activation before creating a complex

fix: adjust ComplexPosesService to ensure account allocation checks are accurate and handle errors properly

refactor: modify ConsumerMiddleware to set consumerData instead of partnerData for better clarity

feat: expand SaleInvoicesService to include additional fields in the invoice selection

chore: update business-activities module to include accounts-charge module for better organization

fix: ensure ComplexPosesService correctly handles account allocation during POS creation

feat: implement PartnerBusinessActivityAccountsCharge module with create functionality for account charges

refactor: streamline getPartnerBusinessActivityAllocationLimits utility for better clarity and functionality

chore: add migration script to update database schema with necessary constraints and foreign keys

feat: create DTO for accounts charge to validate incoming data
This commit is contained in:
2026-04-25 15:16:59 +03:30
parent 12506de863
commit b72e6c7194
36 changed files with 975 additions and 568 deletions
@@ -0,0 +1,23 @@
import { PartnerInfo } from '@/common/decorators/partnerInfo.decorator'
import { Body, Controller, Param, Post } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { PartnerBusinessActivityAccountsChargeService } from './accounts-charge.service'
import { CreateBusinessActivityAccountsChargeDto } from './dto/accounts-charge.dto'
@ApiTags('PartnerConsumerBAAccountsCharge')
@Controller(
'partner/consumers/:consumerId/business-activities/:businessActivityId/accounts-charge',
)
export class PartnerBusinessActivityAccountsChargeController {
constructor(private readonly service: PartnerBusinessActivityAccountsChargeService) {}
@Post()
async create(
@PartnerInfo('id') partnerId: string,
@Param('consumerId') consumerId: string,
@Param('businessActivityId') businessActivityId: string,
@Body() data: CreateBusinessActivityAccountsChargeDto,
) {
return this.service.create(partnerId, consumerId, businessActivityId, data)
}
}
@@ -0,0 +1,11 @@
import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common'
import { PartnerBusinessActivityAccountsChargeController } from './accounts-charge.controller'
import { PartnerBusinessActivityAccountsChargeService } from './accounts-charge.service'
@Module({
imports: [PrismaModule],
controllers: [PartnerBusinessActivityAccountsChargeController],
providers: [PartnerBusinessActivityAccountsChargeService],
})
export class PartnerBusinessActivityAccountsChargeModule {}
@@ -0,0 +1,127 @@
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { CreateBusinessActivityAccountsChargeDto } from './dto/accounts-charge.dto'
@Injectable()
export class PartnerBusinessActivityAccountsChargeService {
constructor(private readonly prisma: PrismaService) {}
async create(
partner_id: string,
consumer_id: string,
business_activity_id: string,
data: CreateBusinessActivityAccountsChargeDto,
) {
const { quantity } = data
const startOfDay = new Date()
startOfDay.setHours(0, 0, 0, 0)
const result = await this.prisma.$transaction(async tx => {
const activation = await tx.licenseActivation.findFirst({
where: {
business_activity_id,
business_activity: {
id: business_activity_id,
consumer: {
id: consumer_id,
partner_id,
},
},
OR: [
{
expires_at: {
gte: startOfDay,
},
},
{
license_renews: {
some: {
expires_at: {
gte: startOfDay,
},
},
},
},
],
},
select: {
id: true,
},
})
if (!activation) {
throw new BadRequestException('لایسنس فعال برای این فعالیت تجاری یافت نشد.')
}
const credits = await tx.partnerAccountQuotaCredit.findMany({
where: {
allocation: null,
charge_transaction: {
partner_id,
activation_expires_at: {
gte: startOfDay,
},
},
},
orderBy: [
{
charge_transaction: {
activation_expires_at: 'asc',
},
},
{
created_at: 'asc',
},
],
take: quantity,
select: {
id: true,
charge_transaction: {
select: {
activation_expires_at: true,
},
},
},
})
if (credits.length < quantity) {
throw new BadRequestException('اعتبار کافی برای تخصیص حساب وجود ندارد.')
}
const allocations = await Promise.all(
credits.map(credit =>
tx.licenseAccountAllocation.create({
data: {
license_activation: {
connect: {
id: activation.id,
},
},
credit: {
connect: {
id: credit.id,
},
},
},
select: {
id: true,
credit_id: true,
license_activation_id: true,
created_at: true,
},
}),
),
)
return {
quantity,
activation_id: activation.id,
allocations,
}
})
return ResponseMapper.create(result.allocations)
}
}
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsInt, Min } from 'class-validator'
export class CreateBusinessActivityAccountsChargeDto {
@ApiProperty({ minimum: 1, example: 1 })
@IsInt()
@Min(1)
quantity: number
}
@@ -1,11 +1,16 @@
import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common'
import { PartnerBusinessActivityAccountsChargeModule } from './accounts-charge/accounts-charge.module'
import { BusinessActivitiesController } from './business-activities.controller'
import { BusinessActivitiesService } from './business-activities.service'
import { AdminBusinessActivityComplexesModule } from './complexes/complexes.module'
@Module({
imports: [PrismaModule, AdminBusinessActivityComplexesModule],
imports: [
PrismaModule,
AdminBusinessActivityComplexesModule,
PartnerBusinessActivityAccountsChargeModule,
],
controllers: [BusinessActivitiesController],
providers: [BusinessActivitiesService],
})
@@ -1,6 +1,6 @@
import { ComplexSelect, ComplexWhereInput } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { CreateComplexDto, UpdateComplexDto } from './dto/complex.dto'
@@ -55,16 +55,62 @@ export class BusinessActivityComplexesService {
}
async create(partner_id: string, business_id: string, data: CreateComplexDto) {
const complex = await this.prisma.complex.create({
data: {
...data,
business_activity: {
connect: {
id: business_id,
const complex = this.prisma.$transaction(async tx => {
const now = new Date()
const relatedLicense = await tx.licenseAccountAllocation.findFirst({
where: {
license_activation: {
license: {
charge_transaction: {
partner_id,
},
},
business_activity: {
id: business_id,
},
OR: [
{
expires_at: {
gte: now,
},
},
{
license_renews: {
some: {
expires_at: {
gte: now,
},
},
},
},
],
},
account_id: null,
},
select: {
id: true,
},
})
if (!relatedLicense) {
throw new BadRequestException(
`متاسفانه به دلیل به پایان رسیدن محدودیت تعریف کاربران برای این فعالیت اقتصادی،‌امکان ایجاد شعبه‌ی جدید وجود ندارد.`,
)
}
return await this.prisma.complex.create({
data: {
...data,
business_activity: {
connect: {
id: business_id,
},
},
},
},
})
})
return ResponseMapper.create(complex)
}
@@ -5,7 +5,7 @@ import {
ConsumerRole,
POSStatus,
} from '@/generated/prisma/enums'
import { PosCreateInput, PosSelect } from '@/generated/prisma/models'
import { PosSelect } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
@@ -95,35 +95,6 @@ export class ComplexPosesService {
}
}
private readonly defaultInsert = (data: CreatePosDto, complex_id: string) => {
const { device_id, provider_id, ...rest } = data
const dataToCreate: PosCreateInput = {
...rest,
complex: {
connect: {
id: complex_id,
},
},
provider: provider_id
? {
connect: {
id: provider_id,
},
}
: undefined,
device: device_id
? {
connect: {
id: device_id,
},
}
: undefined,
}
return dataToCreate
}
async findAll(partner_id: string, business_activity_id: string, complex_id: string) {
const poses = await this.prisma.pos.findMany({
where: this.defaultWhere(partner_id, complex_id, business_activity_id),
@@ -155,26 +126,43 @@ export class ComplexPosesService {
data: CreatePosDto,
) {
const pos = this.prisma.$transaction(async tx => {
const ba = await tx.businessActivity.findUniqueOrThrow({
const now = new Date()
const activeAccountAllocation = await tx.licenseAccountAllocation.findFirst({
where: {
id: business_activity_id,
consumer: {
partner_id,
account_id: null,
license_activation: {
business_activity_id,
OR: [
{
expires_at: {
gte: now,
},
},
{
license_renews: {
some: {
expires_at: {
gte: now,
},
},
},
},
],
license: {
charge_transaction: {
partner_id,
},
},
},
},
select: {
consumer_id: true,
id: true,
license_activation: {
select: {
_count: {
business_activity: {
select: {
account_allocations: {
where: {
account_id: {
not: undefined,
},
},
},
consumer_id: true,
},
},
},
@@ -182,7 +170,7 @@ export class ComplexPosesService {
},
})
if (!ba.license_activation?._count.account_allocations) {
if (!activeAccountAllocation) {
throw new BadRequestException(
`محدودیت تعریف تعداد کاربران این فعالیت تجاری به پایان رسیده است.`,
)
@@ -198,9 +186,16 @@ export class ComplexPosesService {
account: {
create: {
role: ConsumerRole.OPERATOR,
consumer: {
connect: {
id: ba.consumer_id,
id: activeAccountAllocation.license_activation!.business_activity
.consumer_id,
},
},
account_allocation: {
connect: {
id: activeAccountAllocation.id,
},
},
account: {
@@ -274,9 +269,4 @@ export class ComplexPosesService {
})
return ResponseMapper.update(pos)
}
// async delete(id: string) {
// await this.prisma.complex.delete({ where: { id } })
// return ResponseMapper.delete()
// }
}