diff --git a/src/main.ts b/src/main.ts index 9a9ff5c..2e1f453 100644 --- a/src/main.ts +++ b/src/main.ts @@ -50,15 +50,13 @@ async function bootstrap() { ); app.enableCors({ - origin: process.env.NODE_ENV === 'production' - ? ['https://www.jurunense.com', 'https://*.jurunense.com'] - : ['http://localhost:9602 add '], - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], + origin: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], credentials: true, allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], - maxAge: 3600, }); + const config = new DocumentBuilder() .setTitle('Portal Jurunense API') .setDescription('Documentação da API do Portal Jurunense') diff --git a/src/orders/controllers/orders.controller.ts b/src/orders/controllers/orders.controller.ts index a5defac..0ac473e 100644 --- a/src/orders/controllers/orders.controller.ts +++ b/src/orders/controllers/orders.controller.ts @@ -13,6 +13,7 @@ import { HttpStatus, DefaultValuePipe, ParseBoolPipe, + Optional, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags, ApiQuery, ApiParam, ApiResponse } from '@nestjs/swagger'; import { ResponseInterceptor } from '../../common/response.interceptor'; @@ -33,11 +34,13 @@ import { CarrierDto } from 'src/data-consult/dto/carrier.dto'; import { OrderResponseDto } from '../dto/order-response.dto'; import { MarkResponseDto } from '../dto/mark-response.dto'; import { EstLogTransferResponseDto } from '../dto/estlogtransfer.dto'; +import { DeliveryCompletedQuery } from '../dto/delivery-completed-query.dto'; +import { DeliveryCompleted } from '../dto/delivery-completed.dto'; @ApiTags('Orders') -//@ApiBearerAuth() -//@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +@UseGuards(JwtAuthGuard) @Controller('api/v1/orders') export class OrdersController { constructor(private readonly ordersService: OrdersService) {} @@ -48,6 +51,9 @@ 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.' }) @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: '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)' }) @@ -146,10 +152,15 @@ export class OrdersController { @Get('delivery/:orderId') @ApiOperation({ summary: 'Busca dados de entrega do pedido' }) @ApiParam({ name: 'orderId', example: '236001388' }) + @ApiQuery({ name: 'includeCompletedDeliveries', required: false, type: 'boolean', description: 'Incluir dados de entregas realizadas para o pedido' }) @UsePipes(new ValidationPipe({ transform: true })) - async getOrderDelivery(@Param('orderId', ParseIntPipe) orderId: number): Promise { + async getOrderDelivery( + @Param('orderId', ParseIntPipe) orderId: number, + @Query('includeCompletedDeliveries', new DefaultValuePipe(false), ParseBoolPipe) + includeCompletedDeliveries: boolean, + ): Promise { try { - return await this.ordersService.getOrderDelivery(orderId.toString()); + return await this.ordersService.getOrderDelivery(orderId.toString(), includeCompletedDeliveries); } catch (error) { throw new HttpException( error.message || 'Erro ao buscar dados de entrega', @@ -380,4 +391,62 @@ 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, + ); + } +} } diff --git a/src/orders/dto/OrderDeliveryDto.ts b/src/orders/dto/OrderDeliveryDto.ts index 8d92f42..05933a5 100644 --- a/src/orders/dto/OrderDeliveryDto.ts +++ b/src/orders/dto/OrderDeliveryDto.ts @@ -1,3 +1,5 @@ +import { DeliveryCompleted } from './delivery-completed.dto'; + export class OrderDeliveryDto { placeId: number; placeName: string; @@ -24,5 +26,6 @@ export class OrderDeliveryDto { separatorName: string; confName: string; releaseDate: Date; + completedDeliveries?: DeliveryCompleted[]; } \ No newline at end of file diff --git a/src/orders/dto/delivery-completed-query.dto.ts b/src/orders/dto/delivery-completed-query.dto.ts new file mode 100644 index 0000000..2eec087 --- /dev/null +++ b/src/orders/dto/delivery-completed-query.dto.ts @@ -0,0 +1,54 @@ +import { IsOptional, IsString, IsNumber, IsDateString } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export class DeliveryCompletedQuery { + @ApiPropertyOptional({ description: 'Data de início para filtro (formato YYYY-MM-DD)' }) + @IsOptional() + @IsDateString() + startDate?: string; + + @ApiPropertyOptional({ description: 'Data de fim para filtro (formato YYYY-MM-DD)' }) + @IsOptional() + @IsDateString() + endDate?: string; + + @ApiPropertyOptional({ description: 'ID da saída' }) + @IsOptional() + @IsNumber() + outId?: number; + + @ApiPropertyOptional({ description: 'Nome do motorista' }) + @IsOptional() + @IsString() + driverName?: string; + + @ApiPropertyOptional({ description: 'ID do cliente' }) + @IsOptional() + @IsNumber() + customerId?: number; + + @ApiPropertyOptional({ description: 'Nome do cliente' }) + @IsOptional() + @IsString() + customerName?: string; + + @ApiPropertyOptional({ description: 'Número da nota fiscal' }) + @IsOptional() + @IsString() + orderNumber?: string; + + @ApiPropertyOptional({ description: 'Status da entrega' }) + @IsOptional() + @IsString() + status?: string; + + @ApiPropertyOptional({ description: 'Limite de registros por página', default: 100 }) + @IsOptional() + @IsNumber() + limit?: number; + + @ApiPropertyOptional({ description: 'Offset para paginação', default: 0 }) + @IsOptional() + @IsNumber() + offset?: number; +} diff --git a/src/orders/dto/delivery-completed.dto.ts b/src/orders/dto/delivery-completed.dto.ts new file mode 100644 index 0000000..91bcc85 --- /dev/null +++ b/src/orders/dto/delivery-completed.dto.ts @@ -0,0 +1,23 @@ +export class DeliveryCompleted { + outId: number; + transactionId: number; + deliveryDate: Date; + invoiceNumber: string; + customerId: number; + customerName: string; + deliveryDoc: string; + deliveryName: string; + lat: number; + lng: number; + existBreakdown: string; + existReturn: string; + obsReturn: string; + dataCanhoto: Date; + transactionIdInvoice: number; + invoiceUserId: number; + invoiceUserName: string; + deliveryStatus: string; + driverId: number; + driverName: string; + urlImages: string[]; +} diff --git a/src/orders/dto/find-orders.dto.ts b/src/orders/dto/find-orders.dto.ts index 03466d3..20f5466 100644 --- a/src/orders/dto/find-orders.dto.ts +++ b/src/orders/dto/find-orders.dto.ts @@ -218,4 +218,31 @@ sellerName?: string; example: '2024-12-31' }) transferDateEnd?: string; + + @IsOptional() + @Type(() => Boolean) + @IsBoolean() + @ApiPropertyOptional({ + description: 'Incluir dados de entregas realizadas para cada pedido', + example: false + }) + includeCompletedDeliveries?: boolean; + + @IsOptional() + @Type(() => Number) + @IsNumber() + @ApiPropertyOptional({ + description: 'Limite de registros por página', + example: 100 + }) + limit?: number; + + @IsOptional() + @Type(() => Number) + @IsNumber() + @ApiPropertyOptional({ + description: 'Offset para paginação', + example: 0 + }) + offset?: number; } diff --git a/src/orders/repositories/orders.repository.ts b/src/orders/repositories/orders.repository.ts index 4b722d1..a0f529f 100644 --- a/src/orders/repositories/orders.repository.ts +++ b/src/orders/repositories/orders.repository.ts @@ -11,6 +11,8 @@ import { OrderStatusDto } from '../dto/OrderStatusDto'; import { InvoiceCheckDto } from '../dto/invoice-check.dto'; import { LeadtimeDto } from "../dto/leadtime.dto"; import { MarkData } from "../interface/markdata"; +import { DeliveryCompletedQuery } from "../dto/delivery-completed-query.dto"; +import { DeliveryCompleted } from "../dto/delivery-completed.dto"; @Injectable() @@ -1569,4 +1571,251 @@ WHERE `; return await this.oracleDataSource.query(sql, [markName]); } + + /** + * Busca o NUMTRANSVENDA de um pedido específico + * @param orderId - ID do pedido + * @returns NUMTRANSVENDA do pedido ou null se não encontrado + */ + async getOrderTransactionId(orderId: string): Promise { + const sql = ` + SELECT NUMTRANSVENDA + FROM PCPEDC + WHERE NUMPED = :1 + `; + + const result = await this.oracleDataSource.query(sql, [orderId]); + return result.length > 0 ? result[0].NUMTRANSVENDA : null; + } + + /** + * Busca entregas realizadas por transactionId + * @param query - Filtros para a consulta de entregas realizadas incluindo transactionId + * @returns Lista de entregas realizadas + */ + async getCompletedDeliveriesByTransactionId(query: { transactionId: number; limit: number; offset: number }): Promise { + const sql = ` + SELECT + ESTENTREGAS.CODSAIDA AS "outId" + ,ESTENTREGAS.NUMTRANSVENDA AS "transactionId" + ,ESTENTREGAS.DATA AS "deliveryDate" + ,PCNFSAID.NUMNOTA AS "invoiceNumber" + ,PCNFSAID.CODCLI AS "customerId" + ,PCCLIENT.CLIENTE AS "customerName" + ,ESTENTREGAS.DOCUMENTORECEBEDOR AS "deliveryDoc" + ,ESTENTREGAS.NOMERECEBEDOR AS "deliveryName" + ,ESTENTREGAS.LAT AS "lat" + ,ESTENTREGAS.LNG AS "lng" + ,ESTENTREGAS.AVARIA AS "existBreakdown" + ,ESTENTREGAS.DEVOLUCAO AS "existReturn" + ,ESTENTREGAS.MOTIVODEVOLUCAO AS "obsReturn" + ,PCNFSAID.DTCANHOTO AS "dataCanhoto" + ,PCNFSAID.NUMTRANSVENDA AS "transactionIdInvoice" + ,PCNFSAID.CODFUNCCANHOTO AS "invoiceUserId" + ,PCEMPR.NOME AS "invoiceUserName" + ,ESTSITUACAOENTREGA.SITUACAO AS "deliveryStatus" + ,PCCARREG.CODMOTORISTA AS "driverId" + ,MOTORISTA.NOME AS "driverName" + FROM ESTENTREGAS, PCNFSAID, PCCLIENT, PCEMPR, ESTSITUACAOENTREGA, ESTSAIDAVEICULOCARREG, PCCARREG, PCEMPR MOTORISTA + WHERE ESTENTREGAS.NUMTRANSVENDA = PCNFSAID.NUMTRANSVENDA + AND ESTENTREGAS.CODSAIDA = ESTSAIDAVEICULOCARREG.CODSAIDA + AND PCNFSAID.NUMCAR = ESTSAIDAVEICULOCARREG.NUMCAR + AND PCNFSAID.NUMCAR = PCCARREG.NUMCAR + AND PCCARREG.CODMOTORISTA = MOTORISTA.MATRICULA (+) + AND PCNFSAID.CODCLI = PCCLIENT.CODCLI + AND PCNFSAID.CODFUNCCANHOTO = PCEMPR.MATRICULA (+) + AND ESTENTREGAS.CODSAIDA = ESTSITUACAOENTREGA.CODSAIDA (+) + AND PCNFSAID.CODCLI = ESTSITUACAOENTREGA.CODCLI (+) + AND ESTENTREGAS.NUMTRANSVENDA = :1 + ORDER BY ESTENTREGAS.DATA DESC + OFFSET :2 ROWS FETCH NEXT :3 ROWS ONLY + `; + + const deliveries = await this.oracleDataSource.query(sql, [ + query.transactionId, + query.offset, + query.limit + ]); + + // Buscar imagens para cada entrega + for (let index = 0; index < deliveries.length; index++) { + const delivery = deliveries[index]; + const sqlImages = ` + SELECT URL + FROM ESTENTREGASIMAGENS + WHERE CODSAIDA = :1 + AND NUMTRANSVENDA = :2 + `; + const images = await this.oracleDataSource.query(sqlImages, [delivery.outId, delivery.transactionId]); + delivery.urlImages = images.map((image: any) => image.URL); + } + + // Converter os resultados para o formato esperado + return deliveries.map((row: any) => ({ + outId: row.outId, + transactionId: row.transactionId, + deliveryDate: row.deliveryDate, + invoiceNumber: row.invoiceNumber, + customerId: row.customerId, + customerName: row.customerName, + deliveryDoc: row.deliveryDoc, + deliveryName: row.deliveryName, + lat: row.lat, + lng: row.lng, + existBreakdown: row.existBreakdown, + existReturn: row.existReturn, + obsReturn: row.obsReturn, + dataCanhoto: row.dataCanhoto, + transactionIdInvoice: row.transactionIdInvoice, + invoiceUserId: row.invoiceUserId, + invoiceUserName: row.invoiceUserName, + deliveryStatus: row.deliveryStatus, + driverId: row.driverId, + driverName: row.driverName, + urlImages: [...row.urlImages], + })); + } + + /** + * Busca entregas realizadas com filtros opcionais + * @param query - Filtros para a consulta de entregas realizadas + * @returns Lista de entregas realizadas + */ + async getCompletedDeliveries(query: DeliveryCompletedQuery): Promise { + let sql = ` + SELECT + ESTENTREGAS.CODSAIDA AS "outId" + ,ESTENTREGAS.NUMTRANSVENDA AS "transactionId" + ,ESTENTREGAS.DATA AS "deliveryDate" + ,PCNFSAID.NUMNOTA AS "invoiceNumber" + ,PCNFSAID.CODCLI AS "customerId" + ,PCCLIENT.CLIENTE AS "customerName" + ,ESTENTREGAS.DOCUMENTORECEBEDOR AS "deliveryDoc" + ,ESTENTREGAS.NOMERECEBEDOR AS "deliveryName" + ,ESTENTREGAS.LAT AS "lat" + ,ESTENTREGAS.LNG AS "lng" + ,ESTENTREGAS.AVARIA AS "existBreakdown" + ,ESTENTREGAS.DEVOLUCAO AS "existReturn" + ,ESTENTREGAS.MOTIVODEVOLUCAO AS "obsReturn" + ,PCNFSAID.DTCANHOTO AS "dataCanhoto" + ,PCNFSAID.NUMTRANSVENDA AS "transactionIdInvoice" + ,PCNFSAID.CODFUNCCANHOTO AS "invoiceUserId" + ,PCEMPR.NOME AS "invoiceUserName" + ,ESTSITUACAOENTREGA.SITUACAO AS "deliveryStatus" + ,PCCARREG.CODMOTORISTA AS "driverId" + ,MOTORISTA.NOME AS "driverName" + FROM ESTENTREGAS, PCNFSAID, PCCLIENT, PCEMPR, ESTSITUACAOENTREGA, ESTSAIDAVEICULOCARREG, PCCARREG, PCEMPR MOTORISTA + WHERE ESTENTREGAS.NUMTRANSVENDA = PCNFSAID.NUMTRANSVENDA + AND ESTENTREGAS.CODSAIDA = ESTSAIDAVEICULOCARREG.CODSAIDA + AND PCNFSAID.NUMCAR = ESTSAIDAVEICULOCARREG.NUMCAR + AND PCNFSAID.NUMCAR = PCCARREG.NUMCAR + AND PCCARREG.CODMOTORISTA = MOTORISTA.MATRICULA (+) + AND PCNFSAID.CODCLI = PCCLIENT.CODCLI + AND PCNFSAID.CODFUNCCANHOTO = PCEMPR.MATRICULA (+) + AND ESTENTREGAS.CODSAIDA = ESTSITUACAOENTREGA.CODSAIDA (+) + AND PCNFSAID.CODCLI = ESTSITUACAOENTREGA.CODCLI (+) + `; + + const parameters: any[] = []; + let paramIndex = 1; + + // Filtros opcionais + if (query.startDate) { + sql += ` AND TRUNC(ESTENTREGAS.DATA) >= TO_DATE(:${paramIndex},'YYYY-MM-DD')`; + parameters.push(query.startDate); + paramIndex++; + } + + if (query.endDate) { + sql += ` AND TRUNC(ESTENTREGAS.DATA) <= TO_DATE(:${paramIndex},'YYYY-MM-DD')`; + parameters.push(query.endDate); + paramIndex++; + } + + if (query.outId) { + sql += ` AND ESTENTREGAS.CODSAIDA = :${paramIndex}`; + parameters.push(query.outId); + paramIndex++; + } + + if (query.driverName) { + sql += ` AND UPPER(MOTORISTA.NOME) LIKE UPPER(:${paramIndex})`; + parameters.push(`%${query.driverName}%`); + paramIndex++; + } + + if (query.customerId && query.customerId > 0) { + sql += ` AND PCCLIENT.CODCLI = :${paramIndex}`; + parameters.push(query.customerId); + paramIndex++; + } + + if (query.customerName) { + sql += ` AND UPPER(PCCLIENT.CLIENTE) LIKE UPPER(:${paramIndex})`; + parameters.push(`%${query.customerName}%`); + paramIndex++; + } + + if (query.orderNumber) { + sql += ` AND PCNFSAID.NUMNOTA = :${paramIndex}`; + parameters.push(query.orderNumber); + paramIndex++; + } + + if (query.status) { + sql += ` AND ESTSITUACAOENTREGA.SITUACAO = :${paramIndex}`; + parameters.push(query.status); + paramIndex++; + } + + // Ordenação + sql += ` ORDER BY ESTENTREGAS.DATA DESC`; + + // Paginação + const limit = query.limit || 100; + const offset = query.offset || 0; + sql += ` OFFSET :${paramIndex} ROWS FETCH NEXT :${paramIndex + 1} ROWS ONLY`; + parameters.push(offset); + parameters.push(limit); + + const deliveries = await this.oracleDataSource.query(sql, parameters); + + // Buscar imagens para cada entrega + for (let index = 0; index < deliveries.length; index++) { + const delivery = deliveries[index]; + const sqlImages = ` + SELECT URL + FROM ESTENTREGASIMAGENS + WHERE CODSAIDA = :1 + AND NUMTRANSVENDA = :2 + `; + const images = await this.oracleDataSource.query(sqlImages, [delivery.outId, delivery.transactionId]); + delivery.urlImages = images.map((image: any) => image.URL); + } + + // Converter os resultados para o formato esperado + return deliveries.map((row: any) => ({ + outId: row.outId, + transactionId: row.transactionId, + deliveryDate: row.deliveryDate, + invoiceNumber: row.invoiceNumber, + customerId: row.customerId, + customerName: row.customerName, + deliveryDoc: row.deliveryDoc, + deliveryName: row.deliveryName, + lat: row.lat, + lng: row.lng, + existBreakdown: row.existBreakdown, + existReturn: row.existReturn, + obsReturn: row.obsReturn, + dataCanhoto: row.dataCanhoto, + transactionIdInvoice: row.transactionIdInvoice, + invoiceUserId: row.invoiceUserId, + invoiceUserName: row.invoiceUserName, + deliveryStatus: row.deliveryStatus, + driverId: row.driverId, + driverName: row.driverName, + urlImages: [...row.urlImages], + })); + } } \ No newline at end of file