feat: implement correction and original send functionality for Nama provider

- Added new DTOs for correction requests and responses in `nama-provider.dto.ts`.
- Updated `nama-provider.adapter.ts` to include `originalSend` and `correctionSend` methods.
- Enhanced `nama-provider.util.ts` with mapping functions for correction requests.
- Created operational guidelines for agents in `AGENT.md`.
- Updated Prisma migrations to support new invoice types and relationships.
- Introduced new service and DTO for creating sales invoices in `sale-invoice-create.service.ts` and `sale-invoice-create.dto.ts`.
- Added utility for handling Prisma errors in `prisma-error.util.ts`.
This commit is contained in:
2026-05-05 22:42:09 +03:30
parent 4af07fe3e8
commit 658496320b
43 changed files with 2808 additions and 1354 deletions
@@ -1,5 +1,5 @@
import { PartnerInfo } from '@/common/decorators/partnerInfo.decorator'
import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common'
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { BusinessActivitiesService } from './business-activities.service'
import {
@@ -38,7 +38,7 @@ export class BusinessActivitiesController {
return this.businessActivitiesService.create(partnerId, consumerId, data)
}
@Put(':id')
@Patch(':id')
async update(
@PartnerInfo('id') partnerId: string,
@Param('consumerId') consumerId: string,
@@ -54,4 +54,9 @@ export class BusinessActivitiesController {
// }
}
import type { BusinessActivitiesServiceCreateResponseDto, BusinessActivitiesServiceFindAllResponseDto, BusinessActivitiesServiceFindOneResponseDto, BusinessActivitiesServiceUpdateResponseDto } from './dto/business-activities-response.dto'
import type {
BusinessActivitiesServiceCreateResponseDto,
BusinessActivitiesServiceFindAllResponseDto,
BusinessActivitiesServiceFindOneResponseDto,
BusinessActivitiesServiceUpdateResponseDto,
} from './dto/business-activities-response.dto'
@@ -20,6 +20,9 @@ export class BusinessActivitiesService {
name: true,
economic_code: true,
created_at: true,
fiscal_id: true,
invoice_number_sequence: true,
partner_token: true,
guild: {
select: {
id: true,
@@ -81,13 +84,20 @@ export class BusinessActivitiesService {
}
}
async findAll(partner_id: string, consumer_id: string) {
const businessActivities = await this.prisma.businessActivity.findMany({
where: this.defaultWhere(partner_id, consumer_id),
select: this.defaultSelect,
})
return ResponseMapper.list(
async findAll(partner_id: string, consumer_id: string, page = 1, perPage = 10) {
const where = this.defaultWhere(partner_id, consumer_id)
const [businessActivities, total] = await this.prisma.$transaction(async tx => [
await tx.businessActivity.findMany({
where,
select: this.defaultSelect,
skip: (page - 1) * perPage,
take: perPage,
}),
await tx.businessActivity.count({ where }),
])
return ResponseMapper.paginate(
businessActivities.map(this.prepareBusinessActivityResponse),
{ total, page, perPage },
)
}
@@ -42,18 +42,30 @@ export class BusinessActivityComplexesService {
},
})
async findAll(partner_id: string, consumer_id: string, business_activity_id: string) {
const complexes = await this.prisma.complex.findMany({
where: this.defaultWhere(partner_id, consumer_id, business_activity_id),
select: {
...this.defaultSelect,
_count: {
select: {
pos_list: true,
async findAll(
partner_id: string,
consumer_id: string,
business_activity_id: string,
page = 1,
perPage = 10,
) {
const where = this.defaultWhere(partner_id, consumer_id, business_activity_id)
const [complexes, total] = await this.prisma.$transaction(async tx => [
await tx.complex.findMany({
where,
select: {
...this.defaultSelect,
_count: {
select: {
pos_list: true,
},
},
},
},
})
skip: (page - 1) * perPage,
take: perPage,
}),
await tx.complex.count({ where }),
])
const mappedComplexes = complexes.map(complex => {
const { _count, ...rest } = complex
@@ -62,7 +74,7 @@ export class BusinessActivityComplexesService {
pos_count: _count.pos_list,
}
})
return ResponseMapper.list(mappedComplexes)
return ResponseMapper.paginate(mappedComplexes, { total, page, perPage })
}
async findOne(
@@ -1,5 +1,6 @@
import { QUERY_CONSTANTS } from '@/common/queryConstants'
import { consumerRelatedPartner } from '@/common/queryConstants/consumer'
import { PrismaErrorUtil } from '@/common/utils/prisma-error.util'
import { PasswordUtil } from '@/common/utils/password.util'
import {
AccountStatus,
@@ -97,17 +98,32 @@ export class ComplexPosesService {
}
}
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),
},
select: this.defaultSelect,
})
async findAll(
partner_id: string,
business_activity_id: string,
complex_id: string,
page = 1,
perPage = 10,
) {
const [poses, total] = await this.prisma.$transaction(async tx => [
await tx.pos.findMany({
where: {
...this.defaultWhere(partner_id, complex_id, business_activity_id),
},
select: this.defaultSelect,
skip: (page - 1) * perPage,
take: perPage,
}),
await tx.pos.count({
where: {
...this.defaultWhere(partner_id, complex_id, business_activity_id),
},
}),
])
const mappedPoses = poses.map(this.mapPos)
return ResponseMapper.list(mappedPoses)
return ResponseMapper.paginate(mappedPoses, { total, page, perPage })
}
async findOne(
@@ -129,94 +145,102 @@ export class ComplexPosesService {
complex_id: string,
data: CreatePosDto,
) {
const pos = this.prisma.$transaction(async tx => {
const activeAccountAllocation = await tx.licenseAccountAllocation.findFirst({
where: {
account_id: null,
license_activation: {
business_activity_id,
...QUERY_CONSTANTS.LICENSE_ACTIVATION.activeOnDate(),
license: {
charge_transaction: {
partner_id,
},
},
},
},
select: {
id: true,
license_activation: {
select: {
business_activity: {
select: {
consumer_id: true,
let pos
try {
pos = await this.prisma.$transaction(async tx => {
const activeAccountAllocation = await tx.licenseAccountAllocation.findFirst({
where: {
account_id: null,
license_activation: {
business_activity_id,
...QUERY_CONSTANTS.LICENSE_ACTIVATION.activeOnDate(),
license: {
charge_transaction: {
partner_id,
},
},
},
},
},
select: {
id: true,
license_activation: {
select: {
business_activity: {
select: {
consumer_id: true,
},
},
},
},
},
})
if (!activeAccountAllocation) {
throw new BadRequestException(
`محدودیت تعریف تعداد کاربران این فعالیت تجاری به پایان رسیده است.`,
)
}
const { device_id, provider_id, username, password, ...rest } = data
return await tx.pos.create({
data: {
...rest,
status: POSStatus.ACTIVE,
account: {
create: {
role: ConsumerRole.OPERATOR,
consumer: {
connect: {
id: activeAccountAllocation.license_activation!.business_activity
.consumer_id,
},
},
account_allocation: {
connect: {
id: activeAccountAllocation.id,
},
},
account: {
create: {
username,
password: await PasswordUtil.hash(password),
type: AccountType.CONSUMER,
status: AccountStatus.ACTIVE,
},
},
},
},
complex: {
connect: {
id: complex_id,
},
},
device: device_id
? {
connect: {
id: device_id,
},
}
: undefined,
provider: provider_id
? {
connect: {
id: provider_id,
},
}
: undefined,
},
})
})
if (!activeAccountAllocation) {
throw new BadRequestException(
`محدودیت تعریف تعداد کاربران این فعالیت تجاری به پایان رسیده است.`,
)
}
const { device_id, provider_id, username, password, ...rest } = data
return await tx.pos.create({
data: {
...rest,
status: POSStatus.ACTIVE,
account: {
create: {
role: ConsumerRole.OPERATOR,
consumer: {
connect: {
id: activeAccountAllocation.license_activation!.business_activity
.consumer_id,
},
},
account_allocation: {
connect: {
id: activeAccountAllocation.id,
},
},
account: {
create: {
username,
password: await PasswordUtil.hash(password),
type: AccountType.CONSUMER,
status: AccountStatus.ACTIVE,
},
},
},
},
complex: {
connect: {
id: complex_id,
},
},
device: device_id
? {
connect: {
id: device_id,
},
}
: undefined,
provider: provider_id
? {
connect: {
id: provider_id,
},
}
: undefined,
},
} catch (error) {
PrismaErrorUtil.throwIfKnown(error, {
username: 'نام کاربری تکراری است',
accounts_username_key: 'نام کاربری تکراری است',
})
})
}
return ResponseMapper.create(pos)
}