From 5baf5bfea6405ff167f4aff8906cc9295a0afed9 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Sat, 16 May 2026 14:49:23 +0330 Subject: [PATCH] feat: enhance business activities and sale invoices handling with new query constants and pagination support --- src/common/enums/enums.ts | 18 +++--- .../queryConstants/businessActivities.ts | 56 +++++++++++++++++++ src/common/queryConstants/index.ts | 2 + .../business-activities-query.service.ts | 39 +++++-------- .../sale-invoice-create.service.ts | 2 +- src/common/utils/enum-translator.util.ts | 2 + src/main.ts | 10 ++++ .../admin/guilds/goods/goods.service.ts | 2 + src/modules/auth/auth.service.ts | 2 + .../business-activities.service.ts | 1 - .../dto/saleInvoices-filter.dto.ts | 18 ++++++ .../saleInvoices/saleInvoices.controller.ts | 12 +++- .../saleInvoices/saleInvoices.service.ts | 30 ++++++++-- src/modules/pos/goods/goods.service.ts | 1 + 14 files changed, 154 insertions(+), 41 deletions(-) create mode 100644 src/common/queryConstants/businessActivities.ts create mode 100644 src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts diff --git a/src/common/enums/enums.ts b/src/common/enums/enums.ts index 535b452..78279ef 100644 --- a/src/common/enums/enums.ts +++ b/src/common/enums/enums.ts @@ -1,7 +1,7 @@ export enum GoldKarat { - KARAT_18 = '18', - KARAT_21 = '21', - KARAT_24 = '24', + KARAT_18 = 'KARAT_18', + KARAT_21 = 'KARAT_21', + KARAT_24 = 'KARAT_24', } export enum TspProviderRequestType { @@ -16,16 +16,16 @@ export enum SKUGuildType { } export const UploadedFileTypes = { - GOOD: 'good', - SERVICE: 'service', - PROFILE_AVATAR: 'profile_avatar', - PARTNER_LOGO: 'partner_logo', + GOOD: 'GOOD', + SERVICE: 'SERVICE', + PROFILE_AVATAR: 'PROFILE_AVATAR', + PARTNER_LOGO: 'PARTNER_LOGO', } export type UploadedFileTypes = (typeof UploadedFileTypes)[keyof typeof UploadedFileTypes] export const TspProviderCustomerType = { - UNKNOWN: 'Unknown', - KNOWN: 'Known', + UNKNOWN: 'UNKNOWN', + KNOWN: 'KNOWN', } export type TspProviderCustomerType = diff --git a/src/common/queryConstants/businessActivities.ts b/src/common/queryConstants/businessActivities.ts new file mode 100644 index 0000000..e5e9405 --- /dev/null +++ b/src/common/queryConstants/businessActivities.ts @@ -0,0 +1,56 @@ +import { BusinessActivitySelect } from '@/generated/prisma/models' + +export const summarySelect: BusinessActivitySelect = { + id: true, + name: true, + economic_code: true, + created_at: true, + fiscal_id: true, + invoice_number_sequence: true, + partner_token: true, + guild: { + select: { + id: true, + code: true, + name: true, + }, + }, + license_activation: { + select: { + id: true, + starts_at: true, + expires_at: true, + license: { + select: { + activation: { + select: { + _count: { + select: { + account_allocations: true, + }, + }, + }, + }, + }, + }, + }, + }, +} + +export const select: BusinessActivitySelect = { + ...summarySelect, +} + +export const mappedData = (businessActivity: any) => { + const { license_activation, ...rest } = businessActivity + const { license, ...license_activation_rest } = license_activation + const { _count } = license.activation + + return { + ...rest, + license_info: { + ...license_activation_rest, + accounts_limit: _count.account_allocations, + }, + } +} diff --git a/src/common/queryConstants/index.ts b/src/common/queryConstants/index.ts index 0c113f9..7bd3e5a 100644 --- a/src/common/queryConstants/index.ts +++ b/src/common/queryConstants/index.ts @@ -1,3 +1,4 @@ +import * as BUSINESS_ACTIVITIES from './businessActivities' import * as CONSUMER from './consumer' import * as LICENSE_ACTIVATION from './licenseActivation' import * as POS from './pos' @@ -8,4 +9,5 @@ export const QUERY_CONSTANTS = { CONSUMER, LICENSE_ACTIVATION, SALE_INVOICE, + BUSINESS_ACTIVITIES, } diff --git a/src/common/services/businessActivities/business-activities-query.service.ts b/src/common/services/businessActivities/business-activities-query.service.ts index af5af68..c1441fb 100644 --- a/src/common/services/businessActivities/business-activities-query.service.ts +++ b/src/common/services/businessActivities/business-activities-query.service.ts @@ -1,3 +1,4 @@ +import { QUERY_CONSTANTS } from '@/common/queryConstants' import { BusinessActivitySelect } from '@/generated/prisma/models' import { PrismaService } from '@/prisma/prisma.service' import { Injectable } from '@nestjs/common' @@ -7,45 +8,35 @@ export class BusinessActivitiesQueryService { constructor(private readonly prisma: PrismaService) {} readonly baseSelect: BusinessActivitySelect = { - id: true, - name: true, - economic_code: true, - created_at: true, - partner_token: true, - fiscal_id: true, - guild: { - select: { - id: true, - code: true, - name: true, - }, - }, + ...QUERY_CONSTANTS.BUSINESS_ACTIVITIES.summarySelect, } - findAllByConsumer(consumer_id: string, select: BusinessActivitySelect) { - return this.prisma.businessActivity.findMany({ + async findAllByConsumer(consumer_id: string, select: BusinessActivitySelect) { + const businessActivities = await this.prisma.businessActivity.findMany({ where: { consumer_id }, select, }) + return businessActivities.map(QUERY_CONSTANTS.BUSINESS_ACTIVITIES.mappedData) } - findOneByConsumer( + async findOneByConsumer( consumer_id: string, id: string, select: BusinessActivitySelect, orThrow = false, ) { - if (orThrow) { - return this.prisma.businessActivity.findUniqueOrThrow({ - where: { consumer_id, id }, - select, - }) - } + // if (orThrow) { + // return await this.prisma.businessActivity.findUniqueOrThrow({ + // where: { consumer_id, id }, + // select, + // }) + // } - return this.prisma.businessActivity.findUnique({ + const businessActivity = await this.prisma.businessActivity.findUnique({ where: { consumer_id, id }, select, }) + + return QUERY_CONSTANTS.BUSINESS_ACTIVITIES.mappedData(businessActivity) } } - diff --git a/src/common/services/saleInvoices/sale-invoice-create.service.ts b/src/common/services/saleInvoices/sale-invoice-create.service.ts index 67dd04b..7572148 100644 --- a/src/common/services/saleInvoices/sale-invoice-create.service.ts +++ b/src/common/services/saleInvoices/sale-invoice-create.service.ts @@ -151,7 +151,7 @@ export class SharedSaleInvoiceCreateService { const terminalInfo = rawPayments.terminals as TerminalPaymentInfo | undefined const payments: NormalizedPayment[] = Object.entries(rawPayments) - .filter(([key]) => key !== 'terminals') + .filter(([key, value]) => key !== 'terminals' && value && Number(value) > 0) .map(([key, value]) => ({ method: paymentMethodMap[key.toLowerCase()], amount: typeof value === 'number' ? value : Number(value || 0), diff --git a/src/common/utils/enum-translator.util.ts b/src/common/utils/enum-translator.util.ts index 03b87ee..dbb45b6 100644 --- a/src/common/utils/enum-translator.util.ts +++ b/src/common/utils/enum-translator.util.ts @@ -12,6 +12,8 @@ export function translateEnumValue( enumKey: keyof typeof translates.enums, value: string | null | undefined, ): EnumTranslatedValue { + console.log(enumKey, value) + const enumsRegistry = translates.enums as EnumsRegistry const enumMap = enumsRegistry[String(enumKey)] diff --git a/src/main.ts b/src/main.ts index 3a14997..5801f64 100644 --- a/src/main.ts +++ b/src/main.ts @@ -63,6 +63,9 @@ async function bootstrap() { .map(s => s.trim()) .filter(Boolean) : [] + + console.log('envOrigins', envOrigins) + const allowedOrigins = (envOrigins.length ? envOrigins : defaultOrigins).filter(Boolean) const isOriginAllowed = (origin: string) => { @@ -74,6 +77,9 @@ async function bootstrap() { const rootDomain = pattern.slice(2) return hostname === rootDomain || hostname.endsWith(`.${rootDomain}`) } + console.log('origin', origin, 'pattern', pattern, 'hostname', hostname) + console.log(hostname === pattern || origin === pattern) + return hostname === pattern || origin === pattern }) } catch { @@ -84,14 +90,18 @@ async function bootstrap() { app.enableCors({ origin: (origin, callback) => { if (!origin) { + console.log('1') callback(null, true) return } if (isOriginAllowed(origin)) { + console.log('2') + callback(null, true) return } + console.log('3') callback(new Error('Not allowed by CORS'), false) }, diff --git a/src/modules/admin/guilds/goods/goods.service.ts b/src/modules/admin/guilds/goods/goods.service.ts index 04369c3..52374e1 100644 --- a/src/modules/admin/guilds/goods/goods.service.ts +++ b/src/modules/admin/guilds/goods/goods.service.ts @@ -23,12 +23,14 @@ export class GoodsService { select: { id: true, name: true, + code: true, }, }, measure_unit: { select: { id: true, name: true, + code: true, }, }, category: { diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index ecf7b1a..e20048b 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -59,6 +59,8 @@ export class AuthService { include: accountInclude, }) + console.log('account', account) + if (!account) { throw new NotFoundException('اطلاعات ورودی شما اشتباه است') } diff --git a/src/modules/consumer/business-activities/business-activities.service.ts b/src/modules/consumer/business-activities/business-activities.service.ts index 665fa79..0b88046 100644 --- a/src/modules/consumer/business-activities/business-activities.service.ts +++ b/src/modules/consumer/business-activities/business-activities.service.ts @@ -71,7 +71,6 @@ export class BusinessActivitiesService { consumer_id, id, this.defaultSelect, - true, ) return ResponseMapper.single(this.prepareBusinessActivityResponse(businessActivity)) } diff --git a/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts b/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts new file mode 100644 index 0000000..9daf126 --- /dev/null +++ b/src/modules/consumer/saleInvoices/dto/saleInvoices-filter.dto.ts @@ -0,0 +1,18 @@ +import { ApiPropertyOptional } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { IsOptional, Max, Min } from 'class-validator' + +export class ConsumerSaleInvoicesFilterDto { + @ApiPropertyOptional({ type: Number, example: 1 }) + @Type(() => Number) + @Min(1) + @IsOptional() + page?: number + + @ApiPropertyOptional({ type: Number, example: 10, description: 'Max value is 50.' }) + @Type(() => Number) + @Min(1) + @Max(50) + @IsOptional() + perPage?: number +} diff --git a/src/modules/consumer/saleInvoices/saleInvoices.controller.ts b/src/modules/consumer/saleInvoices/saleInvoices.controller.ts index e03c84a..5578bc0 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.controller.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.controller.ts @@ -1,8 +1,9 @@ import { PosInfo } from '@/common/decorators/posInfo.decorator' import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' import type { IPosPayload } from '@/common/models' -import { Body, Controller, Get, Param, Post } from '@nestjs/common' +import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common' import { ApiTags } from '@nestjs/swagger' +import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto' import type { SaleInvoicesServiceFindAllResponseDto, SaleInvoicesServiceFindOneResponseDto, @@ -18,8 +19,15 @@ export class StatisticsController { @Get('') async findAll( @TokenAccount('userId') consumerId: string, + @Query('page') page = '1', + @Query('perPage') perPage = '10', ): Promise { - return this.service.findAll(consumerId) + const filter: ConsumerSaleInvoicesFilterDto = { + page: Number(page) || 1, + perPage: Number(perPage) || 10, + } + + return this.service.findAll(consumerId, filter) } @Get(':id') diff --git a/src/modules/consumer/saleInvoices/saleInvoices.service.ts b/src/modules/consumer/saleInvoices/saleInvoices.service.ts index 6da56aa..68b39a9 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.service.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.service.ts @@ -12,6 +12,7 @@ import { PrismaService } from '@/prisma/prisma.service' import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' import { SalesInvoiceTspService } from '../../tspProviders/sales-invoice-tsp.service' +import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto' @Injectable() export class SaleInvoicesService { @@ -21,6 +22,9 @@ export class SaleInvoicesService { private sharedSaleInvoiceActionsService: SharedSaleInvoiceActionsService, ) {} + private readonly defaultPerPage = 10 + private readonly maxPerPage = 50 + private readonly defaultSelect: SalesInvoiceSelect = { pos: { select: { @@ -54,27 +58,45 @@ export class SaleInvoicesService { } } - async findAll(consumer_id: string, page = 1, perPage = 10) { + async findAll(consumer_id: string, filter: ConsumerSaleInvoicesFilterDto) { const invoicesWhere: SalesInvoiceWhereInput = { consumer_account: { consumer_id, }, } + 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 [invoices, total] = await this.prisma.$transaction(async tx => [ await tx.salesInvoice.findMany({ where: invoicesWhere, + orderBy: { + created_at: 'desc', + }, + skip: (normalizedPage - 1) * normalizedPerPage, + take: normalizedPerPage, select: { ...QUERY_CONSTANTS.SALE_INVOICE.summarySelect, ...this.defaultSelect, }, - skip: (page - 1) * perPage, - take: perPage, }), await tx.salesInvoice.count({ where: invoicesWhere }), ]) const mappedInvoices = invoices.map(this.invoiceMapper) - return ResponseMapper.paginate(mappedInvoices, { page, perPage, total }) + return ResponseMapper.paginate(mappedInvoices, { + page: normalizedPage, + perPage: normalizedPerPage, + total, + }) } async findOne(consumer_id: string, id: string) { diff --git a/src/modules/pos/goods/goods.service.ts b/src/modules/pos/goods/goods.service.ts index 83b0ae1..fa7afc4 100644 --- a/src/modules/pos/goods/goods.service.ts +++ b/src/modules/pos/goods/goods.service.ts @@ -18,6 +18,7 @@ export class GoodsService { select: { id: true, name: true, + code: true, }, }, local_sku: true,