Merge branch 'feature/seller-id-array-and-code-improvements' into feature/deb-query-builder

This commit is contained in:
Joelson
2025-11-05 16:40:24 -03:00
committed by GitHub
16 changed files with 213 additions and 300 deletions

View File

@@ -13,6 +13,7 @@ import { OccurrencesModule } from './crm/occurrences/occurrences.module';
import { ReasonTableModule } from './crm/reason-table/reason-table.module'; import { ReasonTableModule } from './crm/reason-table/reason-table.module';
import { NegotiationsModule } from './crm/negotiations/negotiations.module'; import { NegotiationsModule } from './crm/negotiations/negotiations.module';
import { HttpModule } from '@nestjs/axios'; import { HttpModule } from '@nestjs/axios';
import { DebModule } from './orders/modules/deb.module';
import { LogisticController } from './logistic/logistic.controller'; import { LogisticController } from './logistic/logistic.controller';
import { LogisticService } from './logistic/logistic.service'; import { LogisticService } from './logistic/logistic.service';
import { LoggerModule } from './Log/logger.module'; import { LoggerModule } from './Log/logger.module';
@@ -67,6 +68,7 @@ import { PartnersModule } from './partners/partners.module';
LoggerModule, LoggerModule,
DataConsultModule, DataConsultModule,
AuthModule, AuthModule,
DebModule,
OrdersModule, OrdersModule,
HealthModule, HealthModule,
PartnersModule, PartnersModule,

View File

@@ -3,7 +3,6 @@ import { ApiProperty } from '@nestjs/swagger';
export class LoginDto { export class LoginDto {
@ApiProperty({ @ApiProperty({
example: 'joelson.r',
description: 'Usuário de login', description: 'Usuário de login',
}) })
@IsString() @IsString()
@@ -11,7 +10,6 @@ export class LoginDto {
username: string; username: string;
@ApiProperty({ @ApiProperty({
example: '1010',
description: 'Senha do usuário', description: 'Senha do usuário',
}) })
@IsString() @IsString()

View File

@@ -11,9 +11,9 @@ export interface RateLimitConfig {
@Injectable() @Injectable()
export class RateLimitingService { export class RateLimitingService {
private readonly defaultConfig: RateLimitConfig = { private readonly defaultConfig: RateLimitConfig = {
maxAttempts: 5, // 5 tentativas maxAttempts: 15, // 15 tentativas
windowMs: 15 * 60 * 1000, // 15 minutos windowMs: 1 * 60 * 1000, // 1 minuto
blockDurationMs: 30 * 60 * 1000, // 30 minutos de bloqueio blockDurationMs: 1 * 60 * 1000, // 1 minuto de bloqueio
}; };
constructor( constructor(
@@ -115,7 +115,7 @@ export class RateLimitingService {
const blockKey = this.buildBlockKey(ip); const blockKey = this.buildBlockKey(ip);
const attempts = await this.redis.get<string>(key); const attempts = await this.redis.get<string>(key);
const isBlocked = await this.redis.get(blockKey); const isBlocked = await this.redis.get<string>(blockKey);
const ttl = await this.redis.ttl(blockKey); const ttl = await this.redis.ttl(blockKey);
return { return {

View File

@@ -11,7 +11,14 @@ export class RedisClientAdapter implements IRedisClient {
async get<T>(key: string): Promise<T | null> { async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(key); const data = await this.redis.get(key);
return data ? JSON.parse(data) : null; if (!data) return null;
try {
return JSON.parse(data);
} catch (error) {
// If it's not valid JSON, return the raw string value
return data as T;
}
} }
async set<T>(key: string, value: T, ttlSeconds = 300): Promise<void> { async set<T>(key: string, value: T, ttlSeconds = 300): Promise<void> {

View File

@@ -6,7 +6,7 @@
provide: 'REDIS_CLIENT', provide: 'REDIS_CLIENT',
useFactory: (configService: ConfigService) => { useFactory: (configService: ConfigService) => {
const redis = new Redis({ const redis = new Redis({
host: configService.get<string>('REDIS_HOST', '10.1.1.124'), host: configService.get<string>('REDIS_HOST', '10.1.1.109'),
port: configService.get<number>('REDIS_PORT', 6379), port: configService.get<number>('REDIS_PORT', 6379),
password: configService.get<string>('REDIS_PASSWORD', '1234'), password: configService.get<string>('REDIS_PASSWORD', '1234'),
}); });

View File

@@ -1,34 +1,26 @@
import { Injectable } from '@nestjs/common';
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { DebRepository } from '../repositories/deb.repository'; import { DebRepository } from '../repositories/deb.repository';
import { DebDto } from '../dto/DebDto'; import { DebDto } from '../dto/DebDto';
@Injectable() @Injectable()
export class DebService { export class DebService {
constructor( constructor(
private readonly debRepository: DebRepository, private readonly debRepository: DebRepository,
) {} ) {}
/** /**
* Busca débitos por CPF ou CGCENT * Busca débitos por CPF ou CGCENT
* @param cpfCgcent - CPF ou CGCENT do cliente * @param cpfCgcent - CPF ou CGCENT do cliente (validado pelo DTO)
* @param matricula - Matrícula do funcionário (opcional) * @param matricula - Matrícula do funcionário (opcional)
* @param cobranca - Código de cobrança (opcional) * @param cobranca - Código de cobrança (opcional)
* @returns Lista de débitos do cliente * @returns Lista de débitos do cliente
*/ * @throws {Error} Erro ao buscar débitos no banco de dados
async findByCpfCgcent(cpfCgcent: string, matricula?: number, cobranca?: string): Promise<DebDto[]> { */
if (!cpfCgcent) { async findByCpfCgcent(
throw new HttpException('CPF/CGCENT é obrigatório', HttpStatus.BAD_REQUEST); cpfCgcent: string,
} matricula?: number,
cobranca?: string,
try { ): Promise<DebDto[]> {
const result = await this.debRepository.findByCpfCgcent(cpfCgcent, matricula, cobranca); return await this.debRepository.findByCpfCgcent(cpfCgcent, matricula, cobranca);
return result as DebDto[]; }
} catch (error) {
throw new HttpException(
error.message || 'Erro ao buscar débitos',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} }

View File

@@ -1,5 +1,6 @@
import { Injectable, Inject, HttpStatus } from '@nestjs/common'; import { Injectable, Inject, HttpStatus } from '@nestjs/common';
import { FindOrdersDto } from '../dto/find-orders.dto'; import { FindOrdersDto } from '../dto/find-orders.dto';
import { FindOrdersByDeliveryDateDto } from '../dto/find-orders-by-delivery-date.dto';
import { InvoiceDto } from '../dto/find-invoice.dto'; import { InvoiceDto } from '../dto/find-invoice.dto';
import { CutItemDto } from '../dto/CutItemDto'; import { CutItemDto } from '../dto/CutItemDto';
import { OrdersRepository } from '../repositories/orders.repository'; import { OrdersRepository } from '../repositories/orders.repository';
@@ -19,20 +20,22 @@ import { MarkData } from '../interface/markdata';
import { EstLogTransferFilterDto, EstLogTransferResponseDto } from '../dto/estlogtransfer.dto'; import { EstLogTransferFilterDto, EstLogTransferResponseDto } from '../dto/estlogtransfer.dto';
import { DeliveryCompletedQuery } from '../dto/delivery-completed-query.dto'; import { DeliveryCompletedQuery } from '../dto/delivery-completed-query.dto';
import { DeliveryCompleted } from '../dto/delivery-completed.dto'; import { DeliveryCompleted } from '../dto/delivery-completed.dto';
import { OrderResponseDto } from '../dto/order-response.dto';
@Injectable() @Injectable()
export class OrdersService { export class OrdersService {
private readonly TTL_ORDERS = 60; // 60 segundos // Cache TTL em segundos
private readonly TTL_INVOICE = 60; // 60 segundos private static readonly DEFAULT_TTL = 60;
private readonly TTL_ITENS = 60; // 60 segundos private readonly TTL_ORDERS = OrdersService.DEFAULT_TTL;
private readonly TTL_LEADTIME = 60; // 60 segundos private readonly TTL_INVOICE = OrdersService.DEFAULT_TTL;
private readonly TTL_DELIVERIES = 60; // 60 segundos private readonly TTL_ITENS = OrdersService.DEFAULT_TTL;
private readonly TTL_TRANSFER = 60; // 60 segundos private readonly TTL_LEADTIME = OrdersService.DEFAULT_TTL;
private readonly TTL_STATUS = 60; // 60 segundos private readonly TTL_DELIVERIES = OrdersService.DEFAULT_TTL;
private readonly TTL_CARRIERS = 60; // 60 segundos private readonly TTL_TRANSFER = OrdersService.DEFAULT_TTL;
private readonly TTL_MARKS = 60; // 60 segundos private readonly TTL_STATUS = OrdersService.DEFAULT_TTL;
private readonly TTL_COMPLETED_DELIVERIES = 60; // 60 segundos private readonly TTL_CARRIERS = OrdersService.DEFAULT_TTL;
private readonly TTL_MARKS = OrdersService.DEFAULT_TTL;
private readonly TTL_COMPLETED_DELIVERIES = OrdersService.DEFAULT_TTL;
constructor( constructor(
private readonly ordersRepository: OrdersRepository, private readonly ordersRepository: OrdersRepository,
@@ -41,8 +44,10 @@ export class OrdersService {
/** /**
* Buscar pedidos com cache baseado nos filtros * Buscar pedidos com cache baseado nos filtros
* @param query - Filtros para busca de pedidos
* @returns Lista de pedidos
*/ */
async findOrders(query: FindOrdersDto) { async findOrders(query: FindOrdersDto): Promise<OrderResponseDto[]> {
const key = `orders:query:${this.hashObject(query)}`; const key = `orders:query:${this.hashObject(query)}`;
return getOrSetCache( return getOrSetCache(
@@ -52,21 +57,23 @@ export class OrdersService {
async () => { async () => {
const orders = await this.ordersRepository.findOrders(query); const orders = await this.ordersRepository.findOrders(query);
if (query.includeCompletedDeliveries) { if (!query.includeCompletedDeliveries) {
for (const order of orders) { return orders;
const deliveryQuery = { }
orderNumber: order.invoiceNumber,
limit: 10,
offset: 0
};
try { for (const order of orders) {
const deliveries = await this.ordersRepository.getCompletedDeliveries(deliveryQuery); const deliveryQuery = {
order.completedDeliveries = deliveries; orderNumber: order.invoiceNumber,
} catch (error) { limit: 10,
// Se houver erro, definir como array vazio offset: 0
order.completedDeliveries = []; };
}
try {
const deliveries = await this.ordersRepository.getCompletedDeliveries(deliveryQuery);
order.completedDeliveries = deliveries;
} catch (error) {
// Se houver erro, definir como array vazio
order.completedDeliveries = [];
} }
} }
@@ -77,8 +84,10 @@ export class OrdersService {
/** /**
* Buscar pedidos por data de entrega com cache * Buscar pedidos por data de entrega com cache
* @param query - Filtros para busca por data de entrega
* @returns Lista de pedidos
*/ */
async findOrdersByDeliveryDate(query: any) { async findOrdersByDeliveryDate(query: FindOrdersByDeliveryDateDto): Promise<OrderResponseDto[]> {
const key = `orders:delivery:${this.hashObject(query)}`; const key = `orders:delivery:${this.hashObject(query)}`;
return getOrSetCache( return getOrSetCache(
this.redisClient, this.redisClient,
@@ -90,8 +99,10 @@ export class OrdersService {
/** /**
* Buscar pedidos com resultados de fechamento de caixa * Buscar pedidos com resultados de fechamento de caixa
* @param query - Filtros para busca de pedidos
* @returns Lista de pedidos com dados de fechamento de caixa
*/ */
async findOrdersWithCheckout(query: FindOrdersDto) { async findOrdersWithCheckout(query: FindOrdersDto): Promise<(OrderResponseDto & { checkout: any })[]> {
const key = `orders:checkout:${this.hashObject(query)}`; const key = `orders:checkout:${this.hashObject(query)}`;
return getOrSetCache( return getOrSetCache(
this.redisClient, this.redisClient,
@@ -236,28 +247,30 @@ export class OrdersService {
return null; return null;
} }
if (includeCompletedDeliveries) { if (!includeCompletedDeliveries) {
try { return orderDelivery;
// Buscar entregas realizadas usando o transactionId do pedido }
// Primeiro precisamos obter o NUMTRANSVENDA do pedido
const transactionId = await this.ordersRepository.getOrderTransactionId(orderId);
if (transactionId) { try {
const deliveryQuery = { // Buscar entregas realizadas usando o transactionId do pedido
transactionId: transactionId, const transactionId = await this.ordersRepository.getOrderTransactionId(orderId);
limit: 10,
offset: 0
};
const deliveries = await this.ordersRepository.getCompletedDeliveriesByTransactionId(deliveryQuery); if (!transactionId) {
orderDelivery.completedDeliveries = deliveries;
} else {
orderDelivery.completedDeliveries = [];
}
} catch (error) {
// Se houver erro, definir como array vazio
orderDelivery.completedDeliveries = []; orderDelivery.completedDeliveries = [];
return orderDelivery;
} }
const deliveryQuery = {
transactionId: transactionId,
limit: 10,
offset: 0
};
const deliveries = await this.ordersRepository.getCompletedDeliveriesByTransactionId(deliveryQuery);
orderDelivery.completedDeliveries = deliveries;
} catch (error) {
// Se houver erro, definir como array vazio
orderDelivery.completedDeliveries = [];
} }
return orderDelivery; return orderDelivery;
@@ -322,10 +335,14 @@ export class OrdersService {
} }
/** /**
* Utilitário para gerar hash MD5 de objetos * Utilitário para gerar hash MD5 de objetos para chaves de cache
* @param obj - Objeto a ser serializado e hasheado
* @returns Hash MD5 do objeto serializado
*/ */
private hashObject(obj: any): string { private hashObject(obj: unknown): string {
const str = JSON.stringify(obj, Object.keys(obj).sort()); const objRecord = obj as Record<string, unknown>;
const sortedKeys = Object.keys(objRecord).sort();
const str = JSON.stringify(objRecord, sortedKeys);
return createHash('md5').update(str).digest('hex'); return createHash('md5').update(str).digest('hex');
} }

View File

@@ -1,49 +1,46 @@
import { import {
Controller, Controller,
Get, Get,
Query, Query,
UsePipes, UsePipes,
HttpException, ValidationPipe,
HttpStatus, } from '@nestjs/common';
ValidationPipe, import { ApiOperation, ApiTags, ApiResponse } from '@nestjs/swagger';
} from '@nestjs/common'; import { DebService } from '../application/deb.service';
import { ApiOperation, ApiTags, ApiResponse } from '@nestjs/swagger'; import { DebDto } from '../dto/DebDto';
import { DebService } from '../application/deb.service'; import { FindDebDto } from '../dto/find-deb.dto';
import { DebDto } from '../dto/DebDto';
import { FindDebDto } from '../dto/find-deb.dto';
@ApiTags('Débitos') @ApiTags('Débitos')
@Controller('api/v1/deb') @Controller('api/v1/deb')
export class DebController { export class DebController {
constructor(private readonly debService: DebService) {} constructor(private readonly debService: DebService) {}
@Get('find-by-cpf') @Get('find-by-cpf')
@ApiOperation({ @ApiOperation({
summary: 'Busca débitos por CPF/CGCENT', summary: 'Busca débitos por CPF/CGCENT',
description: 'Busca débitos de um cliente usando CPF ou CGCENT. Opcionalmente pode filtrar por matrícula do funcionário ou código de cobrança.', description: 'Busca débitos de um cliente usando CPF ou CGCENT. Opcionalmente pode filtrar por matrícula do funcionário ou código de cobrança.',
}) })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Lista de débitos retornada com sucesso', description: 'Lista de débitos retornada com sucesso',
type: [DebDto], type: [DebDto],
}) })
@ApiResponse({ @ApiResponse({
status: 400, status: 400,
description: 'CPF/CGCENT é obrigatório', description: 'CPF/CGCENT é obrigatório',
}) })
@UsePipes(new ValidationPipe({ transform: true })) @ApiResponse({
async findByCpfCgcent( status: 500,
@Query() query: FindDebDto, description: 'Erro interno do servidor',
): Promise<DebDto[]> { })
try { @UsePipes(new ValidationPipe({ transform: true }))
return await this.debService.findByCpfCgcent(query.cpfCgcent, query.matricula, query.cobranca); async findByCpfCgcent(
} catch (error) { @Query() query: FindDebDto,
throw new HttpException( ): Promise<DebDto[]> {
error.message || 'Erro ao buscar débitos', return await this.debService.findByCpfCgcent(
error.status || HttpStatus.INTERNAL_SERVER_ERROR, query.cpfCgcent,
); query.matricula,
} query.cobranca,
} );
} }
}

View File

@@ -13,7 +13,6 @@ import {
HttpStatus, HttpStatus,
DefaultValuePipe, DefaultValuePipe,
ParseBoolPipe, ParseBoolPipe,
Optional,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags, ApiQuery, ApiParam, ApiResponse } from '@nestjs/swagger'; import { ApiBearerAuth, ApiOperation, ApiTags, ApiQuery, ApiParam, ApiResponse } from '@nestjs/swagger';
import { ResponseInterceptor } from '../../common/response.interceptor'; import { ResponseInterceptor } from '../../common/response.interceptor';
@@ -34,8 +33,6 @@ import { CarrierDto } from 'src/data-consult/dto/carrier.dto';
import { OrderResponseDto } from '../dto/order-response.dto'; import { OrderResponseDto } from '../dto/order-response.dto';
import { MarkResponseDto } from '../dto/mark-response.dto'; import { MarkResponseDto } from '../dto/mark-response.dto';
import { EstLogTransferResponseDto } from '../dto/estlogtransfer.dto'; import { EstLogTransferResponseDto } from '../dto/estlogtransfer.dto';
import { DeliveryCompletedQuery } from '../dto/delivery-completed-query.dto';
import { DeliveryCompleted } from '../dto/delivery-completed.dto';
@ApiTags('Orders') @ApiTags('Orders')
@@ -51,9 +48,6 @@ export class OrdersController {
description: 'Busca pedidos com filtros avançados. Suporta filtros por data, cliente, vendedor, status, tipo de entrega e status de transferência.' description: 'Busca pedidos com filtros avançados. Suporta filtros por data, cliente, vendedor, status, tipo de entrega e status de transferência.'
}) })
@ApiQuery({ name: 'includeCheckout', required: false, type: 'boolean', description: 'Incluir dados de checkout' }) @ApiQuery({ name: 'includeCheckout', required: false, type: 'boolean', description: 'Incluir dados de checkout' })
@ApiQuery({ name: 'includeCompletedDeliveries', required: false, type: 'boolean', description: 'Incluir dados de entregas realizadas para cada pedido' })
@ApiQuery({ name: 'limit', required: false, type: 'number', description: 'Limite de registros por página', example: 100 })
@ApiQuery({ name: 'offset', required: false, type: 'number', description: 'Offset para paginação', example: 0 })
@ApiQuery({ name: 'statusTransfer', required: false, type: 'string', description: 'Filtrar por status de transferência (Em Trânsito, Em Separação, Aguardando Separação, Concluída)' }) @ApiQuery({ name: 'statusTransfer', required: false, type: 'string', description: 'Filtrar por status de transferência (Em Trânsito, Em Separação, Aguardando Separação, Concluída)' })
@ApiQuery({ name: 'markId', required: false, type: 'number', description: 'ID da marca para filtrar pedidos' }) @ApiQuery({ name: 'markId', required: false, type: 'number', description: 'ID da marca para filtrar pedidos' })
@ApiQuery({ name: 'markName', required: false, type: 'string', description: 'Nome da marca para filtrar pedidos (busca parcial)' }) @ApiQuery({ name: 'markName', required: false, type: 'string', description: 'Nome da marca para filtrar pedidos (busca parcial)' })
@@ -90,7 +84,7 @@ export class OrdersController {
@Get(':orderId/checkout') @Get(':orderId/checkout')
@ApiOperation({ summary: 'Busca fechamento de caixa para um pedido' }) @ApiOperation({ summary: 'Busca fechamento de caixa para um pedido' })
@ApiParam({ name: 'orderId', example: 236001388 }) @ApiParam({ name: 'orderId' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
getOrderCheckout( getOrderCheckout(
@Param('orderId', ParseIntPipe) orderId: number, @Param('orderId', ParseIntPipe) orderId: number,
@@ -104,7 +98,6 @@ export class OrdersController {
name: 'chavenfe', name: 'chavenfe',
required: true, required: true,
description: 'Chave da Nota Fiscal (44 dígitos)', description: 'Chave da Nota Fiscal (44 dígitos)',
example: '35191234567890000123550010000000011000000010',
}) })
@ApiOperation({ summary: 'Busca NF pela chave' }) @ApiOperation({ summary: 'Busca NF pela chave' })
@@ -122,7 +115,7 @@ export class OrdersController {
@Get('itens/:orderId') @Get('itens/:orderId')
@ApiOperation({ summary: 'Busca PELO numero do pedido' }) @ApiOperation({ summary: 'Busca PELO numero do pedido' })
@ApiParam({ name: 'orderId', example: '236001388' }) @ApiParam({ name: 'orderId' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async getItens(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderItemDto[]> { async getItens(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderItemDto[]> {
try { try {
@@ -136,7 +129,7 @@ export class OrdersController {
} }
@Get('cut-itens/:orderId') @Get('cut-itens/:orderId')
@ApiOperation({ summary: 'Busca itens cortados do pedido' }) @ApiOperation({ summary: 'Busca itens cortados do pedido' })
@ApiParam({ name: 'orderId', example: '236001388' }) @ApiParam({ name: 'orderId' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async getCutItens(@Param('orderId', ParseIntPipe) orderId: number): Promise<CutItemDto[]> { async getCutItens(@Param('orderId', ParseIntPipe) orderId: number): Promise<CutItemDto[]> {
try { try {
@@ -151,16 +144,11 @@ export class OrdersController {
@Get('delivery/:orderId') @Get('delivery/:orderId')
@ApiOperation({ summary: 'Busca dados de entrega do pedido' }) @ApiOperation({ summary: 'Busca dados de entrega do pedido' })
@ApiParam({ name: 'orderId', example: '236001388' }) @ApiParam({ name: 'orderId' })
@ApiQuery({ name: 'includeCompletedDeliveries', required: false, type: 'boolean', description: 'Incluir dados de entregas realizadas para o pedido' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async getOrderDelivery( async getOrderDelivery(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderDeliveryDto | null> {
@Param('orderId', ParseIntPipe) orderId: number,
@Query('includeCompletedDeliveries', new DefaultValuePipe(false), ParseBoolPipe)
includeCompletedDeliveries: boolean,
): Promise<OrderDeliveryDto | null> {
try { try {
return await this.ordersService.getOrderDelivery(orderId.toString(), includeCompletedDeliveries); return await this.ordersService.getOrderDelivery(orderId.toString());
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
error.message || 'Erro ao buscar dados de entrega', error.message || 'Erro ao buscar dados de entrega',
@@ -171,7 +159,7 @@ export class OrdersController {
@Get('transfer/:orderId') @Get('transfer/:orderId')
@ApiOperation({ summary: 'Consulta pedidos de transferência' }) @ApiOperation({ summary: 'Consulta pedidos de transferência' })
@ApiParam({ name: 'orderId', example: 236001388 }) @ApiParam({ name: 'orderId' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async getTransfer(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderTransferDto[] | null> { async getTransfer(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderTransferDto[] | null> {
try { try {
@@ -186,7 +174,7 @@ export class OrdersController {
@Get('status/:orderId') @Get('status/:orderId')
@ApiOperation({ summary: 'Consulta status do pedido' }) @ApiOperation({ summary: 'Consulta status do pedido' })
@ApiParam({ name: 'orderId', example: 236001388 }) @ApiParam({ name: 'orderId' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async getStatusOrder(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderStatusDto[] | null> { async getStatusOrder(@Param('orderId', ParseIntPipe) orderId: number): Promise<OrderStatusDto[] | null> {
try { try {
@@ -202,7 +190,7 @@ export class OrdersController {
@Get(':orderId/deliveries') @Get(':orderId/deliveries')
@ApiOperation({ summary: 'Consulta entregas do pedido' }) @ApiOperation({ summary: 'Consulta entregas do pedido' })
@ApiParam({ name: 'orderId', example: '236001388' }) @ApiParam({ name: 'orderId' })
@ApiQuery({ name: 'createDateIni', required: false, description: 'Data inicial para filtro (formato YYYY-MM-DD)' }) @ApiQuery({ name: 'createDateIni', required: false, description: 'Data inicial para filtro (formato YYYY-MM-DD)' })
@ApiQuery({ name: 'createDateEnd', required: false, description: 'Data final para filtro (formato YYYY-MM-DD)' }) @ApiQuery({ name: 'createDateEnd', required: false, description: 'Data final para filtro (formato YYYY-MM-DD)' })
async getOrderDeliveries( async getOrderDeliveries(
@@ -223,7 +211,7 @@ export class OrdersController {
@Get('leadtime/:orderId') @Get('leadtime/:orderId')
@ApiOperation({ summary: 'Consulta leadtime do pedido' }) @ApiOperation({ summary: 'Consulta leadtime do pedido' })
@ApiParam({ name: 'orderId', example: '236001388' }) @ApiParam({ name: 'orderId' })
@UsePipes(new ValidationPipe({ transform: true })) @UsePipes(new ValidationPipe({ transform: true }))
async getLeadtime(@Param('orderId', ParseIntPipe) orderId: number): Promise<LeadtimeDto[]> { async getLeadtime(@Param('orderId', ParseIntPipe) orderId: number): Promise<LeadtimeDto[]> {
try { try {
@@ -315,7 +303,7 @@ async getMarksByName(@Query('name') markName: string): Promise<MarkResponseDto[]
@Get('transfer-log/:orderId') @Get('transfer-log/:orderId')
@ApiOperation({ summary: 'Busca log de transferência por ID do pedido' }) @ApiOperation({ summary: 'Busca log de transferência por ID do pedido' })
@ApiParam({ name: 'orderId', example: 153068638, description: 'ID do pedido para buscar log de transferência' }) @ApiParam({ name: 'orderId', description: 'ID do pedido para buscar log de transferência' })
@ApiQuery({ name: 'dttransf', required: false, type: 'string', description: 'Data de transferência (formato YYYY-MM-DD)' }) @ApiQuery({ name: 'dttransf', required: false, type: 'string', description: 'Data de transferência (formato YYYY-MM-DD)' })
@ApiQuery({ name: 'codfilial', required: false, type: 'number', description: 'Código da filial de origem' }) @ApiQuery({ name: 'codfilial', required: false, type: 'number', description: 'Código da filial de origem' })
@ApiQuery({ name: 'codfilialdest', required: false, type: 'number', description: 'Código da filial de destino' }) @ApiQuery({ name: 'codfilialdest', required: false, type: 'number', description: 'Código da filial de destino' })
@@ -391,62 +379,4 @@ async getTransferLogs(
); );
} }
} }
@Get('completed-deliveries')
@ApiOperation({
summary: 'Busca entregas realizadas',
description: 'Busca entregas realizadas com filtros opcionais por data, cliente, motorista, status e outros critérios.'
})
@ApiQuery({ name: 'startDate', required: false, type: 'string', description: 'Data de início (formato YYYY-MM-DD)' })
@ApiQuery({ name: 'endDate', required: false, type: 'string', description: 'Data de fim (formato YYYY-MM-DD)' })
@ApiQuery({ name: 'outId', required: false, type: 'number', description: 'ID da saída' })
@ApiQuery({ name: 'driverName', required: false, type: 'string', description: 'Nome do motorista' })
@ApiQuery({ name: 'customerId', required: false, type: 'number', description: 'ID do cliente' })
@ApiQuery({ name: 'customerName', required: false, type: 'string', description: 'Nome do cliente' })
@ApiQuery({ name: 'orderNumber', required: false, type: 'string', description: 'Número da nota fiscal' })
@ApiQuery({ name: 'status', required: false, type: 'string', description: 'Status da entrega' })
@ApiQuery({ name: 'limit', required: false, type: 'number', description: 'Limite de registros por página', example: 100 })
@ApiQuery({ name: 'offset', required: false, type: 'number', description: 'Offset para paginação', example: 0 })
@UsePipes(new ValidationPipe({ transform: true }))
@ApiResponse({
status: 200,
description: 'Entregas realizadas encontradas com sucesso',
type: [DeliveryCompleted]
})
@ApiResponse({ status: 400, description: 'Parâmetros inválidos' })
@ApiResponse({ status: 500, description: 'Erro interno do servidor' })
async getCompletedDeliveries(
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
@Query('outId') outId?: string,
@Query('driverName') driverName?: string,
@Query('customerId') customerId?: string,
@Query('customerName') customerName?: string,
@Query('orderNumber') orderNumber?: string,
@Query('status') status?: string,
@Query('limit', new DefaultValuePipe(100), ParseIntPipe) limit: number = 100,
@Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number = 0,
) {
try {
const query: DeliveryCompletedQuery = {
startDate,
endDate,
outId: outId ? parseInt(outId, 10) : undefined,
driverName,
customerId: customerId ? parseInt(customerId, 10) : undefined,
customerName,
orderNumber,
status,
limit,
offset,
};
return await this.ordersService.getCompletedDeliveries(query);
} catch (error) {
throw new HttpException(
error.message || 'Erro ao buscar entregas realizadas',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} }

View File

@@ -38,12 +38,12 @@ export class FindOrdersByDeliveryDateDto {
codfilial?: string; codfilial?: string;
@IsOptional() @IsOptional()
@IsNumber() @IsString()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'ID do vendedor', description: 'ID do vendedor (separado por vírgula para múltiplos valores)',
example: 123 example: '270,431'
}) })
sellerId?: number; sellerId?: string;
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()

View File

@@ -66,10 +66,10 @@ export class FindOrdersDto {
stockId?: string; stockId?: string;
@IsOptional() @IsOptional()
@IsNumber() @IsString()
@ApiPropertyOptional() @ApiPropertyOptional()
sellerId?: number; sellerId?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
@@ -158,7 +158,6 @@ sellerName?: string;
@IsString() @IsString()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Filtrar por status de transferência', description: 'Filtrar por status de transferência',
example: 'Em Trânsito,Em Separação,Aguardando Separação,Concluída',
enum: ['Em Trânsito', 'Em Separação', 'Aguardando Separação', 'Concluída'] enum: ['Em Trânsito', 'Em Separação', 'Aguardando Separação', 'Concluída']
}) })
statusTransfer?: string; statusTransfer?: string;
@@ -182,40 +181,35 @@ sellerName?: string;
@Type(() => Boolean) @Type(() => Boolean)
@IsBoolean() @IsBoolean()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Filtrar pedidos que tenham registros na tabela de transfer log', description: 'Filtrar pedidos que tenham registros na tabela de transfer log'
example: true
}) })
hasPreBox?: boolean; hasPreBox?: boolean;
@IsOptional() @IsOptional()
@IsString() @IsString()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Código da filial de origem da transferência (Pre-Box)', description: 'Código da filial de origem da transferência (Pre-Box)'
example: '5'
}) })
preBoxFilial?: string; preBoxFilial?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Código da filial de destino da transferência', description: 'Código da filial de destino da transferência'
example: '6'
}) })
transferDestFilial?: string; transferDestFilial?: string;
@IsOptional() @IsOptional()
@IsDateString() @IsDateString()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Data de transferência inicial (formato YYYY-MM-DD)', description: 'Data de transferência inicial (formato YYYY-MM-DD)'
example: '2024-01-01'
}) })
transferDateIni?: string; transferDateIni?: string;
@IsOptional() @IsOptional()
@IsDateString() @IsDateString()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Data de transferência final (formato YYYY-MM-DD)', description: 'Data de transferência final (formato YYYY-MM-DD)'
example: '2024-12-31'
}) })
transferDateEnd?: string; transferDateEnd?: string;
@@ -223,8 +217,7 @@ sellerName?: string;
@Type(() => Boolean) @Type(() => Boolean)
@IsBoolean() @IsBoolean()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Incluir dados de entregas realizadas para cada pedido', description: 'Incluir dados de entregas realizadas para cada pedido'
example: false
}) })
includeCompletedDeliveries?: boolean; includeCompletedDeliveries?: boolean;
@@ -232,8 +225,7 @@ sellerName?: string;
@Type(() => Number) @Type(() => Number)
@IsNumber() @IsNumber()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Limite de registros por página', description: 'Limite de registros por página'
example: 100
}) })
limit?: number; limit?: number;
@@ -241,8 +233,7 @@ sellerName?: string;
@Type(() => Number) @Type(() => Number)
@IsNumber() @IsNumber()
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Offset para paginação', description: 'Offset para paginação'
example: 0
}) })
offset?: number; offset?: number;
} }

View File

@@ -3,308 +3,258 @@ import { ApiProperty } from '@nestjs/swagger';
export class OrderResponseDto { export class OrderResponseDto {
@ApiProperty({ @ApiProperty({
description: 'Data de criação do pedido', description: 'Data de criação do pedido',
example: '2024-04-02T10:00:00Z',
}) })
createDate: Date; createDate: Date;
@ApiProperty({ @ApiProperty({
description: 'ID da loja', description: 'ID da loja',
example: '001 - Pre-Box (002)',
}) })
storeId: string; storeId: string;
@ApiProperty({ @ApiProperty({
description: 'ID do pedido', description: 'ID do pedido',
example: 12345,
}) })
orderId: number; orderId: number;
@ApiProperty({ @ApiProperty({
description: 'ID do cliente', description: 'ID do cliente',
example: '12345',
}) })
customerId: string; customerId: string;
@ApiProperty({ @ApiProperty({
description: 'Nome do cliente', description: 'Nome do cliente',
example: '12345 - João da Silva',
}) })
customerName: string; customerName: string;
@ApiProperty({ @ApiProperty({
description: 'ID do vendedor', description: 'ID do vendedor',
example: '001',
}) })
sellerId: string; sellerId: string;
@ApiProperty({ @ApiProperty({
description: 'Nome do vendedor', description: 'Nome do vendedor',
example: '001 - Maria Santos',
}) })
sellerName: string; sellerName: string;
@ApiProperty({ @ApiProperty({
description: 'Nome da loja', description: 'Nome da loja',
example: 'Loja Centro',
}) })
store: string; store: string;
@ApiProperty({ @ApiProperty({
description: 'Tipo de entrega', description: 'Tipo de entrega',
example: 'Entrega (EN)',
}) })
deliveryType: string; deliveryType: string;
@ApiProperty({ @ApiProperty({
description: 'Local de entrega', description: 'Local de entrega',
example: '001-Centro',
}) })
deliveryLocal: string; deliveryLocal: string;
@ApiProperty({ @ApiProperty({
description: 'Local de entrega principal', description: 'Local de entrega principal',
example: '001-Rota Centro',
}) })
masterDeliveryLocal: string; masterDeliveryLocal: string;
@ApiProperty({ @ApiProperty({
description: 'Tipo do pedido', description: 'Tipo do pedido',
example: 'TV8 - Entrega (EN)',
}) })
orderType: string; orderType: string;
@ApiProperty({ @ApiProperty({
description: 'Valor total do pedido', description: 'Valor total do pedido',
example: 1000.00,
}) })
amount: number; amount: number;
@ApiProperty({ @ApiProperty({
description: 'Data de entrega', description: 'Data de entrega',
example: '2024-04-05T10:00:00Z',
}) })
deliveryDate: Date; deliveryDate: Date;
@ApiProperty({ @ApiProperty({
description: 'Prioridade de entrega', description: 'Prioridade de entrega',
example: 'Alta',
}) })
deliveryPriority: string; deliveryPriority: string;
@ApiProperty({ @ApiProperty({
description: 'ID do carregamento', description: 'ID do carregamento',
example: 123,
}) })
shipmentId: number; shipmentId: number;
@ApiProperty({ @ApiProperty({
description: 'Data de liberação', description: 'Data de liberação',
example: '2024-04-02T10:00:00Z',
}) })
releaseDate: Date; releaseDate: Date;
@ApiProperty({ @ApiProperty({
description: 'Usuário de liberação', description: 'Usuário de liberação',
example: '001',
}) })
releaseUser: string; releaseUser: string;
@ApiProperty({ @ApiProperty({
description: 'Nome do usuário de liberação', description: 'Nome do usuário de liberação',
example: '001 - João Silva',
}) })
releaseUserName: string; releaseUserName: string;
@ApiProperty({ @ApiProperty({
description: 'Data de saída do carregamento', description: 'Data de saída do carregamento',
example: '2024-04-03T10:00:00Z',
}) })
shipmentDate: Date; shipmentDate: Date;
@ApiProperty({ @ApiProperty({
description: 'Data de criação do carregamento', description: 'Data de criação do carregamento',
example: '2024-04-02T10:00:00Z',
}) })
shipmentDateCreate: Date; shipmentDateCreate: Date;
@ApiProperty({ @ApiProperty({
description: 'Data de fechamento do carregamento', description: 'Data de fechamento do carregamento',
example: '2024-04-03T18:00:00Z',
}) })
shipmentCloseDate: Date; shipmentCloseDate: Date;
@ApiProperty({ @ApiProperty({
description: 'ID do plano de pagamento', description: 'ID do plano de pagamento',
example: '001',
}) })
paymentId: string; paymentId: string;
@ApiProperty({ @ApiProperty({
description: 'Nome do plano de pagamento', description: 'Nome do plano de pagamento',
example: 'Cartão de Crédito',
}) })
paymentName: string; paymentName: string;
@ApiProperty({ @ApiProperty({
description: 'ID da cobrança', description: 'ID da cobrança',
example: '001',
}) })
billingId: string; billingId: string;
@ApiProperty({ @ApiProperty({
description: 'Nome da cobrança', description: 'Nome da cobrança',
example: '001 - Cartão de Crédito',
}) })
billingName: string; billingName: string;
@ApiProperty({ @ApiProperty({
description: 'Data de faturamento', description: 'Data de faturamento',
example: '2024-04-02T10:00:00Z',
}) })
invoiceDate: Date; invoiceDate: Date;
@ApiProperty({ @ApiProperty({
description: 'Hora de faturamento', description: 'Hora de faturamento',
example: 10,
}) })
invoiceHour: number; invoiceHour: number;
@ApiProperty({ @ApiProperty({
description: 'Minuto de faturamento', description: 'Minuto de faturamento',
example: 30,
}) })
invoiceMinute: number; invoiceMinute: number;
@ApiProperty({ @ApiProperty({
description: 'Número da nota fiscal', description: 'Número da nota fiscal',
example: 123456,
}) })
invoiceNumber: number; invoiceNumber: number;
@ApiProperty({ @ApiProperty({
description: 'Descrição do bloqueio', description: 'Descrição do bloqueio',
example: 'Cliente com restrição',
}) })
BloqDescription: string; BloqDescription: string;
@ApiProperty({ @ApiProperty({
description: 'Data de confirmação de entrega', description: 'Data de confirmação de entrega',
example: '2024-04-05T15:00:00Z',
}) })
confirmDeliveryDate: Date; confirmDeliveryDate: Date;
@ApiProperty({ @ApiProperty({
description: 'Peso total', description: 'Peso total',
example: 50.5,
}) })
totalWeigth: number; totalWeigth: number;
@ApiProperty({ @ApiProperty({
description: 'Processo do pedido', description: 'Processo do pedido',
example: 3,
}) })
processOrder: number; processOrder: number;
@ApiProperty({ @ApiProperty({
description: 'Status do pedido', description: 'Status do pedido',
example: 'FATURADO',
}) })
status: string; status: string;
@ApiProperty({ @ApiProperty({
description: 'Pagamento', description: 'Pagamento',
example: 0,
}) })
payment: number; payment: number;
@ApiProperty({ @ApiProperty({
description: 'Motorista', description: 'Motorista',
example: '001 - João Silva',
}) })
driver: string; driver: string;
@ApiProperty({ @ApiProperty({
description: 'ID do pedido de venda', description: 'ID do pedido de venda',
example: 12346,
}) })
orderSaleId: number; orderSaleId: number;
@ApiProperty({ @ApiProperty({
description: 'Descrição do carro', description: 'Descrição do carro',
example: 'Fiat Fiorino (ABC1234)',
}) })
carDescription: string; carDescription: string;
@ApiProperty({ @ApiProperty({
description: 'Identificação do carro', description: 'Identificação do carro',
example: 'ABC1234',
}) })
carIdentification: string; carIdentification: string;
@ApiProperty({ @ApiProperty({
description: 'Transportadora', description: 'Transportadora',
example: '001 - Transportadora XYZ',
}) })
carrier: string; carrier: string;
@ApiProperty({ @ApiProperty({
description: 'Status da transferência', description: 'Status da transferência',
example: 'Em Trânsito',
nullable: true, nullable: true,
}) })
statusTransfer: string | null; statusTransfer: string | null;
@ApiProperty({ @ApiProperty({
description: 'Loja Pre-Box', description: 'Loja Pre-Box',
example: '002',
}) })
storePreBox: string; storePreBox: string;
@ApiProperty({ @ApiProperty({
description: 'Código do emitente da nota fiscal', description: 'Código do emitente da nota fiscal',
example: 32,
nullable: true, nullable: true,
}) })
codEmitente: number | null; codEmitente: number | null;
@ApiProperty({ @ApiProperty({
description: 'Matrícula do funcionário emitente', description: 'Matrícula do funcionário emitente',
example: 32,
nullable: true, nullable: true,
}) })
emitenteMatricula: number | null; emitenteMatricula: number | null;
@ApiProperty({ @ApiProperty({
description: 'Nome do funcionário emitente', description: 'Nome do funcionário emitente',
example: 'João Silva',
nullable: true, nullable: true,
}) })
emitenteNome: string | null; emitenteNome: string | null;
@ApiProperty({ @ApiProperty({
description: 'Código do funcionário de faturamento', description: 'Código do funcionário de faturamento',
example: 1336,
nullable: true, nullable: true,
}) })
fatUserCode: number | null; fatUserCode: number | null;
@ApiProperty({ @ApiProperty({
description: 'Nome do funcionário de faturamento', description: 'Nome do funcionário de faturamento',
example: 'ADRIANO COSTA DA SILVA',
nullable: true, nullable: true,
}) })
fatUserName: string | null; fatUserName: string | null;
@ApiProperty({ @ApiProperty({
description: 'Descrição completa do funcionário de faturamento', description: 'Descrição completa do funcionário de faturamento',
example: '1336-ADRIANO COSTA DA SILVA',
nullable: true, nullable: true,
}) })
fatUserDescription: string | null; fatUserDescription: string | null;
@ApiProperty({ @ApiProperty({
description: 'Entrega agendada', description: 'Entrega agendada',
example: 'ENTREGA NORMAL',
}) })
schedulerDelivery: string; schedulerDelivery: string;
} }

View File

@@ -0,0 +1,6 @@
export interface DebQueryParams {
cpfCgcent: string;
matricula?: number;
}

View File

@@ -0,0 +1,18 @@
import { Module } from '@nestjs/common';
import { DebController } from '../controllers/deb.controller';
import { DebService } from '../application/deb.service';
import { DebRepository } from '../repositories/deb.repository';
import { DatabaseModule } from '../../core/database/database.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule,
DatabaseModule,
],
controllers: [DebController],
providers: [DebService, DebRepository],
exports: [DebService],
})
export class DebModule {}

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm'; import { InjectDataSource } from '@nestjs/typeorm';
import { DebDto } from '../dto/DebDto';
@Injectable() @Injectable()
export class DebRepository { export class DebRepository {
@@ -15,8 +15,9 @@ export class DebRepository {
* @param matricula - Matrícula do funcionário (opcional) * @param matricula - Matrícula do funcionário (opcional)
* @param cobranca - Código de cobrança (opcional) * @param cobranca - Código de cobrança (opcional)
* @returns Lista de débitos do cliente * @returns Lista de débitos do cliente
* @throws {Error} Erro ao executar a query no banco de dados
*/ */
async findByCpfCgcent(cpfCgcent: string, matricula?: number, cobranca?: string) { async findByCpfCgcent(cpfCgcent: string, matricula?: number, cobranca?: string): Promise<DebDto[]> {
const queryRunner = this.oracleDataSource.createQueryRunner(); const queryRunner = this.oracleDataSource.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
try { try {

View File

@@ -403,7 +403,12 @@ WHERE
); );
} }
if (query.sellerId) { if (query.sellerId) {
conditions.push(`AND PCPEDC.CODUSUR = :sellerId`); const sellerIds = query.sellerId
.split(",")
.map((s) => s.trim())
.filter((s) => s)
.join(",");
conditions.push(`AND PCPEDC.CODUSUR IN (${sellerIds})`);
} }
if (query.customerId) { if (query.customerId) {
conditions.push(`AND PCPEDC.CODCLI = :customerId`); conditions.push(`AND PCPEDC.CODCLI = :customerId`);
@@ -572,9 +577,6 @@ WHERE
if (query.filialretira) { if (query.filialretira) {
parameters.storeStockId = query.filialretira; parameters.storeStockId = query.filialretira;
} }
if (query.sellerId) {
parameters.sellerId = query.sellerId;
}
if (query.customerId) { if (query.customerId) {
parameters.customerId = query.customerId; parameters.customerId = query.customerId;
} }
@@ -805,7 +807,12 @@ WHERE
conditions.push(`AND PCPEDC.CODFILIAL = :storeId`); conditions.push(`AND PCPEDC.CODFILIAL = :storeId`);
} }
if (query.sellerId) { if (query.sellerId) {
conditions.push(`AND PCPEDC.CODUSUR = :sellerId`); const sellerIds = query.sellerId
.split(",")
.map((s) => s.trim())
.filter((s) => s)
.join(",");
conditions.push(`AND PCPEDC.CODUSUR IN (${sellerIds})`);
} }
if (query.customerId) { if (query.customerId) {
conditions.push(`AND PCPEDC.CODCLI = :customerId`); conditions.push(`AND PCPEDC.CODCLI = :customerId`);
@@ -889,9 +896,6 @@ WHERE
if (query.codfilial) { if (query.codfilial) {
parameters.storeId = query.codfilial; parameters.storeId = query.codfilial;
} }
if (query.sellerId) {
parameters.sellerId = query.sellerId;
}
if (query.customerId) { if (query.customerId) {
parameters.customerId = query.customerId; parameters.customerId = query.customerId;
} }