Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-05-10 09:44:49 +03:30
parent 4e61ff618e
commit afa83895a2
28 changed files with 1663 additions and 2186 deletions
@@ -3,11 +3,10 @@ import { JwtService } from '@nestjs/jwt'
import { AppController } from './application.controller'
import { AppService } from './application.service'
import { ApplicationAuthModule } from './auth/auth.module'
import { ApplicationConfigModule } from './config/config.module'
@Module({
controllers: [AppController],
providers: [AppService, JwtService],
imports: [ApplicationConfigModule, ApplicationAuthModule],
imports: [ApplicationAuthModule],
})
export class ApplicationModule {}
@@ -1,21 +0,0 @@
import { Public } from '@/common/decorators/public.decorator'
import { TokenAccount } from '@/common/decorators/tokenInfo.decorator'
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
import { ConfigService } from './config.service'
import { CreateConfigDto } from './dto/create-config.dto'
@Controller('app/config')
export class ConfigController {
constructor(private readonly configService: ConfigService) {}
@Get(':id')
findOne(@Param('id') uuid: string) {
return this.configService.findOne(uuid)
}
@Post()
@Public()
create(@TokenAccount('userId') consumerId: string, @Body() data: CreateConfigDto) {
return this.configService.create(consumerId, data)
}
}
@@ -1,9 +0,0 @@
import { Module } from '@nestjs/common'
import { ConfigController } from './config.controller'
import { ConfigService } from './config.service'
@Module({
controllers: [ConfigController],
providers: [ConfigService],
})
export class ApplicationConfigModule {}
@@ -1,83 +0,0 @@
import { QUERY_CONSTANTS } from '@/common/queryConstants'
import { ConsumerDevicesSelect } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { CreateConfigDto } from './dto/create-config.dto'
@Injectable()
export class ConfigService {
constructor(private prisma: PrismaService) {}
private readonly defaultSelect: ConsumerDevicesSelect = {
uuid: true,
app_version: true,
brand: true,
browser_name: true,
build_number: true,
device: true,
os_version: true,
fcm_token: true,
model: true,
platform: true,
release_number: true,
sdk_version: true,
consumer: {
select: {
id: true,
...QUERY_CONSTANTS.CONSUMER.infoSelect,
},
},
}
async findOne(uuid: string) {
const config = await this.prisma.consumerDevices.findUnique({
where: {
uuid,
},
select: this.defaultSelect,
})
return ResponseMapper.single(config)
}
async create(consumer_id: string, data: CreateConfigDto) {
const prevConfig = await this.prisma.consumerDevices.findUnique({
where: {
uuid: data.uuid,
},
select: {
consumer_id: true,
},
})
if (prevConfig && consumer_id !== prevConfig.consumer_id) {
throw new BadRequestException('این دستگاه با کاربری دیگری ثبت شده است.')
}
const config = await this.prisma.consumerDevices.upsert({
where: {
uuid: data.uuid,
},
update: {
...data,
consumer: {
connect: {
id: consumer_id,
},
},
},
create: {
...data,
consumer: {
connect: {
id: consumer_id,
},
},
},
select: this.defaultSelect,
})
return ResponseMapper.create(config)
}
}
@@ -1,4 +0,0 @@
import type { ConfigService } from '../config.service'
export type ConfigServiceCreateResponseDto = Awaited<ReturnType<ConfigService['create']>>
export type ConfigServiceFindOneResponseDto = Awaited<ReturnType<ConfigService['findOne']>>
@@ -1,66 +0,0 @@
import { ApplicationPlatform, ApplicationPublisher } from '@/generated/prisma/enums'
import { ApiProperty, PartialType } from '@nestjs/swagger'
import { IsEnum, IsOptional, IsString } from 'class-validator'
export class CreateConfigDto {
@ApiProperty({ required: true })
@IsString()
uuid: string
@ApiProperty({ required: true })
@IsEnum(ApplicationPublisher)
publisher: ApplicationPublisher
@ApiProperty({ required: true })
@IsString()
app_version: string
@ApiProperty({ required: true })
@IsString()
build_number: string
@ApiProperty({ required: true })
@IsEnum(ApplicationPlatform)
platform: ApplicationPlatform
@ApiProperty({ required: true })
@IsString()
brand: string
@ApiProperty({ required: true })
@IsString()
model: string
@ApiProperty({ required: true })
@IsString()
device: string
@ApiProperty({ required: true })
@IsString()
os_version: string
@ApiProperty({ required: true })
@IsString()
sdk_version: string
@ApiProperty({ required: true })
@IsString()
release_number: string
@ApiProperty({ required: false })
@IsString()
@IsOptional()
browser_name: string
@ApiProperty({ required: false })
@IsString()
@IsOptional()
user_agent: string
@ApiProperty({ required: false })
@IsString()
@IsOptional()
fcm_token?: string
}
export class UpdateConfigDto extends PartialType(CreateConfigDto) {}
+20 -16
View File
@@ -12,29 +12,33 @@ export class AuthService {
private authUtils: AuthUtils,
) {}
async login(dto: LoginDto, isPos = false) {
async login(dto: LoginDto, isPos = false, deviceId?: string) {
const { username, password } = dto
const account = await this.prisma.account.findUnique({
where: {
username,
},
})
if (!account) {
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
}
if (isPos) {
const posAccount = await this.prisma.consumerAccount.findUnique({
const account = await this.prisma.$transaction(async tx => {
const account = await tx.account.findUnique({
where: {
account_id: account.id,
username,
},
})
if (!posAccount) {
if (!account) {
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
}
}
if (isPos) {
const posAccount = await tx.consumerAccount.findUnique({
where: {
account_id: account.id,
},
})
if (!posAccount) {
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
}
}
return account
})
await this.authUtils.checkAuthPassword(password, account.password)
@@ -39,6 +39,11 @@ export class SalesInvoicesController {
return this.salesInvoicesService.send(id, posInfo)
}
@Post(':id/retry')
retry(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.salesInvoicesService.retry(id, posInfo)
}
@Post(':id/revoke')
revoke(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.salesInvoicesService.revoke(id, posInfo)
@@ -253,6 +253,19 @@ export class SalesInvoicesService {
}
async send(invoiceId: string, posInfo: IPosPayload) {
await this.checkAccessToInvoice(posInfo.consumer_account_id, posInfo.pos_id)
const invoice = await this.salesInvoiceTaxService.originalSend(
posInfo.pos_id,
invoiceId,
)
return ResponseMapper.single(invoice)
}
async retry(invoiceId: string, posInfo: IPosPayload) {
await this.checkAccessToInvoice(posInfo.consumer_account_id, posInfo.pos_id)
const invoice = await this.salesInvoiceTaxService.originalSend(
posInfo.pos_id,
invoiceId,
@@ -33,6 +33,52 @@ export class SalesInvoiceTspService {
private sharedSaleInvoiceCreateService: SharedSaleInvoiceCreateService,
) {}
private mapProviderError(error: any) {
const isProviderFailureObject =
error &&
typeof error === 'object' &&
('provider_request_payload' in error ||
'provider_response_payload' in error ||
'sent_at' in error ||
'received_at' in error)
if (isProviderFailureObject) {
return {
hasError: true,
status: error.status || TspProviderResponseStatus.FAILURE,
message:
error.message?.toString() ||
'خطا در ارتباط با سامانه مالیاتی. لطفا مجددا تلاش کنید.',
provider_request_payload: error.provider_request_payload,
provider_response_payload: error.provider_response_payload,
sent_at: error.sent_at || new Date().toISOString(),
received_at: error.received_at || new Date().toISOString(),
}
}
return {
hasError: true,
status: TspProviderResponseStatus.FAILURE,
message:
error?.message?.toString() ||
'خطا در ارتباط با سامانه مالیاتی. لطفا مجددا تلاش کنید.',
provider_response_payload: {
error: error?.message || 'fetch failed',
cause: error?.cause || null,
},
sent_at: new Date().toISOString(),
received_at: new Date().toISOString(),
}
}
private async runProviderCall(fn: () => Promise<any>) {
try {
return await fn()
} catch (error: any) {
return this.mapProviderError(error)
}
}
async originalSend(
posId: string,
invoice_id: string,
@@ -40,6 +86,7 @@ export class SalesInvoiceTspService {
const payload = await buildPayload(this.prisma, invoice_id, posId)
const attemptNumber = await getOriginalResendAttemptNumber(this.prisma, invoice_id)
console.log('attemptNumber', attemptNumber)
const attempt = await this.prisma.saleInvoiceTspAttempts.create({
data: {
@@ -51,7 +98,15 @@ export class SalesInvoiceTspService {
},
})
const result = await trySend(this.tspSwitchService, payload)
const result = await this.runProviderCall(() =>
trySend(this.tspSwitchService, payload),
)
if (result?.hasError) {
result.provider_request_payload =
result.provider_request_payload || JSON.parse(JSON.stringify(payload))
result.sent_at = result.sent_at || new Date().toISOString()
result.received_at = result.received_at || new Date().toISOString()
}
return await onResult(this.prisma, result, attempt.id)
}
@@ -260,7 +315,16 @@ export class SalesInvoiceTspService {
},
)
const result = await trySend(this.tspSwitchService, correctionPayload)
const result = await this.runProviderCall(() =>
trySend(this.tspSwitchService, correctionPayload),
)
if (result?.hasError) {
result.provider_request_payload =
result.provider_request_payload ||
JSON.parse(JSON.stringify(correctionPayload))
result.sent_at = result.sent_at || new Date().toISOString()
result.received_at = result.received_at || new Date().toISOString()
}
console.log('sendResult')
console.log(result)
@@ -447,7 +511,15 @@ export class SalesInvoiceTspService {
},
)
const result = await this.tspSwitchService.revoke(revokePayload)
const result = await this.runProviderCall(() =>
this.tspSwitchService.revoke(revokePayload),
)
if (result?.hasError) {
result.provider_request_payload =
result.provider_request_payload || JSON.parse(JSON.stringify(revokePayload))
result.sent_at = result.sent_at || new Date().toISOString()
result.received_at = result.received_at || new Date().toISOString()
}
return await onResult(this.prisma, result, attempt.id)
}
@@ -120,7 +120,7 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
tax_id: null,
status: TspProviderResponseStatus.NOT_SEND,
}
return failure
throw failure
}
}
@@ -31,6 +31,9 @@ export async function getOriginalResendAttemptNumber(
},
},
},
orderBy: {
created_at: 'desc',
},
})
if (existingAttempt) {
@@ -464,6 +467,8 @@ export async function onResult(
provider_request_payload: result.provider_request_payload,
provider_response_payload: JSON.parse(JSON.stringify(result)),
status: result.status,
sent_at: result.sent_at || new Date().toISOString(),
received_at: result.received_at || new Date().toISOString(),
message: result.message?.toString() || 'وجود مشکل در ارسال به سامانه مالیاتی',
},
select: {
@@ -507,7 +512,27 @@ export async function onResult(
}
}
throw new NotFoundException(
'متاسفانه امکان ارسال فاکتور به سیستم مالیاتی در حال حاضر وجود ندارد. لطفا بعدا تلاش کنید.',
)
const updatedAttempt = await prisma.saleInvoiceTspAttempts.update({
where: {
id: attempt_id,
},
data: {
provider_response_payload: {},
status: TspProviderResponseStatus.FAILURE,
message:
'متاسفانه امکان ارسال فاکتور به سیستم مالیاتی در حال حاضر وجود ندارد. لطفا بعدا تلاش کنید.',
received_at: new Date().toISOString(),
},
select: {
status: true,
invoice: true,
message: true,
},
})
return {
invoice: updatedAttempt.invoice,
status: updatedAttempt.status,
message: updatedAttempt.message,
}
}