diff --git a/src/common/queryConstants/saleInvoice.ts b/src/common/queryConstants/saleInvoice.ts index 5780cc5..06b8ec1 100644 --- a/src/common/queryConstants/saleInvoice.ts +++ b/src/common/queryConstants/saleInvoice.ts @@ -13,6 +13,8 @@ export const summarySelect: SalesInvoiceSelect = { created_at: true, settlement_type: true, unknown_customer: true, + last_attempt_no: true, + last_tsp_status: true, customer: { select: { type: true, @@ -36,17 +38,6 @@ export const summarySelect: SalesInvoiceSelect = { }, }, }, - tsp_attempts: { - orderBy: { - created_at: 'desc', - }, - take: 1, - select: { - status: true, - sent_at: true, - message: true, - }, - }, reference_invoice: { select: { id: true, @@ -61,6 +52,7 @@ export const select: SalesInvoiceSelect = { tax_amount: true, updated_at: true, unknown_customer: true, + pos: { select: { id: true, @@ -133,19 +125,4 @@ export const select: SalesInvoiceSelect = { }, }, }, - tsp_attempts: { - orderBy: { - created_at: 'desc', - }, - select: { - id: true, - attempt_no: true, - status: true, - message: true, - sent_at: true, - received_at: true, - created_at: true, - }, - take: 1, - }, } diff --git a/src/common/services/saleInvoices/sale-invoice-filter.dto.ts b/src/common/services/saleInvoices/sale-invoice-filter.dto.ts new file mode 100644 index 0000000..7c7bfb0 --- /dev/null +++ b/src/common/services/saleInvoices/sale-invoice-filter.dto.ts @@ -0,0 +1,90 @@ +import { ApiPropertyOptional } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { + IsDateString, + IsEnum, + IsNumber, + IsOptional, + IsString, + Min, +} from 'class-validator' +import { TspProviderResponseStatus } from 'generated/prisma/enums' + +export class SharedSaleInvoicesFilterDto { + @ApiPropertyOptional({ default: 1 }) + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) + page?: number + + @ApiPropertyOptional({ default: 10 }) + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) + perPage?: number + + @ApiPropertyOptional() + @IsOptional() + @IsDateString() + invoice_date_from?: string + + @ApiPropertyOptional() + @IsOptional() + @IsDateString() + invoice_date_to?: string + + @ApiPropertyOptional() + @IsOptional() + @IsDateString() + created_at_from?: string + + @ApiPropertyOptional() + @IsOptional() + @IsDateString() + created_at_to?: string + + @ApiPropertyOptional() + @IsOptional() + @IsString() + customer_name?: string + + @ApiPropertyOptional() + @IsOptional() + @IsString() + customer_mobile?: string + + @ApiPropertyOptional() + @IsOptional() + @IsString() + customer_national_id?: string + + @ApiPropertyOptional() + @IsOptional() + @IsString() + customer_economic_code?: string + + @ApiPropertyOptional({ enum: TspProviderResponseStatus }) + @IsOptional() + @IsEnum(TspProviderResponseStatus) + status?: TspProviderResponseStatus + + @ApiPropertyOptional() + @IsOptional() + @Type(() => Number) + @IsNumber() + total_amount?: number + + @ApiPropertyOptional() + @IsOptional() + @Type(() => Number) + @IsNumber() + total_amount_from?: number + + @ApiPropertyOptional() + @IsOptional() + @Type(() => Number) + @IsNumber() + total_amount_to?: number +} diff --git a/src/common/services/saleInvoices/sale-invoice-filter.service.ts b/src/common/services/saleInvoices/sale-invoice-filter.service.ts new file mode 100644 index 0000000..1679898 --- /dev/null +++ b/src/common/services/saleInvoices/sale-invoice-filter.service.ts @@ -0,0 +1,137 @@ +import { TspProviderResponseStatus } from '@/generated/prisma/enums' +import { SalesInvoiceWhereInput } from '@/generated/prisma/models' +import { Injectable } from '@nestjs/common' +import { SharedSaleInvoicesFilterDto } from './sale-invoice-filter.dto' + +@Injectable() +export class SharedSaleInvoiceFilterService { + buildWhere(filter: SharedSaleInvoicesFilterDto): SalesInvoiceWhereInput { + const where: SalesInvoiceWhereInput = {} + + if (filter.invoice_date_from || filter.invoice_date_to) { + where.invoice_date = { + ...(filter.invoice_date_from ? { gte: new Date(filter.invoice_date_from) } : {}), + ...(filter.invoice_date_to ? { lte: new Date(filter.invoice_date_to) } : {}), + } + } + + if (filter.created_at_from || filter.created_at_to) { + where.created_at = { + ...(filter.created_at_from ? { gte: new Date(filter.created_at_from) } : {}), + ...(filter.created_at_to ? { lte: new Date(filter.created_at_to) } : {}), + } + } + + if ( + filter.total_amount !== undefined || + filter.total_amount_from !== undefined || + filter.total_amount_to !== undefined + ) { + where.total_amount = { + ...(filter.total_amount !== undefined ? { equals: filter.total_amount } : {}), + ...(filter.total_amount_from !== undefined + ? { gte: filter.total_amount_from } + : {}), + ...(filter.total_amount_to !== undefined ? { lte: filter.total_amount_to } : {}), + } + } + + if ( + filter.customer_name?.trim() || + filter.customer_mobile?.trim() || + filter.customer_national_id?.trim() || + filter.customer_economic_code?.trim() + ) { + where.customer = { + is: { + OR: [ + ...(filter.customer_name?.trim() + ? [ + { + individual: { + is: { + OR: [ + { first_name: { contains: filter.customer_name.trim() } }, + { last_name: { contains: filter.customer_name.trim() } }, + ], + }, + }, + }, + { + legal: { + is: { + name: { + contains: filter.customer_name.trim(), + }, + }, + }, + }, + ] + : []), + ...(filter.customer_mobile?.trim() + ? [ + { + individual: { + is: { + mobile_number: { contains: filter.customer_mobile.trim() }, + }, + }, + }, + ] + : []), + ...(filter.customer_national_id?.trim() + ? [ + { + individual: { + is: { + national_id: { contains: filter.customer_national_id.trim() }, + }, + }, + }, + ] + : []), + ...(filter.customer_economic_code?.trim() + ? [ + { + legal: { + is: { + OR: [ + { + economic_code: { + contains: filter.customer_economic_code.trim(), + }, + }, + { + registration_number: { + contains: filter.customer_economic_code.trim(), + }, + }, + ], + }, + }, + }, + ] + : []), + ], + }, + } + } + + if (filter.status) { + if (filter.status === TspProviderResponseStatus.NOT_SEND) { + where.OR = [ + { + last_tsp_status: null, + }, + { + last_tsp_status: TspProviderResponseStatus.NOT_SEND, + }, + ] + } else { + where.last_tsp_status = filter.status + } + } + + return where + } +} diff --git a/src/common/services/saleInvoices/sale-invoice-pagination.service.ts b/src/common/services/saleInvoices/sale-invoice-pagination.service.ts new file mode 100644 index 0000000..0c50d8d --- /dev/null +++ b/src/common/services/saleInvoices/sale-invoice-pagination.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common' + +@Injectable() +export class SharedSaleInvoicePaginationService { + normalize( + page: number = 1, + perPage: number = 10, + defaultPerPage: number = 10, + maxPerPage: number = 50, + ) { + const normalizedPageValue = Number(page ?? 1) + const normalizedPage = Number.isFinite(normalizedPageValue) + ? Math.max(1, Math.floor(normalizedPageValue)) + : 1 + + const requestedPerPageValue = Number(perPage ?? defaultPerPage) + const requestedPerPage = Number.isFinite(requestedPerPageValue) + ? Math.max(1, Math.floor(requestedPerPageValue)) + : defaultPerPage + const normalizedPerPage = Math.min(requestedPerPage, maxPerPage) + + return { + page: normalizedPage, + perPage: normalizedPerPage, + skip: (normalizedPage - 1) * normalizedPerPage, + take: normalizedPerPage, + } + } +} diff --git a/src/modules/consumer/business-activities/business-activities.service.ts b/src/modules/consumer/business-activities/business-activities.service.ts index 56cbee9..ae7e52d 100644 --- a/src/modules/consumer/business-activities/business-activities.service.ts +++ b/src/modules/consumer/business-activities/business-activities.service.ts @@ -71,7 +71,7 @@ export class BusinessActivitiesService { async findOne(consumer_id: string, id: string) { const cacheKey = RedisKeyMaker.consumerBusinessActivityInfo(consumer_id, id) - return await this.redisService.getAndSet(cacheKey, 'list', async () => { + return await this.redisService.getAndSet(cacheKey, 'single', async () => { return await this.businessActivitiesQueryService.findOneByConsumer( consumer_id, id, diff --git a/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.controller.ts b/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.controller.ts index 1ee1cd0..eb266a3 100644 --- a/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.controller.ts +++ b/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param, Post } from '@nestjs/common' +import { Controller, Get, Param, Post, Query } from '@nestjs/common' import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' import { ApiTags } from '@nestjs/swagger' @@ -14,8 +14,10 @@ export class SalesInvoicesController { @TokenAccount('userId') userId: string, @Param('complexId') complexId: string, @Param('posId') posId: string, + @Query('page') page: number, + @Query('perPage') perPage: number, ) { - return this.salesInvoicesService.findAll(userId, complexId, posId) + return this.salesInvoicesService.findAll(userId, complexId, posId, page, perPage) } @Get(':id') diff --git a/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.module.ts b/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.module.ts index 9442d26..ed165a0 100644 --- a/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.module.ts +++ b/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.module.ts @@ -1,5 +1,6 @@ -import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service' +import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' import { SaleInvoiceTspModule } from '@/modules/tspProviders/sales-invoice-tsp.module' import { Module } from '@nestjs/common' import { SalesInvoicesController } from './sales-invoices.controller' @@ -12,6 +13,7 @@ import { SalesInvoicesService } from './sales-invoices.service' SalesInvoicesService, SharedSaleInvoiceActionsService, SharedSaleInvoiceAccessService, + SharedSaleInvoicePaginationService, ], }) export class ConsumerPosSalesInvoicesModule {} diff --git a/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.service.ts b/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.service.ts index 0027ea0..a3f80eb 100644 --- a/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.service.ts +++ b/src/modules/consumer/business-activities/complexes/poses/sales-invoices/sales-invoices.service.ts @@ -1,5 +1,8 @@ +import { QUERY_CONSTANTS } from '@/common/queryConstants' import { ResponseMapper } from '@/common/response/response-mapper' import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' +import { translateEnumValue } from '@/common/utils' import { SalesInvoiceWhereInput } from '@/generated/prisma/models' import { PrismaService } from '@/prisma/prisma.service' import { Injectable, NotFoundException } from '@nestjs/common' @@ -9,9 +12,19 @@ export class SalesInvoicesService { constructor( private prisma: PrismaService, private readonly sharedSaleInvoiceActionsService: SharedSaleInvoiceActionsService, + private readonly sharedSaleInvoicePaginationService: SharedSaleInvoicePaginationService, ) {} - async findAll(consumer_id: string, complex_id: string, pos_id: string) { + async findAll( + consumer_id: string, + complex_id: string, + pos_id: string, + requestedPage: number = 1, + requestedPerPage: number = 10, + ) { + const { page, perPage, skip, take } = + this.sharedSaleInvoicePaginationService.normalize(requestedPage, requestedPerPage) + const defaultWhere: SalesInvoiceWhereInput = { pos_id, pos: { @@ -24,98 +37,41 @@ export class SalesInvoicesService { }, } - const perPage = 10 - const page = 1 - - const [items, total] = await this.prisma.$transaction([ + const [saleInvoices, total] = await this.prisma.$transaction([ this.prisma.salesInvoice.findMany({ where: defaultWhere, select: { - id: true, - code: true, - invoice_date: true, - notes: true, - total_amount: true, - - items: { + ...QUERY_CONSTANTS.SALE_INVOICE.select, + _count: { select: { - measure_unit_code: true, - measure_unit_text: true, - sku_code: true, - discount_amount: true, - tax_amount: true, - notes: true, - quantity: true, - total_amount: true, - unit_price: true, - payload: true, - good: { - select: { - id: true, - name: true, - sku: { - select: { - id: true, - name: true, - }, - }, - barcode: true, - local_sku: true, - pricing_model: true, - measure_unit: { - select: { - id: true, - name: true, - }, - }, - category: { - select: { - id: true, - name: true, - }, - }, - }, - }, + items: true, }, }, - payments: { - select: { - amount: true, - paid_at: true, - payment_method: true, - }, - }, - customer: { - select: { - type: true, - individual: { - select: { - economic_code: true, - first_name: true, - last_name: true, - postal_code: true, - national_id: true, - }, - }, - legal: { - select: { - economic_code: true, - postal_code: true, - registration_number: true, - }, - }, - }, - }, - unknown_customer: true, }, - skip: (page - 1) * perPage, - take: perPage, + orderBy: { + created_at: 'desc', + }, + skip, + take, }), this.prisma.salesInvoice.count({ where: defaultWhere, }), ]) - return ResponseMapper.paginate(items, { total, page, perPage }) + const mappedAccounts = saleInvoices.map(saleInvoice => { + const { _count, consumer_account, last_tsp_status, ...rest } = saleInvoice + return { + ...rest, + items_count: _count.items, + status: translateEnumValue('TspProviderResponseStatus', last_tsp_status), + } + }) + + return ResponseMapper.paginate(mappedAccounts, { + total, + page, + perPage, + }) } findOne(complex_id: string, pos_id: string, id: string) { diff --git a/src/modules/consumer/customers/customers.module.ts b/src/modules/consumer/customers/customers.module.ts index d75b53e..f69b7e4 100644 --- a/src/modules/consumer/customers/customers.module.ts +++ b/src/modules/consumer/customers/customers.module.ts @@ -1,3 +1,4 @@ +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' import { PrismaModule } from '@/prisma/prisma.module' import { Module } from '@nestjs/common' import { consumerCustomersController } from './customers.controller' @@ -7,6 +8,6 @@ import { ConsumerSaleInvoicesModule } from './sale-invoices/sale-invoices.module @Module({ imports: [PrismaModule, ConsumerSaleInvoicesModule], controllers: [consumerCustomersController], - providers: [consumerCustomersService], + providers: [consumerCustomersService, SharedSaleInvoicePaginationService], }) export class ConsumerCustomersModule {} diff --git a/src/modules/consumer/customers/customers.service.ts b/src/modules/consumer/customers/customers.service.ts index c3ab18e..e036f07 100644 --- a/src/modules/consumer/customers/customers.service.ts +++ b/src/modules/consumer/customers/customers.service.ts @@ -1,3 +1,4 @@ +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' import { CustomerSelect, CustomerWhereInput } from '@/generated/prisma/models' import { PrismaService } from '@/prisma/prisma.service' import { Injectable } from '@nestjs/common' @@ -9,7 +10,10 @@ import { @Injectable() export class consumerCustomersService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly sharedSaleInvoicePaginationService: SharedSaleInvoicePaginationService, + ) {} defaultSelect: CustomerSelect = { id: true, @@ -56,13 +60,15 @@ export class consumerCustomersService { } } - async findAll(consumer_id: string, page = 1, perPage = 10) { + async findAll(consumer_id: string, requestedPage = 1, requestedPerPage = 10) { + const { page, perPage, skip, take } = + this.sharedSaleInvoicePaginationService.normalize(requestedPage, requestedPerPage) const [customers, total] = await this.prisma.$transaction(async tx => [ await tx.customer.findMany({ where: this.defaultWhere(consumer_id), select: this.defaultSelect, - skip: (page - 1) * perPage, - take: 10, + skip, + take, }), await tx.customer.count({ where: this.defaultWhere(consumer_id), diff --git a/src/modules/consumer/customers/sale-invoices/sale-invoices.module.ts b/src/modules/consumer/customers/sale-invoices/sale-invoices.module.ts index 43b993f..46eb9b5 100644 --- a/src/modules/consumer/customers/sale-invoices/sale-invoices.module.ts +++ b/src/modules/consumer/customers/sale-invoices/sale-invoices.module.ts @@ -1,3 +1,4 @@ +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' import { PrismaModule } from '@/prisma/prisma.module' import { Module } from '@nestjs/common' import { CustomerSaleInvoicesController } from './sale-invoices.controller' @@ -6,6 +7,6 @@ import { CustomerSaleInvoicesService } from './sale-invoices.service' @Module({ imports: [PrismaModule], controllers: [CustomerSaleInvoicesController], - providers: [CustomerSaleInvoicesService], + providers: [CustomerSaleInvoicesService, SharedSaleInvoicePaginationService], }) export class ConsumerSaleInvoicesModule {} diff --git a/src/modules/consumer/customers/sale-invoices/sale-invoices.service.ts b/src/modules/consumer/customers/sale-invoices/sale-invoices.service.ts index 7d7baea..03f844a 100644 --- a/src/modules/consumer/customers/sale-invoices/sale-invoices.service.ts +++ b/src/modules/consumer/customers/sale-invoices/sale-invoices.service.ts @@ -1,57 +1,28 @@ import { QUERY_CONSTANTS } from '@/common/queryConstants' -import consumer_mappersUtil from '@/common/utils/mappers/consumer_mappers.util' -import { SalesInvoiceSelect, SalesInvoiceWhereInput } from '@/generated/prisma/models' +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' +import { translateEnumValue } from '@/common/utils' +import { TspProviderResponseStatus } from '@/generated/prisma/enums' +import { SalesInvoiceWhereInput } from '@/generated/prisma/models' import { PrismaService } from '@/prisma/prisma.service' -import { Injectable } from '@nestjs/common' +import { Injectable, NotFoundException } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' @Injectable() export class CustomerSaleInvoicesService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly sharedSaleInvoicePaginationService: SharedSaleInvoicePaginationService, + ) {} - private readonly defaultSelect: SalesInvoiceSelect = { - id: true, - code: true, - invoice_date: true, - notes: true, - total_amount: true, - pos: { - select: { - id: true, - name: true, - complex: { - select: { - id: true, - name: true, - business_activity: { - select: { - id: true, - name: true, - }, - }, - }, - }, - }, - }, - consumer_account: { - select: { - role: true, - consumer: { - select: { - ...QUERY_CONSTANTS.CONSUMER.infoSelect, - }, - }, - account: { - select: { - username: true, - }, - }, - }, - }, - created_at: true, - } + async findAll( + consumer_id: string, + customer_id: string, + requestedPage = 1, + requestedPerPage = 10, + ) { + const { page, perPage, skip, take } = + this.sharedSaleInvoicePaginationService.normalize(requestedPage, requestedPerPage) - async findAll(consumer_id: string, customer_id: string, page = 1, perPage = 10) { const salesWhere: SalesInvoiceWhereInput = { customer_id, pos: { @@ -67,15 +38,30 @@ export class CustomerSaleInvoicesService { await tx.salesInvoice.findMany({ where: salesWhere, select: { - ...this.defaultSelect, + ...QUERY_CONSTANTS.SALE_INVOICE.summarySelect, + pos: { + select: { + name: true, + complex: { + select: { + name: true, + business_activity: { + select: { + name: true, + }, + }, + }, + }, + }, + }, _count: { select: { items: true, }, }, }, - skip: (page - 1) * perPage, - take: 10, + skip, + take, }), await tx.salesInvoice.count({ where: salesWhere, @@ -83,16 +69,11 @@ export class CustomerSaleInvoicesService { ]) const mappedAccounts = saleInvoices.map(saleInvoice => { - const { _count, consumer_account, ...rest } = saleInvoice - const { consumer, ...restConsumerAccount } = consumer_account as any - const mappedConsumer = consumer_mappersUtil(consumer) + const { _count, consumer_account, last_tsp_status, ...rest } = saleInvoice return { ...rest, items_count: _count.items, - consumer_account: { - ...restConsumerAccount, - consumer: mappedConsumer, - }, + status: translateEnumValue('TspProviderResponseStatus', last_tsp_status), } }) @@ -104,7 +85,7 @@ export class CustomerSaleInvoicesService { } async findOne(consumer_id: string, customer_id: string, id: string) { - const saleInvoice = await this.prisma.salesInvoice.findUniqueOrThrow({ + const invoice = await this.prisma.salesInvoice.findUnique({ where: { id, customer_id, @@ -117,59 +98,26 @@ export class CustomerSaleInvoicesService { }, }, select: { - ...this.defaultSelect, - items: { - select: { - id: true, - notes: true, - unit_price: true, - quantity: true, - discount_amount: true, - tax_amount: true, - total_amount: true, - payload: true, - good: { - select: { - id: true, - name: true, - pricing_model: true, - measure_unit: { - select: { - id: true, - name: true, - }, - }, - category: { - select: { - id: true, - name: true, - image_url: true, - }, - }, - }, - }, - }, - }, - payments: { - select: { - amount: true, - paid_at: true, - payment_method: true, - }, - }, + ...QUERY_CONSTANTS.SALE_INVOICE.select, }, }) - const { _count, consumer_account, ...rest } = saleInvoice - const { consumer, ...restConsumerAccount } = consumer_account as any - const mappedConsumer = consumer_mappersUtil(consumer) - return ResponseMapper.single({ - ...rest, - items_count: _count.items, - consumer_account: { - ...restConsumerAccount, - consumer: mappedConsumer, - }, - }) + if (invoice) { + const { type, ...rest } = invoice + const mappedInvoice = { + ...rest, + type: translateEnumValue('TspProviderRequestType', type), + status: translateEnumValue( + 'TspProviderResponseStatus', + invoice.last_tsp_status || TspProviderResponseStatus.NOT_SEND, + ), + settlement_type: translateEnumValue( + 'InvoiceSettlementType', + invoice.settlement_type, + ), + } + return ResponseMapper.single(mappedInvoice) + } + throw new NotFoundException('صورت‌حساب مورد نظر شما یافت نشد.') } } diff --git a/src/modules/consumer/poses/poses.service.ts b/src/modules/consumer/poses/poses.service.ts index 12d6822..c1722ac 100644 --- a/src/modules/consumer/poses/poses.service.ts +++ b/src/modules/consumer/poses/poses.service.ts @@ -2,6 +2,7 @@ import { QUERY_CONSTANTS } from '@/common/queryConstants' import { PrismaService } from '@/prisma/prisma.service' import { Injectable } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' +import { UpdateBusinessActivityDto } from './dto/poses.dto' @Injectable() export class PosesService { @@ -43,7 +44,7 @@ export class PosesService { }) } - async update(consumer_id: string, id: string, data: any) { + async update(consumer_id: string, id: string, data: UpdateBusinessActivityDto) { const pos = await this.prisma.pos.update({ where: { ...this.defaultWhere(consumer_id), id }, data, diff --git a/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts b/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts index 2735bbf..2e6d13f 100644 --- a/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts +++ b/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts @@ -1,95 +1,12 @@ +import { SharedSaleInvoicesFilterDto } from '@/common/services/saleInvoices/sale-invoice-filter.dto' import { ApiPropertyOptional } from '@nestjs/swagger' -import { Type } from 'class-transformer' -import { - IsDateString, - IsEnum, - IsNumber, - IsOptional, - IsString, - Max, - Min, -} from 'class-validator' -import { TspProviderResponseStatus } from 'generated/prisma/enums' - -export class ConsumerSaleInvoicesFilterDto { - @ApiPropertyOptional({ type: Number, example: 1 }) - @Type(() => Number) - @Min(1) - @IsOptional() - page?: number +import { Max } from 'class-validator' +export class ConsumerSaleInvoicesFilterDto extends SharedSaleInvoicesFilterDto { @ApiPropertyOptional({ type: Number, example: 10, description: 'Max value is 50.' }) - @Type(() => Number) - @Min(1) @Max(50) - @IsOptional() - perPage?: number + declare perPage?: number @ApiPropertyOptional() - @IsOptional() - @IsDateString() - invoice_date_from?: string - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - invoice_date_to?: string - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - created_at_from?: string - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - created_at_to?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() code?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_name?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_mobile?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_national_id?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_economic_code?: string - - @ApiPropertyOptional({ enum: TspProviderResponseStatus }) - @IsOptional() - @IsEnum(TspProviderResponseStatus) - status?: TspProviderResponseStatus - - @ApiPropertyOptional() - @IsOptional() - @Type(() => Number) - @IsNumber() - total_amount?: number - - @ApiPropertyOptional() - @IsOptional() - @Type(() => Number) - @IsNumber() - total_amount_from?: number - - @ApiPropertyOptional() - @IsOptional() - @Type(() => Number) - @IsNumber() - total_amount_to?: number } diff --git a/src/modules/consumer/saleInvoices/saleInvoices.module.ts b/src/modules/consumer/saleInvoices/saleInvoices.module.ts index 68e7644..7c7fe1a 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.module.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.module.ts @@ -1,5 +1,7 @@ import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service' +import { SharedSaleInvoiceFilterService } from '@/common/services/saleInvoices/sale-invoice-filter.service' +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' import { SaleInvoiceTspModule } from '@/modules/tspProviders/sales-invoice-tsp.module' import { PrismaModule } from '@/prisma/prisma.module' import { Module } from '@nestjs/common' @@ -9,7 +11,13 @@ import { SaleInvoicesService } from './saleInvoices.service' @Module({ imports: [PrismaModule, SaleInvoiceTspModule], controllers: [StatisticsController], - providers: [SaleInvoicesService, SharedSaleInvoiceActionsService, SharedSaleInvoiceAccessService], + providers: [ + SaleInvoicesService, + SharedSaleInvoiceActionsService, + SharedSaleInvoiceAccessService, + SharedSaleInvoiceFilterService, + SharedSaleInvoicePaginationService, + ], exports: [SaleInvoicesService], }) export class ConsumerSaleInvoicesModule {} diff --git a/src/modules/consumer/saleInvoices/saleInvoices.service.ts b/src/modules/consumer/saleInvoices/saleInvoices.service.ts index 430a9d6..447b1dc 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.service.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.service.ts @@ -1,6 +1,8 @@ import { IPosPayload } from '@/common/models' import { QUERY_CONSTANTS } from '@/common/queryConstants' import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' +import { SharedSaleInvoiceFilterService } from '@/common/services/saleInvoices/sale-invoice-filter.service' +import { SharedSaleInvoicePaginationService } from '@/common/services/saleInvoices/sale-invoice-pagination.service' import { translateEnumValue } from '@/common/utils' import { TspProviderResponseStatus } from '@/generated/prisma/enums' import { @@ -20,11 +22,10 @@ export class SaleInvoicesService { private readonly prisma: PrismaService, private salesInvoiceTaxService: SalesInvoiceTspService, private sharedSaleInvoiceActionsService: SharedSaleInvoiceActionsService, + private sharedSaleInvoiceFilterService: SharedSaleInvoiceFilterService, + private sharedSaleInvoicePaginationService: SharedSaleInvoicePaginationService, ) {} - private readonly defaultPerPage = 10 - private readonly maxPerPage = 50 - private readonly defaultSelect: SalesInvoiceSelect = { pos: { select: { @@ -47,29 +48,21 @@ export class SaleInvoicesService { } private readonly invoiceMapper = (invoice: any) => { - const { tsp_attempts, ...rest } = invoice || {} + const { last_tsp_status, ...rest } = invoice || {} return { ...rest, status: translateEnumValue( 'TspProviderResponseStatus', - invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND, + last_tsp_status || TspProviderResponseStatus.NOT_SEND, ), } } async findAll(consumer_id: string, filter: ConsumerSaleInvoicesFilterDto) { const invoicesWhere = this.buildFindAllWhere(consumer_id, filter) - const normalizedPageValue = Number(filter.page ?? 1) - const normalizedPage = Number.isFinite(normalizedPageValue) - ? Math.max(1, Math.floor(normalizedPageValue)) - : 1 - - const requestedPerPageValue = Number(filter.perPage ?? this.defaultPerPage) - const requestedPerPage = Number.isFinite(requestedPerPageValue) - ? Math.max(1, Math.floor(requestedPerPageValue)) - : this.defaultPerPage - const normalizedPerPage = Math.min(requestedPerPage, this.maxPerPage) + const { page, perPage, skip, take } = + this.sharedSaleInvoicePaginationService.normalize(filter.page, filter.perPage) const [invoices, total] = await this.prisma.$transaction(async tx => [ await tx.salesInvoice.findMany({ @@ -77,8 +70,8 @@ export class SaleInvoicesService { orderBy: { created_at: 'desc', }, - skip: (normalizedPage - 1) * normalizedPerPage, - take: normalizedPerPage, + skip, + take, select: { ...QUERY_CONSTANTS.SALE_INVOICE.summarySelect, ...this.defaultSelect, @@ -89,8 +82,8 @@ export class SaleInvoicesService { const mappedInvoices = invoices.map(this.invoiceMapper) return ResponseMapper.paginate(mappedInvoices, { - page: normalizedPage, - perPage: normalizedPerPage, + page, + perPage, total, }) } @@ -100,9 +93,11 @@ export class SaleInvoicesService { filter: ConsumerSaleInvoicesFilterDto, ): SalesInvoiceWhereInput { const where: SalesInvoiceWhereInput = { + ...this.sharedSaleInvoiceFilterService.buildWhere(filter), consumer_account: { consumer_id, }, + referenced_by: null, } if (filter.code?.trim()) { @@ -111,127 +106,6 @@ export class SaleInvoicesService { } } - if (filter.invoice_date_from || filter.invoice_date_to) { - where.invoice_date = { - ...(filter.invoice_date_from ? { gte: new Date(filter.invoice_date_from) } : {}), - ...(filter.invoice_date_to ? { lte: new Date(filter.invoice_date_to) } : {}), - } - } - - if (filter.created_at_from || filter.created_at_to) { - where.created_at = { - ...(filter.created_at_from ? { gte: new Date(filter.created_at_from) } : {}), - ...(filter.created_at_to ? { lte: new Date(filter.created_at_to) } : {}), - } - } - - if ( - filter.total_amount !== undefined || - filter.total_amount_from !== undefined || - filter.total_amount_to !== undefined - ) { - where.total_amount = { - ...(filter.total_amount !== undefined ? { equals: filter.total_amount } : {}), - ...(filter.total_amount_from !== undefined - ? { gte: filter.total_amount_from } - : {}), - ...(filter.total_amount_to !== undefined ? { lte: filter.total_amount_to } : {}), - } - } - - if ( - filter.customer_name?.trim() || - filter.customer_mobile?.trim() || - filter.customer_national_id?.trim() || - filter.customer_economic_code?.trim() - ) { - where.customer = { - is: { - OR: [ - ...(filter.customer_name?.trim() - ? [ - { - individual: { - is: { - OR: [ - { first_name: { contains: filter.customer_name.trim() } }, - { last_name: { contains: filter.customer_name.trim() } }, - ], - }, - }, - }, - { - legal: { - is: { - name: { - contains: filter.customer_name.trim(), - }, - }, - }, - }, - ] - : []), - ...(filter.customer_mobile?.trim() - ? [ - { - individual: { - is: { - mobile_number: { contains: filter.customer_mobile.trim() }, - }, - }, - }, - ] - : []), - ...(filter.customer_national_id?.trim() - ? [ - { - individual: { - is: { - national_id: { contains: filter.customer_national_id.trim() }, - }, - }, - }, - ] - : []), - ...(filter.customer_economic_code?.trim() - ? [ - { - legal: { - is: { - OR: [ - { - economic_code: { - contains: filter.customer_economic_code.trim(), - }, - }, - { - registration_number: { - contains: filter.customer_economic_code.trim(), - }, - }, - ], - }, - }, - }, - ] - : []), - ], - }, - } - } - - if (filter.status) { - if (filter.status === TspProviderResponseStatus.NOT_SEND) { - where.tsp_attempts = undefined - } else { - where.tsp_attempts = { - some: { - status: filter.status, - }, - } - } - } - return where } @@ -242,7 +116,7 @@ export class SaleInvoicesService { consumer_id, }, } - const invoice = await this.prisma.salesInvoice.findUniqueOrThrow({ + const invoice = await this.prisma.salesInvoice.findUnique({ where: invoicesWhere, select: { ...QUERY_CONSTANTS.SALE_INVOICE.select, @@ -251,7 +125,20 @@ export class SaleInvoicesService { }) if (invoice) { - return ResponseMapper.single(this.invoiceMapper(invoice)) + const { type, ...rest } = invoice + const mappedInvoice = { + ...rest, + type: translateEnumValue('TspProviderRequestType', type), + status: translateEnumValue( + 'TspProviderResponseStatus', + invoice.last_tsp_status || TspProviderResponseStatus.NOT_SEND, + ), + settlement_type: translateEnumValue( + 'InvoiceSettlementType', + invoice.settlement_type, + ), + } + return ResponseMapper.single(mappedInvoice) } throw new NotFoundException('صورت‌حساب مورد نظر شما یافت نشد.') } diff --git a/src/modules/pos/sales-invoices/dto/sales-invoices-filter.dto.ts b/src/modules/pos/sales-invoices/dto/sales-invoices-filter.dto.ts index af31e5b..c7ed2d7 100644 --- a/src/modules/pos/sales-invoices/dto/sales-invoices-filter.dto.ts +++ b/src/modules/pos/sales-invoices/dto/sales-invoices-filter.dto.ts @@ -1,95 +1,10 @@ +import { SharedSaleInvoicesFilterDto } from '@/common/services/saleInvoices/sale-invoice-filter.dto' import { ApiPropertyOptional } from '@nestjs/swagger' -import { Type } from 'class-transformer' -import { - IsDateString, - IsEnum, - IsNumber, - IsOptional, - IsString, - Min, -} from 'class-validator' -import { TspProviderResponseStatus } from 'generated/prisma/enums' - -export class SalesInvoicesFilterDto { - @ApiPropertyOptional({ default: 1 }) - @IsOptional() - @Type(() => Number) - @IsNumber() - @Min(1) - page?: number - - @ApiPropertyOptional({ default: 10 }) - @IsOptional() - @Type(() => Number) - @IsNumber() - @Min(1) - perPage?: number - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - invoice_date_from?: string - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - invoice_date_to?: string - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - created_at_from?: string - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - created_at_to?: string +import { IsNumber, IsOptional } from 'class-validator' +export class SalesInvoicesFilterDto extends SharedSaleInvoicesFilterDto { @ApiPropertyOptional() @IsOptional() @IsNumber() invoice_number?: number - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_name?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_mobile?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_national_id?: string - - @ApiPropertyOptional() - @IsOptional() - @IsString() - customer_economic_code?: string - - @ApiPropertyOptional({ enum: TspProviderResponseStatus }) - @IsOptional() - @IsEnum(TspProviderResponseStatus) - status?: TspProviderResponseStatus - - @ApiPropertyOptional() - @IsOptional() - @Type(() => Number) - @IsNumber() - total_amount?: number - - @ApiPropertyOptional() - @IsOptional() - @Type(() => Number) - @IsNumber() - total_amount_from?: number - - @ApiPropertyOptional() - @IsOptional() - @Type(() => Number) - @IsNumber() - total_amount_to?: number } diff --git a/src/modules/pos/sales-invoices/sales-invoices.module.ts b/src/modules/pos/sales-invoices/sales-invoices.module.ts index 7fc2eab..5a95ca1 100644 --- a/src/modules/pos/sales-invoices/sales-invoices.module.ts +++ b/src/modules/pos/sales-invoices/sales-invoices.module.ts @@ -1,6 +1,7 @@ import { SharedSaleInvoiceCreateService } from '@/common/services/saleInvoices/sale-invoice-create.service' import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service' +import { SharedSaleInvoiceFilterService } from '@/common/services/saleInvoices/sale-invoice-filter.service' import { SaleInvoiceTspModule } from '@/modules/tspProviders/sales-invoice-tsp.module' import { Module } from '@nestjs/common' import { SalesInvoicesController } from './sales-invoices.controller' @@ -14,6 +15,7 @@ import { SalesInvoicesService } from './sales-invoices.service' SharedSaleInvoiceCreateService, SharedSaleInvoiceActionsService, SharedSaleInvoiceAccessService, + SharedSaleInvoiceFilterService, ], }) export class PosSalesInvoicesModule {} diff --git a/src/modules/pos/sales-invoices/sales-invoices.service.ts b/src/modules/pos/sales-invoices/sales-invoices.service.ts index 9644d91..79382b3 100644 --- a/src/modules/pos/sales-invoices/sales-invoices.service.ts +++ b/src/modules/pos/sales-invoices/sales-invoices.service.ts @@ -2,6 +2,7 @@ import { IPosPayload } from '@/common/models/posPayload.model' import { QUERY_CONSTANTS } from '@/common/queryConstants' import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' import { SharedSaleInvoiceCreateService } from '@/common/services/saleInvoices/sale-invoice-create.service' +import { SharedSaleInvoiceFilterService } from '@/common/services/saleInvoices/sale-invoice-filter.service' import { translateEnumValue } from '@/common/utils' import { PrismaService } from '@/prisma/prisma.service' import { Injectable, NotFoundException } from '@nestjs/common' @@ -22,6 +23,7 @@ export class SalesInvoicesService { private salesInvoiceTaxService: SalesInvoiceTspService, private sharedSaleInvoiceCreateService: SharedSaleInvoiceCreateService, private sharedSaleInvoiceActionsService: SharedSaleInvoiceActionsService, + private sharedSaleInvoiceFilterService: SharedSaleInvoiceFilterService, ) {} async findAll(posInfo: IPosPayload, query: SalesInvoicesFilterDto = {}) { @@ -195,6 +197,7 @@ export class SalesInvoicesService { private buildFindAllWhere(posInfo: IPosPayload, query: SalesInvoicesFilterDto) { const where: Prisma.SalesInvoiceWhereInput = { + ...this.sharedSaleInvoiceFilterService.buildWhere(query), pos: { id: posInfo.pos_id, complex: { @@ -208,142 +211,6 @@ export class SalesInvoicesService { where.invoice_number = parseInt(query.invoice_number + '') } - if (query.invoice_date_from || query.invoice_date_to) { - where.invoice_date = { - ...(query.invoice_date_from ? { gte: new Date(query.invoice_date_from) } : {}), - ...(query.invoice_date_to ? { lte: new Date(query.invoice_date_to) } : {}), - } - } - - if (query.created_at_from || query.created_at_to) { - where.created_at = { - ...(query.created_at_from ? { gte: new Date(query.created_at_from) } : {}), - ...(query.created_at_to ? { lte: new Date(query.created_at_to) } : {}), - } - } - - if ( - query.total_amount !== undefined || - query.total_amount_from !== undefined || - query.total_amount_to !== undefined - ) { - where.total_amount = { - ...(query.total_amount !== undefined ? { equals: query.total_amount } : {}), - ...(query.total_amount_from !== undefined - ? { gte: query.total_amount_from } - : {}), - ...(query.total_amount_to !== undefined ? { lte: query.total_amount_to } : {}), - } - } - - if ( - query.customer_name?.trim() || - query.customer_mobile?.trim() || - query.customer_national_id?.trim() || - query.customer_economic_code?.trim() - ) { - where.customer = { - is: { - OR: [ - ...(query.customer_name?.trim() - ? [ - { - individual: { - is: { - OR: [ - { - first_name: { - contains: query.customer_name.trim(), - }, - }, - { - last_name: { - contains: query.customer_name.trim(), - }, - }, - ], - }, - }, - }, - { - legal: { - is: { - name: { - contains: query.customer_name.trim(), - }, - }, - }, - }, - ] - : []), - ...(query.customer_mobile?.trim() - ? [ - { - individual: { - is: { - mobile_number: { - contains: query.customer_mobile.trim(), - }, - }, - }, - }, - ] - : []), - ...(query.customer_national_id?.trim() - ? [ - { - individual: { - is: { - national_id: { - contains: query.customer_national_id.trim(), - }, - }, - }, - }, - ] - : []), - ...(query.customer_economic_code?.trim() - ? [ - { - legal: { - is: { - OR: [ - { - economic_code: { - contains: query.customer_economic_code.trim(), - }, - }, - { - registration_number: { - contains: query.customer_economic_code.trim(), - }, - }, - ], - }, - }, - }, - ] - : []), - ], - }, - } - } - - if (query.status) { - if (query.status === TspProviderResponseStatus.NOT_SEND) { - where.OR = [ - { - last_tsp_status: null, - }, - { - last_tsp_status: TspProviderResponseStatus.NOT_SEND, - }, - ] - } else { - where.last_tsp_status = query.status - } - } - return where } } diff --git a/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts b/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts index f627ff8..70d2f58 100644 --- a/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts +++ b/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts @@ -657,6 +657,7 @@ export async function onResult( where: { id: invoice_id }, data: { last_tsp_status: attemptUpdatedData.status, + last_attempt_no: attemptUpdatedData.attempt_no, }, }) return updatedInvoice