diff --git a/.env b/.env index ab24328..19ff54e 100644 --- a/.env +++ b/.env @@ -11,8 +11,9 @@ POSTGRES_USER=ti POSTGRES_PASSWORD=ti POSTGRES_DB=ksdb - JWT_SECRET=4557C0D7-DFB0-40DA-BF83-91A75103F7A9 JWT_EXPIRES_IN=8h + + diff --git a/package-lock.json b/package-lock.json index 50adad5..cc2ba7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/axios": "^4.0.0", + "@nestjs/bull": "^11.0.2", "@nestjs/common": "^11.0.12", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.12", @@ -27,6 +28,7 @@ "@types/estree": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.8.4", + "bullmq": "^5.45.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "fs": "0.0.1-security", @@ -1582,6 +1584,84 @@ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==" }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@nestjs/axios": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.0.tgz", @@ -1593,6 +1673,34 @@ "rxjs": "^7.0.0" } }, + "node_modules/@nestjs/bull": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/bull/-/bull-11.0.2.tgz", + "integrity": "sha512-RjyP9JZUuLmMhmq1TMNIZqolkAd14az1jyXMMVki+C9dYvaMjWzBSwcZAtKs9Pk15Rm7qN1xn3R11aMV2Xv4gg==", + "license": "MIT", + "dependencies": { + "@nestjs/bull-shared": "^11.0.2", + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "bull": "^3.3 || ^4.0.0" + } + }, + "node_modules/@nestjs/bull-shared": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.2.tgz", + "integrity": "sha512-dFlttJvBqIFD6M8JVFbkrR4Feb39OTAJPJpFVILU50NOJCM4qziRw3dSNG84Q3v+7/M6xUGMFdZRRGvBBKxoSA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } + }, "node_modules/@nestjs/cli": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.5.tgz", @@ -3811,6 +3919,34 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/bullmq": { + "version": "5.45.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.45.2.tgz", + "integrity": "sha512-wHZfcD4z4aLolxREmwNNDSbfh7USeq2e3yu5W2VGkzHMUcrH0fzZuRuCMsjD0XKS9ViK1U854oM9yWR6ftPeDA==", + "license": "MIT", + "dependencies": { + "cron-parser": "^4.9.0", + "ioredis": "^5.4.1", + "msgpackr": "^1.11.2", + "node-abort-controller": "^3.1.1", + "semver": "^7.5.4", + "tslib": "^2.0.0", + "uuid": "^9.0.0" + } + }, + "node_modules/bullmq/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -4508,6 +4644,18 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4743,6 +4891,16 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -6314,6 +6472,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==", + "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -7984,6 +8143,15 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, + "node_modules/luxon": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -8222,6 +8390,37 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/msgpackr": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, "node_modules/multer": { "version": "1.4.5-lts.2", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", @@ -8318,8 +8517,7 @@ "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, "node_modules/node-emoji": { "version": "1.11.0", @@ -8330,6 +8528,21 @@ "lodash": "^4.17.21" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index 73ac369..de6741c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@nestjs/axios": "^4.0.0", + "@nestjs/bull": "^11.0.2", "@nestjs/common": "^11.0.12", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.12", @@ -38,6 +39,7 @@ "@types/estree": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.8.4", + "bullmq": "^5.45.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "fs": "0.0.1-security", diff --git a/src/app.module.ts b/src/app.module.ts index feb2197..ac7104e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,7 +8,7 @@ import { ProductsModule } from './products/products.module'; import { AuthModule } from './auth/auth/auth.module'; import { DataConsultModule } from './data-consult/data-consult.module'; - import { OrdersModule } from './orders/orders.module'; + import { OrdersModule } from './orders/modules/orders.module'; import { OcorrencesController } from './crm/occurrences/ocorrences.controller'; import { OccurrencesModule } from './crm/occurrences/occurrences.module'; import { ReasonTableModule } from './crm/reason-table/reason-table.module'; @@ -51,6 +51,6 @@ OrdersModule, ], controllers: [OcorrencesController, LogisticController ], - providers: [ LogisticService, ], + providers: [ LogisticService, ], }) export class AppModule {} diff --git a/src/auth/auth/auth.controller.ts b/src/auth/auth/auth.controller.ts index 3f252fb..9a39d24 100644 --- a/src/auth/auth/auth.controller.ts +++ b/src/auth/auth/auth.controller.ts @@ -7,7 +7,7 @@ import { } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { CqrsModule } from '@nestjs/cqrs'; -import { AuthenticateUserCommand } from './authenticate-user.command'; +import { AuthenticateUserCommand } from './commands/authenticate-user.command'; import { LoginResponseDto } from './dto/LoginResponseDto'; import { LoginDto } from './dto/login.dto'; import { ResultModel } from 'src/core/models/result.model'; diff --git a/src/auth/auth/auth.module.ts b/src/auth/auth/auth.module.ts index 46cdc9a..3050188 100644 --- a/src/auth/auth/auth.module.ts +++ b/src/auth/auth/auth.module.ts @@ -8,7 +8,7 @@ import { UsersModule } from '../users/users.module'; import { AuthController } from './auth.controller'; import { CqrsModule } from '@nestjs/cqrs'; import { ConfigModule, ConfigService } from '@nestjs/config'; -import { AuthenticateUserHandler } from './authenticate-user.service'; +import { AuthenticateUserHandler } from './commands/authenticate-user.service'; @Module({ imports: [ @@ -29,7 +29,7 @@ import { AuthenticateUserHandler } from './authenticate-user.service'; UsersModule, ], controllers: [AuthController], - providers: [AuthService, JwtStrategy, AuthenticateUserHandler], + providers: [AuthService, JwtStrategy], exports: [AuthService], }) export class AuthModule {} diff --git a/src/auth/auth/authenticate-user.command.ts b/src/auth/auth/commands/authenticate-user.command.ts similarity index 100% rename from src/auth/auth/authenticate-user.command.ts rename to src/auth/auth/commands/authenticate-user.command.ts diff --git a/src/auth/auth/authenticate-user.service.ts b/src/auth/auth/commands/authenticate-user.service.ts similarity index 91% rename from src/auth/auth/authenticate-user.service.ts rename to src/auth/auth/commands/authenticate-user.service.ts index d1fa0fe..6e4848c 100644 --- a/src/auth/auth/authenticate-user.service.ts +++ b/src/auth/auth/commands/authenticate-user.service.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { AuthenticateUserCommand } from './authenticate-user.command'; -import { UserRepository } from '../users/UserRepository'; -import { Result } from '../models/result'; +import { UserRepository } from '../../users/UserRepository'; +import { Result } from '../../models/result'; import { Injectable } from '@nestjs/common'; import { UserModel } from 'src/core/models/user.model'; diff --git a/src/auth/users/users.module.ts b/src/auth/users/users.module.ts index fc95d55..c742ad5 100644 --- a/src/auth/users/users.module.ts +++ b/src/auth/users/users.module.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; // <-- importe aqui +import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersService } from './users.service'; import { UserRepository } from './UserRepository'; - -import { AuthenticateUserHandler } from '../auth/authenticate-user.service'; +import { AuthenticateUserHandler } from '../auth/commands/authenticate-user.service'; import { ResetPasswordService } from './reset-password.service'; import { ChangePasswordService } from './change-password.service'; import { EmailService } from './email.service'; diff --git a/src/auth/users/users.service.ts b/src/auth/users/users.service.ts index 0d66221..7ca6401 100644 --- a/src/auth/users/users.service.ts +++ b/src/auth/users/users.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AuthenticateUserHandler } from '../auth/authenticate-user.service'; +import { AuthenticateUserHandler } from '../auth/commands/authenticate-user.service'; import { ResetPasswordService } from './reset-password.service'; import { ChangePasswordService } from './change-password.service'; -import { AuthenticateUserCommand } from '../auth/authenticate-user.command'; +import { AuthenticateUserCommand } from '../auth/commands/authenticate-user.command'; diff --git a/src/logistic/logistic.service.ts b/src/logistic/logistic.service.ts index 3d995eb..ae2f044 100644 --- a/src/logistic/logistic.service.ts +++ b/src/logistic/logistic.service.ts @@ -98,7 +98,7 @@ export class LogisticService { } async getDeliveries(placa: string) { - const dataSource = new DataSource(createPostgresConfig(this.configService)); + const dataSource = new DataSource(createOracleConfig(this.configService)); await dataSource.initialize(); const queryRunner = dataSource.createQueryRunner(); await queryRunner.connect(); diff --git a/src/main.ts b/src/main.ts index 39bf37d..236683d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,6 @@ async function bootstrap() { app.useGlobalInterceptors(new ResponseInterceptor()); - - app.useGlobalPipes( new ValidationPipe({ whitelist: true, diff --git a/src/orders/application/orders.service.ts b/src/orders/application/orders.service.ts index 2235703..eabba0e 100644 --- a/src/orders/application/orders.service.ts +++ b/src/orders/application/orders.service.ts @@ -1,14 +1,113 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; import { FindOrdersDto } from '../dto/find-orders.dto'; +import { InvoiceDto } from '../dto/find-invoice.dto'; +import { CutItemDto } from '../dto/CutItemDto'; import { OrdersRepository } from '../repositories/orders.repository'; +import { OrderItemDto } from '../dto/OrderItemDto'; +import { IRedisClient } from 'src/core/configs/cache/IRedisClient'; +import { RedisClientToken } from 'src/core/configs/cache/redis-client.adapter.provider'; +import { getOrSetCache } from 'src/shared/cache.util'; +import { createHash } from 'crypto'; @Injectable() export class OrdersService { - constructor(private readonly ordersRepository: OrdersRepository) {} + private readonly TTL_ORDERS = 60 * 10; // 10 minutos + private readonly TTL_INVOICE = 60 * 60; // 1 hora + private readonly TTL_ITENS = 60 * 10; // 10 minutos + constructor( + private readonly ordersRepository: OrdersRepository, + @Inject(RedisClientToken) private readonly redisClient: IRedisClient, + ) {} + + /** + * Buscar pedidos com cache baseado nos filtros + */ async findOrders(query: FindOrdersDto) { - return await this.ordersRepository.findOrders(query); + const key = `orders:query:${this.hashObject(query)}`; + + return getOrSetCache( + this.redisClient, + key, + this.TTL_ORDERS, + () => this.ordersRepository.findOrders(query), + ); } - -} + /** + * Buscar nota fiscal por chave NFe com cache + */ + async findInvoice(chavenfe: string): Promise { + const key = `orders:invoice:${chavenfe}`; + + return getOrSetCache(this.redisClient, key, this.TTL_INVOICE, async () => { + const invoiceData = await this.ordersRepository.findInvoice(chavenfe); + + const invoice: InvoiceDto = { + storeId: invoiceData.storeId, + invoiceDate: new Date(invoiceData.invoiceDate), + orderId: invoiceData.orderId, + invoiceId: invoiceData.invoiceId, + transactionId: invoiceData.transactionId, + customerId: invoiceData.customerId, + customer: invoiceData.customer, + sellerId: invoiceData.sellerId, + sellerName: invoiceData.sellerName, + itensQt: invoiceData.itensQt, + itens: invoiceData.itens, + }; + + return invoice; + }); + } + + /** + * Buscar itens de pedido com cache + */ + async getItens(orderId: string): Promise { + const key = `orders:itens:${orderId}`; + + return getOrSetCache(this.redisClient, key, this.TTL_ITENS, async () => { + const itens = await this.ordersRepository.getItens(orderId); + + return itens.map(item => ({ + productId: Number(item.productId), + description: item.description, + pacth: item.pacth, + color: item.color, + stockId: Number(item.stockId), + quantity: Number(item.quantity), + salePrice: Number(item.salePrice), + deliveryType: item.deliveryType, + total: Number(item.total), + weight: Number(item.weight), + department: item.department, + brand: item.brand, + })); + }); + } + + async getCutItens(orderId: string): Promise { + const itens = await this.ordersRepository.getCutItens(orderId); + + return itens.map(item => ({ + productId: Number(item.productId), + description: item.description, + pacth: item.pacth, + stockId: Number(item.stockId), + saleQuantity: Number(item.saleQuantity), + cutQuantity: Number(item.cutQuantity), + separedQuantity: Number(item.separedQuantity), + })); + } + + + /** + * Utilitário para gerar hash MD5 de objetos + */ + private hashObject(obj: any): string { + const str = JSON.stringify(obj, Object.keys(obj).sort()); + return createHash('md5').update(str).digest('hex'); + } + +} \ No newline at end of file diff --git a/src/orders/controllers/orders.controller.ts b/src/orders/controllers/orders.controller.ts new file mode 100644 index 0000000..aa6fc3e --- /dev/null +++ b/src/orders/controllers/orders.controller.ts @@ -0,0 +1,76 @@ +import { + Controller, + Get, + Param, + Query, + UsePipes, + UseGuards, + UseInterceptors, + ValidationPipe, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ResponseInterceptor } from '../../common/response.interceptor'; +import { OrdersService } from '../application/orders.service'; +import { FindOrdersDto } from '../dto/find-orders.dto'; +import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; +import { InvoiceDto } from '../dto/find-invoice.dto'; +import { OrderItemDto } from "../dto/OrderItemDto"; +import { CutItemDto } from '../dto/CutItemDto'; + + +@ApiTags('Orders') +@ApiBearerAuth() +@UseGuards(JwtAuthGuard) +@UseInterceptors(ResponseInterceptor) +@Controller('api/v1/orders') +export class OrdersController { + constructor(private readonly ordersService: OrdersService) {} + + @Get('find') + @UsePipes(new ValidationPipe({ transform: true })) + findOrders(@Query() query: FindOrdersDto) { + return this.ordersService.findOrders(query); + } + + @Get('invoice/:chavenfe') + @ApiOperation({ summary: 'Busca NF pela chave' }) + @UsePipes(new ValidationPipe({ transform: true })) + async getInvoice(@Param('chavenfe') chavenfe: string): Promise { + try { + return await this.ordersService.findInvoice(chavenfe); + } catch (error) { + throw new HttpException( + error.message || 'Erro ao buscar nota fiscal', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @Get('itens/:orderId') + @ApiOperation({ summary: 'Busca PELO numero do pedido' }) + @UsePipes(new ValidationPipe({ transform: true })) + async getItens(@Param('orderId') orderId: string): Promise { + try { + return await this.ordersService.getItens(orderId); + } catch (error) { + throw new HttpException( + error.message || 'Erro ao buscar itens do pedido', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @Get('cut-itens/:orderId') + @ApiOperation({ summary: 'Busca itens cortados do pedido' }) + @UsePipes(new ValidationPipe({ transform: true })) + async getCutItens(@Param('orderId') orderId: string): Promise { + try { + return await this.ordersService.getCutItens(orderId); + } catch (error) { + throw new HttpException( + error.message || 'Erro ao buscar itens cortados', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/orders/dto/CutItemDto.ts b/src/orders/dto/CutItemDto.ts new file mode 100644 index 0000000..1ad5be5 --- /dev/null +++ b/src/orders/dto/CutItemDto.ts @@ -0,0 +1,10 @@ +export class CutItemDto { + productId: number; + description: string; + pacth: string; + stockId: number; + saleQuantity: number; + cutQuantity: number; + separedQuantity: number; + } + \ No newline at end of file diff --git a/src/orders/dto/OrderItemDto.ts b/src/orders/dto/OrderItemDto.ts new file mode 100644 index 0000000..58a2d88 --- /dev/null +++ b/src/orders/dto/OrderItemDto.ts @@ -0,0 +1,14 @@ +export class OrderItemDto { + productId: number; + description: string; + pacth: string; + color: string; + stockId: number; + quantity: number; + salePrice: number; + deliveryType: string; + total: number; + weight: number; + department: string; + brand: string; + } \ No newline at end of file diff --git a/src/orders/dto/find-invoice.dto.ts b/src/orders/dto/find-invoice.dto.ts new file mode 100644 index 0000000..a52d42d --- /dev/null +++ b/src/orders/dto/find-invoice.dto.ts @@ -0,0 +1,29 @@ + +export class FindInvoiceDto { + chavenfe: string; +} + +export class InvoiceItemDto { + productId: number; + productName: string; + package: string; + qt: number; + ean: string; + multiple: number; + productType: string; + image: string; +} + +export class InvoiceDto { + storeId: number; + invoiceDate: Date; + orderId: number; + invoiceId: number; + transactionId: number; + customerId: number; + customer: string; + sellerId: number; + sellerName: string; + itensQt: number; + itens: InvoiceItemDto[]; +} diff --git a/src/orders/dto/find-orders.dto.ts b/src/orders/dto/find-orders.dto.ts index f0a2e82..8e32ad7 100644 --- a/src/orders/dto/find-orders.dto.ts +++ b/src/orders/dto/find-orders.dto.ts @@ -12,13 +12,13 @@ export class FindOrdersDto { @IsOptional() @IsString() @ApiPropertyOptional() - storeId?: string; + codfilial?: string; @IsOptional() @IsString() @ApiPropertyOptional() - storeStockId?: string; + filialretira?: string; @IsOptional() @IsNumber() diff --git a/src/orders/modules/orders.module.ts b/src/orders/modules/orders.module.ts new file mode 100644 index 0000000..79edaed --- /dev/null +++ b/src/orders/modules/orders.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { OrdersController } from '../controllers/orders.controller'; +import { OrdersService } from '../application/orders.service'; +import { OrdersRepository } from '../repositories/orders.repository'; + + +@Module({ + imports: [], + controllers: [OrdersController], + providers: [OrdersService, OrdersRepository], +}) +export class OrdersModule {} diff --git a/src/orders/orders.controller.ts b/src/orders/orders.controller.ts deleted file mode 100644 index ccaf485..0000000 --- a/src/orders/orders.controller.ts +++ /dev/null @@ -1,33 +0,0 @@ -// src/modules/orders/interfaces/controllers/orders.controller.ts - -import { - Body, - Controller, - Get, - Param, - Post, - Query, - UsePipes, - UseGuards, - ValidationPipe, -} from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { ResponseInterceptor } from '../common/response.interceptor'; -import { OrdersService } from './application/orders.service'; -import { FindOrdersDto } from './dto/find-orders.dto'; -import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; - -@ApiTags('Orders') -@ApiBearerAuth() -@UseGuards(JwtAuthGuard) -@Controller('api/v1/orders') -export class OrdersController { - constructor(private readonly ordersService: OrdersService) {} - - @Get('find') - @UsePipes(new ValidationPipe({ transform: true })) - findOrders(@Query() query: FindOrdersDto) { - return this.ordersService.findOrders(query); - } - -} \ No newline at end of file diff --git a/src/orders/orders.module.ts b/src/orders/orders.module.ts deleted file mode 100644 index c6e5eb7..0000000 --- a/src/orders/orders.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -// src/modules/orders/orders.module.ts - -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { OrdersService } from './application/orders.service'; -import { OrdersController } from './orders.controller'; -import { OrdersRepository } from './repositories/orders.repository'; - -@Module({ - imports: [ - TypeOrmModule, - ], - controllers: [OrdersController], - providers: [OrdersService, OrdersRepository], -}) -export class OrdersModule {} diff --git a/src/orders/repositories/orders.repository.ts b/src/orders/repositories/orders.repository.ts index b381769..f817faf 100644 --- a/src/orders/repositories/orders.repository.ts +++ b/src/orders/repositories/orders.repository.ts @@ -1,19 +1,19 @@ -// src/modules/orders/infrastructure/repositories/orders.repository.ts +import { Injectable, HttpException, HttpStatus } from "@nestjs/common"; +import { DataSource } from "typeorm"; +import { InjectDataSource } from "@nestjs/typeorm"; +import { FindOrdersDto } from "../dto/find-orders.dto"; +import { OrderItemDto } from "../dto/OrderItemDto"; +import { CutItemDto } from '../dto/CutItemDto'; + -import { Injectable } from '@nestjs/common'; -import { DataSource } from 'typeorm'; -import { InjectDataSource } from '@nestjs/typeorm'; -import { createOracleConfig } from '../../core/configs/typeorm.oracle.config'; -import { FindOrdersDto } from '../dto/find-orders.dto'; @Injectable() export class OrdersRepository { constructor( - @InjectDataSource('oracle') private readonly dataSource: DataSource + @InjectDataSource("oracle") private readonly dataSource: DataSource ) {} async findOrders(query: FindOrdersDto): Promise { - await this.dataSource.initialize(); const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); @@ -143,11 +143,13 @@ export class OrdersRepository { const conditions: string[] = []; - if (query.storeId) { + if (query.codfilial) { conditions.push(`AND PCPEDC.CODFILIAL = :storeId`); } - if (query.storeStockId) { - conditions.push(`AND EXISTS(SELECT 1 FROM PCPEDI WHERE PCPEDI.NUMPED = PCPEDC.NUMPED AND PCPEDI.CODFILIALRETIRA = :storeStockId)`); + if (query.filialretira) { + conditions.push( + `AND EXISTS(SELECT 1 FROM PCPEDI WHERE PCPEDI.NUMPED = PCPEDC.NUMPED AND PCPEDI.CODFILIALRETIRA = :storeStockId)` + ); } if (query.sellerId) { conditions.push(`AND PCPEDC.CODUSUR = :sellerId`); @@ -159,42 +161,65 @@ export class OrdersRepository { conditions.push(`AND PCPEDC.CODCOB = :billingId`); } if (query.orderId) { - conditions.push(`AND (PCPEDC.NUMPED = :orderId OR PCPEDC.NUMPEDENTFUT = :orderId)`); + conditions.push( + `AND (PCPEDC.NUMPED = :orderId OR PCPEDC.NUMPEDENTFUT = :orderId)` + ); } if (query.invoiceId) { conditions.push(`AND PCPEDC.NUMNOTA = :invoiceId`); } if (query.productId) { - conditions.push(`AND EXISTS(SELECT 1 FROM PCPEDI WHERE PCPEDI.NUMPED = PCPEDC.NUMPED AND PCPEDI.CODPROD = :productId)`); + conditions.push( + `AND EXISTS(SELECT 1 FROM PCPEDI WHERE PCPEDI.NUMPED = PCPEDC.NUMPED AND PCPEDI.CODPROD = :productId)` + ); } if (query.createDateIni) { - conditions.push(`AND PCPEDC.DATA >= TO_DATE(:createDateIni, 'YYYY-MM-DD')`); + conditions.push( + `AND PCPEDC.DATA >= TO_DATE(:createDateIni, 'YYYY-MM-DD')` + ); } if (query.createDateEnd) { - conditions.push(`AND PCPEDC.DATA <= TO_DATE(:createDateEnd, 'YYYY-MM-DD')`); + conditions.push( + `AND PCPEDC.DATA <= TO_DATE(:createDateEnd, 'YYYY-MM-DD')` + ); } if (query.invoiceDateIni) { - conditions.push(`AND PCPEDC.DTFAT >= TO_DATE(:invoiceDateIni, 'YYYY-MM-DD')`); + conditions.push( + `AND PCPEDC.DTFAT >= TO_DATE(:invoiceDateIni, 'YYYY-MM-DD')` + ); } if (query.invoiceDateEnd) { - conditions.push(`AND PCPEDC.DTFAT <= TO_DATE(:invoiceDateEnd, 'YYYY-MM-DD')`); + conditions.push( + `AND PCPEDC.DTFAT <= TO_DATE(:invoiceDateEnd, 'YYYY-MM-DD')` + ); } if (query.shippimentId) { conditions.push(`AND PCPEDC.NUMCAR = :shippimentId`); } if (query.deliveryType) { - const types = query.deliveryType.split(',').map(t => `'${t}'`).join(','); - conditions.push(`AND EXISTS(SELECT 1 FROM PCPEDI WHERE PCPEDI.NUMPED = PCPEDC.NUMPED AND PCPEDI.TIPOENTREGA IN (${types}))`); + const types = query.deliveryType + .split(",") + .map((t) => `'${t}'`) + .join(","); + conditions.push( + `AND EXISTS(SELECT 1 FROM PCPEDI WHERE PCPEDI.NUMPED = PCPEDC.NUMPED AND PCPEDI.TIPOENTREGA IN (${types}))` + ); } if (query.status) { - const statusList = query.status.split(',').map(s => `'${s}'`).join(','); + const statusList = query.status + .split(",") + .map((s) => `'${s}'`) + .join(","); conditions.push(`AND PCPEDC.POSICAO IN (${statusList})`); } if (query.type) { - const types = query.type.split(',').map(t => `'${t}'`).join(','); + const types = query.type + .split(",") + .map((t) => `'${t}'`) + .join(","); conditions.push(`AND PCPEDC.CONDVENDA IN (${types})`); } - if (query.onlyPendentingTransfer === 'S') { + if (query.onlyPendentingTransfer === "S") { conditions.push(` AND NOT EXISTS(SELECT 1 FROM PCNFENT, PCFILIAL WHERE PCFILIAL.CODIGO = PCPEDC.CODFILIAL @@ -202,8 +227,8 @@ export class OrdersRepository { AND PCNFENT.NUMNOTA = PCPEDC.NUMNOTA)`); } - sql += '\n' + conditions.join('\n'); - sql += '\nAND ROWNUM < 5000'; + sql += "\n" + conditions.join("\n"); + sql += "\nAND ROWNUM < 5000"; const orders = await queryRunner.manager.query(sql); return orders; @@ -212,4 +237,132 @@ export class OrdersRepository { await this.dataSource.destroy(); } } -} + + async findInvoice(chavenfe: string): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + + try { + const sql = ` + SELECT + pcnfsaid.codfilial AS "storeId", + pcnfsaid.dtsaida AS "invoiceDate", + pcnfsaid.numped AS "orderId", + pcnfsaid.numnota AS "invoiceId", + pcnfsaid.numtransvenda AS "transactionId", + pcnfsaid.codcli AS "customerId", + pcclient.cliente AS "customer", + pcnfsaid.codusur AS "sellerId", + pcusuari.nome AS "sellerName", + (SELECT SUM(pcmov.qt) FROM pcmov WHERE pcmov.numtransvenda = pcnfsaid.numtransvenda) AS "itensQt", + NULL AS "itens" + FROM pcnfsaid + JOIN pcclient ON pcnfsaid.codcli = pcclient.codcli + JOIN pcusuari ON pcnfsaid.codusur = pcusuari.codusur + WHERE pcnfsaid.chavenfe = '${chavenfe}' + `; + + const invoice = await queryRunner.manager.query(sql); + if (!invoice || invoice.length === 0) { + throw new HttpException('Nota fiscal não foi localizada no sistema', HttpStatus.BAD_REQUEST); + } + + const sqlItem = ` + SELECT + pcmov.codprod AS "productId", + pcprodut.descricao AS "productName", + pcprodut.embalagem AS "package", + pcmov.qt AS "qt", + pcprodut.codauxiliar AS "ean", + pcprodut.multiplo AS "multiple", + pcprodut.tipoproduto AS "productType", + REPLACE( + CASE + WHEN INSTR(pcprodut.URLIMAGEM, ';') > 0 + THEN SUBSTR(pcprodut.URLIMAGEM,1,INSTR(pcprodut.URLIMAGEM, ';') - 1) + WHEN pcprodut.URLIMAGEM IS NOT NULL + THEN pcprodut.URLIMAGEM + ELSE NULL + END, + '167.249.211.178:8001', + '10.1.1.191' + ) AS "image" + FROM pcmov + JOIN pcprodut ON pcmov.codprod = pcprodut.codprod + WHERE pcmov.numtransvenda = ${invoice[0].transactionId} + `; + + const itens = await queryRunner.manager.query(sqlItem); + invoice[0].itens = itens; + + return invoice[0]; + } finally { + await queryRunner.release(); + } + } + + async getItens(orderId: string): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + try { + const sql = `SELECT PCPEDI.CODPROD as "productId" + , PCPRODUT.DESCRICAO as "description" + , PCPRODUT.EMBALAGEM as "pacth" + , NVL(PCPEDI.COMPLEMENTO, + ( SELECT TV7I.COMPLEMENTO FROM PCPEDC, PCPEDC TV7, PCPEDITEMP TV7I + WHERE TV7.NUMPED = PCPEDC.NUMPEDENTFUT + AND PCPEDC.NUMPED = PCPEDI.NUMPED + AND TV7.NUMPEDRCA = TV7I.NUMPEDRCA + AND TV7I.CODPROD = PCPEDI.codprod + AND TV7I.NUMSEQ = PCPEDI.NUMSEQ ) ) as "color" + , PCPEDI.CODFILIALRETIRA as "stockId" + , PCPEDI.QT as "quantity" + , PCPEDI.PVENDA as "salePrice" + , CASE WHEN PCPEDI.TIPOENTREGA = 'RI' THEN 'RETIRA IMEDIATA' + WHEN PCPEDI.TIPOENTREGA = 'RP' THEN 'RETIRA POSTERIOR' + WHEN PCPEDI.TIPOENTREGA = 'EN' THEN 'ENTREGA' + WHEN PCPEDI.TIPOENTREGA = 'EF' THEN 'ENTREGA FUTURA' + END as "deliveryType" + , ( PCPEDI.QT * + PCPEDI.PVENDA ) as "total" + , ( PCPEDI.QT * PCPRODUT.PESOBRUTO ) as "weigth" + , PCDEPTO.DESCRICAO as "department" + , PCMARCA.MARCA as "brand" + FROM PCPEDI, PCPRODUT, PCDEPTO, PCMARCA + WHERE PCPEDI.CODPROD = PCPRODUT.CODPROD + AND PCPRODUT.CODEPTO = PCDEPTO.CODEPTO + AND PCPRODUT.CODMARCA = PCMARCA.CODMARCA + AND PCPEDI.NUMPED = ${orderId}`; + + const itens = await queryRunner.manager.query(sql); + return itens; + } finally { + await queryRunner.release(); + } + } + async getCutItens(orderId: string): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + + try { + const sql = ` + SELECT + PCCORTEI.CODPROD as "productId", + PCPRODUT.DESCRICAO as "description", + PCPRODUT.EMBALAGEM as "pacth", + PCCORTEI.CODFILIAL as "stockId", + (PCCORTEI.QTSEPARADA + PCCORTEI.QTCORTADA) as "saleQuantity", + PCCORTEI.QTCORTADA as "cutQuantity", + PCCORTEI.QTSEPARADA as "separedQuantity" + FROM PCCORTEI + JOIN PCPRODUT ON PCCORTEI.CODPROD = PCPRODUT.CODPROD + WHERE PCCORTEI.NUMPED = ${orderId} + `; + + const itens = await queryRunner.manager.query(sql); + return itens; + } finally { + await queryRunner.release(); + } + } +} \ No newline at end of file