fix: ajuste no endpoint de impressão de pedidos.
This commit is contained in:
58
.github/workflows/publish-sdk.yml
vendored
Normal file
58
.github/workflows/publish-sdk.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Publish SDK to GitHub Packages
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'sdk-v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
registry-url: 'https://npm.pkg.github.com'
|
||||
scope: '@portaljuru'
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd sdk && npm ci
|
||||
|
||||
- name: Build SDK
|
||||
run: cd sdk && npm run build
|
||||
|
||||
- name: Publish to GitHub Packages
|
||||
run: cd sdk && npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: SDK ${{ github.ref }}
|
||||
body: |
|
||||
## SDK Portal Jurunense API
|
||||
|
||||
Nova versão publicada no GitHub Packages.
|
||||
|
||||
### Instalação
|
||||
```bash
|
||||
npm install @portaljuru/api-client
|
||||
```
|
||||
|
||||
Veja o [CHANGELOG](./sdk/CHANGELOG.md) para detalhes.
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
173
SDK_README.md
Normal file
173
SDK_README.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Portal Jurunense API - SDK
|
||||
|
||||
Este projeto agora inclui um **SDK oficial** para consumir a API Portal Jurunense de forma simples e tipada em TypeScript/JavaScript.
|
||||
|
||||
## Localização
|
||||
|
||||
O SDK está localizado no diretório `/sdk` na raiz do projeto.
|
||||
|
||||
## Estrutura do SDK
|
||||
|
||||
```
|
||||
sdk/
|
||||
├── src/ # Código fonte TypeScript
|
||||
│ ├── types/ # Definições de tipos
|
||||
│ ├── client/ # Clientes HTTP
|
||||
│ └── index.ts # Ponto de entrada
|
||||
├── dist/ # Código compilado (gerado)
|
||||
├── examples/ # Exemplos de uso
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── README.md # Documentação completa
|
||||
├── QUICK_START.md # Guia de início rápido
|
||||
├── CONTRIBUTING.md # Guia de contribuição
|
||||
└── PUBLISH.md # Guia de publicação
|
||||
```
|
||||
|
||||
## Scripts Disponíveis
|
||||
|
||||
No diretório raiz do projeto, você pode usar os seguintes scripts para trabalhar com o SDK:
|
||||
|
||||
```bash
|
||||
npm run sdk:install # Instala dependências do SDK
|
||||
npm run sdk:build # Compila o SDK
|
||||
npm run sdk:watch # Compila e observa mudanças
|
||||
npm run sdk:publish # Publica o SDK no NPM
|
||||
```
|
||||
|
||||
## Uso do SDK
|
||||
|
||||
### Instalação (quando publicado)
|
||||
|
||||
```bash
|
||||
npm install @portaljuru/api-client
|
||||
```
|
||||
|
||||
### Exemplo Básico
|
||||
|
||||
```typescript
|
||||
import { PortalJuruClient } from '@portaljuru/api-client';
|
||||
|
||||
const client = new PortalJuruClient({
|
||||
baseURL: 'https://api.portaljuru.com.br'
|
||||
});
|
||||
|
||||
// Fazer login
|
||||
const { data } = await client.auth.login({
|
||||
username: 'usuario',
|
||||
password: 'senha'
|
||||
});
|
||||
|
||||
// Buscar pedidos
|
||||
const orders = await client.orders.findOrders({
|
||||
status: 'processando'
|
||||
});
|
||||
|
||||
// Listar produtos
|
||||
const products = await client.products.listProducts();
|
||||
```
|
||||
|
||||
## Módulos Disponíveis
|
||||
|
||||
O SDK expõe os seguintes módulos:
|
||||
|
||||
- **auth** - Autenticação (login, refresh token, logout)
|
||||
- **logistic** - Logística (expedição, funcionários, entregas, veículos)
|
||||
- **orders** - Pedidos (busca, criação, transferências, notas fiscais)
|
||||
- **products** - Produtos (listagem, busca, validação)
|
||||
- **partners** - Parceiros (CRUD completo)
|
||||
- **dataConsult** - Consulta de dados (clientes, transportadoras, vendedores, lojas)
|
||||
- **ordersPayment** - Pagamentos de pedidos
|
||||
- **crm** - CRM (negociações, ocorrências, tabela de motivos)
|
||||
|
||||
## Tipos TypeScript
|
||||
|
||||
O SDK inclui tipos completos para TypeScript, facilitando o desenvolvimento com autocompletar e validação de tipos.
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
Order,
|
||||
Product,
|
||||
Customer,
|
||||
LoginResponse,
|
||||
ApiResponse
|
||||
} from '@portaljuru/api-client';
|
||||
```
|
||||
|
||||
## Desenvolvimento
|
||||
|
||||
Para desenvolver o SDK localmente:
|
||||
|
||||
1. Entre no diretório do SDK:
|
||||
```bash
|
||||
cd sdk
|
||||
```
|
||||
|
||||
2. Instale as dependências:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Compile o código:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
4. Para desenvolvimento contínuo:
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
## Documentação
|
||||
|
||||
- [README completo do SDK](./sdk/README.md) - Documentação detalhada de todas as funcionalidades
|
||||
- [Guia de Início Rápido](./sdk/QUICK_START.md) - Comece a usar em minutos
|
||||
- [Guia de Contribuição](./sdk/CONTRIBUTING.md) - Como contribuir com o SDK
|
||||
- [Guia de Publicação](./sdk/PUBLISH.md) - Como publicar no NPM
|
||||
|
||||
## Exemplos
|
||||
|
||||
Veja os exemplos práticos em [sdk/examples/](./sdk/examples/):
|
||||
|
||||
- `basic-usage.ts` - Exemplo básico de uso
|
||||
- `logistic-example.ts` - Exemplo de operações logísticas
|
||||
|
||||
## Publicação
|
||||
|
||||
Para publicar uma nova versão do SDK no NPM:
|
||||
|
||||
1. Atualize a versão:
|
||||
```bash
|
||||
cd sdk
|
||||
npm version patch # ou minor/major
|
||||
```
|
||||
|
||||
2. Compile:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. Publique:
|
||||
```bash
|
||||
npm publish --access public
|
||||
```
|
||||
|
||||
Ou use o script do projeto raiz:
|
||||
```bash
|
||||
npm run sdk:publish
|
||||
```
|
||||
|
||||
## Benefícios do SDK
|
||||
|
||||
- Tipagem completa em TypeScript
|
||||
- Autocompletar em IDEs
|
||||
- Tratamento de erros consistente
|
||||
- Gerenciamento automático de tokens
|
||||
- Interface intuitiva e orientada a objetos
|
||||
- Documentação completa com exemplos
|
||||
- Suporte a todas as funcionalidades da API
|
||||
|
||||
## Licença
|
||||
|
||||
MIT - Veja o arquivo [LICENSE](./sdk/LICENSE) para mais detalhes.
|
||||
|
||||
141
SDK_UPDATE_SUMMARY.md
Normal file
141
SDK_UPDATE_SUMMARY.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Resumo da Atualização do SDK - v1.1.0
|
||||
|
||||
## Módulo de Orders - COMPLETO
|
||||
|
||||
O módulo de orders estava **incompleto** na versão 1.0.0. Agora na versão **1.1.0**, todos os 19 endpoints do controller de orders estão implementados no SDK.
|
||||
|
||||
### Endpoints Adicionados ao OrdersClient
|
||||
|
||||
| Método | Endpoint Original | Método no SDK | Status |
|
||||
|--------|------------------|---------------|---------|
|
||||
| GET | `/find` | `findOrders()` | Atualizado |
|
||||
| GET | `/find-by-delivery-date` | `findOrdersByDeliveryDate()` | Já existia |
|
||||
| GET | `/:orderId/checkout` | `getOrderCheckout()` | **NOVO** |
|
||||
| GET | `/invoice/:chavenfe` | `getInvoiceByKey()` | **NOVO** |
|
||||
| GET | `/itens/:orderId` | `getOrderItems()` | **NOVO** |
|
||||
| GET | `/cut-itens/:orderId` | `getCutItems()` | **NOVO** |
|
||||
| GET | `/delivery/:orderId` | `getOrderDelivery()` | **NOVO** |
|
||||
| GET | `/transfer/:orderId` | `getOrderTransfers()` | **NOVO** |
|
||||
| GET | `/status/:orderId` | `getOrderStatus()` | **NOVO** |
|
||||
| GET | `/:orderId/deliveries` | `getOrderDeliveries()` | **NOVO** |
|
||||
| GET | `/leadtime/:orderId` | `getLeadTime()` | Já existia |
|
||||
| POST | `/invoice/check` | `createInvoiceCheck()` | **NOVO** |
|
||||
| GET | `/carriers/:orderId` | `getOrderCarriers()` | **NOVO** |
|
||||
| GET | `/mark/:orderId` | `findOrderMark()` | **NOVO** |
|
||||
| GET | `/marks` | `getAllMarks()` | **NOVO** |
|
||||
| GET | `/marks/search` | `searchMarksByName()` | **NOVO** |
|
||||
| GET | `/transfer-log/:orderId` | `getTransferLog()` | **NOVO** |
|
||||
| GET | `/transfer-log` | `getTransferLogs()` | **NOVO** |
|
||||
| GET | `/completed-deliveries` | `getCompletedDeliveries()` | **NOVO** |
|
||||
|
||||
**Total: 19 endpoints - 100% cobertos**
|
||||
|
||||
## Novo Módulo: DEB (Débitos)
|
||||
|
||||
Foi adicionado um novo módulo completo para operações de débitos:
|
||||
|
||||
### DebClient
|
||||
|
||||
| Método | Endpoint | Descrição |
|
||||
|--------|----------|-----------|
|
||||
| `findByCpfCgcent()` | GET `/api/v1/deb/find-by-cpf` | Busca débitos por CPF/CGCENT |
|
||||
|
||||
## Novos Tipos Adicionados
|
||||
|
||||
### Orders
|
||||
- `OrderItem` - Item do pedido (estrutura completa com 12 campos)
|
||||
- `CutItem` - Item cortado do pedido
|
||||
- `OrderDelivery` - Dados completos de entrega (29 campos)
|
||||
- `DeliveryCompleted` - Entrega realizada (22 campos)
|
||||
- `DeliveryCompletedQuery` - Filtros para buscar entregas concluídas
|
||||
- `OrderStatusDto` - Status do pedido
|
||||
- `InvoiceCheck` - Conferência de nota fiscal
|
||||
- `InvoiceCheckItem` - Item da conferência
|
||||
- `Mark` - Marca de produtos (MARCA, CODMARCA, ATIVO)
|
||||
- `TransferLog` - Log de transferência entre filiais
|
||||
- `TransferLogFilter` - Filtros para logs de transferência
|
||||
- `OrderCheckout` - Fechamento de caixa do pedido
|
||||
|
||||
### Deb
|
||||
- `Deb` - Débito
|
||||
- `FindDebDto` - Filtros para buscar débitos
|
||||
|
||||
## Estatísticas
|
||||
|
||||
### Versão 1.0.0
|
||||
- **6 módulos**: auth, logistic, orders, products, partners, dataConsult, ordersPayment, crm
|
||||
- **Orders**: ~7 métodos
|
||||
|
||||
### Versão 1.1.0
|
||||
- **9 módulos**: auth, logistic, orders, products, partners, dataConsult, ordersPayment, crm, **deb**
|
||||
- **Orders**: **19 métodos** (+12 novos)
|
||||
- **Deb**: 1 método (novo módulo)
|
||||
|
||||
## Exemplos de Uso
|
||||
|
||||
### Buscar Itens do Pedido
|
||||
```typescript
|
||||
const { data: items } = await client.orders.getOrderItems(236001388);
|
||||
console.log(`Pedido tem ${items.length} itens`);
|
||||
```
|
||||
|
||||
### Buscar Entregas Concluídas
|
||||
```typescript
|
||||
const { data: deliveries } = await client.orders.getCompletedDeliveries({
|
||||
startDate: '2024-01-01',
|
||||
endDate: '2024-12-31',
|
||||
driverName: 'João',
|
||||
limit: 50
|
||||
});
|
||||
```
|
||||
|
||||
### Buscar Marcas
|
||||
```typescript
|
||||
const { data: marks } = await client.orders.getAllMarks();
|
||||
const { data: nike } = await client.orders.searchMarksByName('Nike');
|
||||
```
|
||||
|
||||
### Buscar Débitos
|
||||
```typescript
|
||||
const { data: debts } = await client.deb.findByCpfCgcent('12345678900');
|
||||
```
|
||||
|
||||
### Criar Conferência de Nota Fiscal
|
||||
```typescript
|
||||
await client.orders.createInvoiceCheck({
|
||||
transactionId: 123,
|
||||
storeId: 1,
|
||||
invoiceId: 456,
|
||||
startDate: '2024-11-02',
|
||||
endDate: '2024-11-02',
|
||||
userId: 789,
|
||||
itens: [
|
||||
{ productId: 1, quantity: 10, checked: true },
|
||||
{ productId: 2, quantity: 5, checked: true }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Testar localmente**: `npm run sdk:build`
|
||||
2. **Publicar nova versão**:
|
||||
```bash
|
||||
cd sdk
|
||||
npm publish --access public
|
||||
```
|
||||
3. **Atualizar em projetos**: `npm install @portaljuru/api-client@1.1.0`
|
||||
|
||||
## Build Status
|
||||
|
||||
- Compilação bem-sucedida
|
||||
- Sem erros de TypeScript
|
||||
- Todos os tipos exportados corretamente
|
||||
- Pronto para publicação
|
||||
|
||||
---
|
||||
|
||||
**Data:** 02/11/2025
|
||||
**Versão:** 1.1.0
|
||||
**Status:** Completo e testado
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"sdk:install": "cd sdk && npm install",
|
||||
"sdk:build": "cd sdk && npm run build",
|
||||
"sdk:watch": "cd sdk && npm run watch",
|
||||
"sdk:publish": "cd sdk && npm publish --access public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/axios": "^4.0.0",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { StoreDto } from './dto/store.dto';
|
||||
import { SellerDto } from './dto/seller.dto';
|
||||
import { BillingDto } from './dto/billing.dto';
|
||||
import { CustomerDto } from './dto/customer.dto';
|
||||
import { RegionDto } from './dto/region.dto';
|
||||
import { CarrierDto, FindCarriersDto } from './dto/carrier.dto';
|
||||
|
||||
@ApiTags('DataConsult')
|
||||
@@ -103,4 +104,13 @@ export class DataConsultController {
|
||||
return this.dataConsultService.getOrderCarriers(orderId);
|
||||
}
|
||||
|
||||
@Get('regions')
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
//@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Lista todas as regiões cadastradas' })
|
||||
@ApiResponse({ status: 200, description: 'Lista de regiões retornada com sucesso', type: [RegionDto] })
|
||||
async getRegions(): Promise<RegionDto[]> {
|
||||
return this.dataConsultService.getRegions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { SellerDto } from './dto/seller.dto';
|
||||
import { BillingDto } from './dto/billing.dto';
|
||||
import { CustomerDto } from './dto/customer.dto';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
import { RegionDto } from './dto/region.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { DATA_SOURCE } from '../core/constants';
|
||||
|
||||
@@ -217,4 +218,19 @@ async findSellers(): Promise<SellerDto[]> {
|
||||
`;
|
||||
return await this.executeQuery<any[]>(sql, [orderId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca todas as regiões cadastradas
|
||||
*/
|
||||
async findRegions(): Promise<RegionDto[]> {
|
||||
const sql = `
|
||||
SELECT
|
||||
PCREGIAO.NUMREGIAO as "numregiao",
|
||||
PCREGIAO.REGIAO as "regiao"
|
||||
FROM PCREGIAO
|
||||
ORDER BY PCREGIAO.NUMREGIAO
|
||||
`;
|
||||
const results = await this.executeQuery<RegionDto[]>(sql);
|
||||
return results.map(result => new RegionDto(result));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { SellerDto } from './dto/seller.dto';
|
||||
import { BillingDto } from './dto/billing.dto';
|
||||
import { CustomerDto } from './dto/customer.dto';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
import { RegionDto } from './dto/region.dto';
|
||||
import { CarrierDto, FindCarriersDto } from './dto/carrier.dto';
|
||||
import { ILogger } from '../Log/ILogger';
|
||||
import { RedisClientToken } from '../core/configs/cache/redis-client.adapter.provider';
|
||||
@@ -24,6 +25,8 @@ export class DataConsultService {
|
||||
private readonly CUSTOMERS_TTL = 3600;
|
||||
private readonly CARRIERS_CACHE_KEY = 'data-consult:carriers:all';
|
||||
private readonly CARRIERS_TTL = 3600;
|
||||
private readonly REGIONS_CACHE_KEY = 'data-consult:regions';
|
||||
private readonly REGIONS_TTL = 7200;
|
||||
|
||||
constructor(
|
||||
private readonly repository: DataConsultRepository,
|
||||
@@ -205,4 +208,26 @@ export class DataConsultService {
|
||||
throw new HttpException('Erro ao buscar transportadoras do pedido', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter todas as regiões cadastradas
|
||||
* @returns Array de RegionDto
|
||||
*/
|
||||
async getRegions(): Promise<RegionDto[]> {
|
||||
this.logger.log('Buscando todas as regiões');
|
||||
try {
|
||||
return getOrSetCache<RegionDto[]>(
|
||||
this.redisClient,
|
||||
this.REGIONS_CACHE_KEY,
|
||||
this.REGIONS_TTL,
|
||||
async () => {
|
||||
const regions = await this.repository.findRegions();
|
||||
return regions;
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao buscar regiões', error);
|
||||
throw new HttpException('Erro ao buscar regiões', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/data-consult/dto/region.dto.ts
Normal file
23
src/data-consult/dto/region.dto.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* DTO para dados de região
|
||||
*/
|
||||
export class RegionDto {
|
||||
@ApiProperty({
|
||||
description: 'Código da região',
|
||||
example: 1,
|
||||
})
|
||||
numregiao: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nome/descrição da região',
|
||||
example: 'REGIÃO SUL',
|
||||
})
|
||||
regiao: string;
|
||||
|
||||
constructor(partial: Partial<RegionDto>) {
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
||||
|
||||
84
src/orders/dto/DebDto.ts
Normal file
84
src/orders/dto/DebDto.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* DTO para dados de débitos/prestações de clientes
|
||||
*/
|
||||
export class DebDto {
|
||||
@ApiProperty({
|
||||
description: 'Data de emissão da prestação',
|
||||
example: '2024-01-15',
|
||||
type: Date,
|
||||
})
|
||||
dtemissao: Date;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Código da filial',
|
||||
example: '1',
|
||||
})
|
||||
codfilial: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Número da duplicata',
|
||||
example: '12345',
|
||||
})
|
||||
duplic: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Número da prestação',
|
||||
example: '1',
|
||||
})
|
||||
prest: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Código do cliente',
|
||||
example: 1000,
|
||||
})
|
||||
codcli: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nome do cliente',
|
||||
example: 'JOÃO DA SILVA',
|
||||
})
|
||||
cliente: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Código de cobrança',
|
||||
example: 'BL',
|
||||
})
|
||||
codcob: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Descrição da forma de cobrança',
|
||||
example: 'BOLETO',
|
||||
})
|
||||
cobranca: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Data de vencimento',
|
||||
example: '2024-02-15',
|
||||
type: Date,
|
||||
})
|
||||
dtvenc: Date;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Data de pagamento',
|
||||
example: '2024-02-10',
|
||||
type: Date,
|
||||
nullable: true,
|
||||
})
|
||||
dtpag: Date | null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Valor da prestação',
|
||||
example: 150.50,
|
||||
})
|
||||
valor: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Situação da prestação',
|
||||
example: 'PAGO',
|
||||
enum: ['PAGO', 'EM ATRASO', 'A VENCER', 'NENHUM'],
|
||||
})
|
||||
situacao: string;
|
||||
}
|
||||
|
||||
33
src/products/dto/product-detail-query.dto.ts
Normal file
33
src/products/dto/product-detail-query.dto.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* DTO para requisição de detalhes de produtos
|
||||
*/
|
||||
export class ProductDetailQueryDto {
|
||||
@ApiProperty({
|
||||
description: 'Código da região para buscar o preço',
|
||||
example: 1,
|
||||
})
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
numregiao: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Array de códigos de produtos',
|
||||
example: [1, 2, 3],
|
||||
type: [Number],
|
||||
})
|
||||
@IsArray()
|
||||
@IsNotEmpty()
|
||||
codprod: number[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Código da filial',
|
||||
example: '1',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
codfilial: string;
|
||||
}
|
||||
|
||||
55
src/products/dto/product-detail-response.dto.ts
Normal file
55
src/products/dto/product-detail-response.dto.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* DTO para resposta de detalhes de produtos
|
||||
*/
|
||||
export class ProductDetailResponseDto {
|
||||
@ApiProperty({
|
||||
description: 'Código do produto',
|
||||
example: 12345,
|
||||
})
|
||||
codprod: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Descrição completa do produto (com marca)',
|
||||
example: 'PRODUTO EXEMPLO - MARCA EXEMPLO',
|
||||
})
|
||||
descricao: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tipo de embalagem',
|
||||
example: 'UN',
|
||||
})
|
||||
embalagem: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Código auxiliar (código de barras)',
|
||||
example: '7891234567890',
|
||||
})
|
||||
codauxiliar: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nome da marca',
|
||||
example: 'MARCA EXEMPLO',
|
||||
})
|
||||
marca: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Preço de venda do produto',
|
||||
example: 99.90,
|
||||
})
|
||||
preco: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nome da filial',
|
||||
example: 'FILIAL MATRIZ',
|
||||
})
|
||||
filial: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Nome da região',
|
||||
example: 'REGIÃO SUL',
|
||||
})
|
||||
regiao: string;
|
||||
}
|
||||
|
||||
@@ -1,59 +1,78 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { Body, Controller, Get, Param, Post,UseGuards } from '@nestjs/common';
|
||||
import { ProductsService } from './products.service';
|
||||
import { ExposedProduct } from 'src/core/models/exposed-product.model';
|
||||
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
|
||||
import { ExposedProductDto } from './dto/exposed-product.dto';
|
||||
import { ProductValidationDto } from './dto/ProductValidationDto';
|
||||
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
|
||||
import { ApiTags, ApiOperation, ApiParam, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiTags('Produtos')
|
||||
@Controller('api/v1/products')
|
||||
export class ProductsController {
|
||||
constructor(private readonly productsService: ProductsService) {}
|
||||
|
||||
///enpoit produtos-ecommecer
|
||||
@Get('products-ecommerce')
|
||||
@ApiOperation({ summary: 'Lista produtos para o e-commerce' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de produtos retornada com sucesso.',
|
||||
type: ProductEcommerceDto,
|
||||
isArray: true
|
||||
})
|
||||
|
||||
///ENDPOIT DE VALIDAR PRODUTO POR FILTRO
|
||||
@Get('product-validation/:storeId/:filtro')
|
||||
@ApiOperation({ summary: 'Valida produto pelo filtro (código, EAN ou descrição)' })
|
||||
@ApiParam({ name: 'storeId', type: String, description: 'ID da loja' })
|
||||
@ApiParam({ name: 'filtro', type: String, description: 'Filtro de busca (código, EAN ou descrição)' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Produto encontrado com sucesso.',
|
||||
type: ProductValidationDto
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Produto não localizado.' })
|
||||
async productValidation(
|
||||
@Param('storeId') storeId: string,
|
||||
@Param('filtro') filtro: string,
|
||||
): Promise<ProductValidationDto> {
|
||||
return this.productsService.productsValidation(storeId, filtro);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// ENDPOIT PRODUTOS EXPOSTOS
|
||||
@Post('exposed-product')
|
||||
@ApiOperation({ summary: 'Registra produto em exposição' })
|
||||
@ApiBody({ type: ExposedProductDto })
|
||||
@ApiResponse({ status: 201, description: 'Produto exposto registrado com sucesso.' })
|
||||
async exposedProduct(@Body() exposedProduct: ExposedProduct) {
|
||||
return this.productsService.exposedProduct(exposedProduct);
|
||||
}
|
||||
}
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { Body, Controller, Get, Param, Post,UseGuards } from '@nestjs/common';
|
||||
import { ProductsService } from './products.service';
|
||||
import { ExposedProduct } from 'src/core/models/exposed-product.model';
|
||||
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
|
||||
import { ExposedProductDto } from './dto/exposed-product.dto';
|
||||
import { ProductValidationDto } from './dto/ProductValidationDto';
|
||||
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
|
||||
import { ApiTags, ApiOperation, ApiParam, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ProductDetailQueryDto } from './dto/product-detail-query.dto';
|
||||
import { ProductDetailResponseDto } from './dto/product-detail-response.dto';
|
||||
|
||||
|
||||
//@ApiBearerAuth()
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
@ApiTags('Produtos')
|
||||
@Controller('api/v1/products')
|
||||
export class ProductsController {
|
||||
constructor(private readonly productsService: ProductsService) {}
|
||||
|
||||
///enpoit produtos-ecommecer
|
||||
@Get('products-ecommerce')
|
||||
@ApiOperation({ summary: 'Lista produtos para o e-commerce' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de produtos retornada com sucesso.',
|
||||
type: ProductEcommerceDto,
|
||||
isArray: true
|
||||
})
|
||||
|
||||
///ENDPOIT DE VALIDAR PRODUTO POR FILTRO
|
||||
@Get('product-validation/:storeId/:filtro')
|
||||
@ApiOperation({ summary: 'Valida produto pelo filtro (código, EAN ou descrição)' })
|
||||
@ApiParam({ name: 'storeId', type: String, description: 'ID da loja' })
|
||||
@ApiParam({ name: 'filtro', type: String, description: 'Filtro de busca (código, EAN ou descrição)' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Produto encontrado com sucesso.',
|
||||
type: ProductValidationDto
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Produto não localizado.' })
|
||||
async productValidation(
|
||||
@Param('storeId') storeId: string,
|
||||
@Param('filtro') filtro: string,
|
||||
): Promise<ProductValidationDto> {
|
||||
return this.productsService.productsValidation(storeId, filtro);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// ENDPOIT PRODUTOS EXPOSTOS
|
||||
@Post('exposed-product')
|
||||
@ApiOperation({ summary: 'Registra produto em exposição' })
|
||||
@ApiBody({ type: ExposedProductDto })
|
||||
@ApiResponse({ status: 201, description: 'Produto exposto registrado com sucesso.' })
|
||||
async exposedProduct(@Body() exposedProduct: ExposedProduct) {
|
||||
return this.productsService.exposedProduct(exposedProduct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint para buscar detalhes de produtos com preço e estoque
|
||||
*/
|
||||
@Post('product-details')
|
||||
@ApiOperation({ summary: 'Busca detalhes de produtos com preço e estoque' })
|
||||
@ApiBody({ type: ProductDetailQueryDto })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de produtos com detalhes retornada com sucesso.',
|
||||
type: ProductDetailResponseDto,
|
||||
isArray: true
|
||||
})
|
||||
@ApiResponse({ status: 400, description: 'Parâmetros inválidos.' })
|
||||
async getProductDetails(@Body() query: ProductDetailQueryDto): Promise<ProductDetailResponseDto[]> {
|
||||
return this.productsService.getProductDetails(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +1,146 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { ExposedProduct } from 'src/core/models/exposed-product.model';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ProductValidationDto } from './dto/ProductValidationDto';
|
||||
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
|
||||
import { ResultModel } from 'src/shared/ResultModel';
|
||||
|
||||
@Injectable()
|
||||
export class ProductsService {
|
||||
constructor(
|
||||
@InjectDataSource("oracle") private readonly dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async productsValidation(storeId: string, filtro: string): Promise<ProductValidationDto> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
|
||||
try {
|
||||
const sql = `SELECT PCPRODUT.DESCRICAO as "descricao"
|
||||
,PCPRODUT.CODPROD as "codigoProduto"
|
||||
,PCPRODUT.CODAUXILIAR as "codigoAuxiliar"
|
||||
,PCMARCA.MARCA as "marca"
|
||||
,REPLACE(NVL(URLIMAGEM, 'http://10.1.1.191/si.png'), 'http://167.249.211.178:8001/', 'http://10.1.1.191/') as "images"
|
||||
,CASE WHEN PCPRODUT.TIPOPRODUTO = 'A' THEN 'AUTOSSERVICO'
|
||||
WHEN PCPRODUT.TIPOPRODUTO = 'S' THEN 'SHOWROOM'
|
||||
WHEN PCPRODUT.TIPOPRODUTO = 'E' THEN 'ELETROMOVEIS'
|
||||
ELSE 'OUTROS' END as "tipoProduto"
|
||||
,PCTABPR.PVENDA1 as "precoVenda"
|
||||
,( PCEST.QTESTGER - PCEST.QTRESERV - PCEST.QTBLOQUEADA ) as "qtdeEstoqueLoja"
|
||||
,( SELECT ( ESTOQUE_CD.QTESTGER - ESTOQUE_CD.QTRESERV - ESTOQUE_CD.QTBLOQUEADA )
|
||||
FROM PCEST ESTOQUE_CD
|
||||
WHERE ESTOQUE_CD.CODPROD = PCPRODUT.CODPROD
|
||||
AND ESTOQUE_CD.CODFILIAL = 6 ) as "qtdeEstoqueCD"
|
||||
FROM PCPRODUT, PCEST, PCTABPR, PCMARCA
|
||||
WHERE PCPRODUT.CODPROD = PCEST.CODPROD
|
||||
AND PCPRODUT.CODPROD = PCTABPR.CODPROD
|
||||
AND PCPRODUT.CODMARCA = PCMARCA.CODMARCA
|
||||
AND PCTABPR.NUMREGIAO = 1
|
||||
AND PCEST.CODFILIAL = '${storeId}'
|
||||
AND ( PCPRODUT.CODAUXILIAR = REGEXP_REPLACE('${filtro}', '[^0-9]', '') OR
|
||||
PCPRODUT.CODPROD = REGEXP_REPLACE('${filtro}', '[^0-9]', '') OR
|
||||
PCPRODUT.DESCRICAO LIKE '%'||'${filtro}'||'%' )`;
|
||||
|
||||
const products = await queryRunner.manager.query(sql);
|
||||
|
||||
if (products.length === 0) {
|
||||
throw new HttpException('Produto não localizado!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
const product = products[0];
|
||||
|
||||
if (!product.images) {
|
||||
product.images = [];
|
||||
} else {
|
||||
product.images = product.images.includes(';')
|
||||
? product.images.split(';')
|
||||
: [product.images];
|
||||
}
|
||||
|
||||
return product;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
async exposedProduct(product: ExposedProduct) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const sqlInsert = `INSERT INTO ESTPRODUTOEXPOSICAO ( CODFILIAL, DATA, CODAUXILIAR, CODFUNC )
|
||||
VALUES ( '${product.storeId}', TRUNC(SYSDATE), ${product.ean}, ${product.userId})`;
|
||||
await queryRunner.query(sqlInsert);
|
||||
await queryRunner.commitTransaction();
|
||||
return { message: 'Registro incluído com sucesso!' };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
async getProductsEcommerce(): Promise<ProductEcommerceDto[]> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
|
||||
try {
|
||||
const sql = `SELECT P.CODPROD as "productIdErp"
|
||||
,P.VTEXSKUID as "productId"
|
||||
,ROUND(P.PVENDA,2) as "price"
|
||||
,ROUND(P.PRECOKIT,2) as "priceKit"
|
||||
FROM ESVPRODUTOSECOMMERCE P
|
||||
WHERE P.VTEXSKUID > 0
|
||||
AND P.CODPROD IN (52057, 33702, 46410, 24518, 25816)`;
|
||||
|
||||
const products = await queryRunner.query(sql);
|
||||
return products;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { ExposedProduct } from 'src/core/models/exposed-product.model';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ProductValidationDto } from './dto/ProductValidationDto';
|
||||
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
|
||||
import { ProductDetailQueryDto } from './dto/product-detail-query.dto';
|
||||
import { ProductDetailResponseDto } from './dto/product-detail-response.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ProductsService {
|
||||
constructor(
|
||||
@InjectDataSource("oracle") private readonly dataSource: DataSource
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Valida e busca informações de um produto por código ou descrição
|
||||
* @param storeId - Código da filial
|
||||
* @param filtro - Filtro de busca (código auxiliar, código produto ou descrição)
|
||||
* @returns Dados do produto encontrado com estoque e preço
|
||||
* @throws HttpException quando produto não é encontrado
|
||||
*/
|
||||
async productsValidation(storeId: string, filtro: string): Promise<ProductValidationDto> {
|
||||
const sql = `SELECT PCPRODUT.DESCRICAO as "descricao"
|
||||
,PCPRODUT.CODPROD as "codigoProduto"
|
||||
,PCPRODUT.CODAUXILIAR as "codigoAuxiliar"
|
||||
,PCMARCA.MARCA as "marca"
|
||||
,REPLACE(NVL(URLIMAGEM, 'http://10.1.1.191/si.png'), 'http://167.249.211.178:8001/', 'http://10.1.1.191/') as "images"
|
||||
,CASE WHEN PCPRODUT.TIPOPRODUTO = 'A' THEN 'AUTOSSERVICO'
|
||||
WHEN PCPRODUT.TIPOPRODUTO = 'S' THEN 'SHOWROOM'
|
||||
WHEN PCPRODUT.TIPOPRODUTO = 'E' THEN 'ELETROMOVEIS'
|
||||
ELSE 'OUTROS' END as "tipoProduto"
|
||||
,PCTABPR.PVENDA1 as "precoVenda"
|
||||
,( PCEST.QTESTGER - PCEST.QTRESERV - PCEST.QTBLOQUEADA ) as "qtdeEstoqueLoja"
|
||||
,( SELECT ( ESTOQUE_CD.QTESTGER - ESTOQUE_CD.QTRESERV - ESTOQUE_CD.QTBLOQUEADA )
|
||||
FROM PCEST ESTOQUE_CD
|
||||
WHERE ESTOQUE_CD.CODPROD = PCPRODUT.CODPROD
|
||||
AND ESTOQUE_CD.CODFILIAL = 6 ) as "qtdeEstoqueCD"
|
||||
FROM PCPRODUT, PCEST, PCTABPR, PCMARCA
|
||||
WHERE PCPRODUT.CODPROD = PCEST.CODPROD
|
||||
AND PCPRODUT.CODPROD = PCTABPR.CODPROD
|
||||
AND PCPRODUT.CODMARCA = PCMARCA.CODMARCA
|
||||
AND PCTABPR.NUMREGIAO = 1
|
||||
AND PCEST.CODFILIAL = :storeId
|
||||
AND ( PCPRODUT.CODAUXILIAR = REGEXP_REPLACE(:filtro1, '[^0-9]', '') OR
|
||||
PCPRODUT.CODPROD = REGEXP_REPLACE(:filtro2, '[^0-9]', '') OR
|
||||
PCPRODUT.DESCRICAO LIKE '%'||:filtro3||'%' )`;
|
||||
|
||||
const products = await this.dataSource.query(sql, [storeId, filtro, filtro, filtro]);
|
||||
|
||||
if (products.length === 0) {
|
||||
throw new HttpException('Produto não localizado!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
const product = products[0];
|
||||
|
||||
if (!product.images) {
|
||||
product.images = [];
|
||||
} else {
|
||||
product.images = product.images.includes(';')
|
||||
? product.images.split(';')
|
||||
: [product.images];
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra produto exposto em showroom
|
||||
* @param product - Dados do produto exposto (storeId, ean, userId)
|
||||
* @returns Mensagem de sucesso
|
||||
* @throws Lança exceção em caso de erro na transação
|
||||
*/
|
||||
async exposedProduct(product: ExposedProduct) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const sqlInsert = `INSERT INTO ESTPRODUTOEXPOSICAO ( CODFILIAL, DATA, CODAUXILIAR, CODFUNC )
|
||||
VALUES ( :storeId, TRUNC(SYSDATE), :ean, :userId )`;
|
||||
await queryRunner.query(sqlInsert, [product.storeId, product.ean, product.userId]);
|
||||
await queryRunner.commitTransaction();
|
||||
return { message: 'Registro incluído com sucesso!' };
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca produtos disponíveis para e-commerce
|
||||
* @returns Lista de produtos com preços para e-commerce
|
||||
*/
|
||||
async getProductsEcommerce(): Promise<ProductEcommerceDto[]> {
|
||||
const sql = `SELECT P.CODPROD as "productIdErp"
|
||||
,P.VTEXSKUID as "productId"
|
||||
,ROUND(P.PVENDA,2) as "price"
|
||||
,ROUND(P.PRECOKIT,2) as "priceKit"
|
||||
FROM ESVPRODUTOSECOMMERCE P
|
||||
WHERE P.VTEXSKUID > 0
|
||||
AND P.CODPROD IN (52057, 33702, 46410, 24518, 25816)`;
|
||||
|
||||
const products = await this.dataSource.query(sql);
|
||||
return products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca detalhes de produtos com preço integrado com região
|
||||
* @param query - Parâmetros de busca (codprod, numregiao, codfilial)
|
||||
* @returns Lista de produtos com detalhes
|
||||
*/
|
||||
async getProductDetails(query: ProductDetailQueryDto): Promise<ProductDetailResponseDto[]> {
|
||||
const { numregiao, codprod, codfilial } = query;
|
||||
|
||||
const placeholders = codprod.map((_, index) => `:codprod${index}`).join(',');
|
||||
|
||||
const sql = `
|
||||
SELECT
|
||||
PCPRODUT.CODPROD AS "codprod",
|
||||
PCPRODUT.DESCRICAO || ' - ' || PCMARCA.MARCA AS "descricao",
|
||||
PCPRODUT.EMBALAGEM AS "embalagem",
|
||||
PCPRODUT.CODAUXILIAR AS "codauxiliar",
|
||||
PCMARCA.MARCA AS "marca",
|
||||
(SELECT PCTABPR.PVENDA1
|
||||
FROM PCTABPR
|
||||
WHERE PCTABPR.CODPROD = PCPRODUT.CODPROD
|
||||
AND PCTABPR.NUMREGIAO = :numregiao1) AS "preco",
|
||||
(SELECT FANTASIA
|
||||
FROM PCFILIAL F
|
||||
WHERE CODIGO = :codfilial) AS "filial",
|
||||
(SELECT REGIAO
|
||||
FROM PCREGIAO
|
||||
WHERE NUMREGIAO = :numregiao2) AS "regiao"
|
||||
FROM PCPRODUT
|
||||
LEFT JOIN PCMARCA ON PCPRODUT.CODMARCA = PCMARCA.CODMARCA
|
||||
WHERE PCPRODUT.CODPROD IN (${placeholders})
|
||||
`;
|
||||
|
||||
const params = [numregiao, codfilial, numregiao, ...codprod];
|
||||
const products = await this.dataSource.query(sql, params);
|
||||
return products;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user