diff --git a/.editorconfig b/.editorconfig index b16ca2c..9142239 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,4 +10,4 @@ trim_trailing_whitespace = true insert_final_newline = true [*.md] -trim_trailing_whitespace = false \ No newline at end of file +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json index 35fb922..b49c84d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,11 +6,12 @@ "extends": [ "standard", "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaVersion": 12, + "ecmaVersion": "latest", "sourceType": "module" }, "plugins": ["@typescript-eslint"], @@ -29,6 +30,12 @@ "rules": { "camelcase": "off" } + }, + { + "files": ["*"], + "rules": { + "space-before-function-paren": "off" + } } ] } diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..373edcc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: [AlexandreBellas] +ko_fi: alebatistella +custom: ["https://www.paypal.com/donate/?hosted_button_id=G2NJKZ5MUMKBS"] diff --git a/.github/workflows/on-commit-test.yaml b/.github/workflows/on-commit-test.yaml new file mode 100644 index 0000000..4618f94 --- /dev/null +++ b/.github/workflows/on-commit-test.yaml @@ -0,0 +1,21 @@ +name: On commit => execute test workflow + +on: push + +jobs: + unit-feature-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build project + run: npm run build + + - name: Execute tests + run: npm run test diff --git a/.prettierrc.json b/.prettierrc.json index 68c8428..855a65a 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,6 +1,6 @@ { - "trailingComma": "none", - "tabWidth": 2, - "semi": false, - "singleQuote": true -} + "trailingComma": "none", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a2ca4f3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: node_js -node_js: - - 14.18.1 -cache: npm -install: - - npm install - - npm ci -script: - - npm run format - - npm run lint - - npm run build - - npm run test:coveralls -deploy: - provider: npm - skip_cleanup: true - email: alexandre.bellas@gmail.com - tag: next - api_key: $NPM_TOKEN - on: - branch: main diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aefbb7d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,32 @@ +{ + "material-icon-theme.folders.associations": { + "@shared": "Shared", + "exceptions": "Error" + }, + "cSpell.words": [ + "aliquota", + "Amazônia", + "borderô", + "Borderos", + "borderôs", + "Contabeis", + "Contábeis", + "Contabil", + "contábil", + "CONTRAN", + "customizado", + "Customizados", + "Eletrônicas", + "Frotista", + "ICMS", + "ICMSST", + "ICRT", + "intermediador", + "nfces", + "Parcelada", + "pickone", + "Rastreamento", + "sublimite", + "SUFRAMA" + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..934ffbd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contribuição ao projeto + +Agradeço muito o interesse de apoio ao projeto. Para uma contribuição saudável e +inclusiva, devemos manter um padrão de execução. + +## Como começar a contribuição + +1. Crie uma _issue_ explicando o problema e o desejo de alteração; +2. Após receber um retorno validando o problema e permitindo a alteração, faça + um novo `fork` do repositório; +3. Implemente as mudanças ou novas funcionalidades que deseja realizar em seu + `fork`; +4. Crie um novo _pull request_ para a _branch_ `develop` do repositório oficial. + +## Padrões de projeto + +- Todas as variáveis e nomes de funções devem estar em `camelCase`; +- Todas as classes devem estar em `PascalCase`; +- Todas as entidades devem seguir o padrão: + 1. Uma pasta para cada entidade; + 2. Um arquivo para cada rota disponibilizada em relação aos envios/retornos; + 3. Um arquivo principal de declaração e utilização da entidade; + 4. Teste unitário de chamada e tipagem de retorno. +- Deve-se manter o padrão do código baseado nas configurações do **prettier** e + do **eslint** do projeto. + +### Mensagens de _commits_ + +Siga o padrão [_conventional commits_](https://www.conventionalcommits.org/en/v1.0.0/). + +## Adicionar uma nova linguagem de programação ao projeto + +1. Crie uma _issue_ anunciando o desejo de trabalhar em uma nova linguagem de + programação; +2. A sua _issue_ será fixada para que outras pessoas possam acompanhar e apoiar + a sua implementação; +3. Realize a sua implementação em uma nova pasta na raiz do projeto com o nome + da linguagem de programação em letras minúsculas (por exemplo: Java seria + criado como `java`, Elixir seria criado como `elixir`, etc); +4. Crie um _pull request_ para a _branch_ `develop` do repositório oficial a + cada nova entidade implementada. + +A ideia é ter o pacote já inicializado e disponibilizado mesmo que nem todas as +entidades estejam disponíveis. Isso permite uma contribuição mais facilitada de +outros desenvolvedores. diff --git a/README.md b/README.md index ebe27f8..fdb40f8 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,19 @@ [![install size](https://packagephobia.com/badge?p=bling-erp-api)](https://packagephobia.com/result?p=bling-erp-api) [![code coverage](https://coveralls.io/repos/github/AlexandreBellas/bling-erp-api/badge.svg?branch=main)](https://coveralls.io/github/AlexandreBellas/bling-erp-api?branch=main) -> ### A biblioteca atualmente usa a API v2 do Bling. Uma nova versão para uso da API v3 está em desenvolvimento no momento, com previsão de ser disponibilizada entre Dezembro/2023 e Janeiro/2024. +Pacote de integração com a [API v3 do ERP Bling](https://developer.bling.com.br). +O mais completo existente (e se não é, será). -Pacote de integração com a [API do ERP Bling](https://ajuda.bling.com.br/hc/pt-br/categories/360002186394-API-para-Desenvolvedores). O mais completo existente (e se não é, será). -Disponível também para **Typescript**. +Disponível para: + +- [x] JavaScript +- [x] TypeScript +- [ ] PHP (em breve) +- [ ] C# (em breve) + +**Atenção**: a versão 5.0.0+ do `bling-erp-api` utiliza a API v3 do Bling. Caso +deseje utilizar a API v2 do Bling, +[utilize a versão 4.0.0](https://github.com/AlexandreBellas/bling-erp-api/tree/v4.0.0). ## Instalação @@ -22,102 +31,99 @@ npm i bling-erp-api ### CommonJS ```js -const { Bling } = require('bling-erp-api') +const Bling = require('bling-erp-api') ``` ### ES6 ```ts -import { Bling } from 'bling-erp-api' +import Bling from 'bling-erp-api' ``` ## Criação de uma nova conexão -Para criar uma conexão ao serviço do Bling, basta instanciar o objeto com a [API -key](https://ajuda.bling.com.br/hc/pt-br/articles/360046937853-Introdu%C3%A7%C3%A3o-para-a-API-do-Bling-para-desenvolvedores-) em -seu construtor. +Para criar uma conexão ao serviço do Bling, basta instanciar o objeto com a [API key](https://developer.bling.com.br/autenticacao) em seu construtor. ```js const apiKey = 'sua_api_key' const blingConnection = new Bling(apiKey) ``` +Vale destacar que o fluxo de criação e autorização do aplicativo **não é feito +pela biblioteca**. Ou seja, a biblioteca somente recebe o `access_token` gerado +a partir do _endpoint_ `/token`. [Veja a referência](https://developer.bling.com.br/aplicativos#tokens-de-acesso). + +Para entender na prática como a autenticação citada acima funciona, [veja o +projeto de demonstração](https://github.com/AlexandreBellas/bling-erp-api/tree/main/demo). + ## Entidades disponíveis -As entidades atualmente permitidas para interação são: - -- Borderos (`.borderos()`) -- Campos customizados (`.customizedFields()` ou `.camposCustomizados()`) -- Categorias (`.categories()` ou `.categorias()`) -- Categorias Loja (`.shopCategories()` ou `.categoriasLoja()`) -- Contatos (`.contacts()` ou `.contatos()`) -- Contas a pagar (`.billsToPay()` ou `.contasAPagar()`) -- Contas a receber (`.billsToReceive()` ou `.contasAReceber()`) -- Contratos (`.contracts()` ou `.contratos()`) -- CTes (`.ctes()`) -- Depósitos (`.deposits()` ou `.depositos()`) -- Formas de pagamento (`.paymentMethods()` ou `.formasDePagamento()`) -- Grupo de produtos (`.productGroups()` ou `.grupoDeProdutos()`) -- NFCes (`.nfces()`) -- Notas fiscais (`.invoices()` ou `.notasFiscais()`) -- Notas de serviço (`.serviceInvoices()` ou `.notasServicos()`) -- Pedidos (`.orders()` ou `.pedidos()`) -- Pedidos de compra (`.purchaseOrders()` ou `.pedidosDeCompra()`) -- Produtos (`.products()` ou `.produtos()`) -- Propostas comerciais (`.commercialProposals()` ou `.propostasComerciais()`) - -Ainda estão em desenvolvimento as entidades: - -- Logística -- Ordem de produção -- Produto Fornecedores -- Produto Loja - -Adicionaremos as restantes de acordo com as _releases_. - -## Métodos permitidos - -- `all()`: retorna todos os registros da entidade -- `find()`: retorna um registro da entidade desejada através de seu `id` ou - `codigo` -- `findBy()`: retorna os registros da entidade **que se adequem aos filtros - passados** -- `create()`: cria um registro da entidade -- `update()`: atualiza um registro da entidade a partir de seu `id` ou - `codigo` -- `delete()`: remove um registro da entidade a partir de seu `id` ou - `codigo` - -Nem todas as entidades possuem interação com todos os métodos (de acordo com a -documentação da API do Bling). Ao utilizar o pacote e estar no VSCode, se o -desenvolvedor utilizar intelliSense ao programar, os métodos permitidos -aparecerão automaticamente após usar o atalho `Ctrl` + `Barra de espaço`. +Todas as entidades do Bling atualmente são permitidas para interação. São elas: + +- [x] Borderos (`.borderos`) +- [x] Campos customizados (`.camposCustomizados`) +- [x] Categorias - Lojas (`.categoriasLojas`) +- [x] Categorias - Produtos (`.categoriasProdutos`) +- [x] Categorias - Receitas e Despesas (`.categoriasReceitasDespesas`) +- [x] Contas a Pagar (`.contasPagar`) +- [x] Contas a Receber (`.contasReceber`) +- [x] Contas Contábeis (`.contasContabeis`) +- [x] Contatos (`.contatos`) +- [x] Contatos - Tipos (`.contatosTipos`) +- [x] Contratos (`.contratos`) +- [x] Depósitos (`.depositos`) +- [x] Empresas (`.empresas`) +- [x] Estoques (`.estoques`) +- [x] Formas de pagamento (`.formasDePagamento`) +- [x] Homologação (`.homologacao`) +- [x] Logísticas (`.logisticas`) +- [x] Logísticas - Etiquetas (`.logisticasEtiquetas`) +- [x] Logísticas - Objetos (`.logisticasObjetos`) +- [x] Logísticas - Serviços (`.logisticasServicos`) +- [x] Naturezas de Operações (`.naturezasDeOperacoes`) +- [x] Notas Fiscais de Consumidor Eletrônicas (`.nfces`) +- [x] Notas Fiscais de Serviço Eletrônicas (`.nfses`) +- [x] Notas Fiscais Eletrônicas (`.nfes`) +- [x] Notificações (`.notificacoes`) +- [x] Pedidos - Compras (`.pedidosCompras`) +- [x] Pedidos - Vendas (`.pedidosVendas`) +- [x] Produtos (`.produtos`) +- [x] Produtos - Estruturas (`.produtosEstruturas`) +- [x] Produtos - Fornecedores (`.produtosFornecedores`) +- [x] Produtos - Lojas (`.produtosLojas`) +- [x] Produtos - Variações (`.produtosVariacoes`) +- [x] Situações (`.situacoes`) +- [x] Situações - Módulos (`.situacoesModulos`) +- [x] Situações - Transições (`.situacoesTransicoes`) +- [x] Usuários (`.usuarios`) +- [x] Vendedores (`.vendedores`) ## Exemplo de uso -Para listar todos os produtos, basta executar: +Para listar seus produtos, basta executar: ```js -// Também disponível pelo método: -// import { Bling } from 'bling-erp-api' -const { Bling } = require('bling-erp-api') +// Também disponível como: +// import Bling from 'bling-erp-api' +const Bling = require('bling-erp-api') const apiKey = 'sua_api_key' const blingConnection = new Bling(apiKey) -const products = await blingConnection.products().all() +const products = await blingConnection.produtos.get() console.log(products) ``` -## Executando testes automatizados -Para isso, faça o clone do projeto e execute +## Executando os testes do projeto + +Faça o clone do projeto, instale as dependências e execute: ```bash npm run test ``` -## Contribuição +## Recursos -Basta fazer um _fork_ do projeto e abrir novos _Pull Requests_ ou interagir -conosco abrindo _issues_ sobre os problemas encontrados. +- [Guia de contribuição](https://github.com/AlexandreBellas/bling-erp-api/blob/v5.0.0/CONTRIBUTING.md) +- [Apoie o projeto](https://www.paypal.com/donate/?hosted_button_id=G2NJKZ5MUMKBS) diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 4fedde6..0000000 --- a/commitlint.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { extends: ['@commitlint/config-conventional'] } diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..1060f29 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,27 @@ +# Projeto de demonstração + +## Aplicativo no Bling + +Crie um aplicativo para teste [como esta página descreve](https://developer.bling.com.br/aplicativos#introdu%C3%A7%C3%A3o). + +É obrigatório que você crie com a URL de redirecionamento como `http://localhost:3000/auth`. + +Além disso, para fins de teste, conceda a permissão para listar produtos. + +## Execução + +Inicie o servidor executando: + +```bash +npm i +npm run dev +``` + +Abra o arquivo `index.html` e siga o que é apresentado. + +### Passo a passo + +1. Insira o _client ID_ e o _client secret_; +2. Conceda as permissões pedidas; +3. O _token_ de acesso aparecerá em seu navegador em formato JSON. Além disso, + será feita uma listagem de seus produtos ilustrando o uso da biblioteca. diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..9572445 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,19 @@ + + + + Demo Bling ERP API + + +
+
+
+
+
+
+
+

+
+ +
+ + diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..12e01dd --- /dev/null +++ b/demo/package.json @@ -0,0 +1,21 @@ +{ + "name": "demo", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc", + "dev": "ts-node-dev --inspect --transpile-only --ignore-watch node_modules server.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "express": "^4.18.2" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "typescript": "^5.3.3" + } +} \ No newline at end of file diff --git a/demo/server.ts b/demo/server.ts new file mode 100644 index 0000000..8f51f87 --- /dev/null +++ b/demo/server.ts @@ -0,0 +1,53 @@ +import Bling from 'bling-erp-api' +import express, { Request, Response } from 'express' +import fs from 'fs' + +const tmpFile = './access.json' +const app = express() +app.use(express.urlencoded({ extended: true })) + +app.post('/', (req: Request, res: Response) => { + const { client_id: clientId, client_secret: clientSecret } = req.body + const baseUrl = 'https://www.bling.com.br' + const endpoint = 'Api/v3/oauth/authorize' + const state = '1234567890' + const redirectUri = 'http://localhost:3000/auth' + + fs.writeFileSync(tmpFile, JSON.stringify({ clientId, clientSecret })) + + res.redirect( + `${baseUrl}/${endpoint}?response_type=code&client_id=${clientId}&state=${state}&redirect_uri=${redirectUri}` + ) +}) + +app.get('/auth', (req: Request, res: Response) => { + const { code } = req.query + + const { clientId, clientSecret } = JSON.parse( + fs.readFileSync(tmpFile).toString() + ) + fs.rmSync(tmpFile) + + const authKey = `${clientId}:${clientSecret}` + + fetch('https://www.bling.com.br/Api/v3/oauth/token', { + method: 'POST', + body: `grant_type=authorization_code&code=${code}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${btoa(authKey)}` + } + }).then((response) => { + response.json().then((content) => { + const bling = new Bling(content.access_token) + bling.produtos.get().then((products) => { + res.json({ + ...content, + products + }) + }) + }) + }) +}) + +app.listen(3000, () => 'server running on port 3000') diff --git a/demo/tsconfig.json b/demo/tsconfig.json new file mode 100644 index 0000000..f733fce --- /dev/null +++ b/demo/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "outDir": "./lib", + "rootDir": "." + } +} diff --git a/jest.config.json b/jest.config.json deleted file mode 100644 index b2e6204..0000000 --- a/jest.config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "transform": { - "^.+\\.(t|j)sx?$": "ts-jest" - }, - "testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], - "transformIgnorePatterns": [ - "/node_modules/", - "\\.pnp\\.[^\\\/]+$" - ], - "testPathIgnorePatterns": [ - "/test/config/", - "/test/generators/" - ], - "maxWorkers": 1, - "maxConcurrency": 2 -} diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..303b5c3 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,22 @@ +import type { Config } from 'jest' + +const config: Config = { + transform: { + '^.+\\.(t|j)sx?$': 'ts-jest' + }, + testRegex: '(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + transformIgnorePatterns: [ + '/node_modules/', + '\\.pnp\\.[^\\/]+$', + '/coverage/' + ], + testPathIgnorePatterns: ['/lib/'], + maxWorkers: 1, + maxConcurrency: 2, + collectCoverageFrom: ['**/*.(t|j)s'], + coveragePathIgnorePatterns: ['/coverage/', 'jest.config.ts', '/lib/'], + coverageDirectory: './coverage' +} + +export default config diff --git a/package.json b/package.json index be1fa4b..2c0840f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bling-erp-api", - "version": "4.0.0", - "description": "Pacote de interação com a REST API do serviço Bling ERP", + "version": "5.0.0", + "description": "Pacote de integração com a API do Bling ERP", "main": "lib/bling.js", "directories": { "test": "test" @@ -10,25 +10,30 @@ "lib/**/*" ], "scripts": { - "build": "tsc", + "build": "tsc --project tsconfig.build.json", "format": "prettier --write 'src/**/*.ts'", "lint": "eslint --fix 'src/**/*.ts'", - "test": "npm run build && jest --config jest.config.json", - "test:coveralls": "jest --config jest.config.json --coverage && coveralls < coverage/lcov.info", - "prepare": "npm run build", - "prepublishOnly": "npm test && npm run lint", - "preversion": "npm run format", - "version": "npm run lint && git add -A src", - "postversion": "git push && git push --tags" + "test": "npm run build && jest --config jest.config.ts", + "test:coverage": "jest --config jest.config.ts --coverage" }, "repository": { "type": "git", "url": "git+https://github.com/AlexandreBellas/bling-erp-api.git" }, "keywords": [ - "bling", + "javascript", + "api", + "php", + "typescript", + "csharp", + "integration", + "js", "erp", - "api" + "ts", + "nfe", + "sefaz", + "bling", + "bling-erp" ], "author": "AlexandreBellas; vitor-san", "license": "ISC", @@ -36,63 +41,32 @@ "url": "https://github.com/AlexandreBellas/bling-erp-api/issues" }, "homepage": "https://github.com/AlexandreBellas/bling-erp-api#readme", - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "*.js": [ - "prettier --write", - "eslint --fix", - "git add" - ], - "*.ts": [ - "prettier --write", - "eslint --fix", - "git add" - ] - }, "dependencies": { - "axios": "^0.23.0", - "xml2js": "^0.4.23" + "axios": "^1.6.2" }, "devDependencies": { - "@commitlint/cli": "^11.0.0", - "@commitlint/config-conventional": "^11.0.0", "@types/chance": "^1.1.3", "@types/jest": "^27.5.2", "@types/uuid": "^8.3.3", - "@types/xml2js": "^0.4.9", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "chance": "^1.1.8", - "commitizen": "^4.2.4", - "coveralls": "^3.1.1", "cpf_cnpj": "^0.2.0", - "cross-env": "^7.0.3", "dotenv": "^10.0.0", - "eslint": "^7.32.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.25.2", + "eslint": "^8.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", - "husky": "^4.3.6", - "jest": "^27.3.0", - "lint-staged": "^10.5.3", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.7.0", "prettier": "^2.4.1", - "travis": "^0.1.1", - "ts-jest": "^27.0.7", + "ts-jest": "^29.1.1", "ts-node": "^10.3.0", - "typescript": "^4.4.4", + "typescript": "^5.0.0", "uuid": "^8.3.2" }, - "config": { - "commitizen": { - "path": "./node_modules/cz-conventional-changelog" - } - }, "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/src/bling.spec.ts b/src/bling.spec.ts new file mode 100644 index 0000000..856e380 --- /dev/null +++ b/src/bling.spec.ts @@ -0,0 +1,241 @@ +'use strict' + +import { Chance } from 'chance' +import Bling from './bling' +import { Borderos } from './entities/borderos' +import { CamposCustomizados } from './entities/camposCustomizados' +import { CategoriasLojas } from './entities/categoriasLojas' +import { CategoriasProdutos } from './entities/categoriasProdutos' +import { CategoriasReceitasDespesas } from './entities/categoriasReceitasDespesas' +import { ContasContabeis } from './entities/contasContabeis' +import { ContasPagar } from './entities/contasPagar' +import { ContasReceber } from './entities/contasReceber' +import { Contatos } from './entities/contatos' +import { ContatosTipos } from './entities/contatosTipos' +import { Contratos } from './entities/contratos' +import { Depositos } from './entities/depositos' +import { Empresas } from './entities/empresas' +import { Estoques } from './entities/estoques' +import { FormasDePagamento } from './entities/formasDePagamento' +import { Homologacao } from './entities/homologacao' +import { Logisticas } from './entities/logisticas' +import { LogisticasEtiquetas } from './entities/logisticasEtiquetas' +import { LogisticasObjetos } from './entities/logisticasObjetos' +import { LogisticasServicos } from './entities/logisticasServicos' +import { NaturezasDeOperacoes } from './entities/naturezasDeOperacoes' +import { Nfces } from './entities/nfces' +import { Nfes } from './entities/nfes' +import { Nfses } from './entities/nfses' +import { Notificacoes } from './entities/notificacoes' +import { PedidosCompras } from './entities/pedidosCompras' +import { PedidosVendas } from './entities/pedidosVendas' +import { Produtos } from './entities/produtos' +import { ProdutosEstruturas } from './entities/produtosEstruturas' +import { ProdutosFornecedores } from './entities/produtosFornecedores' +import { ProdutosLojas } from './entities/produtosLojas' +import { ProdutosVariacoes } from './entities/produtosVariacoes' +import { Situacoes } from './entities/situacoes' +import { SituacoesModulos } from './entities/situacoesModulos' +import { SituacoesTransicoes } from './entities/situacoesTransicoes' +import { Usuarios } from './entities/usuarios' +import { Vendedores } from './entities/vendedores' + +const chance = Chance() + +const createBling = (accessToken: string) => { + return new Bling(accessToken) +} + +describe('Bling main module', () => { + it('should instantiate correctly', () => { + expect(createBling(chance.word())).toBeInstanceOf(Bling) + }) + + it('should retrieve borderôs entity', () => { + expect(createBling(chance.word()).borderos).toBeInstanceOf(Borderos) + }) + + it('should retrieve campos customizados entity', () => { + expect(createBling(chance.word()).camposCustomizados).toBeInstanceOf( + CamposCustomizados + ) + }) + + it('should retrieve categorias - lojas entity', () => { + expect(createBling(chance.word()).categoriasLojas).toBeInstanceOf( + CategoriasLojas + ) + }) + + it('should retrieve categorias - produtos entity', () => { + expect(createBling(chance.word()).categoriasProdutos).toBeInstanceOf( + CategoriasProdutos + ) + }) + + it('should retrieve categorias - receitas e despesas entity', () => { + expect( + createBling(chance.word()).categoriasReceitasDespesas + ).toBeInstanceOf(CategoriasReceitasDespesas) + }) + + it('should retrieve contas a pagar entity', () => { + expect(createBling(chance.word()).contasPagar).toBeInstanceOf(ContasPagar) + }) + + it('should retrieve contas a receber entity', () => { + expect(createBling(chance.word()).contasReceber).toBeInstanceOf( + ContasReceber + ) + }) + + it('should retrieve contas contábeis entity', () => { + expect(createBling(chance.word()).contasContabeis).toBeInstanceOf( + ContasContabeis + ) + }) + + it('should retrieve contatos entity', () => { + expect(createBling(chance.word()).contatos).toBeInstanceOf(Contatos) + }) + + it('should retrieve contatos - tipos entity', () => { + expect(createBling(chance.word()).contatosTipos).toBeInstanceOf( + ContatosTipos + ) + }) + + it('should retrieve contratos entity', () => { + expect(createBling(chance.word()).contratos).toBeInstanceOf(Contratos) + }) + + it('should retrieve depósitos entity', () => { + expect(createBling(chance.word()).depositos).toBeInstanceOf(Depositos) + }) + + it('should retrieve empresas entity', () => { + expect(createBling(chance.word()).empresas).toBeInstanceOf(Empresas) + }) + + it('should retrieve estoques entity', () => { + expect(createBling(chance.word()).estoques).toBeInstanceOf(Estoques) + }) + + it('should retrieve formas de pagamento entity', () => { + expect(createBling(chance.word()).formasDePagamento).toBeInstanceOf( + FormasDePagamento + ) + }) + + it('should retrieve homologação entity', () => { + expect(createBling(chance.word()).homologacao).toBeInstanceOf(Homologacao) + }) + + it('should retrieve logísticas entity', () => { + expect(createBling(chance.word()).logisticas).toBeInstanceOf(Logisticas) + }) + + it('should retrieve logísticas - etiquetas entity', () => { + expect(createBling(chance.word()).logisticasEtiquetas).toBeInstanceOf( + LogisticasEtiquetas + ) + }) + + it('should retrieve logísticas - objetos entity', () => { + expect(createBling(chance.word()).logisticasObjetos).toBeInstanceOf( + LogisticasObjetos + ) + }) + + it('should retrieve logísticas - serviços entity', () => { + expect(createBling(chance.word()).logisticasServicos).toBeInstanceOf( + LogisticasServicos + ) + }) + + it('should retrieve naturezas de operações entity', () => { + expect(createBling(chance.word()).naturezasDeOperacoes).toBeInstanceOf( + NaturezasDeOperacoes + ) + }) + + it('should retrieve notas fiscais de consumidor eletrônicas entity', () => { + expect(createBling(chance.word()).nfces).toBeInstanceOf(Nfces) + }) + + it('should retrieve notas fiscais de serviço eletrônicas entity', () => { + expect(createBling(chance.word()).nfses).toBeInstanceOf(Nfses) + }) + + it('should retrieve notas fiscais eletrônicas entity', () => { + expect(createBling(chance.word()).nfes).toBeInstanceOf(Nfes) + }) + + it('should retrieve notificações entity', () => { + expect(createBling(chance.word()).notificacoes).toBeInstanceOf(Notificacoes) + }) + + it('should retrieve pedidos - compras entity', () => { + expect(createBling(chance.word()).pedidosCompras).toBeInstanceOf( + PedidosCompras + ) + }) + + it('should retrieve pedidos - vendas entity', () => { + expect(createBling(chance.word()).pedidosVendas).toBeInstanceOf( + PedidosVendas + ) + }) + + it('should retrieve produtos entity', () => { + expect(createBling(chance.word()).produtos).toBeInstanceOf(Produtos) + }) + + it('should retrieve produtos - estruturas entity', () => { + expect(createBling(chance.word()).produtosEstruturas).toBeInstanceOf( + ProdutosEstruturas + ) + }) + + it('should retrieve produtos - fornecedores entity', () => { + expect(createBling(chance.word()).produtosFornecedores).toBeInstanceOf( + ProdutosFornecedores + ) + }) + + it('should retrieve produtos - lojas entity', () => { + expect(createBling(chance.word()).produtosLojas).toBeInstanceOf( + ProdutosLojas + ) + }) + + it('should retrieve produtos - variações entity', () => { + expect(createBling(chance.word()).produtosVariacoes).toBeInstanceOf( + ProdutosVariacoes + ) + }) + + it('should retrieve situações entity', () => { + expect(createBling(chance.word()).situacoes).toBeInstanceOf(Situacoes) + }) + + it('should retrieve situações - módulos entity', () => { + expect(createBling(chance.word()).situacoesModulos).toBeInstanceOf( + SituacoesModulos + ) + }) + + it('should retrieve situações - transições entity', () => { + expect(createBling(chance.word()).situacoesTransicoes).toBeInstanceOf( + SituacoesTransicoes + ) + }) + + it('should retrieve usuários entity', () => { + expect(createBling(chance.word()).usuarios).toBeInstanceOf(Usuarios) + }) + + it('should retrieve vendedores entity', () => { + expect(createBling(chance.word()).vendedores).toBeInstanceOf(Vendedores) + }) +}) diff --git a/src/bling.ts b/src/bling.ts index ca641e5..6d85184 100644 --- a/src/bling.ts +++ b/src/bling.ts @@ -1,332 +1,423 @@ 'use strict' -import Borderos from './entities/borderos' -import CustomizedField from './entities/customizedFields' -import Categories from './entities/categories' -import CommercialProposals from './entities/commercialProposals' -import Contacts from './entities/contacts' -import Deposits from './entities/deposits' -import Products from './entities/products' -import Orders from './entities/orders' -import PurchaseOrders from './entities/purchaseOrders' -import Invoices from './entities/invoices' -import ShopCategories from './entities/shopCategories' -import BillsToPay from './entities/billsToPay' -import BillsToReceive from './entities/billsToReceive' -import Contracts from './entities/contracts' -import Ctes from './entities/ctes' -import PaymentMethods from './entities/paymentMethods' -import ProductGroups from './entities/productGroups' -import Nfces from './entities/nfces' -import ServiceInvoices from './entities/serviceInvoices' - -import createError, { - IBlingError as IStandardBlingError -} from './core/helpers/createError' - -import { IApiInstance } from './core/interfaces/method' - -import * as qs from 'querystring' -import axios from 'axios' - -export type IBorderos = ReturnType -export type ICustomizedFields = ReturnType -export type ICategories = ReturnType -export type ICommercialProposals = ReturnType -export type IContacts = ReturnType -export type IDeposits = ReturnType -export type IProducts = ReturnType -export type IOrders = ReturnType -export type IPurchaseOrders = ReturnType -export type IInvoices = ReturnType -export type IShopCategories = ReturnType -export type IBillsToPay = ReturnType -export type IBillsToReceive = ReturnType -export type IContracts = ReturnType -export type ICtes = ReturnType -export type IPaymentMethods = ReturnType -export type IProductGroups = ReturnType -export type INfces = ReturnType -export type IServiceInvoices = ReturnType - -export type IBlingError = IStandardBlingError - -export class Bling { - #api: IApiInstance - #raw: boolean - - #borderos: IBorderos | undefined - #customizedFields: ICustomizedFields | undefined - #categories: ICategories | undefined - #commercialProposals: ICommercialProposals | undefined - #contacts: IContacts | undefined - #deposits: IDeposits | undefined - #orders: IOrders | undefined - #products: IProducts | undefined - #purchaseOrders: IPurchaseOrders | undefined - #invoices: IInvoices | undefined - #shopCategories: IShopCategories | undefined - #billsToPay: IBillsToPay | undefined - #billsToReceive: IBillsToReceive | undefined - #contracts: IContracts | undefined - #ctes: ICtes | undefined - #paymentMethods: IPaymentMethods | undefined - #productGroups: IProductGroups | undefined - #nfces: INfces | undefined - #serviceInvoices: IServiceInvoices | undefined - - constructor (apiKey: string, options: { raw: boolean } = { raw: false }) { - if (!apiKey || typeof apiKey !== 'string') { - throw createError({ - name: 'BlingModuleError', - message: "The API key wasn't correctly provided for Bling connection.", - status: '500', - data: { - apiKey: apiKey - }, - code: 'ERR_NO_API_KEY' - }) +import { Entity } from './entities/@shared/entity' +import { Borderos } from './entities/borderos' +import { CamposCustomizados } from './entities/camposCustomizados' +import { CategoriasLojas } from './entities/categoriasLojas' +import { CategoriasProdutos } from './entities/categoriasProdutos' +import { CategoriasReceitasDespesas } from './entities/categoriasReceitasDespesas' +import { ContasContabeis } from './entities/contasContabeis' +import { ContasPagar } from './entities/contasPagar' +import { ContasReceber } from './entities/contasReceber' +import { Contatos } from './entities/contatos' +import { ContatosTipos } from './entities/contatosTipos' +import { Contratos } from './entities/contratos' +import { Depositos } from './entities/depositos' +import { Empresas } from './entities/empresas' +import { Estoques } from './entities/estoques' +import { FormasDePagamento } from './entities/formasDePagamento' +import { Homologacao } from './entities/homologacao' +import { Logisticas } from './entities/logisticas' +import { LogisticasEtiquetas } from './entities/logisticasEtiquetas' +import { LogisticasObjetos } from './entities/logisticasObjetos' +import { LogisticasServicos } from './entities/logisticasServicos' +import { NaturezasDeOperacoes } from './entities/naturezasDeOperacoes' +import { Nfces } from './entities/nfces' +import { Nfes } from './entities/nfes' +import { Nfses } from './entities/nfses' +import { Notificacoes } from './entities/notificacoes' +import { PedidosCompras } from './entities/pedidosCompras' +import { PedidosVendas } from './entities/pedidosVendas' +import { Produtos } from './entities/produtos' +import { ProdutosEstruturas } from './entities/produtosEstruturas' +import { ProdutosFornecedores } from './entities/produtosFornecedores' +import { ProdutosLojas } from './entities/produtosLojas' +import { ProdutosVariacoes } from './entities/produtosVariacoes' +import { Situacoes } from './entities/situacoes' +import { SituacoesModulos } from './entities/situacoesModulos' +import { SituacoesTransicoes } from './entities/situacoesTransicoes' +import { Usuarios } from './entities/usuarios' +import { Vendedores } from './entities/vendedores' +import { Newable } from './helpers/types/newable.type' +import { getRepository } from './providers/ioc' +import { IBlingRepository } from './repositories/bling.repository.interface' + +/** + * Módulo conector à API do Bling. + * + * @class + * @example + * // Constrói um novo conector + * const accessToken = 'sua-api-key' + * const bling = new Bling(accessToken) + */ +export default class Bling { + #repository: IBlingRepository + #modules: Record + + /** + * Constrói o objeto. + * + * @param accessToken O token de acesso à API do Bling. + */ + constructor(accessToken: string) { + this.#repository = getRepository(accessToken) + this.#modules = {} + } + + /** + * Obtém um módulo através de sua assinatura (seguindo o _pattern_ `Instance`). + * + * @param {Newable} EntityClass A entidade desejada. + * + * @returns {T} A instância da entidade. + */ + private getModule(EntityClass: Newable): T { + if (!this.#modules[EntityClass.name]) { + this.#modules[EntityClass.name] = new EntityClass(this.#repository) } - if (typeof options.raw !== 'boolean') { - throw createError({ - name: 'BlingModuleError', - message: - 'The raw attribute must be a boolean to configure the Bling connection.', - status: '500', - data: { - raw: options.raw - }, - code: 'ERR_WRONG_BLING_RAW_ATTR' - }) - } - - this.#raw = options.raw - - const api = axios.create({ - baseURL: 'https://bling.com.br/Api/v2' - }) - - api.interceptors.request.use((config) => { - if ( - config.method && - ['POST', 'PUT', 'post', 'put'].includes(config.method) - ) { - config.data = qs.stringify({ - apikey: apiKey, - ...config.data - }) - } - - config.params = { - apikey: apiKey, - ...config.params - } - - return config - }) - - this.#api = api - } - - static create (apiKey: string, options: { raw: boolean } = { raw: false }) { - return new this(apiKey, options) - } - - public borderos () { - if (!this.#borderos) { - this.#borderos = Borderos(this.#api, this.#raw) - } - return this.#borderos - } - - public customizedFields () { - if (!this.#customizedFields) { - this.#customizedFields = CustomizedField(this.#api, this.#raw) - } - return this.#customizedFields - } - - public camposCustomizados () { - return this.customizedFields() - } - - public categories () { - if (!this.#categories) { - this.#categories = Categories(this.#api, this.#raw) - } - return this.#categories - } - - public categorias () { - return this.categories() - } - - public shopCategories () { - if (!this.#shopCategories) { - this.#shopCategories = ShopCategories(this.#api, this.#raw) - } - return this.#shopCategories + return this.#modules[EntityClass.name] as T } - - public categoriasLoja () { - return this.shopCategories() - } - - public contacts () { - if (!this.#contacts) { - this.#contacts = Contacts(this.#api, this.#raw) - } - return this.#contacts - } - - public contatos () { - return this.contacts() - } - - public billsToPay () { - if (!this.#billsToPay) { - this.#billsToPay = BillsToPay(this.#api, this.#raw) - } - return this.#billsToPay - } - - public contasAPagar () { - return this.billsToPay() - } - - public billsToReceive () { - if (!this.#billsToReceive) { - this.#billsToReceive = BillsToReceive(this.#api, this.#raw) - } - return this.#billsToReceive - } - - public contasAReceber () { - return this.billsToReceive() - } - - public contracts () { - if (!this.#contracts) { - this.#contracts = Contracts(this.#api, this.#raw) - } - return this.#contracts - } - - public contratos () { - return this.contracts() - } - - public ctes () { - if (!this.#ctes) { - this.#ctes = Ctes(this.#api, this.#raw) - } - return this.#ctes - } - - public deposits () { - if (!this.#deposits) { - this.#deposits = Deposits(this.#api, this.#raw) - } - return this.#deposits - } - - public depositos () { - return this.deposits() - } - - public paymentMethods () { - if (!this.#paymentMethods) { - this.#paymentMethods = PaymentMethods(this.#api, this.#raw) - } - return this.#paymentMethods - } - - public formasDePagamento () { - return this.paymentMethods() - } - - public productGroups () { - if (!this.#productGroups) { - this.#productGroups = ProductGroups(this.#api, this.#raw) - } - return this.#productGroups - } - - public grupoDeProdutos () { - return this.productGroups() - } - - public nfces () { - if (!this.#nfces) { - this.#nfces = Nfces(this.#api, this.#raw) - } - return this.#nfces - } - - public invoices () { - if (!this.#invoices) { - this.#invoices = Invoices(this.#api, this.#raw) - } - return this.#invoices - } - - public notasFiscais () { - return this.invoices() - } - - public serviceInvoices () { - if (!this.#serviceInvoices) { - this.#serviceInvoices = ServiceInvoices(this.#api, this.#raw) - } - return this.#serviceInvoices - } - - public notasServicos () { - return this.serviceInvoices() - } - - public orders () { - if (!this.#orders) { - this.#orders = Orders(this.#api, this.#raw) - } - return this.#orders - } - - public pedidos () { - return this.orders() - } - - public purchaseOrders () { - if (!this.#purchaseOrders) { - this.#purchaseOrders = PurchaseOrders(this.#api, this.#raw) - } - return this.#purchaseOrders - } - - public pedidosDeCompra () { - return this.purchaseOrders() - } - - public products () { - if (!this.#products) { - this.#products = Products(this.#api, this.#raw) - } - return this.#products - } - - public produtos () { - return this.products() - } - - public commercialProposals () { - if (!this.#commercialProposals) { - this.#commercialProposals = CommercialProposals(this.#api, this.#raw) - } - return this.#commercialProposals - } - - public propostasComerciais () { - return this.commercialProposals() + + /** + * Obtém a instância de interação com borderôs. + * + * @returns {Borderos} + */ + public get borderos(): Borderos { + return this.getModule(Borderos) + } + + /** + * Obtém a instância de interação com campos customizados. + * + * @returns {CamposCustomizados} + */ + public get camposCustomizados(): CamposCustomizados { + return this.getModule(CamposCustomizados) + } + + /** + * Obtém a instância de interação com categorias - lojas. + * + * @return {CategoriasLojas} + */ + public get categoriasLojas(): CategoriasLojas { + return this.getModule(CategoriasLojas) + } + + /** + * Obtém a instância de interação com categorias - produtos. + * + * @return {CategoriasProdutos} + */ + public get categoriasProdutos(): CategoriasProdutos { + return this.getModule(CategoriasProdutos) + } + + /** + * Obtém a instância de interação com categorias - receitas e despesas. + * + * @return {CategoriasReceitasDespesas} + */ + public get categoriasReceitasDespesas(): CategoriasReceitasDespesas { + return this.getModule(CategoriasReceitasDespesas) + } + + /** + * Obtém a instância de interação com contas a pagar. + * + * @return {ContasPagar} + */ + public get contasPagar(): ContasPagar { + return this.getModule(ContasPagar) + } + + /** + * Obtém a instância de interação com contas a receber. + * + * @return {ContasReceber} + */ + public get contasReceber(): ContasReceber { + return this.getModule(ContasReceber) + } + + /** + * Obtém a instância de interação com contas contábeis. + * + * @return {ContasContabeis} + */ + public get contasContabeis(): ContasContabeis { + return this.getModule(ContasContabeis) + } + + /** + * Obtém a instância de interação com contatos. + * + * @return {Contatos} + */ + public get contatos(): Contatos { + return this.getModule(Contatos) + } + + /** + * Obtém a instância de interação com contatos - tipos. + * + * @return {ContatosTipos} + */ + public get contatosTipos(): ContatosTipos { + return this.getModule(ContatosTipos) + } + + /** + * Obtém a instância de interação com contratos. + * + * @return {Contratos} + */ + public get contratos(): Contratos { + return this.getModule(Contratos) + } + + /** + * Obtém a instância de interação com depósitos. + * + * @return {Depositos} + */ + public get depositos(): Depositos { + return this.getModule(Depositos) + } + + /** + * Obtém a instância de interação com empresas. + * + * @return {Empresas} + */ + public get empresas(): Empresas { + return this.getModule(Empresas) + } + + /** + * Obtém a instância de interação com estoques. + * + * @return {Estoques} + */ + public get estoques(): Estoques { + return this.getModule(Estoques) + } + + /** + * Obtém a instância de interação com formas de pagamento. + * + * @return {FormasDePagamento} + */ + public get formasDePagamento(): FormasDePagamento { + return this.getModule(FormasDePagamento) + } + + /** + * Obtém a instância de interação com homologação. + * + * @return {Homologacao} + */ + public get homologacao(): Homologacao { + return this.getModule(Homologacao) + } + + /** + * Obtém a instância de interação com logísticas. + * + * @return {Logisticas} + */ + public get logisticas(): Logisticas { + return this.getModule(Logisticas) + } + + /** + * Obtém a instância de interação com logísticas - etiquetas. + * + * @return {LogisticasEtiquetas} + */ + public get logisticasEtiquetas(): LogisticasEtiquetas { + return this.getModule(LogisticasEtiquetas) + } + + /** + * Obtém a instância de interação com logísticas - objetos. + * + * @return {LogisticasObjetos} + */ + public get logisticasObjetos(): LogisticasObjetos { + return this.getModule(LogisticasObjetos) + } + + /** + * Obtém a instância de interação com logísticas - serviços. + * + * @return {LogisticasServicos} + */ + public get logisticasServicos(): LogisticasServicos { + return this.getModule(LogisticasServicos) + } + + /** + * Obtém a instância de interação com naturezas de operações. + * + * @return {NaturezasDeOperacoes} + */ + public get naturezasDeOperacoes(): NaturezasDeOperacoes { + return this.getModule(NaturezasDeOperacoes) + } + + /** + * Obtém a instância de interação com notas fiscals de consumidor eletrônicas. + * + * @return {Nfces} + */ + public get nfces(): Nfces { + return this.getModule(Nfces) + } + + /** + * Obtém a instância de interação com notas fiscals de serviço eletrônicas. + * + * @return {Nfses} + */ + public get nfses(): Nfses { + return this.getModule(Nfses) + } + + /** + * Obtém a instância de interação com notas fiscals de serviço eletrônicas. + * + * @return {Nfes} + */ + public get nfes(): Nfes { + return this.getModule(Nfes) + } + + /** + * Obtém a instância de interação com notificações. + * + * @return {Notificacoes} + */ + public get notificacoes(): Notificacoes { + return this.getModule(Notificacoes) + } + + /** + * Obtém a instância de interação com pedidos - compras. + * + * @return {PedidosCompras} + */ + public get pedidosCompras(): PedidosCompras { + return this.getModule(PedidosCompras) + } + + /** + * Obtém a instância de interação com pedidos - vendas. + * + * @return {PedidosVendas} + */ + public get pedidosVendas(): PedidosVendas { + return this.getModule(PedidosVendas) + } + + /** + * Obtém a instância de interação com produtos. + * + * @return {Produtos} + */ + public get produtos(): Produtos { + return this.getModule(Produtos) + } + + /** + * Obtém a instância de interação com produtos - estruturas. + * + * @return {ProdutosEstruturas} + */ + public get produtosEstruturas(): ProdutosEstruturas { + return this.getModule(ProdutosEstruturas) + } + + /** + * + * Obtém a instância de interação com produtos - fornecedores. + * + * @return {ProdutosFornecedores} + */ + public get produtosFornecedores(): ProdutosFornecedores { + return this.getModule(ProdutosFornecedores) + } + + /** + * + * Obtém a instância de interação com produtos - fornecedores. + * + * @return {ProdutosLojas} + */ + public get produtosLojas(): ProdutosLojas { + return this.getModule(ProdutosLojas) + } + + /** + * + * Obtém a instância de interação com produtos - variações. + * + * @return {ProdutosVariacoes} + */ + public get produtosVariacoes(): ProdutosVariacoes { + return this.getModule(ProdutosVariacoes) + } + + /** + * + * Obtém a instância de interação com situações. + * + * @return {Situacoes} + */ + public get situacoes(): Situacoes { + return this.getModule(Situacoes) + } + + /** + * + * Obtém a instância de interação com situações - módulos. + * + * @return {SituacoesModulos} + */ + public get situacoesModulos(): SituacoesModulos { + return this.getModule(SituacoesModulos) + } + + /** + * + * Obtém a instância de interação com situações - transições. + * + * @return {SituacoesTransicoes} + */ + public get situacoesTransicoes(): SituacoesTransicoes { + return this.getModule(SituacoesTransicoes) + } + + /** + * + * Obtém a instância de interação com usuários. + * + * @return {Usuarios} + */ + public get usuarios(): Usuarios { + return this.getModule(Usuarios) + } + + /** + * + * Obtém a instância de interação com vendedores. + * + * @return {Vendedores} + */ + public get vendedores(): Vendedores { + return this.getModule(Vendedores) } } diff --git a/src/core/functions/all.ts b/src/core/functions/all.ts deleted file mode 100644 index b16fe34..0000000 --- a/src/core/functions/all.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - IPluralResponse, - IPluralEntity, - ISingularEntity, - IApiError, - IPluralError -} from '../interfaces/method' - -import Method from '../template/method' -import createError from '../helpers/createError' -import packErrorsToJsonApi from '../helpers/packErrorsToJsonApi' -import handleApiError from '../helpers/handleApiError' - -export default class All extends Method { - /** - * Retrieve all entities from the given endpoint. - * @private - * @access private - * @async - * @param params The params to filter or enhance the response. - * @param infos Parameters to enhance the response data. - * @param filters The filters used for the query. - * @param options The options object. - * @param raw Return either raw data from Bling or beautified processed data. - * @param page The response page with pagination is desired. - */ - - public async all(options?: { - params?: { - filters?: IFilters - infos?: IInfo - } - raw?: false - page?: number - }): Promise - - public async all(options?: { - params?: { - filters?: IFilters - infos?: IInfo - } - raw: true - page?: number - }): Promise> - - public async all (options?: { - params?: { - filters?: IFilters - infos?: IInfo - } - raw?: boolean - page?: number - }): Promise> { - const entities: IEntityResponse[] = [] - - const params: { filters?: string; [key: string]: unknown } = {} - - const endpoint = this.endpoint || this.pluralName - const raw = options && options.raw !== undefined ? options.raw : this.raw - - let hasMore = true - let reqCount = 0 - let page = 1 - - let isSinglePage = false - - if (options) { - if (options.params) { - if (options.params.filters) { - const typedParams = options.params.filters as unknown as { - [key: string]: string - } - const filters = Object.keys(options.params.filters) - .map((key: string) => - typedParams[key] ? `${key}[${typedParams[key]}]` : null - ) - .filter((item) => !!item) - .join(';') - - params.filters = filters - } - - if (options.params.infos) { - for (const infoKey in options.params.infos) { - params[infoKey] = options.params.infos[infoKey] - } - } - } - - if (options.page) { - isSinglePage = true - page = options.page - } - } - - while (hasMore) { - const response = await this.api - .get(`/${endpoint}/page=${page}/json`, { - params - }) - .catch((err: IApiError) => { - const errorData = { - name: 'BlingRequestError', - message: `Error on all method during request call: ${err.message}`, - status: String(err.response?.status) || '400', - code: 'ERR_GET_REQUEST_FAILURE' - } - - const rawData = err.response?.data as IPluralResponse - - return handleApiError({ - rawData, - errorData, - raw - }) - }) - - const rawData = response.data as IPluralResponse - const responseData = rawData.retorno - - if (responseData.erros) { - hasMore = false - - const pluralError = responseData as IPluralError - const jsonApiErrorObj = packErrorsToJsonApi(pluralError) - - // If there is an error different than 'not found' - if (jsonApiErrorObj.errors.some((err) => err.code !== '14')) { - const errorData = { - name: 'BlingRequestError', - message: 'Error on all method after request call', - status: '400', - code: 'ERR_ALL_METHOD_FAILURE' - } - - if (raw) { - throw createError({ - ...errorData, - data: rawData - }) - } else { - throw createError({ - ...errorData, - data: jsonApiErrorObj - }) - } - } - } else { - const rawNewEntities = responseData as IPluralEntity - - const newEntities = rawNewEntities[ - this.pluralName - ] as ISingularEntity[] - - const singularEntities = newEntities.map( - (item) => item[this.singularName] - ) - for (const entity of singularEntities) { - entities.push(entity) - } - } - - if (isSinglePage) { - break - } - - page++ - - reqCount++ - if (reqCount === 3) { - const sleep = new Promise((resolve) => { - setTimeout(resolve, 1000) - }) - - await sleep - - reqCount = 0 - } - } - - if (raw) { - return { - retorno: { - [this.pluralName]: entities.map((entity) => ({ - [this.singularName]: entity - })) - } - } - } else { - return entities - } - } -} diff --git a/src/core/functions/create.ts b/src/core/functions/create.ts deleted file mode 100644 index 24cb7a6..0000000 --- a/src/core/functions/create.ts +++ /dev/null @@ -1,191 +0,0 @@ -import xml2js from 'xml2js' - -import { - IPluralResponse, - IPluralEntity, - ISingularEntity, - IApiError -} from '../interfaces/method' - -import Method from '../template/method' - -import createError from '../helpers/createError' -import handleApiError from '../helpers/handleApiError' -import convertArraysToObj from '../helpers/convertArraysToObj' - -export default class Create extends Method { - /** - * Create an entity on the given endpoint. - * @protected - * @access protected - * @async - * @param data The data for the entity to be created. - * @param options The options object to define the response structure. - * @param raw Return either raw data from Bling or beautified processed data. - * @returns The created entity. - */ - public async create( - data: IEntity, - options?: { - raw?: false - }, - ...restData: unknown[] - ): Promise - - public async create( - data: IEntity, - options?: { - raw: true - }, - ...restData: unknown[] - ): Promise> - - public async create ( - data: IEntity, - options?: { - raw?: boolean - }, - ...restData: unknown[] - ): Promise> { - if (!data || typeof data !== 'object' || Object.keys(data).length === 0) { - throw createError({ - name: 'BlingCreateError', - message: 'The "data" argument must be a not empty object', - status: '500', - data, - code: 'ERR_INCORRECT_CREATE_DATA' - }) - } - - const xmlBuilder = new xml2js.Builder({ rootName: this.singularName }) - const xml = xmlBuilder.buildObject(convertArraysToObj(data)) - - const params = { - xml, - ...restData - } - - const endpoint = this.endpoint || this.singularName - const raw = options && options.raw !== undefined ? options.raw : this.raw - - const response = await this.api - .post(`/${endpoint}/json`, params) - .catch((err: IApiError) => { - const errorData = { - name: 'BlingRequestError', - message: `Error on create method during request call: ${err.message}`, - status: String(err.response?.status) || '400', - code: err.code || 'ERR_POST_REQUEST_FAILURE' - } - - const rawData = err.response?.data as - | IPluralResponse - | '' - - if (rawData === '') { - console.log(err.response?.data) - throw createError({ - ...errorData, - data: { - errors: [ - { - title: 'Empty return', - detail: 'The request has gotten an empty return.' - } - ] - } - }) - } else { - return handleApiError({ - rawData, - errorData, - raw - }) - } - }) - - const rawData = response.data as IPluralResponse - const responseData = rawData.retorno - - if (responseData.erros) { - /** - * It can return as (most of the cases) - * { - * retorno: { - * erros: { - * cod: string, - * msg: string - * }[] - * } - * } - * - * or (paymentMethod case) - * { - * retorno: { - * [cod: string]: string - * }[] - * } - * - * or (also paymentMethod case) - * { - * retorno: string[] - * } - * */ - const errorData = { - name: 'BlingRequestError', - message: 'Error on create method after request call', - status: '400', - code: 'ERR_CREATE_METHOD_FAILURE' - } - - return handleApiError({ - rawData, - errorData, - raw - }) - } else { - if (raw) { - return rawData - } else { - const rawResponse = responseData as IPluralEntity - - if (Array.isArray(rawResponse[this.pluralName])) { - /** - * It can return as (most of the cases) - * { - * retorno: { - * [pluralName]: { - * [singularName]: IEntityResponse - * }[] - * } - * } - * - * or (paymentMethod case) - * { - * retorno: { - * [pluralName]: IEntityResponse[] - * } - * } - * */ - const rawEntity = rawResponse[this.pluralName] as - | ISingularEntity[] - | IEntityResponse[] - - if (Object.keys(rawEntity[0]).length === 1) { - const arrRawReturn = rawEntity as ISingularEntity[] - return arrRawReturn.map( - (entity) => entity[this.singularName] - ) as IEntityResponse[] - } else { - return rawEntity as IEntityResponse[] - } - } else { - const rawEntity = rawResponse[ - this.pluralName - ] as ISingularEntity - return [rawEntity[this.singularName]] - } - } - } - } -} diff --git a/src/core/functions/delete.ts b/src/core/functions/delete.ts deleted file mode 100644 index 2bc55b7..0000000 --- a/src/core/functions/delete.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - IPluralResponse, - IPluralEntity, - ISingularEntity, - IApiError -} from '../interfaces/method' - -import Method from '../template/method' -import createError from '../helpers/createError' -import handleApiError from '../helpers/handleApiError' - -export default class Find extends Method { - /** - * Delete an entity on the given endpoint. - * @protected - * @access protected - * @async - * @param id The entity code or id. - * @param options The options object. - * @param raw Return either raw data from Bling or beautified processed data. - * @returns The deleted entity. - */ - public async delete( - id: number | string, - options?: { - raw?: false - } - ): Promise - - public async delete( - id: number | string, - options?: { - raw: true - } - ): Promise> - - public async delete ( - id: number | string, - options?: { - raw?: boolean - } - ): Promise> { - if (!id || typeof id === 'object' || Array.isArray(id)) { - throw createError({ - name: 'BlingDeleteError', - message: 'The "id" argument must be a number or string', - status: '500', - data: { id }, - code: 'ERR_INCORRECT_DELETE_ID' - }) - } - - const endpoint = this.endpoint || this.singularName - const raw = options && options.raw !== undefined ? options.raw : this.raw - - const response = await this.api - .delete(`/${endpoint}/${id}/json`) - .catch((err: IApiError) => { - const errorData = { - name: 'BlingRequestError', - message: `Error on delete method during request call: ${err.message}`, - status: String(err.response?.status) || '400', - code: err.code || 'ERR_DELETE_REQUEST_FAILURE' - } - - const rawData = err.response?.data as IPluralResponse - - return handleApiError({ - rawData, - errorData, - raw - }) - }) - - const rawData = response.data as IPluralResponse - const responseData = rawData.retorno - - if (responseData.erros) { - const errorData = { - name: 'BlingRequestError', - message: 'Error on delete method after request call', - status: '400', - code: 'ERR_DELETE_METHOD_FAILURE' - } - - return handleApiError({ - rawData, - errorData, - raw - }) - } else { - if (raw) { - return rawData - } else { - const rawResponse = responseData as IPluralEntity - const rawEntity = rawResponse[ - this.pluralName - ] as ISingularEntity[] - return rawEntity[0][this.singularName] - } - } - } -} diff --git a/src/core/functions/find.ts b/src/core/functions/find.ts deleted file mode 100644 index 84807c7..0000000 --- a/src/core/functions/find.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - IPluralResponse, - IPluralEntity, - ISingularEntity, - IApiError -} from '../interfaces/method' - -import Method from '../template/method' -import createError from '../helpers/createError' -import handleApiError from '../helpers/handleApiError' - -export default class Find extends Method { - /** - * Retrieve one entity from the given endpoint. - * @protected - * @access protected - * @async - * @param id The entity id. - * @param options The options object. - * @param infos Parameters to enhance the response data. - * @param params The params to filter or enhance the response. - * @param raw Return either raw data from Bling or beautified processed data. - * @returns The found entity. - */ - public async find( - id: number | string, - options?: { params?: IInfos; raw?: false } - ): Promise - - public async find( - id: number | string, - options?: { params?: IInfos; raw: true } - ): Promise> - - public async find ( - id: number | string, - options?: { - params?: IInfos - raw?: boolean - } - ): Promise< - IEntityResponse | IEntityResponse[] | IPluralResponse - > { - if (!id) { - throw createError({ - name: 'BlingFindError', - message: 'The "id" argument must be a number or string.', - status: '500', - data: { id }, - code: 'ERR_INCORRECT_FIND_ID' - }) - } - - const endpoint = this.endpoint || this.singularName - const raw = options && options.raw !== undefined ? options.raw : this.raw - - const response = await this.api - .get(`/${endpoint}/${id}/json`, { - params: options && options.params - }) - .catch((err: IApiError) => { - const errorData = { - name: 'BlingRequestError', - message: `Error on find method during request call: ${err.message}`, - status: String(err.response?.status) || '400', - code: 'ERR_GET_REQUEST_FAILURE' - } - - const rawData = err.response?.data as IPluralResponse - - return handleApiError({ - rawData, - errorData, - raw - }) - }) - - const rawData = response.data as IPluralResponse - const responseData = rawData.retorno - - if (responseData.erros) { - const errorData = { - name: 'BlingRequestError', - message: 'Error on find method after request call', - status: String(response.status), - code: 'ERR_FIND_METHOD_FAILURE' - } - - return handleApiError({ - rawData, - errorData, - raw - }) - } else { - if (raw) { - return rawData - } else { - const rawResponse = responseData as IPluralEntity - const rawEntity = rawResponse[ - this.pluralName - ] as ISingularEntity[] - - if (rawEntity.length === 1) { - return rawEntity[0][this.singularName] - } else { - return rawEntity.map((entity) => entity[this.singularName]) - } - } - } - } -} diff --git a/src/core/functions/findBy.ts b/src/core/functions/findBy.ts deleted file mode 100644 index fe50d3c..0000000 --- a/src/core/functions/findBy.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { IPluralResponse } from '../interfaces/method' - -import All from './all' -import Method from '../template/method' -import createError from '../helpers/createError' - -export default class FindBy extends Method { - /** - * Retrieve entities that match a certain filter. - * @private - * @access private - * @async - * @param params The params to filter or enhance the response. - * @param filters The filters used for the query. - * @param infos Parameters to enhance the response data. - * @param options The options object. - * @param raw Return either raw data from Bling or beautified processed data. - * @param page The response page with pagination is desired. - */ - public async findBy( - params: { - filters: IFilters - infos?: IInfo - }, - options?: { - raw?: false - page?: number - } - ): Promise - - public async findBy( - params: { - filters: IFilters - infos?: IInfo - }, - options?: { - raw: true - page?: number - } - ): Promise> - - public async findBy ( - params: { - filters: IFilters - infos?: IInfo - }, - options?: { - raw?: boolean - page?: number - } - ): Promise> { - if (!params) { - throw createError({ - name: 'BlingFindByError', - message: 'No options passed to `.findBy()` method', - status: '500', - data: { params }, - code: 'ERR_INCORRECT_FINDBY_OPTIONS' - }) - } - - if (!params.filters) { - throw createError({ - name: 'BlingFindByError', - message: 'No filters passed to `.findBy()` method', - status: '500', - data: { params }, - code: 'ERR_INCORRECT_FINDBY_OPTION_FILTERS' - }) - } - - const raw = options && options.raw !== undefined ? options.raw : this.raw - - const config = { - api: this.api, - raw, - endpoint: this.endpoint, - singularName: this.singularName, - pluralName: this.pluralName - } - - const allEntity = new All(config) - - // @TODO: deal with interfaces problems to reuse code properly - if (raw) { - return await allEntity.all({ - params, - raw: true, - page: options && options.page - }) - } else { - return await allEntity.all({ - params, - page: options && options.page - }) - } - } -} diff --git a/src/core/functions/update.ts b/src/core/functions/update.ts deleted file mode 100644 index 7b8460f..0000000 --- a/src/core/functions/update.ts +++ /dev/null @@ -1,136 +0,0 @@ -import xml2js from 'xml2js' - -import { - IPluralResponse, - IPluralEntity, - ISingularEntity, - IApiError -} from '../interfaces/method' - -import Method from '../template/method' - -import createError from '../helpers/createError' -import handleApiError from '../helpers/handleApiError' -import convertArraysToObj from '../helpers/convertArraysToObj' - -export default class Update extends Method { - /** - * Update an entity on the given endpoint. - * @protected - * @access protected - * @async - * @param id The entity code or id. - * @param data The data for the entity to be updated. - * @param options The options object. - * @param raw Return either raw data from Bling or beautified processed data. - * @return The updated entity. - */ - public async update( - id: number | string, - data: IEntity, - options?: { - raw?: false - } - ): Promise - - public async update( - id: number | string, - data: IEntity, - options?: { - raw: true - } - ): Promise> - - public async update ( - id: number | string, - data: IEntity, - options?: { - raw?: boolean - } - ): Promise> { - if (!data || typeof data !== 'object' || Object.keys(data).length === 0) { - throw createError({ - name: 'BlingUpdateError', - message: 'The "data" argument must be a not empty object', - status: '500', - data, - code: 'ERR_INCORRECT_UPDATE_DATA' - }) - } - - if (!id || typeof id === 'object' || Array.isArray(id)) { - throw createError({ - name: 'BlingUpdateError', - message: 'The "id" argument must be a number or string', - status: '500', - data: { id }, - code: 'ERR_INCORRECT_UPDATE_ID' - }) - } - - const xmlBuilder = new xml2js.Builder({ rootName: this.singularName }) - const xml = xmlBuilder.buildObject(convertArraysToObj(data)) - - const params = { - xml - } - - const endpoint = this.endpoint || this.singularName - const raw = options && options.raw !== undefined ? options.raw : this.raw - - const response = await this.api - .put(`/${endpoint}/${id}/json`, params) - .catch((err: IApiError) => { - const errorData = { - name: 'BlingRequestError', - message: `Error on update method during request call: ${err.message}`, - status: String(err.response?.status) || '400', - code: err.code || 'ERR_PUT_REQUEST_FAILURE' - } - - const rawData = err.response?.data as IPluralResponse - - return handleApiError({ - rawData, - errorData, - raw - }) - }) - - const rawData = response.data as IPluralResponse - const responseData = rawData.retorno - - if (responseData.erros) { - const errorData = { - name: 'BlingRequestError', - message: 'Error on update method after request call', - status: '400', - code: 'ERR_UPDATE_METHOD_FAILURE' - } - - return handleApiError({ - rawData, - errorData, - raw - }) - } else { - if (raw) { - return rawData - } else { - const rawResponse = responseData as IPluralEntity - - if (Array.isArray(rawResponse[this.pluralName])) { - const rawEntity = rawResponse[ - this.pluralName - ] as ISingularEntity[] - return rawEntity[0][this.singularName] - } else { - const rawEntity = rawResponse[ - this.pluralName - ] as ISingularEntity - return rawEntity[this.singularName] - } - } - } - } -} diff --git a/src/core/helpers/convertArraysToObj.ts b/src/core/helpers/convertArraysToObj.ts deleted file mode 100644 index 1c12118..0000000 --- a/src/core/helpers/convertArraysToObj.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -const convertArraysToObj = (data: any) => { - const processedData = {} as Record - - for (const key in data) { - const value = data[key] - - if (Array.isArray(value)) { - const firstElem = value[0] - - if (typeof firstElem === 'object') { - const attr = Object.keys(firstElem)[0] - - const newElems = { [attr]: value.map((item) => item[attr]) } - processedData[key] = newElems - } else { - processedData[key] = value - } - } else if (value && typeof value === 'object') { - processedData[key] = convertArraysToObj(value) - } else if (value !== null && value !== undefined && value !== '') { - processedData[key] = value - } - } - - return processedData -} - -export default convertArraysToObj diff --git a/src/core/helpers/createError.ts b/src/core/helpers/createError.ts deleted file mode 100644 index 948dca1..0000000 --- a/src/core/helpers/createError.ts +++ /dev/null @@ -1,48 +0,0 @@ -export interface IBlingErrorArgs { - name: string - message: string - status: string - data?: unknown - code: string -} - -export interface IBlingError extends Error { - name: string - status: string - code: string - data?: unknown - toJSON: () => { - name: string - message: string - code: string - data?: unknown - stack?: string - } -} - -/** - * Create an Error with the specified message, config, error code and status. - * - * @param {string} name The error name, default to 'BlingError'. - * @param {string} message The error message. - * @param {string} status The error status (for example, '500'). - * @param {unknown} data The error data (for example, the API call response). - * @param {string} code The error code (for example, 'E_CONN_ABORTED'). - * @returns {IBlingError} The created error. - */ -export default function createError (args: IBlingErrorArgs) { - const rawError = new Error(args.message) - - const error: IBlingError = { - ...rawError, - ...args, - toJSON: () => { - return { - ...args, - stack: rawError.stack - } - } - } - - return error -} diff --git a/src/core/helpers/handleApiError.ts b/src/core/helpers/handleApiError.ts deleted file mode 100644 index 5391ad2..0000000 --- a/src/core/helpers/handleApiError.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { IPluralResponse, IPluralError } from '../interfaces/method' - -import packErrorsToJsonApi from './packErrorsToJsonApi' -import createError, { IBlingErrorArgs } from './createError' - -export default (args: { - rawData: IPluralResponse - errorData: IBlingErrorArgs - raw?: boolean -}) => { - const pluralError = args.rawData.retorno as IPluralError - - if (args.raw) { - throw createError({ - ...args.errorData, - data: args.rawData || null - }) - } else { - const jsonApiErrorArr = packErrorsToJsonApi(pluralError) - - throw createError({ - ...args.errorData, - data: jsonApiErrorArr - }) - } -} diff --git a/src/core/helpers/packErrorsToJsonApi.ts b/src/core/helpers/packErrorsToJsonApi.ts deleted file mode 100644 index 4b16203..0000000 --- a/src/core/helpers/packErrorsToJsonApi.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - IPluralError, - ISingularError, - IShortenedError -} from '../interfaces/method' - -/** - * Convert Bling array/object/string errors to JSON API standards. Useful for standardizing the error response. - * - * ######## JSON API SPECIFICATION ######## - * - * Source: https://jsonapi.org/format/#errors - * - * ======================================== - * - * An error object MAY have the following members: - * - `id`: a unique identifier for this particular occurrence of the problem. - * - `links`: a links object containing the following members: - * - `about`: a link that leads to further details about this particular occurrence of the problem. - * - `status`: the HTTP status code applicable to this problem, expressed as a string value. - * - `code`: an application-specific error code, expressed as a string value. - * - `title`: a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. - * - `detail`: a human-readable explanation specific to this occurrence of the problem. Like title, this field’s value can be localized. - * - `source`: an object containing references to the source of the error, optionally including any of the following members: - * - `pointer`: a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. `"/data"` for a primary data object, or `"/data/attributes/title"` for a specific attribute]. - * - `parameter`: a string indicating which URI query parameter caused the error. - * - `meta`: a meta object containing non-standard meta-information about the error. - */ - -export interface JsonApiErrorObject { - id?: string - links?: { - about?: string - } - status?: string - code: string - title?: string - detail: string - source?: { - pointer?: string - parameter?: string - } - meta?: unknown -} - -export default (errArr: IPluralError) => { - const rawErrors = errArr.erros - - const returnData: { errors: JsonApiErrorObject[] } = { - errors: [] - } - - if (Array.isArray(rawErrors)) { - if (typeof rawErrors[0] === 'string') { - const definedRawErrData = rawErrors as string[] - - returnData.errors = definedRawErrData.map((err: string) => ({ - detail: err, - code: '_' - })) - } else { - const definedRawErrData = rawErrors as ISingularError[] - - returnData.errors = definedRawErrData.map((errObj: ISingularError) => ({ - detail: errObj.erro.msg, - code: String(errObj.erro.cod) - })) - } - } else { - if (rawErrors.erro) { - const errors = rawErrors as ISingularError - - returnData.errors = [ - { - detail: errors.erro.msg, - code: String(errors.erro.cod) - } - ] - } else { - const errors = rawErrors as IShortenedError - - returnData.errors = Object.keys(errors).map((errCode) => ({ - detail: errors[errCode], - code: String(errCode) - })) - } - } - - return returnData -} diff --git a/src/core/interfaces/method.ts b/src/core/interfaces/method.ts deleted file mode 100644 index 799eb01..0000000 --- a/src/core/interfaces/method.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - AxiosInstance as IAxiosInstance, - AxiosError as IAxiosError -} from 'axios' - -export type IApiInstance = IAxiosInstance -export type IApiError = IAxiosError - -export interface ISingularEntity { - [singular: string]: T -} - -export interface IPluralEntity { - [plural: string]: ISingularEntity[] | ISingularEntity | T -} - -export interface ISingularError { - erro: { - cod: number - msg: string - } -} - -export interface IShortenedError { - [code: string]: string -} - -export interface IPluralError { - erros: ISingularError[] | ISingularError | IShortenedError | string[] -} - -export interface IPluralResponse { - retorno: IPluralEntity | IPluralError -} - -export interface ISingularResponse { - retorno: ISingularEntity | IPluralError -} diff --git a/src/core/template/method.ts b/src/core/template/method.ts deleted file mode 100644 index 90fcbae..0000000 --- a/src/core/template/method.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IApiInstance } from '../interfaces/method' - -import axios from 'axios' - -export default abstract class Method { - protected api: IApiInstance = axios - protected raw? = false - protected endpoint?: string - protected singularName = '' - protected pluralName = '' - - constructor (args?: { - api: IApiInstance - raw?: boolean - endpoint?: string - singularName: string - pluralName: string - }) { - if (args) { - this.api = args.api - this.endpoint = args.endpoint - this.singularName = args.singularName - this.pluralName = args.pluralName - - if (args.raw) { - this.raw = args.raw - } - } - } -} diff --git a/src/entities/@shared/entity.ts b/src/entities/@shared/entity.ts new file mode 100644 index 0000000..fa56658 --- /dev/null +++ b/src/entities/@shared/entity.ts @@ -0,0 +1,40 @@ +import convertDateToString from '../../helpers/functions/convert-date-to-string' +import { IBlingRepository } from '../../repositories/bling.repository.interface' + +/** + * Entidade base para o projeto. + */ +export abstract class Entity { + /** @property Repositório para conexão com o Bling. */ + protected repository: IBlingRepository + + /** + * Constrói o objeto. + * + * @param repository O repositório para uso da integração. + */ + constructor(repository: IBlingRepository) { + this.repository = repository + } + + /** + * Prepara um parâmetro de data para chamada do repositório. + * + * @param param Parâmetro do tipo `string`, `Date` ou `undefined` + * + * @returns {string|undefined} + */ + protected prepareStringOrDateParam( + param?: string | Date + ): string | undefined { + if (param === undefined) { + return undefined + } + + if (typeof param === 'string') { + return param + } + + return convertDateToString(param) + } +} diff --git a/src/entities/@shared/interfaces/error.interface.ts b/src/entities/@shared/interfaces/error.interface.ts new file mode 100644 index 0000000..282bc9d --- /dev/null +++ b/src/entities/@shared/interfaces/error.interface.ts @@ -0,0 +1,27 @@ +interface IDefaultErrorFieldsCollectionItemResponse { + index: number + code: number + msg: string + element: string + namespace: string +} + +export interface IDefaultErrorFieldsResponse { + code: number + msg: string + element: string + namespace: string + collection: IDefaultErrorFieldsCollectionItemResponse[] +} + +/** + * Interface padrão para erros da API. + */ +export interface IDefaultErrorResponse { + error: { + type: string + message: string + description: string + fields?: IDefaultErrorFieldsResponse[] + } +} diff --git a/src/entities/@shared/types/contribuinte.type.ts b/src/entities/@shared/types/contribuinte.type.ts new file mode 100644 index 0000000..5dcef05 --- /dev/null +++ b/src/entities/@shared/types/contribuinte.type.ts @@ -0,0 +1,10 @@ +/** + * Tipagem referente ao tipo de contribuinte do ICMS. + * + * - `1`: Contribuinte do ICMS + * - `2`: Contribuinte isento de ICMS + * - `9`: Não contribuinte. + */ +type IContribuinte = 1 | 2 | 9 + +export default IContribuinte diff --git a/src/entities/@shared/types/crt.type.ts b/src/entities/@shared/types/crt.type.ts new file mode 100644 index 0000000..b92a6bd --- /dev/null +++ b/src/entities/@shared/types/crt.type.ts @@ -0,0 +1,10 @@ +/** + * Tipagem referente ao código de regime tributário. + * + * - `1`: Simples Nacional + * - `2`: Simples Nacional - excesso de sublimite de receita bruta + * - `3`: Regime Normal + */ +type ICRT = 1 | 2 | 3 + +export default ICRT diff --git a/src/entities/@shared/types/frete-por-conta.type.ts b/src/entities/@shared/types/frete-por-conta.type.ts new file mode 100644 index 0000000..2099463 --- /dev/null +++ b/src/entities/@shared/types/frete-por-conta.type.ts @@ -0,0 +1,13 @@ +/** + * Tipagem referente ao tipo de frete. + * + * - `0`: Contratação do Frete por conta do Remetente (CIF) + * - `1`: Contratação do Frete por conta do Destinatário (FOB) + * - `2`: Contratação do Frete por conta de Terceiros + * - `3`: Transporte Próprio por conta do Remetente + * - `4`: Transporte Próprio por conta do Destinatário + * - `9`: Sem Ocorrência de Transporte + */ +type IFretePorConta = 0 | 1 | 2 | 3 | 4 | 9 + +export default IFretePorConta diff --git a/src/entities/@shared/types/modelo-documento-referenciado.type.ts b/src/entities/@shared/types/modelo-documento-referenciado.type.ts new file mode 100644 index 0000000..6cb20da --- /dev/null +++ b/src/entities/@shared/types/modelo-documento-referenciado.type.ts @@ -0,0 +1,13 @@ +/** + * Tipagem referente ao modelo do documento fiscal referenciado. + * + * - `1`: Nota fiscal talão + * - `2`: Nota fiscal de consumidor talão + * - `2D`: Cupom fiscal + * - `4`: Nota de produtor + * - `55`: NF-e + * - `65`: NFC-e + */ +type IModeloDocumentoReferenciado = '1' | '2' | '2D' | '4' | '55' | '65' + +export default IModeloDocumentoReferenciado diff --git a/src/entities/@shared/types/origem.type.ts b/src/entities/@shared/types/origem.type.ts new file mode 100644 index 0000000..cf2a5d3 --- /dev/null +++ b/src/entities/@shared/types/origem.type.ts @@ -0,0 +1,16 @@ +/** + * Tipagem referente à origem de um item. + * + * - `0`: Nacional, exceto as indicadas nos códigos 3, 4, 5 e 8 + * - `1`: Estrangeira - Importação direta, exceto a indicada no código 6 + * - `2`: Estrangeira - Adquirida no mercado interno, exceto a indicada no código 7 + * - `3`: Nacional, mercadoria ou bem com Conteúdo de Importação superior a 40% e inferior ou igual a 70% + * - `4`: Nacional, cuja produção tenha sido feita em conformidade com os processos produtivos básicos de que tratam as legislações citadas nos Ajustes + * - `5`: Nacional, mercadoria ou bem com Conteúdo de Importação inferior ou igual a 40% + * - `6`: Estrangeira - Importação direta, sem similar nacional, constante em lista da CAMEX + * - `7`: Estrangeira - Adquirida no mercado interno, sem similar nacional, constante em lista da CAMEX + * - `8`: Nacional, mercadoria ou bem com Conteúdo de Importação superior a 70% + */ +type IOrigem = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 + +export default IOrigem diff --git a/src/entities/@shared/types/situacao.type.ts b/src/entities/@shared/types/situacao.type.ts new file mode 100644 index 0000000..765472e --- /dev/null +++ b/src/entities/@shared/types/situacao.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente a uma situação. + * + * - `0`: Inativo + * - `1`: Ativo + */ +type ISituacao = 0 | 1 + +export default ISituacao diff --git a/src/entities/@shared/types/tipo-item.type.ts b/src/entities/@shared/types/tipo-item.type.ts new file mode 100644 index 0000000..e445efa --- /dev/null +++ b/src/entities/@shared/types/tipo-item.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente ao tipo do item de uma nota fiscal. + * + * - `P`: Produto + * - `S`: Serviço + */ +type ITipoItem = 'P' | 'S' + +export default ITipoItem diff --git a/src/entities/@shared/types/tipoPessoa.type.ts b/src/entities/@shared/types/tipoPessoa.type.ts new file mode 100644 index 0000000..70629ce --- /dev/null +++ b/src/entities/@shared/types/tipoPessoa.type.ts @@ -0,0 +1,10 @@ +/** + * Tipagem representativa do tipo de pessoa. + * + * - `F`: Física + * - `J`: Jurídica + * - `E`: Estrangeira + */ +type ITipoPessoa = 'F' | 'J' | 'E' + +export default ITipoPessoa diff --git a/src/entities/types/uf.ts b/src/entities/@shared/types/uf.type.ts similarity index 69% rename from src/entities/types/uf.ts rename to src/entities/@shared/types/uf.type.ts index 1003581..03f4389 100644 --- a/src/entities/types/uf.ts +++ b/src/entities/@shared/types/uf.type.ts @@ -1,4 +1,9 @@ -type IUFs = +/** + * Tipagem representativa de uma UF. + * + * - `UX`: Exterior + */ +type IUF = | 'AC' | 'AL' | 'AP' @@ -26,5 +31,6 @@ type IUFs = | 'SP' | 'SE' | 'TO' + | 'UX' -export default IUFs +export default IUF diff --git a/src/entities/billsToPay.ts b/src/entities/billsToPay.ts deleted file mode 100644 index c8250d8..0000000 --- a/src/entities/billsToPay.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IBillToPay { - dataEmissao?: string - vencimentoOriginal?: string - competencia?: string - nroDocumento?: string - valor: number - historico?: string - categoria?: string - portador?: string - idFormaPagamento?: string - ocorrencia: { - ocorrenciaTipo: 'U' | 'P' | 'M' | 'T' | 'S' | 'A' | 'E' - diaVencimento?: number - nroParcelas?: number - diaSemanaVencimento?: 1 | 2 | 3 | 4 | 5 | 6 | 7 - } - fornecedor: { - nome?: string - id?: string - cpf_cnpj?: string - tipoPessoa?: 'F' | 'J' - ie_rg?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - bairro?: string - cep?: string - uf?: string - email?: string - fone?: string - celular?: string - } -} - -export interface IBillToPayUpdateContent { - dataLiquidacao: string - juros?: number - desconto?: number - acrescimo?: number - tarifa?: number -} - -export interface IBillToPayFilters { - dataEmissao?: string - dataVencimento?: string - situacao?: 'pago' | 'cancelada' | 'aberto' | 'parcial' - cnpj?: string -} - -export type IBillToPayInfos = Record - -export interface IBillToPayCreateResponse { - id: number - nroDocumento: string - vencimento: number -} - -export interface IBillToPayResponse { - id: string - situacao: 'pago' | 'cancelada' | 'aberto' | 'parcial' - dataEmissao: string - vencimentoOriginal: string - vencimento: string - competencia: string - nroDocumento?: string - valor: string - saldo: string - historico?: string - categoria?: string - portador?: string - pagamento: - | { - totalPago: number - totalJuro: number - totalDesconto: number - totalAcrescimo: number - totalTarifa: number - data: string - borderos: { - bordero: { - id: string - conta: string - dataPagamento: string - valorPago: string - valorJuro: string - valorDesconto: string - valorAcrescimo: string - valorTarifa: string - } - }[] - } - | [] - ocorrencia: - | 'Única' - | 'Parcela' - | 'Mensal' - | 'Trimestral' - | 'Semestral' - | 'Anual' - | 'Semanal' - fornecedor: { - idContato: string - nome: string - tipoPessoa: 'F' | 'J' - cpf?: string - cnpj?: string - rg?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - bairro?: string - cep?: string - uf?: string - email?: string - } -} - -export default function BillsToPay (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'contapagar', - pluralName: 'contaspagar' - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy() - .findBy, - create: new Create().create, - update: new Update().update - }) -} diff --git a/src/entities/billsToReceive.ts b/src/entities/billsToReceive.ts deleted file mode 100644 index 1742633..0000000 --- a/src/entities/billsToReceive.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IBillToReceive { - dataEmissao?: string - vencimentoOriginal?: string - competencia?: string - nroDocumento?: string - valor: number - historico?: string - categoria?: string - idFormaPagamento?: string - portador?: string - vendedor?: string - ocorrencia: { - ocorrenciaTipo: 'U' | 'P' | 'M' | 'T' | 'S' | 'A' | 'E' - diaVencimento?: number - nroParcelas?: number - diaSemanaVencimento?: 1 | 2 | 3 | 4 | 5 | 6 | 7 - } - cliente: { - nome?: string - id?: string - cpf_cnpj?: string - tipoPessoa?: 'F' | 'J' - ie_rg?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - bairro?: string - cep?: string - uf?: string - email?: string - fone?: string - celular?: string - } -} - -export interface IBillToReceiveUpdateContent { - dataLiquidacao: string - juros?: number - desconto?: number - acrescimo?: number - tarifa?: number -} - -export interface IBillToReceiveFilters { - dataEmissao?: string - dataVencimento?: string - situacao?: 'pago' | 'cancelada' | 'aberto' | 'parcial' - cnpj?: string - dataPagamento?: string -} - -export type IBillToReceiveInfos = Record - -export interface IBillToReceiveCreateResponse { - id: number - nroDocumento: string - vencimento: number -} - -export interface IBillToReceiveResponse { - id: string - situacao: 'pago' | 'cancelada' | 'aberto' | 'parcial' - dataEmissao: string - vencimentoOriginal: string - vencimento: string - competencia: string - nroDocumento?: string - valor: string - saldo: string - historico?: string - categoria?: string - idFormaPagamento?: string - portador?: string - linkBoleto: string - vendedor?: string - pagamento: - | { - totalPago: number - totalJuro: number - totalDesconto: number - totalAcrescimo: number - totalTarifa: number - data: string - } - | [] - ocorrencia: - | 'Única' - | 'Parcela' - | 'Mensal' - | 'Trimestral' - | 'Semestral' - | 'Anual' - | 'Semanal' - cliente: { - idContato: string - nome: string - tipoPessoa: 'F' | 'J' - cpf?: string - cnpj?: string - rg?: string - ie?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - bairro?: string - cep?: string - uf?: string - email?: string - } -} - -export default function BillsToReceive (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'contareceber', - pluralName: 'contasreceber' - } - - const create = async ( - data: IBillToReceive, - options?: { - raw?: boolean - } - ) => { - const createMethod = new Create< - IBillToReceive, - IBillToReceiveCreateResponse - >({ - ...config, - endpoint: 'contareceber', - singularName: 'contaReceber' - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await createMethod.create(data, { raw: true }) - } else { - return await createMethod.create(data, { raw: false }) - } - } else { - return await createMethod.create(data, { raw: false }) - } - } - - return Object.assign(config, { - all: new All< - IBillToReceiveResponse, - IBillToReceiveFilters, - IBillToReceiveInfos - >().all, - find: new Find().find, - findBy: new FindBy< - IBillToReceiveResponse, - IBillToReceiveFilters, - IBillToReceiveInfos - >().findBy, - create, - update: new Update() - .update - }) -} diff --git a/src/entities/borderos.ts b/src/entities/borderos.ts deleted file mode 100644 index e4d0c27..0000000 --- a/src/entities/borderos.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import Delete from '../core/functions/delete' - -export interface IBorderoResponse { - id: string - mensagem: string -} - -export default function Borderos (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'bordero', - pluralName: 'borderos' - } - - return Object.assign(config, { - delete: new Delete().delete - }) -} diff --git a/src/entities/borderos/__tests__/delete-response.ts b/src/entities/borderos/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/borderos/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/borderos/__tests__/find-response.ts b/src/entities/borderos/__tests__/find-response.ts new file mode 100644 index 0000000..5a38c95 --- /dev/null +++ b/src/entities/borderos/__tests__/find-response.ts @@ -0,0 +1,26 @@ +export default { + data: { + id: 12345678, + data: '2023-01-12', + historico: 'Referente ao pedido nº 12345678', + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + pagamentos: [ + { + contato: { + id: 12345678 + }, + numeroDocumento: '', + valorPago: 1500.75, + juros: 10, + desconto: 10, + acrescimo: 10, + tarifa: 10 + } + ] + } +} diff --git a/src/entities/borderos/__tests__/index.spec.ts b/src/entities/borderos/__tests__/index.spec.ts new file mode 100644 index 0000000..529da80 --- /dev/null +++ b/src/entities/borderos/__tests__/index.spec.ts @@ -0,0 +1,49 @@ +import { Chance } from 'chance' +import { Borderos } from '../' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import deleteResponse from './delete-response' +import findResponse from './find-response' + +const chance = Chance() + +describe('Borderôs entity', () => { + let repository: InMemoryBlingRepository + let entity: Borderos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Borderos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete borderô successfully', async () => { + const idBordero = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idBordero }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'borderos', + id: String(idBordero) + }) + expect(response).toBeNull() + }) + + it('should find borderô successfully', async () => { + const idBordero = chance.natural() + const spy = jest.spyOn(repository, 'show') + repository.setResponse(findResponse) + + const response = await entity.find({ idBordero }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'borderos', + id: String(idBordero) + }) + expect(response).toBe(findResponse) + }) +}) diff --git a/src/entities/borderos/index.ts b/src/entities/borderos/index.ts new file mode 100644 index 0000000..312ec2b --- /dev/null +++ b/src/entities/borderos/index.ts @@ -0,0 +1,44 @@ +import { Entity } from '../@shared/entity' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindSuccessResponse } from './interfaces/find.interface' + +/** + * Entidade para interação com borderôs. + * + * @see https://developer.bling.com.br/referencia#/Border%C3%B4s + */ +export class Borderos extends Entity { + /** + * Remove um borderô. + * + * @param params Parâmetros para a deleção (somente o ID). + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Border%C3%B4s/delete_borderos__idBordero_ + */ + async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'borderos', + id: String(params.idBordero) + }) + } + + /** + * Encontra um borderô. + * + * @param params Parâmetros para a busca (somente o ID). + * + * @returns {Promise} Os dados do borderô pesquisado. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Border%C3%B4s/get_borderos__idBordero_ + */ + async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'borderos', + id: String(params.idBordero) + }) + } +} diff --git a/src/entities/borderos/interfaces/delete.interface.ts b/src/entities/borderos/interfaces/delete.interface.ts new file mode 100644 index 0000000..c658443 --- /dev/null +++ b/src/entities/borderos/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +/** + * Parâmetros para deleção de um borderô. + */ +export interface IDeleteParams { + idBordero: number +} diff --git a/src/entities/borderos/interfaces/find.interface.ts b/src/entities/borderos/interfaces/find.interface.ts new file mode 100644 index 0000000..06a68e6 --- /dev/null +++ b/src/entities/borderos/interfaces/find.interface.ts @@ -0,0 +1,34 @@ +/** + * Parâmetros para encontrar um borderô. + */ +export interface IFindParams { + idBordero: number +} + +/** + * Interface de resposta bem sucedida ao encontrar um borderô. + */ +export interface IFindSuccessResponse { + data: { + id: number + data: string + historico: string + portador: { + id: number + } + categoria: { + id: number + } + pagamentos: { + contato: { + id: number + } + numeroDocumento: string + valorPago: number + juros: number + desconto: number + acrescimo: number + tarifa: number + }[] + } +} diff --git a/src/entities/camposCustomizados/__tests__/change-situation-response.ts b/src/entities/camposCustomizados/__tests__/change-situation-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/change-situation-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/camposCustomizados/__tests__/create-response.ts b/src/entities/camposCustomizados/__tests__/create-response.ts new file mode 100644 index 0000000..cf17236 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/create-response.ts @@ -0,0 +1,37 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export default { + data: { + id: 12345678, + idsVinculosAgrupadores: [12345678], + idsOpcoes: [12345678] + } +} + +export const createRequestBody = { + nome: 'Marca', + situacao: 1 as ISituacao, + placeholder: 'Informe a marca do produto', + obrigatorio: false, + opcoes: [ + { + id: 12345678, + nome: 'Opção 1' + } + ], + tamanho: { + minimo: 1, + maximo: 10 + }, + agrupadores: [ + { + id: 12345678 + } + ], + modulo: { + id: 12345678 + }, + tipoCampo: { + id: 12345678 + } +} diff --git a/src/entities/camposCustomizados/__tests__/delete-response.ts b/src/entities/camposCustomizados/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/camposCustomizados/__tests__/find-by-module-response.ts b/src/entities/camposCustomizados/__tests__/find-by-module-response.ts new file mode 100644 index 0000000..60f5ead --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/find-by-module-response.ts @@ -0,0 +1,9 @@ +export default { + data: [ + { + id: 12345678, + nome: 'Marca', + situacao: 1 + } + ] +} diff --git a/src/entities/camposCustomizados/__tests__/find-response.ts b/src/entities/camposCustomizados/__tests__/find-response.ts new file mode 100644 index 0000000..d077581 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/find-response.ts @@ -0,0 +1,30 @@ +export default { + data: { + id: 12345678, + nome: 'Marca', + situacao: 1, + placeholder: 'Informe a marca do produto', + obrigatorio: false, + opcoes: [ + { + id: 12345678, + nome: 'Opção 1' + } + ], + tamanho: { + minimo: 1, + maximo: 10 + }, + agrupadores: [ + { + id: 12345678 + } + ], + modulo: { + id: 12345678 + }, + tipoCampo: { + id: 12345678 + } + } +} diff --git a/src/entities/camposCustomizados/__tests__/get-modules-response.ts b/src/entities/camposCustomizados/__tests__/get-modules-response.ts new file mode 100644 index 0000000..ac63064 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/get-modules-response.ts @@ -0,0 +1,17 @@ +export default { + data: [ + { + id: 12345678, + nome: 'Clientes e Fornecedores', + modulo: 'Contatos', + agrupador: 'Tipo de contato', + permissoes: [ + { + nome: 'Clientes e Fornecedores', + modulo: 'Contatos', + autorizado: true + } + ] + } + ] +} diff --git a/src/entities/camposCustomizados/__tests__/get-types-response.ts b/src/entities/camposCustomizados/__tests__/get-types-response.ts new file mode 100644 index 0000000..4eb8ece --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/get-types-response.ts @@ -0,0 +1,9 @@ +export default { + data: [ + { + id: 12345678, + nome: 'Inteiro', + mascara: '' + } + ] +} diff --git a/src/entities/camposCustomizados/__tests__/index.spec.ts b/src/entities/camposCustomizados/__tests__/index.spec.ts new file mode 100644 index 0000000..9ee9677 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/index.spec.ts @@ -0,0 +1,150 @@ +import { Chance } from 'chance' +import { CamposCustomizados } from '../' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import ISituacao from '../../@shared/types/situacao.type' +import changeSituationResponse from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findByModuleResponse from './find-by-module-response' +import findResponse from './find-response' +import getModulesResponse from './get-modules-response' +import getTypesResponse from './get-types-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Campos customizados entity', () => { + let repository: InMemoryBlingRepository + let entity: CamposCustomizados + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new CamposCustomizados(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idCampoCustomizado = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idCampoCustomizado }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados', + id: String(idCampoCustomizado) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get modules successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getModulesResponse) + + const response = await entity.getModules() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados/modulos' + }) + expect(response).toBe(getModulesResponse) + }) + + it('should get types successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getTypesResponse) + + const response = await entity.getTypes() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados/tipos' + }) + expect(response).toBe(getTypesResponse) + }) + + it('should find by module successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idModulo = chance.natural() + const pagina = chance.pickone([chance.natural(), undefined]) + const limite = chance.pickone([chance.natural(), undefined]) + repository.setResponse(findByModuleResponse) + + const response = await entity.findByModule({ idModulo, pagina, limite }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados/modulos', + id: String(idModulo), + params: { + pagina, + limite + } + }) + expect(response).toBe(findByModuleResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idCampoCustomizado = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idCampoCustomizado }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados', + id: String(idCampoCustomizado) + }) + expect(response).toBe(findResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idCampoCustomizado = chance.natural() + const situacao = chance.pickone([0, 1]) as ISituacao + repository.setResponse(changeSituationResponse) + + const response = await entity.changeSituation({ + idCampoCustomizado, + situacao + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados', + id: `${idCampoCustomizado}/situacoes`, + body: { situacao } + }) + expect(response).toBe(changeSituationResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idCampoCustomizado = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idCampoCustomizado, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'campos-customizados', + id: String(idCampoCustomizado), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/camposCustomizados/__tests__/update-response.ts b/src/entities/camposCustomizados/__tests__/update-response.ts new file mode 100644 index 0000000..b8c8e43 --- /dev/null +++ b/src/entities/camposCustomizados/__tests__/update-response.ts @@ -0,0 +1,31 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export default { + data: { + id: 12345678, + idsVinculosAgrupadores: [12345678], + idsOpcoes: [12345678] + } +} + +export const updateRequestBody = { + nome: 'Marca', + situacao: 1 as ISituacao, + placeholder: 'Informe a marca do produto', + obrigatorio: false, + opcoes: [ + { + id: 12345678, + nome: 'Opção 1' + } + ], + tamanho: { + minimo: 1, + maximo: 10 + }, + agrupadores: [ + { + id: 12345678 + } + ] +} diff --git a/src/entities/camposCustomizados/index.ts b/src/entities/camposCustomizados/index.ts new file mode 100644 index 0000000..a80c00e --- /dev/null +++ b/src/entities/camposCustomizados/index.ts @@ -0,0 +1,166 @@ +import { Entity } from '../@shared/entity' +import { + IChangeSituationBody, + IChangeSituationParams +} from './interfaces/change-situation.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { + IFindByModuleParams, + IFindByModuleResponse +} from './interfaces/find-by-module.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetModuleResponse } from './interfaces/get-modules.interface' +import { IGetTypeResponse } from './interfaces/get-types.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com campos customizados. + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados + */ +export class CamposCustomizados extends Entity { + /** + * Remove um campo customizado. + * + * @param params Parâmetros da deleção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/delete_campos_customizados__idCampoCustomizado_ + */ + async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'campos-customizados', + id: String(params.idCampoCustomizado) + }) + } + + /** + * Obtém módulos que possuem campos customizados. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/get_campos_customizados_modulos + */ + async getModules(): Promise { + return await this.repository.index({ + endpoint: 'campos-customizados/modulos' + }) + } + + /** + * Obtém tipos de campos customizados. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/get_campos_customizados_tipos + */ + async getTypes(): Promise { + return await this.repository.index({ + endpoint: 'campos-customizados/tipos' + }) + } + + /** + * Obtém campos customizados por módulo. + * + * @param {IFindByModuleParams} params Parâmetros da busca + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/get_campos_customizados_modulos__idModulo_ + */ + async findByModule( + params: IFindByModuleParams + ): Promise { + return await this.repository.show({ + endpoint: 'campos-customizados/modulos', + id: String(params.idModulo), + params: { + pagina: params.pagina, + limite: params.limite + } + }) + } + + /** + * Obtém um campo customizado. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/get_campos_customizados__idCampoCustomizado_ + */ + async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'campos-customizados', + id: String(params.idCampoCustomizado) + }) + } + + /** + * Altera a situação de um campo customizado. + * + * @param {IChangeSituationParams & IChangeSituationBody} params Parâmetros da atualização. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/patch_campos_customizados__idCampoCustomizado__situacoes + */ + async changeSituation( + params: IChangeSituationParams & IChangeSituationBody + ): Promise { + return await this.repository.update({ + endpoint: 'campos-customizados', + id: `${params.idCampoCustomizado}/situacoes`, + body: { situacao: params.situacao } + }) + } + + /** + * Cria um campo customizado. + * + * @param {ICreateBody} body O corpo da requisição. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/post_campos_customizados + */ + async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'campos-customizados', + body + }) + } + + /** + * Altera um campo customizado. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Campos%20Customizados/put_campos_customizados__idCampoCustomizado_ + */ + async update(params: IUpdateParams & IUpdateBody): Promise { + const { idCampoCustomizado, ...body } = params + + return await this.repository.update({ + endpoint: 'campos-customizados', + id: String(idCampoCustomizado), + body + }) + } +} diff --git a/src/entities/camposCustomizados/interfaces/change-situation.interface.ts b/src/entities/camposCustomizados/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..79501dc --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/change-situation.interface.ts @@ -0,0 +1,9 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export interface IChangeSituationParams { + idCampoCustomizado: number +} + +export interface IChangeSituationBody { + situacao: ISituacao +} diff --git a/src/entities/camposCustomizados/interfaces/create.interface.ts b/src/entities/camposCustomizados/interfaces/create.interface.ts new file mode 100644 index 0000000..b6eaddf --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/create.interface.ts @@ -0,0 +1,27 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export interface ICreateBody { + nome: string + situacao?: ISituacao + placeholder?: string + obrigatorio?: boolean + opcoes?: { + id: number + nome: string + }[] + tamanho?: { + minimo?: number + maximo?: number + } + agrupadores?: { id: number }[] + modulo: { id: number } + tipoCampo: { id: number } +} + +export interface ICreateResponse { + data: { + id: number + idsVinculosAgrupadores: number[] + idsOpcoes: number[] + } +} diff --git a/src/entities/camposCustomizados/interfaces/delete.interface.ts b/src/entities/camposCustomizados/interfaces/delete.interface.ts new file mode 100644 index 0000000..7d0b9ed --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/delete.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteParams { + idCampoCustomizado: number +} diff --git a/src/entities/camposCustomizados/interfaces/find-by-module.interface.ts b/src/entities/camposCustomizados/interfaces/find-by-module.interface.ts new file mode 100644 index 0000000..78cb22d --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/find-by-module.interface.ts @@ -0,0 +1,15 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export interface IFindByModuleParams { + idModulo: number + pagina?: number + limite?: number +} + +export interface IFindByModuleResponse { + data: { + id: number + nome: string + situacao: ISituacao + }[] +} diff --git a/src/entities/camposCustomizados/interfaces/find.interface.ts b/src/entities/camposCustomizados/interfaces/find.interface.ts new file mode 100644 index 0000000..3af7187 --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/find.interface.ts @@ -0,0 +1,26 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export interface IFindParams { + idCampoCustomizado: number +} + +export interface IFindResponse { + data: { + id: number + nome: string + situacao: ISituacao + placeholder: string + obrigatorio: boolean + opcoes: { + id: number + nome: string + }[] + tamanho: { + minimo: number + maximo: number + } + agrupadores: { id: number }[] + modulo: { id: number } + tipoCampo: { id: number } + } +} diff --git a/src/entities/camposCustomizados/interfaces/get-modules.interface.ts b/src/entities/camposCustomizados/interfaces/get-modules.interface.ts new file mode 100644 index 0000000..f361b80 --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/get-modules.interface.ts @@ -0,0 +1,13 @@ +export interface IGetModuleResponse { + data: { + id: number + nome: string + modulo: string + agrupador: string + permissoes: { + nome: string + modulo: string + autorizado: boolean + }[] + }[] +} diff --git a/src/entities/camposCustomizados/interfaces/get-types.interface.ts b/src/entities/camposCustomizados/interfaces/get-types.interface.ts new file mode 100644 index 0000000..83cd2e2 --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/get-types.interface.ts @@ -0,0 +1,7 @@ +export interface IGetTypeResponse { + data: { + id: number + nome: string + mascara: string + }[] +} diff --git a/src/entities/camposCustomizados/interfaces/update.interface.ts b/src/entities/camposCustomizados/interfaces/update.interface.ts new file mode 100644 index 0000000..ae687fe --- /dev/null +++ b/src/entities/camposCustomizados/interfaces/update.interface.ts @@ -0,0 +1,29 @@ +import ISituacao from '../../@shared/types/situacao.type' + +export interface IUpdateParams { + idCampoCustomizado: number +} + +export interface IUpdateBody { + nome: string + situacao?: ISituacao + placeholder?: string + obrigatorio?: boolean + opcoes?: { + id: number + nome: string + }[] + tamanho?: { + minimo?: number + maximo?: number + } + agrupadores: { id: number }[] +} + +export interface IUpdateResponse { + data: { + id: number + idsVinculosAgrupadores: number[] + idsOpcoes: number[] + } +} diff --git a/src/entities/categoriasLojas/__tests__/create-response.ts b/src/entities/categoriasLojas/__tests__/create-response.ts new file mode 100644 index 0000000..37da4aa --- /dev/null +++ b/src/entities/categoriasLojas/__tests__/create-response.ts @@ -0,0 +1,16 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + loja: { + id: 12345678 + }, + descricao: 'Categoria de produto vinculado à loja', + codigo: '12345678', + categoriaProduto: { + id: 12345678 + } +} diff --git a/src/entities/categoriasLojas/__tests__/delete-response.ts b/src/entities/categoriasLojas/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/categoriasLojas/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/categoriasLojas/__tests__/find-response.ts b/src/entities/categoriasLojas/__tests__/find-response.ts new file mode 100644 index 0000000..0044a6f --- /dev/null +++ b/src/entities/categoriasLojas/__tests__/find-response.ts @@ -0,0 +1,13 @@ +export default { + data: { + id: 12345678, + loja: { + id: 12345678 + }, + descricao: 'Categoria de produto vinculado à loja', + codigo: '12345678', + categoriaProduto: { + id: 12345678 + } + } +} diff --git a/src/entities/categoriasLojas/__tests__/get-response.ts b/src/entities/categoriasLojas/__tests__/get-response.ts new file mode 100644 index 0000000..0eae565 --- /dev/null +++ b/src/entities/categoriasLojas/__tests__/get-response.ts @@ -0,0 +1,15 @@ +export default { + data: [ + { + id: 12345678, + loja: { + id: 12345678 + }, + descricao: 'Categoria de produto vinculado à loja', + codigo: '12345678', + categoriaProduto: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/categoriasLojas/__tests__/index.spec.ts b/src/entities/categoriasLojas/__tests__/index.spec.ts new file mode 100644 index 0000000..eabad54 --- /dev/null +++ b/src/entities/categoriasLojas/__tests__/index.spec.ts @@ -0,0 +1,102 @@ +import { Chance } from 'chance' +import { CategoriasLojas } from '../' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Categorias - Lojas entity', () => { + let repository: InMemoryBlingRepository + let entity: CategoriasLojas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new CategoriasLojas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idCategoriaLoja = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idCategoriaLoja }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/lojas', + id: String(idCategoriaLoja) + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idCategoriaLoja = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idCategoriaLoja }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/lojas', + id: String(idCategoriaLoja) + }) + expect(response).toBe(findResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/lojas', + params: { + idCategoriaProduto: undefined, + idCategoriaProdutoPai: undefined, + idLoja: undefined, + limite: undefined, + pagina: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/lojas', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idCategoriaLoja = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idCategoriaLoja, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/lojas', + id: String(idCategoriaLoja), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/categoriasLojas/__tests__/update-response.ts b/src/entities/categoriasLojas/__tests__/update-response.ts new file mode 100644 index 0000000..1435b7d --- /dev/null +++ b/src/entities/categoriasLojas/__tests__/update-response.ts @@ -0,0 +1,12 @@ +export default null + +export const updateRequestBody = { + loja: { + id: 12345678 + }, + descricao: 'Categoria de produto vinculado à loja', + codigo: '12345678', + categoriaProduto: { + id: 12345678 + } +} diff --git a/src/entities/categoriasLojas/index.ts b/src/entities/categoriasLojas/index.ts new file mode 100644 index 0000000..35f57d3 --- /dev/null +++ b/src/entities/categoriasLojas/index.ts @@ -0,0 +1,107 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IUpdateBody, IUpdateParams } from './interfaces/update.interface' + +/** + * Entidade para interação com Categorias - Lojas. + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Lojas + */ +export class CategoriasLojas extends Entity { + /** + * Remove o vínculo de uma categoria da loja com a de produto. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Lojas/delete_categorias_lojas__idCategoriaLoja_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'categorias/lojas', + id: String(params.idCategoriaLoja) + }) + } + + /** + * Obtém categorias de lojas virtuais vinculadas a de produtos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Lojas/get_categorias_lojas + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'categorias/lojas', + params: { + pagina: params?.pagina, + limite: params?.limite, + idLoja: params?.idLoja, + idCategoriaProduto: params?.idCategoriaProduto, + idCategoriaProdutoPai: params?.idCategoriaProdutoPai + } + }) + } + + /** + * Obtém uma categoria da loja vinculada a de produto. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Lojas/get_categorias_lojas__idCategoriaLoja_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'categorias/lojas', + id: String(params.idCategoriaLoja) + }) + } + + /** + * Cria o vínculo de uma categoria da loja com a de produto. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Lojas/post_categorias_lojas + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'categorias/lojas', + body + }) + } + + /** + * Altera o vínculo de uma categoria da loja com a de produto. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Lojas/put_categorias_lojas__idCategoriaLoja_ + */ + public async update(params: IUpdateParams & IUpdateBody): Promise { + const { idCategoriaLoja, ...body } = params + + return await this.repository.replace({ + endpoint: 'categorias/lojas', + id: String(idCategoriaLoja), + body + }) + } +} diff --git a/src/entities/categoriasLojas/interfaces/create.interface.ts b/src/entities/categoriasLojas/interfaces/create.interface.ts new file mode 100644 index 0000000..970b4cb --- /dev/null +++ b/src/entities/categoriasLojas/interfaces/create.interface.ts @@ -0,0 +1,12 @@ +export interface ICreateBody { + loja: { id: number } + descricao: string + codigo: string + categoriaProduto: { id: number } +} + +export interface ICreateResponse { + data: { + id: number + } +} diff --git a/src/entities/categoriasLojas/interfaces/delete.interface.ts b/src/entities/categoriasLojas/interfaces/delete.interface.ts new file mode 100644 index 0000000..4781798 --- /dev/null +++ b/src/entities/categoriasLojas/interfaces/delete.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteParams { + idCategoriaLoja: number +} diff --git a/src/entities/categoriasLojas/interfaces/find.interface.ts b/src/entities/categoriasLojas/interfaces/find.interface.ts new file mode 100644 index 0000000..e063044 --- /dev/null +++ b/src/entities/categoriasLojas/interfaces/find.interface.ts @@ -0,0 +1,13 @@ +export interface IFindParams { + idCategoriaLoja: number +} + +export interface IFindResponse { + data: { + id: number + loja: { id: number } + descricao: string + codigo: string + categoriaProduto: { id: number } + } +} diff --git a/src/entities/categoriasLojas/interfaces/get.interface.ts b/src/entities/categoriasLojas/interfaces/get.interface.ts new file mode 100644 index 0000000..5d8b1c7 --- /dev/null +++ b/src/entities/categoriasLojas/interfaces/get.interface.ts @@ -0,0 +1,17 @@ +export interface IGetParams { + pagina?: number + limite?: number + idLoja?: number + idCategoriaProduto?: number + idCategoriaProdutoPai?: number +} + +export interface IGetResponse { + data: { + id: number + loja: { id: number } + descricao: string + codigo: string + categoriaProduto: { id: number } + }[] +} diff --git a/src/entities/categoriasLojas/interfaces/update.interface.ts b/src/entities/categoriasLojas/interfaces/update.interface.ts new file mode 100644 index 0000000..e5262d5 --- /dev/null +++ b/src/entities/categoriasLojas/interfaces/update.interface.ts @@ -0,0 +1,10 @@ +export interface IUpdateParams { + idCategoriaLoja: number +} + +export interface IUpdateBody { + loja: { id: number } + descricao: string + codigo: string + categoriaProduto: { id: number } +} diff --git a/src/entities/categoriasProdutos/__tests__/create-response.ts b/src/entities/categoriasProdutos/__tests__/create-response.ts new file mode 100644 index 0000000..448b5e3 --- /dev/null +++ b/src/entities/categoriasProdutos/__tests__/create-response.ts @@ -0,0 +1,12 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + descricao: 'Eletrônicos', + categoriaPai: { + id: 12345678 + } +} diff --git a/src/entities/categoriasProdutos/__tests__/delete-response.ts b/src/entities/categoriasProdutos/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/categoriasProdutos/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/categoriasProdutos/__tests__/find-response.ts b/src/entities/categoriasProdutos/__tests__/find-response.ts new file mode 100644 index 0000000..b738a69 --- /dev/null +++ b/src/entities/categoriasProdutos/__tests__/find-response.ts @@ -0,0 +1,9 @@ +export default { + data: { + id: 12345678, + descricao: 'Eletrônicos', + categoriaPai: { + id: 12345678 + } + } +} diff --git a/src/entities/categoriasProdutos/__tests__/get-response.ts b/src/entities/categoriasProdutos/__tests__/get-response.ts new file mode 100644 index 0000000..6a36edf --- /dev/null +++ b/src/entities/categoriasProdutos/__tests__/get-response.ts @@ -0,0 +1,11 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Eletrônicos', + categoriaPai: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/categoriasProdutos/__tests__/index.spec.ts b/src/entities/categoriasProdutos/__tests__/index.spec.ts new file mode 100644 index 0000000..2ea77c0 --- /dev/null +++ b/src/entities/categoriasProdutos/__tests__/index.spec.ts @@ -0,0 +1,99 @@ +import { Chance } from 'chance' +import { CategoriasProdutos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Categorias - Produtos entity', () => { + let repository: InMemoryBlingRepository + let entity: CategoriasProdutos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new CategoriasProdutos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idCategoriaProduto = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idCategoriaProduto }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/produtos', + id: String(idCategoriaProduto) + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idCategoriaProduto = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idCategoriaProduto }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/produtos', + id: String(idCategoriaProduto) + }) + expect(response).toBe(findResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/produtos', + params: { + limite: undefined, + pagina: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/produtos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idCategoriaProduto = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idCategoriaProduto, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/produtos', + id: String(idCategoriaProduto), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/categoriasProdutos/__tests__/update-response.ts b/src/entities/categoriasProdutos/__tests__/update-response.ts new file mode 100644 index 0000000..06e1838 --- /dev/null +++ b/src/entities/categoriasProdutos/__tests__/update-response.ts @@ -0,0 +1,5 @@ +export default null + +export const updateRequestBody = { + descricao: 'Eletrônicos' +} diff --git a/src/entities/categoriasProdutos/index.ts b/src/entities/categoriasProdutos/index.ts new file mode 100644 index 0000000..4225818 --- /dev/null +++ b/src/entities/categoriasProdutos/index.ts @@ -0,0 +1,104 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IUpdateBody, IUpdateParams } from './interfaces/update.interface' + +/** + * Entidade para interação com Categorias - Produtos. + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Produtos + */ +export class CategoriasProdutos extends Entity { + /** + * Remove uma categoria de produto. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Produtos/delete_categorias_produtos__idCategoriaProduto_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'categorias/produtos', + id: String(params.idCategoriaProduto) + }) + } + + /** + * Obtém categorias de produtos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Produtos/get_categorias_produtos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'categorias/produtos', + params: { + pagina: params?.pagina, + limite: params?.limite + } + }) + } + + /** + * Obtém uma categoria de produto. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Produtos/get_categorias_produtos__idCategoriaProduto_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'categorias/produtos', + id: String(params.idCategoriaProduto) + }) + } + + /** + * Cria uma categoria de produto. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Produtos/post_categorias_produtos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'categorias/produtos', + body + }) + } + + /** + * Altera uma categoria de produto. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Produtos/put_categorias_produtos__idCategoriaProduto_ + */ + public async update(params: IUpdateParams & IUpdateBody): Promise { + const { idCategoriaProduto, ...body } = params + + return await this.repository.replace({ + endpoint: 'categorias/produtos', + id: String(idCategoriaProduto), + body + }) + } +} diff --git a/src/entities/categoriasProdutos/interfaces/create.interface.ts b/src/entities/categoriasProdutos/interfaces/create.interface.ts new file mode 100644 index 0000000..10a2189 --- /dev/null +++ b/src/entities/categoriasProdutos/interfaces/create.interface.ts @@ -0,0 +1,12 @@ +export interface ICreateBody { + descricao: string + categoriaPai?: { + id: number + } +} + +export interface ICreateResponse { + data: { + id: number + } +} diff --git a/src/entities/categoriasProdutos/interfaces/delete.interface.ts b/src/entities/categoriasProdutos/interfaces/delete.interface.ts new file mode 100644 index 0000000..46de23e --- /dev/null +++ b/src/entities/categoriasProdutos/interfaces/delete.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteParams { + idCategoriaProduto: number +} diff --git a/src/entities/categoriasProdutos/interfaces/find.interface.ts b/src/entities/categoriasProdutos/interfaces/find.interface.ts new file mode 100644 index 0000000..c6a2e07 --- /dev/null +++ b/src/entities/categoriasProdutos/interfaces/find.interface.ts @@ -0,0 +1,13 @@ +export interface IFindParams { + idCategoriaProduto: number +} + +export interface IFindResponse { + data: { + id: number + descricao: string + categoriaPai: { + id: number + } + } +} diff --git a/src/entities/categoriasProdutos/interfaces/get.interface.ts b/src/entities/categoriasProdutos/interfaces/get.interface.ts new file mode 100644 index 0000000..6e55b2e --- /dev/null +++ b/src/entities/categoriasProdutos/interfaces/get.interface.ts @@ -0,0 +1,14 @@ +export interface IGetParams { + pagina?: number + limite?: number +} + +export interface IGetResponse { + data: { + id: number + descricao: string + categoriaPai: { + id: number + } + }[] +} diff --git a/src/entities/categoriasProdutos/interfaces/update.interface.ts b/src/entities/categoriasProdutos/interfaces/update.interface.ts new file mode 100644 index 0000000..059189c --- /dev/null +++ b/src/entities/categoriasProdutos/interfaces/update.interface.ts @@ -0,0 +1,7 @@ +export interface IUpdateParams { + idCategoriaProduto: number +} + +export interface IUpdateBody { + descricao: string +} diff --git a/src/entities/categoriasReceitasDespesas/__tests__/find-response.ts b/src/entities/categoriasReceitasDespesas/__tests__/find-response.ts new file mode 100644 index 0000000..9095417 --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/__tests__/find-response.ts @@ -0,0 +1,9 @@ +export default { + data: { + id: 12345678, + idCategoriaPai: 0, + descricao: 'Vendas de mercadorias', + tipo: 1, + situacao: 1 + } +} diff --git a/src/entities/categoriasReceitasDespesas/__tests__/get-response.ts b/src/entities/categoriasReceitasDespesas/__tests__/get-response.ts new file mode 100644 index 0000000..86a67ec --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/__tests__/get-response.ts @@ -0,0 +1,10 @@ +export default { + data: [ + { + id: 12345678, + idCategoriaPai: 0, + descricao: 'Vendas de mercadorias', + tipo: 1 + } + ] +} diff --git a/src/entities/categoriasReceitasDespesas/__tests__/index.spec.ts b/src/entities/categoriasReceitasDespesas/__tests__/index.spec.ts new file mode 100644 index 0000000..1a3bfb9 --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/__tests__/index.spec.ts @@ -0,0 +1,52 @@ +import { Chance } from 'chance' +import { CategoriasReceitasDespesas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import findResponse from './find-response' +import getResponse from './get-response' +const chance = Chance() + +describe('Categorias - Receitas e Despesas entity', () => { + let repository: InMemoryBlingRepository + let entity: CategoriasReceitasDespesas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new CategoriasReceitasDespesas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idCategoria = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idCategoria }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/receitas-despesas', + id: String(idCategoria) + }) + expect(response).toBe(findResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'categorias/receitas-despesas', + params: { + limite: undefined, + pagina: undefined, + situacao: undefined, + tipo: undefined + } + }) + expect(response).toBe(getResponse) + }) +}) diff --git a/src/entities/categoriasReceitasDespesas/index.ts b/src/entities/categoriasReceitasDespesas/index.ts new file mode 100644 index 0000000..b823e64 --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/index.ts @@ -0,0 +1,49 @@ +import { Entity } from '../@shared/entity' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com Categorias - Receitas e Despesas. + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Receitas%20e%20Despesas + */ +export class CategoriasReceitasDespesas extends Entity { + /** + * Obtém categorias de receitas e despesas. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Receitas%20e%20Despesas/get_categorias_receitas_despesas + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'categorias/receitas-despesas', + params: { + pagina: params?.pagina, + limite: params?.limite, + tipo: params?.tipo, + situacao: params?.situacao + } + }) + } + + /** + * Obtém uma categoria de receita e despesa. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Categorias%20-%20Receitas%20e%20Despesas/get_categorias_receitas_despesas__idCategoria_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'categorias/receitas-despesas', + id: String(params.idCategoria) + }) + } +} diff --git a/src/entities/categoriasReceitasDespesas/interfaces/find.interface.ts b/src/entities/categoriasReceitasDespesas/interfaces/find.interface.ts new file mode 100644 index 0000000..8b1447f --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/interfaces/find.interface.ts @@ -0,0 +1,16 @@ +import { ISituacao } from '../types/situacao.type' +import { ITipo } from '../types/tipo.type' + +export interface IFindParams { + idCategoria: number +} + +export interface IFindResponse { + data: { + id: number + idCategoriaPai: number + descricao: string + tipo: ITipo + situacao: ISituacao + } +} diff --git a/src/entities/categoriasReceitasDespesas/interfaces/get.interface.ts b/src/entities/categoriasReceitasDespesas/interfaces/get.interface.ts new file mode 100644 index 0000000..ba4d874 --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/interfaces/get.interface.ts @@ -0,0 +1,18 @@ +import { ISituacao } from '../types/situacao.type' +import { ITipo } from '../types/tipo.type' + +export interface IGetParams { + pagina?: number + limite?: number + tipo?: ITipo + situacao?: ISituacao +} + +export interface IGetResponse { + data: { + id: number + idCategoriaPai: number + descricao: string + tipo: ITipo + }[] +} diff --git a/src/entities/categoriasReceitasDespesas/types/situacao.type.ts b/src/entities/categoriasReceitasDespesas/types/situacao.type.ts new file mode 100644 index 0000000..198ccc6 --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/types/situacao.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem representativa da situação da categoria. + * + * `0`: Ativas e Inativas (padrão) + * `1`: Ativas + * `2`: Inativas + */ +export type ISituacao = 0 | 1 | 2 diff --git a/src/entities/categoriasReceitasDespesas/types/tipo.type.ts b/src/entities/categoriasReceitasDespesas/types/tipo.type.ts new file mode 100644 index 0000000..e05524d --- /dev/null +++ b/src/entities/categoriasReceitasDespesas/types/tipo.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem representativa do tipo da categoria. + * + * `0`: Todas (padrão) + * `1`: Despesa + * `2`: Receita + * `3`: Receita e despesa + */ +export type ITipo = 0 | 1 | 2 | 3 diff --git a/src/entities/categories.ts b/src/entities/categories.ts deleted file mode 100644 index 2744276..0000000 --- a/src/entities/categories.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface ICategory { - descricao: string - idCategoriaPai?: number -} - -export type ICategoryFilters = Record - -export type ICategoryInfos = Record - -export interface ICategoryResponse { - id: number - descricao: string - idCategoriaPai: number -} - -export default function Categories (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'categoria', - pluralName: 'categorias' - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy() - .findBy, - create: new Create().create, - update: new Update().update - }) -} diff --git a/src/entities/commercialProposals.ts b/src/entities/commercialProposals.ts deleted file mode 100644 index 483f210..0000000 --- a/src/entities/commercialProposals.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface ICommercialProposal { - data?: string - dataProximoContato?: string - contatoAc?: string - loja?: number - numero?: number - vendedor?: string - desconto?: string - outrasDespesas?: number - validade?: number - prazoEntrega?: string - garantia?: number - observacao?: string - obsInterna?: string - assinaturaSaudacao?: string - assinaturaResponsavel?: string - cliente: { - id?: number | string - nome: string - tipoPessoa?: 'F' | 'J' | 'E' - cpfCnpj?: string - ie?: string - rg?: string - contribuinte?: '1' | '2' | '3' - endereco?: string - numero?: string - complemento?: string - bairro?: string - cep?: string - cidade?: string - uf?: string - fone?: string - celular?: string - email?: string - } - itens: { - item: { - codigo?: string - descricao?: string - un?: string - qtde: number - valorUnidade: number | string - } - }[] - transporte?: { - transportadora?: string - tipoFrete?: 'R' | 'D' | 'T' | '3' | '4' | '5' - frete?: number - } - parcelas?: { - parcela: { - nrDias: number - valor: number - obs?: string - formaPagamento: { - id: number - } - } - }[] -} - -export interface ICommercialProposalUpdateContent { - situacao: - | 'Descrição' - | 'Pendente' - | 'Aguardando' - | 'Não aprovado' - | 'Aprovado' - | 'Concluído' - | 'Rascunho' -} - -export interface ICommercialProposalFilters { - data?: string - situacao?: - | 'Descrição' - | 'Pendente' - | 'Aguardando' - | 'Não aprovado' - | 'Aprovado' - | 'Concluído' - | 'Rascunho' - idContato?: number -} - -export type ICommercialProposalInfos = Record - -export interface ICommercialProposalCreateResponse { - id: number - numero: number -} - -export interface ICommercialProposalUpdateResponse { - numero: number - mensagem: number -} - -export interface ICommercialProposalResponse { - desconto: string - obsInterna?: string - data: string - dataProximoContato: string - numeroProposta: string - vendedor?: string - valorFrete: string - subtotal: string - totalOrcamento: string - situacao: - | 'Descrição' - | 'Pendente' - | 'Aguardando' - | 'Não aprovado' - | 'Aprovado' - | 'Concluído' - | 'Rascunho' - loja: string - aosCuidadosDe?: string - garantia: string - validadeDaProposta: string - observacao?: string - prazoEntrega?: string - assinaturaSaudacao?: string - assinaturaResponsavel?: string - cliente: { - idContato: string - nome: string - cpfCnpj?: string - ie: string | 'ISENTO' - rg?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - cep?: string - uf?: string - email?: string - celular?: string - fone?: string - } - itens: { - item: { - codigo: string - descricao: string - quantidade: string - valorUnidade: string - precoLista: string - descontoItem: string - un: string - } - }[] - transporte: { - transportadora?: string - tipoFrete?: string - qtdVolumes?: string - pesoBruto?: string - } -} - -export default function CommercialProposals (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'propostacomercial', - pluralName: 'propostascomerciais' - } - - return Object.assign(config, { - all: new All< - ICommercialProposalResponse, - ICommercialProposalFilters, - ICommercialProposalInfos - >().all, - find: new Find() - .find, - findBy: new FindBy< - ICommercialProposalResponse, - ICommercialProposalFilters, - ICommercialProposalInfos - >().findBy, - create: new Create() - .create, - update: new Update() - .update - }) -} diff --git a/src/entities/contacts.ts b/src/entities/contacts.ts deleted file mode 100644 index 28056b2..0000000 --- a/src/entities/contacts.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IContact { - nome: string - fantasia?: string - tipoPessoa: 'F' | 'J' | 'E' - contribuinte: '1' | '2' | '9' - cpf_cnpj: string - ie_rg?: string - endereco?: string - numero?: string - complemento?: string - bairro?: string - cep?: string - cidade?: string - uf?: string - fone?: string - celular?: string - email?: string - emailNfe?: string - informacaoContato?: string - limiteCredito?: number - paisOrigem?: string - codigo?: string - site?: string - obs?: string - tipos_contatos?: { - tipo_contato: { - descricao?: string - } - }[] -} - -export interface IContactFilters { - dataInclusao?: string - dataAlteracao?: string - tipoPessoa?: 'F' | 'J' | 'E' -} - -export interface IContactInfos { - identificador?: '1' | '2' -} - -export interface IContactResponse { - id: string - codigo?: string - nome: string - fantasia?: string - tipo: 'F' | 'J' | 'E' - cpf: string - cnpj: string - ie_rg?: string - endereco?: string - numero?: string - bairro?: string - cep?: string - cidade?: string - complemento?: string - uf?: string - fone?: string - email?: string - situacao: string - contribuinte: '1' | '2' | '9' - site?: string - celular?: string - dataAlteracao: string - dataInclusao: string - sexo?: string - clienteDesde: string - limiteCredito: string - dataNascimento?: string - informacoesContato?: string -} - -export interface IContactCreateResponse { - id: number - nome: string - cpf_cnpj: string -} - -export interface IContactUpdateResponse { - id: string - nome: string - cpf_cnpj: string -} - -export default function Contacts (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'contato', - pluralName: 'contatos' - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy() - .findBy, - create: new Create().create, - update: new Update().update - }) -} diff --git a/src/entities/contasContabeis/__tests__/find-response.ts b/src/entities/contasContabeis/__tests__/find-response.ts new file mode 100644 index 0000000..d6c68a9 --- /dev/null +++ b/src/entities/contasContabeis/__tests__/find-response.ts @@ -0,0 +1,6 @@ +export default { + data: { + id: 12345678, + descricao: 'Contas a pagar' + } +} diff --git a/src/entities/contasContabeis/__tests__/get-response.ts b/src/entities/contasContabeis/__tests__/get-response.ts new file mode 100644 index 0000000..5401570 --- /dev/null +++ b/src/entities/contasContabeis/__tests__/get-response.ts @@ -0,0 +1,8 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Contas a pagar' + } + ] +} diff --git a/src/entities/contasContabeis/__tests__/index.spec.ts b/src/entities/contasContabeis/__tests__/index.spec.ts new file mode 100644 index 0000000..5ed0af7 --- /dev/null +++ b/src/entities/contasContabeis/__tests__/index.spec.ts @@ -0,0 +1,51 @@ +import { Chance } from 'chance' +import { ContasContabeis } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import findResponse from './find-response' +import getResponse from './get-response' + +const chance = Chance() + +describe('Contas contábeis entity', () => { + let repository: InMemoryBlingRepository + let entity: ContasContabeis + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ContasContabeis(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas-contabeis', + params: { + limite: undefined, + pagina: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idContaContabil = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idContaContabil }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas-contabeis', + id: String(idContaContabil) + }) + expect(response).toBe(findResponse) + }) +}) diff --git a/src/entities/contasContabeis/index.ts b/src/entities/contasContabeis/index.ts new file mode 100644 index 0000000..fb6018e --- /dev/null +++ b/src/entities/contasContabeis/index.ts @@ -0,0 +1,47 @@ +import { Entity } from '../@shared/entity' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com Contas Contábeis. + * + * @see https://developer.bling.com.br/referencia#/Contas%20Cont%C3%A1beis + */ +export class ContasContabeis extends Entity { + /** + * Obtém contas contábeis. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20Cont%C3%A1beis/get_contas_contabeis + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'contas-contabeis', + params: { + pagina: params?.pagina, + limite: params?.limite + } + }) + } + + /** + * Obtém uma conta contábil. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20Cont%C3%A1beis/get_contas_contabeis__idContaContabil_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'contas-contabeis', + id: String(params.idContaContabil) + }) + } +} diff --git a/src/entities/contasContabeis/interfaces/find.interface.ts b/src/entities/contasContabeis/interfaces/find.interface.ts new file mode 100644 index 0000000..28481f4 --- /dev/null +++ b/src/entities/contasContabeis/interfaces/find.interface.ts @@ -0,0 +1,10 @@ +export interface IFindParams { + idContaContabil: number +} + +export interface IFindResponse { + data: { + id: number + descricao: string + } +} diff --git a/src/entities/contasContabeis/interfaces/get.interface.ts b/src/entities/contasContabeis/interfaces/get.interface.ts new file mode 100644 index 0000000..524ec4a --- /dev/null +++ b/src/entities/contasContabeis/interfaces/get.interface.ts @@ -0,0 +1,11 @@ +export interface IGetParams { + pagina?: number + limite?: number +} + +export interface IGetResponse { + data: { + id: number + descricao: string + }[] +} diff --git a/src/entities/contasPagar/__tests__/create-response.ts b/src/entities/contasPagar/__tests__/create-response.ts new file mode 100644 index 0000000..9fedd70 --- /dev/null +++ b/src/entities/contasPagar/__tests__/create-response.ts @@ -0,0 +1,18 @@ +export default { + id: 12345678 +} + +export const createRequestBody = { + vencimento: '2023-01-12', + valor: 1500.75, + contato: { id: 12345678 }, + formaPagamento: { id: 12345678 }, + saldo: 100.75, + dataEmissao: '2023-01-12', + numeroDocumento: '', + competencia: '2023-01-12', + historico: '', + portador: { id: 12345678 }, + categoria: { id: 12345678 }, + ocorrencia: { tipo: 1 as const } +} diff --git a/src/entities/contasPagar/__tests__/delete-response.ts b/src/entities/contasPagar/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/contasPagar/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/contasPagar/__tests__/download-response.ts b/src/entities/contasPagar/__tests__/download-response.ts new file mode 100644 index 0000000..edc870c --- /dev/null +++ b/src/entities/contasPagar/__tests__/download-response.ts @@ -0,0 +1,21 @@ +export default { + bordero: { + id: 12345678 + } +} + +export const downloadRequestBody = { + data: '2023-01-12', + usarDataVencimento: false, + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + historico: '', + juros: 10.5, + desconto: 10.5, + acrescimo: 10.5, + valorRecebido: 100.5 +} diff --git a/src/entities/contasPagar/__tests__/find-response.ts b/src/entities/contasPagar/__tests__/find-response.ts new file mode 100644 index 0000000..c72c4af --- /dev/null +++ b/src/entities/contasPagar/__tests__/find-response.ts @@ -0,0 +1,28 @@ +export default { + data: { + id: 12345678, + situacao: 1, + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + saldo: 100.75, + dataEmissao: '2023-01-12', + vencimentoOriginal: '2023-01-12', + numeroDocumento: '', + competencia: '2023-01-12', + historico: '', + numeroBanco: '', + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + borderos: [0] + } +} diff --git a/src/entities/contasPagar/__tests__/get-response.ts b/src/entities/contasPagar/__tests__/get-response.ts new file mode 100644 index 0000000..5435477 --- /dev/null +++ b/src/entities/contasPagar/__tests__/get-response.ts @@ -0,0 +1,16 @@ +export default { + data: [ + { + id: 12345678, + situacao: 1, + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/contasPagar/__tests__/index.spec.ts b/src/entities/contasPagar/__tests__/index.spec.ts new file mode 100644 index 0000000..9de85c0 --- /dev/null +++ b/src/entities/contasPagar/__tests__/index.spec.ts @@ -0,0 +1,125 @@ +import { Chance } from 'chance' +import { ContasPagar } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import downloadResponse, { downloadRequestBody } from './download-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Contas a pagar entity', () => { + let repository: InMemoryBlingRepository + let entity: ContasPagar + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ContasPagar(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idContaPagar = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idContaPagar }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/pagar', + id: String(idContaPagar) + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idContaPagar = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idContaPagar }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/pagar', + id: String(idContaPagar) + }) + expect(response).toBe(findResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/pagar', + params: { + limite: undefined, + pagina: undefined, + dataEmissaoInicial: undefined, + dataEmissaoFinal: undefined, + dataVencimentoInicial: undefined, + dataVencimentoFinal: undefined, + dataPagamentoInicial: undefined, + dataPagamentoFinal: undefined, + situacao: undefined, + idContato: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/pagar', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should download successfully', async () => { + const idContaPagar = chance.natural() + const spy = jest.spyOn(repository, 'store') + repository.setResponse(downloadResponse) + + const response = await entity.download({ + idContaPagar, + ...downloadRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `contas/pagar/${idContaPagar}/baixar`, + body: downloadRequestBody + }) + expect(response).toBe(downloadResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idContaPagar = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idContaPagar, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/pagar', + id: String(idContaPagar), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/contasPagar/__tests__/update-response.ts b/src/entities/contasPagar/__tests__/update-response.ts new file mode 100644 index 0000000..ef7eb33 --- /dev/null +++ b/src/entities/contasPagar/__tests__/update-response.ts @@ -0,0 +1,25 @@ +export default { + id: 12345678 +} + +export const updateRequestBody = { + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + saldo: 100.75, + dataEmissao: '2023-01-12', + numeroDocumento: '', + competencia: '2023-01-12', + historico: '', + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + } +} diff --git a/src/entities/contasPagar/index.ts b/src/entities/contasPagar/index.ts new file mode 100644 index 0000000..e00d673 --- /dev/null +++ b/src/entities/contasPagar/index.ts @@ -0,0 +1,155 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { + IDownloadBody, + IDownloadParams, + IDownloadResponse +} from './interfaces/download.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Contas a Pagar. + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar + */ +export class ContasPagar extends Entity { + /** + * Remove uma conta a pagar. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar/delete_contas_pagar__idContaPagar_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'contas/pagar', + id: String(params.idContaPagar) + }) + } + + /** + * Obtém contas a pagar. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar/get_contas_pagar + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'contas/pagar', + params: { + pagina: params?.pagina, + limite: params?.limite, + dataEmissaoInicial: this.prepareStringOrDateParam( + params?.dataEmissaoInicial + ), + dataEmissaoFinal: this.prepareStringOrDateParam( + params?.dataEmissaoFinal + ), + dataVencimentoInicial: this.prepareStringOrDateParam( + params?.dataVencimentoInicial + ), + dataVencimentoFinal: this.prepareStringOrDateParam( + params?.dataVencimentoFinal + ), + dataPagamentoInicial: this.prepareStringOrDateParam( + params?.dataPagamentoInicial + ), + dataPagamentoFinal: this.prepareStringOrDateParam( + params?.dataPagamentoFinal + ), + situacao: params?.situacao, + idContato: params?.idContato + } + }) + } + + /** + * Obtém uma conta a pagar. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar/get_contas_pagar__idContaPagar_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'contas/pagar', + id: String(params.idContaPagar) + }) + } + + /** + * Cria uma conta a pagar. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar/post_contas_pagar + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'contas/pagar', + body + }) + } + + /** + * Cria o recebimento de uma conta a pagar. + * + * @param {IDownloadParams & IDownloadBody} params O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar/post_contas_pagar__idContaPagar__baixar + */ + public async download( + params: IDownloadParams & IDownloadBody + ): Promise { + const { idContaPagar, ...body } = params + return await this.repository.store({ + endpoint: `contas/pagar/${idContaPagar}/baixar`, + body + }) + } + + /** + * Atualiza uma conta a pagar. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Pagar/put_contas_pagar__idContaPagar_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idContaPagar, ...body } = params + + return await this.repository.replace({ + endpoint: 'contas/pagar', + id: String(idContaPagar), + body + }) + } +} diff --git a/src/entities/contasPagar/interfaces/create.interface.ts b/src/entities/contasPagar/interfaces/create.interface.ts new file mode 100644 index 0000000..bdb5e16 --- /dev/null +++ b/src/entities/contasPagar/interfaces/create.interface.ts @@ -0,0 +1,64 @@ +interface ContasReceberOcorrenciaUnicaDTO { + /** + * `1`: Única + */ + tipo: 1 +} + +interface ContasReceberOcorrenciaParceladaDTO { + /** + * `2`: Parcelada + */ + tipo: 2 + considerarDiasUteis?: boolean + diaVencimento: number + numeroParcelas?: number +} + +interface ContasReceberOcorrenciaDTO { + /** + * `3`: Mensal + * `4`: Bimestral + * `5`: Trimestral + * `6`: Semestral + * `7`: Anual + * `8`: Quinzenal + */ + tipo: 3 | 4 | 5 | 6 | 7 | 8 + considerarDiasUteis?: boolean + diaVencimento: number + dataLimite?: string +} + +interface ContasReceberOcorrenciaSemanalDTO { + /** + * `9`: Semanal + */ + tipo: 9 + considerarDiasUteis?: boolean + diaSemanaVencimento: number + dataLimite?: string +} + +export interface ICreateBody { + vencimento: string + valor: number + contato: { id: number } + formaPagamento?: { id: number } + saldo?: number + dataEmissao?: string + numeroDocumento?: string + competencia?: string + historico?: string + portador?: { id: number } + categoria?: { id: number } + ocorrencia?: + | ContasReceberOcorrenciaUnicaDTO + | ContasReceberOcorrenciaParceladaDTO + | ContasReceberOcorrenciaDTO + | ContasReceberOcorrenciaSemanalDTO +} + +export interface ICreateResponse { + id: number +} diff --git a/src/entities/contasPagar/interfaces/delete.interface.ts b/src/entities/contasPagar/interfaces/delete.interface.ts new file mode 100644 index 0000000..7ea85ce --- /dev/null +++ b/src/entities/contasPagar/interfaces/delete.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteParams { + idContaPagar: number +} diff --git a/src/entities/contasPagar/interfaces/download.interface.ts b/src/entities/contasPagar/interfaces/download.interface.ts new file mode 100644 index 0000000..04047c8 --- /dev/null +++ b/src/entities/contasPagar/interfaces/download.interface.ts @@ -0,0 +1,19 @@ +export interface IDownloadParams { + idContaPagar: number +} + +export interface IDownloadBody { + data: string + usarDataVencimento: boolean + portador: { id: number } + categoria: { id: number } + historico: string + juros?: number + desconto?: number + acrescimo?: number + valorRecebido?: number +} + +export interface IDownloadResponse { + bordero: { id: number } +} diff --git a/src/entities/contasPagar/interfaces/find.interface.ts b/src/entities/contasPagar/interfaces/find.interface.ts new file mode 100644 index 0000000..10dc7f3 --- /dev/null +++ b/src/entities/contasPagar/interfaces/find.interface.ts @@ -0,0 +1,26 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IFindParams { + idContaPagar: number +} + +export interface IFindResponse { + data: { + id: number + situacao: ISituacao + vencimento: string + valor: number + contato: { id: number } + formaPagamento: { id: number } + saldo: number + dataEmissao: string + vencimentoOriginal: string + numeroDocumento: string + competencia: string + historico: string + numeroBanco: string + portador: { id: number } + categoria: { id: number } + borderos: number[] + } +} diff --git a/src/entities/contasPagar/interfaces/get.interface.ts b/src/entities/contasPagar/interfaces/get.interface.ts new file mode 100644 index 0000000..b6e6cec --- /dev/null +++ b/src/entities/contasPagar/interfaces/get.interface.ts @@ -0,0 +1,25 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IGetParams { + pagina?: number + limite?: number + dataEmissaoInicial?: Date | string + dataEmissaoFinal?: Date | string + dataVencimentoInicial?: Date | string + dataVencimentoFinal?: Date | string + dataPagamentoInicial?: Date | string + dataPagamentoFinal?: Date | string + situacao?: ISituacao + idContato?: number +} + +export interface IGetResponse { + data: { + id: number + situacao: ISituacao + vencimento: string + valor: number + contato: { id: number } + formaPagamento: { id: number } + }[] +} diff --git a/src/entities/contasPagar/interfaces/update.interface.ts b/src/entities/contasPagar/interfaces/update.interface.ts new file mode 100644 index 0000000..0409d3c --- /dev/null +++ b/src/entities/contasPagar/interfaces/update.interface.ts @@ -0,0 +1,21 @@ +export interface IUpdateParams { + idContaPagar: number +} + +export interface IUpdateBody { + vencimento: string + valor: number + contato: { id: number } + formaPagamento?: { id: number } + saldo?: number + dataEmissao?: string + numeroDocumento?: string + competencia?: string + historico?: string + portador?: { id: number } + categoria?: { id: number } +} + +export interface IUpdateResponse { + id: number +} diff --git a/src/entities/contasPagar/types/situacao.type.ts b/src/entities/contasPagar/types/situacao.type.ts new file mode 100644 index 0000000..0d899b0 --- /dev/null +++ b/src/entities/contasPagar/types/situacao.type.ts @@ -0,0 +1,12 @@ +/** + * Tipagem representativa da situação. + * + * `1`: Em aberto + * `2`: Recebido + * `3`: Parcialmente recebido + * `4`: Devolvido + * `5`: Cancelado + * `6`: Devolvido parcial + * `7`: Confirmado + */ +export type ISituacao = 1 | 2 | 3 | 4 | 5 | 6 | 7 diff --git a/src/entities/contasReceber/__tests__/cancel-bank-slips-response.ts b/src/entities/contasReceber/__tests__/cancel-bank-slips-response.ts new file mode 100644 index 0000000..8ed70b2 --- /dev/null +++ b/src/entities/contasReceber/__tests__/cancel-bank-slips-response.ts @@ -0,0 +1,9 @@ +export default null + +export const cancelBankSlipRequest = { + type2FA: 1, + code2FA: '111111', + idOrigem: 16853468718, + idDuplicata: 16853468712, + reason: 'motivo' +} diff --git a/src/entities/contasReceber/__tests__/create-response.ts b/src/entities/contasReceber/__tests__/create-response.ts new file mode 100644 index 0000000..e7fd29c --- /dev/null +++ b/src/entities/contasReceber/__tests__/create-response.ts @@ -0,0 +1,32 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + dataEmissao: '2023-01-12', + numeroDocumento: '', + competencia: '2023-01-12', + historico: '', + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + vendedor: { + id: 12345678 + }, + ocorrencia: { + tipo: 1 as const + } +} diff --git a/src/entities/contasReceber/__tests__/delete-response.ts b/src/entities/contasReceber/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/contasReceber/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/contasReceber/__tests__/download-response.ts b/src/entities/contasReceber/__tests__/download-response.ts new file mode 100644 index 0000000..edc870c --- /dev/null +++ b/src/entities/contasReceber/__tests__/download-response.ts @@ -0,0 +1,21 @@ +export default { + bordero: { + id: 12345678 + } +} + +export const downloadRequestBody = { + data: '2023-01-12', + usarDataVencimento: false, + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + historico: '', + juros: 10.5, + desconto: 10.5, + acrescimo: 10.5, + valorRecebido: 100.5 +} diff --git a/src/entities/contasReceber/__tests__/find-response.ts b/src/entities/contasReceber/__tests__/find-response.ts new file mode 100644 index 0000000..c391764 --- /dev/null +++ b/src/entities/contasReceber/__tests__/find-response.ts @@ -0,0 +1,34 @@ +export default { + data: { + id: 12345678, + situacao: 1, + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + saldo: 100.75, + dataEmissao: '2023-01-12', + vencimentoOriginal: '2023-01-12', + numeroDocumento: '', + competencia: '2023-01-12', + historico: '', + numeroBanco: '', + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + vendedor: { + id: 12345678 + }, + borderos: [0], + ocorrencia: { + tipo: 1 as const + } + } +} diff --git a/src/entities/contasReceber/__tests__/get-bank-slips-response.ts b/src/entities/contasReceber/__tests__/get-bank-slips-response.ts new file mode 100644 index 0000000..beed0a8 --- /dev/null +++ b/src/entities/contasReceber/__tests__/get-bank-slips-response.ts @@ -0,0 +1,18 @@ +export default { + numberSale: '149', + numberNF: '000001', + amountAccounts: 1, + amountValuesAccounts: 111.2, + haveAccountWithIntegration: true, + accounts: [ + { + id: 1328793273, + idExternal: 'BWbXB', + dueDate: '2023-09-12', + value: 111.2, + situation: 'aberto', + iconSituation: 'aberto', + descriptionSituation: 'Em aberto' + } + ] +} diff --git a/src/entities/contasReceber/__tests__/get-response.ts b/src/entities/contasReceber/__tests__/get-response.ts new file mode 100644 index 0000000..5435477 --- /dev/null +++ b/src/entities/contasReceber/__tests__/get-response.ts @@ -0,0 +1,16 @@ +export default { + data: [ + { + id: 12345678, + situacao: 1, + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/contasReceber/__tests__/index.spec.ts b/src/entities/contasReceber/__tests__/index.spec.ts new file mode 100644 index 0000000..b8d3cea --- /dev/null +++ b/src/entities/contasReceber/__tests__/index.spec.ts @@ -0,0 +1,161 @@ +import { Chance } from 'chance' +import { ContasReceber } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import cancelBankSlipsResponse, { + cancelBankSlipRequest +} from './cancel-bank-slips-response' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import downloadResponse, { downloadRequestBody } from './download-response' +import findResponse from './find-response' +import getBankSlipsResponse from './get-bank-slips-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Contas a receber entity', () => { + let repository: InMemoryBlingRepository + let entity: ContasReceber + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ContasReceber(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idContaReceber = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idContaReceber }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber', + id: String(idContaReceber) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber', + params: { + limite: undefined, + pagina: undefined, + situacoes: undefined, + tipoFiltroData: undefined, + dataInicial: undefined, + dataFinal: undefined, + idsCategorias: undefined, + idPortador: undefined, + idVendedor: undefined, + idFormaPagamento: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idContaReceber = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idContaReceber }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber', + id: String(idContaReceber) + }) + expect(response).toBe(findResponse) + }) + + it('should get bank slips successfully', async () => { + const spy = jest.spyOn(repository, 'index') + const idOrigem = chance.natural() + repository.setResponse(getBankSlipsResponse) + + const response = await entity.getBankSlips({ + idOrigem + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber/view/bankslips', + params: { + idOrigem, + situations: undefined + } + }) + expect(response).toBe(getBankSlipsResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should download successfully', async () => { + const idContaReceber = chance.natural() + const spy = jest.spyOn(repository, 'store') + repository.setResponse(downloadResponse) + + const response = await entity.download({ + idContaReceber, + ...downloadRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `contas/receber/${idContaReceber}/baixar`, + body: downloadRequestBody + }) + expect(response).toBe(downloadResponse) + }) + + it('should cancel bank slips successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(cancelBankSlipsResponse) + + const response = await entity.cancelBankSlips(cancelBankSlipRequest) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber/cancel/bankslips', + body: cancelBankSlipRequest + }) + expect(response).toBe(cancelBankSlipsResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idContaReceber = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idContaReceber, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contas/receber', + id: String(idContaReceber), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/contasReceber/__tests__/update-response.ts b/src/entities/contasReceber/__tests__/update-response.ts new file mode 100644 index 0000000..d372c28 --- /dev/null +++ b/src/entities/contasReceber/__tests__/update-response.ts @@ -0,0 +1,29 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + vencimento: '2023-01-12', + valor: 1500.75, + contato: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + dataEmissao: '2023-01-12', + numeroDocumento: '', + competencia: '2023-01-12', + historico: '', + portador: { + id: 12345678 + }, + categoria: { + id: 12345678 + }, + vendedor: { + id: 12345678 + } +} diff --git a/src/entities/contasReceber/index.ts b/src/entities/contasReceber/index.ts new file mode 100644 index 0000000..5646add --- /dev/null +++ b/src/entities/contasReceber/index.ts @@ -0,0 +1,187 @@ +import { Entity } from '../@shared/entity' +import { ICancelBankSlipsBody } from './interfaces/cancel-bank-slips.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { + IDownloadBody, + IDownloadParams, + IDownloadResponse +} from './interfaces/download.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { + IGetBankSlipsParams, + IGetBankSlipsResponse +} from './interfaces/get-bank-slips.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Contas a Receber. + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber + */ +export class ContasReceber extends Entity { + /** + * Remove uma conta a receber. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/delete_contas_receber__idContaReceber_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'contas/receber', + id: String(params.idContaReceber) + }) + } + + /** + * Obtém contas a receber. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/get_contas_receber + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'contas/receber', + params: { + pagina: params?.pagina, + limite: params?.limite, + situacoes: params?.situacoes, + tipoFiltroData: params?.tipoFiltroData, + dataInicial: this.prepareStringOrDateParam(params?.dataInicial), + dataFinal: this.prepareStringOrDateParam(params?.dataFinal), + idsCategorias: params?.idsCategorias, + idPortador: params?.idPortador, + idVendedor: params?.idVendedor, + idFormaPagamento: params?.idFormaPagamento + } + }) + } + + /** + * Obtém uma conta a receber. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/get_contas_receber__idContaReceber_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'contas/receber', + id: String(params.idContaReceber) + }) + } + + /** + * Obtém os boletos - Bling conta. + * + * @param {IGetBankSlipsParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/get_contas_receber_view_bankslips + */ + public async getBankSlips( + params: IGetBankSlipsParams + ): Promise { + return await this.repository.index({ + endpoint: 'contas/receber/view/bankslips', + params: { + idOrigem: params.idOrigem, + situations: params.situations + } + }) + } + + /** + * Cria uma conta a receber. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/post_contas_receber + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'contas/receber', + body + }) + } + + /** + * Cria o recebimento de uma conta a receber. + * + * @param {IDownloadParams & IDownloadBody} params O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/post_contas_receber__idContaReceber__baixar + */ + public async download( + params: IDownloadParams & IDownloadBody + ): Promise { + const { idContaReceber, ...body } = params + return await this.repository.store({ + endpoint: `contas/receber/${idContaReceber}/baixar`, + body + }) + } + + /** + * Cancelar Boletos - Bling Conta. + * + * @param {ICancelBankSlipsBody} body Parâmetros do cancelamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/post_contas_receber_cancel_bankslips + */ + public async cancelBankSlips(body: ICancelBankSlipsBody): Promise { + return await this.repository.store({ + endpoint: 'contas/receber/cancel/bankslips', + body + }) + } + + /** + * Altera uma conta a receber. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contas%20a%20Receber/put_contas_receber__idContaReceber_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idContaReceber, ...body } = params + + return await this.repository.replace({ + endpoint: 'contas/receber', + id: String(idContaReceber), + body + }) + } +} diff --git a/src/entities/contasReceber/interfaces/cancel-bank-slips.interface.ts b/src/entities/contasReceber/interfaces/cancel-bank-slips.interface.ts new file mode 100644 index 0000000..e891853 --- /dev/null +++ b/src/entities/contasReceber/interfaces/cancel-bank-slips.interface.ts @@ -0,0 +1,37 @@ +interface ContasReceberBankSlipsCancelUnicoDTO { + type2FA: number + code2FA: string + /** + * caso for cancelar uma conta sem idOrigem enviar o valor `0` + */ + idOrigem: number + idDuplicata: number + reason: string +} + +interface ContasReceberBankSlipsCancelTodosDTO { + type2FA: number + code2FA: string + idOrigem: number + reason: string +} + +interface ContasReceberBankSlipsCancelUnicoSem2FADTO { + /** + * caso for cancelar uma conta sem idOrigem enviar o valor `0` + */ + idOrigem: number + idDuplicata: number + reason: string +} + +interface ContasReceberBankSlipsCancelTodosSem2FADTO { + idOrigem: number + reason: string +} + +export type ICancelBankSlipsBody = + | ContasReceberBankSlipsCancelUnicoDTO + | ContasReceberBankSlipsCancelTodosDTO + | ContasReceberBankSlipsCancelUnicoSem2FADTO + | ContasReceberBankSlipsCancelTodosSem2FADTO diff --git a/src/entities/contasReceber/interfaces/create.interface.ts b/src/entities/contasReceber/interfaces/create.interface.ts new file mode 100644 index 0000000..a70c9bc --- /dev/null +++ b/src/entities/contasReceber/interfaces/create.interface.ts @@ -0,0 +1,64 @@ +interface ContasReceberOcorrenciaUnicaDTO { + /** + * `1`: Única + */ + tipo: 1 +} + +interface ContasReceberOcorrenciaParceladaDTO { + /** + * `2`: Parcelada + */ + tipo: 2 + considerarDiasUteis?: boolean + diaVencimento: number + numeroParcelas?: number +} + +interface ContasReceberOcorrenciaDTO { + /** + * `3`: Mensal + * `4`: Bimestral + * `5`: Trimestral + * `6`: Semestral + * `7`: Anual + * `8`: Quinzenal + */ + tipo: 3 | 4 | 5 | 6 | 7 | 8 + considerarDiasUteis?: boolean + diaVencimento: number + dataLimite?: string +} + +interface ContasReceberOcorrenciaSemanalDTO { + /** + * `9`: Semanal + */ + tipo: 9 + considerarDiasUteis?: boolean + diaSemanaVencimento: number + dataLimite?: string +} + +export interface ICreateBody { + vencimento: string + valor: number + contato: { id: number } + formaPagamento?: { id: number } + dataEmissao?: string + numeroDocumento?: string + competencia?: string + historico?: string + portador?: { id: number } + categoria?: { id: number } + vendedor?: { id: number } + ocorrencia?: + | ContasReceberOcorrenciaUnicaDTO + | ContasReceberOcorrenciaParceladaDTO + | ContasReceberOcorrenciaDTO + | ContasReceberOcorrenciaSemanalDTO +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/contasReceber/interfaces/delete.interface.ts b/src/entities/contasReceber/interfaces/delete.interface.ts new file mode 100644 index 0000000..ac9ad75 --- /dev/null +++ b/src/entities/contasReceber/interfaces/delete.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteParams { + idContaReceber: number +} diff --git a/src/entities/contasReceber/interfaces/download.interface.ts b/src/entities/contasReceber/interfaces/download.interface.ts new file mode 100644 index 0000000..a8bdc7b --- /dev/null +++ b/src/entities/contasReceber/interfaces/download.interface.ts @@ -0,0 +1,19 @@ +export interface IDownloadParams { + idContaReceber: number +} + +export interface IDownloadBody { + data: string + usarDataVencimento: boolean + portador: { id: number } + categoria: { id: number } + historico: string + juros?: number + desconto?: number + acrescimo?: number + valorRecebido?: number +} + +export interface IDownloadResponse { + bordero: { id: number } +} diff --git a/src/entities/contasReceber/interfaces/find.interface.ts b/src/entities/contasReceber/interfaces/find.interface.ts new file mode 100644 index 0000000..9f4aeae --- /dev/null +++ b/src/entities/contasReceber/interfaces/find.interface.ts @@ -0,0 +1,74 @@ +import { ISituacao } from '../types/situacao.type' + +interface ContasReceberOcorrenciaUnicaDTO { + /** + * `1`: Única + */ + tipo: 1 +} + +interface ContasReceberOcorrenciaParceladaDTO { + /** + * `2`: Parcelada + */ + tipo: 2 + considerarDiasUteis: boolean + diaVencimento: number + numeroParcelas: number +} + +interface ContasReceberOcorrenciaDTO { + /** + * `3`: Mensal + * `4`: Bimestral + * `5`: Trimestral + * `6`: Semestral + * `7`: Anual + * `8`: Quinzenal + */ + tipo: 3 | 4 | 5 | 6 | 7 | 8 + considerarDiasUteis: boolean + diaVencimento: number + dataLimite: string +} + +interface ContasReceberOcorrenciaSemanalDTO { + /** + * `9`: Semanal + */ + tipo: 9 + considerarDiasUteis: boolean + diaSemanaVencimento: number + dataLimite: string +} + +export interface IFindParams { + idContaReceber: number +} + +export interface IFindResponse { + data: { + id: number + situacao: ISituacao + vencimento: string + valor: number + contato: { id: number } + formaPagamento: { id: number } + saldo: number + dataEmissao: string + vencimentoOriginal: string + numeroDocumento: string + competencia: string + historico: string + numeroBanco: string + portador: { id: number } + categoria: { id: number } + vendedor: { id: number } + borderos: number[] + ocorrencia: + | ContasReceberOcorrenciaUnicaDTO + | ContasReceberOcorrenciaParceladaDTO + | ContasReceberOcorrenciaDTO + | ContasReceberOcorrenciaSemanalDTO + } +} diff --git a/src/entities/contasReceber/interfaces/get-bank-slips.interface.ts b/src/entities/contasReceber/interfaces/get-bank-slips.interface.ts new file mode 100644 index 0000000..34ae20a --- /dev/null +++ b/src/entities/contasReceber/interfaces/get-bank-slips.interface.ts @@ -0,0 +1,23 @@ +import { ISituacaoString } from '../types/situacao.type' + +export interface IGetBankSlipsParams { + idOrigem: number + situations?: ISituacaoString[] +} + +export interface IGetBankSlipsResponse { + numberSale: string + numberNF: string + amountAccounts: number + amountValuesAccounts: number + haveAccountWithIntegration: true + accounts: { + id: number + idExternal: string + dueDate: string + value: number + situation: ISituacaoString + iconSituation: string + descriptionSituation: string + }[] +} diff --git a/src/entities/contasReceber/interfaces/get.interface.ts b/src/entities/contasReceber/interfaces/get.interface.ts new file mode 100644 index 0000000..7230009 --- /dev/null +++ b/src/entities/contasReceber/interfaces/get.interface.ts @@ -0,0 +1,29 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IGetParams { + pagina?: number + limite?: number + situacoes?: ISituacao[] + /** + * `E`: filtrar por data de emissão + * `V`: filtrar por data de vencimento + */ + tipoFiltroData?: 'E' | 'V' + dataInicial?: Date | string + dataFinal?: Date | string + idsCategorias?: number[] + idPortador?: number + idVendedor?: number + idFormaPagamento?: number +} + +export interface IGetResponse { + data: { + id: number + situacao: ISituacao + vencimento: string + valor: number + contato: { id: number } + formaPagamento: { id: number } + }[] +} diff --git a/src/entities/contasReceber/interfaces/update.interface.ts b/src/entities/contasReceber/interfaces/update.interface.ts new file mode 100644 index 0000000..b855265 --- /dev/null +++ b/src/entities/contasReceber/interfaces/update.interface.ts @@ -0,0 +1,23 @@ +export interface IUpdateParams { + idContaReceber: number +} + +export interface IUpdateBody { + vencimento: string + valor: number + contato: { id: number } + formaPagamento?: { id: number } + dataEmissao?: string + numeroDocumento?: string + competencia?: string + historico?: string + portador?: { id: number } + categoria?: { id: number } + vendedor?: { id: number } +} + +export interface IUpdateResponse { + data: { + id: number + } +} diff --git a/src/entities/contasReceber/types/situacao.type.ts b/src/entities/contasReceber/types/situacao.type.ts new file mode 100644 index 0000000..849fb47 --- /dev/null +++ b/src/entities/contasReceber/types/situacao.type.ts @@ -0,0 +1,24 @@ +/** + * Tipagem representativa da situação. + * + * `1`: Em aberto + * `2`: Recebido + * `3`: Parcialmente recebido + * `4`: Devolvido + * `5`: Cancelado + * `6`: Devolvido parcial + * `7`: Confirmado + */ +export type ISituacao = 1 | 2 | 3 | 4 | 5 | 6 | 7 + +/** + * Tipagem representativa da situação em formato `string`. + */ +export type ISituacaoString = + | 'aberto' + | 'confirmado' + | 'pacial' + | 'devolvido' + | 'devolvidoP' + | 'pago' + | 'cancelada' diff --git a/src/entities/contatos/__tests__/change-situation-many-response.ts b/src/entities/contatos/__tests__/change-situation-many-response.ts new file mode 100644 index 0000000..c6d3786 --- /dev/null +++ b/src/entities/contatos/__tests__/change-situation-many-response.ts @@ -0,0 +1,6 @@ +export default null + +export const changeSituationManyRequest = { + idsContatos: [12345678], + situacao: 'A' as const +} diff --git a/src/entities/contatos/__tests__/change-situation-response.ts b/src/entities/contatos/__tests__/change-situation-response.ts new file mode 100644 index 0000000..ff212c0 --- /dev/null +++ b/src/entities/contatos/__tests__/change-situation-response.ts @@ -0,0 +1,5 @@ +export default null + +export const changeSituationRequest = { + situacao: 'A' as const +} diff --git a/src/entities/contatos/__tests__/create-response.ts b/src/entities/contatos/__tests__/create-response.ts new file mode 100644 index 0000000..40e95bb --- /dev/null +++ b/src/entities/contatos/__tests__/create-response.ts @@ -0,0 +1,71 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + nome: 'Contato', + codigo: 'ASD001', + situacao: 'A' as const, + numeroDocumento: '12345678910', + telefone: '(54) 3333-4444', + celular: '(54) 99999-8888', + fantasia: 'Nome fantasia', + tipo: 'J' as const, + indicadorIe: 1 as const, + ie: '123.456.789.101', + rg: '1234567890', + orgaoEmissor: '1234567890', + email: 'contato@email.com', + endereco: { + geral: { + endereco: 'R. Olavo Bilac', + cep: '95702-000', + bairro: 'Imigrante', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + numero: '914', + complemento: 'Sede 101' + }, + cobranca: { + endereco: 'R. Olavo Bilac', + cep: '95702-000', + bairro: 'Imigrante', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + numero: '914', + complemento: 'Sede 101' + } + }, + vendedor: { + id: 12345678 + }, + dadosAdicionais: { + dataNascimento: '1990-08-24', + sexo: 'M' as const, + naturalidade: 'Brasileira' + }, + financeiro: { + limiteCredito: 0, + condicaoPagamento: '30', + categoria: { + id: 12345678 + } + }, + pais: { + nome: 'ESTADOS UNIDOS' + }, + tiposContato: [ + { + id: 12345678, + descricao: 'Fornecedor' + } + ], + pessoasContato: [ + { + id: 12345678, + descricao: 'Fornecedor Fulano' + } + ] +} diff --git a/src/entities/contatos/__tests__/delete-many-response.ts b/src/entities/contatos/__tests__/delete-many-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/contatos/__tests__/delete-many-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/contatos/__tests__/delete-response.ts b/src/entities/contatos/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/contatos/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/contatos/__tests__/find-response.ts b/src/entities/contatos/__tests__/find-response.ts new file mode 100644 index 0000000..6e6e3da --- /dev/null +++ b/src/entities/contatos/__tests__/find-response.ts @@ -0,0 +1,68 @@ +export default { + data: { + id: 12345678, + nome: 'Contato', + codigo: 'ASD001', + situacao: 'A', + numeroDocumento: '12345678910', + telefone: '(54) 3333-4444', + celular: '(54) 99999-8888', + fantasia: 'Nome fantasia', + tipo: 'J', + indicadorIe: 1, + ie: '123.456.789.101', + rg: '1234567890', + orgaoEmissor: '1234567890', + email: 'contato@email.com', + endereco: { + geral: { + endereco: 'R. Olavo Bilac', + cep: '95702-000', + bairro: 'Imigrante', + municipio: 'Bento Gonçalves', + uf: 'RS', + numero: '914', + complemento: 'Sede 101' + }, + cobranca: { + endereco: 'R. Olavo Bilac', + cep: '95702-000', + bairro: 'Imigrante', + municipio: 'Bento Gonçalves', + uf: 'RS', + numero: '914', + complemento: 'Sede 101' + } + }, + vendedor: { + id: 12345678 + }, + dadosAdicionais: { + dataNascimento: '1990-08-24', + sexo: 'M', + naturalidade: 'Brasileira' + }, + financeiro: { + limiteCredito: 0, + condicaoPagamento: '30', + categoria: { + id: 12345678 + } + }, + pais: { + nome: 'ESTADOS UNIDOS' + }, + tiposContato: [ + { + id: 12345678, + descricao: 'Fornecedor' + } + ], + pessoasContato: [ + { + id: 12345678, + descricao: 'Fornecedor Fulano' + } + ] + } +} diff --git a/src/entities/contatos/__tests__/find-types-response.ts b/src/entities/contatos/__tests__/find-types-response.ts new file mode 100644 index 0000000..08a290a --- /dev/null +++ b/src/entities/contatos/__tests__/find-types-response.ts @@ -0,0 +1,8 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Fornecedor' + } + ] +} diff --git a/src/entities/contatos/__tests__/get-response.ts b/src/entities/contatos/__tests__/get-response.ts new file mode 100644 index 0000000..cd43b28 --- /dev/null +++ b/src/entities/contatos/__tests__/get-response.ts @@ -0,0 +1,13 @@ +export default { + data: [ + { + id: 12345678, + nome: 'Contato', + codigo: 'ASD001', + situacao: 'A', + numeroDocumento: '123.456.789-10', + telefone: '(54) 3333-4444', + celular: '(54) 99999-8888' + } + ] +} diff --git a/src/entities/contatos/__tests__/index.spec.ts b/src/entities/contatos/__tests__/index.spec.ts new file mode 100644 index 0000000..42d896b --- /dev/null +++ b/src/entities/contatos/__tests__/index.spec.ts @@ -0,0 +1,180 @@ +import { Chance } from 'chance' +import { Contatos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import changeSituationManyResponse, { + changeSituationManyRequest +} from './change-situation-many-response' +import changeSituationResponse, { + changeSituationRequest +} from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import deleteManyResponse from './delete-many-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import findTypesResponse from './find-types-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Contatos entity', () => { + let repository: InMemoryBlingRepository + let entity: Contatos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Contatos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete many successfully', async () => { + const idsContatos = [] + for (let i = 0; i < chance.natural({ min: 2, max: 10 }); i++) { + idsContatos.push(chance.natural()) + } + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteManyResponse) + + const response = await entity.deleteMany({ idsContatos }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + id: '', + params: { idsContatos } + }) + expect(response).toBe(deleteManyResponse) + }) + + it('should delete successfully', async () => { + const idContato = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idContato }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + id: String(idContato) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + params: { + limite: undefined, + pagina: undefined, + pesquisa: undefined, + criterio: undefined, + idTipoContato: undefined, + idVendedor: undefined, + uf: undefined, + telefone: undefined, + idsContatos: undefined, + numeroDocumento: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idContato = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idContato }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + id: String(idContato) + }) + expect(response).toBe(findResponse) + }) + + it('should find types successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idContato = chance.natural() + repository.setResponse(findTypesResponse) + + const response = await entity.findTypes({ idContato }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + id: `${idContato}/tipos` + }) + expect(response).toBe(findTypesResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idContato = chance.natural() + repository.setResponse(changeSituationResponse) + + const response = await entity.changeSituation({ + idContato, + ...changeSituationRequest + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + id: `${idContato}/situacoes`, + body: changeSituationRequest + }) + expect(response).toBe(changeSituationResponse) + }) + + it('should change situation many successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(changeSituationManyResponse) + + const response = await entity.changeSituationMany( + changeSituationManyRequest + ) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos/situacoes', + body: changeSituationManyRequest + }) + expect(response).toBe(changeSituationResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idContato = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idContato, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos', + id: String(idContato), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/contatos/__tests__/update-response.ts b/src/entities/contatos/__tests__/update-response.ts new file mode 100644 index 0000000..a1f828a --- /dev/null +++ b/src/entities/contatos/__tests__/update-response.ts @@ -0,0 +1,67 @@ +export default null + +export const updateRequestBody = { + nome: 'Contato', + codigo: 'ASD001', + situacao: 'A' as const, + numeroDocumento: '12345678910', + telefone: '(54) 3333-4444', + celular: '(54) 99999-8888', + fantasia: 'Nome fantasia', + tipo: 'J' as const, + indicadorIe: 1 as const, + ie: '123.456.789.101', + rg: '1234567890', + orgaoEmissor: '1234567890', + email: 'contato@email.com', + endereco: { + geral: { + endereco: 'R. Olavo Bilac', + cep: '95702-000', + bairro: 'Imigrante', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + numero: '914', + complemento: 'Sede 101' + }, + cobranca: { + endereco: 'R. Olavo Bilac', + cep: '95702-000', + bairro: 'Imigrante', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + numero: '914', + complemento: 'Sede 101' + } + }, + vendedor: { + id: 12345678 + }, + dadosAdicionais: { + dataNascimento: '1990-08-24', + sexo: 'M' as const, + naturalidade: 'Brasileira' + }, + financeiro: { + limiteCredito: 0, + condicaoPagamento: '30', + categoria: { + id: 12345678 + } + }, + pais: { + nome: 'ESTADOS UNIDOS' + }, + tiposContato: [ + { + id: 12345678, + descricao: 'Fornecedor' + } + ], + pessoasContato: [ + { + id: 12345678, + descricao: 'Fornecedor Fulano' + } + ] +} diff --git a/src/entities/contatos/index.ts b/src/entities/contatos/index.ts new file mode 100644 index 0000000..885f3b3 --- /dev/null +++ b/src/entities/contatos/index.ts @@ -0,0 +1,201 @@ +import { Entity } from '../@shared/entity' +import { IChangeSituationManyBody } from './interfaces/change-situation-many.interface' +import { + IChangeSituationBody, + IChangeSituationParams +} from './interfaces/change-situation.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteManyParams } from './interfaces/delete-many.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { + IFindTypesParams, + IFindTypesResponse +} from './interfaces/find-types.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IUpdateBody, IUpdateParams } from './interfaces/update.interface' + +/** + * Entidade para interação com Contatos. + * + * @see https://developer.bling.com.br/referencia#/Contatos + */ +export class Contatos extends Entity { + /** + * Remove múltiplos contatos. + * + * @param {IDeleteManyParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/delete_contatos + */ + public async deleteMany(params: IDeleteManyParams): Promise { + return await this.repository.destroy({ + endpoint: 'contatos', + id: String(''), + params: { + idsContatos: params.idsContatos + } + }) + } + + /** + * Remove um contato. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/delete_contatos__idContato_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'contatos', + id: String(params.idContato) + }) + } + + /** + * Obtém contatos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/get_contatos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'contatos', + params: { + pagina: params?.pagina, + limite: params?.limite, + pesquisa: params?.pesquisa, + criterio: params?.criterio, + idTipoContato: params?.idTipoContato, + idVendedor: params?.idVendedor, + uf: params?.uf, + telefone: params?.telefone, + idsContatos: params?.idsContatos, + numeroDocumento: params?.numeroDocumento + } + }) + } + + /** + * Obtém um contato. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/get_contatos__idContato_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'contatos', + id: String(params.idContato) + }) + } + + /** + * Obtém os tipos de contato de um contato. + * + * @param {IFindTypesParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/get_contatos__idContato__tipos + */ + public async findTypes( + params: IFindTypesParams + ): Promise { + return await this.repository.show({ + endpoint: 'contatos', + id: `${params.idContato}/tipos` + }) + } + + /** + * Altera a situação de um contato. + * + * @param {IChangeSituationParams & IChangeSituationBody} params Os parâmetros para a alteração. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/patch_contatos__idContato__situacoes + */ + public async changeSituation( + params: IChangeSituationParams & IChangeSituationBody + ): Promise { + const { idContato, ...body } = params + return await this.repository.replace({ + endpoint: 'contatos', + id: `${idContato}/situacoes`, + body + }) + } + + /** + * Altera a situação de múltiplos contatos. + * + * @param {IChangeSituationManyBody} body O corpo da requisição. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/post_contatos_situacoes + */ + public async changeSituationMany( + body: IChangeSituationManyBody + ): Promise { + return await this.repository.store({ + endpoint: 'contatos/situacoes', + body + }) + } + + /** + * Cria um contato. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/post_contatos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'contatos', + body + }) + } + + /** + * Altera um contato. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos/put_contatos__idContato_ + */ + public async update(params: IUpdateParams & IUpdateBody): Promise { + const { idContato, ...body } = params + + return await this.repository.replace({ + endpoint: 'contatos', + id: String(idContato), + body + }) + } +} diff --git a/src/entities/contatos/interfaces/change-situation-many.interface.ts b/src/entities/contatos/interfaces/change-situation-many.interface.ts new file mode 100644 index 0000000..78cbd86 --- /dev/null +++ b/src/entities/contatos/interfaces/change-situation-many.interface.ts @@ -0,0 +1,6 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IChangeSituationManyBody { + idsContatos: number[] + situacao: ISituacao +} diff --git a/src/entities/contatos/interfaces/change-situation.interface.ts b/src/entities/contatos/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..60a0dc0 --- /dev/null +++ b/src/entities/contatos/interfaces/change-situation.interface.ts @@ -0,0 +1,12 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IChangeSituationParams { + /** + * ID do contato + */ + idContato: number +} + +export interface IChangeSituationBody { + situacao: ISituacao +} diff --git a/src/entities/contatos/interfaces/create.interface.ts b/src/entities/contatos/interfaces/create.interface.ts new file mode 100644 index 0000000..0c76cb2 --- /dev/null +++ b/src/entities/contatos/interfaces/create.interface.ts @@ -0,0 +1,67 @@ +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IIndicadorIE } from '../types/indicador-ie.type' +import { ISexo } from '../types/sexo.type' +import { ISituacao } from '../types/situacao.type' + +export interface ICreateBody { + nome?: string + codigo?: string + situacao?: ISituacao + numeroDocumento?: string + telefone?: string + celular?: string + fantasia?: string + tipo?: ITipoPessoa + indicadorIe?: IIndicadorIE + ie?: string + rg?: string + orgaoEmissor?: string + email?: string + endereco?: { + geral?: { + endereco?: string + cep?: string + bairro?: string + municipio?: string + uf?: IUF + numero?: string + complemento?: string + } + cobranca?: { + endereco?: string + cep?: string + bairro?: string + municipio?: string + uf?: IUF + numero?: string + complemento?: string + } + } + vendedor?: { id: number } + dadosAdicionais?: { + dataNascimento?: string + sexo?: ISexo + naturalidade?: string + } + financeiro?: { + limiteCredito?: number + condicaoPagamento?: string + categoria?: { id: number } + } + pais?: { nome?: string } + tiposContato?: { + id: number + descricao?: string + }[] + pessoasContato?: { + id: number + descricao?: string + }[] +} + +export interface ICreateResponse { + data: { + id: number + } +} diff --git a/src/entities/contatos/interfaces/delete-many.interface.ts b/src/entities/contatos/interfaces/delete-many.interface.ts new file mode 100644 index 0000000..6ebabab --- /dev/null +++ b/src/entities/contatos/interfaces/delete-many.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteManyParams { + idsContatos: number[] +} diff --git a/src/entities/contatos/interfaces/delete.interface.ts b/src/entities/contatos/interfaces/delete.interface.ts new file mode 100644 index 0000000..797425d --- /dev/null +++ b/src/entities/contatos/interfaces/delete.interface.ts @@ -0,0 +1,3 @@ +export interface IDeleteParams { + idContato: number +} diff --git a/src/entities/contatos/interfaces/find-types.interface.ts b/src/entities/contatos/interfaces/find-types.interface.ts new file mode 100644 index 0000000..ff604b9 --- /dev/null +++ b/src/entities/contatos/interfaces/find-types.interface.ts @@ -0,0 +1,13 @@ +export interface IFindTypesParams { + /** + * ID do contato + */ + idContato: number +} + +export interface IFindTypesResponse { + data: { + id: number + descricao: string + }[] +} diff --git a/src/entities/contatos/interfaces/find.interface.ts b/src/entities/contatos/interfaces/find.interface.ts new file mode 100644 index 0000000..6ce3d8f --- /dev/null +++ b/src/entities/contatos/interfaces/find.interface.ts @@ -0,0 +1,71 @@ +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IIndicadorIE } from '../types/indicador-ie.type' +import { ISexo } from '../types/sexo.type' +import { ISituacao } from '../types/situacao.type' + +export interface IFindParams { + /** + * ID do contato + */ + idContato: number +} + +export interface IFindResponse { + data: { + id: number + nome: string + codigo: string + situacao: ISituacao + numeroDocumento: string + telefone: string + celular: string + fantasia: string + tipo: ITipoPessoa + indicadorIe: IIndicadorIE + ie: string + rg: string + orgaoEmissor: string + email: string + endereco: { + geral: { + endereco: string + cep: string + bairro: string + municipio: string + uf: IUF + numero: string + complemento: string + } + cobranca: { + endereco: string + cep: string + bairro: string + municipio: string + uf: IUF + numero: string + complemento: string + } + } + vendedor: { id: number } + dadosAdicionais: { + dataNascimento: string + sexo: ISexo + naturalidade: string + } + financeiro: { + limiteCredito: 0 + condicaoPagamento: string + categoria: { id: number } + } + pais: { nome: string } + tiposContato: { + id: number + descricao: string + }[] + pessoasContato: { + id: number + descricao: string + }[] + } +} diff --git a/src/entities/contatos/interfaces/get.interface.ts b/src/entities/contatos/interfaces/get.interface.ts new file mode 100644 index 0000000..8260a4c --- /dev/null +++ b/src/entities/contatos/interfaces/get.interface.ts @@ -0,0 +1,58 @@ +import IUF from 'src/entities/@shared/types/uf.type' +import { ICriterio } from '../types/criterio.type' +import { ISituacao } from '../types/situacao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Nome, CPF/CNPJ, fantasia, e-mail ou código do contato + */ + pesquisa?: string + /** + * Criterio de listagem + */ + criterio?: ICriterio + /** + * ID do tipo do contato + */ + idTipoContato?: number + /** + * ID do vendedor relacionado ao contato + */ + idVendedor?: number + /** + * UF do contato + */ + uf?: IUF + /** + * Telefone do contato + */ + telefone?: string + /** + * IDs dos contatos + */ + idsContatos?: number[] + /** + * CPF/CNPJ, desconsiderando a pontuação + */ + numeroDocumento?: string +} + +export interface IGetResponse { + data: { + id: number + nome: string + codigo: string + situacao: ISituacao + numeroDocumento: string + telefone: string + celular: string + }[] +} diff --git a/src/entities/contatos/interfaces/update.interface.ts b/src/entities/contatos/interfaces/update.interface.ts new file mode 100644 index 0000000..d17937c --- /dev/null +++ b/src/entities/contatos/interfaces/update.interface.ts @@ -0,0 +1,68 @@ +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IIndicadorIE } from '../types/indicador-ie.type' +import { ISexo } from '../types/sexo.type' +import { ISituacao } from '../types/situacao.type' + +export interface IUpdateParams { + /** + * ID do contato + */ + idContato: number +} + +export interface IUpdateBody { + nome?: string + codigo?: string + situacao?: ISituacao + numeroDocumento?: string + telefone?: string + celular?: string + fantasia?: string + tipo?: ITipoPessoa + indicadorIe?: IIndicadorIE + ie?: string + rg?: string + orgaoEmissor?: string + email?: string + endereco?: { + geral?: { + endereco?: string + cep?: string + bairro?: string + municipio?: string + uf?: IUF + numero?: string + complemento?: string + } + cobranca?: { + endereco?: string + cep?: string + bairro?: string + municipio?: string + uf?: IUF + numero?: string + complemento?: string + } + } + vendedor?: { id: number } + dadosAdicionais?: { + dataNascimento?: string + sexo?: ISexo + naturalidade?: string + } + financeiro?: { + limiteCredito?: number + condicaoPagamento?: string + categoria?: { id: number } + } + pais?: { nome?: string } + tiposContato?: { + id: number + descricao?: string + }[] + pessoasContato?: { + id: number + descricao?: string + }[] +} diff --git a/src/entities/contatos/types/criterio.type.ts b/src/entities/contatos/types/criterio.type.ts new file mode 100644 index 0000000..5578e8d --- /dev/null +++ b/src/entities/contatos/types/criterio.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem para critério de listagem. + * + * - `1`: Todos + * - `2`: Excluídos + * - `3`: Últimos incluídos + * - `4`: Sem movimento + */ +export type ICriterio = 1 | 2 | 3 | 4 diff --git a/src/entities/contatos/types/indicador-ie.type.ts b/src/entities/contatos/types/indicador-ie.type.ts new file mode 100644 index 0000000..7c87d7e --- /dev/null +++ b/src/entities/contatos/types/indicador-ie.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao indicador de inscrição estadual. + * + * - `1`: Contribuinte ICMS + * - `2`: Contribuinte isento de Inscrição no cadastro de Contribuintes + * - `9`: Não Contribuinte + */ +export type IIndicadorIE = 1 | 2 | 9 diff --git a/src/entities/contatos/types/sexo.type.ts b/src/entities/contatos/types/sexo.type.ts new file mode 100644 index 0000000..f8e6f41 --- /dev/null +++ b/src/entities/contatos/types/sexo.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao sexo do contato. + * + * - `M`: Masculino + * - `F`: Feminino + */ +export type ISexo = 'M' | 'F' diff --git a/src/entities/contatos/types/situacao.type.ts b/src/entities/contatos/types/situacao.type.ts new file mode 100644 index 0000000..fb3951b --- /dev/null +++ b/src/entities/contatos/types/situacao.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem para a situação do contato. + * + * - `A`: Ativo + * - `E`: Excluído + * - `I`: Inativo + * - `S`: Sem movimentação + */ +export type ISituacao = 'A' | 'E' | 'I' | 'S' diff --git a/src/entities/contatosTipos/__tests__/get-response.ts b/src/entities/contatosTipos/__tests__/get-response.ts new file mode 100644 index 0000000..08a290a --- /dev/null +++ b/src/entities/contatosTipos/__tests__/get-response.ts @@ -0,0 +1,8 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Fornecedor' + } + ] +} diff --git a/src/entities/contatosTipos/__tests__/index.spec.ts b/src/entities/contatosTipos/__tests__/index.spec.ts new file mode 100644 index 0000000..33502b8 --- /dev/null +++ b/src/entities/contatosTipos/__tests__/index.spec.ts @@ -0,0 +1,29 @@ +import { ContatosTipos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import getResponse from './get-response' + +describe('Contatos - Tipos entity', () => { + let repository: InMemoryBlingRepository + let entity: ContatosTipos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ContatosTipos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contatos/tipos' + }) + expect(response).toBe(getResponse) + }) +}) diff --git a/src/entities/contatosTipos/index.ts b/src/entities/contatosTipos/index.ts new file mode 100644 index 0000000..b9c01f5 --- /dev/null +++ b/src/entities/contatosTipos/index.ts @@ -0,0 +1,23 @@ +import { Entity } from '../@shared/entity' +import { IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com Contatos - Tipos. + * + * @see https://developer.bling.com.br/referencia#/Contatos%20-%20Tipos + */ +export class ContatosTipos extends Entity { + /** + * Obtém tipos de contato. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contatos%20-%20Tipos/get_contatos_tipos + */ + public async get(): Promise { + return await this.repository.index({ + endpoint: 'contatos/tipos' + }) + } +} diff --git a/src/entities/contatosTipos/interfaces/get.interface.ts b/src/entities/contatosTipos/interfaces/get.interface.ts new file mode 100644 index 0000000..6202c6c --- /dev/null +++ b/src/entities/contatosTipos/interfaces/get.interface.ts @@ -0,0 +1,6 @@ +export interface IGetResponse { + data: { + id: number + descricao: string + }[] +} diff --git a/src/entities/contatosTipos/types/criterio.type.ts b/src/entities/contatosTipos/types/criterio.type.ts new file mode 100644 index 0000000..5578e8d --- /dev/null +++ b/src/entities/contatosTipos/types/criterio.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem para critério de listagem. + * + * - `1`: Todos + * - `2`: Excluídos + * - `3`: Últimos incluídos + * - `4`: Sem movimento + */ +export type ICriterio = 1 | 2 | 3 | 4 diff --git a/src/entities/contatosTipos/types/indicador-ie.type.ts b/src/entities/contatosTipos/types/indicador-ie.type.ts new file mode 100644 index 0000000..7c87d7e --- /dev/null +++ b/src/entities/contatosTipos/types/indicador-ie.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao indicador de inscrição estadual. + * + * - `1`: Contribuinte ICMS + * - `2`: Contribuinte isento de Inscrição no cadastro de Contribuintes + * - `9`: Não Contribuinte + */ +export type IIndicadorIE = 1 | 2 | 9 diff --git a/src/entities/contatosTipos/types/sexo.type.ts b/src/entities/contatosTipos/types/sexo.type.ts new file mode 100644 index 0000000..f8e6f41 --- /dev/null +++ b/src/entities/contatosTipos/types/sexo.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao sexo do contato. + * + * - `M`: Masculino + * - `F`: Feminino + */ +export type ISexo = 'M' | 'F' diff --git a/src/entities/contatosTipos/types/situacao.type.ts b/src/entities/contatosTipos/types/situacao.type.ts new file mode 100644 index 0000000..fb3951b --- /dev/null +++ b/src/entities/contatosTipos/types/situacao.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem para a situação do contato. + * + * - `A`: Ativo + * - `E`: Excluído + * - `I`: Inativo + * - `S`: Sem movimentação + */ +export type ISituacao = 'A' | 'E' | 'I' | 'S' diff --git a/src/entities/contracts.ts b/src/entities/contracts.ts deleted file mode 100644 index 93f3a4a..0000000 --- a/src/entities/contracts.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' -import Delete from '../core/functions/delete' - -type IContractSituacao = 'A' | 'I' | 'B' | 'S' | 'T' - -export interface IContract { - dataCriacao?: string - dataBase?: string - contatoDiferenteCobranca?: number - numeroContrato?: string - descricao?: string - situacao: IContractSituacao - valor: number - emiteNota: 'S' | 'N' - periodicidadeCobranca?: string - opcoesNota?: { - percentualISS?: number - descISSTotalNota: 'S' | 'N' - descIRTotalNota?: 'S' | 'N' - codListaServico?: string - idProdutoVinculado?: string - mesNota?: string - textoNota?: string - naturezaOperacao?: string - cfop?: string - } - idCategoria?: number - idPortador?: number - idVendedor?: number - desconto?: number - mesFimDesconto?: string - anoFimDesconto?: string - mesTermino?: string - anoTermino?: string - mesVencimento?: string - nroParcelasVendedor?: number - percentualVendedor?: number - emiteOS?: 'S' | 'N' - obs?: string - cliente: { - nome?: string - cnpj_cpf: string - tipo: 'F' | 'J' - ie_rg?: string - rg?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - bairro?: string - cep?: string - uf?: string - email?: string - fone?: string - celular?: string - } - contato?: { - nome?: string - cnpj_cpf: string - tipoPessoa?: 'F' | 'J' - ie_rg?: string - rg?: string - endereco?: string - numero?: string - complemento?: string - cidade?: string - bairro?: string - cep?: string - uf?: string - email?: string - fone?: string - celular?: string - } - anexos: { - anexo: { - filename: string - data: string - } - }[] - diaVencimento?: string -} - -export interface IContractFilters { - dataCriacao?: string - dataBase?: string - situacao?: IContractSituacao - idContato?: number - idCliente?: number -} - -export type IContractInfos = Record - -export interface IContractCreateResponse { - id: string - numeroContrato: string -} - -export interface IContractDeleteResponse { - id: string - mensagem: string -} - -export interface IContractResponse { - id: string - nome: string - descricao: string - contatoDiferenteCobranca: string - numeroContrato: string - idCliente: string - idContato: string - situacao: IContractSituacao - dataCriacao: string - valor: string - dataBase: string - mesVencimento: string - diaVencimento: string - periodicidadeCobranca: string - emiteNota: 'S' | 'N' - tipoManutencao: string - idCategoria: string - idPortador: string - idFormaPagamento?: string - desconto: string - mesFimDesconto: string - anoFimDesconto: string - mesTermino: string - anoTermino: string - idVendedor: string - nroParcelasVendedor: string - percentualVendedor: string - emiteOS: 'S' | 'N' - obs: string - dataUltimoPagamento?: string - opcoesNota: { - percentualISS: string - descontarISS?: string - descISSTotalNota: 'S' | 'N' - descIRTotalNota: 'S' | 'N' - codListaServico: string - idProdutoVinculado: string - mesNota: string - textoNota: string - naturezaOperacao: string - cfop?: string - } - nroContasEmAtraso: string - anexos: { - nome: string - link: string - }[] -} - -export default function Contracts (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'contrato', - pluralName: 'contratos' - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy() - .findBy, - create: new Create().create, - update: new Update().update, - delete: new Delete().delete - }) -} diff --git a/src/entities/contratos/__tests__/create-response.ts b/src/entities/contratos/__tests__/create-response.ts new file mode 100644 index 0000000..6ed9f47 --- /dev/null +++ b/src/entities/contratos/__tests__/create-response.ts @@ -0,0 +1,68 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + descricao: 'Alugel do apartamento A102', + data: '2023-02-19', + numero: '25', + valor: 59.99, + situacao: 1 as const, + contato: { + id: 12345678 + }, + dataFim: '2024-05', + tipoManutencao: 1 as const, + emitirOrdemServico: false, + observacoes: '', + vendedor: { + id: 12345678, + comissao: { + aliquota: 0.5, + numeroParcelas: 1 + } + }, + categoria: { + id: 12345678 + }, + desconto: { + valor: 4.99, + dataFim: '2023-02' + }, + contaContabil: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + notaFiscal: { + mes: 2 as const, + gerar: 1 as const, + descontarImpostoRenda: 1 as const, + texto: 'Exemplo de texto.', + cfop: '5.556', + iss: { + descontar: false, + aliquota: 2.5 + }, + item: { + codigoServico: '14.13', + produto: { + id: 12345678 + } + } + }, + cobranca: { + dataBase: '2023-02-22', + contato: { + id: 12345678 + }, + vencimento: { + tipo: 1 as const, + dia: 10, + periodicidade: 1 as const + } + } +} diff --git a/src/entities/contratos/__tests__/delete-response.ts b/src/entities/contratos/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/contratos/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/contratos/__tests__/find-response.ts b/src/entities/contratos/__tests__/find-response.ts new file mode 100644 index 0000000..91e0855 --- /dev/null +++ b/src/entities/contratos/__tests__/find-response.ts @@ -0,0 +1,65 @@ +export default { + data: { + id: 123455678, + descricao: 'Alugel do apartamento A102', + data: '2023-02-19', + numero: '25', + valor: 59.99, + situacao: 1 as const, + contato: { + id: 12345678 + }, + dataFim: '2024-05', + tipoManutencao: 1 as const, + emitirOrdemServico: false, + observacoes: '', + vendedor: { + id: 12345678, + comissao: { + aliquota: 0.5, + numeroParcelas: 1 + } + }, + categoria: { + id: 12345678 + }, + desconto: { + valor: 4.99, + dataFim: '2023-02' + }, + contaContabil: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + notaFiscal: { + mes: 2 as const, + gerar: 1 as const, + descontarImpostoRenda: 1 as const, + texto: 'Exemplo de texto.', + cfop: '5.556', + iss: { + descontar: false, + aliquota: 2.5 + }, + item: { + codigoServico: '14.13', + produto: { + id: 12345678 + } + } + }, + cobranca: { + dataBase: '2023-02-22', + contato: { + id: 12345678 + }, + vencimento: { + tipo: 1 as const, + dia: 10, + periodicidade: 1 as const + } + } + } +} diff --git a/src/entities/contratos/__tests__/get-response.ts b/src/entities/contratos/__tests__/get-response.ts new file mode 100644 index 0000000..415d6b9 --- /dev/null +++ b/src/entities/contratos/__tests__/get-response.ts @@ -0,0 +1,15 @@ +export default { + data: [ + { + id: 123455678, + descricao: 'Alugel do apartamento A102', + data: '2023-02-19', + numero: '25', + valor: 59.99, + situacao: 1, + contato: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/contratos/__tests__/index.spec.ts b/src/entities/contratos/__tests__/index.spec.ts new file mode 100644 index 0000000..17f84e3 --- /dev/null +++ b/src/entities/contratos/__tests__/index.spec.ts @@ -0,0 +1,106 @@ +import { Chance } from 'chance' +import { Contratos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Contratos entity', () => { + let repository: InMemoryBlingRepository + let entity: Contratos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Contratos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idContrato = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idContrato }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contratos', + id: String(idContrato) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contratos', + params: { + limite: undefined, + pagina: undefined, + dataCriacaoInicio: undefined, + dataCriacaoFinal: undefined, + dataBaseInicio: undefined, + dataBaseFinal: undefined, + situacao: undefined, + idContato: undefined, + idContatoCobranca: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idContrato = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idContrato }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contratos', + id: String(idContrato) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contratos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idContrato = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idContrato, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'contratos', + id: String(idContrato), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/contratos/__tests__/update-response.ts b/src/entities/contratos/__tests__/update-response.ts new file mode 100644 index 0000000..dd32e3c --- /dev/null +++ b/src/entities/contratos/__tests__/update-response.ts @@ -0,0 +1,68 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + descricao: 'Alugel do apartamento A102', + data: '2023-02-19', + numero: '25', + valor: 59.99, + situacao: 1 as const, + contato: { + id: 12345678 + }, + dataFim: '2024-05', + tipoManutencao: 1 as const, + emitirOrdemServico: false, + observacoes: '', + vendedor: { + id: 12345678, + comissao: { + aliquota: 0.5, + numeroParcelas: 1 + } + }, + categoria: { + id: 12345678 + }, + desconto: { + valor: 4.99, + dataFim: '2023-02' + }, + contaContabil: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + notaFiscal: { + mes: 2 as const, + gerar: 1 as const, + descontarImpostoRenda: 1 as const, + texto: 'Exemplo de texto.', + cfop: '5.556', + iss: { + descontar: false, + aliquota: 2.5 + }, + item: { + codigoServico: '14.13', + produto: { + id: 12345678 + } + } + }, + cobranca: { + dataBase: '2023-02-22', + contato: { + id: 12345678 + }, + vencimento: { + tipo: 1 as const, + dia: 10, + periodicidade: 1 as const + } + } +} diff --git a/src/entities/contratos/index.ts b/src/entities/contratos/index.ts new file mode 100644 index 0000000..a556af7 --- /dev/null +++ b/src/entities/contratos/index.ts @@ -0,0 +1,121 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com contratos. + * + * @see https://developer.bling.com.br/referencia#/Contratos + */ +export class Contratos extends Entity { + /** + * Remove um contrato. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contratos/delete_contratos__idContrato_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'contratos', + id: String(params.idContrato) + }) + } + + /** + * Obtém contratos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contratos/get_contratos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'contratos', + params: { + pagina: params?.pagina, + limite: params?.limite, + dataCriacaoInicio: this.prepareStringOrDateParam( + params?.dataCriacaoInicio + ), + dataCriacaoFinal: this.prepareStringOrDateParam( + params?.dataCriacaoFinal + ), + dataBaseInicio: this.prepareStringOrDateParam(params?.dataBaseInicio), + dataBaseFinal: this.prepareStringOrDateParam(params?.dataBaseFinal), + situacao: params?.situacao, + idContato: params?.idContato, + idContatoCobranca: params?.idContatoCobranca + } + }) + } + + /** + * Obtém um contrato. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contratos/get_contratos__idContrato_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'contratos', + id: String(params.idContrato) + }) + } + + /** + * Cria um contrato. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contratos/post_contratos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'contratos', + body + }) + } + + /** + * Altera um contrato. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contratos/put_contratos__idContrato_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idContrato, ...body } = params + + return await this.repository.replace({ + endpoint: 'contratos', + id: String(idContrato), + body + }) + } +} diff --git a/src/entities/contratos/interfaces/create.interface.ts b/src/entities/contratos/interfaces/create.interface.ts new file mode 100644 index 0000000..ecf5171 --- /dev/null +++ b/src/entities/contratos/interfaces/create.interface.ts @@ -0,0 +1,62 @@ +import { INotaFiscalDescontarImpostoRenda } from '../types/nota-fiscal-descontar-imposto-renda.type' +import { INotaFiscalGerar } from '../types/nota-fiscal-gerar.type' +import { INotaFiscalMes } from '../types/nota-fiscal-mes.type' +import { IPeriodicidade } from '../types/periodicidade.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoManutencao } from '../types/tipo-manutencao.type' +import { ITipoVencimento } from '../types/tipo-vencimento.type' + +export interface ICreateBody { + descricao: string + data: string + numero: string + valor: number + situacao: ISituacao + contato: { id: number } + dataFim: string + tipoManutencao: ITipoManutencao + emitirOrdemServico: boolean + observacoes: string + vendedor: { + id: number + comissao: { + aliquota: number + numeroParcelas: number + } + } + categoria: { id: number } + desconto: { + valor: number + dataFim: string + } + contaContabil: { id: number } + formaPagamento: { id: number } + notaFiscal?: { + mes?: INotaFiscalMes + gerar?: INotaFiscalGerar + descontarImpostoRenda?: INotaFiscalDescontarImpostoRenda + texto?: string + cfop?: string + iss?: { + descontar?: boolean + aliquota?: number + } + item?: { + codigoServico?: string + produto?: { id: number } + } + } + cobranca: { + dataBase?: string + contato?: { id: number } + vencimento?: { + tipo?: ITipoVencimento + dia?: number + periodicidade?: IPeriodicidade + } + } +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/contratos/interfaces/delete.interface.ts b/src/entities/contratos/interfaces/delete.interface.ts new file mode 100644 index 0000000..7a30af3 --- /dev/null +++ b/src/entities/contratos/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do contrato + */ + idContrato: number +} diff --git a/src/entities/contratos/interfaces/find.interface.ts b/src/entities/contratos/interfaces/find.interface.ts new file mode 100644 index 0000000..4d0133f --- /dev/null +++ b/src/entities/contratos/interfaces/find.interface.ts @@ -0,0 +1,72 @@ +import { INotaFiscalDescontarImpostoRenda } from '../types/nota-fiscal-descontar-imposto-renda.type' +import { INotaFiscalGerar } from '../types/nota-fiscal-gerar.type' +import { INotaFiscalMes } from '../types/nota-fiscal-mes.type' +import { IPeriodicidade } from '../types/periodicidade.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoManutencao } from '../types/tipo-manutencao.type' +import { ITipoVencimento } from '../types/tipo-vencimento.type' + +export interface IFindParams { + /** + * ID do contrato + */ + idContrato: number +} + +export interface IFindResponse { + data: { + id: number + descricao: string + data: string + numero: string + valor: number + situacao: ISituacao + contato: { id: number } + dataFim: string + tipoManutencao: ITipoManutencao + emitirOrdemServico: boolean + observacoes: string + vendedor: { + id: number + comissao: { + aliquota: number + numeroParcelas: number + } + } + categoria: { id: number } + desconto: { + valor: number + dataFim: string + } + contaContabil: { id: number } + formaPagamento: { id: number } + notaFiscal: { + mes: INotaFiscalMes + gerar: INotaFiscalGerar + descontarImpostoRenda: INotaFiscalDescontarImpostoRenda + texto: string + cfop: string + iss: { + descontar: boolean + aliquota: number + } + item: { + codigoServico: string + produto: { id: number } + } + } + cobranca: { + dataBase: string + contato: { id: number } + vencimento: { + tipo: ITipoVencimento + /** + * Caso o dia informado não exista em um determinado mês(29, 30, 31), o + * vencimento da cobrança utilizará o ultimo dia válido do mês. + */ + dia: number + periodicidade: IPeriodicidade + } + } + } +} diff --git a/src/entities/contratos/interfaces/get.interface.ts b/src/entities/contratos/interfaces/get.interface.ts new file mode 100644 index 0000000..57c2dfe --- /dev/null +++ b/src/entities/contratos/interfaces/get.interface.ts @@ -0,0 +1,52 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Data inicial de criação + */ + dataCriacaoInicio?: Date | string + /** + * Data final de criação + */ + dataCriacaoFinal?: Date | string + /** + * Data base inicial para geração de cobranças + */ + dataBaseInicio?: Date | string + /** + * Data base final para geração de cobranças + */ + dataBaseFinal?: Date | string + /** + * + */ + situacao?: ISituacao + /** + * ID do contato + */ + idContato?: number + /** + * ID do contato de cobrança + */ + idContatoCobranca?: number +} + +export interface IGetResponse { + data: { + id: number + descricao: string + data: string + numero: string + valor: number + situacao: ISituacao + contato: { id: number } + }[] +} diff --git a/src/entities/contratos/interfaces/update.interface.ts b/src/entities/contratos/interfaces/update.interface.ts new file mode 100644 index 0000000..4dff4ed --- /dev/null +++ b/src/entities/contratos/interfaces/update.interface.ts @@ -0,0 +1,69 @@ +import { INotaFiscalDescontarImpostoRenda } from '../types/nota-fiscal-descontar-imposto-renda.type' +import { INotaFiscalGerar } from '../types/nota-fiscal-gerar.type' +import { INotaFiscalMes } from '../types/nota-fiscal-mes.type' +import { IPeriodicidade } from '../types/periodicidade.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoManutencao } from '../types/tipo-manutencao.type' +import { ITipoVencimento } from '../types/tipo-vencimento.type' + +export interface IUpdateParams { + /** + * ID do contrato + */ + idContrato: number +} + +export interface IUpdateBody { + descricao: string + data: string + numero: string + valor: number + situacao: ISituacao + contato: { id: number } + dataFim: string + tipoManutencao: ITipoManutencao + emitirOrdemServico: boolean + observacoes: string + vendedor: { + id: number + comissao: { + aliquota: number + numeroParcelas: number + } + } + categoria: { id: number } + desconto: { + valor: number + dataFim: string + } + contaContabil: { id: number } + formaPagamento: { id: number } + notaFiscal: { + mes: INotaFiscalMes + gerar: INotaFiscalGerar + descontarImpostoRenda: INotaFiscalDescontarImpostoRenda + texto: string + cfop: string + iss: { + descontar: boolean + aliquota: number + } + item: { + codigoServico: string + produto: { id: number } + } + } + cobranca: { + dataBase: string + contato: { id: number } + vencimento: { + tipo: ITipoVencimento + dia: number + periodicidade: IPeriodicidade + } + } +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/contratos/types/nota-fiscal-descontar-imposto-renda.type.ts b/src/entities/contratos/types/nota-fiscal-descontar-imposto-renda.type.ts new file mode 100644 index 0000000..c9000f5 --- /dev/null +++ b/src/entities/contratos/types/nota-fiscal-descontar-imposto-renda.type.ts @@ -0,0 +1,8 @@ +/** + * Reter o IR e descontar do total da NFS-e caso ultrapasse R$ 10,00. + * + * - `1`: Sim + * - `2`: Não + * - `3`: Utilizar padrão da configuração da NFS-e + */ +export type INotaFiscalDescontarImpostoRenda = 1 | 2 | 3 diff --git a/src/entities/contratos/types/nota-fiscal-gerar.type.ts b/src/entities/contratos/types/nota-fiscal-gerar.type.ts new file mode 100644 index 0000000..e8f0a93 --- /dev/null +++ b/src/entities/contratos/types/nota-fiscal-gerar.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente à geração de uma nota fiscal ao criar um contrato. + * + * - `1`: Não + * - `2`: Ao gerar cobrança + */ +export type INotaFiscalGerar = 1 | 2 diff --git a/src/entities/contratos/types/nota-fiscal-mes.type.ts b/src/entities/contratos/types/nota-fiscal-mes.type.ts new file mode 100644 index 0000000..6e3f8f3 --- /dev/null +++ b/src/entities/contratos/types/nota-fiscal-mes.type.ts @@ -0,0 +1,9 @@ +/** + * Período de referência da cobrança que será incluído nas informações + * complementares das notas fiscais e observações da conta a receber. + * + * - `1`: Não imprime + * - `2`: Mês atual + * - `3`: Mês anterior + */ +export type INotaFiscalMes = 1 | 2 | 3 diff --git a/src/entities/contratos/types/periodicidade.type.ts b/src/entities/contratos/types/periodicidade.type.ts new file mode 100644 index 0000000..fcac001 --- /dev/null +++ b/src/entities/contratos/types/periodicidade.type.ts @@ -0,0 +1,12 @@ +/** + * Tipagem referente à periodicidade de um contrato. + * + * - `1`: Mensal + * - `2`: Bimestral + * - `3`: Trimestral + * - `4`: Semestral + * - `5`: Anual + * - `6`: Bianual + * - `7`: Trianual + */ +export type IPeriodicidade = 1 | 2 | 3 | 4 | 5 | 6 | 7 diff --git a/src/entities/contratos/types/situacao.type.ts b/src/entities/contratos/types/situacao.type.ts new file mode 100644 index 0000000..5ee1219 --- /dev/null +++ b/src/entities/contratos/types/situacao.type.ts @@ -0,0 +1,10 @@ +/** + * Tipagem referente à situação do contrato. + * + * - `0`: Inativo + * - `1`: Ativo + * - `2`: Baixado + * - `3`: Isento + * - `4`: Em avaliação + */ +export type ISituacao = 0 | 1 | 2 | 3 | 4 diff --git a/src/entities/contratos/types/tipo-manutencao.type.ts b/src/entities/contratos/types/tipo-manutencao.type.ts new file mode 100644 index 0000000..65e938f --- /dev/null +++ b/src/entities/contratos/types/tipo-manutencao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo de manutenção do contrato. + * + * - `1`: Valor + * - `2`: Indexação + */ +export type ITipoManutencao = 1 | 2 diff --git a/src/entities/contratos/types/tipo-vencimento.type.ts b/src/entities/contratos/types/tipo-vencimento.type.ts new file mode 100644 index 0000000..c8ba2a8 --- /dev/null +++ b/src/entities/contratos/types/tipo-vencimento.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao tipo de vencimento de um contrato. + * + * - `1`: No mês corrente + * - `2`: No mês seguinte + * - `3`: Em dois meses + */ +export type ITipoVencimento = 1 | 2 | 3 diff --git a/src/entities/ctes.ts b/src/entities/ctes.ts deleted file mode 100644 index 51a4556..0000000 --- a/src/entities/ctes.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' -import Delete from '../core/functions/delete' - -export interface ICte { - pedido: { - xml: string - } -} - -export interface ICteFilters { - dataEmissao?: string -} - -export type ICteInfos = Record - -export interface ICteDeleteResponse { - id: string - mensagem: string -} - -export interface ICteResponse { - id: string - serie: string - numero: string - natureza: string - cfop: string - dataEmissao: string - situacao: string - tipoCte: string - tipoServico: string - modal: string - municipioInicio: string - UFInicio: string - municipioFim: string - UFFim: string - cst: string - baseCalculo: string - aliquotaIcms: string - valorIcms: string - valorPis: string - valorCofins: string - valorIr: string - valorInss: string - valorCsll: string - valorTotal: string - rntrc: string - emissor: { - nome: string - cnpj: string - ie: string - endereco: string - numero: string - complemento?: string - bairro: string - cep: string - municipio: string - uf: string - fone: string - } - remetente: { - nome: string - cnpj: string - ie: string - endereco: string - numero: string - complemento?: string - bairro: string - cep: string - municipio: string - uf: string - fone?: string - email?: string - } - destinatario: { - nome: string - cnpj: string - ie: string - endereco: string - numero: string - complemento: string - bairro: string - cep: string - municipio: string - uf: string - fone?: string - email?: string - } - autorizacao: { - chave: string - dataRecebimento: string - numeroProtocolo: string - } - nota: { - chave: string - } -} - -export default function Ctes (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'cte', - pluralName: 'ctes' - } - - const find = async ( - numero: number | string, - serie: number | string, - options?: { - params?: ICteInfos - raw?: boolean - } - ) => { - const findMethod = new Find(config) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await findMethod.find(`${numero}/${serie}`, { - params: options.params, - raw: true - }) - } else { - return await findMethod.find(`${numero}/${serie}`, { - params: options.params, - raw: false - }) - } - } else { - return await findMethod.find(`${numero}/${serie}`) - } - } - - const create = async ( - data: ICte, - options?: { - loja?: number | string - raw?: boolean - } - ) => { - const createMethod = new Create(config) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await createMethod.create(data, { raw: true }, options.loja) - } else { - return await createMethod.create(data, { raw: false }, options.loja) - } - } else { - return await createMethod.create(data, { raw: false }) - } - } - - const post = async ( - id: number | string, - options?: { - raw?: boolean - } - ) => { - const createMethod = new Create({ - ...config, - endpoint: `/cte/lancamento/contas/${id}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (raw) { - return await createMethod.create(undefined, { raw: true }) - } else { - return await createMethod.create(undefined, { raw: false }) - } - } - - const customDelete = async ( - id: number | string, - options?: { - raw?: boolean - } - ) => { - const deleteMethod = new Delete({ - ...config, - endpoint: '/cte/estorno/contas' - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (raw) { - return await deleteMethod.delete(id, { raw: true }) - } else { - return await deleteMethod.delete(id, { raw: false }) - } - } - - return Object.assign(config, { - all: new All().all, - find, - findBy: new FindBy().findBy, - create, - post, - update: new Update().update, - delete: customDelete - }) -} diff --git a/src/entities/customizedFields.ts b/src/entities/customizedFields.ts deleted file mode 100644 index f52feb4..0000000 --- a/src/entities/customizedFields.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import Find from '../core/functions/find' - -export interface ICustomizedFieldResponse { - id: string - alias: string - nome: string - descricao: string - situacao: boolean - tipoCampo: string - obrigatorio: boolean - tamanhoMinimo?: string - tamanhoMaximo?: string - agrupadores: { - agrupador: { - id: string - nome: string - } - }[] -} - -export default async function CustomizedField (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'campocustomizado', - pluralName: 'camposcustomizados' - } - - const find = async ( - id: 'Produtos' | 'OrdemServico' | 'Contatos', - options?: { - raw?: boolean - } - ) => { - const findMethod = new Find< - ICustomizedFieldResponse, - Record - >(config) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - if (raw) { - return await findMethod.find(id, { raw: true }) - } else { - return await findMethod.find(id, { raw: false }) - } - } - - return Object.assign(config, { find }) -} diff --git a/src/entities/depositos/__tests__/create-response.ts b/src/entities/depositos/__tests__/create-response.ts new file mode 100644 index 0000000..cb6b75c --- /dev/null +++ b/src/entities/depositos/__tests__/create-response.ts @@ -0,0 +1,12 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + descricao: 'Depósito Geral', + situacao: 1 as const, + padrao: false, + desconsiderarSaldo: false +} diff --git a/src/entities/depositos/__tests__/find-response.ts b/src/entities/depositos/__tests__/find-response.ts new file mode 100644 index 0000000..b0c2878 --- /dev/null +++ b/src/entities/depositos/__tests__/find-response.ts @@ -0,0 +1,9 @@ +export default { + data: { + id: 12345678, + descricao: 'Depósito Geral', + situacao: 1 as const, + padrao: false, + desconsiderarSaldo: false + } +} diff --git a/src/entities/depositos/__tests__/get-response.ts b/src/entities/depositos/__tests__/get-response.ts new file mode 100644 index 0000000..e72bf1a --- /dev/null +++ b/src/entities/depositos/__tests__/get-response.ts @@ -0,0 +1,11 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Depósito Geral', + situacao: 1 as const, + padrao: false, + desconsiderarSaldo: false + } + ] +} diff --git a/src/entities/depositos/__tests__/index.spec.ts b/src/entities/depositos/__tests__/index.spec.ts new file mode 100644 index 0000000..76a6445 --- /dev/null +++ b/src/entities/depositos/__tests__/index.spec.ts @@ -0,0 +1,86 @@ +import { Chance } from 'chance' +import { Depositos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Depósitos entity', () => { + let repository: InMemoryBlingRepository + let entity: Depositos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Depositos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'depositos', + params: { + limite: undefined, + pagina: undefined, + descricao: undefined, + situacao: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idDeposito = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idDeposito }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'depositos', + id: String(idDeposito) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'depositos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idDeposito = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idDeposito, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'depositos', + id: String(idDeposito), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/depositos/__tests__/update-response.ts b/src/entities/depositos/__tests__/update-response.ts new file mode 100644 index 0000000..d513f2a --- /dev/null +++ b/src/entities/depositos/__tests__/update-response.ts @@ -0,0 +1,27 @@ +export default { + id: 12345678, + alertas: [ + { + code: 49, + msg: 'Uma ou mais parcelas da venda possuem erros de validação', + element: 'parcelas', + namespace: 'VENDAS', + collection: [ + { + index: 1, + code: 12, + msg: 'Id da forma de pagamento inválido.', + element: 'formaPagamento', + namespace: 'VENDAS' + } + ] + } + ] +} + +export const updateRequestBody = { + descricao: 'Depósito Geral', + situacao: 1 as const, + padrao: false, + desconsiderarSaldo: false +} diff --git a/src/entities/depositos/index.ts b/src/entities/depositos/index.ts new file mode 100644 index 0000000..13df886 --- /dev/null +++ b/src/entities/depositos/index.ts @@ -0,0 +1,94 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com depósitos. + * + * @see https://developer.bling.com.br/referencia#/Dep%C3%B3sitos + */ +export class Depositos extends Entity { + /** + * Obtém depósitos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Dep%C3%B3sitos/get_depositos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'depositos', + params: { + pagina: params?.pagina, + limite: params?.limite, + descricao: params?.descricao, + situacao: params?.situacao + } + }) + } + + /** + * Obtém um depósito. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Dep%C3%B3sitos/get_depositos__idDeposito_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'depositos', + id: String(params.idDeposito) + }) + } + + /** + * Cria um depósito. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Dep%C3%B3sitos/post_depositos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'depositos', + body + }) + } + + /** + * Altera um depósito. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Dep%C3%B3sitos/put_depositos__idDeposito_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idDeposito, ...body } = params + + return await this.repository.replace({ + endpoint: 'depositos', + id: String(idDeposito), + body + }) + } +} diff --git a/src/entities/depositos/interfaces/create.interface.ts b/src/entities/depositos/interfaces/create.interface.ts new file mode 100644 index 0000000..0ac517d --- /dev/null +++ b/src/entities/depositos/interfaces/create.interface.ts @@ -0,0 +1,12 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' + +export interface ICreateBody { + descricao: string + situacao: ISituacao + padrao: boolean + desconsiderarSaldo: boolean +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/depositos/interfaces/find.interface.ts b/src/entities/depositos/interfaces/find.interface.ts new file mode 100644 index 0000000..240ba66 --- /dev/null +++ b/src/entities/depositos/interfaces/find.interface.ts @@ -0,0 +1,18 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' + +export interface IFindParams { + /** + * ID do depósito + */ + idDeposito: number +} + +export interface IFindResponse { + data: { + id: number + descricao: string + situacao: ISituacao + padrao: boolean + desconsiderarSaldo: boolean + } +} diff --git a/src/entities/depositos/interfaces/get.interface.ts b/src/entities/depositos/interfaces/get.interface.ts new file mode 100644 index 0000000..17cb4a4 --- /dev/null +++ b/src/entities/depositos/interfaces/get.interface.ts @@ -0,0 +1,30 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Descrição do depósito + */ + descricao?: string + /** + * + */ + situacao?: ISituacao +} + +export interface IGetResponse { + data: { + id: number + descricao: string + situacao: ISituacao + padrao: boolean + desconsiderarSaldo: boolean + }[] +} diff --git a/src/entities/depositos/interfaces/update.interface.ts b/src/entities/depositos/interfaces/update.interface.ts new file mode 100644 index 0000000..31c3a0a --- /dev/null +++ b/src/entities/depositos/interfaces/update.interface.ts @@ -0,0 +1,21 @@ +import { IDefaultErrorFieldsResponse } from 'src/entities/@shared/interfaces/error.interface' +import ISituacao from 'src/entities/@shared/types/situacao.type' + +export interface IUpdateParams { + /** + * ID do depósito + */ + idDeposito: number +} + +export interface IUpdateBody { + descricao: string + situacao: ISituacao + padrao: boolean + desconsiderarSaldo: boolean +} + +export interface IUpdateResponse { + id: number + alertas?: IDefaultErrorFieldsResponse[] +} diff --git a/src/entities/deposits.ts b/src/entities/deposits.ts deleted file mode 100644 index 0a49a87..0000000 --- a/src/entities/deposits.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IDeposit { - descricao?: string - desconsiderarSaldo?: boolean - depositoPadrao?: boolean - situacao?: 'A' | 'I' -} - -export interface IDepositFilters { - situacao?: 'A' | 'I' -} - -export type IDepositInfos = Record - -export interface IDepositResponse { - id: string - descricao: string - situacao: string - depositaoPadrao: string - desconsiderarSaldo: 'true' | 'false' -} - -export default function Deposits (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'deposito', - pluralName: 'depositos' - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy() - .findBy, - create: new Create().create, - update: new Update().update - }) -} diff --git a/src/entities/empresas/__tests__/index.spec.ts b/src/entities/empresas/__tests__/index.spec.ts new file mode 100644 index 0000000..f1f9b85 --- /dev/null +++ b/src/entities/empresas/__tests__/index.spec.ts @@ -0,0 +1,29 @@ +import { Empresas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import meResponse from './me-response' + +describe('Empresas entity', () => { + let repository: InMemoryBlingRepository + let entity: Empresas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Empresas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(meResponse) + + const response = await entity.me() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'empresas/me/dados-basicos' + }) + expect(response).toBe(meResponse) + }) +}) diff --git a/src/entities/empresas/__tests__/me-response.ts b/src/entities/empresas/__tests__/me-response.ts new file mode 100644 index 0000000..6eb0d96 --- /dev/null +++ b/src/entities/empresas/__tests__/me-response.ts @@ -0,0 +1,7 @@ +export default { + data: { + nome: 'Empresa Teste LTDA', + cnpj: '12.345.657/8910-11', + email: 'empresa@email.com' + } +} diff --git a/src/entities/empresas/index.ts b/src/entities/empresas/index.ts new file mode 100644 index 0000000..badd225 --- /dev/null +++ b/src/entities/empresas/index.ts @@ -0,0 +1,23 @@ +import { Entity } from '../@shared/entity' +import { IMeResponse } from './interfaces/me.interface' + +/** + * Entidade para interação com empresas. + * + * @see https://developer.bling.com.br/referencia#/Empresas + */ +export class Empresas extends Entity { + /** + * Obtém dados básicos da empresa. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Empresas/get_empresas_me_dados_basicos + */ + public async me(): Promise { + return await this.repository.index({ + endpoint: 'empresas/me/dados-basicos' + }) + } +} diff --git a/src/entities/empresas/interfaces/me.interface.ts b/src/entities/empresas/interfaces/me.interface.ts new file mode 100644 index 0000000..368d5f8 --- /dev/null +++ b/src/entities/empresas/interfaces/me.interface.ts @@ -0,0 +1,7 @@ +export interface IMeResponse { + data: { + nome: string + cnpj: string + email: string + } +} diff --git a/src/entities/estoques/__tests__/create-response.ts b/src/entities/estoques/__tests__/create-response.ts new file mode 100644 index 0000000..48961fa --- /dev/null +++ b/src/entities/estoques/__tests__/create-response.ts @@ -0,0 +1,19 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + produto: { + id: 12345678 + }, + deposito: { + id: 12345678 + }, + operacao: 'B' as const, + preco: 1500.75, + custo: 1500.75, + quantidade: 50.75, + observacoes: 'Observações de estoque' +} diff --git a/src/entities/estoques/__tests__/find-balance-response.ts b/src/entities/estoques/__tests__/find-balance-response.ts new file mode 100644 index 0000000..8c1f29e --- /dev/null +++ b/src/entities/estoques/__tests__/find-balance-response.ts @@ -0,0 +1,11 @@ +export default { + data: [ + { + produto: { + id: 12345678 + }, + saldoFisicoTotal: 1500.75, + saldoVirtualTotal: 1500.75 + } + ] +} diff --git a/src/entities/estoques/__tests__/get-balances-response.ts b/src/entities/estoques/__tests__/get-balances-response.ts new file mode 100644 index 0000000..2cbd464 --- /dev/null +++ b/src/entities/estoques/__tests__/get-balances-response.ts @@ -0,0 +1,18 @@ +export default { + data: [ + { + produto: { + id: 12345678 + }, + saldoFisicoTotal: 1500.75, + saldoVirtualTotal: 1500.75, + depositos: [ + { + id: 12345678, + saldoFisico: 1250.75, + saldoVirtual: 1250.75 + } + ] + } + ] +} diff --git a/src/entities/estoques/__tests__/index.spec.ts b/src/entities/estoques/__tests__/index.spec.ts new file mode 100644 index 0000000..07b4858 --- /dev/null +++ b/src/entities/estoques/__tests__/index.spec.ts @@ -0,0 +1,90 @@ +import { Chance } from 'chance' +import { Estoques } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import findResponse from './find-balance-response' +import getResponse from './get-balances-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Estoques entity', () => { + let repository: InMemoryBlingRepository + let entity: Estoques + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Estoques(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should find balance successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idDeposito = chance.natural() + const idsProdutos: number[] = [] + for (let i = 0; i < chance.natural({ min: 2, max: 5 }); i++) { + idsProdutos.push(chance.natural()) + } + repository.setResponse(findResponse) + + const response = await entity.findBalance({ idDeposito, idsProdutos }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'estoques/saldos', + id: String(idDeposito), + params: { idsProdutos } + }) + expect(response).toBe(findResponse) + }) + + it('should get balances successfully', async () => { + const spy = jest.spyOn(repository, 'index') + const idsProdutos: number[] = [] + for (let i = 0; i < chance.natural({ min: 2, max: 5 }); i++) { + idsProdutos.push(chance.natural()) + } + repository.setResponse(getResponse) + + const response = await entity.getBalances({ idsProdutos }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'estoques/saldos', + params: { idsProdutos } + }) + expect(response).toBe(getResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'estoques', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idEstoque = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idEstoque, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'estoques', + id: String(idEstoque), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/estoques/__tests__/update-response.ts b/src/entities/estoques/__tests__/update-response.ts new file mode 100644 index 0000000..c0d79be --- /dev/null +++ b/src/entities/estoques/__tests__/update-response.ts @@ -0,0 +1,10 @@ +export default null + +export const updateRequestBody = { + operacao: 'B' as const, + preco: 1500.75, + custo: 1500.75, + quantidade: 50.75, + observacoes: 'Observações de estoque', + data: '2023-02-10 11:41:27' +} diff --git a/src/entities/estoques/index.ts b/src/entities/estoques/index.ts new file mode 100644 index 0000000..d734aa5 --- /dev/null +++ b/src/entities/estoques/index.ts @@ -0,0 +1,94 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { + IFindBalanceParams, + IFindBalanceResponse +} from './interfaces/find-balance.interface' +import { + IGetBalancesParams, + IGetBalancesResponse +} from './interfaces/get-balances.interface' +import { IUpdateBody, IUpdateParams } from './interfaces/update.interface' + +/** + * Entidade para interação com estoques. + * + * @see https://developer.bling.com.br/referencia#/Estoques + */ +export class Estoques extends Entity { + /** + * Obtém o saldo em estoque de produtos por depósito. + * + * @param {IFindBalanceParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Estoques/get_estoques_saldos__idDeposito_ + */ + public async findBalance( + params: IFindBalanceParams + ): Promise { + return await this.repository.show({ + endpoint: 'estoques/saldos', + id: String(params.idDeposito), + params: { idsProdutos: params.idsProdutos } + }) + } + + /** + * Obtém o saldo em estoque de produtos. + * + * @param {IGetBalancesParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Estoques/get_estoques_saldos + */ + public async getBalances( + params: IGetBalancesParams + ): Promise { + return await this.repository.index({ + endpoint: 'estoques/saldos', + params: { idsProdutos: params.idsProdutos } + }) + } + + /** + * Cria um registro de estoque. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Estoques/post_estoques + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'estoques', + body + }) + } + + /** + * Altera um registro de estoque. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Estoques/put_estoques__idEstoque_ + */ + public async update(params: IUpdateParams & IUpdateBody): Promise { + const { idEstoque, ...body } = params + + return await this.repository.replace({ + endpoint: 'estoques', + id: String(idEstoque), + body + }) + } +} diff --git a/src/entities/estoques/interfaces/create.interface.ts b/src/entities/estoques/interfaces/create.interface.ts new file mode 100644 index 0000000..610549d --- /dev/null +++ b/src/entities/estoques/interfaces/create.interface.ts @@ -0,0 +1,15 @@ +import { IOperacao } from '../types/operacao.type' + +export interface ICreateBody { + produto: { id: number } + deposito: { id: number } + operacao: IOperacao + preco?: number + custo?: number + quantidade: number + observacoes?: string +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/estoques/interfaces/find-balance.interface.ts b/src/entities/estoques/interfaces/find-balance.interface.ts new file mode 100644 index 0000000..c4a26f6 --- /dev/null +++ b/src/entities/estoques/interfaces/find-balance.interface.ts @@ -0,0 +1,18 @@ +export interface IFindBalanceParams { + /** + * ID do depósito + */ + idDeposito: number + /** + * IDs dos produtos + */ + idsProdutos: number[] +} + +export interface IFindBalanceResponse { + data: { + produto: { id: number } + saldoFisicoTotal: number + saldoVirtualTotal: number + }[] +} diff --git a/src/entities/estoques/interfaces/get-balances.interface.ts b/src/entities/estoques/interfaces/get-balances.interface.ts new file mode 100644 index 0000000..07521fd --- /dev/null +++ b/src/entities/estoques/interfaces/get-balances.interface.ts @@ -0,0 +1,19 @@ +export interface IGetBalancesParams { + /** + * IDs dos produtos + */ + idsProdutos: number[] +} + +export interface IGetBalancesResponse { + data: { + produto: { id: number } + saldoFisicoTotal: number + saldoVirtualTotal: number + depositos: { + id: number + saldoFisico: number + saldoVirtual: number + }[] + }[] +} diff --git a/src/entities/estoques/interfaces/update.interface.ts b/src/entities/estoques/interfaces/update.interface.ts new file mode 100644 index 0000000..96b1052 --- /dev/null +++ b/src/entities/estoques/interfaces/update.interface.ts @@ -0,0 +1,17 @@ +import { IOperacao } from '../types/operacao.type' + +export interface IUpdateParams { + /** + * ID do estoque + */ + idEstoque: number +} + +export interface IUpdateBody { + operacao: IOperacao + preco?: number + custo?: number + quantidade: number + observacoes?: string + data?: string +} diff --git a/src/entities/estoques/types/operacao.type.ts b/src/entities/estoques/types/operacao.type.ts new file mode 100644 index 0000000..2579b80 --- /dev/null +++ b/src/entities/estoques/types/operacao.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à operação de estoque. + * + * - `B`: Balanço + * - `E`: Entrada + * - `S`: Saída + */ +export type IOperacao = 'B' | 'E' | 'S' diff --git a/src/entities/formasDePagamento/__tests__/create-response.ts b/src/entities/formasDePagamento/__tests__/create-response.ts new file mode 100644 index 0000000..5014943 --- /dev/null +++ b/src/entities/formasDePagamento/__tests__/create-response.ts @@ -0,0 +1,25 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + descricao: 'Dinheiro', + tipoPagamento: 1 as const, + situacao: 1 as const, + padrao: 0 as const, + condicao: '1x', + destino: 1 as const, + finalidade: 1 as const, + taxas: { + aliquota: 3.5, + valor: 1.99, + prazo: 2 + }, + dadosCartao: { + bandeira: 1 as const, + tipo: 1 as const, + cnpjCredenciadora: '67168564000109' + } +} diff --git a/src/entities/formasDePagamento/__tests__/delete-response.ts b/src/entities/formasDePagamento/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/formasDePagamento/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/formasDePagamento/__tests__/find-response.ts b/src/entities/formasDePagamento/__tests__/find-response.ts new file mode 100644 index 0000000..b679955 --- /dev/null +++ b/src/entities/formasDePagamento/__tests__/find-response.ts @@ -0,0 +1,23 @@ +export default { + data: { + id: 12345678, + descricao: 'Dinheiro', + tipoPagamento: 1, + situacao: 1, + fixa: false, + padrao: 0, + condicao: '1x', + destino: 1, + finalidade: 1, + taxas: { + aliquota: 3.5, + valor: 1.99, + prazo: 2 + }, + dadosCartao: { + bandeira: 1, + tipo: 1, + cnpjCredenciadora: '67168564000109' + } + } +} diff --git a/src/entities/formasDePagamento/__tests__/get-response.ts b/src/entities/formasDePagamento/__tests__/get-response.ts new file mode 100644 index 0000000..17a6b47 --- /dev/null +++ b/src/entities/formasDePagamento/__tests__/get-response.ts @@ -0,0 +1,12 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Dinheiro', + tipoPagamento: 1, + situacao: 1, + fixa: false, + padrao: 0 + } + ] +} diff --git a/src/entities/formasDePagamento/__tests__/index.spec.ts b/src/entities/formasDePagamento/__tests__/index.spec.ts new file mode 100644 index 0000000..379083b --- /dev/null +++ b/src/entities/formasDePagamento/__tests__/index.spec.ts @@ -0,0 +1,102 @@ +import { Chance } from 'chance' +import { FormasDePagamento } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Formas de pagamento entity', () => { + let repository: InMemoryBlingRepository + let entity: FormasDePagamento + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new FormasDePagamento(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idFormaPagamento = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idFormaPagamento }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'formas-pagamentos', + id: String(idFormaPagamento) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'formas-pagamentos', + params: { + limite: undefined, + pagina: undefined, + descricao: undefined, + tiposPagamentos: undefined, + situacao: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idFormaPagamento = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idFormaPagamento }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'formas-pagamentos', + id: String(idFormaPagamento) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'formas-pagamentos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idFormaPagamento = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idFormaPagamento, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'formas-pagamentos', + id: String(idFormaPagamento), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/formasDePagamento/__tests__/update-response.ts b/src/entities/formasDePagamento/__tests__/update-response.ts new file mode 100644 index 0000000..a7200b8 --- /dev/null +++ b/src/entities/formasDePagamento/__tests__/update-response.ts @@ -0,0 +1,25 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + descricao: 'Dinheiro', + tipoPagamento: 1 as const, + situacao: 1 as const, + padrao: 0 as const, + condicao: '1x', + destino: 1 as const, + finalidade: 1 as const, + taxas: { + aliquota: 3.5, + valor: 1.99, + prazo: 2 + }, + dadosCartao: { + bandeira: 1 as const, + tipo: 1 as const, + cnpjCredenciadora: '67168564000109' + } +} diff --git a/src/entities/formasDePagamento/index.ts b/src/entities/formasDePagamento/index.ts new file mode 100644 index 0000000..bcf20cb --- /dev/null +++ b/src/entities/formasDePagamento/index.ts @@ -0,0 +1,113 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com formas de pagamento. + * + * @see https://developer.bling.com.br/referencia#/Formas%20de%20Pagamentos + */ +export class FormasDePagamento extends Entity { + /** + * Remove uma forma de pagamento. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Formas%20de%20Pagamentos/delete_formas_pagamentos__idFormaPagamento_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'formas-pagamentos', + id: String(params.idFormaPagamento) + }) + } + + /** + * Obtém formas de pagamentos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Contratos/get_contratos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'formas-pagamentos', + params: { + pagina: params?.pagina, + limite: params?.limite, + descricao: params?.descricao, + tiposPagamentos: params?.tiposPagamentos, + situacao: params?.situacao + } + }) + } + + /** + * Obtém uma forma de pagamento. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Formas%20de%20Pagamentos/get_formas_pagamentos__idFormaPagamento_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'formas-pagamentos', + id: String(params.idFormaPagamento) + }) + } + + /** + * Cria uma forma de pagamento. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Formas%20de%20Pagamentos/post_formas_pagamentos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'formas-pagamentos', + body + }) + } + + /** + * Altera uma forma de pagamento. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Formas%20de%20Pagamentos/put_formas_pagamentos__idFormaPagamento_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idFormaPagamento, ...body } = params + + return await this.repository.replace({ + endpoint: 'formas-pagamentos', + id: String(idFormaPagamento), + body + }) + } +} diff --git a/src/entities/formasDePagamento/interfaces/create.interface.ts b/src/entities/formasDePagamento/interfaces/create.interface.ts new file mode 100644 index 0000000..19b7be5 --- /dev/null +++ b/src/entities/formasDePagamento/interfaces/create.interface.ts @@ -0,0 +1,31 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' +import { IBandeiraCartao } from '../types/bandeira-cartao.type' +import { IDestino } from '../types/destino.type' +import { IFinalidade } from '../types/finalidade.type' +import { IPadrao } from '../types/padrao.type' +import { ITipoCartao } from '../types/tipo-cartao.type' +import { ITipoPagamento } from '../types/tipo-pagamento.type' + +export interface ICreateBody { + descricao: string + tipoPagamento: ITipoPagamento + situacao?: ISituacao + padrao?: IPadrao + condicao?: string + destino: IDestino + finalidade: IFinalidade + taxas?: { + aliquota?: number + valor?: number + prazo?: number + } + dadosCartao?: { + bandeira: IBandeiraCartao + tipo: ITipoCartao + cnpjCredenciadora?: string + } +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/formasDePagamento/interfaces/delete.interface.ts b/src/entities/formasDePagamento/interfaces/delete.interface.ts new file mode 100644 index 0000000..5a3af00 --- /dev/null +++ b/src/entities/formasDePagamento/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID da forma de pagamento + */ + idFormaPagamento: number +} diff --git a/src/entities/formasDePagamento/interfaces/find.interface.ts b/src/entities/formasDePagamento/interfaces/find.interface.ts new file mode 100644 index 0000000..7656a60 --- /dev/null +++ b/src/entities/formasDePagamento/interfaces/find.interface.ts @@ -0,0 +1,38 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' +import { IBandeiraCartao } from '../types/bandeira-cartao.type' +import { IDestino } from '../types/destino.type' +import { IFinalidade } from '../types/finalidade.type' +import { IPadrao } from '../types/padrao.type' +import { ITipoCartao } from '../types/tipo-cartao.type' +import { ITipoPagamento } from '../types/tipo-pagamento.type' + +export interface IFindParams { + /** + * ID da forma de pagamento + */ + idFormaPagamento: number +} + +export interface IFindResponse { + data: { + id: number + descricao: string + tipoPagamento: ITipoPagamento + situacao: ISituacao + fixa: boolean + padrao: IPadrao + condicao: string + destino: IDestino + finalidade: IFinalidade + taxas: { + aliquota: number + valor: number + prazo: number + } + dadosCartao?: { + bandeira: IBandeiraCartao + tipo: ITipoCartao + cnpjCredenciadora: string + } + } +} diff --git a/src/entities/formasDePagamento/interfaces/get.interface.ts b/src/entities/formasDePagamento/interfaces/get.interface.ts new file mode 100644 index 0000000..9bab96c --- /dev/null +++ b/src/entities/formasDePagamento/interfaces/get.interface.ts @@ -0,0 +1,37 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' +import { IPadrao } from '../types/padrao.type' +import { ITipoPagamento } from '../types/tipo-pagamento.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Descrição da forma de pagamento + */ + descricao?: string + /** + * + */ + tiposPagamentos?: ITipoPagamento[] + /** + * + */ + situacao?: ISituacao +} + +export interface IGetResponse { + data: { + id: number + descricao: string + tipoPagamento: ITipoPagamento + situacao: ISituacao + fixa: boolean + padrao: IPadrao + }[] +} diff --git a/src/entities/formasDePagamento/interfaces/update.interface.ts b/src/entities/formasDePagamento/interfaces/update.interface.ts new file mode 100644 index 0000000..66a694a --- /dev/null +++ b/src/entities/formasDePagamento/interfaces/update.interface.ts @@ -0,0 +1,38 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' +import { IBandeiraCartao } from '../types/bandeira-cartao.type' +import { IDestino } from '../types/destino.type' +import { IFinalidade } from '../types/finalidade.type' +import { IPadrao } from '../types/padrao.type' +import { ITipoCartao } from '../types/tipo-cartao.type' +import { ITipoPagamento } from '../types/tipo-pagamento.type' + +export interface IUpdateParams { + /** + * ID da forma de pagamento + */ + idFormaPagamento: number +} + +export interface IUpdateBody { + descricao: string + tipoPagamento: ITipoPagamento + situacao?: ISituacao + padrao?: IPadrao + condicao?: string + destino: IDestino + finalidade: IFinalidade + taxas?: { + aliquota?: number + valor?: number + prazo?: number + } + dadosCartao?: { + bandeira: IBandeiraCartao + tipo: ITipoCartao + cnpjCredenciadora?: string + } +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/formasDePagamento/types/bandeira-cartao.type.ts b/src/entities/formasDePagamento/types/bandeira-cartao.type.ts new file mode 100644 index 0000000..6b141e4 --- /dev/null +++ b/src/entities/formasDePagamento/types/bandeira-cartao.type.ts @@ -0,0 +1,15 @@ +/** + * Tipagem referente à bandeira de um cartão de crédito. + * + * - `1`: Visa + * - `2`: Mastercard + * - `3`: American Express + * - `4`: Sorocred + * - `5`: Diners Club + * - `6`: Elo + * - `7`: Hipercard + * - `8`: Aura + * - `9`: Cabal + * - `99`: Outros + */ +export type IBandeiraCartao = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 99 diff --git a/src/entities/formasDePagamento/types/destino.type.ts b/src/entities/formasDePagamento/types/destino.type.ts new file mode 100644 index 0000000..04adbd3 --- /dev/null +++ b/src/entities/formasDePagamento/types/destino.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao destino da forma de pagamento. + * + * - `1`: Conta a receber/pagar + * - `2`: Ficha financeira + * - `3`: Caixa e bancos + */ +export type IDestino = 1 | 2 | 3 diff --git a/src/entities/formasDePagamento/types/finalidade.type.ts b/src/entities/formasDePagamento/types/finalidade.type.ts new file mode 100644 index 0000000..37b1c79 --- /dev/null +++ b/src/entities/formasDePagamento/types/finalidade.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à finalidade de uma forma de pagamento. + * + * - `1`: Pagamentos + * - `2`: Recebimentos + * - `3`: Pagamentos e Recebimentos + */ +export type IFinalidade = 1 | 2 | 3 diff --git a/src/entities/formasDePagamento/types/padrao.type.ts b/src/entities/formasDePagamento/types/padrao.type.ts new file mode 100644 index 0000000..b1362a0 --- /dev/null +++ b/src/entities/formasDePagamento/types/padrao.type.ts @@ -0,0 +1,8 @@ +/** + * Define o padrão da forma de pagamento. + * + * - `0`: Não + * - `1`: Padrão + * - `2`: Padrão devolução + */ +export type IPadrao = 0 | 1 | 2 diff --git a/src/entities/formasDePagamento/types/tipo-cartao.type.ts b/src/entities/formasDePagamento/types/tipo-cartao.type.ts new file mode 100644 index 0000000..9d4c521 --- /dev/null +++ b/src/entities/formasDePagamento/types/tipo-cartao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo de um cartão. + * + * - `1`: TEF + * - `2`: POS + */ +export type ITipoCartao = 1 | 2 diff --git a/src/entities/formasDePagamento/types/tipo-pagamento.type.ts b/src/entities/formasDePagamento/types/tipo-pagamento.type.ts new file mode 100644 index 0000000..3d65d74 --- /dev/null +++ b/src/entities/formasDePagamento/types/tipo-pagamento.type.ts @@ -0,0 +1,37 @@ +/** + * - `1`: Dinheiro + * - `2`: Cheque + * - `3`: Cartão de Crédito + * - `4`: Cartão de Débito + * - `5`: Crédito Loja + * - `10`: Vale Alimentação + * - `11`: Vale Refeição + * - `12`: Vale Presente + * - `13`: Vale Combustível + * - `14`: Duplicata Mercantil + * - `15`: Boleto Bancário + * - `16`: Depósito Bancário + * - `17`: Pagamento Instantâneo (PIX) + * - `18`: Transferência Bancária, Carteira Digital + * - `19`: Programa de Fidelidade, Cashback, Crédito Virtual + * - `90`: Sem pagamento + * - `99`: Outros + */ +export type ITipoPagamento = + | 1 + | 2 + | 3 + | 4 + | 5 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 90 + | 99 diff --git a/src/entities/homologacao/__tests__/change-situation-response.ts b/src/entities/homologacao/__tests__/change-situation-response.ts new file mode 100644 index 0000000..22535aa --- /dev/null +++ b/src/entities/homologacao/__tests__/change-situation-response.ts @@ -0,0 +1,9 @@ +export default { + headers: { + 'x-bling-homologacao': '123' + } +} + +export const changeSituationRequest = { + situacao: 'I' +} diff --git a/src/entities/homologacao/__tests__/create-response.ts b/src/entities/homologacao/__tests__/create-response.ts new file mode 100644 index 0000000..d7b426e --- /dev/null +++ b/src/entities/homologacao/__tests__/create-response.ts @@ -0,0 +1,20 @@ +export default { + data: { + id: 12345678, + nome: 'Copo do Bling', + preco: 32.56, + codigo: 'COD-4587' + }, + headers: { + 'x-bling-homologacao': '123' + } +} + +export const createRequestBody = { + nome: 'Copo do Bling', + preco: 32.56, + codigo: 'COD-4587', + headers: { + 'x-bling-homologacao': '123' + } +} diff --git a/src/entities/homologacao/__tests__/delete-response.ts b/src/entities/homologacao/__tests__/delete-response.ts new file mode 100644 index 0000000..54eac37 --- /dev/null +++ b/src/entities/homologacao/__tests__/delete-response.ts @@ -0,0 +1,5 @@ +export default { + headers: { + 'x-bling-homologacao': '123' + } +} diff --git a/src/entities/homologacao/__tests__/get-response.ts b/src/entities/homologacao/__tests__/get-response.ts new file mode 100644 index 0000000..f02dc41 --- /dev/null +++ b/src/entities/homologacao/__tests__/get-response.ts @@ -0,0 +1,10 @@ +export default { + data: { + nome: 'Copo do Bling', + preco: 32.56, + codigo: 'COD-4587' + }, + headers: { + 'x-bling-homologacao': '123' + } +} diff --git a/src/entities/homologacao/__tests__/index.spec.ts b/src/entities/homologacao/__tests__/index.spec.ts new file mode 100644 index 0000000..374f3d9 --- /dev/null +++ b/src/entities/homologacao/__tests__/index.spec.ts @@ -0,0 +1,192 @@ +import { Chance } from 'chance' +import { Homologacao } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import changeSituationResponse, { + changeSituationRequest +} from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Homologação entity', () => { + let repository: InMemoryBlingRepository + let entity: Homologacao + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Homologacao(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idProdutoHomologacao = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + const hash = chance.word() + + const response = await entity.delete({ + idProdutoHomologacao, + hash + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + id: String(idProdutoHomologacao), + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + shouldIncludeHeadersInResponse: true + }) + expect(response).toBe(getResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idProdutoHomologacao = chance.natural() + repository.setResponse(changeSituationResponse) + const hash = chance.word() + + const response = await entity.changeSituation({ + idProdutoHomologacao, + hash, + ...changeSituationRequest + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + id: String(idProdutoHomologacao), + body: changeSituationRequest, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(response).toBe(changeSituationResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + const hash = chance.word() + + const response = await entity.create({ + hash, + ...createRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + body: createRequestBody, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idProdutoHomologacao = chance.natural() + repository.setResponse(updateResponse) + const hash = chance.word() + + const response = await entity.update({ + idProdutoHomologacao, + hash, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + id: String(idProdutoHomologacao), + body: updateRequestBody, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(response).toBe(updateResponse) + }) + + it('should execute successfully', async () => { + const getSpy = jest.spyOn(repository, 'index') + const postSpy = jest.spyOn(repository, 'store') + const putSpy = jest.spyOn(repository, 'replace') + const patchSpy = jest.spyOn(repository, 'update') + const deleteSpy = jest.spyOn(repository, 'destroy') + repository.setIndexResponse(getResponse) + repository.setStoreResponse(createResponse) + repository.setUpdateResponse(changeSituationResponse) + repository.setReplaceResponse(updateResponse) + repository.setDestroyResponse(deleteResponse) + const { headers, data } = createResponse + const hash = headers['x-bling-homologacao'] + const { id, ...body } = data + + await entity.execute() + + expect(getSpy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + shouldIncludeHeadersInResponse: true + }) + expect(postSpy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + body, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(putSpy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + body: { + ...body, + nome: 'Copo' + }, + id: String(id), + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(patchSpy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + body: { + situacao: 'I' + }, + id: String(id), + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + expect(deleteSpy).toHaveBeenCalledWith({ + endpoint: 'homologacao/produtos', + id: String(id), + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + }) +}) diff --git a/src/entities/homologacao/__tests__/update-response.ts b/src/entities/homologacao/__tests__/update-response.ts new file mode 100644 index 0000000..432c32f --- /dev/null +++ b/src/entities/homologacao/__tests__/update-response.ts @@ -0,0 +1,11 @@ +export default { + headers: { + 'x-bling-homologacao': '123' + } +} + +export const updateRequestBody = { + nome: 'Copo do Bling', + preco: 32.56, + codigo: 'COD-4587' +} diff --git a/src/entities/homologacao/index.ts b/src/entities/homologacao/index.ts new file mode 100644 index 0000000..28bcda8 --- /dev/null +++ b/src/entities/homologacao/index.ts @@ -0,0 +1,192 @@ +import { Entity } from '../@shared/entity' +import { + IChangeSituationBody, + IChangeSituationHeaders, + IChangeSituationParams, + IChangeSituationResponse +} from './interfaces/change-situation.interface' +import { + ICreateBody, + ICreateHeaders, + ICreateResponse +} from './interfaces/create.interface' +import { + IDeleteHeaders, + IDeleteParams, + IDeleteResponse +} from './interfaces/delete.interface' +import { IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateHeaders, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com homologação. + * + * @see https://developer.bling.com.br/referencia#/Homologa%C3%A7%C3%A3o + */ +export class Homologacao extends Entity { + /** + * Remove o produto da homologação. + * + * Será retornado, no objeto de retorno, a _hash_ de homologação dentro da + * propriedade `headers`. + * + * @param {IDeleteParams & IDeleteHeaders} params Parâmetros da remoção. + * + * @returns {Promise} Retorno da deleção. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Homologa%C3%A7%C3%A3o/delete_homologacao_produtos__idProdutoHomologacao_ + */ + public async delete( + params: IDeleteParams & IDeleteHeaders + ): Promise { + return await this.repository.destroy({ + endpoint: 'homologacao/produtos', + id: String(params.idProdutoHomologacao), + headers: { + 'x-bling-homologacao': params.hash + }, + shouldIncludeHeadersInResponse: true + }) + } + + /** + * Obtém o produto da homologação. + * + * @param {IGetHeaders} params Os parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Homologa%C3%A7%C3%A3o/get_homologacao_produtos + */ + public async get(): Promise { + return await this.repository.index({ + endpoint: 'homologacao/produtos', + shouldIncludeHeadersInResponse: true + }) + } + + /** + * Altera a situação do produto da homologação. + * + * @param {IChangeSituationParams & IChangeSituationHeaders & IChangeSituationBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Homologa%C3%A7%C3%A3o/patch_homologacao_produtos__idProdutoHomologacao__situacoes + */ + public async changeSituation( + params: IChangeSituationParams & + IChangeSituationHeaders & + IChangeSituationBody + ): Promise { + const { idProdutoHomologacao, hash, ...body } = params + + return await this.repository.update({ + endpoint: 'homologacao/produtos', + id: String(idProdutoHomologacao), + body, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + } + + /** + * Cria um contrato. + * + * @param {ICreateHeaders & ICreateBody} params O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Homologa%C3%A7%C3%A3o/post_homologacao_produtos + */ + public async create( + params: ICreateHeaders & ICreateBody + ): Promise { + const { hash, ...body } = params + return await this.repository.store({ + endpoint: 'homologacao/produtos', + body, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + } + + /** + * Altera o produto da homologação. + * + * @param {IUpdateParams & IUpdateHeaders & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Homologa%C3%A7%C3%A3o/put_homologacao_produtos__idProdutoHomologacao_ + */ + public async update( + params: IUpdateParams & IUpdateHeaders & IUpdateBody + ): Promise { + const { idProdutoHomologacao, hash, ...body } = params + + return await this.repository.replace({ + endpoint: 'homologacao/produtos', + id: String(idProdutoHomologacao), + body, + headers: { + 'x-bling-homologacao': hash + }, + shouldIncludeHeadersInResponse: true + }) + } + + /** + * Executa a homologação do aplicativo. + * + * @returns {Promise} + * + * @see https://developer.bling.com.br/homologacao + */ + public async execute(): Promise { + // Passo 1: GET + const getResponse = await this.get() + + // Passo 2: POST + const postResponse = await this.create({ + hash: getResponse.headers['x-bling-homologacao'], + ...getResponse.data + }) + + // Passo 3: PUT + const { id: patchId, ...patchBody } = postResponse.data + const patchResponse = await this.update({ + hash: postResponse.headers['x-bling-homologacao'], + idProdutoHomologacao: patchId, + ...patchBody, + nome: 'Copo' + }) + + // Passo 4: PATCH + const putResponse = await this.changeSituation({ + hash: patchResponse.headers['x-bling-homologacao'], + idProdutoHomologacao: postResponse.data.id, + situacao: 'I' + }) + + // Passo 5: DELETE + await this.delete({ + hash: putResponse.headers['x-bling-homologacao'], + idProdutoHomologacao: postResponse.data.id + }) + } +} diff --git a/src/entities/homologacao/interfaces/change-situation.interface.ts b/src/entities/homologacao/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..c0f35fe --- /dev/null +++ b/src/entities/homologacao/interfaces/change-situation.interface.ts @@ -0,0 +1,20 @@ +export interface IChangeSituationParams { + /** + * ID do produto da homologação + */ + idProdutoHomologacao: number +} + +export interface IChangeSituationHeaders { + hash: string +} + +export interface IChangeSituationBody { + situacao: string +} + +export interface IChangeSituationResponse { + headers: { + 'x-bling-homologacao': string + } +} diff --git a/src/entities/homologacao/interfaces/create.interface.ts b/src/entities/homologacao/interfaces/create.interface.ts new file mode 100644 index 0000000..2d03b67 --- /dev/null +++ b/src/entities/homologacao/interfaces/create.interface.ts @@ -0,0 +1,21 @@ +export interface ICreateHeaders { + hash: string +} + +export interface ICreateBody { + nome: string + preco: number + codigo: string +} + +export interface ICreateResponse { + headers: { + 'x-bling-homologacao': string + } + data: { + id: number + nome: string + preco: number + codigo: string + } +} diff --git a/src/entities/homologacao/interfaces/delete.interface.ts b/src/entities/homologacao/interfaces/delete.interface.ts new file mode 100644 index 0000000..044b642 --- /dev/null +++ b/src/entities/homologacao/interfaces/delete.interface.ts @@ -0,0 +1,16 @@ +export interface IDeleteParams { + /** + * ID do produto da homologação + */ + idProdutoHomologacao: number +} + +export interface IDeleteHeaders { + hash: string +} + +export interface IDeleteResponse { + headers: { + 'x-bling-homologacao': string + } +} diff --git a/src/entities/homologacao/interfaces/get.interface.ts b/src/entities/homologacao/interfaces/get.interface.ts new file mode 100644 index 0000000..31faf76 --- /dev/null +++ b/src/entities/homologacao/interfaces/get.interface.ts @@ -0,0 +1,10 @@ +export interface IGetResponse { + headers: { + 'x-bling-homologacao': string + } + data: { + nome: string + preco: number + codigo: string + } +} diff --git a/src/entities/homologacao/interfaces/update.interface.ts b/src/entities/homologacao/interfaces/update.interface.ts new file mode 100644 index 0000000..e86ea0d --- /dev/null +++ b/src/entities/homologacao/interfaces/update.interface.ts @@ -0,0 +1,22 @@ +export interface IUpdateParams { + /** + * ID do produto da homologação + */ + idProdutoHomologacao: number +} + +export interface IUpdateHeaders { + hash: string +} + +export interface IUpdateBody { + nome: string + preco: number + codigo: string +} + +export interface IUpdateResponse { + headers: { + 'x-bling-homologacao': string + } +} diff --git a/src/entities/invoices.ts b/src/entities/invoices.ts deleted file mode 100644 index 8ea3007..0000000 --- a/src/entities/invoices.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' - -export interface IInvoice { - tipo?: 'S' | 'E' - finalidade?: '1' | '2' | '3' | '4' - loja?: number - numero_loja?: number - numero_nf?: number - nat_operacao?: string - data_operacao?: string - doc_referenciado?: { - modelo: '1' | '2' | '2D' | '4' | '55' | '65' - data?: string - numero?: number - serie?: string - coo?: string - chave_acesso?: string - } - cliente: { - nome: string - tipo_pessoa: 'J' | 'F' | 'E' - cpf_cnpj?: string - ie_rg?: string - contribuinte?: '1' | '2' | '9' - endereco: string - numero: string - complemento?: string - bairro: string - cep: string - cidade: string - uf: string - pais?: string - fone?: string - email: string - } - transporte?: { - transportadora: string - cpf_cnpj?: string - ie_rg?: string - endereco?: string - cidade?: string - uf?: string - placa?: string - uf_veiculo?: string - marca?: string - tipo_frete?: 'R' | 'D' | 'T' | '3' | '4' | 'S' - qtde_volumes?: number - especie?: string - numero?: number - peso_bruto?: string - peso_liquido?: string - servico_correios?: string - dados_etiqueta?: { - nome?: string - endereco?: string - numero?: string - complemento?: string - municipio?: string - uf?: string - cep?: string - bairro?: string - } - volumes?: { - volume?: { - servico: string - codigoRastreamento?: string - } - }[] - } - itens?: { - item: { - codigo?: string - descricao: string - un: 'pc' | 'un' | 'cx' - qtde: number - vlr_unit: number - tipo: 'P' | 'S' - peso_bruto?: string - numero_pedido_compra?: number - peso_liq?: string - class_fiscal?: string - cest?: string - cod_servico?: string - origem: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' - informacoes_adicionais?: string - } - }[] - parcelas?: { - parcela: { - dias?: number - data?: string - vlr?: number - obs?: string - forma?: string - } - }[] - nf_produtor_rural_referenciada?: { - numero?: string - serie?: string - ano_mes_emissao?: string - } - vlr_frete?: string - vlr_seguro?: string - vlr_despesas?: string - vlr_desconto?: string - obs?: string - intermediador?: { - cnpj?: string - nomeUsuario?: string - } -} - -export interface IInvoiceFilters { - dataEmissao?: string - situacao?: - | 'Pendente' - | 'Emitida' - | 'Cancelada' - | 'Enviada - Aguardando recibo' - | 'Rejeitada' - | 'Autorizada' - | 'Emitida DANFE' - | 'Registrada' - | 'Enviada - Aguardando protocolo' - | 'Denegada' - | 'Consultar situação' - | 'Bloqueada' - tipo?: 'E' | 'S' -} - -export type IInvoiceInfos = Record - -export interface IInvoiceCreateResponse { - numero: string - serie: string - codigos_rastreamento: { - codigo_rastreamento: string - } - volumes: { - volume: { - servico: string - codigoRastreamento: string - } - }[] -} - -export interface IInvoiceSendResponse { - situacao: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' - mensagem: string - erro?: string - chaveAcesso: string - linkDanfe: string -} - -export interface IInvoiceResponse { - id: string - serie: string - numero: string - loja: number - numeroPedidoLoja: string - tipo: 'E' | 'S' - situacao: - | 'Pendente' - | 'Emitida' - | 'Cancelada' - | 'Enviada - Aguardando recibo' - | 'Rejeitada' - | 'Autorizada' - | 'Emitida DANFE' - | 'Registrada' - | 'Enviada - Aguardando protocolo' - | 'Denegada' - | 'Consultar situação' - | 'Bloqueada' - cliente: { - nome: string - cnpj: string - ie?: string - rg?: string - endereco: string - numero: string - complemento: string - cidade: string - bairro: string - cep: string - uf: string - email: string - fone: string - } - contato: string - cnpj: string - vendedor?: string - dataEmissao: string - valorNota: number - chaveAcesso?: string - xml?: string - linkDanfe: string - codigosRastreamento: { - codigoRastreamento?: string - } - cfops: string[] - tipoIntegracao: string - transporte: { - transportadora: string - cnpj: string - tipo_frete: 'R' | 'D' | 'T' | '3' | '4' | 'S' - volumes: { - volume: { - id: string - idServico: string - servico: string - codigoServico: string - codigoRastreamento?: string - valorFretePrevisto: number - remessa?: { - numero: string - dataCriacao: string - } - dataSaida: string - prazoEntregaPrevisto: number - valorDeclarado: string - avisoRecebimento: boolean - maoPropria: boolean - dimensoes: { - peso: string - altura: string - largura: string - comprimento: string - diametro: string - } - urlRastreamento: string - } - }[] - enderecoEntrega: { - nome: string - endereco: string - numero: string - complemento?: string - cidade: string - bairro: string - cep: string - uf: string - } - } -} - -export default function Invoices (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'notafiscal', - pluralName: 'notasfiscais' - } - - const find = async ( - numero: number | string, - serie: number | string, - options?: { - raw?: boolean - } - ) => { - const findMethod = new Find(config) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (raw) { - return await findMethod.find(`${numero}/${serie}`, { raw: true }) - } else { - return await findMethod.find(`${numero}/${serie}`, { raw: false }) - } - } - - const send = async ( - numero: number | string, - serie: number | string, - options?: { - sendEmail?: boolean - raw?: boolean - } - ) => { - const createMethod = new Create, IInvoiceResponse>({ - ...config, - endpoint: `${config.singularName}/${numero}/${serie}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await createMethod.create( - {}, - { raw: true }, - { - sendEmail: options.sendEmail - } - ) - } else { - return await createMethod.create( - {}, - { raw: false }, - { - sendEmail: options.sendEmail - } - ) - } - } else { - return await createMethod.create({}) - } - } - - const create = async ( - data: IInvoice, - options?: { - raw?: boolean - } - ) => { - const createMethod = new Create, IInvoiceResponse>({ - ...config, - endpoint: 'notafiscal', - singularName: 'notaFiscal' - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (raw) { - return await createMethod.create(data, { raw: true }) - } else { - return await createMethod.create(data, { raw: false }) - } - } - - return Object.assign(config, { - all: new All().all, - find, - send, - findBy: new FindBy() - .findBy, - create - }) -} diff --git a/src/entities/logisticas/__tests__/create-response.ts b/src/entities/logisticas/__tests__/create-response.ts new file mode 100644 index 0000000..8ebcbb4 --- /dev/null +++ b/src/entities/logisticas/__tests__/create-response.ts @@ -0,0 +1,20 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + descricao: 'Correios Cliente', + situacao: 'H' as const, + servicos: [ + { + descricao: 'CARTA REG AR CONV B1 MFD', + freteItem: 12.45, + estimativaEntrega: 2, + codigo: 'ABC1234', + transportador: { id: 12345678 }, + aliases: ['ALIAS1'] + } + ] +} diff --git a/src/entities/logisticas/__tests__/delete-response.ts b/src/entities/logisticas/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/logisticas/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/logisticas/__tests__/find-response.ts b/src/entities/logisticas/__tests__/find-response.ts new file mode 100644 index 0000000..1927713 --- /dev/null +++ b/src/entities/logisticas/__tests__/find-response.ts @@ -0,0 +1,28 @@ +export default { + data: { + id: 6423813145, + descricao: 'Correios Cliente', + tipoIntegracao: 'Correios', + integracaoNativa: false, + situacao: 'H', + integracao: { + id: 12345678 + }, + servicos: [ + { + id: 6423813145, + descricao: 'CARTA REG AR CONV B1 MFD', + freteItem: 12.45, + estimativaEntrega: 2, + codigo: 'ABC1234', + logistica: { + id: 12345678 + }, + transportador: { + id: 12345678 + }, + aliases: ['ALIAS1'] + } + ] + } +} diff --git a/src/entities/logisticas/__tests__/get-response.ts b/src/entities/logisticas/__tests__/get-response.ts new file mode 100644 index 0000000..74b44af --- /dev/null +++ b/src/entities/logisticas/__tests__/get-response.ts @@ -0,0 +1,19 @@ +export default { + data: [ + { + id: 6423813145, + descricao: 'Correios Cliente', + tipoIntegracao: 'Correios', + integracaoNativa: false, + situacao: 'H', + integracao: { + id: 12345678 + }, + servicos: [ + { + id: 6423813145 + } + ] + } + ] +} diff --git a/src/entities/logisticas/__tests__/index.spec.ts b/src/entities/logisticas/__tests__/index.spec.ts new file mode 100644 index 0000000..38ffa2e --- /dev/null +++ b/src/entities/logisticas/__tests__/index.spec.ts @@ -0,0 +1,101 @@ +import { Chance } from 'chance' +import { Logisticas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Logísticas entity', () => { + let repository: InMemoryBlingRepository + let entity: Logisticas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Logisticas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idLogistica = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idLogistica }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas', + id: String(idLogistica) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas', + params: { + limite: undefined, + pagina: undefined, + tipoIntegracao: undefined, + situacao: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idLogistica = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idLogistica }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas', + id: String(idLogistica) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idLogistica = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idLogistica, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas', + id: String(idLogistica), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/logisticas/__tests__/update-response.ts b/src/entities/logisticas/__tests__/update-response.ts new file mode 100644 index 0000000..2cd5dec --- /dev/null +++ b/src/entities/logisticas/__tests__/update-response.ts @@ -0,0 +1,6 @@ +export default null + +export const updateRequestBody = { + descricao: 'Correios Cliente', + situacao: 'H' as const +} diff --git a/src/entities/logisticas/index.ts b/src/entities/logisticas/index.ts new file mode 100644 index 0000000..a05d4ed --- /dev/null +++ b/src/entities/logisticas/index.ts @@ -0,0 +1,106 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IUpdateBody, IUpdateParams } from './interfaces/update.interface' + +/** + * Entidade para interação com logísticas. + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas + */ +export class Logisticas extends Entity { + /** + * Remove uma logística. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas/delete_logisticas__idLogistica_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'logisticas', + id: String(params.idLogistica) + }) + } + + /** + * Obtém logísticas. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas/get_logisticas + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'logisticas', + params: { + pagina: params?.pagina, + limite: params?.limite, + tipoIntegracao: params?.tipoIntegracao, + situacao: params?.situacao + } + }) + } + + /** + * Obtém uma logística. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas/get_logisticas__idLogistica_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'logisticas', + id: String(params.idLogistica) + }) + } + + /** + * Cria logística. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas/post_logisticas + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'logisticas', + body + }) + } + + /** + * Altera uma logística. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas/put_logisticas__idLogistica_ + */ + public async update(params: IUpdateParams & IUpdateBody): Promise { + const { idLogistica, ...body } = params + + return await this.repository.replace({ + endpoint: 'logisticas', + id: String(idLogistica), + body + }) + } +} diff --git a/src/entities/logisticas/interfaces/create.interface.ts b/src/entities/logisticas/interfaces/create.interface.ts new file mode 100644 index 0000000..4adaa29 --- /dev/null +++ b/src/entities/logisticas/interfaces/create.interface.ts @@ -0,0 +1,18 @@ +import { ISituacao } from '../types/situacao.type' + +export interface ICreateBody { + descricao: string + situacao: ISituacao + servicos?: { + descricao: string + freteItem?: number + estimativaEntrega?: number + codigo?: string + transportador?: { id: number } + aliases?: string[] + }[] +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/logisticas/interfaces/delete.interface.ts b/src/entities/logisticas/interfaces/delete.interface.ts new file mode 100644 index 0000000..3774279 --- /dev/null +++ b/src/entities/logisticas/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID da logística + */ + idLogistica: number +} diff --git a/src/entities/logisticas/interfaces/find.interface.ts b/src/entities/logisticas/interfaces/find.interface.ts new file mode 100644 index 0000000..8856919 --- /dev/null +++ b/src/entities/logisticas/interfaces/find.interface.ts @@ -0,0 +1,30 @@ +import { ISituacao } from '../types/situacao.type' +import { ITipoIntegracao } from '../types/tipo-integracao.type' + +export interface IFindParams { + /** + * ID da logística + */ + idLogistica: number +} + +export interface IFindResponse { + data: { + id: number + descricao: string + tipoIntegracao: ITipoIntegracao + integracaoNativa: boolean + situacao: ISituacao + integracao: { id: number } + servicos: { + id: number + descricao: string + freteItem: number + estimativaEntrega: number + codigo: string + logistica: { id: number } + transportador: { id: number } + aliases: string[] + }[] + } +} diff --git a/src/entities/logisticas/interfaces/get.interface.ts b/src/entities/logisticas/interfaces/get.interface.ts new file mode 100644 index 0000000..2615208 --- /dev/null +++ b/src/entities/logisticas/interfaces/get.interface.ts @@ -0,0 +1,33 @@ +import { ISituacao } from '../types/situacao.type' +import { ITipoIntegracao } from '../types/tipo-integracao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Parâmetro para filtrar os registros através do tipo da logística + */ + tipoIntegracao?: ITipoIntegracao + /** + * Parâmetro para filtrar os registros através da situação + */ + situacao?: ISituacao +} + +export interface IGetResponse { + data: { + id: number + descricao: string + tipoIntegracao: ITipoIntegracao + integracaoNativa: boolean + situacao: ISituacao + integracao: { id: number } + servicos: { id: number }[] + }[] +} diff --git a/src/entities/logisticas/interfaces/update.interface.ts b/src/entities/logisticas/interfaces/update.interface.ts new file mode 100644 index 0000000..4433e40 --- /dev/null +++ b/src/entities/logisticas/interfaces/update.interface.ts @@ -0,0 +1,13 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IUpdateParams { + /** + * ID da logística + */ + idLogistica: number +} + +export interface IUpdateBody { + descricao: string + situacao: ISituacao +} diff --git a/src/entities/logisticas/types/situacao.type.ts b/src/entities/logisticas/types/situacao.type.ts new file mode 100644 index 0000000..6a92ee3 --- /dev/null +++ b/src/entities/logisticas/types/situacao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente à situação da logística. + * + * - `H`: Habilitado + * - `D`: Desabilitado + */ +export type ISituacao = 'H' | 'D' diff --git a/src/entities/logisticas/types/tipo-integracao.type.ts b/src/entities/logisticas/types/tipo-integracao.type.ts new file mode 100644 index 0000000..fdad649 --- /dev/null +++ b/src/entities/logisticas/types/tipo-integracao.type.ts @@ -0,0 +1,27 @@ +/** + * Tipagem referente ao tipo de integração da logística. + */ +export type ITipoIntegracao = + | 'AmazonDBA' + | 'B2WEntrega' + | 'B2WO2O' + | 'Cainiao' + | 'Correios' + | 'CorreiosLog' + | 'CustomLogistic' + | 'DafitiMilkrun' + | 'Envvias' + | 'Frenet' + | 'FreteDescomplicado' + | 'Intelipost' + | 'Jadlog' + | 'Jamef' + | 'Kangu' + | 'LogisticaShopee' + | 'Loggi' + | 'MagaluEntregas' + | 'Mandae' + | 'MelhorEnvio' + | 'MercadoEnvios' + | 'OlistFulfillment' + | 'TotalExpress' diff --git a/src/entities/logisticasEtiquetas/__tests__/get-response.ts b/src/entities/logisticasEtiquetas/__tests__/get-response.ts new file mode 100644 index 0000000..5e78783 --- /dev/null +++ b/src/entities/logisticasEtiquetas/__tests__/get-response.ts @@ -0,0 +1,14 @@ +export default { + data: [ + { + id: 6423813145, + link: 'https://bling-storage-dev.s3.sa-east-1.amazonaws.com', + observacao: + 'O formato de impressão selecionado difere do retornado pelo integrador ou pela configuração da logística.' + } + ] +} + +export const getRequest = { + idsVendas: [6423813145] +} diff --git a/src/entities/logisticasEtiquetas/__tests__/index.spec.ts b/src/entities/logisticasEtiquetas/__tests__/index.spec.ts new file mode 100644 index 0000000..cb3154c --- /dev/null +++ b/src/entities/logisticasEtiquetas/__tests__/index.spec.ts @@ -0,0 +1,38 @@ +import { Chance } from 'chance' +import { LogisticasEtiquetas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import getResponse from './get-response' + +const chance = Chance() + +describe('Logísticas - Etiquetas entity', () => { + let repository: InMemoryBlingRepository + let entity: LogisticasEtiquetas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new LogisticasEtiquetas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + const idsVendas: number[] = [] + for (let i = 0; i < chance.natural({ min: 1, max: 5 }) + 1; i++) { + idsVendas.push(chance.natural()) + } + + const response = await entity.get({ idsVendas }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/etiquetas', + body: { idsVendas }, + params: { formato: undefined } + }) + expect(response).toBe(getResponse) + }) +}) diff --git a/src/entities/logisticasEtiquetas/index.ts b/src/entities/logisticasEtiquetas/index.ts new file mode 100644 index 0000000..7f1e0f2 --- /dev/null +++ b/src/entities/logisticasEtiquetas/index.ts @@ -0,0 +1,28 @@ +import { Entity } from '../@shared/entity' +import { IGetBody, IGetParams, IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com logísticas - etiquetas. + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Etiquetas + */ +export class LogisticasEtiquetas extends Entity { + /** + * Obtém etiquetas das vendas. + * + * @param {IGetParams & IGetBody} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Etiquetas/get_logisticas_etiquetas + */ + public async get(params: IGetParams & IGetBody): Promise { + const { formato, ...body } = params + return await this.repository.index({ + endpoint: 'logisticas/etiquetas', + body, + params: { formato } + }) + } +} diff --git a/src/entities/logisticasEtiquetas/interfaces/get.interface.ts b/src/entities/logisticasEtiquetas/interfaces/get.interface.ts new file mode 100644 index 0000000..900c9e7 --- /dev/null +++ b/src/entities/logisticasEtiquetas/interfaces/get.interface.ts @@ -0,0 +1,17 @@ +import { IFormato } from '../types/formato.type' + +export interface IGetParams { + formato?: IFormato +} + +export interface IGetBody { + idsVendas: number[] +} + +export interface IGetResponse { + data: { + id: number + link: string + observacao: string + }[] +} diff --git a/src/entities/logisticasEtiquetas/types/formato.type.ts b/src/entities/logisticasEtiquetas/types/formato.type.ts new file mode 100644 index 0000000..d20f5e1 --- /dev/null +++ b/src/entities/logisticasEtiquetas/types/formato.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao formato da etiqueta. + * + * - `PDF`: Formato PDF + * - `ZPL`: Formato ZPL + */ +export type IFormato = 'PDF' | 'ZPL' diff --git a/src/entities/logisticasObjetos/__tests__/create-response.ts b/src/entities/logisticasObjetos/__tests__/create-response.ts new file mode 100644 index 0000000..685db09 --- /dev/null +++ b/src/entities/logisticasObjetos/__tests__/create-response.ts @@ -0,0 +1,42 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + pedidoVenda: { + id: 12345678 + }, + notaFiscal: { + id: 12345678 + }, + servico: { + id: 12345678 + }, + rastreamento: { + codigo: 'EC272330554BR', + descricao: 'Criado', + situacao: 1 as const, + origem: 'São Paulo, SP', + destino: 'São Paulo, SP', + ultimaAlteracao: '2020-11-11 16:40:33', + url: 'https://www.rastreamento.exemplo.com.br/EC272330554BR' + }, + dimensoes: { + peso: 1.5, + altura: 1.5, + largura: 1.5, + comprimento: 1.5, + diametro: 1.5 + }, + embalagem: { + id: 12345678 + }, + dataSaida: '2022-12-01', + prazoEntregaPrevisto: 15, + fretePrevisto: 59.9, + valorDeclarado: 55.9, + avisoRecebimento: false, + maoPropria: false +} diff --git a/src/entities/logisticasObjetos/__tests__/delete-response.ts b/src/entities/logisticasObjetos/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/logisticasObjetos/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/logisticasObjetos/__tests__/find-response.ts b/src/entities/logisticasObjetos/__tests__/find-response.ts new file mode 100644 index 0000000..8a650fa --- /dev/null +++ b/src/entities/logisticasObjetos/__tests__/find-response.ts @@ -0,0 +1,38 @@ +export default { + data: { + pedidoVenda: { + id: 12345678 + }, + notaFiscal: { + id: 12345678 + }, + servico: { + id: 12345678 + }, + rastreamento: { + codigo: 'EC272330554BR', + descricao: 'Criado', + situacao: 1, + origem: 'São Paulo, SP', + destino: 'São Paulo, SP', + ultimaAlteracao: '2020-11-11 16:40:33', + url: 'https://www.rastreamento.exemplo.com.br/EC272330554BR' + }, + dimensao: { + peso: 1.5, + altura: 1.5, + largura: 1.5, + comprimento: 1.5, + diametro: 1.5 + }, + embalagem: { + id: 12345678 + }, + dataSaida: '2022-12-01', + prazoEntregaPrevisto: 15, + fretePrevisto: 59.9, + valorDeclarado: 55.9, + avisoRecebimento: false, + maoPropria: false + } +} diff --git a/src/entities/logisticasObjetos/__tests__/index.spec.ts b/src/entities/logisticasObjetos/__tests__/index.spec.ts new file mode 100644 index 0000000..10a6c2d --- /dev/null +++ b/src/entities/logisticasObjetos/__tests__/index.spec.ts @@ -0,0 +1,82 @@ +import { Chance } from 'chance' +import { LogisticasObjetos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Logísticas - objetos entity', () => { + let repository: InMemoryBlingRepository + let entity: LogisticasObjetos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new LogisticasObjetos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idObjeto = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idObjeto }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/objetos', + id: String(idObjeto) + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idObjeto = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idObjeto }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/objetos', + id: String(idObjeto) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/objetos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idObjeto = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idObjeto, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/objetos', + id: String(idObjeto), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/logisticasObjetos/__tests__/update-response.ts b/src/entities/logisticasObjetos/__tests__/update-response.ts new file mode 100644 index 0000000..a507c46 --- /dev/null +++ b/src/entities/logisticasObjetos/__tests__/update-response.ts @@ -0,0 +1,33 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + rastreamento: { + codigo: 'EC272330554BR', + descricao: 'Criado', + situacao: 1 as const, + origem: 'São Paulo, SP', + destino: 'São Paulo, SP', + ultimaAlteracao: '2020-11-11 16:40:33', + url: 'https://www.rastreamento.exemplo.com.br/EC272330554BR' + }, + dimensoes: { + peso: 1.5, + altura: 1.5, + largura: 1.5, + comprimento: 1.5, + diametro: 1.5 + }, + embalagem: { + id: 12345678 + }, + dataSaida: '2022-12-01', + prazoEntregaPrevisto: 15, + fretePrevisto: 59.9, + valorDeclarado: 55.9, + avisoRecebimento: false, + maoPropria: false +} diff --git a/src/entities/logisticasObjetos/index.ts b/src/entities/logisticasObjetos/index.ts new file mode 100644 index 0000000..922f2f4 --- /dev/null +++ b/src/entities/logisticasObjetos/index.ts @@ -0,0 +1,89 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com logísticas - objetos. + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Objetos + */ +export class LogisticasObjetos extends Entity { + /** + * Remove um objeto de logística personalizada. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Objetos/delete_logisticas_objetos__idObjeto_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'logisticas/objetos', + id: String(params.idObjeto) + }) + } + + /** + * Obtém um objeto de logística. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Objetos/get_logisticas_objetos__idObjeto_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'logisticas/objetos', + id: String(params.idObjeto) + }) + } + + /** + * Cria um objeto de logística. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Objetos/post_logisticas_objetos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'logisticas/objetos', + body + }) + } + + /** + * Altera um objeto de logística pelo ID. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Objetos/put_logisticas_objetos__idObjeto_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idObjeto, ...body } = params + + return await this.repository.replace({ + endpoint: 'logisticas/objetos', + id: String(idObjeto), + body + }) + } +} diff --git a/src/entities/logisticasObjetos/interfaces/create.interface.ts b/src/entities/logisticasObjetos/interfaces/create.interface.ts new file mode 100644 index 0000000..26641c4 --- /dev/null +++ b/src/entities/logisticasObjetos/interfaces/create.interface.ts @@ -0,0 +1,34 @@ +import { ISituacao } from '../types/situacao.type' + +export interface ICreateBody { + pedidoVenda: { id: number } + notaFiscal: { id: number } + servico: { id: number } + rastreamento: { + codigo: string + descricao: string + situacao: ISituacao + origem: string + destino: string + ultimaAlteracao: string + url: string + } + dimensoes?: { + peso: number + altura: number + largura: number + comprimento: number + diametro: number + } + embalagem: { id: number } + dataSaida: string + prazoEntregaPrevisto: number + fretePrevisto: number + valorDeclarado: number + avisoRecebimento: boolean + maoPropria: boolean +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/logisticasObjetos/interfaces/delete.interface.ts b/src/entities/logisticasObjetos/interfaces/delete.interface.ts new file mode 100644 index 0000000..7b688a6 --- /dev/null +++ b/src/entities/logisticasObjetos/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do objeto logístico + */ + idObjeto: number +} diff --git a/src/entities/logisticasObjetos/interfaces/find.interface.ts b/src/entities/logisticasObjetos/interfaces/find.interface.ts new file mode 100644 index 0000000..93829cd --- /dev/null +++ b/src/entities/logisticasObjetos/interfaces/find.interface.ts @@ -0,0 +1,39 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IFindParams { + /** + * ID do objeto logístico + */ + idObjeto: number +} + +export interface IFindResponse { + data: { + pedidoVenda: { id: number } + notaFiscal: { id: number } + servico: { id: number } + rastreamento: { + codigo: string + descricao: string + situacao: ISituacao + origem: string + destino: string + ultimaAlteracao: string + url: string + } + dimensao: { + peso: number + altura: number + largura: number + comprimento: number + diametro: number + } + embalagem: { id: number } + dataSaida: '2022-12-01' + prazoEntregaPrevisto: number + fretePrevisto: number + valorDeclarado: number + avisoRecebimento: boolean + maoPropria: boolean + } +} diff --git a/src/entities/logisticasObjetos/interfaces/update.interface.ts b/src/entities/logisticasObjetos/interfaces/update.interface.ts new file mode 100644 index 0000000..bc22ac4 --- /dev/null +++ b/src/entities/logisticasObjetos/interfaces/update.interface.ts @@ -0,0 +1,38 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IUpdateParams { + /** + * ID do objeto logístico + */ + idObjeto: number +} + +export interface IUpdateBody { + rastreamento: { + codigo: string + descricao: string + situacao: ISituacao + origem: string + destino: string + ultimaAlteracao: string + url: string + } + dimensoes?: { + peso: number + altura: number + largura: number + comprimento: number + diametro: number + } + embalagem: { id: number } + dataSaida: string + prazoEntregaPrevisto: number + fretePrevisto: number + valorDeclarado: number + avisoRecebimento: boolean + maoPropria: boolean +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/logisticasObjetos/types/situacao.type.ts b/src/entities/logisticasObjetos/types/situacao.type.ts new file mode 100644 index 0000000..948e488 --- /dev/null +++ b/src/entities/logisticasObjetos/types/situacao.type.ts @@ -0,0 +1,15 @@ +/** + * Tipagem referente à situação do objeto de logística. + * + * - `0`: Postado + * - `1`: Em andamento + * - `2`: Não entregue + * - `3`: Entregue + * - `4`: Aguardando retirada + * - `5`: Em aberto + * - `6`: Vinculado + * - `7`: Atrasado + * - `8`: Não postado + * - `9`: Entrega suspensa + */ +export type ISituacao = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 diff --git a/src/entities/logisticasServicos/__tests__/change-situation-response.ts b/src/entities/logisticasServicos/__tests__/change-situation-response.ts new file mode 100644 index 0000000..a4ef8c7 --- /dev/null +++ b/src/entities/logisticasServicos/__tests__/change-situation-response.ts @@ -0,0 +1,5 @@ +export default null + +export const changeSituationRequest = { + ativo: true +} diff --git a/src/entities/logisticasServicos/__tests__/create-response.ts b/src/entities/logisticasServicos/__tests__/create-response.ts new file mode 100644 index 0000000..f3e28a7 --- /dev/null +++ b/src/entities/logisticasServicos/__tests__/create-response.ts @@ -0,0 +1,27 @@ +export default { + data: [ + { + id: 12345678 + } + ] +} + +export const createRequestBody = { + logistica: { + id: 12345678 + }, + servicos: [ + { + descricao: 'CARTA REG AR CONV B1 MFD', + codigo: 'ABC1234', + aliases: ['ALIAS1'], + ativo: true, + freteItem: 12.45, + estimativaEntrega: 2, + idCodigoServico: '13112', + transportador: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/logisticasServicos/__tests__/find-response.ts b/src/entities/logisticasServicos/__tests__/find-response.ts new file mode 100644 index 0000000..0574d64 --- /dev/null +++ b/src/entities/logisticasServicos/__tests__/find-response.ts @@ -0,0 +1,18 @@ +export default { + data: { + id: 123445, + descricao: 'CARTA REG AR CONV B1 MFD', + codigo: 'ABC1234', + aliases: ['ALIAS1'], + ativo: true, + freteItem: 12.45, + estimativaEntrega: 2, + idCodigoServico: '13112', + logistica: { + id: 12345678 + }, + transportador: { + id: 12345678 + } + } +} diff --git a/src/entities/logisticasServicos/__tests__/get-response.ts b/src/entities/logisticasServicos/__tests__/get-response.ts new file mode 100644 index 0000000..16f1cab --- /dev/null +++ b/src/entities/logisticasServicos/__tests__/get-response.ts @@ -0,0 +1,20 @@ +export default { + data: [ + { + id: 123445, + descricao: 'CARTA REG AR CONV B1 MFD', + codigo: 'ABC1234', + aliases: ['ALIAS1'], + ativo: true, + freteItem: 12.45, + estimativaEntrega: 2, + idCodigoServico: '13112', + logistica: { + id: 12345678 + }, + transportador: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/logisticasServicos/__tests__/index.spec.ts b/src/entities/logisticasServicos/__tests__/index.spec.ts new file mode 100644 index 0000000..33b05e6 --- /dev/null +++ b/src/entities/logisticasServicos/__tests__/index.spec.ts @@ -0,0 +1,102 @@ +import { Chance } from 'chance' +import { LogisticasServicos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import { changeSituationRequest } from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Logísticas - servicos entity', () => { + let repository: InMemoryBlingRepository + let entity: LogisticasServicos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new LogisticasServicos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/servicos', + params: { + limite: undefined, + pagina: undefined, + tipoIntegracao: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idLogisticaServico = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idLogisticaServico }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/servicos', + id: String(idLogisticaServico) + }) + expect(response).toBe(findResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idLogisticaServico = chance.natural() + const ativo = chance.bool() + repository.setResponse(changeSituationRequest) + + const response = await entity.changeSituation({ idLogisticaServico, ativo }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas', + id: `${String(idLogisticaServico)}/situacoes`, + body: { ativo } + }) + expect(response).toBe(changeSituationRequest) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/servicos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idLogisticaServico = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idLogisticaServico, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'logisticas/servicos', + id: String(idLogisticaServico), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/logisticasServicos/__tests__/update-response.ts b/src/entities/logisticasServicos/__tests__/update-response.ts new file mode 100644 index 0000000..4f9ac75 --- /dev/null +++ b/src/entities/logisticasServicos/__tests__/update-response.ts @@ -0,0 +1,18 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + descricao: 'CARTA REG AR CONV B1 MFD', + codigo: 'ABC1234', + aliases: ['ALIAS1'], + ativo: true, + freteItem: 12.45, + estimativaEntrega: 2, + idCodigoServico: '13112', + transportador: { + id: 12345678 + } +} diff --git a/src/entities/logisticasServicos/index.ts b/src/entities/logisticasServicos/index.ts new file mode 100644 index 0000000..09dd971 --- /dev/null +++ b/src/entities/logisticasServicos/index.ts @@ -0,0 +1,118 @@ +import { Entity } from '../@shared/entity' +import { + IChangeSituationBody, + IChangeSituationParams +} from './interfaces/change-situation.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com logísticas - serviços. + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Servi%C3%A7os + */ +export class LogisticasServicos extends Entity { + /** + * Obtém serviços de logísticas. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Servi%C3%A7os/get_logisticas_servicos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'logisticas/servicos', + params: { + pagina: params?.pagina, + limite: params?.limite, + tipoIntegracao: params?.tipoIntegracao + } + }) + } + + /** + * Obtém um servico de logística. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Servi%C3%A7os/get_logisticas_servicos__idLogisticaServico_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'logisticas/servicos', + id: String(params.idLogisticaServico) + }) + } + + /** + * Desativa ou ativa um serviço de uma logística. + * + * @param {IChangeSituationParams & IChangeSituationBody} params Dados da atualização. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Servi%C3%A7os/patch_logisticas__idLogisticaServico__situacoes + */ + public async changeSituation( + params: IChangeSituationParams & IChangeSituationBody + ): Promise { + const { idLogisticaServico, ...body } = params + return await this.repository.update({ + endpoint: 'logisticas', + id: `${String(idLogisticaServico)}/situacoes`, + body + }) + } + + /** + * Cria um serviço de logística. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Servi%C3%A7os/post_logisticas_servicos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'logisticas/servicos', + body + }) + } + + /** + * Altera um serviço de logística pelo ID. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Log%C3%ADsticas%20-%20Servi%C3%A7os/put_logisticas_servicos__idLogisticaServico_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idLogisticaServico, ...body } = params + + return await this.repository.replace({ + endpoint: 'logisticas/servicos', + id: String(idLogisticaServico), + body + }) + } +} diff --git a/src/entities/logisticasServicos/interfaces/change-situation.interface.ts b/src/entities/logisticasServicos/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..db0bae9 --- /dev/null +++ b/src/entities/logisticasServicos/interfaces/change-situation.interface.ts @@ -0,0 +1,10 @@ +export interface IChangeSituationParams { + /** + * ID do serviço + */ + idLogisticaServico: number +} + +export interface IChangeSituationBody { + ativo: boolean +} diff --git a/src/entities/logisticasServicos/interfaces/create.interface.ts b/src/entities/logisticasServicos/interfaces/create.interface.ts new file mode 100644 index 0000000..7c44f6d --- /dev/null +++ b/src/entities/logisticasServicos/interfaces/create.interface.ts @@ -0,0 +1,17 @@ +export interface ICreateBody { + logistica: { id: number } + servicos: { + descricao: string + codigo: string + aliases: string[] + ativo?: boolean + freteItem: number + estimativaEntrega: number + idCodigoServico?: string + transportador: { id: number } + }[] +} + +export interface ICreateResponse { + data: { id: number }[] +} diff --git a/src/entities/logisticasServicos/interfaces/find.interface.ts b/src/entities/logisticasServicos/interfaces/find.interface.ts new file mode 100644 index 0000000..994589c --- /dev/null +++ b/src/entities/logisticasServicos/interfaces/find.interface.ts @@ -0,0 +1,21 @@ +export interface IFindParams { + /** + * ID do serviço + */ + idLogisticaServico: number +} + +export interface IFindResponse { + data: { + id?: number + descricao: string + codigo: string + aliases: string[] + ativo?: boolean + freteItem: number + estimativaEntrega: number + idCodigoServico?: string + logistica: { id: number } + transportador: { id: number } + } +} diff --git a/src/entities/logisticasServicos/interfaces/get.interface.ts b/src/entities/logisticasServicos/interfaces/get.interface.ts new file mode 100644 index 0000000..cb3584f --- /dev/null +++ b/src/entities/logisticasServicos/interfaces/get.interface.ts @@ -0,0 +1,31 @@ +import { ITipoIntegracao } from '../types/tipo-integracao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Parâmetro para filtrar os registros através do tipo da logística. + */ + tipoIntegracao?: ITipoIntegracao +} + +export interface IGetResponse { + data: { + id?: number + descricao: string + codigo: string + aliases: string[] + ativo?: boolean + freteItem: number + estimativaEntrega: number + idCodigoServico: string + logistica: { id: number } + transportador: { id: number } + }[] +} diff --git a/src/entities/logisticasServicos/interfaces/update.interface.ts b/src/entities/logisticasServicos/interfaces/update.interface.ts new file mode 100644 index 0000000..39786e3 --- /dev/null +++ b/src/entities/logisticasServicos/interfaces/update.interface.ts @@ -0,0 +1,21 @@ +export interface IUpdateParams { + /** + * ID do serviço + */ + idLogisticaServico: number +} + +export interface IUpdateBody { + descricao: string + codigo: string + aliases: string[] + ativo?: boolean + freteItem: number + estimativaEntrega: number + idCodigoServico?: string + transportador?: { id: number } +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/logisticasServicos/types/tipo-integracao.type.ts b/src/entities/logisticasServicos/types/tipo-integracao.type.ts new file mode 100644 index 0000000..52f66dd --- /dev/null +++ b/src/entities/logisticasServicos/types/tipo-integracao.type.ts @@ -0,0 +1,27 @@ +/** + * Tipagem referente ao tipo de integração para serviços de logística. + */ +export type ITipoIntegracao = + | 'AmazonDBA' + | 'B2WEntrega' + | 'B2WO2O' + | 'Cainiao' + | 'Correios' + | 'CorreiosLog' + | 'CustomLogistic' + | 'DafitiMilkrun' + | 'Envvias' + | 'Frenet' + | 'FreteDescomplicado' + | 'Intelipost' + | 'Jadlog' + | 'Jamef' + | 'Kangu' + | 'LogisticaShopee' + | 'Loggi' + | 'MagaluEntregas' + | 'Mandae' + | 'MelhorEnvio' + | 'MercadoEnvios' + | 'OlistFulfillment' + | 'TotalExpress' diff --git a/src/entities/logistics.ts b/src/entities/logistics.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/entities/naturezasDeOperacoes/__tests__/calculate-item-tax-response.ts b/src/entities/naturezasDeOperacoes/__tests__/calculate-item-tax-response.ts new file mode 100644 index 0000000..b963690 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/__tests__/calculate-item-tax-response.ts @@ -0,0 +1,237 @@ +export default { + data: { + faturada: false, + observacoes: 'Total aproximado de tributos: R$ [APROX_TRIB]. Fonte IBPT.', + pisCofinsPresumido: false, + somaImpostosTotal: false, + somaIcmsTotal: false, + aliquotaFunrural: 0, + descontaFunrural: false, + consumidorFinal: false, + retImpostoRetido: false, + retAliquotaIr: 0, + retValorIr: 0, + retAliquotaCsll: 0, + retValorCsll: 0, + descontoCondicional: false, + baseComissao: 0, + icms: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + porcentagemFcpDifal: 0, + valorFcpDifal: 0, + valorFcpEfetivo: 0, + porcentagemFcp: 0, + porcentagemFcpUfDestino: 0, + modalidadeBaseCalculo: 0, + valorPauta: 0, + aliquotaPresumido: 0, + porcentagemBaseCalculoUfDestino: 0, + porcentagemIcmsUfDestino: 0, + tipoPartilha: 0, + valorIcmsDesonerado: 0, + motivoDesoneracaoIcms: 0 as const, + baseDiferimento: 0, + valorBaseDiferimento: 0, + valorPresumido: 0, + aliquotaPosicao: 0 + }, + valorPmc: 0, + aliquotaValorAproxImpostos: 0, + informacoesAdicionaisFisco: '', + incluirFreteIpi: false, + simples: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + baseDiferimento: 0, + modalidadeBaseCalculo: 0, + valorPauta: 0, + valorImpostoSt: 0, + valorBaseCalculoSt: 0, + baseCalculoSt: 0, + percentualAdicionadoSt: 0, + modalidadeBaseCalculoSt: 0, + valorPautaSt: 0, + aliquotaSt: 0, + aliquotaStRetido: 0, + baseStRetido: 0, + valorUnitarioBaseCstRetencao: 0, + valorUnitarioIcmsStRetencao: 0, + valorUnitarioIcmsSubstituto: 0 + }, + ipi: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + valorIpiFixoUnitario: 0, + classeEnquadramentoIpi: '', + codigoEnquadramentoIpi: 0, + codigoSelo: '', + codigoExTipi: 0 + }, + issqn: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + codigoListaServicos: '01.04', + descontarIssTotalNota: false, + reterIss: false + }, + pis: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + valorPisFixo: 0 + }, + cofins: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + valorCofinsFixo: 0 + }, + icmsSt: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '', + percentualAdicionado: 0, + modalidadeBaseCalculo: 0 as const, + valorPauta: 0 + }, + pisSt: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '' + }, + cofinsSt: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '' + }, + ii: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '' + }, + codigoBeneficioFiscal: '', + porcentagemFcp: 0, + cfop: 0, + simplesSt: { + regraOperacao: { + id: 12345678 + }, + tributacao: 1 as const, + cst: '49', + aliquota: 0, + base: 0, + valorBaseCalculo: 0, + valorImposto: 0, + observacoes: '', + informacoesAdicionaisFisco: '' + } + } +} + +export const calculateItemTaxRequestBody = { + tipoNota: 1 as const, + uf: 'RS' as const, + municipio: { + id: 4302105 + }, + calcularImpostos: true, + crt: 1 as const, + loja: { + id: 12345678, + unidadeNegocio: { + id: 12345678 + } + }, + produto: { + id: 12345678, + valorUnitario: 0, + cupomFiscal: 0, + origem: 0 as const, + quantidade: 0 + } +} diff --git a/src/entities/naturezasDeOperacoes/__tests__/get-response.ts b/src/entities/naturezasDeOperacoes/__tests__/get-response.ts new file mode 100644 index 0000000..0926f91 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/__tests__/get-response.ts @@ -0,0 +1,10 @@ +export default { + data: [ + { + id: 12345678, + situacao: 1, + padrao: 1, + descricao: 'Compra de Mercadoria' + } + ] +} diff --git a/src/entities/naturezasDeOperacoes/__tests__/index.spec.ts b/src/entities/naturezasDeOperacoes/__tests__/index.spec.ts new file mode 100644 index 0000000..52bdeab --- /dev/null +++ b/src/entities/naturezasDeOperacoes/__tests__/index.spec.ts @@ -0,0 +1,58 @@ +import { Chance } from 'chance' +import { NaturezasDeOperacoes } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import calculateItemTaxResponse, { + calculateItemTaxRequestBody +} from './calculate-item-tax-response' +import getResponse from './get-response' + +const chance = Chance() + +describe('Naturezas de Operação entity', () => { + let repository: InMemoryBlingRepository + let entity: NaturezasDeOperacoes + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new NaturezasDeOperacoes(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'naturezas-operacoes', + params: { + limite: undefined, + pagina: undefined, + situacao: undefined, + descricao: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should calculate item tax successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNaturezaOperacao = chance.natural() + repository.setResponse(calculateItemTaxResponse) + + const response = await entity.calculateItemTax({ + idNaturezaOperacao, + ...calculateItemTaxRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `naturezas-operacoes/${idNaturezaOperacao}/calcular-imposto-item`, + body: calculateItemTaxRequestBody + }) + expect(response).toBe(calculateItemTaxResponse) + }) +}) diff --git a/src/entities/naturezasDeOperacoes/index.ts b/src/entities/naturezasDeOperacoes/index.ts new file mode 100644 index 0000000..9db4e40 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/index.ts @@ -0,0 +1,56 @@ +import { Entity } from '../@shared/entity' +import { + ICalculateItemTaxBody, + ICalculateItemTaxParams, + ICalculateItemTaxResponse +} from './interfaces/calculate-item-tax.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com naturezas de operações. + * + * @see https://developer.bling.com.br/referencia#/Naturezas%20de%20Opera%C3%A7%C3%B5es + */ +export class NaturezasDeOperacoes extends Entity { + /** + * Obtém naturezas de operações. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Naturezas%20de%20Opera%C3%A7%C3%B5es/get_naturezas_operacoes + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'naturezas-operacoes', + params: { + pagina: params?.pagina, + limite: params?.limite, + situacao: params?.situacao, + descricao: params?.descricao + } + }) + } + + /** + * Calcula os impostos de um item. + * + * @param {ICalculateItemTaxParams & ICalculateItemTaxBody} params O conteúdo para o cálculo. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Naturezas%20de%20Opera%C3%A7%C3%B5es/post_naturezas_operacoes__idNaturezaOperacao__calcular_imposto_item + */ + public async calculateItemTax( + params: ICalculateItemTaxParams & ICalculateItemTaxBody + ): Promise { + const { idNaturezaOperacao, ...body } = params + return await this.repository.store({ + endpoint: `naturezas-operacoes/${idNaturezaOperacao}/calcular-imposto-item`, + body + }) + } +} diff --git a/src/entities/naturezasDeOperacoes/interfaces/calculate-item-tax.interface.ts b/src/entities/naturezasDeOperacoes/interfaces/calculate-item-tax.interface.ts new file mode 100644 index 0000000..594e662 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/interfaces/calculate-item-tax.interface.ts @@ -0,0 +1,226 @@ +import ICRT from 'src/entities/@shared/types/crt.type' +import IOrigem from 'src/entities/@shared/types/origem.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IModalidadeBaseCalculoICMSST } from '../types/modalidade-base-calculo-icms-st.type' +import { IMotivoDesoneracaoICMS } from '../types/motivo-desoneracao-icms.type' +import { ITipoNota } from '../types/tipo-nota.type' +import { ITributacao } from '../types/tributacao.type' + +export interface ICalculateItemTaxParams { + /** + * ID da natureza de operação + */ + idNaturezaOperacao: number +} + +export interface ICalculateItemTaxBody { + tipoNota: ITipoNota + uf: IUF + municipio: { id: number } + calcularImpostos?: boolean + crt?: ICRT + loja: { + id: number + unidadeNegocio?: { id: number } + } + produto: { + id: number + valorUnitario: number + cupomFiscal: number + origem: IOrigem + quantidade: number + } +} + +export interface ICalculateItemTaxResponse { + data: { + faturada?: boolean + observacoes?: string + pisCofinsPresumido?: boolean + somaImpostosTotal?: boolean + somaIcmsTotal?: boolean + aliquotaFunrural?: number + descontaFunrural?: boolean + consumidorFinal?: boolean + retImpostoRetido?: boolean + retAliquotaIr?: number + retValorIr?: number + retAliquotaCsll?: number + retValorCsll?: number + descontoCondicional?: boolean + baseComissao?: number + icms?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + porcentagemFcpDifal?: number + valorFcpDifal?: number + valorFcpEfetivo?: number + porcentagemFcp?: number + porcentagemFcpUfDestino?: number + modalidadeBaseCalculo?: number + valorPauta?: number + aliquotaPresumido?: number + porcentagemBaseCalculoUfDestino?: number + porcentagemIcmsUfDestino?: number + tipoPartilha?: number + valorIcmsDesonerado?: number + motivoDesoneracaoIcms?: IMotivoDesoneracaoICMS + baseDiferimento?: number + valorBaseDiferimento?: number + valorPresumido?: number + aliquotaPosicao?: number + } + valorPmc?: number + aliquotaValorAproxImpostos?: number + informacoesAdicionaisFisco?: string + incluirFreteIpi?: boolean + simples?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + baseDiferimento?: number + modalidadeBaseCalculo?: number + valorPauta?: number + valorImpostoSt?: number + valorBaseCalculoSt?: number + baseCalculoSt?: number + percentualAdicionadoSt?: number + modalidadeBaseCalculoSt?: number + valorPautaSt?: number + aliquotaSt?: number + aliquotaStRetido?: number + baseStRetido?: number + valorUnitarioBaseCstRetencao?: number + valorUnitarioIcmsStRetencao?: number + valorUnitarioIcmsSubstituto?: number + } + ipi?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + valorIpiFixoUnitario?: number + classeEnquadramentoIpi?: string + codigoEnquadramentoIpi?: number + codigoSelo?: string + codigoExTipi?: number + } + issqn?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + codigoListaServicos?: string + descontarIssTotalNota?: boolean + reterIss?: boolean + } + pis?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + valorPisFixo?: number + } + cofins?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + valorCofinsFixo?: number + } + icmsSt?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + percentualAdicionado?: number + modalidadeBaseCalculo?: IModalidadeBaseCalculoICMSST + valorPauta?: number + } + pisSt?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + } + cofinsSt?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + } + ii?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + } + codigoBeneficioFiscal?: string + porcentagemFcp?: number + cfop?: number + simplesSt?: { + regraOperacao?: { id: number } + tributacao?: ITributacao + cst?: string + aliquota?: number + base?: number + valorBaseCalculo?: number + valorImposto?: number + observacoes?: string + informacoesAdicionaisFisco?: string + } + } +} diff --git a/src/entities/naturezasDeOperacoes/interfaces/get.interface.ts b/src/entities/naturezasDeOperacoes/interfaces/get.interface.ts new file mode 100644 index 0000000..ad92d49 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/interfaces/get.interface.ts @@ -0,0 +1,30 @@ +import ISituacao from 'src/entities/@shared/types/situacao.type' +import { IPadrao } from '../types/padrao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * + */ + situacao?: ISituacao + /** + * Descrição da natureza de operação + */ + descricao?: string +} + +export interface IGetResponse { + data: { + id?: number + situacao?: ISituacao + padrao?: IPadrao + descricao?: string + }[] +} diff --git a/src/entities/naturezasDeOperacoes/types/modalidade-base-calculo-icms-st.type.ts b/src/entities/naturezasDeOperacoes/types/modalidade-base-calculo-icms-st.type.ts new file mode 100644 index 0000000..34cff68 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/types/modalidade-base-calculo-icms-st.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente à modalidade de base de cálculo para ICMS ST. + * + * _ `0`: Margem Valor Agregado (%) + * _ `1`: Pauta (valor), + * _ `2`: Preço Tabelado Máximo (valor) + * _ `3`: Valor da operação + */ +export type IModalidadeBaseCalculoICMSST = 0 | 1 | 2 | 3 diff --git a/src/entities/naturezasDeOperacoes/types/motivo-desoneracao-icms.type.ts b/src/entities/naturezasDeOperacoes/types/motivo-desoneracao-icms.type.ts new file mode 100644 index 0000000..8ac1059 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/types/motivo-desoneracao-icms.type.ts @@ -0,0 +1,30 @@ +/** + * Tipagem referente ao motivo de desoneração do ICMS. + * + * - `0`: Nenhum + * - `1`: Táxi + * - `3`: Produtor Agropecuário + * - `4`: Frotista/Locadora + * - `5`: Diplomático/Consular + * - `6`: Utilitários e Motocicletas da Amazônia Ocidental e Áreas de Livre + * Comércio (Resolução 714/88 e 790/94 – CONTRAN e suas alterações) + * - `7`: SUFRAMA + * - `8`: Venda a Órgão Público + * - `9`: Outros + * - `10`: Deficiente Condutor + * - `11`: Deficiente Não Condutor + * - `90`: Solicitado pelo Fisco + */ +export type IMotivoDesoneracaoICMS = + | 0 + | 1 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 90 diff --git a/src/entities/naturezasDeOperacoes/types/padrao.type.ts b/src/entities/naturezasDeOperacoes/types/padrao.type.ts new file mode 100644 index 0000000..f36b1f4 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/types/padrao.type.ts @@ -0,0 +1,15 @@ +/** + * Tipagem referente ao padrão da natureza de operação. + * + * - `0`: Sem padrão + * - `1`: Padrão venda + * - `2`: Padrão compra + * - `3`: Padrão venda física + * - `4`: Padrão venda jurídica + * - `5`: Padrão compra física + * - `6`: Padrão compra jurídica + * - `7`: Padrão venda cupom + * - `8`: Padrão devolução (entrada) + * - `9`: Padrão devolução (saída) + */ +export type IPadrao = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 diff --git a/src/entities/naturezasDeOperacoes/types/tipo-nota.type.ts b/src/entities/naturezasDeOperacoes/types/tipo-nota.type.ts new file mode 100644 index 0000000..d7f4889 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/types/tipo-nota.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo da nota. + * + * - `0`: Entrada + * - `1`: Saída + */ +export type ITipoNota = 0 | 1 diff --git a/src/entities/naturezasDeOperacoes/types/tributacao.type.ts b/src/entities/naturezasDeOperacoes/types/tributacao.type.ts new file mode 100644 index 0000000..b463de3 --- /dev/null +++ b/src/entities/naturezasDeOperacoes/types/tributacao.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à tributação. + * + * - `1`: Tributado + * - `2`: Isento + * - `3`: Outra situação + */ +export type ITributacao = 1 | 2 | 3 diff --git a/src/entities/nfces.ts b/src/entities/nfces.ts deleted file mode 100644 index 21fcce0..0000000 --- a/src/entities/nfces.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' - -import IUFs from './types/uf' -import ITipoPessoa from './types/tipoPessoa' -import IContribuinte from './types/contribuinte' -import IUn from './types/un' -import IOrigem from './types/origem' -import ITipoFrete from './types/tipoFrete' - -type ISituacaoNumber = - | '0' - | '1' - | '2' - | '3' - | '4' - | '5' - | '6' - | '7' - | '8' - | '9' - | '10' - -type ISituacaoName = - | 'Todas' - | 'Pendente' - | 'Emitida' - | 'Cancelada' - | 'Enviada - Aguardando recibo' - | 'Rejeitada' - | 'Autorizada' - | 'Emitida DANFE' - | 'Registrada' - | 'Enviada - Aguardando protocolo' - | 'Denegada' - -export interface INfce { - tipo?: 'E' | 'S' - numero_loja?: string - nat_operacao?: string - data_operacao?: Date - nome: string - tipo_pessoa: ITipoPessoa - cpf_cnpj?: string - ie_rg?: string - contribuinte?: IContribuinte - endereco: string - numero: string - complemento?: string - bairro: string - cep: string - cidade: string - uf: IUFs - pais?: string - fone?: string - email: string - transporte?: { - transportadora?: string - cpf_cnpj?: string - ie_rg?: string - endereco?: string - cidade?: string - uf?: string - placa?: string - uf_veiculo?: string - marca?: string - tipo_frete?: ITipoFrete - qtde_volumes?: number - especie?: string - numero?: string - peso_bruto?: number - peso_liquido?: number - servico_correios?: string - dados_etiqueta?: { - nome?: string - endereco?: string - numero?: string - complemento?: string - municipio?: string - uf?: string - cep?: string - bairro?: string - } - } - itens: { - item: { - codigo?: string - descricao: string - un: IUn - qtde: number - vlr_unit: number - tipo: 'P' | 'S' - peso_bruto?: number - peso_liq?: number - class_fiscal?: string - cest?: string - cod_servico?: string - origem: IOrigem - } - }[] - parcelas?: { - parcela: { - dias?: number - data?: Date - vlr?: number - obs?: string - forma?: string - } - }[] - nf_produtor_rural_referenciada?: { - numero?: string - serie?: string - ano_mes_emissao?: string - } - vlr_frete?: number - vlr_seguro?: number - vlr_despesas?: number - vlr_desconto?: string - obs?: string - intermediador?: { - cnpj?: string - nomeUsuario?: string - } -} - -export interface INfceFilters { - dataEmissao?: string - situacao?: ISituacaoNumber -} - -export type INfceInfos = Record - -export interface INfceCreateResponse { - numero: string - codigos_rastreamento: { - codigo_rastreamento: string - } -} - -export interface INfceSendResponse { - situacao: ISituacaoNumber - mensagem: string - erro?: string - chaveAcesso: string - linkDanfe: string -} - -export interface INfceResponse { - serie: string - numero: string - numeroPedidoLoja: string - loja: number - tipo: 'E' | 'S' - situacao: ISituacaoName - contato: string - vendedor?: string - dataEmissao: string - valorNota: number - chaveAcesso: string - xml?: string - linkDanfe?: string - tipoIntegracao: string - codigosRastreamento: { - codigoRastreamento: string - } - transporte: { - transportadora: string - tipo_frete: ITipoFrete - servico_correios: string - volumes: { - idServico: number - servico: string - codigoRastreamento: string - dataSaida: string - prazoEntregaPrevisto: number - valorFretePrevisto: number - }[] - enderecoEntrega: { - nome: string - endereco: string - numero: string - complemento?: string - cidade: string - bairro: string - cep: string - uf: IUFs - } - } -} - -export default function Nfces (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'nfce', - pluralName: 'nfces' - } - - const send = async ( - numero: number | string, - serie: number | string, - options?: { - sendEmail?: boolean - raw?: boolean - } - ) => { - const createMethod = new Create, INfceSendResponse>({ - ...config, - endpoint: `${config.singularName}/${numero}/${serie}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await createMethod.create( - {}, - { raw: true }, - { - sendEmail: options.sendEmail - } - ) - } else { - return await createMethod.create( - {}, - { raw: false }, - { - sendEmail: options.sendEmail - } - ) - } - } else { - return await createMethod.create({}) - } - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy().findBy, - create: new Create().create, - send - }) -} diff --git a/src/entities/nfces/__tests__/create-response.ts b/src/entities/nfces/__tests__/create-response.ts new file mode 100644 index 0000000..e78705b --- /dev/null +++ b/src/entities/nfces/__tests__/create-response.ts @@ -0,0 +1,135 @@ +export default { + data: { + id: 12345678, + numero: '6541', + serie: '1', + contato: { + nome: 'Contato do Bling' + } + } +} + +export const createRequestBody = { + tipo: 1 as const, + numero: '6541', + dataOperacao: '2023-01-12 09:52:12', + contato: { + nome: 'Contato do Bling', + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + contribuinte: 1 as const, + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678, + numero: 'LOJA_8864' + }, + finalidade: 1 as const, + seguro: 1.15, + despesas: 5.08, + desconto: 10.12, + observacoes: 'Observação da nota.', + documentoReferenciado: { + modelo: '55' as const, + data: '2023-01-12', + numero: '123', + serie: '1', + contadorOrdemOperacao: '1', + chaveAcesso: '62634519764512837946527549134679858182373412' + }, + itens: [ + { + codigo: 'BLG-5', + descricao: 'Produto do Bling', + unidade: 'UN', + quantidade: 1, + valor: 4.9, + tipo: 'P' as const, + pesoBruto: 0.5, + pesoLiquido: 0.5, + numeroPedidoCompra: '235', + classificacaoFiscal: '9999.99.99', + cest: '99.999.99', + codigoServico: '99.99', + origem: 0 as const, + informacoesAdicionais: 'Descrição do item' + } + ], + parcelas: [ + { + data: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + veiculo: { + placa: 'LDO-2373', + uf: 'RS' as const, + marca: 'Volvo' + }, + transportador: { + nome: 'Transportador', + numeroDocumento: '30188025000121', + ie: '949895756023', + endereco: { + endereco: 'Olavo Bilac', + municipio: 'Bento Gonçalves', + uf: 'RS' as const + } + }, + volume: { + quantidade: 5, + especie: 'Volumes', + numero: '1', + pesoBruto: 0.5, + pesoLiquido: 0.35 + }, + volumes: [ + { + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ], + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante' + } + }, + notaFiscalProdutorRuralReferenciada: { + numero: '125', + serie: '1', + data: '2023-01-12' + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } +} diff --git a/src/entities/nfces/__tests__/find-response.ts b/src/entities/nfces/__tests__/find-response.ts new file mode 100644 index 0000000..7a7f77a --- /dev/null +++ b/src/entities/nfces/__tests__/find-response.ts @@ -0,0 +1,36 @@ +export default { + data: { + id: 12345678, + tipo: 1 as const, + situacao: 1 as const, + numero: '6541', + dataEmissao: '2023-01-12 09:52:12', + dataOperacao: '2023-01-12 09:52:12', + contato: { + id: 12345678, + nome: 'Contato do Bling', + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678 + }, + serie: '1' + } +} diff --git a/src/entities/nfces/__tests__/get-response.ts b/src/entities/nfces/__tests__/get-response.ts new file mode 100644 index 0000000..edeb38e --- /dev/null +++ b/src/entities/nfces/__tests__/get-response.ts @@ -0,0 +1,37 @@ +export default { + data: [ + { + id: 12345678, + tipo: 1 as const, + situacao: 1 as const, + numero: '6541', + dataEmissao: '2023-01-12 09:52:12', + dataOperacao: '2023-01-12 09:52:12', + contato: { + id: 12345678, + nome: 'Contato do Bling', + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/nfces/__tests__/index.spec.ts b/src/entities/nfces/__tests__/index.spec.ts new file mode 100644 index 0000000..46c986e --- /dev/null +++ b/src/entities/nfces/__tests__/index.spec.ts @@ -0,0 +1,132 @@ +import { Chance } from 'chance' +import { Nfces } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import findResponse from './find-response' +import getResponse from './get-response' +import postAccountsResponse from './post-accounts-response' +import reverseAccountsResponse from './reverse-accounts-response' +import sendResponse from './send-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('NFC-es entity', () => { + let repository: InMemoryBlingRepository + let entity: Nfces + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Nfces(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfce', + params: { + limite: undefined, + pagina: undefined, + situacao: undefined, + dataEmissaoInicial: undefined, + dataEmissaoFinal: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idNotaFiscalConsumidor = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idNotaFiscalConsumidor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfce', + id: String(idNotaFiscalConsumidor) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfce', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should send successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaFiscalConsumidor = chance.natural() + repository.setResponse(sendResponse) + + const response = await entity.send({ idNotaFiscalConsumidor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfce/${idNotaFiscalConsumidor}/enviar`, + body: {} + }) + expect(response).toBe(sendResponse) + }) + + it('should post accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaFiscalConsumidor = chance.natural() + repository.setResponse(postAccountsResponse) + + const response = await entity.postAccounts({ idNotaFiscalConsumidor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfce/${idNotaFiscalConsumidor}/lancar-contas`, + body: {} + }) + expect(response).toBe(postAccountsResponse) + }) + + it('should reverse accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaFiscalConsumidor = chance.natural() + repository.setResponse(reverseAccountsResponse) + + const response = await entity.reverseAccounts({ idNotaFiscalConsumidor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfce/${idNotaFiscalConsumidor}/estornar-contas`, + body: {} + }) + expect(response).toBe(reverseAccountsResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idNotaFiscalConsumidor = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idNotaFiscalConsumidor, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfce', + id: String(idNotaFiscalConsumidor), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/nfces/__tests__/post-accounts-response.ts b/src/entities/nfces/__tests__/post-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/nfces/__tests__/post-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/nfces/__tests__/reverse-accounts-response.ts b/src/entities/nfces/__tests__/reverse-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/nfces/__tests__/reverse-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/nfces/__tests__/send-response.ts b/src/entities/nfces/__tests__/send-response.ts new file mode 100644 index 0000000..b8eed5a --- /dev/null +++ b/src/entities/nfces/__tests__/send-response.ts @@ -0,0 +1,5 @@ +export default { + data: { + xml: 'string' + } +} diff --git a/src/entities/nfces/__tests__/update-response.ts b/src/entities/nfces/__tests__/update-response.ts new file mode 100644 index 0000000..2da8797 --- /dev/null +++ b/src/entities/nfces/__tests__/update-response.ts @@ -0,0 +1,130 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + tipo: 1 as const, + numero: '6541', + dataOperacao: '2023-01-12 09:52:12', + contato: { + nome: 'Contato do Bling', + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + contribuinte: 1 as const, + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678, + numero: 'LOJA_8864' + }, + finalidade: 1 as const, + seguro: 1.15, + despesas: 5.08, + desconto: 10.12, + observacoes: 'Observação da nota.', + documentoReferenciado: { + modelo: '55' as const, + data: '2023-01-12', + numero: '123', + serie: '1', + contadorOrdemOperacao: '1', + chaveAcesso: '62634519764512837946527549134679858182373412' + }, + itens: [ + { + codigo: 'BLG-5', + descricao: 'Produto do Bling', + unidade: 'UN', + quantidade: 1, + valor: 4.9, + tipo: 'P' as const, + pesoBruto: 0.5, + pesoLiquido: 0.5, + numeroPedidoCompra: '235', + classificacaoFiscal: '9999.99.99', + cest: '99.999.99', + codigoServico: '99.99', + origem: 0 as const, + informacoesAdicionais: 'Descrição do item' + } + ], + parcelas: [ + { + data: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + veiculo: { + placa: 'LDO-2373', + uf: 'RS' as const, + marca: 'Volvo' + }, + transportador: { + nome: 'Transportador', + numeroDocumento: '30188025000121', + ie: '949895756023', + endereco: { + endereco: 'Olavo Bilac', + municipio: 'Bento Gonçalves', + uf: 'RS' as const + } + }, + volume: { + quantidade: 5, + especie: 'Volumes', + numero: '1', + pesoBruto: 0.5, + pesoLiquido: 0.35 + }, + volumes: [ + { + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ], + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante' + } + }, + notaFiscalProdutorRuralReferenciada: { + numero: '125', + serie: '1', + data: '2023-01-12' + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } +} diff --git a/src/entities/nfces/index.ts b/src/entities/nfces/index.ts new file mode 100644 index 0000000..2b29c1d --- /dev/null +++ b/src/entities/nfces/index.ts @@ -0,0 +1,153 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IPostAccountsParams } from './interfaces/post-accounts.interface' +import { IReverseAccountsParams } from './interfaces/reverse-accounts.interface' +import { ISendParams, ISendResponse } from './interfaces/send.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com notas fiscais de consumidor eletrônicas. + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas + */ +export class Nfces extends Entity { + /** + * Obtém notas fiscais de consumidor. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/get_nfce + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'nfce', + params: { + pagina: params?.pagina, + limite: params?.limite, + situacao: params?.situacao, + dataEmissaoInicial: this.prepareStringOrDateParam( + params?.dataEmissaoInicial + ), + dataEmissaoFinal: this.prepareStringOrDateParam( + params?.dataEmissaoFinal + ) + } + }) + } + + /** + * Obtém uma nota fiscal de consumidor. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/get_nfce__idNotaFiscalConsumidor_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'nfce', + id: String(params.idNotaFiscalConsumidor) + }) + } + + /** + * Cria uma nota fiscal de consumidor. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/post_nfce + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'nfce', + body + }) + } + + /** + * Envia uma nota de consumidor. + * + * @param {ISendParams} params O conteúdo para o envio. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/post_nfce__idNotaFiscalConsumidor__enviar + */ + public async send(params: ISendParams): Promise { + return await this.repository.store({ + endpoint: `nfce/${params.idNotaFiscalConsumidor}/enviar`, + body: {} + }) + } + + /** + * Lança as contas de uma nota fiscal. + * + * @param {IPostAccountsParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/post_nfce__idNotaFiscalConsumidor__lancar_contas + */ + public async postAccounts(params: IPostAccountsParams): Promise { + return await this.repository.store({ + endpoint: `nfce/${params.idNotaFiscalConsumidor}/lancar-contas`, + body: {} + }) + } + + /** + * Estorna as contas de uma nota fiscal. + * + * @param {IReverseAccountsParams} params O conteúdo para o estorno. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/post_nfce__idNotaFiscalConsumidor__estornar_contas + */ + public async reverseAccounts(params: IReverseAccountsParams): Promise { + return await this.repository.store({ + endpoint: `nfce/${params.idNotaFiscalConsumidor}/estornar-contas`, + body: {} + }) + } + + /** + * Altera uma nota fiscal de consumidor. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Consumidor%20Eletr%C3%B4nicas/put_nfce__idNotaFiscalConsumidor_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idNotaFiscalConsumidor, ...body } = params + + return await this.repository.replace({ + endpoint: 'nfce', + id: String(idNotaFiscalConsumidor), + body + }) + } +} diff --git a/src/entities/nfces/interfaces/create.interface.ts b/src/entities/nfces/interfaces/create.interface.ts new file mode 100644 index 0000000..be6bbc6 --- /dev/null +++ b/src/entities/nfces/interfaces/create.interface.ts @@ -0,0 +1,136 @@ +import IContribuinte from 'src/entities/@shared/types/contribuinte.type' +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import IModeloDocumentoReferenciado from 'src/entities/@shared/types/modelo-documento-referenciado.type' +import IOrigem from 'src/entities/@shared/types/origem.type' +import ITipoItem from 'src/entities/@shared/types/tipo-item.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IFinalidadeNfce } from '../types/finalidade.type' +import { ITipoNfce } from '../types/tipo.type' + +export interface ICreateBody { + tipo: ITipoNfce + numero?: string + dataOperacao?: string + contato: { + nome: string + tipoPessoa: ITipoPessoa + numeroDocumento: string + ie?: string + rg?: string + contribuinte?: IContribuinte + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { + id: number + numero: string + } + finalidade?: IFinalidadeNfce + seguro?: number + despesas?: number + desconto?: number + observacoes?: string + documentoReferenciado?: { + modelo: IModeloDocumentoReferenciado + data?: string + numero?: string + serie?: string + contadorOrdemOperacao?: string + chaveAcesso?: string + } + itens?: { + codigo: string + descricao?: string + unidade?: string + quantidade?: number + valor?: number + tipo?: ITipoItem + pesoBruto?: number + pesoLiquido?: number + numeroPedidoCompra?: string + classificacaoFiscal?: string + cest?: string + codigoServico?: string + origem?: IOrigem + informacoesAdicionais?: string + }[] + + parcelas?: { + data: string + valor: number + observacoes?: string + formaPagamento?: { id: number } + }[] + + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + veiculo?: { + placa?: string + uf?: IUF + marca?: string + } + transportador?: { + nome: string + numeroDocumento?: string + ie?: string + endereco?: { + endereco?: string + municipio?: string + uf?: IUF + } + } + volume?: { + quantidade?: number + especie?: string + numero?: string + pesoBruto?: number + pesoLiquido?: number + } + volumes: { + servico: string + codigoRastreamento?: string + }[] + + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + } + } + notaFiscalProdutorRuralReferenciada?: { + numero: string + serie: string + data: string + } + intermediador?: { + cnpj: string + nomeUsuario: string + } +} + +export interface ICreateResponse { + data: { + id: number + numero: string + serie: string + contato: { nome?: string } + } +} diff --git a/src/entities/nfces/interfaces/find.interface.ts b/src/entities/nfces/interfaces/find.interface.ts new file mode 100644 index 0000000..c7d8e9c --- /dev/null +++ b/src/entities/nfces/interfaces/find.interface.ts @@ -0,0 +1,43 @@ +import IUF from 'src/entities/@shared/types/uf.type' +import { ISituacaoNfce } from '../types/situacao.type' +import { ITipoNfce } from '../types/tipo.type' + +export interface IFindParams { + /** + * ID da nota fiscal de consumidor + */ + idNotaFiscalConsumidor: number +} + +export interface IFindResponse { + data: { + id?: number + tipo: ITipoNfce + situacao?: ISituacaoNfce + numero: string + dataEmissao?: string + dataOperacao?: string + contato: { + id?: number + nome: string + numeroDocumento: string + ie?: string + rg?: string + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { id: number } + serie: string + } +} diff --git a/src/entities/nfces/interfaces/get.interface.ts b/src/entities/nfces/interfaces/get.interface.ts new file mode 100644 index 0000000..75ea5b1 --- /dev/null +++ b/src/entities/nfces/interfaces/get.interface.ts @@ -0,0 +1,58 @@ +import IUF from 'src/entities/@shared/types/uf.type' +import { ISituacaoNfce } from '../types/situacao.type' +import { ITipoNfce } from '../types/tipo.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * + */ + situacao?: ISituacaoNfce + /** + * Data incial para filtragem das notas fiscais + */ + dataEmissaoInicial?: Date | string + /** + * Data final para filtragem das notas fiscais + */ + dataEmissaoFinal?: Date | string +} + +export interface IGetResponse { + data: { + id?: number + tipo: ITipoNfce + situacao?: ISituacaoNfce + numero?: string + dataEmissao?: string + dataOperacao?: string + contato: { + id?: number + nome: string + numeroDocumento: string + ie?: string + rg?: string + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { id: number } + }[] +} diff --git a/src/entities/nfces/interfaces/post-accounts.interface.ts b/src/entities/nfces/interfaces/post-accounts.interface.ts new file mode 100644 index 0000000..22991d9 --- /dev/null +++ b/src/entities/nfces/interfaces/post-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IPostAccountsParams { + /** + * ID da nota fiscal de consumidor + */ + idNotaFiscalConsumidor: number +} diff --git a/src/entities/nfces/interfaces/reverse-accounts.interface.ts b/src/entities/nfces/interfaces/reverse-accounts.interface.ts new file mode 100644 index 0000000..4fe7384 --- /dev/null +++ b/src/entities/nfces/interfaces/reverse-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IReverseAccountsParams { + /** + * ID da nota fiscal de consumidor + */ + idNotaFiscalConsumidor: number +} diff --git a/src/entities/nfces/interfaces/send.interface.ts b/src/entities/nfces/interfaces/send.interface.ts new file mode 100644 index 0000000..d2c1712 --- /dev/null +++ b/src/entities/nfces/interfaces/send.interface.ts @@ -0,0 +1,10 @@ +export interface ISendParams { + /** + * ID da nota fiscal de consumidor + */ + idNotaFiscalConsumidor: number +} + +export interface ISendResponse { + data: { xml?: string } +} diff --git a/src/entities/nfces/interfaces/update.interface.ts b/src/entities/nfces/interfaces/update.interface.ts new file mode 100644 index 0000000..b7c85a5 --- /dev/null +++ b/src/entities/nfces/interfaces/update.interface.ts @@ -0,0 +1,143 @@ +import IContribuinte from 'src/entities/@shared/types/contribuinte.type' +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import IModeloDocumentoReferenciado from 'src/entities/@shared/types/modelo-documento-referenciado.type' +import IOrigem from 'src/entities/@shared/types/origem.type' +import ITipoItem from 'src/entities/@shared/types/tipo-item.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IFinalidadeNfce } from '../types/finalidade.type' +import { ITipoNfce } from '../types/tipo.type' + +export interface IUpdateParams { + /** + * ID da nota fiscal de consumidor + */ + idNotaFiscalConsumidor: number +} + +export interface IUpdateBody { + tipo: ITipoNfce + numero?: string + dataOperacao?: string + contato: { + nome: string + tipoPessoa: ITipoPessoa + numeroDocumento: string + ie?: string + rg?: string + contribuinte?: IContribuinte + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { + id: number + numero: string + } + finalidade?: IFinalidadeNfce + seguro?: number + despesas?: number + desconto?: number + observacoes?: string + documentoReferenciado?: { + modelo: IModeloDocumentoReferenciado + data?: string + numero?: string + serie?: string + contadorOrdemOperacao?: string + chaveAcesso?: string + } + itens?: { + codigo: string + descricao?: string + unidade?: string + quantidade?: number + valor?: number + tipo?: ITipoItem + pesoBruto?: number + pesoLiquido?: number + numeroPedidoCompra?: string + classificacaoFiscal?: string + cest?: string + codigoServico?: string + origem?: IOrigem + informacoesAdicionais?: string + }[] + + parcelas?: { + data: string + valor: number + observacoes?: string + formaPagamento?: { id: number } + }[] + + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + veiculo?: { + placa?: string + uf?: IUF + marca?: string + } + transportador?: { + nome: string + numeroDocumento?: string + ie?: string + endereco?: { + endereco?: string + municipio?: string + uf?: IUF + } + } + volume?: { + quantidade?: number + especie?: string + numero?: string + pesoBruto?: number + pesoLiquido?: number + } + volumes: { + servico: string + codigoRastreamento?: string + }[] + + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + } + } + notaFiscalProdutorRuralReferenciada?: { + numero: string + serie: string + data: string + } + intermediador?: { + cnpj: string + nomeUsuario: string + } +} + +export interface IUpdateResponse { + data: { + id: number + numero: string + serie: string + contato: { nome?: string } + } +} diff --git a/src/entities/nfces/types/finalidade.type.ts b/src/entities/nfces/types/finalidade.type.ts new file mode 100644 index 0000000..a271aba --- /dev/null +++ b/src/entities/nfces/types/finalidade.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente à finalidade da NFC-e. + * + * - `1`: Normal + * - `2`: Complementar + * - `3`: Ajuste + * - `4`: Devolução + */ +export type IFinalidadeNfce = 1 | 2 | 3 | 4 diff --git a/src/entities/nfces/types/situacao.type.ts b/src/entities/nfces/types/situacao.type.ts new file mode 100644 index 0000000..6a9d367 --- /dev/null +++ b/src/entities/nfces/types/situacao.type.ts @@ -0,0 +1,16 @@ +/** + * Tipagem referente à uma situação de NFC-e. + * + * - `1`: Pendente + * - `2`: Cancelada + * - `3`: Aguardando recibo + * - `4`: Rejeitada + * - `5`: Autorizada + * - `6`: Emitida DANFE + * - `7`: Registrada + * - `8`: Aguardando protocolo + * - `9`: Denegada + * - `10`: Consulta situação + * - `11`: Bloqueada + */ +export type ISituacaoNfce = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 diff --git a/src/entities/nfces/types/tipo.type.ts b/src/entities/nfces/types/tipo.type.ts new file mode 100644 index 0000000..cd4e941 --- /dev/null +++ b/src/entities/nfces/types/tipo.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo da NFC-e. + * + * - `0`: Entrada + * - `1`: Saída + */ +export type ITipoNfce = 0 | 1 diff --git a/src/entities/nfes/__tests__/create-response.ts b/src/entities/nfes/__tests__/create-response.ts new file mode 100644 index 0000000..e78705b --- /dev/null +++ b/src/entities/nfes/__tests__/create-response.ts @@ -0,0 +1,135 @@ +export default { + data: { + id: 12345678, + numero: '6541', + serie: '1', + contato: { + nome: 'Contato do Bling' + } + } +} + +export const createRequestBody = { + tipo: 1 as const, + numero: '6541', + dataOperacao: '2023-01-12 09:52:12', + contato: { + nome: 'Contato do Bling', + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + contribuinte: 1 as const, + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678, + numero: 'LOJA_8864' + }, + finalidade: 1 as const, + seguro: 1.15, + despesas: 5.08, + desconto: 10.12, + observacoes: 'Observação da nota.', + documentoReferenciado: { + modelo: '55' as const, + data: '2023-01-12', + numero: '123', + serie: '1', + contadorOrdemOperacao: '1', + chaveAcesso: '62634519764512837946527549134679858182373412' + }, + itens: [ + { + codigo: 'BLG-5', + descricao: 'Produto do Bling', + unidade: 'UN', + quantidade: 1, + valor: 4.9, + tipo: 'P' as const, + pesoBruto: 0.5, + pesoLiquido: 0.5, + numeroPedidoCompra: '235', + classificacaoFiscal: '9999.99.99', + cest: '99.999.99', + codigoServico: '99.99', + origem: 0 as const, + informacoesAdicionais: 'Descrição do item' + } + ], + parcelas: [ + { + data: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + veiculo: { + placa: 'LDO-2373', + uf: 'RS' as const, + marca: 'Volvo' + }, + transportador: { + nome: 'Transportador', + numeroDocumento: '30188025000121', + ie: '949895756023', + endereco: { + endereco: 'Olavo Bilac', + municipio: 'Bento Gonçalves', + uf: 'RS' as const + } + }, + volume: { + quantidade: 5, + especie: 'Volumes', + numero: '1', + pesoBruto: 0.5, + pesoLiquido: 0.35 + }, + volumes: [ + { + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ], + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante' + } + }, + notaFiscalProdutorRuralReferenciada: { + numero: '125', + serie: '1', + data: '2023-01-12' + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } +} diff --git a/src/entities/nfes/__tests__/delete-response.ts b/src/entities/nfes/__tests__/delete-response.ts new file mode 100644 index 0000000..a8acb8d --- /dev/null +++ b/src/entities/nfes/__tests__/delete-response.ts @@ -0,0 +1,6 @@ +export default { + data: { + alertas: ['12345677: Apenas notas pendentes podem ser excluídas.'], + idsExcluidos: [12345678] + } +} diff --git a/src/entities/nfes/__tests__/find-response.ts b/src/entities/nfes/__tests__/find-response.ts new file mode 100644 index 0000000..c07e9e5 --- /dev/null +++ b/src/entities/nfes/__tests__/find-response.ts @@ -0,0 +1,62 @@ +export default { + data: { + id: 12345678, + tipo: 1 as const, + situacao: 1 as const, + numero: '6541', + dataEmissao: '2023-01-12 09:52:12', + dataOperacao: '2023-01-12 09:52:12', + contato: { + id: 12345678, + nome: 'Contato do Bling', + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678 + }, + serie: 1, + chaveAcesso: 'string', + xml: 'string', + linkDanfe: 'string', + linkPDF: 'string', + transporte: { + fretePorConta: 0 as const, + transportador: { + nome: 'Transportador', + numeroDocumento: '30188025000121' + }, + volumes: [ + { + id: 12345678 + } + ], + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante' + } + } + } +} diff --git a/src/entities/nfes/__tests__/get-response.ts b/src/entities/nfes/__tests__/get-response.ts new file mode 100644 index 0000000..edeb38e --- /dev/null +++ b/src/entities/nfes/__tests__/get-response.ts @@ -0,0 +1,37 @@ +export default { + data: [ + { + id: 12345678, + tipo: 1 as const, + situacao: 1 as const, + numero: '6541', + dataEmissao: '2023-01-12 09:52:12', + dataOperacao: '2023-01-12 09:52:12', + contato: { + id: 12345678, + nome: 'Contato do Bling', + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/nfes/__tests__/index.spec.ts b/src/entities/nfes/__tests__/index.spec.ts new file mode 100644 index 0000000..752ccc2 --- /dev/null +++ b/src/entities/nfes/__tests__/index.spec.ts @@ -0,0 +1,153 @@ +import { Chance } from 'chance' +import { Nfes } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import postAccountsResponse from './post-accounts-response' +import reverseAccountsResponse from './reverse-accounts-response' +import sendResponse from './send-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('NF-es entity', () => { + let repository: InMemoryBlingRepository + let entity: Nfes + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Nfes(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + const idsNotas: number[] = [] + for (let i = 0; i < chance.natural({ min: 1, max: 5 }); i++) { + idsNotas.push(chance.natural()) + } + + const response = await entity.delete({ idsNotas }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfe', + id: '', + params: { idsNotas } + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfe', + params: { + limite: undefined, + pagina: undefined, + numeroLoja: undefined, + situacao: undefined, + tipo: undefined, + dataEmissaoInicial: undefined, + dataEmissaoFinal: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idNotaFiscal = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idNotaFiscal }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfe', + id: String(idNotaFiscal) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfe', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should send successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaFiscal = chance.natural() + repository.setResponse(sendResponse) + + const response = await entity.send({ idNotaFiscal }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfe/${idNotaFiscal}/enviar`, + body: {} + }) + expect(response).toBe(sendResponse) + }) + + it('should post accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaFiscal = chance.natural() + repository.setResponse(postAccountsResponse) + + const response = await entity.postAccounts({ idNotaFiscal }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfe/${idNotaFiscal}/lancar-contas`, + body: {} + }) + expect(response).toBe(postAccountsResponse) + }) + + it('should reverse accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaFiscal = chance.natural() + repository.setResponse(reverseAccountsResponse) + + const response = await entity.reverseAccounts({ idNotaFiscal }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfe/${idNotaFiscal}/estornar-contas`, + body: {} + }) + expect(response).toBe(reverseAccountsResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idNotaFiscal = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idNotaFiscal, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfe', + id: String(idNotaFiscal), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/nfes/__tests__/post-accounts-response.ts b/src/entities/nfes/__tests__/post-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/nfes/__tests__/post-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/nfes/__tests__/reverse-accounts-response.ts b/src/entities/nfes/__tests__/reverse-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/nfes/__tests__/reverse-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/nfes/__tests__/send-response.ts b/src/entities/nfes/__tests__/send-response.ts new file mode 100644 index 0000000..b8eed5a --- /dev/null +++ b/src/entities/nfes/__tests__/send-response.ts @@ -0,0 +1,5 @@ +export default { + data: { + xml: 'string' + } +} diff --git a/src/entities/nfes/__tests__/update-response.ts b/src/entities/nfes/__tests__/update-response.ts new file mode 100644 index 0000000..811dd57 --- /dev/null +++ b/src/entities/nfes/__tests__/update-response.ts @@ -0,0 +1,135 @@ +export default { + data: { + id: 12345678, + numero: '6541', + serie: '1', + contato: { + nome: 'Contato do Bling' + } + } +} + +export const updateRequestBody = { + tipo: 1 as const, + numero: '6541', + dataOperacao: '2023-01-12 09:52:12', + contato: { + nome: 'Contato do Bling', + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121', + ie: '7364873393', + rg: '451838701', + contribuinte: 1 as const, + telefone: '54 3771-7278', + email: 'pedrosilva@bling.com.br', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + pais: '' + } + }, + naturezaOperacao: { + id: 12345678 + }, + loja: { + id: 12345678, + numero: 'LOJA_8864' + }, + finalidade: 1 as const, + seguro: 1.15, + despesas: 5.08, + desconto: 10.12, + observacoes: 'Observação da nota.', + documentoReferenciado: { + modelo: '55' as const, + data: '2023-01-12', + numero: '123', + serie: '1', + contadorOrdemOperacao: '1', + chaveAcesso: '62634519764512837946527549134679858182373412' + }, + itens: [ + { + codigo: 'BLG-5', + descricao: 'Produto do Bling', + unidade: 'UN', + quantidade: 1, + valor: 4.9, + tipo: 'P' as const, + pesoBruto: 0.5, + pesoLiquido: 0.5, + numeroPedidoCompra: '235', + classificacaoFiscal: '9999.99.99', + cest: '99.999.99', + codigoServico: '99.99', + origem: 0 as const, + informacoesAdicionais: 'Descrição do item' + } + ], + parcelas: [ + { + data: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + veiculo: { + placa: 'LDO-2373', + uf: 'RS' as const, + marca: 'Volvo' + }, + transportador: { + nome: 'Transportador', + numeroDocumento: '30188025000121', + ie: '949895756023', + endereco: { + endereco: 'Olavo Bilac', + municipio: 'Bento Gonçalves', + uf: 'RS' as const + } + }, + volume: { + quantidade: 5, + especie: 'Volumes', + numero: '1', + pesoBruto: 0.5, + pesoLiquido: 0.35 + }, + volumes: [ + { + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ], + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante' + } + }, + notaFiscalProdutorRuralReferenciada: { + numero: '125', + serie: '1', + data: '2023-01-12' + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } +} diff --git a/src/entities/nfes/index.ts b/src/entities/nfes/index.ts new file mode 100644 index 0000000..c2ced83 --- /dev/null +++ b/src/entities/nfes/index.ts @@ -0,0 +1,176 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams, IDeleteResponse } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IPostAccountsParams } from './interfaces/post-accounts.interface' +import { IReverseAccountsParams } from './interfaces/reverse-accounts.interface' +import { ISendParams, ISendResponse } from './interfaces/send.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com notas fiscais eletrônicas. + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas + */ +export class Nfes extends Entity { + /** + * Remove múltiplas notas fiscais. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Retorno da deleção. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/delete_nfe + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'nfe', + id: '', + params: { + idsNotas: params.idsNotas + } + }) + } + + /** + * Obtém notas fiscais. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/get_nfe + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'nfe', + params: { + pagina: params?.pagina, + limite: params?.limite, + numeroLoja: params?.numeroLoja, + situacao: params?.situacao, + tipo: params?.tipo, + dataEmissaoInicial: this.prepareStringOrDateParam( + params?.dataEmissaoInicial + ), + dataEmissaoFinal: this.prepareStringOrDateParam( + params?.dataEmissaoFinal + ) + } + }) + } + + /** + * Obtém uma nota fiscal. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/get_nfe__idNotaFiscal_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'nfe', + id: String(params.idNotaFiscal) + }) + } + + /** + * Cria uma nota fiscal. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/post_nfe + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'nfe', + body + }) + } + + /** + * Envia uma nota fiscal. + * + * @param {ISendParams} params O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/post_nfe__idNotaFiscal__enviar + */ + public async send(params: ISendParams): Promise { + return await this.repository.store({ + endpoint: `nfe/${params.idNotaFiscal}/enviar`, + body: {} + }) + } + + /** + * Lança as contas de uma nota fiscal. + * + * @param {IPostAccountsParams} params O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/post_nfe__idNotaFiscal__lancar_contas + */ + public async postAccounts(params: IPostAccountsParams): Promise { + return await this.repository.store({ + endpoint: `nfe/${params.idNotaFiscal}/lancar-contas`, + body: {} + }) + } + + /** + * Estorna as contas de uma nota fiscal. + * + * @param {IReverseAccountsParams} params O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/post_nfe__idNotaFiscal__estornar_contas + */ + public async reverseAccounts(params: IReverseAccountsParams): Promise { + return await this.repository.store({ + endpoint: `nfe/${params.idNotaFiscal}/estornar-contas`, + body: {} + }) + } + + /** + * Altera uma nota fiscal. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20Eletr%C3%B4nicas/put_nfe__idNotaFiscal_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idNotaFiscal, ...body } = params + + return await this.repository.replace({ + endpoint: 'nfe', + id: String(idNotaFiscal), + body + }) + } +} diff --git a/src/entities/nfes/interfaces/create.interface.ts b/src/entities/nfes/interfaces/create.interface.ts new file mode 100644 index 0000000..e8f12ad --- /dev/null +++ b/src/entities/nfes/interfaces/create.interface.ts @@ -0,0 +1,136 @@ +import IContribuinte from 'src/entities/@shared/types/contribuinte.type' +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import IModeloDocumentoReferenciado from 'src/entities/@shared/types/modelo-documento-referenciado.type' +import IOrigem from 'src/entities/@shared/types/origem.type' +import ITipoItem from 'src/entities/@shared/types/tipo-item.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IFinalidadeNfe } from '../types/finalidade.type' +import { ITipoNfe } from '../types/tipo.type' + +export interface ICreateBody { + tipo: ITipoNfe + numero?: string + dataOperacao?: string + contato: { + nome: string + tipoPessoa: ITipoPessoa + numeroDocumento: string + ie?: string + rg?: string + contribuinte?: IContribuinte + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { + id: number + numero?: string + } + finalidade?: IFinalidadeNfe + seguro?: number + despesas?: number + desconto?: number + observacoes?: string + documentoReferenciado?: { + modelo: IModeloDocumentoReferenciado + data?: string + numero?: string + serie?: string + contadorOrdemOperacao?: string + chaveAcesso?: string + } + itens?: { + codigo: string + descricao?: string + unidade?: string + quantidade?: number + valor?: number + tipo?: ITipoItem + pesoBruto?: number + pesoLiquido?: number + numeroPedidoCompra?: string + classificacaoFiscal?: string + cest?: string + codigoServico?: string + origem?: IOrigem + informacoesAdicionais?: string + }[] + + parcelas: { + data: string + valor: number + observacoes?: string + formaPagamento?: { id: number } + }[] + + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + veiculo?: { + placa?: string + uf?: IUF + marca?: string + } + transportador?: { + nome: string + numeroDocumento?: string + ie?: string + endereco?: { + endereco?: string + municipio?: string + uf?: IUF + } + } + volume?: { + quantidade?: number + especie?: string + numero?: string + pesoBruto?: number + pesoLiquido?: number + } + volumes: { + servico: string + codigoRastreamento?: string + }[] + + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + } + } + notaFiscalProdutorRuralReferenciada?: { + numero: string + serie: string + data: string + } + intermediador?: { + cnpj: string + nomeUsuario: string + } +} + +export interface ICreateResponse { + data: { + id: number + numero: string + serie: string + contato: { nome?: string } + } +} diff --git a/src/entities/nfes/interfaces/delete.interface.ts b/src/entities/nfes/interfaces/delete.interface.ts new file mode 100644 index 0000000..36a17a8 --- /dev/null +++ b/src/entities/nfes/interfaces/delete.interface.ts @@ -0,0 +1,13 @@ +export interface IDeleteParams { + /** + * IDs das notas fiscais + */ + idsNotas: number[] +} + +export interface IDeleteResponse { + data: { + alertas: string[] + idsExcluidos: number[] + } +} diff --git a/src/entities/nfes/interfaces/find.interface.ts b/src/entities/nfes/interfaces/find.interface.ts new file mode 100644 index 0000000..d4c41f5 --- /dev/null +++ b/src/entities/nfes/interfaces/find.interface.ts @@ -0,0 +1,66 @@ +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { ISituacaoNfe } from '../types/situacao.type' +import { ITipoNfe } from '../types/tipo.type' + +export interface IFindParams { + /** + * ID da nota fiscal + */ + idNotaFiscal: number +} + +export interface IFindResponse { + data: { + id?: number + tipo: ITipoNfe + situacao?: ISituacaoNfe + numero?: string + dataEmissao?: string + dataOperacao?: string + contato: { + id?: number + nome: string + numeroDocumento: string + ie?: string + rg?: string + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { id: number } + serie?: number + chaveAcesso?: string + xml?: string + linkDanfe?: string + linkPDF?: string + transporte?: { + fretePorConta?: IFretePorConta + transportador?: { + nome: string + numeroDocumento?: string + } + volumes?: { id?: number }[] + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + } + } + } +} diff --git a/src/entities/nfes/interfaces/get.interface.ts b/src/entities/nfes/interfaces/get.interface.ts new file mode 100644 index 0000000..18b7a12 --- /dev/null +++ b/src/entities/nfes/interfaces/get.interface.ts @@ -0,0 +1,66 @@ +import IUF from 'src/entities/@shared/types/uf.type' +import { ISituacaoNfe } from '../types/situacao.type' +import { ITipoNfe } from '../types/tipo.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Número do pedido na loja + */ + numeroLoja?: string + /** + * + */ + situacao?: ISituacaoNfe + /** + * + */ + tipo?: ITipoNfe + /** + * Data e hora incial de emissão + */ + dataEmissaoInicial?: string + /** + * Data e hora final de emissão + */ + dataEmissaoFinal?: string +} + +export interface IGetResponse { + data: { + id?: number + tipo: ITipoNfe + situacao?: ISituacaoNfe + numero?: string + dataEmissao?: string + dataOperacao?: string + contato: { + id?: number + nome: string + numeroDocumento: string + ie?: string + rg?: string + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { id: number } + }[] +} diff --git a/src/entities/nfes/interfaces/post-accounts.interface.ts b/src/entities/nfes/interfaces/post-accounts.interface.ts new file mode 100644 index 0000000..f01104e --- /dev/null +++ b/src/entities/nfes/interfaces/post-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IPostAccountsParams { + /** + * ID da nota fiscal + */ + idNotaFiscal: number +} diff --git a/src/entities/nfes/interfaces/reverse-accounts.interface.ts b/src/entities/nfes/interfaces/reverse-accounts.interface.ts new file mode 100644 index 0000000..f2a7d67 --- /dev/null +++ b/src/entities/nfes/interfaces/reverse-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IReverseAccountsParams { + /** + * ID da nota fiscal + */ + idNotaFiscal: number +} diff --git a/src/entities/nfes/interfaces/send.interface.ts b/src/entities/nfes/interfaces/send.interface.ts new file mode 100644 index 0000000..cd40cf5 --- /dev/null +++ b/src/entities/nfes/interfaces/send.interface.ts @@ -0,0 +1,12 @@ +export interface ISendParams { + /** + * ID da nota fiscal + */ + idNotaFiscal: number +} + +export interface ISendResponse { + data: { + xml?: string + } +} diff --git a/src/entities/nfes/interfaces/update.interface.ts b/src/entities/nfes/interfaces/update.interface.ts new file mode 100644 index 0000000..304c5e3 --- /dev/null +++ b/src/entities/nfes/interfaces/update.interface.ts @@ -0,0 +1,143 @@ +import IContribuinte from 'src/entities/@shared/types/contribuinte.type' +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import IModeloDocumentoReferenciado from 'src/entities/@shared/types/modelo-documento-referenciado.type' +import IOrigem from 'src/entities/@shared/types/origem.type' +import ITipoItem from 'src/entities/@shared/types/tipo-item.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IFinalidadeNfe } from '../types/finalidade.type' +import { ITipoNfe } from '../types/tipo.type' + +export interface IUpdateParams { + /** + * ID da nota fiscal + */ + idNotaFiscal: number +} + +export interface IUpdateBody { + tipo: ITipoNfe + numero?: string + dataOperacao?: string + contato: { + nome: string + tipoPessoa: ITipoPessoa + numeroDocumento: string + ie?: string + rg?: string + contribuinte?: IContribuinte + telefone?: string + email?: string + endereco?: { + endereco: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + pais?: string + } + } + naturezaOperacao?: { id: number } + loja?: { + id: number + numero?: string + } + finalidade?: IFinalidadeNfe + seguro?: number + despesas?: number + desconto?: number + observacoes?: string + documentoReferenciado?: { + modelo: IModeloDocumentoReferenciado + data?: string + numero?: string + serie?: string + contadorOrdemOperacao?: string + chaveAcesso?: string + } + itens?: { + codigo: string + descricao?: string + unidade?: string + quantidade?: number + valor?: number + tipo?: ITipoItem + pesoBruto?: number + pesoLiquido?: number + numeroPedidoCompra?: string + classificacaoFiscal?: string + cest?: string + codigoServico?: string + origem?: IOrigem + informacoesAdicionais?: string + }[] + + parcelas: { + data: string + valor: number + observacoes?: string + formaPagamento?: { id: number } + }[] + + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + veiculo?: { + placa?: string + uf?: IUF + marca?: string + } + transportador?: { + nome: string + numeroDocumento?: string + ie?: string + endereco?: { + endereco?: string + municipio?: string + uf?: IUF + } + } + volume?: { + quantidade?: number + especie?: string + numero?: string + pesoBruto?: number + pesoLiquido?: number + } + volumes: { + servico: string + codigoRastreamento?: string + }[] + + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + } + } + notaFiscalProdutorRuralReferenciada?: { + numero: string + serie: string + data: string + } + intermediador?: { + cnpj: string + nomeUsuario: string + } +} + +export interface IUpdateResponse { + data: { + id: number + numero: string + serie: string + contato: { nome?: string } + } +} diff --git a/src/entities/nfes/types/finalidade.type.ts b/src/entities/nfes/types/finalidade.type.ts new file mode 100644 index 0000000..21f5be9 --- /dev/null +++ b/src/entities/nfes/types/finalidade.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente à finalidade da NF-e. + * + * - `1`: Normal + * - `2`: Complementar + * - `3`: Ajuste + * - `4`: Devolução + */ +export type IFinalidadeNfe = 1 | 2 | 3 | 4 diff --git a/src/entities/nfes/types/situacao.type.ts b/src/entities/nfes/types/situacao.type.ts new file mode 100644 index 0000000..e4793ab --- /dev/null +++ b/src/entities/nfes/types/situacao.type.ts @@ -0,0 +1,16 @@ +/** + * Tipagem referente à situação da NF-e. + * + * - `1`: Pendente + * - `2`: Cancelada + * - `3`: Aguardando recibo + * - `4`: Rejeitada + * - `5`: Autorizada + * - `6`: Emitida DANFE + * - `7`: Registrada + * - `8`: Aguardando protocolo + * - `9`: Denegada + * - `10`: Consulta situação + * - `11`: Bloqueada + */ +export type ISituacaoNfe = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 diff --git a/src/entities/nfes/types/tipo.type.ts b/src/entities/nfes/types/tipo.type.ts new file mode 100644 index 0000000..4f2ceda --- /dev/null +++ b/src/entities/nfes/types/tipo.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo da NF-e. + * + * - `0`: Entrada + * - `1`: Saída + */ +export type ITipoNfe = 0 | 1 diff --git a/src/entities/nfses/__tests__/cancel-response.ts b/src/entities/nfses/__tests__/cancel-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/nfses/__tests__/cancel-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/nfses/__tests__/create-response.ts b/src/entities/nfses/__tests__/create-response.ts new file mode 100644 index 0000000..43bb3ca --- /dev/null +++ b/src/entities/nfses/__tests__/create-response.ts @@ -0,0 +1,56 @@ +export default { + data: { + id: 12345678, + numeroRPS: '123', + serie: '1' + } +} + +export const createRequestBody = { + numero: '123', + numeroRPS: '32', + serie: '1', + dataEmissao: '2023-01-12', + contato: { + id: 12345678, + nome: 'Pedro Silva', + numeroDocumento: '30188025000121', + email: 'pedrosilva@bling.com.br', + ie: '949895756023', + telefone: '54 3771-7278', + endereco: { + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + bairro: 'Imigrante', + cep: '95702-000', + municipio: 'Bento Gonçalves', + uf: 'RS' as const + } + }, + link: 'https://linkexemplo.com.br/nfse', + codigoVerificacao: 'string', + data: '2023-01-12', + reterISS: false, + desconto: 15.45, + vendedor: { + id: 12345678 + }, + servicos: [ + { + codigo: '10301', + descricao: 'Pintura', + valor: 100.25 + } + ], + parcelas: [ + { + data: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/nfses/__tests__/delete-response.ts b/src/entities/nfses/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/nfses/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/nfses/__tests__/find-response.ts b/src/entities/nfses/__tests__/find-response.ts new file mode 100644 index 0000000..8c4f046 --- /dev/null +++ b/src/entities/nfses/__tests__/find-response.ts @@ -0,0 +1,19 @@ +export default { + data: { + id: 12345678, + numero: '123', + numeroRPS: '32', + serie: '1', + situacao: 0 as const, + dataEmissao: '2023-01-12', + valor: 100, + contato: { + id: 12345678, + nome: 'Pedro Silva', + numeroDocumento: '30188025000121', + email: 'pedrosilva@bling.com.br' + }, + link: 'https://linkexemplo.com.br/nfse', + codigoVerificacao: 'string' + } +} diff --git a/src/entities/nfses/__tests__/get-configurations-response.ts b/src/entities/nfses/__tests__/get-configurations-response.ts new file mode 100644 index 0000000..6c36ac5 --- /dev/null +++ b/src/entities/nfses/__tests__/get-configurations-response.ts @@ -0,0 +1,86 @@ +export default { + basicas: { + emissorPadrao: 3, + naturezaOperacao: 1 + }, + ISS: { + zerar: false, + reter: true, + descontar: true, + tributos: [ + { + id: 1, + percentualISS: 5, + CNAE: '82.99', + descricaoServico: 'Laudo de Vistoria Veicular', + padrao: false, + codigo: { + listaServico: '0107', + tributacao: '0107' + } + } + ] + }, + controle: { + numeracaoRPS: { + cnpjEmitente: '48.426.683/0001-70', + id: 1, + numero: 1, + serie: 1 + } + }, + impostos: { + bloquearRetencaoPessoaFisica: true, + IR: { + percentual: 0, + valorMinimoAlternativoDescontol: 0, + descontar: false, + texto: { + padrao: '( - ) IRenda Fonte 1,5%', + isento: 'IR Isento Cfe. Lei nro. 9430/96 Art.64' + } + }, + outros: { + CSLLPISCOFINSDTO: { + calcular: true, + reter: false + }, + INSS: { + reter: true + }, + aproximados: { + utilizarAliqIBPT: true, + percentualAliq: 0 + } + } + }, + envioEmail: { + enviarBoletoRPS: true, + remetente: 'Nome remetente padrão', + assunto: 'Assunto padrão', + mensagem: 'Mensagem padrão e-mail', + padrao: { + copia: 'E-mail padrão de cópia', + resposta: 'E-mail padrão de resposta' + } + }, + adicionais: { + CFPS: '9.001', + CFOP: '1.250', + AEDF: '', + proximoNumeroLote: 78, + observacaoImpressaNota: 'OBS', + descricaoComplementar: 'OBS', + tipoEmissao: 'R', + campoNumeroDocContas: true, + incentivadorFiscal: true, + alterarSituacao: true, + incluirParcelas: false, + considerarDataParcela: true, + considerarDataOrdemServico: true, + cadastroPrefeitura: { + login: 'Login prefeitura', + senha: 'Senha prefeitura' + } + } +} diff --git a/src/entities/nfses/__tests__/get-response.ts b/src/entities/nfses/__tests__/get-response.ts new file mode 100644 index 0000000..dacf72a --- /dev/null +++ b/src/entities/nfses/__tests__/get-response.ts @@ -0,0 +1,19 @@ +export default { + data: [ + { + id: 12345678, + numero: '123', + numeroRPS: '32', + serie: '1', + situacao: 0, + dataEmissao: '2023-01-12', + valor: 100, + contato: { + id: 12345678, + nome: 'Pedro Silva', + numeroDocumento: '30188025000121', + email: 'pedrosilva@bling.com.br' + } + } + ] +} diff --git a/src/entities/nfses/__tests__/index.spec.ts b/src/entities/nfses/__tests__/index.spec.ts new file mode 100644 index 0000000..74c5f29 --- /dev/null +++ b/src/entities/nfses/__tests__/index.spec.ts @@ -0,0 +1,146 @@ +import { Chance } from 'chance' +import { Nfses } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import cancelResponse from './cancel-response' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getConfigurationsResponse from './get-configurations-response' +import getResponse from './get-response' +import sendResponse from './send-response' +import updateConfigurationsResponse, { + updateConfigurationsRequestBody +} from './update-configurations-response' + +const chance = Chance() + +describe('NFS-es entity', () => { + let repository: InMemoryBlingRepository + let entity: Nfses + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Nfses(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idNotaServico = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idNotaServico }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfse', + id: String(idNotaServico) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfse', + params: { + limite: undefined, + pagina: undefined, + situacao: undefined, + dataEmissaoInicial: undefined, + dataEmissaoFinal: undefined, + dataBaseFinal: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idNotaServico = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idNotaServico }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfse', + id: String(idNotaServico) + }) + expect(response).toBe(findResponse) + }) + + it('should get configurations successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getConfigurationsResponse) + + const response = await entity.getConfigurations() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfse/configuracoes' + }) + expect(response).toBe(getConfigurationsResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfse', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should send successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaServico = chance.natural() + repository.setResponse(sendResponse) + + const response = await entity.send({ idNotaServico }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfse/${idNotaServico}/enviar`, + body: {} + }) + expect(response).toBe(sendResponse) + }) + + it('should cancel successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotaServico = chance.natural() + repository.setResponse(cancelResponse) + + const response = await entity.cancel({ idNotaServico }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `nfse/${idNotaServico}/cancelar`, + body: {} + }) + expect(response).toBe(cancelResponse) + }) + + it('should update configurations successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + repository.setResponse(updateConfigurationsResponse) + + const response = await entity.updateConfigurations( + updateConfigurationsRequestBody + ) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'nfse/configuracoes', + id: '', + body: updateConfigurationsRequestBody + }) + expect(response).toBe(updateConfigurationsResponse) + }) +}) diff --git a/src/entities/nfses/__tests__/send-response.ts b/src/entities/nfses/__tests__/send-response.ts new file mode 100644 index 0000000..8c4f046 --- /dev/null +++ b/src/entities/nfses/__tests__/send-response.ts @@ -0,0 +1,19 @@ +export default { + data: { + id: 12345678, + numero: '123', + numeroRPS: '32', + serie: '1', + situacao: 0 as const, + dataEmissao: '2023-01-12', + valor: 100, + contato: { + id: 12345678, + nome: 'Pedro Silva', + numeroDocumento: '30188025000121', + email: 'pedrosilva@bling.com.br' + }, + link: 'https://linkexemplo.com.br/nfse', + codigoVerificacao: 'string' + } +} diff --git a/src/entities/nfses/__tests__/update-configurations-response.ts b/src/entities/nfses/__tests__/update-configurations-response.ts new file mode 100644 index 0000000..2e45c44 --- /dev/null +++ b/src/entities/nfses/__tests__/update-configurations-response.ts @@ -0,0 +1,88 @@ +export default null + +export const updateConfigurationsRequestBody = { + basicas: { + emissorPadrao: 3, + naturezaOperacao: 1 + }, + ISS: { + zerar: false, + reter: true, + descontar: true, + tributos: [ + { + id: 1, + percentualISS: 5, + CNAE: '82.99', + descricaoServico: 'Laudo de Vistoria Veicular', + padrao: false, + codigo: { + listaServico: '0107', + tributacao: '0107' + } + } + ] + }, + controle: { + numeracaoRPS: { + cnpjEmitente: '48.426.683/0001-70', + id: 1, + numero: 1, + serie: 1 + } + }, + impostos: { + bloquearRetencaoPessoaFisica: true, + IR: { + percentual: 0, + valorMinimoAlternativoDescontol: 0, + descontar: false, + texto: { + padrao: '( - ) IRenda Fonte 1,5%', + isento: 'IR Isento Cfe. Lei nro. 9430/96 Art.64' + } + }, + outros: { + CSLLPISCOFINSDTO: { + calcular: true, + reter: false + }, + INSS: { + reter: true + }, + aproximados: { + utilizarAliqIBPT: true, + percentualAliq: 0 + } + } + }, + envioEmail: { + enviarBoletoRPS: true, + remetente: 'Nome remetente padrão', + assunto: 'Assunto padrão', + mensagem: 'Mensagem padrão e-mail', + padrao: { + copia: 'E-mail padrão de cópia', + resposta: 'E-mail padrão de resposta' + } + }, + adicionais: { + CFPS: '9.001', + CFOP: '1.250', + AEDF: '', + proximoNumeroLote: 78, + observacaoImpressaNota: 'OBS', + descricaoComplementar: 'OBS', + tipoEmissao: 'R', + campoNumeroDocContas: true, + incentivadorFiscal: true, + alterarSituacao: true, + incluirParcelas: false, + considerarDataParcela: true, + considerarDataOrdemServico: true, + cadastroPrefeitura: { + login: 'Login prefeitura', + senha: 'Senha prefeitura' + } + } +} diff --git a/src/entities/nfses/index.ts b/src/entities/nfses/index.ts new file mode 100644 index 0000000..8835db9 --- /dev/null +++ b/src/entities/nfses/index.ts @@ -0,0 +1,163 @@ +import { Entity } from '../@shared/entity' +import { ISendResponse } from '../nfces/interfaces/send.interface' +import { ICancelParams } from './interfaces/cancel.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetConfigurationsResponse } from './interfaces/get-configurations.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { ISendParams } from './interfaces/send.interface' +import { IUpdateConfigurationsBody } from './interfaces/update-configurations.interface' + +/** + * Entidade para interação com notas fiscais de serviço eletrônicas. + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas + */ +export class Nfses extends Entity { + /** + * Exclui uma nota de serviço. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/delete_nfse__idNotaServico_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'nfse', + id: String(params.idNotaServico) + }) + } + + /** + * Obtém notas de serviços. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/get_nfse + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'nfse', + params: { + pagina: params?.pagina, + limite: params?.limite, + situacao: params?.situacao, + dataEmissaoInicial: this.prepareStringOrDateParam( + params?.dataEmissaoInicial + ), + dataEmissaoFinal: this.prepareStringOrDateParam( + params?.dataEmissaoFinal + ) + } + }) + } + + /** + * Obtém uma nota de serviço. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/get_nfse__idNotaServico_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'nfse', + id: String(params.idNotaServico) + }) + } + + /** + * Configurações de nota de serviço. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/get_nfse_configuracoes + */ + public async getConfigurations(): Promise { + return await this.repository.index({ + endpoint: 'nfse/configuracoes' + }) + } + + /** + * Cria uma nota de serviço. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/post_nfse + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'nfse', + body + }) + } + + /** + * Envia uma nota de serviço. + * + * @param {ISendParams} params Os parâmetros de envio. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/post_nfse__idNotaServico__enviar + */ + public async send(params: ISendParams): Promise { + return await this.repository.store({ + endpoint: `nfse/${params.idNotaServico}/enviar`, + body: {} + }) + } + + /** + * Cancela uma nota de serviço. + * + * @param {ICancelParams} params Os parâmetros de envio. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/post_nfse__idNotaServico__cancelar + */ + public async cancel(params: ICancelParams): Promise { + return await this.repository.store({ + endpoint: `nfse/${params.idNotaServico}/cancelar`, + body: {} + }) + } + + /** + * Configurações de nota de serviço. + * + * @param {IUpdateConfigurationsBody} body Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notas%20Fiscais%20de%20Servi%C3%A7o%20Eletr%C3%B4nicas/put_nfse_configuracoes + */ + public async updateConfigurations( + body: IUpdateConfigurationsBody + ): Promise { + return await this.repository.replace({ + endpoint: 'nfse/configuracoes', + id: '', + body + }) + } +} diff --git a/src/entities/nfses/interfaces/cancel.interface.ts b/src/entities/nfses/interfaces/cancel.interface.ts new file mode 100644 index 0000000..b55da50 --- /dev/null +++ b/src/entities/nfses/interfaces/cancel.interface.ts @@ -0,0 +1,6 @@ +export interface ICancelParams { + /** + * ID da nota de serviço + */ + idNotaServico: number +} diff --git a/src/entities/nfses/interfaces/create.interface.ts b/src/entities/nfses/interfaces/create.interface.ts new file mode 100644 index 0000000..c9b12c2 --- /dev/null +++ b/src/entities/nfses/interfaces/create.interface.ts @@ -0,0 +1,51 @@ +import IUF from 'src/entities/@shared/types/uf.type' + +export interface ICreateBody { + numero?: string + numeroRPS: string + serie: string + dataEmissao?: string + contato: { + id: number + nome: string + numeroDocumento: string + email: string + ie?: string + telefone?: string + endereco?: { + endereco?: string + numero?: string + complemento?: string + bairro: string + cep?: string + municipio: string + uf?: IUF + } + } + link?: string + codigoVerificacao?: string + data?: string + reterISS?: boolean + desconto?: number + vendedor?: { id: number } + servicos: { + codigo: string + descricao: string + valor: number + }[] + + parcelas: { + data: string + valor: number + observacoes?: string + formaPagamento?: { id: number } + }[] +} + +export interface ICreateResponse { + data: { + id: number + numeroRPS: string + serie: string + } +} diff --git a/src/entities/nfses/interfaces/delete.interface.ts b/src/entities/nfses/interfaces/delete.interface.ts new file mode 100644 index 0000000..514abcf --- /dev/null +++ b/src/entities/nfses/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID da nota de serviço + */ + idNotaServico: number +} diff --git a/src/entities/nfses/interfaces/find.interface.ts b/src/entities/nfses/interfaces/find.interface.ts new file mode 100644 index 0000000..de970ba --- /dev/null +++ b/src/entities/nfses/interfaces/find.interface.ts @@ -0,0 +1,28 @@ +import { ISituacaoNfse } from '../types/situacao.type' + +export interface IFindParams { + /** + * ID da nota de serviço + */ + idNotaServico: number +} + +export interface IFindResponse { + data: { + id: number + numero?: string + numeroRPS: string + serie: string + situacao?: ISituacaoNfse + dataEmissao?: string + valor?: number + contato: { + id: number + nome: string + numeroDocumento: string + email: string + } + link?: string + codigoVerificacao?: string + } +} diff --git a/src/entities/nfses/interfaces/get-configurations.interface.ts b/src/entities/nfses/interfaces/get-configurations.interface.ts new file mode 100644 index 0000000..247f27e --- /dev/null +++ b/src/entities/nfses/interfaces/get-configurations.interface.ts @@ -0,0 +1,84 @@ +export interface IGetConfigurationsResponse { + basicas?: { + emissorPadrao?: number + naturezaOperacao?: number + } + ISS?: { + zerar?: boolean + reter?: boolean + descontar?: boolean + tributos: { + id?: number + percentualISS?: number + CNAE: string + descricaoServico: string + padrao?: boolean + codigo: { + listaServico: string + tributacao?: string + } + }[] + } + controle?: { + numeracaoRPS?: { + cnpjEmitente?: string + id?: number + numero?: number + serie?: number + } + } + impostos?: { + bloquearRetencaoPessoaFisica?: boolean + IR?: { + percentual?: number + valorMinimoAlternativoDescontol?: number + descontar?: boolean + texto?: { + padrao?: string + isento?: string + } + } + outros?: { + CSLLPISCOFINSDTO?: { + calcular?: boolean + reter?: boolean + } + INSS?: { + reter?: boolean + } + aproximados?: { + utilizarAliqIBPT?: boolean + percentualAliq?: number + } + } + } + envioEmail?: { + enviarBoletoRPS?: boolean + remetente?: string + assunto?: string + mensagem?: string + padrao?: { + copia?: string + resposta?: string + } + } + adicionais?: { + CFPS?: string + CFOP?: string + AEDF?: string + proximoNumeroLote?: number + observacaoImpressaNota?: string + descricaoComplementar?: string + tipoEmissao?: string + campoNumeroDocContas?: boolean + incentivadorFiscal?: boolean + alterarSituacao?: boolean + incluirParcelas?: boolean + considerarDataParcela?: boolean + considerarDataOrdemServico?: boolean + cadastroPrefeitura?: { + login?: string + senha?: string + } + } +} diff --git a/src/entities/nfses/interfaces/get.interface.ts b/src/entities/nfses/interfaces/get.interface.ts new file mode 100644 index 0000000..4227c9e --- /dev/null +++ b/src/entities/nfses/interfaces/get.interface.ts @@ -0,0 +1,42 @@ +import { ISituacaoNfse } from '../types/situacao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * + */ + situacao?: ISituacaoNfse + /** + * Data incial do período de emissão + */ + dataEmissaoInicial?: string | Date + /** + * Data final do período de emissão + */ + dataEmissaoFinal?: string | Date +} + +export interface IGetResponse { + data: { + id: number + numero?: string + numeroRPS: string + serie: string + situacao?: ISituacaoNfse + dataEmissao?: string + valor?: number + contato: { + id: number + nome: string + numeroDocumento: string + email: string + } + }[] +} diff --git a/src/entities/nfses/interfaces/send.interface.ts b/src/entities/nfses/interfaces/send.interface.ts new file mode 100644 index 0000000..21c0ba6 --- /dev/null +++ b/src/entities/nfses/interfaces/send.interface.ts @@ -0,0 +1,28 @@ +import { ISituacaoNfse } from '../types/situacao.type' + +export interface ISendParams { + /** + * ID da nota de serviço + */ + idNotaServico: number +} + +export interface ISendResponse { + data: { + id: number + numero?: string + numeroRPS: string + serie: string + situacao?: ISituacaoNfse + dataEmissao?: string + valor?: number + contato: { + id: number + nome: string + numeroDocumento: string + email: string + } + link?: string + codigoVerificacao?: string + } +} diff --git a/src/entities/nfses/interfaces/update-configurations.interface.ts b/src/entities/nfses/interfaces/update-configurations.interface.ts new file mode 100644 index 0000000..9893c74 --- /dev/null +++ b/src/entities/nfses/interfaces/update-configurations.interface.ts @@ -0,0 +1,84 @@ +export interface IUpdateConfigurationsBody { + basicas?: { + emissorPadrao?: number + naturezaOperacao?: number + } + ISS?: { + zerar?: boolean + reter?: boolean + descontar?: boolean + tributos: { + id?: number + percentualISS?: number + CNAE: string + descricaoServico: string + padrao?: boolean + codigo: { + listaServico: string + tributacao?: string + } + }[] + } + controle?: { + numeracaoRPS?: { + cnpjEmitente?: string + id?: number + numero?: number + serie?: number + } + } + impostos?: { + bloquearRetencaoPessoaFisica?: boolean + IR?: { + percentual?: number + valorMinimoAlternativoDescontol?: number + descontar?: boolean + texto?: { + padrao?: string + isento?: string + } + } + outros?: { + CSLLPISCOFINSDTO?: { + calcular?: boolean + reter?: boolean + } + INSS?: { + reter?: boolean + } + aproximados?: { + utilizarAliqIBPT?: boolean + percentualAliq?: number + } + } + } + envioEmail?: { + enviarBoletoRPS?: boolean + remetente?: string + assunto?: string + mensagem?: string + padrao?: { + copia?: string + resposta?: string + } + } + adicionais?: { + CFPS?: string + CFOP?: string + AEDF?: string + proximoNumeroLote?: number + observacaoImpressaNota?: string + descricaoComplementar?: string + tipoEmissao?: string + campoNumeroDocContas?: boolean + incentivadorFiscal?: boolean + alterarSituacao?: boolean + incluirParcelas?: boolean + considerarDataParcela?: boolean + considerarDataOrdemServico?: boolean + cadastroPrefeitura?: { + login?: string + senha?: string + } + } +} diff --git a/src/entities/nfses/types/situacao.type.ts b/src/entities/nfses/types/situacao.type.ts new file mode 100644 index 0000000..08d12ca --- /dev/null +++ b/src/entities/nfses/types/situacao.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente à situação de uma nota fiscal de serviço eletrônica. + * + * - `0`: Pendente + * - `1`: Emitida + * - `2`: Disponível para consulta + * - `3`: Cancelada + */ +export type ISituacaoNfse = 0 | 1 | 2 | 3 diff --git a/src/entities/notificacoes/__tests__/create-response.ts b/src/entities/notificacoes/__tests__/create-response.ts new file mode 100644 index 0000000..f49471e --- /dev/null +++ b/src/entities/notificacoes/__tests__/create-response.ts @@ -0,0 +1,28 @@ +export default { + data: [ + { + id: '01ARZ3NDEKTSV4RRFFQ69G5FAV', + emitente: 'string', + modulo: 'FISCAL', + descricao: 'string', + titulo: 'string', + fonte: 'SEFAZ', + linkAjuda: 'string', + dataCriacao: '2023-01-12', + dataEnvio: '2023-01-12 00:00:00', + dataVigencia: '2023-01-12', + dataAcao: '2023-01-12', + dataLeitura: '2023-01-12 11:50:00', + dataAlerta: '2023-01-12', + dataPerigo: '2023-01-12', + enquadramentos: [ + { + tamanhoEmpresa: ['micro', 'pequena'], + idMunicipio: ['2704104', '2704203'], + uf: ['SP', 'RS'], + crt: [1, 2] + } + ] + } + ] +} diff --git a/src/entities/notificacoes/__tests__/get-response.ts b/src/entities/notificacoes/__tests__/get-response.ts new file mode 100644 index 0000000..f49471e --- /dev/null +++ b/src/entities/notificacoes/__tests__/get-response.ts @@ -0,0 +1,28 @@ +export default { + data: [ + { + id: '01ARZ3NDEKTSV4RRFFQ69G5FAV', + emitente: 'string', + modulo: 'FISCAL', + descricao: 'string', + titulo: 'string', + fonte: 'SEFAZ', + linkAjuda: 'string', + dataCriacao: '2023-01-12', + dataEnvio: '2023-01-12 00:00:00', + dataVigencia: '2023-01-12', + dataAcao: '2023-01-12', + dataLeitura: '2023-01-12 11:50:00', + dataAlerta: '2023-01-12', + dataPerigo: '2023-01-12', + enquadramentos: [ + { + tamanhoEmpresa: ['micro', 'pequena'], + idMunicipio: ['2704104', '2704203'], + uf: ['SP', 'RS'], + crt: [1, 2] + } + ] + } + ] +} diff --git a/src/entities/notificacoes/__tests__/index.spec.ts b/src/entities/notificacoes/__tests__/index.spec.ts new file mode 100644 index 0000000..bcbd14d --- /dev/null +++ b/src/entities/notificacoes/__tests__/index.spec.ts @@ -0,0 +1,50 @@ +import { Chance } from 'chance' +import { Notificacoes } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse from './create-response' +import getResponse from './get-response' + +const chance = Chance() + +describe('Notificações entity', () => { + let repository: InMemoryBlingRepository + let entity: Notificacoes + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Notificacoes(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'notificacoes', + params: { + periodo: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should read successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idNotificacao = chance.word() + repository.setResponse(createResponse) + + const response = await entity.read({ idNotificacao }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `notificacoes/${idNotificacao}/confirmar-leitura`, + body: {} + }) + expect(response).toBe(createResponse) + }) +}) diff --git a/src/entities/notificacoes/index.ts b/src/entities/notificacoes/index.ts new file mode 100644 index 0000000..2bc1b19 --- /dev/null +++ b/src/entities/notificacoes/index.ts @@ -0,0 +1,46 @@ +import { Entity } from '../@shared/entity' +import { ICreateParams, ICreateResponse } from './interfaces/create.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com notificações. + * + * @see https://developer.bling.com.br/referencia#/Notifica%C3%A7%C3%B5es + */ +export class Notificacoes extends Entity { + /** + * Obtém todas as notificações de uma empresa em um período. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notifica%C3%A7%C3%B5es/get_notificacoes + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'notificacoes', + params: { + periodo: params?.periodo + } + }) + } + + /** + * Marca notificação como lida. + * + * @param {ICreateParams} params Os parâmetros para leitura. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Notifica%C3%A7%C3%B5es/post_notificacoes__idNotificacao__confirmar_leitura + */ + public async read(params: ICreateParams): Promise { + return await this.repository.store({ + endpoint: `notificacoes/${params.idNotificacao}/confirmar-leitura`, + body: {} + }) + } +} diff --git a/src/entities/notificacoes/interfaces/create.interface.ts b/src/entities/notificacoes/interfaces/create.interface.ts new file mode 100644 index 0000000..5a68c66 --- /dev/null +++ b/src/entities/notificacoes/interfaces/create.interface.ts @@ -0,0 +1,34 @@ +import ICRT from 'src/entities/@shared/types/crt.type' +import IUF from 'src/entities/@shared/types/uf.type' + +export interface ICreateParams { + /** + * ULID da notificação. + */ + idNotificacao: string +} + +export interface ICreateResponse { + data: { + id?: string + emitente: string + modulo: string + descricao: string + titulo: string + fonte?: string + linkAjuda?: string + dataCriacao?: string + dataEnvio: string + dataVigencia?: string + dataAcao?: string + dataLeitura?: string + dataAlerta?: string + dataPerigo?: string + enquadramentos?: { + tamanhoEmpresa?: string[] + idMunicipio?: string[] + uf?: IUF[] + crt?: ICRT[] + }[] + }[] +} diff --git a/src/entities/notificacoes/interfaces/get.interface.ts b/src/entities/notificacoes/interfaces/get.interface.ts new file mode 100644 index 0000000..0b19722 --- /dev/null +++ b/src/entities/notificacoes/interfaces/get.interface.ts @@ -0,0 +1,34 @@ +import ICRT from 'src/entities/@shared/types/crt.type' +import IUF from 'src/entities/@shared/types/uf.type' + +export interface IGetParams { + /** + * Apenas ano ou ano e mês em que a empresa foi notificada. Caso não informado, será utilizado o ano atual. + */ + periodo?: string +} + +export interface IGetResponse { + data: { + id?: string + emitente: string + modulo: string + descricao: string + titulo: string + fonte?: string + linkAjuda?: string + dataCriacao?: string + dataEnvio: string + dataVigencia?: string + dataAcao?: string + dataLeitura?: string + dataAlerta?: string + dataPerigo?: string + enquadramentos?: { + tamanhoEmpresa?: string[] + idMunicipio?: string[] + uf?: IUF[] + crt?: ICRT[] + }[] + }[] +} diff --git a/src/entities/orders.ts b/src/entities/orders.ts deleted file mode 100644 index fad627b..0000000 --- a/src/entities/orders.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IOrder { - data?: Date - data_saida?: Date - data_prevista?: Date - numero?: string - numero_loja?: string - loja?: number - nat_operacao?: string - vendedor?: string - cliente: { - id?: number - nome: string - tipoPessoa?: 'F' | 'J' - cpf_cnpj?: string - ie?: string - rg?: string - contribuinte?: '1' | '2' | '9' - endereco?: string - numero?: string - complemento?: string - bairro?: string - cep?: string - cidade?: string - uf?: string - fone?: string - celular?: string - email?: string - } - transporte?: { - transportadora?: string - tipo_frete?: 'R' | 'D' | 'T' | '3' | '4' | 'S' - servico_correios?: string - codigo_cotacao?: string - peso_bruto?: number - qtde_volumes?: number - dados_etiqueta?: { - nome?: string - endereco?: string - numero?: string - complemento?: string - municipio?: string - uf?: string - cep?: string - bairro?: string - } - volumes?: { - volume: { - servico: string - codigoRastreamento?: string - } - }[] - } - itens?: { - item?: { - codigo?: string - descricao: string - un?: 'pc' | 'un' | 'cx' - qtde: number - vlr_unit: number - vlr_desconto?: number - } - }[] - idFormaPagamento?: number - parcelas?: { - parcela?: { - dias?: number - data?: Date - vlr: number - obs?: string - forma_pagamento?: { - id?: number - } - } - }[] - vlr_frete?: number - vlr_desconto?: string - obs?: string - obs_internas?: string - numeroOrdemCompra?: string - outrasDespesas?: number - intermediador?: { - nomeUsuario?: string - cnpjInstituicaoPagamento?: string - } -} - -export interface IOrderInfos { - historico?: 'true' | 'false' -} - -export interface IOrderFilters { - dataEmissao?: string - dataAlteracao?: string - dataPrevista?: string - idSituacao?: string - idContato?: string -} - -export interface IOrderResponse { - desconto: string - observacoes: string - observacaointerna: string - data: string - numero: string - numeroOrdemCompra: string - vendedor: string - valorfrete: string - outrasdespesas: string - totalprodutos: string - totalvenda: string - situacao: string - dataSaida: string - loja: string - numeroPedidoLoja: string - tipoIntegracao: string - cliente: { - id: string - nome: string - cnpj: string - ie?: string - rg?: string - endereco: string - numero: string - complemento: string - cidade: string - bairro: string - cep: string - uf: string - email: string - celular?: string - fone: string - } - nota: { - serie: string - numero: string - dataEmissao: string - situacao: string - valorNota: string - chaveAcesso: string - } - transporte: { - transportadora?: string - cnpj?: string - tipo_frete?: string - qtde_volumes?: string - enderecoEntrega: { - nome: string - endereco: string - numero: string - complemento: string - cidade: string - bairro: string - cep: string - uf: string - } - volumes?: { - volume: { - id: string - idServico: string - idOrigem: string - servico: string - codigoServico: string - codigoRastreamento: string - valorFretePrevisto: string - remessa?: string - dataSaida: string - prazoEntregaPrevisto: string - valorDeclarado: string - avisoRecebimento: boolean - maoPropria: boolean - dimensoes: { - peso: string - altura: string - largura: string - comprimento: string - diametro: string - } - urlRastreamento: string - } - }[] - servico_correios?: string - } - itens: { - item: { - codigo: string - descricao: string - quantidade: number - valorunidade: number - precocusto: number - descontoItem: number - un: string - pesoBruto?: number - largura?: number - altura?: number - profundidade?: number - descricaoDetalhada?: string - unidadeMedida: string - gtin?: string - } - }[] - parcelas: { - parcela: { - idLancamento: string - valor: string - dataVencimento: string - obs?: string - destino: string - forma_pagamento: { - id: string - descricao: string - codigoFiscal: string - } - } - }[] - codigosRastreamento?: { - codigoRastreamento?: string - } -} - -export default function Orders (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'pedido', - pluralName: 'pedidos' - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy().findBy, - create: new Create().create, - update: new Update().update - }) -} diff --git a/src/entities/paymentMethods.ts b/src/entities/paymentMethods.ts deleted file mode 100644 index 0207cc8..0000000 --- a/src/entities/paymentMethods.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' -import Delete from '../core/functions/delete' - -type IPaymentMethodCodigoFiscal = - | '1' - | '2' - | '3' - | '4' - | '5' - | '10' - | '11' - | '12' - | '13' - | '14' - | '15' - | '90' - | '99' - -type IPaymentMethodSituacao = '0' | '1' - -type IPaymentMethodPadrao = '0' | '1' - -type IPaymentMethodDestino = '1' | '2' | '3' - -type IPaymentMethodBandeira = - | '1' - | '2' - | '3' - | '4' - | '5' - | '6' - | '7' - | '8' - | '9' - | '99' - -type IPaymentMethodTipoIntegracao = '1' | '2' - -type IPaymentMethodAutoLiquidacao = '1' | '2' - -export interface IPaymentMethod { - descricao: string - codigofiscal?: IPaymentMethodCodigoFiscal - condicao?: string - destino?: IPaymentMethodDestino - padrao?: IPaymentMethodPadrao - situacao?: IPaymentMethodSituacao - dadoscartao?: { - bandeira?: IPaymentMethodBandeira - tipointegracao?: IPaymentMethodTipoIntegracao - cnpjcredenciadora?: string - autoliquidacao?: IPaymentMethodAutoLiquidacao - } - dadostaxas?: { - valoraliquota?: string - valorfixo?: string - prazo?: string - } -} - -export interface IPaymentMethodFilters { - descricao?: string - codigoFiscal?: number - situacao?: IPaymentMethodSituacao -} - -export type IPaymentMethodInfos = Record - -export interface IPaymentMethodCreateResponse { - id: string - descricao: string - idDestino: number - tband: number - tpIntegra: number - cnpjCredenciadora?: string - autoLiquidacao: number -} - -export interface IPaymentMethodDeleteResponse { - id: string - descricao: string - mensagem: string -} - -export interface IPaymentMethodResponse { - id: string - descricao: string - codigoFiscal: IPaymentMethodCodigoFiscal - padrao: IPaymentMethodPadrao - situacao: IPaymentMethodSituacao - fixa: string -} - -export default function PaymentMethods (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'formapagamento', - pluralName: 'formaspagamento' - } - - // @TODO: deal with "IPaymentMethodCreateResponse" content properly - - return Object.assign(config, { - all: new All< - IPaymentMethodResponse, - IPaymentMethodFilters, - IPaymentMethodInfos - >().all, - find: new Find().find, - findBy: new FindBy< - IPaymentMethodResponse, - IPaymentMethodFilters, - IPaymentMethodInfos - >().findBy, - create: new Create().create, - update: new Update().update, - delete: new Delete().delete - }) -} diff --git a/src/entities/pedidosCompras/__tests__/change-situation-response.ts b/src/entities/pedidosCompras/__tests__/change-situation-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/change-situation-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosCompras/__tests__/create-response.ts b/src/entities/pedidosCompras/__tests__/create-response.ts new file mode 100644 index 0000000..9e7cf0c --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/create-response.ts @@ -0,0 +1,64 @@ +export default { + data: { + id: 12345678, + numero: 123, + alertas: ['O número do seu pedido foi modificado para: 124'], + errosAnexo: ['Arquivo comprovante.png : [erro]'] + } +} + +export const createRequestBody = { + numero: 12, + data: '2020-08-24', + dataPrevista: '2020-08-30', + fornecedor: { + id: 12345678 + }, + situacao: { + valor: 0 as const + }, + ordemCompra: '351635', + observacoes: 'Observação sobre o pedido.', + observacoesInternas: 'Observação interna sobre o pedido.', + desconto: { + valor: 15.45, + unidade: 'REAL' as const + }, + categoria: { + id: 12345678 + }, + tributacao: { + totalICMS: 5.55 + }, + transporte: { + frete: 15.78, + transportador: 'Zé Transportes', + fretePorConta: 0 as const, + pesoBruto: 15.78, + volumes: 11 + }, + itens: [ + { + descricao: 'Copo do Bling', + codigoFornecedor: '46546546', + unidade: 'Un', + valor: 149.99, + quantidade: 12, + aliquotaIPI: 15.85, + descricaoDetalhada: 'Descrição do item do pedido.', + produto: { + id: 12345678 + } + } + ], + parcelas: [ + { + valor: 2090.66, + dataVencimento: '2020-09-23', + observacao: 'Observação da parcela.', + formaPagamento: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/pedidosCompras/__tests__/delete-response.ts b/src/entities/pedidosCompras/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosCompras/__tests__/find-response.ts b/src/entities/pedidosCompras/__tests__/find-response.ts new file mode 100644 index 0000000..3d56857 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/find-response.ts @@ -0,0 +1,62 @@ +export default { + data: { + id: 12345678, + numero: 12, + data: '2020-08-24', + dataPrevista: '2020-08-30', + totalProdutos: 2090.66, + total: 2090.66, + fornecedor: { + id: 12345678 + }, + situacao: { + valor: 0 as const + }, + ordemCompra: '351635', + observacoes: 'Observação sobre o pedido.', + observacoesInternas: 'Observação interna sobre o pedido.', + desconto: { + valor: 15.45, + unidade: 'REAL' as const + }, + categoria: { + id: 12345678 + }, + tributacao: { + totalICMS: 5.55, + totalIPI: 5.55 + }, + transporte: { + frete: 15.78, + transportador: 'Zé Transportes', + fretePorConta: 0, + pesoBruto: 15.78, + volumes: 11 + }, + itens: [ + { + descricao: 'Copo do Bling', + codigoFornecedor: '46546546', + unidade: 'Un', + valor: 149.99, + quantidade: 12, + aliquotaIPI: 15.85, + descricaoDetalhada: 'Descrição do item do pedido.', + produto: { + id: 12345678, + codigo: 'CODE123' + } + } + ], + parcelas: [ + { + valor: 2090.66, + dataVencimento: '2020-09-23', + observacao: 'Observação da parcela.', + formaPagamento: { + id: 12345678 + } + } + ] + } +} diff --git a/src/entities/pedidosCompras/__tests__/get-response.ts b/src/entities/pedidosCompras/__tests__/get-response.ts new file mode 100644 index 0000000..64f8f5d --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/get-response.ts @@ -0,0 +1,18 @@ +export default { + data: [ + { + id: 12345678, + numero: 12, + data: '2020-08-24', + dataPrevista: '2020-08-30', + totalProdutos: 2090.66, + total: 2090.66, + fornecedor: { + id: 12345678 + }, + situacao: { + valor: 0 as const + } + } + ] +} diff --git a/src/entities/pedidosCompras/__tests__/index.spec.ts b/src/entities/pedidosCompras/__tests__/index.spec.ts new file mode 100644 index 0000000..7cb3441 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/index.spec.ts @@ -0,0 +1,180 @@ +import { Chance } from 'chance' +import { PedidosCompras } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import changeSituationResponse from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import postAccountsResponse from './post-accounts-response' +import postStockResponse from './post-stock-response' +import reverseAccountsResponse from './reverse-accounts-response' +import reverseStockResponse from './reverse-stock-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Pedidos - Compras entity', () => { + let repository: InMemoryBlingRepository + let entity: PedidosCompras + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new PedidosCompras(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idPedidoCompra = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idPedidoCompra }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/compras', + id: String(idPedidoCompra) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/compras', + params: { + limite: undefined, + pagina: undefined, + idFornecedor: undefined, + valorSituacao: undefined, + idSituacao: undefined, + dataInicial: undefined, + dataFinal: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idPedidoCompra = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idPedidoCompra }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/compras', + id: String(idPedidoCompra) + }) + expect(response).toBe(findResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idPedidoCompra = chance.natural() + repository.setResponse(changeSituationResponse) + + const response = await entity.changeSituation({ idPedidoCompra, valor: 0 }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/compras', + id: `${idPedidoCompra}/situacoes`, + body: { valor: 0 } + }) + expect(response).toBe(changeSituationResponse) + }) + + it('should post accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoCompra = chance.natural() + repository.setResponse(postAccountsResponse) + + const response = await entity.postAccounts({ idPedidoCompra }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/compras/${idPedidoCompra}/lancar-contas`, + body: {} + }) + expect(response).toBe(postAccountsResponse) + }) + + it('should reverse accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoCompra = chance.natural() + repository.setResponse(reverseAccountsResponse) + + const response = await entity.reverseAccounts({ idPedidoCompra }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/compras/${idPedidoCompra}/estornar-contas`, + body: {} + }) + expect(response).toBe(reverseAccountsResponse) + }) + + it('should post stock successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoCompra = chance.natural() + repository.setResponse(postStockResponse) + + const response = await entity.postStock({ idPedidoCompra }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/compras/${idPedidoCompra}/lancar-estoque`, + body: {} + }) + expect(response).toBe(postStockResponse) + }) + + it('should reverse stock successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoCompra = chance.natural() + repository.setResponse(reverseStockResponse) + + const response = await entity.reverseStock({ idPedidoCompra }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/compras/${idPedidoCompra}/estornar-estoque`, + body: {} + }) + expect(response).toBe(reverseStockResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/compras', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idPedidoCompra = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idPedidoCompra, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/compras', + id: String(idPedidoCompra), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/pedidosCompras/__tests__/post-accounts-response.ts b/src/entities/pedidosCompras/__tests__/post-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/post-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosCompras/__tests__/post-stock-response.ts b/src/entities/pedidosCompras/__tests__/post-stock-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/post-stock-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosCompras/__tests__/reverse-accounts-response.ts b/src/entities/pedidosCompras/__tests__/reverse-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/reverse-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosCompras/__tests__/reverse-stock-response.ts b/src/entities/pedidosCompras/__tests__/reverse-stock-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/reverse-stock-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosCompras/__tests__/update-response.ts b/src/entities/pedidosCompras/__tests__/update-response.ts new file mode 100644 index 0000000..18856e1 --- /dev/null +++ b/src/entities/pedidosCompras/__tests__/update-response.ts @@ -0,0 +1,64 @@ +export default { + data: { + id: 12345678, + numero: 123, + alertas: ['O número do seu pedido foi modificado para: 124'], + errosAnexo: ['Arquivo comprovante.png : [erro]'] + } +} + +export const updateRequestBody = { + numero: 12, + data: '2020-08-24', + dataPrevista: '2020-08-30', + fornecedor: { + id: 12345678 + }, + situacao: { + valor: 0 as const + }, + ordemCompra: '351635', + observacoes: 'Observação sobre o pedido.', + observacoesInternas: 'Observação interna sobre o pedido.', + desconto: { + valor: 15.45, + unidade: 'REAL' as const + }, + categoria: { + id: 12345678 + }, + tributacao: { + totalICMS: 5.55 + }, + transporte: { + frete: 15.78, + transportador: 'Zé Transportes', + fretePorConta: 0 as const, + pesoBruto: 15.78, + volumes: 11 + }, + itens: [ + { + descricao: 'Copo do Bling', + codigoFornecedor: '46546546', + unidade: 'Un', + valor: 149.99, + quantidade: 12, + aliquotaIPI: 15.85, + descricaoDetalhada: 'Descrição do item do pedido.', + produto: { + id: 12345678 + } + } + ], + parcelas: [ + { + valor: 2090.66, + dataVencimento: '2020-09-23', + observacao: 'Observação da parcela.', + formaPagamento: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/pedidosCompras/index.ts b/src/entities/pedidosCompras/index.ts new file mode 100644 index 0000000..cca029e --- /dev/null +++ b/src/entities/pedidosCompras/index.ts @@ -0,0 +1,212 @@ +import { Entity } from '../@shared/entity' +import { + IChangeSituationBody, + IChangeSituationParams +} from './interfaces/change-situation.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IPostAccountsParams } from './interfaces/post-accounts.interface' +import { IPostStockParams } from './interfaces/post-stock.interface' +import { IReverseAccountsParams } from './interfaces/reverse-accounts.interface' +import { IReverseStockParams } from './interfaces/reverse-stock.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Pedidos - Compras. + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras + */ +export class PedidosCompras extends Entity { + /** + * Remove um pedido de compra. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/delete_pedidos_compras__idPedidoCompra_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'pedidos/compras', + id: String(params.idPedidoCompra) + }) + } + + /** + * Obtém pedidos de compras. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/get_pedidos_compras + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'pedidos/compras', + params: { + pagina: params?.pagina, + limite: params?.limite, + idFornecedor: params?.idFornecedor, + valorSituacao: params?.valorSituacao, + idSituacao: params?.idSituacao, + dataInicial: this.prepareStringOrDateParam(params?.dataInicial), + dataFinal: this.prepareStringOrDateParam(params?.dataFinal) + } + }) + } + + /** + * Obtém um pedido de compra. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/get_pedidos_compras__idPedidoCompra_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'pedidos/compras', + id: String(params.idPedidoCompra) + }) + } + + /** + * Altera a situação de um pedido de compra. + * + * @param {IChangeSituationParams & IChangeSituationBody} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/patch_pedidos_compras__idPedidoCompra__situacoes + */ + public async changeSituation( + params: IChangeSituationParams & IChangeSituationBody + ): Promise { + const { idPedidoCompra, ...body } = params + return await this.repository.update({ + endpoint: 'pedidos/compras', + id: `${idPedidoCompra}/situacoes`, + body + }) + } + + /** + * Cria um pedido de compra. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/post_pedidos_compras + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'pedidos/compras', + body + }) + } + + /** + * Lança as contas de um pedido de compra. + * + * @param {IPostAccountsParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/post_pedidos_compras__idPedidoCompra__lancar_contas + */ + public async postAccounts(params: IPostAccountsParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/compras/${params.idPedidoCompra}/lancar-contas`, + body: {} + }) + } + + /** + * Estorna as contas de um pedido de compra. + * + * @param {IReverseAccountsParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/post_pedidos_compras__idPedidoCompra__estornar_contas + */ + public async reverseAccounts(params: IReverseAccountsParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/compras/${params.idPedidoCompra}/estornar-contas`, + body: {} + }) + } + + /** + * Lança o estoque de um pedido de compra. + * + * @param {IPostStockParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/post_pedidos_compras__idPedidoCompra__lancar_estoque + */ + public async postStock(params: IPostStockParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/compras/${params.idPedidoCompra}/lancar-estoque`, + body: {} + }) + } + + /** + * Estorna o estoque de um pedido de compra. + * + * @param {IReverseStockParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/post_pedidos_compras__idPedidoCompra__estornar_estoque + */ + public async reverseStock(params: IReverseStockParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/compras/${params.idPedidoCompra}/estornar-estoque`, + body: {} + }) + } + + /** + * Altera um pedido de compra. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Compras/put_pedidos_compras__idPedidoCompra_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idPedidoCompra, ...body } = params + + return await this.repository.replace({ + endpoint: 'pedidos/compras', + id: String(idPedidoCompra), + body + }) + } +} diff --git a/src/entities/pedidosCompras/interfaces/change-situation.interface.ts b/src/entities/pedidosCompras/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..eb861f0 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/change-situation.interface.ts @@ -0,0 +1,12 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IChangeSituationParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} + +export interface IChangeSituationBody { + valor: ISituacao +} diff --git a/src/entities/pedidosCompras/interfaces/create.interface.ts b/src/entities/pedidosCompras/interfaces/create.interface.ts new file mode 100644 index 0000000..5d67f13 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/create.interface.ts @@ -0,0 +1,52 @@ +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import { IDescontoUnidade } from '../types/desconto-unidade.type' +import { ISituacao } from '../types/situacao.type' + +export interface ICreateBody { + numero?: number + data?: string + dataPrevista?: string + fornecedor: { id: number } + situacao?: { valor: ISituacao } + ordemCompra?: string + observacoes?: string + observacoesInternas?: string + desconto?: { + valor: number + unidade?: IDescontoUnidade + } + categoria?: { id: number } + tributacao?: { totalICMS?: number } + transporte?: { + frete?: number + transportador?: string + fretePorConta?: IFretePorConta + pesoBruto?: number + volumes?: number + } + itens: { + descricao: string + codigoFornecedor?: string + unidade?: string + valor: number + quantidade?: number + aliquotaIPI?: number + descricaoDetalhada?: string + produto?: { id: number } + }[] + parcelas?: { + valor: number + dataVencimento: string + observacao?: string + formaPagamento?: { id: number } + }[] +} + +export interface ICreateResponse { + data: { + id: number + numero: number + alertas?: string[] + errosAnexo?: string[] + } +} diff --git a/src/entities/pedidosCompras/interfaces/delete.interface.ts b/src/entities/pedidosCompras/interfaces/delete.interface.ts new file mode 100644 index 0000000..4957ba4 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} diff --git a/src/entities/pedidosCompras/interfaces/find.interface.ts b/src/entities/pedidosCompras/interfaces/find.interface.ts new file mode 100644 index 0000000..2da73d2 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/find.interface.ts @@ -0,0 +1,61 @@ +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import { IDescontoUnidade } from '../types/desconto-unidade.type' +import { ISituacao } from '../types/situacao.type' + +export interface IFindParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} + +export interface IFindResponse { + data: { + id?: number + numero?: number + data?: string + dataPrevista?: string + totalProdutos?: number + total?: number + fornecedor: { id: number } + situacao?: { valor: ISituacao } + ordemCompra: string + observacoes?: string + observacoesInternas?: string + desconto?: { + valor: number + unidade?: IDescontoUnidade + } + categoria?: { id: number } + tributacao?: { + totalICMS?: number + totalIPI?: number + } + transporte?: { + frete?: number + transportador?: string + fretePorConta?: IFretePorConta + pesoBruto?: number + volumes?: number + } + itens: { + descricao: string + codigoFornecedor?: string + unidade?: string + valor: number + quantidade?: number + aliquotaIPI?: number + descricaoDetalhada?: string + produto?: { + id: number + codigo?: string + } + }[] + parcelas?: { + valor: number + dataVencimento: string + observacao?: string + formaPagamento?: { id: number } + }[] + } +} diff --git a/src/entities/pedidosCompras/interfaces/get.interface.ts b/src/entities/pedidosCompras/interfaces/get.interface.ts new file mode 100644 index 0000000..b003be9 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/get.interface.ts @@ -0,0 +1,45 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * ID do contato do tipo fornecedor + */ + idFornecedor?: number + /** + * Valor da situação + */ + valorSituacao?: number + /** + * ID da situação + */ + idSituacao?: ISituacao + /** + * Data inicial do período da compra + */ + dataInicial?: Date | string + /** + * Data final do período da compra + */ + dataFinal?: Date | string +} + +export interface IGetResponse { + data: { + id?: number + numero?: number + data?: string + dataPrevista?: string + totalProdutos?: number + total?: number + fornecedor: { id: number } + situacao?: { valor: ISituacao } + }[] +} diff --git a/src/entities/pedidosCompras/interfaces/post-accounts.interface.ts b/src/entities/pedidosCompras/interfaces/post-accounts.interface.ts new file mode 100644 index 0000000..fb85cd2 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/post-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IPostAccountsParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} diff --git a/src/entities/pedidosCompras/interfaces/post-stock.interface.ts b/src/entities/pedidosCompras/interfaces/post-stock.interface.ts new file mode 100644 index 0000000..fc5be03 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/post-stock.interface.ts @@ -0,0 +1,6 @@ +export interface IPostStockParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} diff --git a/src/entities/pedidosCompras/interfaces/reverse-accounts.interface.ts b/src/entities/pedidosCompras/interfaces/reverse-accounts.interface.ts new file mode 100644 index 0000000..7d94cd0 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/reverse-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IReverseAccountsParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} diff --git a/src/entities/pedidosCompras/interfaces/reverse-stock.interface.ts b/src/entities/pedidosCompras/interfaces/reverse-stock.interface.ts new file mode 100644 index 0000000..83bbcb5 --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/reverse-stock.interface.ts @@ -0,0 +1,6 @@ +export interface IReverseStockParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} diff --git a/src/entities/pedidosCompras/interfaces/update.interface.ts b/src/entities/pedidosCompras/interfaces/update.interface.ts new file mode 100644 index 0000000..4d12ecd --- /dev/null +++ b/src/entities/pedidosCompras/interfaces/update.interface.ts @@ -0,0 +1,59 @@ +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import { IDescontoUnidade } from '../types/desconto-unidade.type' +import { ISituacao } from '../types/situacao.type' + +export interface IUpdateParams { + /** + * ID do pedido de compra + */ + idPedidoCompra: number +} + +export interface IUpdateBody { + numero?: number + data?: string + dataPrevista?: string + fornecedor: { id: number } + situacao?: { valor: ISituacao } + ordemCompra?: string + observacoes?: string + observacoesInternas?: string + desconto?: { + valor: number + unidade?: IDescontoUnidade + } + categoria?: { id: number } + tributacao?: { totalICMS?: number } + transporte?: { + frete?: number + transportador?: string + fretePorConta?: IFretePorConta + pesoBruto?: number + volumes?: number + } + itens: { + descricao: string + codigoFornecedor?: string + unidade?: string + valor: number + quantidade?: number + aliquotaIPI?: number + descricaoDetalhada?: string + produto?: { id: number } + }[] + parcelas?: { + valor: number + dataVencimento: string + observacao?: string + formaPagamento?: { id: number } + }[] +} + +export interface IUpdateResponse { + data: { + id: number + numero: number + alertas?: string[] + errosAnexo?: string[] + } +} diff --git a/src/entities/pedidosCompras/types/desconto-unidade.type.ts b/src/entities/pedidosCompras/types/desconto-unidade.type.ts new file mode 100644 index 0000000..c5e06cd --- /dev/null +++ b/src/entities/pedidosCompras/types/desconto-unidade.type.ts @@ -0,0 +1,4 @@ +/** + * Tipagem referente à unidade do desconto aplicado em um pedido de compra. + */ +export type IDescontoUnidade = 'REAL' | 'PERCENTUAL' diff --git a/src/entities/pedidosCompras/types/situacao.type.ts b/src/entities/pedidosCompras/types/situacao.type.ts new file mode 100644 index 0000000..4f33b00 --- /dev/null +++ b/src/entities/pedidosCompras/types/situacao.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente à situação de pedidos de compra. + * + * - `0`: Em aberto + * - `1`: Atendido + * - `2`: Cancelado + * - `3`: Em andamento + */ +export type ISituacao = 0 | 1 | 2 | 3 diff --git a/src/entities/pedidosVendas/__tests__/change-situation-response.ts b/src/entities/pedidosVendas/__tests__/change-situation-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/change-situation-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/create-response.ts b/src/entities/pedidosVendas/__tests__/create-response.ts new file mode 100644 index 0000000..8b3b58a --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/create-response.ts @@ -0,0 +1,122 @@ +export default { + data: { + id: 12345678, + alertas: [ + { + code: 49, + msg: 'Uma ou mais parcelas da venda possuem erros de validação', + element: 'parcelas', + namespace: 'VENDAS', + collection: [ + { + index: 1, + code: 12, + msg: 'Id da forma de pagamento inválido.', + element: 'formaPagamento', + namespace: 'VENDAS' + } + ] + } + ], + rastreamento: {} + } +} + +export const createRequestBody = { + numero: 123, + numeroLoja: 'Loja_123', + data: '2023-01-12', + dataSaida: '2023-01-12', + dataPrevista: '2023-01-12', + contato: { + id: 12345678, + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121' + }, + loja: { + id: 12345678 + }, + numeroPedidoCompra: '123', + outrasDespesas: 2, + observacoes: 'Observações do pedido.', + observacoesInternas: 'Observações internas do pedido.', + desconto: { + valor: 15.45, + unidade: 'REAL' as const + }, + categoria: { + id: 12345678 + }, + tributacao: { + totalICMS: 5.55, + totalIPI: 5.55 + }, + itens: [ + { + id: 12345678, + codigo: 'BLG-5', + unidade: 'UN', + quantidade: 1, + desconto: 2, + valor: 4.9, + aliquotaIPI: 0, + descricao: 'Produto do Bling', + descricaoDetalhada: 'Brinde', + produto: { + id: 12345678 + }, + comissao: { + base: 10, + aliquota: 2, + valor: 0.2 + } + } + ], + parcelas: [ + { + id: 12345678, + dataVencimento: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + quantidadeVolumes: 1, + pesoBruto: 0.5, + prazoEntrega: 10, + contato: { + id: 12345678, + nome: 'Transportador' + }, + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante', + nomePais: 'BRASIL' + }, + volumes: [ + { + id: 12345678, + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ] + }, + vendedor: { + id: 12345678 + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } +} diff --git a/src/entities/pedidosVendas/__tests__/delete-many-response.ts b/src/entities/pedidosVendas/__tests__/delete-many-response.ts new file mode 100644 index 0000000..0b0e482 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/delete-many-response.ts @@ -0,0 +1,5 @@ +export default { + data: { + alertas: ['A venda número 123 contém contas a receber...'] + } +} diff --git a/src/entities/pedidosVendas/__tests__/delete-response.ts b/src/entities/pedidosVendas/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/find-response.ts b/src/entities/pedidosVendas/__tests__/find-response.ts new file mode 100644 index 0000000..3e28409 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/find-response.ts @@ -0,0 +1,111 @@ +export default { + data: { + id: 12345678, + numero: 123, + numeroLoja: 'Loja_123', + data: '2023-01-12', + dataSaida: '2023-01-12', + dataPrevista: '2023-01-12', + totalProdutos: 10, + total: 12, + contato: { + id: 12345678, + nome: 'Contato do Bling', + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121' + }, + situacao: { + id: 12345678, + valor: 1 as const + }, + loja: { + id: 12345678 + }, + numeroPedidoCompra: '123', + outrasDespesas: 2, + observacoes: 'Observações do pedido.', + observacoesInternas: 'Observações internas do pedido.', + desconto: { + valor: 15.45, + unidade: 'REAL' + }, + categoria: { + id: 12345678 + }, + notaFiscal: { + id: 12345678 + }, + tributacao: { + totalICMS: 5.55, + totalIPI: 5.55 + }, + itens: [ + { + id: 12345678, + codigo: 'BLG-5', + unidade: 'UN', + quantidade: 1, + desconto: 2, + valor: 4.9, + aliquotaIPI: 0, + descricao: 'Produto do Bling', + descricaoDetalhada: 'Brinde', + produto: { + id: 12345678 + }, + comissao: { + base: 10, + aliquota: 2, + valor: 0.2 + } + } + ], + parcelas: [ + { + id: 12345678, + dataVencimento: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + quantidadeVolumes: 1, + pesoBruto: 0.5, + prazoEntrega: 10, + contato: { + id: 12345678, + nome: 'Transportador' + }, + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante', + nomePais: 'BRASIL' + }, + volumes: [ + { + id: 12345678, + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ] + }, + vendedor: { + id: 12345678 + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } + } +} diff --git a/src/entities/pedidosVendas/__tests__/get-response.ts b/src/entities/pedidosVendas/__tests__/get-response.ts new file mode 100644 index 0000000..c443d5f --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/get-response.ts @@ -0,0 +1,27 @@ +export default { + data: [ + { + id: 12345678, + numero: 123, + numeroLoja: 'Loja_123', + data: '2023-01-12', + dataSaida: '2023-01-12', + dataPrevista: '2023-01-12', + totalProdutos: 10, + total: 12, + contato: { + id: 12345678, + nome: 'Contato do Bling', + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121' + }, + situacao: { + id: 12345678, + valor: 1 as const + }, + loja: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/pedidosVendas/__tests__/index.spec.ts b/src/entities/pedidosVendas/__tests__/index.spec.ts new file mode 100644 index 0000000..ed37a20 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/index.spec.ts @@ -0,0 +1,227 @@ +import { Chance } from 'chance' +import { PedidosVendas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import changeSituationResponse from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import deleteManyResponse from './delete-many-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import postAccountsResponse from './post-accounts-response' +import postStockResponse from './post-stock-response' +import postStockToDepositResponse from './post-stock-to-deposit-response' +import reverseAccountsResponse from './reverse-accounts-response' +import reverseStockResponse from './reverse-stock-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Pedidos - Vendas entity', () => { + let repository: InMemoryBlingRepository + let entity: PedidosVendas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new PedidosVendas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete many successfully', async () => { + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteManyResponse) + const idsPedidosVendas: number[] = [] + for (let i = 0; i < chance.natural({ min: 1, max: 5 }); i++) { + idsPedidosVendas.push(chance.natural()) + } + + const response = await entity.deleteMany({ idsPedidosVendas }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + id: '', + params: { idsPedidosVendas } + }) + expect(response).toBe(deleteManyResponse) + }) + + it('should delete successfully', async () => { + const idPedidoVenda = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idPedidoVenda }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + id: String(idPedidoVenda) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + params: { + limite: undefined, + pagina: undefined, + idContato: undefined, + idsSituacoes: undefined, + dataInicial: undefined, + dataFinal: undefined, + dataAlteracaoInicial: undefined, + dataAlteracaoFinal: undefined, + dataPrevistaInicial: undefined, + dataPrevistaFinal: undefined, + numero: undefined, + idLoja: undefined, + idVendedor: undefined, + idControleCaixa: undefined, + numerosLojas: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idPedidoVenda = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idPedidoVenda }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + id: String(idPedidoVenda) + }) + expect(response).toBe(findResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idPedidoVenda = chance.natural() + const idSituacao = 0 + repository.setResponse(changeSituationResponse) + + const response = await entity.changeSituation({ idPedidoVenda, idSituacao }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + id: `${idPedidoVenda}/situacoes/${idSituacao}`, + body: {} + }) + expect(response).toBe(changeSituationResponse) + }) + + it('should post stock to deposit successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoVenda = chance.natural() + const idDeposito = chance.natural() + repository.setResponse(postStockToDepositResponse) + + const response = await entity.postStockToDeposit({ + idPedidoVenda, + idDeposito + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/vendas/${idPedidoVenda}/lancar-estoque/${idDeposito}`, + body: {} + }) + expect(response).toBe(postStockToDepositResponse) + }) + + it('should post stock successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoVenda = chance.natural() + repository.setResponse(postStockResponse) + + const response = await entity.postStock({ idPedidoVenda }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/vendas/${idPedidoVenda}/lancar-estoque`, + body: {} + }) + expect(response).toBe(postStockResponse) + }) + + it('should reverse stock successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoVenda = chance.natural() + repository.setResponse(reverseStockResponse) + + const response = await entity.reverseStock({ idPedidoVenda }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/vendas/${idPedidoVenda}/estornar-estoque`, + body: {} + }) + expect(response).toBe(reverseStockResponse) + }) + + it('should post accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoVenda = chance.natural() + repository.setResponse(postAccountsResponse) + + const response = await entity.postAccounts({ idPedidoVenda }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/vendas/${idPedidoVenda}/lancar-contas`, + body: {} + }) + expect(response).toBe(postAccountsResponse) + }) + + it('should reverse accounts successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const idPedidoVenda = chance.natural() + repository.setResponse(reverseAccountsResponse) + + const response = await entity.reverseAccounts({ idPedidoVenda }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `pedidos/vendas/${idPedidoVenda}/estornar-contas`, + body: {} + }) + expect(response).toBe(reverseAccountsResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idPedidoVenda = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idPedidoVenda, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'pedidos/vendas', + id: String(idPedidoVenda), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/pedidosVendas/__tests__/post-accounts-response.ts b/src/entities/pedidosVendas/__tests__/post-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/post-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/post-stock-response.ts b/src/entities/pedidosVendas/__tests__/post-stock-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/post-stock-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/post-stock-to-deposit-response.ts b/src/entities/pedidosVendas/__tests__/post-stock-to-deposit-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/post-stock-to-deposit-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/reverse-accounts-response.ts b/src/entities/pedidosVendas/__tests__/reverse-accounts-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/reverse-accounts-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/reverse-stock-response.ts b/src/entities/pedidosVendas/__tests__/reverse-stock-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/reverse-stock-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/pedidosVendas/__tests__/update-response.ts b/src/entities/pedidosVendas/__tests__/update-response.ts new file mode 100644 index 0000000..029115b --- /dev/null +++ b/src/entities/pedidosVendas/__tests__/update-response.ts @@ -0,0 +1,122 @@ +export default { + data: { + id: 12345678, + alertas: [ + { + code: 49, + msg: 'Uma ou mais parcelas da venda possuem erros de validação', + element: 'parcelas', + namespace: 'VENDAS', + collection: [ + { + index: 1, + code: 12, + msg: 'Id da forma de pagamento inválido.', + element: 'formaPagamento', + namespace: 'VENDAS' + } + ] + } + ], + rastreamento: {} + } +} + +export const updateRequestBody = { + numero: 123, + numeroLoja: 'Loja_123', + data: '2023-01-12', + dataSaida: '2023-01-12', + dataPrevista: '2023-01-12', + contato: { + id: 12345678, + tipoPessoa: 'J' as const, + numeroDocumento: '30188025000121' + }, + loja: { + id: 12345678 + }, + numeroPedidoCompra: '123', + outrasDespesas: 2, + observacoes: 'Observações do pedido.', + observacoesInternas: 'Observações internas do pedido.', + desconto: { + valor: 15.45, + unidade: 'REAL' as const + }, + categoria: { + id: 12345678 + }, + tributacao: { + totalICMS: 5.55, + totalIPI: 5.55 + }, + itens: [ + { + id: 12345678, + codigo: 'BLG-5', + unidade: 'UN', + quantidade: 1, + desconto: 2, + valor: 4.9, + aliquotaIPI: 0, + descricao: 'Produto do Bling', + descricaoDetalhada: 'Brinde', + produto: { + id: 12345678 + }, + comissao: { + base: 10, + aliquota: 2, + valor: 0.2 + } + } + ], + parcelas: [ + { + id: 12345678, + dataVencimento: '2023-01-12', + valor: 123.45, + observacoes: 'Observação da parcela', + formaPagamento: { + id: 12345678 + } + } + ], + transporte: { + fretePorConta: 0 as const, + frete: 20, + quantidadeVolumes: 1, + pesoBruto: 0.5, + prazoEntrega: 10, + contato: { + id: 12345678, + nome: 'Transportador' + }, + etiqueta: { + nome: 'Transportador', + endereco: 'Olavo Bilac', + numero: '914', + complemento: 'Sala 101', + municipio: 'Bento Gonçalves', + uf: 'RS' as const, + cep: '95702-000', + bairro: 'Imigrante', + nomePais: 'BRASIL' + }, + volumes: [ + { + id: 12345678, + servico: 'ALIAS_123', + codigoRastreamento: 'COD123BR' + } + ] + }, + vendedor: { + id: 12345678 + }, + intermediador: { + cnpj: '13921649000197', + nomeUsuario: 'usuario' + } +} diff --git a/src/entities/pedidosVendas/index.ts b/src/entities/pedidosVendas/index.ts new file mode 100644 index 0000000..b660550 --- /dev/null +++ b/src/entities/pedidosVendas/index.ts @@ -0,0 +1,266 @@ +import { Entity } from '../@shared/entity' +import { IChangeSituationParams } from './interfaces/change-situation.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { + IDeleteManyParams, + IDeleteManyResponse +} from './interfaces/delete-many.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { IPostAccountsParams } from './interfaces/post-accounts.interface' +import { IPostStockToDepositParams } from './interfaces/post-stock-to-deposit.interface' +import { IPostStockParams } from './interfaces/post-stock.interface' +import { IReverseAccountsParams } from './interfaces/reverse-accounts.interface' +import { IReverseStockParams } from './interfaces/reverse-stock.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Pedidos - Vendas. + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas + */ +export class PedidosVendas extends Entity { + /** + * Remove pedidos de vendas. + * + * @param {IDeleteManyParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/delete_pedidos_vendas + */ + public async deleteMany( + params: IDeleteManyParams + ): Promise { + return await this.repository.destroy({ + endpoint: 'pedidos/vendas', + id: '', + params: { idsPedidosVendas: params.idsPedidosVendas } + }) + } + + /** + * Remove um pedido de venda. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/delete_pedidos_vendas__idPedidoVenda_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'pedidos/vendas', + id: String(params.idPedidoVenda) + }) + } + + /** + * Obtém pedidos de vendas. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/get_pedidos_vendas + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'pedidos/vendas', + params: { + pagina: params?.pagina, + limite: params?.limite, + idContato: params?.idContato, + idsSituacoes: params?.idsSituacoes, + dataInicial: this.prepareStringOrDateParam(params?.dataInicial), + dataFinal: this.prepareStringOrDateParam(params?.dataFinal), + dataAlteracaoInicial: this.prepareStringOrDateParam( + params?.dataAlteracaoInicial + ), + dataAlteracaoFinal: this.prepareStringOrDateParam( + params?.dataAlteracaoFinal + ), + dataPrevistaInicial: this.prepareStringOrDateParam( + params?.dataPrevistaInicial + ), + dataPrevistaFinal: this.prepareStringOrDateParam( + params?.dataPrevistaFinal + ), + numero: params?.numero, + idLoja: params?.idLoja, + idVendedor: params?.idVendedor, + idControleCaixa: params?.idControleCaixa, + numerosLojas: params?.numerosLojas + } + }) + } + + /** + * Obtém um pedido de venda. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/get_pedidos_vendas__idPedidoVenda_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'pedidos/vendas', + id: String(params.idPedidoVenda) + }) + } + + /** + * Altera a situação de um pedido de venda. + * + * @param {IChangeSituationParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/patch_pedidos_vendas__idPedidoVenda__situacoes__idSituacao_ + */ + public async changeSituation(params: IChangeSituationParams): Promise { + return await this.repository.update({ + endpoint: 'pedidos/vendas', + id: `${params.idPedidoVenda}/situacoes/${params.idSituacao}`, + body: {} + }) + } + + /** + * Cria um pedido de venda. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/post_pedidos_vendas + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'pedidos/vendas', + body + }) + } + + /** + * Lança o estoque de um pedido de venda especificando o depósito. + * + * @param {IPostStockToDepositParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/post_pedidos_vendas__idPedidoVenda__lancar_estoque__idDeposito_ + */ + public async postStockToDeposit( + params: IPostStockToDepositParams + ): Promise { + return await this.repository.store({ + endpoint: `pedidos/vendas/${params.idPedidoVenda}/lancar-estoque/${params.idDeposito}`, + body: {} + }) + } + + /** + * Lança o estoque de um pedido de venda no depósito padrão. + * + * @param {IPostStockParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/post_pedidos_vendas__idPedidoVenda__lancar_estoque + */ + public async postStock(params: IPostStockParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/vendas/${params.idPedidoVenda}/lancar-estoque`, + body: {} + }) + } + + /** + * Estorna o estoque de um pedido de venda. + * + * @param {IReverseStockParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/post_pedidos_vendas__idPedidoVenda__estornar_estoque + */ + public async reverseStock(params: IReverseStockParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/vendas/${params.idPedidoVenda}/estornar-estoque`, + body: {} + }) + } + + /** + * Lança as contas de um pedido de venda. + * + * @param {IPostAccountsParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/post_pedidos_vendas__idPedidoVenda__lancar_contas + */ + public async postAccounts(params: IPostAccountsParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/vendas/${params.idPedidoVenda}/lancar-contas`, + body: {} + }) + } + + /** + * Estorna as contas de um pedido de venda. + * + * @param {IReverseAccountsParams} params O conteúdo para o lançamento. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/post_pedidos_vendas__idPedidoVenda__estornar_contas + */ + public async reverseAccounts(params: IReverseAccountsParams): Promise { + return await this.repository.store({ + endpoint: `pedidos/vendas/${params.idPedidoVenda}/estornar-contas`, + body: {} + }) + } + + /** + * Altera um pedido de venda. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Pedidos%20-%20Vendas/put_pedidos_vendas__idPedidoVenda_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idPedidoVenda, ...body } = params + + return await this.repository.replace({ + endpoint: 'pedidos/vendas', + id: String(idPedidoVenda), + body + }) + } +} diff --git a/src/entities/pedidosVendas/interfaces/change-situation.interface.ts b/src/entities/pedidosVendas/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..4449929 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/change-situation.interface.ts @@ -0,0 +1,10 @@ +export interface IChangeSituationParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number + /** + * ID da situação do pedido de venda + */ + idSituacao: number +} diff --git a/src/entities/pedidosVendas/interfaces/create.interface.ts b/src/entities/pedidosVendas/interfaces/create.interface.ts new file mode 100644 index 0000000..ef9e31f --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/create.interface.ts @@ -0,0 +1,96 @@ +import { IDefaultErrorFieldsResponse } from 'src/entities/@shared/interfaces/error.interface' +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IDescontoUnidade } from '../types/desconto-unidade.type' + +export interface ICreateBody { + numero?: number + numeroLoja?: string + data: string + dataSaida: string + dataPrevista: string + contato: { + id: number + tipoPessoa?: ITipoPessoa + numeroDocumento?: string + } + loja?: { id: number } + numeroPedidoCompra?: string + outrasDespesas?: number + observacoes?: string + observacoesInternas?: string + desconto?: { + valor: number + unidade?: IDescontoUnidade + } + categoria?: { id: number } + tributacao?: { + totalICMS?: number + totalIPI?: number + } + itens: { + id: number + codigo?: string + unidade?: string + quantidade: number + desconto?: number + valor: number + aliquotaIPI?: number + descricao: string + descricaoDetalhada?: string + produto?: { id: number } + comissao?: { + base?: number + aliquota?: number + valor?: number + } + }[] + parcelas: { + id: number + dataVencimento: string + valor: number + observacoes?: string + formaPagamento: { id: number } + }[] + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + quantidadeVolumes?: number + pesoBruto?: number + prazoEntrega?: number + contato?: { + id?: number + nome: string + } + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + nomePais?: string + } + volumes?: { + id: number + servico: string + codigoRastreamento?: string + }[] + } + vendedor?: { id: number } + intermediador?: { + cnpj?: string + nomeUsuario?: string + } +} + +export interface ICreateResponse { + data: { + id: number + alertas?: IDefaultErrorFieldsResponse[] + rastreamento?: { description?: string } + } +} diff --git a/src/entities/pedidosVendas/interfaces/delete-many.interface.ts b/src/entities/pedidosVendas/interfaces/delete-many.interface.ts new file mode 100644 index 0000000..c3cdf68 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/delete-many.interface.ts @@ -0,0 +1,12 @@ +export interface IDeleteManyParams { + /** + * IDs dos pedidos de vendas + */ + idsPedidosVendas: number[] +} + +export interface IDeleteManyResponse { + data: { + alertas: string[] + } +} diff --git a/src/entities/pedidosVendas/interfaces/delete.interface.ts b/src/entities/pedidosVendas/interfaces/delete.interface.ts new file mode 100644 index 0000000..692d7ea --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} diff --git a/src/entities/pedidosVendas/interfaces/find.interface.ts b/src/entities/pedidosVendas/interfaces/find.interface.ts new file mode 100644 index 0000000..fde5baa --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/find.interface.ts @@ -0,0 +1,106 @@ +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IDescontoUnidade } from '../types/desconto-unidade.type' + +export interface IFindParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} + +export interface IFindResponse { + data: { + id?: number + numero?: number + numeroLoja?: string + data: string + dataSaida: string + dataPrevista: string + totalProdutos?: number + total?: number + contato: { + id: number + nome: string + tipoPessoa?: ITipoPessoa + numeroDocumento?: string + } + situacao?: { + id: number + valor: number + } + loja?: { id: number } + numeroPedidoCompra?: string + outrasDespesas?: number + observacoes?: string + observacoesInternas?: string + desconto?: { + valor: number + unidade?: IDescontoUnidade + } + categoria?: { id: number } + notaFiscal?: { id: number } + tributacao?: { + totalICMS?: number + totalIPI?: number + } + itens: { + id: number + codigo?: string + unidade?: string + quantidade: number + desconto?: number + valor: number + aliquotaIPI?: number + descricao: string + descricaoDetalhada?: string + produto?: { id: number } + comissao?: { + base?: number + aliquota?: number + valor?: number + } + }[] + parcelas: { + id: number + dataVencimento: string + valor: number + observacoes?: string + formaPagamento: { id: number } + }[] + + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + quantidadeVolumes?: number + pesoBruto?: number + prazoEntrega?: number + contato?: { + id?: number + nome: string + } + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + nomePais?: string + } + volumes: { + id: number + servico: string + codigoRastreamento?: string + }[] + } + vendedor?: { id: number } + intermediador?: { + cnpj?: string + nomeUsuario?: string + } + } +} diff --git a/src/entities/pedidosVendas/interfaces/get.interface.ts b/src/entities/pedidosVendas/interfaces/get.interface.ts new file mode 100644 index 0000000..72999c9 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/get.interface.ts @@ -0,0 +1,92 @@ +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * ID do contato + */ + idContato?: number + /** + * Conjunto de situações + */ + idsSituacoes?: number[] + /** + * Data inicial + */ + dataInicial?: Date | string + /** + * Data final + */ + dataFinal?: Date | string + /** + * Data inicial da alteração + */ + dataAlteracaoInicial?: Date | string + /** + * Data final da alteração + */ + dataAlteracaoFinal?: Date | string + /** + * Data inicial prevista + */ + dataPrevistaInicial?: Date | string + /** + * Data final prevista + */ + dataPrevistaFinal?: Date | string + /** + * Número do pedido de venda + */ + numero?: number + /** + * ID da loja + */ + idLoja?: number + /** + * ID do vendedor + */ + idVendedor?: number + /** + * ID do controle de caixa + */ + idControleCaixa?: number + /** + * Conjunto de números de pedidos nas lojas + */ + numerosLojas?: string[] +} + +export interface IGetResponse { + data: [ + { + id?: number + numero?: number + numeroLoja?: string + data: string + dataSaida: string + dataPrevista: string + totalProdutos?: number + total?: number + contato: { + id: number + nome: string + tipoPessoa?: ITipoPessoa + numeroDocumento?: string + } + situacao?: { + id: number + valor: number + } + loja?: { + id: number + } + } + ] +} diff --git a/src/entities/pedidosVendas/interfaces/post-accounts.interface.ts b/src/entities/pedidosVendas/interfaces/post-accounts.interface.ts new file mode 100644 index 0000000..a999968 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/post-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IPostAccountsParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} diff --git a/src/entities/pedidosVendas/interfaces/post-stock-to-deposit.interface.ts b/src/entities/pedidosVendas/interfaces/post-stock-to-deposit.interface.ts new file mode 100644 index 0000000..69905ed --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/post-stock-to-deposit.interface.ts @@ -0,0 +1,10 @@ +export interface IPostStockToDepositParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number + /** + * ID do depósito de estoque + */ + idDeposito: number +} diff --git a/src/entities/pedidosVendas/interfaces/post-stock.interface.ts b/src/entities/pedidosVendas/interfaces/post-stock.interface.ts new file mode 100644 index 0000000..46bebaa --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/post-stock.interface.ts @@ -0,0 +1,6 @@ +export interface IPostStockParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} diff --git a/src/entities/pedidosVendas/interfaces/reverse-accounts.interface.ts b/src/entities/pedidosVendas/interfaces/reverse-accounts.interface.ts new file mode 100644 index 0000000..3149a32 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/reverse-accounts.interface.ts @@ -0,0 +1,6 @@ +export interface IReverseAccountsParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} diff --git a/src/entities/pedidosVendas/interfaces/reverse-stock.interface.ts b/src/entities/pedidosVendas/interfaces/reverse-stock.interface.ts new file mode 100644 index 0000000..b73c619 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/reverse-stock.interface.ts @@ -0,0 +1,6 @@ +export interface IReverseStockParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} diff --git a/src/entities/pedidosVendas/interfaces/update.interface.ts b/src/entities/pedidosVendas/interfaces/update.interface.ts new file mode 100644 index 0000000..f8e9fc8 --- /dev/null +++ b/src/entities/pedidosVendas/interfaces/update.interface.ts @@ -0,0 +1,103 @@ +import { IDefaultErrorFieldsResponse } from 'src/entities/@shared/interfaces/error.interface' +import IFretePorConta from 'src/entities/@shared/types/frete-por-conta.type' +import ITipoPessoa from 'src/entities/@shared/types/tipoPessoa.type' +import IUF from 'src/entities/@shared/types/uf.type' +import { IDescontoUnidade } from '../types/desconto-unidade.type' + +export interface IUpdateParams { + /** + * ID do pedido de venda + */ + idPedidoVenda: number +} + +export interface IUpdateBody { + numero?: number + numeroLoja?: string + data: string + dataSaida: string + dataPrevista: string + contato: { + id: number + tipoPessoa?: ITipoPessoa + numeroDocumento?: string + } + loja?: { id: number } + numeroPedidoCompra?: string + outrasDespesas?: number + observacoes?: string + observacoesInternas?: string + desconto?: { + valor: number + unidade?: IDescontoUnidade + } + categoria?: { id: number } + tributacao?: { + totalICMS?: number + totalIPI?: number + } + itens: { + id: number + codigo?: string + unidade?: string + quantidade: number + desconto?: number + valor: number + aliquotaIPI?: number + descricao: string + descricaoDetalhada?: string + produto?: { id: number } + comissao?: { + base?: number + aliquota?: number + valor?: number + } + }[] + parcelas: { + id: number + dataVencimento: string + valor: number + observacoes?: string + formaPagamento: { id: number } + }[] + transporte?: { + fretePorConta?: IFretePorConta + frete?: number + quantidadeVolumes?: number + pesoBruto?: number + prazoEntrega?: number + contato?: { + id?: number + nome: string + } + etiqueta?: { + nome?: string + endereco?: string + numero?: string + complemento?: string + municipio?: string + uf?: IUF + cep?: string + bairro?: string + nomePais?: string + } + volumes?: { + id: number + servico: string + codigoRastreamento?: string + }[] + } + vendedor?: { id: number } + intermediador?: { + cnpj?: string + nomeUsuario?: string + } +} + +export interface IUpdateResponse { + data: { + id: number + alertas?: IDefaultErrorFieldsResponse[] + rastreamento?: { description?: string } + } +} diff --git a/src/entities/pedidosVendas/types/desconto-unidade.type.ts b/src/entities/pedidosVendas/types/desconto-unidade.type.ts new file mode 100644 index 0000000..c5e06cd --- /dev/null +++ b/src/entities/pedidosVendas/types/desconto-unidade.type.ts @@ -0,0 +1,4 @@ +/** + * Tipagem referente à unidade do desconto aplicado em um pedido de compra. + */ +export type IDescontoUnidade = 'REAL' | 'PERCENTUAL' diff --git a/src/entities/productGroups.ts b/src/entities/productGroups.ts deleted file mode 100644 index d68fda6..0000000 --- a/src/entities/productGroups.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' - -export interface IGroupProductFilters { - nome?: string - nomePai?: string -} - -export type IGroupProductInfos = Record - -export interface IGroupProductResponse { - id: string - nome: string - idPai?: string - nomePai?: string -} - -export default function GroupProducts (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'grupoprodutos', - pluralName: 'gruposprodutos' - } - - return Object.assign(config, { - all: new All< - IGroupProductResponse, - IGroupProductFilters, - IGroupProductInfos - >().all, - find: new Find().find, - findBy: new FindBy< - IGroupProductResponse, - IGroupProductFilters, - IGroupProductInfos - >().findBy - }) -} diff --git a/src/entities/productionOrders.ts b/src/entities/productionOrders.ts deleted file mode 100644 index 694072e..0000000 --- a/src/entities/productionOrders.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Delete from '../core/functions/delete' - -export interface IProductionOrder { - itens: { - item: { - codigoProduto?: string - nomeProduto: string - quantidade: number - } - }[] - idDepositoOrigem: number - idDepositoDestino: number - numero?: number - previsaoInicio?: string // TODO: transform to Date and adapt code for that - previsaoFinal?: string // TODO: same as above - observacoes?: string -} - -export type IProductionOrderInfos = Record - -export type IProductionOrderFilters = Record - -export interface IProductionOrderResponse { - itens: { - item: { - codigoProduto?: string - nomeProduto: string - quantidade: number - } - }[] - idDepositoOrigem: number - idDepositoDestino: number - numero?: number - cliente: [] - responsavel?: string - situacao: string // TODO: constraint values on the accepted ones - idSituacao: string - previsaoInicio?: string // TODO: transform to Date and adapt code for that - previsaoFinal?: string // TODO: same as above - dataInicio?: string // TODO: same as above - dataFim?: string // TODO: same as above - dataCriacao?: string // TODO: same as above - dataAlteracao?: string // TODO: same as above - observacoes?: string -} - -export interface IProductionOrderCreateResponse { - id: number - numero: number -} - -export interface IProductionOrderDeleteResponse { - id: number - numero: number - msg: string -} - -export default function ProductionOrders (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'ordemproducao', - pluralName: 'ordensproducao' - } - - return Object.assign(config, { - all: new All< - IProductionOrderResponse, - IProductionOrderFilters, - IProductionOrderInfos - >().all, - find: new Find().find, - findBy: new FindBy< - IProductionOrderResponse, - IProductionOrderFilters, - IProductionOrderInfos - >().findBy, - create: new Create() - .create, - delete: new Delete().delete - }) -} diff --git a/src/entities/products.ts b/src/entities/products.ts deleted file mode 100644 index 4473800..0000000 --- a/src/entities/products.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Delete from '../core/functions/delete' - -export interface IProduct { - codigo: string - codigoItem?: '06' | '21' | '22' - descricao: string - tipo?: 'S' | 'P' | 'N' - situacao?: 'Ativo' | 'Inativo' - descricaoCurta?: string - descricaoComplementar?: string - un?: 'pc' | 'un' | 'cx' - vlr_unit?: number - preco_custo?: number - peso_bruto?: number - peso_liq?: number - class_fiscal?: string - marca?: string - cest?: string - origem?: string - idGrupoProduto?: number - condicao?: 'Não especificado' | 'Novo' | 'Usado' - freteGratis?: 'S' | 'N' - linkExterno?: string - observacoes?: string - producao?: 'T' | 'P' - unidadeMedida?: 'Metros' | 'Centimetros' | 'Milimetro' - dataValidade?: Date - descricaoFornecedor?: string - idFabricante?: number - codigoFabricante?: string - estoque?: number - deposito?: { - id?: number - estoque?: number - } - gtin?: string - gtinEmbalagem?: string - largura?: string - altura?: string - profundidade?: string - estoqueMinimo?: number - estoqueMaximo?: number - itensPorCaixa?: number - volumes?: number - urlVideo?: string - localizacao?: string - crossdocking?: number - garantia?: number - spedTipoItem?: - | '00' - | '01' - | '02' - | '03' - | '04' - | '05' - | '06' - | '07' - | '08' - | '09' - | '10' - | '99' - variacoes?: { - variacao: { - nome: string - codigo?: string - vlr_unit?: number - clonarDadosPai?: 'S' | 'N' - estoque?: number - deposito?: { - id?: number - estoque?: number - } - un?: string - }[] - } - imagens?: { - url?: string[] - } - idCategoria?: number - estrutura?: { - tipoEstoque?: 'F' | 'V' - lancarEstoque?: 'P' | 'C' | 'PC' - componente: { - nome: string - codigo: string - quantidade: number - }[] - } - camposCustomizados?: unknown -} - -export interface IProductInfos { - estoque?: 'S' - loja?: string - imagem?: 'S' -} - -export interface IProductFilters { - dataInclusao?: string - dataAlteracao?: string - dataAlteracaoLoja?: string - dataInclusaoLoja?: string - tipo?: 'P' | 'S' - situacao?: 'A' | 'I' -} - -// @TODO: conditional response content based on info passed -export interface IProductResponse { - id: string - codigo?: string - descricao: string - tipo: 'S' | 'P' | 'N' - situacao: 'Ativo' | 'Inativo' - unidade?: 'PC' | 'UN' | 'CX' - preco: string - precoCusto: string - descricaoCurta?: string - descricaoComplementar?: string - dataInclusao: string - dataAlteracao: string - imageThumbnail?: string - urlVideo?: string - nomeFornecedor?: string - codigoFabricante?: string - marca?: string - class_fiscal?: string - cest?: string - origem?: string - idGrupoProduto?: string - linkExterno?: string - observacoes?: string - grupoProduto?: string - garantia: string - descricaoFornecedor?: string - idFabricante: string - categoria: { - id: string - descricao: string - } - pesoLiq: string - pesoBruto: string - estoqueMinimo: string - estoqueMaximo: string - gtin?: string - gtinEmbalagem?: string - larguraProduto: string - alturaProduto: string - profundidadeProduto: string - unidadeMedida: 'Metros' | 'Centímetros' | 'Milímetros' - itensPorCaixa: number - volumes: number - localizacao?: string - crossdocking: string - condicao: 'Não especificado' | 'Novo' | 'Usado' - freteGratis: 'N' | 'S' - producao: 'T' | 'P' - dataValidade: string - spedTipoItem?: string - imagem?: { - /** - * imagem = 'S' - */ - link: string - validade: string - tipoArmazenamento: 'interno' | 'externo' - }[] - produtoLoja?: { - /** - * loja !== undefined - */ - idProdutoLoja: string - preco: { - preco: number - precoPromocional: string - } - dataInclusao: string - dataAlteracao: string - } - codigoPai?: string - /** - * estoque = 'S' - */ - estoqueAtual: number - depositos?: { - /** - * estoque = 'S' - */ - deposito: { - id: string - nome: string - saldo: number - desconsiderar: 'S' | 'N' - saldoVirtual: number - } - }[] - variacoes: { - variacao: { - nome: string, - codigo: string - } | { - nome: string - estoqueAtual: number, - depositos?: { - deposito: { - id: string; - nome: string; - saldo: number; - desconsiderar: 'S' | 'N'; - saldoVirtual: number; - }; - }[]; - } - }[] - camposCustomizados?: { [key: string]: unknown } -} - -export interface IProductFindResponse - extends Pick> { - variacoes: { - variacao: { - nome: string; - estoqueAtual: number; - depositos?: { - deposito: { - id: string; - nome: string; - saldo: number; - desconsiderar: 'S' | 'N'; - saldoVirtual: number; - }; - }[]; - }; - }[]; -} - -export interface IProductDeleteResponse { - codigo: string - mensagem: string -} - -export default function Products (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'produto', - pluralName: 'produtos' - } - - const update = async ( - id: number | string, - data: IProduct, - options?: { raw?: boolean } - ) => { - const createMethod = new Create({ - ...config, - endpoint: `${config.singularName}/${id}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - if (raw) { - return await createMethod.create(data, { raw: true }) - } else { - return await createMethod.create(data, { raw: false }) - } - } - - const findBySupplierCode = async ( - code: number | string, - supplierId: number | string, - options?: { - params?: IProductInfos - raw?: boolean - } - ) => { - const findMethod = new Find(config) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await findMethod.find(`${code}/${supplierId}`, { - params: options.params, - raw: true - }) - } else { - return await findMethod.find(`${code}/${supplierId}`, { - params: options.params, - raw: false - }) - } - } else { - return await findMethod.find(`${code}/${supplierId}`) - } - } - - return Object.assign(config, { - all: new All().all, - find: new Find().find, - findBy: new FindBy() - .findBy, - create: new Create().create, - update, - delete: new Delete().delete, - findBySupplierCode - }) -} diff --git a/src/entities/produtos/__tests__/change-situation-many-response.ts b/src/entities/produtos/__tests__/change-situation-many-response.ts new file mode 100644 index 0000000..4cab987 --- /dev/null +++ b/src/entities/produtos/__tests__/change-situation-many-response.ts @@ -0,0 +1,37 @@ +export default { + data: { + alertas: [ + { + id: 12345678, + error: { + type: 'VALIDATION_ERROR', + message: 'Não foi possível salvar a venda', + description: + 'A venda não pode ser salva, pois ocorreram problemas em sua validação.', + fields: [ + { + code: 49, + msg: 'Uma ou mais parcelas da venda possuem erros de validação', + element: 'parcelas', + namespace: 'VENDAS', + collection: [ + { + index: 1, + code: 12, + msg: 'Id da forma de pagamento inválido.', + element: 'formaPagamento', + namespace: 'VENDAS' + } + ] + } + ] + } + } + ] + } +} + +export const changeSituationManyRequest = { + idsProdutos: [12345678], + situacao: 'A' as const +} diff --git a/src/entities/produtos/__tests__/change-situation-response.ts b/src/entities/produtos/__tests__/change-situation-response.ts new file mode 100644 index 0000000..ff212c0 --- /dev/null +++ b/src/entities/produtos/__tests__/change-situation-response.ts @@ -0,0 +1,5 @@ +export default null + +export const changeSituationRequest = { + situacao: 'A' as const +} diff --git a/src/entities/produtos/__tests__/create-response.ts b/src/entities/produtos/__tests__/create-response.ts new file mode 100644 index 0000000..940b913 --- /dev/null +++ b/src/entities/produtos/__tests__/create-response.ts @@ -0,0 +1,253 @@ +export default { + data: { + id: 6423817751, + variations: { + deleted: [ + { + id: 6423817579, + variations: {}, + warnings: ['Mensagem de aviso.'] + } + ], + updated: [ + { + id: 6423817579, + variations: {}, + warnings: ['Mensagem de aviso.'] + } + ], + saved: [ + { + id: 6423817579, + variations: {}, + warnings: ['Mensagem de aviso.'] + } + ] + }, + warnings: ['Mensagem de aviso.'] + } +} + +export const createRequestBody = { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: 'Z' as const, + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 123456789, + valor: '256GB', + item: 'Opção A' + } + ], + variacoes: [ + { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: 'Z' as const, + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 123456789, + valor: '256GB', + item: 'Opção A' + } + ], + variacao: { + nome: 'Tamanho:G;Cor:Verde', + ordem: 1, + produtoPai: { + cloneInfo: true + } + } + } + ] +} diff --git a/src/entities/produtos/__tests__/delete-many-response.ts b/src/entities/produtos/__tests__/delete-many-response.ts new file mode 100644 index 0000000..10c276c --- /dev/null +++ b/src/entities/produtos/__tests__/delete-many-response.ts @@ -0,0 +1,32 @@ +export default { + data: { + alertas: [ + { + id: 12345678, + error: { + type: 'VALIDATION_ERROR', + message: 'Não foi possível salvar a venda', + description: + 'A venda não pode ser salva, pois ocorreram problemas em sua validação.', + fields: [ + { + code: 49, + msg: 'Uma ou mais parcelas da venda possuem erros de validação', + element: 'parcelas', + namespace: 'VENDAS', + collection: [ + { + index: 1, + code: 12, + msg: 'Id da forma de pagamento inválido.', + element: 'formaPagamento', + namespace: 'VENDAS' + } + ] + } + ] + } + } + ] + } +} diff --git a/src/entities/produtos/__tests__/delete-response.ts b/src/entities/produtos/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/produtos/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/produtos/__tests__/find-response.ts b/src/entities/produtos/__tests__/find-response.ts new file mode 100644 index 0000000..36344bc --- /dev/null +++ b/src/entities/produtos/__tests__/find-response.ts @@ -0,0 +1,225 @@ +export default { + data: { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: 'Z' as const, + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 123456789, + valor: '256GB', + item: 'Opção A' + } + ], + variacoes: [ + { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: 'Z' as const, + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 123456789, + valor: '256GB', + item: 'Opção A' + } + ], + variacao: { + nome: 'Tamanho:G;Cor:Verde', + ordem: 1, + produtoPai: { + cloneInfo: true + } + } + } + ] + } +} diff --git a/src/entities/produtos/__tests__/get-response.ts b/src/entities/produtos/__tests__/get-response.ts new file mode 100644 index 0000000..ee8bccb --- /dev/null +++ b/src/entities/produtos/__tests__/get-response.ts @@ -0,0 +1,14 @@ +export default { + data: [ + { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta' + } + ] +} diff --git a/src/entities/produtos/__tests__/index.spec.ts b/src/entities/produtos/__tests__/index.spec.ts new file mode 100644 index 0000000..1a767e9 --- /dev/null +++ b/src/entities/produtos/__tests__/index.spec.ts @@ -0,0 +1,200 @@ +import { Chance } from 'chance' +import { Produtos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import { IChangeSituationManyResponse } from '../interfaces/change-situation-many.interface' +import { ICreateResponse } from '../interfaces/create.interface' +import { IDeleteManyResponse } from '../interfaces/delete-many.interface' +import { IFindResponse } from '../interfaces/find.interface' +import { IGetResponse } from '../interfaces/get.interface' +import { IUpdateResponse } from '../interfaces/update.interface' +import changeSituationManyResponse, { + changeSituationManyRequest +} from './change-situation-many-response' +import changeSituationResponse, { + changeSituationRequest +} from './change-situation-response' +import createResponse, { createRequestBody } from './create-response' +import deleteManyResponse from './delete-many-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Produtos entity', () => { + let repository: InMemoryBlingRepository + let entity: Produtos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Produtos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete many successfully', async () => { + const idsProdutos: number[] = [] + for (let i = 0; i < chance.natural({ min: 1, max: 5 }); i++) { + idsProdutos.push(chance.natural()) + } + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteManyResponse) + + const response = await entity.deleteMany({ idsProdutos }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + id: '', + params: { idsProdutos } + }) + expect(response).toBe(deleteManyResponse) + + const typingResponseTest: IDeleteManyResponse = deleteManyResponse + expect(typingResponseTest).toBe(deleteManyResponse) + }) + + it('should delete successfully', async () => { + const idProduto = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idProduto }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + id: String(idProduto) + }) + expect(response).toBe(deleteResponse) + + const typingResponseTest: null = deleteResponse + expect(typingResponseTest).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + params: { + limite: undefined, + pagina: undefined, + criterio: undefined, + tipo: undefined, + idComponente: undefined, + dataInclusaoInicial: undefined, + dataInclusaoFinal: undefined, + dataAlteracaoInicial: undefined, + dataAlteracaoFinal: undefined, + idCategoria: undefined, + idLoja: undefined, + codigo: undefined, + nome: undefined, + idsProdutos: undefined + } + }) + expect(response).toBe(getResponse) + + const typingResponseTest: IGetResponse = getResponse + expect(typingResponseTest).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idProduto = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idProduto }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + id: String(idProduto) + }) + expect(response).toBe(findResponse) + + const typingResponseTest: IFindResponse = findResponse + expect(typingResponseTest).toBe(findResponse) + }) + + it('should change situation successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idProduto = chance.natural() + repository.setResponse(changeSituationResponse) + + const response = await entity.changeSituation({ + idProduto, + ...changeSituationRequest + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + id: `${idProduto}/situacoes`, + body: changeSituationRequest + }) + expect(response).toBe(changeSituationResponse) + + const typingResponseTest: null = changeSituationResponse + expect(typingResponseTest).toBe(changeSituationResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + body: createRequestBody + }) + expect(response).toBe(createResponse) + + const typingResponseTest: ICreateResponse = createResponse + expect(typingResponseTest).toBe(createResponse) + }) + + it('should change situation many successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(changeSituationManyResponse) + + const response = await entity.changeSituationMany( + changeSituationManyRequest + ) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/situacoes', + body: changeSituationManyRequest + }) + expect(response).toBe(changeSituationManyResponse) + + const typingResponseTest: IChangeSituationManyResponse = + changeSituationManyResponse + expect(typingResponseTest).toBe(changeSituationManyResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idProduto = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idProduto, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos', + id: String(idProduto), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + + const typingResponseTest: IUpdateResponse = updateResponse + expect(typingResponseTest).toBe(updateResponse) + }) +}) diff --git a/src/entities/produtos/__tests__/update-response.ts b/src/entities/produtos/__tests__/update-response.ts new file mode 100644 index 0000000..afc3e28 --- /dev/null +++ b/src/entities/produtos/__tests__/update-response.ts @@ -0,0 +1,253 @@ +export default { + data: { + id: 6423817751, + variations: { + deleted: [ + { + id: 6423817579, + variations: {}, + warnings: ['Mensagem de aviso.'] + } + ], + updated: [ + { + id: 6423817579, + variations: {}, + warnings: ['Mensagem de aviso.'] + } + ], + saved: [ + { + id: 6423817579, + variations: {}, + warnings: ['Mensagem de aviso.'] + } + ] + }, + warnings: ['Mensagem de aviso.'] + } +} + +export const updateRequestBody = { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: 'Z' as const, + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 123456789, + valor: '256GB', + item: 'Opção A' + } + ], + variacoes: [ + { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: 'Z' as const, + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 123456789, + valor: '256GB', + item: 'Opção A' + } + ], + variacao: { + nome: 'Tamanho:G;Cor:Verde', + ordem: 1, + produtoPai: { + cloneInfo: true + } + } + } + ] +} diff --git a/src/entities/produtos/index.ts b/src/entities/produtos/index.ts new file mode 100644 index 0000000..b73b9a3 --- /dev/null +++ b/src/entities/produtos/index.ts @@ -0,0 +1,202 @@ +import { Entity } from '../@shared/entity' +import { + IChangeSituationManyBody, + IChangeSituationManyResponse +} from './interfaces/change-situation-many.interface' +import { + IChangeSituationBody, + IChangeSituationParams +} from './interfaces/change-situation.interface' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { + IDeleteManyParams, + IDeleteManyResponse +} from './interfaces/delete-many.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Produtos. + * + * @see https://developer.bling.com.br/referencia#/Produtos + */ +export class Produtos extends Entity { + /** + * Remove múltiplos produtos. + * + * @param {IDeleteManyParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/delete_produtos + */ + public async deleteMany( + params: IDeleteManyParams + ): Promise { + return await this.repository.destroy({ + endpoint: 'produtos', + id: '', + params: { idsProdutos: params.idsProdutos } + }) + } + + /** + * Remove um produto. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/delete_produtos__idProduto_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'produtos', + id: String(params.idProduto) + }) + } + + /** + * Obtém produtos. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/get_produtos + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'produtos', + params: { + pagina: params?.pagina, + limite: params?.limite, + criterio: params?.criterio, + tipo: params?.tipo, + idComponente: params?.idComponente, + dataInclusaoInicial: this.prepareStringOrDateParam( + params?.dataInclusaoInicial + ), + dataInclusaoFinal: this.prepareStringOrDateParam( + params?.dataInclusaoFinal + ), + dataAlteracaoInicial: this.prepareStringOrDateParam( + params?.dataAlteracaoInicial + ), + dataAlteracaoFinal: this.prepareStringOrDateParam( + params?.dataAlteracaoFinal + ), + idCategoria: params?.idCategoria, + idLoja: params?.idLoja, + codigo: params?.codigo, + nome: params?.nome, + idsProdutos: params?.idsProdutos + } + }) + } + + /** + * Obtém um produto. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/get_produtos__idProduto_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'produtos', + id: String(params.idProduto) + }) + } + + /** + * Altera a situação de um produto. + * + * @param {IChangeSituationParams & IChangeSituationBody} params Parâmetros da alteração. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/patch_produtos__idProduto__situacoes + */ + public async changeSituation( + params: IChangeSituationParams & IChangeSituationBody + ): Promise { + const { idProduto, ...body } = params + return await this.repository.update({ + endpoint: 'produtos', + id: `${idProduto}/situacoes`, + body + }) + } + + /** + * Cria um produto. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/post_produtos + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'produtos', + body + }) + } + + /** + * Altera a situação de múltiplos produtos. + * + * @param {IChangeSituationManyBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/post_produtos_situacoes + */ + public async changeSituationMany( + body: IChangeSituationManyBody + ): Promise { + return await this.repository.store({ + endpoint: 'produtos/situacoes', + body + }) + } + + /** + * Altera um produto. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos/put_produtos__idProduto_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idProduto, ...body } = params + + return await this.repository.replace({ + endpoint: 'produtos', + id: String(idProduto), + body + }) + } +} diff --git a/src/entities/produtos/interfaces/change-situation-many.interface.ts b/src/entities/produtos/interfaces/change-situation-many.interface.ts new file mode 100644 index 0000000..1622959 --- /dev/null +++ b/src/entities/produtos/interfaces/change-situation-many.interface.ts @@ -0,0 +1,13 @@ +import { IDefaultErrorResponse } from 'src/entities/@shared/interfaces/error.interface' +import { ISituacao } from '../types/situacao.type' + +export interface IChangeSituationManyBody { + idsProdutos?: number[] + situacao?: ISituacao | 'E' +} + +export interface IChangeSituationManyResponse { + data: { + alertas?: ({ id: number } & IDefaultErrorResponse)[] + } +} diff --git a/src/entities/produtos/interfaces/change-situation.interface.ts b/src/entities/produtos/interfaces/change-situation.interface.ts new file mode 100644 index 0000000..0a1bdc2 --- /dev/null +++ b/src/entities/produtos/interfaces/change-situation.interface.ts @@ -0,0 +1,12 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IChangeSituationParams { + /** + * ID do produto + */ + idProduto: number +} + +export interface IChangeSituationBody { + situacao: ISituacao +} diff --git a/src/entities/produtos/interfaces/create.interface.ts b/src/entities/produtos/interfaces/create.interface.ts new file mode 100644 index 0000000..0fa2296 --- /dev/null +++ b/src/entities/produtos/interfaces/create.interface.ts @@ -0,0 +1,207 @@ +import { IActionEstoque } from '../types/action-estoque.type' +import { ICondicao } from '../types/condicao.type' +import { IEstruturaLancamentoEstoque } from '../types/estrutura-lancamento-estoque.type' +import { IEstruturaTipoEstoque } from '../types/estrutura-tipo-estoque.type' +import { IFormato } from '../types/formato.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoArmamento } from '../types/tipo-armamento.type' +import { ITipoProducao } from '../types/tipo-producao.type' +import { ITipo } from '../types/tipo.type' + +export interface ICreateBody { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacoes: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacao: { + nome: string + ordem: number + produtoPai: { cloneInfo: boolean } + } + }[] +} + +interface ICreateResponseActionItem { + id?: number + variations?: any + warnings?: string[] +} + +export interface ICreateResponse { + data: { + id?: number + variations?: { + deleted?: ICreateResponseActionItem[] + updated?: ICreateResponseActionItem[] + saved?: ICreateResponseActionItem[] + } + warnings?: string[] + } +} diff --git a/src/entities/produtos/interfaces/delete-many.interface.ts b/src/entities/produtos/interfaces/delete-many.interface.ts new file mode 100644 index 0000000..7e3af37 --- /dev/null +++ b/src/entities/produtos/interfaces/delete-many.interface.ts @@ -0,0 +1,14 @@ +import { IDefaultErrorResponse } from 'src/entities/@shared/interfaces/error.interface' + +export interface IDeleteManyParams { + /** + * IDs dos produtos + */ + idsProdutos: number[] +} + +export interface IDeleteManyResponse { + data: { + alertas?: ({ id: number } & IDefaultErrorResponse)[] + } +} diff --git a/src/entities/produtos/interfaces/delete.interface.ts b/src/entities/produtos/interfaces/delete.interface.ts new file mode 100644 index 0000000..2e42f46 --- /dev/null +++ b/src/entities/produtos/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do produto + */ + idProduto: number +} diff --git a/src/entities/produtos/interfaces/find.interface.ts b/src/entities/produtos/interfaces/find.interface.ts new file mode 100644 index 0000000..13b52b0 --- /dev/null +++ b/src/entities/produtos/interfaces/find.interface.ts @@ -0,0 +1,198 @@ +import { IActionEstoque } from '../types/action-estoque.type' +import { ICondicao } from '../types/condicao.type' +import { IEstruturaLancamentoEstoque } from '../types/estrutura-lancamento-estoque.type' +import { IEstruturaTipoEstoque } from '../types/estrutura-tipo-estoque.type' +import { IFormato } from '../types/formato.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoArmamento } from '../types/tipo-armamento.type' +import { ITipoProducao } from '../types/tipo-producao.type' +import { ITipo } from '../types/tipo.type' + +export interface IFindParams { + /** + * ID do produto + */ + idProduto: number +} + +export interface IFindResponse { + data: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacoes: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacao: { + nome: string + ordem: number + produtoPai: { cloneInfo: boolean } + } + }[] + } +} diff --git a/src/entities/produtos/interfaces/get.interface.ts b/src/entities/produtos/interfaces/get.interface.ts new file mode 100644 index 0000000..f105f33 --- /dev/null +++ b/src/entities/produtos/interfaces/get.interface.ts @@ -0,0 +1,77 @@ +import { ICriterio } from '../types/criterio.type' +import { IFormato } from '../types/formato.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoFiltro } from '../types/tipo-filtro.type' +import { ITipo } from '../types/tipo.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Critério de listagem + */ + criterio?: ICriterio + /** + * + */ + tipo?: ITipoFiltro + /** + * ID do componente. Utilizado quando o filtro tipo for `E`. + */ + idComponente?: number + /** + * Data de inclusão inicial + */ + dataInclusaoInicial?: Date | string + /** + * Data de inclusão final + */ + dataInclusaoFinal?: Date | string + /** + * Data de alteração inicial + */ + dataAlteracaoInicial?: Date | string + /** + * Data de alteração final + */ + dataAlteracaoFinal?: Date | string + /** + * ID da categoria do produto + */ + idCategoria?: number + /** + * ID da loja + */ + idLoja?: number + /** + * Código do produto + */ + codigo?: string + /** + * Nome do produto + */ + nome?: string + /** + * IDs dos produtos + */ + idsProdutos?: number[] +} + +export interface IGetResponse { + data: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + }[] +} diff --git a/src/entities/produtos/interfaces/update.interface.ts b/src/entities/produtos/interfaces/update.interface.ts new file mode 100644 index 0000000..7d469b9 --- /dev/null +++ b/src/entities/produtos/interfaces/update.interface.ts @@ -0,0 +1,214 @@ +import { IActionEstoque } from '../types/action-estoque.type' +import { ICondicao } from '../types/condicao.type' +import { IEstruturaLancamentoEstoque } from '../types/estrutura-lancamento-estoque.type' +import { IEstruturaTipoEstoque } from '../types/estrutura-tipo-estoque.type' +import { IFormato } from '../types/formato.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoArmamento } from '../types/tipo-armamento.type' +import { ITipoProducao } from '../types/tipo-producao.type' +import { ITipo } from '../types/tipo.type' + +export interface IUpdateParams { + /** + * ID do produto + */ + idProduto: number +} + +export interface IUpdateBody { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacoes: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacao: { + nome: string + ordem: number + produtoPai: { cloneInfo: boolean } + } + }[] +} + +interface ICreateResponseActionItem { + id?: number + variations?: any + warnings?: string[] +} + +export interface IUpdateResponse { + data: { + id?: number + variations?: { + deleted?: ICreateResponseActionItem[] + updated?: ICreateResponseActionItem[] + saved?: ICreateResponseActionItem[] + } + warnings?: string[] + } +} diff --git a/src/entities/produtos/types/action-estoque.type.ts b/src/entities/produtos/types/action-estoque.type.ts new file mode 100644 index 0000000..012232f --- /dev/null +++ b/src/entities/produtos/types/action-estoque.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à ação de estoque ao transformar um produto simples em + * variação. + * + * - `Z`: Irá zerar os saldos de estoque + * - `T`: Transfere o estoque do produto pai para a primeira variação informada + */ +export type IActionEstoque = 'Z' | 'T' diff --git a/src/entities/produtos/types/condicao.type.ts b/src/entities/produtos/types/condicao.type.ts new file mode 100644 index 0000000..53ed33c --- /dev/null +++ b/src/entities/produtos/types/condicao.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à condição do produto. + * + * - `0`: Não especificado + * - `1`: Novo + * - `2`: Usado + */ +export type ICondicao = 0 | 1 | 2 diff --git a/src/entities/produtos/types/criterio.type.ts b/src/entities/produtos/types/criterio.type.ts new file mode 100644 index 0000000..814d124 --- /dev/null +++ b/src/entities/produtos/types/criterio.type.ts @@ -0,0 +1,10 @@ +/** + * Tipagem referente ao critério de listagem de produtos. + * + * - `1`: Últimos incluídos + * - `2`: Ativos + * - `3`: Inativos + * - `4`: Excluídos + * - `5`: Todos + */ +export type ICriterio = 1 | 2 | 3 | 4 | 5 diff --git a/src/entities/produtos/types/estrutura-lancamento-estoque.type.ts b/src/entities/produtos/types/estrutura-lancamento-estoque.type.ts new file mode 100644 index 0000000..4823a94 --- /dev/null +++ b/src/entities/produtos/types/estrutura-lancamento-estoque.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao lançamento de estoque para a estrutura. + * + * - `A`: Produto e Componente + * - `M`: Componente + * - `P`: Produto + */ +export type IEstruturaLancamentoEstoque = 'A' | 'M' | 'P' diff --git a/src/entities/produtos/types/estrutura-tipo-estoque.type.ts b/src/entities/produtos/types/estrutura-tipo-estoque.type.ts new file mode 100644 index 0000000..a835dd1 --- /dev/null +++ b/src/entities/produtos/types/estrutura-tipo-estoque.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo do estoque para a estrutura. + * + * - `F`: Físico + * - `V`: Virtual + */ +export type IEstruturaTipoEstoque = 'F' | 'V' diff --git a/src/entities/produtos/types/formato.type.ts b/src/entities/produtos/types/formato.type.ts new file mode 100644 index 0000000..79c6437 --- /dev/null +++ b/src/entities/produtos/types/formato.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao formato do produto. + * + * - `S`: Simples + * - `V`: Com variações + * - `E`: Com composição + */ +export type IFormato = 'S' | 'V' | 'E' diff --git a/src/entities/produtos/types/situacao.type.ts b/src/entities/produtos/types/situacao.type.ts new file mode 100644 index 0000000..bffb723 --- /dev/null +++ b/src/entities/produtos/types/situacao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente à situação de um produto. + * + * - `A`: Ativo + * - `I`: Inativo + */ +export type ISituacao = 'A' | 'I' diff --git a/src/entities/produtos/types/tipo-armamento.type.ts b/src/entities/produtos/types/tipo-armamento.type.ts new file mode 100644 index 0000000..277ff6d --- /dev/null +++ b/src/entities/produtos/types/tipo-armamento.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo do armamento (quando o produto se referir a um). + * + * - `0`: Uso permitido + * - `1`: Uso restrito + */ +export type ITipoArmamento = 0 | 1 diff --git a/src/entities/produtos/types/tipo-filtro.type.ts b/src/entities/produtos/types/tipo-filtro.type.ts new file mode 100644 index 0000000..3ab1a30 --- /dev/null +++ b/src/entities/produtos/types/tipo-filtro.type.ts @@ -0,0 +1,12 @@ +/** + * Tipagem referente ao filtro do tipo do produto. + * + * - `T`: Todos + * - `P`: Produtos + * - `S`: Serviços + * - `E`: Composições + * - `PS`: Produtos simples + * - `C`: Com variações + * - `V`: Variações + */ +export type ITipoFiltro = 'T' | 'P' | 'S' | 'E' | 'PS' | 'C' | 'V' diff --git a/src/entities/produtos/types/tipo-producao.type.ts b/src/entities/produtos/types/tipo-producao.type.ts new file mode 100644 index 0000000..023e52e --- /dev/null +++ b/src/entities/produtos/types/tipo-producao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo da produção do produto. + * + * - `P`: Própria + * - `T`: Terceiros + */ +export type ITipoProducao = 'P' | 'T' diff --git a/src/entities/produtos/types/tipo.type.ts b/src/entities/produtos/types/tipo.type.ts new file mode 100644 index 0000000..73e99f9 --- /dev/null +++ b/src/entities/produtos/types/tipo.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao tipo de um produto. + * + * - `S`: Serviço + * - `P`: Produto + * - `N`: Serviço 06 21 22 + */ +export type ITipo = 'S' | 'P' | 'N' diff --git a/src/entities/produtosEstruturas/__tests__/add-component-response.ts b/src/entities/produtosEstruturas/__tests__/add-component-response.ts new file mode 100644 index 0000000..78251ee --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/add-component-response.ts @@ -0,0 +1,10 @@ +export default null + +export const addComponentRequest = [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } +] diff --git a/src/entities/produtosEstruturas/__tests__/change-component-response.ts b/src/entities/produtosEstruturas/__tests__/change-component-response.ts new file mode 100644 index 0000000..e58b206 --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/change-component-response.ts @@ -0,0 +1,8 @@ +export default null + +export const changeComponentRequest = { + produto: { + id: 1 + }, + quantidade: 2.1 +} diff --git a/src/entities/produtosEstruturas/__tests__/delete-components-response.ts b/src/entities/produtosEstruturas/__tests__/delete-components-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/delete-components-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/produtosEstruturas/__tests__/delete-response.ts b/src/entities/produtosEstruturas/__tests__/delete-response.ts new file mode 100644 index 0000000..573470f --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/delete-response.ts @@ -0,0 +1,29 @@ +export default { + data: { + alertas: [ + { + type: 'VALIDATION_ERROR', + message: 'Não foi possível salvar a venda', + description: + 'A venda não pode ser salva, pois ocorreram problemas em sua validação.', + fields: [ + { + code: 49, + msg: 'Uma ou mais parcelas da venda possuem erros de validação', + element: 'parcelas', + namespace: 'VENDAS', + collection: [ + { + index: 1, + code: 12, + msg: 'Id da forma de pagamento inválido.', + element: 'formaPagamento', + namespace: 'VENDAS' + } + ] + } + ] + } + ] + } +} diff --git a/src/entities/produtosEstruturas/__tests__/find-response.ts b/src/entities/produtosEstruturas/__tests__/find-response.ts new file mode 100644 index 0000000..2a1317c --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/find-response.ts @@ -0,0 +1,14 @@ +export default { + data: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + } +} diff --git a/src/entities/produtosEstruturas/__tests__/index.spec.ts b/src/entities/produtosEstruturas/__tests__/index.spec.ts new file mode 100644 index 0000000..f064b45 --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/index.spec.ts @@ -0,0 +1,138 @@ +import { Chance } from 'chance' +import { ProdutosEstruturas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import addComponentResponse, { + addComponentRequest +} from './add-component-response' +import changeComponentResponse, { + changeComponentRequest +} from './change-component-response' +import deleteComponentsResponse from './delete-components-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Produtos- Estruturas entity', () => { + let repository: InMemoryBlingRepository + let entity: ProdutosEstruturas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ProdutosEstruturas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete components successfully', async () => { + const idProdutoEstrutura = chance.natural() + const idsComponentes: number[] = [] + for (let i = 0; i < chance.natural({ min: 1, max: 5 }); i++) { + idsComponentes.push(chance.natural()) + } + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteComponentsResponse) + + const response = await entity.deleteComponents({ + idProdutoEstrutura, + idsComponentes + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/estruturas', + id: `${idProdutoEstrutura}/componentes`, + params: { idsComponentes } + }) + expect(response).toBe(deleteComponentsResponse) + }) + + it('should delete successfully', async () => { + const idsProdutos: number[] = [] + for (let i = 0; i < chance.natural({ min: 1, max: 5 }); i++) { + idsProdutos.push(chance.natural()) + } + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idsProdutos }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/estruturas', + id: '', + params: { idsProdutos } + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idProdutoEstrutura = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idProdutoEstrutura }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/estruturas', + id: String(idProdutoEstrutura) + }) + expect(response).toBe(findResponse) + }) + + it('should change component successfully', async () => { + const idComponente = chance.natural() + const idProdutoEstrutura = chance.natural() + const spy = jest.spyOn(repository, 'update') + repository.setResponse(changeComponentResponse) + + const response = await entity.changeComponent({ + idProdutoEstrutura, + idComponente, + ...changeComponentRequest + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/estruturas', + id: `${idProdutoEstrutura}/componentes/${idComponente}`, + body: changeComponentRequest + }) + expect(response).toBe(changeComponentResponse) + }) + + it('should add component successfully', async () => { + const idProdutoEstrutura = chance.natural() + const spy = jest.spyOn(repository, 'store') + repository.setResponse(addComponentResponse) + + const response = await entity.addComponent({ + params: { idProdutoEstrutura }, + body: addComponentRequest + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: `produtos/estruturas/${idProdutoEstrutura}`, + body: addComponentRequest + }) + expect(response).toBe(addComponentResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idProdutoEstrutura = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idProdutoEstrutura, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/estruturas', + id: String(idProdutoEstrutura), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/produtosEstruturas/__tests__/update-response.ts b/src/entities/produtosEstruturas/__tests__/update-response.ts new file mode 100644 index 0000000..df6d1b7 --- /dev/null +++ b/src/entities/produtosEstruturas/__tests__/update-response.ts @@ -0,0 +1,14 @@ +export default null + +export const updateRequestBody = { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] +} diff --git a/src/entities/produtosEstruturas/index.ts b/src/entities/produtosEstruturas/index.ts new file mode 100644 index 0000000..6818e9f --- /dev/null +++ b/src/entities/produtosEstruturas/index.ts @@ -0,0 +1,130 @@ +import { Entity } from '../@shared/entity' +import { IAddComponentParameter } from './interfaces/add-component.interface' +import { + IChangeComponentBody, + IChangeComponentParams +} from './interfaces/change-component.interface' +import { IDeleteComponentsParams } from './interfaces/delete-components.interface' +import { IDeleteParams, IDeleteResponse } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IUpdateBody, IUpdateParams } from './interfaces/update.interface' + +/** + * Entidade para interação com Produtos - Estruturas. + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas + */ +export class ProdutosEstruturas extends Entity { + /** + * Remove componentes específicos de um produto com composição. + * + * @param {IDeleteComponentsParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas/delete_produtos_estruturas__idProdutoEstrutura__componentes + */ + public async deleteComponents( + params: IDeleteComponentsParams + ): Promise { + return await this.repository.destroy({ + endpoint: 'produtos/estruturas', + id: `${params.idProdutoEstrutura}/componentes`, + params: { idsComponentes: params.idsComponentes } + }) + } + + /** + * Remove a estrutura de múltiplos produtos. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Retorno da deleção. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas/delete_produtos_estruturas + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'produtos/estruturas', + id: '', + params: { idsProdutos: params.idsProdutos } + }) + } + + /** + * Obtém a estrutura de um produto com composição. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas/get_produtos_estruturas__idProdutoEstrutura_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'produtos/estruturas', + id: String(params.idProdutoEstrutura) + }) + } + + /** + * Altera um componente de uma estrutura. + * + * @param {IChangeComponentParams & IChangeComponentBody} params O conteúdo para a alteração. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas/patch_produtos_estruturas__idProdutoEstrutura__componentes__idComponente_ + */ + public async changeComponent( + params: IChangeComponentParams & IChangeComponentBody + ): Promise { + const { idComponente, idProdutoEstrutura, ...body } = params + return await this.repository.update({ + endpoint: 'produtos/estruturas', + id: `${idProdutoEstrutura}/componentes/${idComponente}`, + body + }) + } + + /** + * Adiciona componente(s) a uma estrutura. + * + * @param {IAddComponentParameter} parameters O conteúdo para a adição. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas/post_produtos_estruturas__idProdutoEstrutura__componentes + */ + public async addComponent(parameters: IAddComponentParameter): Promise { + return await this.repository.store({ + endpoint: `produtos/estruturas/${parameters.params.idProdutoEstrutura}`, + body: parameters.body + }) + } + + /** + * Altera a estrutura de um produto com composição. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Estruturas/put_produtos_estruturas__idProdutoEstrutura_ + */ + public async update(params: IUpdateParams & IUpdateBody): Promise { + const { idProdutoEstrutura, ...body } = params + + return await this.repository.replace({ + endpoint: 'produtos/estruturas', + id: String(idProdutoEstrutura), + body + }) + } +} diff --git a/src/entities/produtosEstruturas/interfaces/add-component.interface.ts b/src/entities/produtosEstruturas/interfaces/add-component.interface.ts new file mode 100644 index 0000000..5859674 --- /dev/null +++ b/src/entities/produtosEstruturas/interfaces/add-component.interface.ts @@ -0,0 +1,16 @@ +export interface IAddComponentParams { + /** + * ID do produto com composição + */ + idProdutoEstrutura: number +} + +export interface IAddComponentBody { + produto: { id: number } + quantidade: number +} + +export interface IAddComponentParameter { + params: IAddComponentParams + body: IAddComponentBody[] +} diff --git a/src/entities/produtosEstruturas/interfaces/change-component.interface.ts b/src/entities/produtosEstruturas/interfaces/change-component.interface.ts new file mode 100644 index 0000000..3c881ab --- /dev/null +++ b/src/entities/produtosEstruturas/interfaces/change-component.interface.ts @@ -0,0 +1,15 @@ +export interface IChangeComponentParams { + /** + * ID do produto com composição + */ + idProdutoEstrutura: number + /** + * ID do componente + */ + idComponente: number +} + +export interface IChangeComponentBody { + produto: { id: number } + quantidade: number +} diff --git a/src/entities/produtosEstruturas/interfaces/delete-components.interface.ts b/src/entities/produtosEstruturas/interfaces/delete-components.interface.ts new file mode 100644 index 0000000..0542fac --- /dev/null +++ b/src/entities/produtosEstruturas/interfaces/delete-components.interface.ts @@ -0,0 +1,10 @@ +export interface IDeleteComponentsParams { + /** + * ID do produto com composição + */ + idProdutoEstrutura: number + /** + * IDs dos produtos + */ + idsComponentes: number[] +} diff --git a/src/entities/produtosEstruturas/interfaces/delete.interface.ts b/src/entities/produtosEstruturas/interfaces/delete.interface.ts new file mode 100644 index 0000000..b3f55ac --- /dev/null +++ b/src/entities/produtosEstruturas/interfaces/delete.interface.ts @@ -0,0 +1,14 @@ +import { IDefaultErrorFieldsResponse } from '../../@shared/interfaces/error.interface' + +export interface IDeleteParams { + /** + * IDs dos produtos + */ + idsProdutos: number[] +} + +export interface IDeleteResponse { + data: { + alertas?: IDefaultErrorFieldsResponse[] + } +} diff --git a/src/entities/produtosEstruturas/interfaces/find.interface.ts b/src/entities/produtosEstruturas/interfaces/find.interface.ts new file mode 100644 index 0000000..c7c0eac --- /dev/null +++ b/src/entities/produtosEstruturas/interfaces/find.interface.ts @@ -0,0 +1,20 @@ +import { ILancamentoEstoque } from '../types/lancamento-estoque.type' +import { ITipoEstoque } from '../types/tipo-estoque.type' + +export interface IFindParams { + /** + * ID do produto com composição + */ + idProdutoEstrutura: number +} + +export interface IFindResponse { + data: { + tipoEstoque: ITipoEstoque + lancamentoEstoque: ILancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } +} diff --git a/src/entities/produtosEstruturas/interfaces/update.interface.ts b/src/entities/produtosEstruturas/interfaces/update.interface.ts new file mode 100644 index 0000000..c614182 --- /dev/null +++ b/src/entities/produtosEstruturas/interfaces/update.interface.ts @@ -0,0 +1,18 @@ +import { ILancamentoEstoque } from '../types/lancamento-estoque.type' +import { ITipoEstoque } from '../types/tipo-estoque.type' + +export interface IUpdateParams { + /** + * ID do produto com composição + */ + idProdutoEstrutura: number +} + +export interface IUpdateBody { + tipoEstoque: ITipoEstoque + lancamentoEstoque: ILancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] +} diff --git a/src/entities/produtosEstruturas/types/lancamento-estoque.type.ts b/src/entities/produtosEstruturas/types/lancamento-estoque.type.ts new file mode 100644 index 0000000..c3fe28b --- /dev/null +++ b/src/entities/produtosEstruturas/types/lancamento-estoque.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao lançamento no estoque para estrutura de produtos. + * + * - `A`: Produto e Componente + * - `M`: Componente + * - `P`: Produto + */ +export type ILancamentoEstoque = 'A' | 'M' | 'P' diff --git a/src/entities/produtosEstruturas/types/tipo-estoque.type.ts b/src/entities/produtosEstruturas/types/tipo-estoque.type.ts new file mode 100644 index 0000000..af820be --- /dev/null +++ b/src/entities/produtosEstruturas/types/tipo-estoque.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo de estoque para a estrutura de produtos. + * + * - `F`: Físico + * - `V`: Virtual + */ +export type ITipoEstoque = 'F' | 'V' diff --git a/src/entities/produtosFornecedores/__tests__/create-response.ts b/src/entities/produtosFornecedores/__tests__/create-response.ts new file mode 100644 index 0000000..6c32b33 --- /dev/null +++ b/src/entities/produtosFornecedores/__tests__/create-response.ts @@ -0,0 +1,21 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + id: 12345678, + descricao: 'Copo do Bling', + codigo: 'COD-123', + precoCusto: 5.9, + precoCompra: 3.5, + padrao: false, + produto: { + id: 12345678 + }, + fornecedor: { + id: 12345678 + }, + garantia: 3 +} diff --git a/src/entities/produtosFornecedores/__tests__/delete-response.ts b/src/entities/produtosFornecedores/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/produtosFornecedores/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/produtosFornecedores/__tests__/find-response.ts b/src/entities/produtosFornecedores/__tests__/find-response.ts new file mode 100644 index 0000000..b3f733d --- /dev/null +++ b/src/entities/produtosFornecedores/__tests__/find-response.ts @@ -0,0 +1,17 @@ +export default { + data: { + id: 12345678, + descricao: 'Copo do Bling', + codigo: 'COD-123', + precoCusto: 5.9, + precoCompra: 3.5, + padrao: false, + produto: { + id: 12345678 + }, + fornecedor: { + id: 12345678 + }, + garantia: 3 + } +} diff --git a/src/entities/produtosFornecedores/__tests__/get-response.ts b/src/entities/produtosFornecedores/__tests__/get-response.ts new file mode 100644 index 0000000..0b8dcae --- /dev/null +++ b/src/entities/produtosFornecedores/__tests__/get-response.ts @@ -0,0 +1,18 @@ +export default { + data: [ + { + id: 12345678, + descricao: 'Copo do Bling', + codigo: 'COD-123', + precoCusto: 5.9, + precoCompra: 3.5, + padrao: false, + produto: { + id: 12345678 + }, + fornecedor: { + id: 12345678 + } + } + ] +} diff --git a/src/entities/produtosFornecedores/__tests__/index.spec.ts b/src/entities/produtosFornecedores/__tests__/index.spec.ts new file mode 100644 index 0000000..2755558 --- /dev/null +++ b/src/entities/produtosFornecedores/__tests__/index.spec.ts @@ -0,0 +1,101 @@ +import { Chance } from 'chance' +import { ProdutosFornecedores } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Produtos - Fornecedores entity', () => { + let repository: InMemoryBlingRepository + let entity: ProdutosFornecedores + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ProdutosFornecedores(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idProdutoFornecedor = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idProdutoFornecedor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/fornecedores', + id: String(idProdutoFornecedor) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/fornecedores', + params: { + limite: undefined, + pagina: undefined, + idProduto: undefined, + idFornecedor: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idProdutoFornecedor = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idProdutoFornecedor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/fornecedores', + id: String(idProdutoFornecedor) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/fornecedores', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idProdutoFornecedor = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idProdutoFornecedor, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/fornecedores', + id: String(idProdutoFornecedor), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/produtosFornecedores/__tests__/update-response.ts b/src/entities/produtosFornecedores/__tests__/update-response.ts new file mode 100644 index 0000000..74e6d6e --- /dev/null +++ b/src/entities/produtosFornecedores/__tests__/update-response.ts @@ -0,0 +1,21 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + id: 12345678, + descricao: 'Copo do Bling', + codigo: 'COD-123', + precoCusto: 5.9, + precoCompra: 3.5, + padrao: false, + produto: { + id: 12345678 + }, + fornecedor: { + id: 12345678 + }, + garantia: 3 +} diff --git a/src/entities/produtosFornecedores/index.ts b/src/entities/produtosFornecedores/index.ts new file mode 100644 index 0000000..4e48da8 --- /dev/null +++ b/src/entities/produtosFornecedores/index.ts @@ -0,0 +1,112 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Produtos - Fornecedores. + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Fornecedores + */ +export class ProdutosFornecedores extends Entity { + /** + * Remove um produto fornecedor. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Fornecedores/delete_produtos_fornecedores__idProdutoFornecedor_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'produtos/fornecedores', + id: String(params.idProdutoFornecedor) + }) + } + + /** + * Obtém produtos fornecedores. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Fornecedores/get_produtos_fornecedores + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'produtos/fornecedores', + params: { + pagina: params?.pagina, + limite: params?.limite, + idProduto: params?.idProduto, + idFornecedor: params?.idFornecedor + } + }) + } + + /** + * Obtém um produto fornecedor. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Fornecedores/get_produtos_fornecedores__idProdutoFornecedor_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'produtos/fornecedores', + id: String(params.idProdutoFornecedor) + }) + } + + /** + * Cria um produto fornecedor. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Fornecedores/post_produtos_fornecedores + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'produtos/fornecedores', + body + }) + } + + /** + * Altera um produto fornecedor. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Fornecedores/put_produtos_fornecedores__idProdutoFornecedor_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idProdutoFornecedor, ...body } = params + + return await this.repository.replace({ + endpoint: 'produtos/fornecedores', + id: String(idProdutoFornecedor), + body + }) + } +} diff --git a/src/entities/produtosFornecedores/interfaces/create.interface.ts b/src/entities/produtosFornecedores/interfaces/create.interface.ts new file mode 100644 index 0000000..efe5ddb --- /dev/null +++ b/src/entities/produtosFornecedores/interfaces/create.interface.ts @@ -0,0 +1,14 @@ +export interface ICreateBody { + descricao?: string + codigo?: string + precoCusto?: number + precoCompra?: number + padrao?: boolean + produto: { id: number } + fornecedor: { id: number } + garantia?: number +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/produtosFornecedores/interfaces/delete.interface.ts b/src/entities/produtosFornecedores/interfaces/delete.interface.ts new file mode 100644 index 0000000..0b33517 --- /dev/null +++ b/src/entities/produtosFornecedores/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do produto fornecedor + */ + idProdutoFornecedor: number +} diff --git a/src/entities/produtosFornecedores/interfaces/find.interface.ts b/src/entities/produtosFornecedores/interfaces/find.interface.ts new file mode 100644 index 0000000..e7c293b --- /dev/null +++ b/src/entities/produtosFornecedores/interfaces/find.interface.ts @@ -0,0 +1,20 @@ +export interface IFindParams { + /** + * ID do produto fornecedor + */ + idProdutoFornecedor: number +} + +export interface IFindResponse { + data: { + id?: number + descricao?: string + codigo?: string + precoCusto?: number + precoCompra?: number + padrao?: boolean + produto: { id: number } + fornecedor: { id: number } + garantia?: number + } +} diff --git a/src/entities/produtosFornecedores/interfaces/get.interface.ts b/src/entities/produtosFornecedores/interfaces/get.interface.ts new file mode 100644 index 0000000..14e1439 --- /dev/null +++ b/src/entities/produtosFornecedores/interfaces/get.interface.ts @@ -0,0 +1,31 @@ +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * ID do produto + */ + idProduto?: number + /** + * ID do contato do tipo fornecedor + */ + idFornecedor?: number +} + +export interface IGetResponse { + data: { + id?: number + descricao?: string + codigo?: string + precoCusto?: number + precoCompra?: number + padrao?: boolean + produto?: { id: number } + fornecedor?: { id: number } + }[] +} diff --git a/src/entities/produtosFornecedores/interfaces/update.interface.ts b/src/entities/produtosFornecedores/interfaces/update.interface.ts new file mode 100644 index 0000000..2d224f4 --- /dev/null +++ b/src/entities/produtosFornecedores/interfaces/update.interface.ts @@ -0,0 +1,21 @@ +export interface IUpdateParams { + /** + * ID do produto fornecedor + */ + idProdutoFornecedor: number +} + +export interface IUpdateBody { + descricao?: string + codigo?: string + precoCusto?: number + precoCompra?: number + padrao?: boolean + produto: { id: number } + fornecedor: { id: number } + garantia?: number +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/produtosLojas/__tests__/create-response.ts b/src/entities/produtosLojas/__tests__/create-response.ts new file mode 100644 index 0000000..2a91711 --- /dev/null +++ b/src/entities/produtosLojas/__tests__/create-response.ts @@ -0,0 +1,33 @@ +export default { + data: { + id: 12345678, + categoriasProdutos: [ + { + id: 12345678 + } + ] + } +} + +export const createRequestBody = { + codigo: 'ASDF1234', + preco: 25.99, + precoPromocional: 22.99, + produto: { + id: 12345678 + }, + loja: { + id: 12345678 + }, + fornecedorLoja: { + id: 12345678 + }, + marcaLoja: { + id: 12345678 + }, + categoriasProdutos: [ + { + id: 12345678 + } + ] +} diff --git a/src/entities/produtosLojas/__tests__/delete-response.ts b/src/entities/produtosLojas/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/produtosLojas/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/produtosLojas/__tests__/find-response.ts b/src/entities/produtosLojas/__tests__/find-response.ts new file mode 100644 index 0000000..e57ea7b --- /dev/null +++ b/src/entities/produtosLojas/__tests__/find-response.ts @@ -0,0 +1,25 @@ +export default { + data: { + id: 12345678, + codigo: 'ASDF1234', + preco: 25.99, + precoPromocional: 22.99, + produto: { + id: 12345678 + }, + loja: { + id: 12345678 + }, + fornecedorLoja: { + id: 12345678 + }, + marcaLoja: { + id: 12345678 + }, + categoriasProdutos: [ + { + id: 12345678 + } + ] + } +} diff --git a/src/entities/produtosLojas/__tests__/get-response.ts b/src/entities/produtosLojas/__tests__/get-response.ts new file mode 100644 index 0000000..3564673 --- /dev/null +++ b/src/entities/produtosLojas/__tests__/get-response.ts @@ -0,0 +1,11 @@ +export default { + data: [ + { + categoriasProdutos: [ + { + id: 12345678 + } + ] + } + ] +} diff --git a/src/entities/produtosLojas/__tests__/index.spec.ts b/src/entities/produtosLojas/__tests__/index.spec.ts new file mode 100644 index 0000000..a00ef0f --- /dev/null +++ b/src/entities/produtosLojas/__tests__/index.spec.ts @@ -0,0 +1,102 @@ +import { Chance } from 'chance' +import { ProdutosLojas } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import getResponse from './get-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Produtos - Lojas entity', () => { + let repository: InMemoryBlingRepository + let entity: ProdutosLojas + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ProdutosLojas(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idProdutoLoja = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idProdutoLoja }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/lojas', + id: String(idProdutoLoja) + }) + expect(response).toBe(deleteResponse) + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/lojas', + params: { + limite: undefined, + pagina: undefined, + idProduto: undefined, + idLoja: undefined, + idCategoriaProduto: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idProdutoLoja = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idProdutoLoja }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/lojas', + id: String(idProdutoLoja) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/lojas', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idProdutoLoja = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idProdutoLoja, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/lojas', + id: String(idProdutoLoja), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/produtosLojas/__tests__/update-response.ts b/src/entities/produtosLojas/__tests__/update-response.ts new file mode 100644 index 0000000..4895ff3 --- /dev/null +++ b/src/entities/produtosLojas/__tests__/update-response.ts @@ -0,0 +1,33 @@ +export default { + data: { + id: 12345678, + categoriasProdutos: [ + { + id: 12345678 + } + ] + } +} + +export const updateRequestBody = { + codigo: 'ASDF1234', + preco: 25.99, + precoPromocional: 22.99, + produto: { + id: 12345678 + }, + loja: { + id: 12345678 + }, + fornecedorLoja: { + id: 12345678 + }, + marcaLoja: { + id: 12345678 + }, + categoriasProdutos: [ + { + id: 12345678 + } + ] +} diff --git a/src/entities/produtosLojas/index.ts b/src/entities/produtosLojas/index.ts new file mode 100644 index 0000000..90deb9e --- /dev/null +++ b/src/entities/produtosLojas/index.ts @@ -0,0 +1,113 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Produtos - Lojas. + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Lojas + */ +export class ProdutosLojas extends Entity { + /** + * Remove o vínculo de um produto com uma loja. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Lojas/delete_produtos_lojas__idProdutoLoja_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'produtos/lojas', + id: String(params.idProdutoLoja) + }) + } + + /** + * Obtém vínculos de produtos com lojas. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Lojas/get_produtos_lojas + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'produtos/lojas', + params: { + pagina: params?.pagina, + limite: params?.limite, + idProduto: params?.idProduto, + idLoja: params?.idLoja, + idCategoriaProduto: params?.idCategoriaProduto + } + }) + } + + /** + * Obtém um vínculo de produto com loja. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Lojas/get_produtos_lojas__idProdutoLoja_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'produtos/lojas', + id: String(params.idProdutoLoja) + }) + } + + /** + * Cria o vínculo de um produto com uma loja. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Lojas/post_produtos_lojas + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'produtos/lojas', + body + }) + } + + /** + * Altera o vínculo de um produto com uma loja. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Lojas/put_produtos_lojas__idProdutoLoja_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idProdutoLoja, ...body } = params + + return await this.repository.replace({ + endpoint: 'produtos/lojas', + id: String(idProdutoLoja), + body + }) + } +} diff --git a/src/entities/produtosLojas/interfaces/create.interface.ts b/src/entities/produtosLojas/interfaces/create.interface.ts new file mode 100644 index 0000000..8009e6b --- /dev/null +++ b/src/entities/produtosLojas/interfaces/create.interface.ts @@ -0,0 +1,17 @@ +export interface ICreateBody { + codigo: string + preco?: number + precoPromocional?: number + produto: { id: number } + loja: { id: number } + fornecedorLoja?: { id: number } + marcaLoja?: { id: number } + categoriasProdutos?: { id: number }[] +} + +export interface ICreateResponse { + data: { + id: number + categoriasProdutos?: { id?: number }[] + } +} diff --git a/src/entities/produtosLojas/interfaces/delete.interface.ts b/src/entities/produtosLojas/interfaces/delete.interface.ts new file mode 100644 index 0000000..e19368b --- /dev/null +++ b/src/entities/produtosLojas/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID do vínculo do produto com a loja + */ + idProdutoLoja: number +} diff --git a/src/entities/produtosLojas/interfaces/find.interface.ts b/src/entities/produtosLojas/interfaces/find.interface.ts new file mode 100644 index 0000000..0c0b60d --- /dev/null +++ b/src/entities/produtosLojas/interfaces/find.interface.ts @@ -0,0 +1,20 @@ +export interface IFindParams { + /** + * ID do vínculo do produto com a loja + */ + idProdutoLoja: number +} + +export interface IFindResponse { + data: { + id?: number + codigo: string + preco?: number + precoPromocional?: number + produto: { id: number } + loja: { id: number } + fornecedorLoja?: { id: number } + marcaLoja?: { id: number } + categoriasProdutos?: { id: number }[] + } +} diff --git a/src/entities/produtosLojas/interfaces/get.interface.ts b/src/entities/produtosLojas/interfaces/get.interface.ts new file mode 100644 index 0000000..d84a1f1 --- /dev/null +++ b/src/entities/produtosLojas/interfaces/get.interface.ts @@ -0,0 +1,30 @@ +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * ID do produto + */ + idProduto?: number + /** + * ID da loja + */ + idLoja?: number + /** + * ID da categoria do produto vinculada à loja + */ + idCategoriaProduto?: number +} + +export interface IGetResponse { + data: { + categoriasProdutos: { + id: 12345678 + }[] + }[] +} diff --git a/src/entities/produtosLojas/interfaces/update.interface.ts b/src/entities/produtosLojas/interfaces/update.interface.ts new file mode 100644 index 0000000..7865758 --- /dev/null +++ b/src/entities/produtosLojas/interfaces/update.interface.ts @@ -0,0 +1,24 @@ +export interface IUpdateParams { + /** + * ID do vínculo do produto com a loja + */ + idProdutoLoja: number +} + +export interface IUpdateBody { + codigo: string + preco?: number + precoPromocional?: number + produto: { id: number } + loja: { id: number } + fornecedorLoja?: { id: number } + marcaLoja?: { id: number } + categoriasProdutos?: { id: number }[] +} + +export interface IUpdateResponse { + data: { + id: number + categoriasProdutos?: { id?: number }[] + } +} diff --git a/src/entities/produtosVariacoes/__tests__/change-attribute-name-response.ts b/src/entities/produtosVariacoes/__tests__/change-attribute-name-response.ts new file mode 100644 index 0000000..686a857 --- /dev/null +++ b/src/entities/produtosVariacoes/__tests__/change-attribute-name-response.ts @@ -0,0 +1,12 @@ +export default { + data: [ + { + id: 12345678 + } + ] +} + +export const changeAttributeNameRequest = { + atributoAntigo: 'Cor', + atributoNovo: 'Coloração' +} diff --git a/src/entities/produtosVariacoes/__tests__/find-response.ts b/src/entities/produtosVariacoes/__tests__/find-response.ts new file mode 100644 index 0000000..c699c21 --- /dev/null +++ b/src/entities/produtosVariacoes/__tests__/find-response.ts @@ -0,0 +1,226 @@ +export default { + data: { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: '', + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 'Utilize para atualizar o valor existente. Ex: 123456789', + valor: '256GB', + item: 'Opção A' + } + ], + variacoes: [ + { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: '', + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: + 'Utilize para atualizar o valor existente. Ex: 123456789', + valor: '256GB', + item: 'Opção A' + } + ], + variacao: { + nome: 'Tamanho:G;Cor:Verde', + ordem: 1, + produtoPai: { + cloneInfo: true + } + } + } + ] + } +} diff --git a/src/entities/produtosVariacoes/__tests__/generate-combinations-response.ts b/src/entities/produtosVariacoes/__tests__/generate-combinations-response.ts new file mode 100644 index 0000000..3c46e57 --- /dev/null +++ b/src/entities/produtosVariacoes/__tests__/generate-combinations-response.ts @@ -0,0 +1,238 @@ +export default { + data: { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: '', + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: 'Utilize para atualizar o valor existente. Ex: 123456789', + valor: '256GB', + item: 'Opção A' + } + ], + variacoes: [ + { + id: 123456789, + nome: 'Produto 1', + codigo: 'CODE_123', + preco: 1, + tipo: 'P' as const, + situacao: 'A' as const, + formato: 'S' as const, + descricaoCurta: 'Descrição curta', + dataValidade: '2020-01-01', + unidade: 'UN', + pesoLiquido: 1, + pesoBruto: 1, + volumes: 1, + itensPorCaixa: 1, + gtin: '1234567890123', + gtinEmbalagem: '1234567890123', + tipoProducao: 'P' as const, + condicao: 0 as const, + freteGratis: false, + marca: 'Marca', + descricaoComplementar: 'Descrição complementar', + linkExterno: 'https://www.google.com', + observacoes: 'Observações', + categoria: { + id: 123456789 + }, + estoque: { + minimo: 1, + maximo: 100, + crossdocking: 1, + localizacao: '14A' + }, + actionEstoque: '', + dimensoes: { + largura: 1, + altura: 1, + profundidade: 1, + unidadeMedida: 1 + }, + tributacao: { + origem: 0, + nFCI: '', + ncm: '', + cest: '', + codigoListaServicos: '', + spedTipoItem: '', + codigoItem: '', + percentualTributos: 0, + valorBaseStRetencao: 0, + valorStRetencao: 0, + valorICMSSubstituto: 0, + codigoExcecaoTipi: '', + classeEnquadramentoIpi: '', + valorIpiFixo: 0, + codigoSeloIpi: '', + valorPisFixo: 0, + valorCofinsFixo: 0, + codigoANP: '', + descricaoANP: '', + percentualGLP: 0, + percentualGasNacional: 0, + percentualGasImportado: 0, + valorPartida: 0, + tipoArmamento: 0 as const, + descricaoCompletaArmamento: '', + dadosAdicionais: '', + grupoProduto: { + id: 123456789 + } + }, + midia: { + video: { + url: 'https://www.youtube.com/watch?v=1' + }, + imagens: { + externas: [ + { + link: 'https://shutterstock.com/lalala123' + } + ] + } + }, + linhaProduto: { + id: 1 + }, + estrutura: { + tipoEstoque: 'F' as const, + lancamentoEstoque: 'A' as const, + componentes: [ + { + produto: { + id: 1 + }, + quantidade: 2.1 + } + ] + }, + camposCustomizados: [ + { + idCampoCustomizado: 123456789, + idVinculo: + 'Utilize para atualizar o valor existente. Ex: 123456789', + valor: '256GB', + item: 'Opção A' + } + ], + variacao: { + nome: 'Tamanho:G;Cor:Verde', + ordem: 1, + produtoPai: { + cloneInfo: true + } + } + } + ] + } +} + +export const generateCombinationsRequest = { + produtoPai: { + id: 123456789 + }, + atributos: [ + { + nome: 'Cor', + opcoes: ['Azul', 'Vermelho'] + } + ] +} diff --git a/src/entities/produtosVariacoes/__tests__/index.spec.ts b/src/entities/produtosVariacoes/__tests__/index.spec.ts new file mode 100644 index 0000000..d3ce6da --- /dev/null +++ b/src/entities/produtosVariacoes/__tests__/index.spec.ts @@ -0,0 +1,73 @@ +import { Chance } from 'chance' +import { ProdutosVariacoes } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import changeAttributeNameResponse, { + changeAttributeNameRequest +} from './change-attribute-name-response' +import findResponse from './find-response' +import generateCombinationsResponse, { + generateCombinationsRequest +} from './generate-combinations-response' + +const chance = Chance() + +describe('Produtos - Variações entity', () => { + let repository: InMemoryBlingRepository + let entity: ProdutosVariacoes + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new ProdutosVariacoes(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idProdutoPai = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idProdutoPai }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/variacoes', + id: String(idProdutoPai) + }) + expect(response).toBe(findResponse) + }) + + it('should change attribute name successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const idProdutoPai = chance.natural() + repository.setResponse(changeAttributeNameResponse) + + const response = await entity.changeAttributeName({ + idProdutoPai, + ...changeAttributeNameRequest + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/variacoes', + id: `${idProdutoPai}/atributos`, + body: changeAttributeNameRequest + }) + expect(response).toBe(changeAttributeNameResponse) + }) + + it('should generate combinations successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(generateCombinationsResponse) + + const response = await entity.generateCombinations( + generateCombinationsRequest + ) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'produtos/variacoes/atributos/gerar-combinacoes', + body: generateCombinationsRequest + }) + expect(response).toBe(generateCombinationsResponse) + }) +}) diff --git a/src/entities/produtosVariacoes/index.ts b/src/entities/produtosVariacoes/index.ts new file mode 100644 index 0000000..92d751f --- /dev/null +++ b/src/entities/produtosVariacoes/index.ts @@ -0,0 +1,75 @@ +import { Entity } from '../@shared/entity' +import { + IChangeAttributeNameBody, + IChangeAttributeNameParams, + IChangeAttributeNameResponse +} from './interfaces/change-attribute-name.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { + IGenerateCombinationsBody, + IGenerateCombinationsResponse +} from './interfaces/generate-combinations.interface' + +/** + * Entidade para interação com Produtos - Variações. + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Varia%C3%A7%C3%B5es + */ +export class ProdutosVariacoes extends Entity { + /** + * Obtém o produto e variações. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Varia%C3%A7%C3%B5es/get_produtos_variacoes__idProdutoPai_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'produtos/variacoes', + id: String(params.idProdutoPai) + }) + } + + /** + * Altera o nome do atributo nas variações. + * + * @param {IChangeAttributeNameParams & IChangeAttributeNameBody} params Parâmetros da alteração. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Varia%C3%A7%C3%B5es/patch_produtos_variacoes__idProdutoPai__atributos + */ + public async changeAttributeName( + params: IChangeAttributeNameParams & IChangeAttributeNameBody + ): Promise { + const { idProdutoPai, ...body } = params + return await this.repository.update({ + endpoint: 'produtos/variacoes', + id: `${idProdutoPai}/atributos`, + body + }) + } + + /** + * Retorna o produto pai com combinações de novas variações. + * + * @param {IGenerateCombinationsBody} params O conteúdo para a geração das combinações. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Produtos%20-%20Varia%C3%A7%C3%B5es/post_produtos_variacoes_atributos_gerar_combinacoes + */ + public async generateCombinations( + params: IGenerateCombinationsBody + ): Promise { + return await this.repository.store({ + endpoint: 'produtos/variacoes/atributos/gerar-combinacoes', + body: params + }) + } +} diff --git a/src/entities/produtosVariacoes/interfaces/change-attribute-name.interface.ts b/src/entities/produtosVariacoes/interfaces/change-attribute-name.interface.ts new file mode 100644 index 0000000..0d36650 --- /dev/null +++ b/src/entities/produtosVariacoes/interfaces/change-attribute-name.interface.ts @@ -0,0 +1,15 @@ +export interface IChangeAttributeNameParams { + /** + * ID do produto pai + */ + idProdutoPai: number +} + +export interface IChangeAttributeNameBody { + atributoAntigo: string + atributoNovo: string +} + +export interface IChangeAttributeNameResponse { + data: { id: number }[] +} diff --git a/src/entities/produtosVariacoes/interfaces/find.interface.ts b/src/entities/produtosVariacoes/interfaces/find.interface.ts new file mode 100644 index 0000000..c0ddf04 --- /dev/null +++ b/src/entities/produtosVariacoes/interfaces/find.interface.ts @@ -0,0 +1,198 @@ +import { IActionEstoque } from '../types/action-estoque.type' +import { ICondicao } from '../types/condicao.type' +import { IEstruturaLancamentoEstoque } from '../types/estrutura-lancamento-estoque.type' +import { IEstruturaTipoEstoque } from '../types/estrutura-tipo-estoque.type' +import { IFormato } from '../types/formato.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoArmamento } from '../types/tipo-armamento.type' +import { ITipoProducao } from '../types/tipo-producao.type' +import { ITipo } from '../types/tipo.type' + +export interface IFindParams { + /** + * ID do produto pai + */ + idProdutoPai: number +} + +export interface IFindResponse { + data: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacoes: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacao: { + nome: string + ordem: number + produtoPai: { cloneInfo: boolean } + } + }[] + } +} diff --git a/src/entities/produtosVariacoes/interfaces/generate-combinations.interface.ts b/src/entities/produtosVariacoes/interfaces/generate-combinations.interface.ts new file mode 100644 index 0000000..e615cf9 --- /dev/null +++ b/src/entities/produtosVariacoes/interfaces/generate-combinations.interface.ts @@ -0,0 +1,199 @@ +import { IActionEstoque } from '../types/action-estoque.type' +import { ICondicao } from '../types/condicao.type' +import { IEstruturaLancamentoEstoque } from '../types/estrutura-lancamento-estoque.type' +import { IEstruturaTipoEstoque } from '../types/estrutura-tipo-estoque.type' +import { IFormato } from '../types/formato.type' +import { ISituacao } from '../types/situacao.type' +import { ITipoArmamento } from '../types/tipo-armamento.type' +import { ITipoProducao } from '../types/tipo-producao.type' +import { ITipo } from '../types/tipo.type' + +export interface IGenerateCombinationsBody { + produtoPai: { id?: number } + atributos: { + nome: string + opcoes?: string[] + }[] +} + +export interface IGenerateCombinationsResponse { + data: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacoes: { + id?: number + nome: string + codigo?: string + preco?: number + tipo: ITipo + situacao: ISituacao + formato: IFormato + descricaoCurta?: string + dataValidade?: string + unidade?: string + pesoLiquido?: number + pesoBruto?: number + volumes?: number + itensPorCaixa?: number + gtin?: string + gtinEmbalagem?: string + tipoProducao?: ITipoProducao + condicao?: ICondicao + freteGratis?: boolean + marca?: string + descricaoComplementar?: string + linkExterno?: string + observacoes?: string + categoria?: { id: number } + estoque?: { + minimo?: number + maximo?: number + crossdocking?: number + localizacao?: string + } + actionEstoque?: IActionEstoque + dimensoes?: { + largura?: number + altura?: number + profundidade?: number + unidadeMedida?: number + } + tributacao?: { + origem?: number + nFCI?: string + ncm?: string + cest?: string + codigoListaServicos?: string + spedTipoItem?: string + codigoItem?: string + percentualTributos?: number + valorBaseStRetencao?: number + valorStRetencao?: number + valorICMSSubstituto?: number + codigoExcecaoTipi?: string + classeEnquadramentoIpi?: string + valorIpiFixo?: number + codigoSeloIpi?: string + valorPisFixo?: number + valorCofinsFixo?: number + codigoANP?: string + descricaoANP?: string + percentualGLP?: number + percentualGasNacional?: number + percentualGasImportado?: number + valorPartida?: number + tipoArmamento?: ITipoArmamento + descricaoCompletaArmamento?: string + dadosAdicionais?: string + grupoProduto?: { id: number } + } + midia?: { + video: { url: string } + imagens: { externas: { link: string }[] } + } + linhaProduto?: { id: number } + estrutura?: { + tipoEstoque: IEstruturaTipoEstoque + lancamentoEstoque: IEstruturaLancamentoEstoque + componentes: { + produto: { id: number } + quantidade: number + }[] + } + camposCustomizados?: { + idCampoCustomizado: number + idVinculo?: number + valor?: string + item?: string + }[] + variacao: { + nome: string + ordem: number + produtoPai: { cloneInfo: boolean } + } + }[] + } +} diff --git a/src/entities/produtosVariacoes/types/action-estoque.type.ts b/src/entities/produtosVariacoes/types/action-estoque.type.ts new file mode 100644 index 0000000..012232f --- /dev/null +++ b/src/entities/produtosVariacoes/types/action-estoque.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à ação de estoque ao transformar um produto simples em + * variação. + * + * - `Z`: Irá zerar os saldos de estoque + * - `T`: Transfere o estoque do produto pai para a primeira variação informada + */ +export type IActionEstoque = 'Z' | 'T' diff --git a/src/entities/produtosVariacoes/types/condicao.type.ts b/src/entities/produtosVariacoes/types/condicao.type.ts new file mode 100644 index 0000000..53ed33c --- /dev/null +++ b/src/entities/produtosVariacoes/types/condicao.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente à condição do produto. + * + * - `0`: Não especificado + * - `1`: Novo + * - `2`: Usado + */ +export type ICondicao = 0 | 1 | 2 diff --git a/src/entities/produtosVariacoes/types/estrutura-lancamento-estoque.type.ts b/src/entities/produtosVariacoes/types/estrutura-lancamento-estoque.type.ts new file mode 100644 index 0000000..4823a94 --- /dev/null +++ b/src/entities/produtosVariacoes/types/estrutura-lancamento-estoque.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao lançamento de estoque para a estrutura. + * + * - `A`: Produto e Componente + * - `M`: Componente + * - `P`: Produto + */ +export type IEstruturaLancamentoEstoque = 'A' | 'M' | 'P' diff --git a/src/entities/produtosVariacoes/types/estrutura-tipo-estoque.type.ts b/src/entities/produtosVariacoes/types/estrutura-tipo-estoque.type.ts new file mode 100644 index 0000000..a835dd1 --- /dev/null +++ b/src/entities/produtosVariacoes/types/estrutura-tipo-estoque.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo do estoque para a estrutura. + * + * - `F`: Físico + * - `V`: Virtual + */ +export type IEstruturaTipoEstoque = 'F' | 'V' diff --git a/src/entities/produtosVariacoes/types/formato.type.ts b/src/entities/produtosVariacoes/types/formato.type.ts new file mode 100644 index 0000000..79c6437 --- /dev/null +++ b/src/entities/produtosVariacoes/types/formato.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao formato do produto. + * + * - `S`: Simples + * - `V`: Com variações + * - `E`: Com composição + */ +export type IFormato = 'S' | 'V' | 'E' diff --git a/src/entities/produtosVariacoes/types/situacao.type.ts b/src/entities/produtosVariacoes/types/situacao.type.ts new file mode 100644 index 0000000..bffb723 --- /dev/null +++ b/src/entities/produtosVariacoes/types/situacao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente à situação de um produto. + * + * - `A`: Ativo + * - `I`: Inativo + */ +export type ISituacao = 'A' | 'I' diff --git a/src/entities/produtosVariacoes/types/tipo-armamento.type.ts b/src/entities/produtosVariacoes/types/tipo-armamento.type.ts new file mode 100644 index 0000000..277ff6d --- /dev/null +++ b/src/entities/produtosVariacoes/types/tipo-armamento.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo do armamento (quando o produto se referir a um). + * + * - `0`: Uso permitido + * - `1`: Uso restrito + */ +export type ITipoArmamento = 0 | 1 diff --git a/src/entities/produtosVariacoes/types/tipo-producao.type.ts b/src/entities/produtosVariacoes/types/tipo-producao.type.ts new file mode 100644 index 0000000..023e52e --- /dev/null +++ b/src/entities/produtosVariacoes/types/tipo-producao.type.ts @@ -0,0 +1,7 @@ +/** + * Tipagem referente ao tipo da produção do produto. + * + * - `P`: Própria + * - `T`: Terceiros + */ +export type ITipoProducao = 'P' | 'T' diff --git a/src/entities/produtosVariacoes/types/tipo.type.ts b/src/entities/produtosVariacoes/types/tipo.type.ts new file mode 100644 index 0000000..73e99f9 --- /dev/null +++ b/src/entities/produtosVariacoes/types/tipo.type.ts @@ -0,0 +1,8 @@ +/** + * Tipagem referente ao tipo de um produto. + * + * - `S`: Serviço + * - `P`: Produto + * - `N`: Serviço 06 21 22 + */ +export type ITipo = 'S' | 'P' | 'N' diff --git a/src/entities/purchaseOrders.ts b/src/entities/purchaseOrders.ts deleted file mode 100644 index 9ce22bc..0000000 --- a/src/entities/purchaseOrders.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IPurchaseOrder { - numeropedido?: string - datacompra?: string - dataprevista?: string - ordemcompra?: string - desconto?: string - observacoes?: string - observacaointerna?: string - idcategoria?: number - fornecedor: { - id?: number - nome?: string - tipopessoa?: 'F' | 'J' | 'E' - /** - * used if !!id - */ - cpfcnpj?: string - ie?: string - rg?: string - contribuinte?: '1' | '2' | '9' - endereco?: string - endereconro?: string - complemento?: string - bairro?: string - cep?: string - cidade?: string - uf?: string - fone?: string - celular?: string - email?: string - } - transporte?: { - transportador?: string - freteporconta?: 'R' | 'D' | 'T' | '3' | '4' | 'S' - qtdvolumes?: number - frete?: number - } - itens: { - item: { - codigo?: string - descricao: string - un?: 'pc' | 'un' | 'cx' - qtde: number - valor: number - } - }[] - parcelas?: { - parcela?: { - nrodias: number - valor: number - obs?: string - idformapagamento: number - } - }[] -} - -export interface IPurchaseOrderUpdateContent { - situacao: '0' | '1' | '2' | '3' -} - -export interface IPurchaseOrderFilters { - dataEmissao?: string - situacao?: '0' | '1' | '2' | '3' -} - -export type IPurchaseOrderInfos = Record - -export interface IPurchaseOrderResponse { - numeropedido: string - datacompra: string - dataprevista?: string - ordemcompra?: string - desconto?: string - observacoes?: string - observacaointerna?: string - fornecedor: { - id: string - nome: string - tipopessoa: 'F' | 'J' | 'E' - cpfcnpj?: string - ie?: string - rg?: string - contribuinte: '1' | '2' | '9' - endereco?: string - endereconro?: string - complemento?: string - bairro?: string - cep?: string - cidade?: string - uf?: string - fone?: string - celular?: string - email?: string - } - itens: { - item: { - codigo?: string - codigofornecedor?: string - descricao: string - un?: string - qtde: number - valor: number - } - }[] - transporte: { - transportador?: string - freteporconta: 'R' | 'D' | 'T' | '3' | '4' | 'S' - qtdvolumes: string - frete: number - } - categoria: { - id: number - descricao: string - } -} - -export interface IPurchaseOrderCreateResponse { - id: number - numeropedido: number - mensagens?: { - mensagem: string - }[] -} - -export interface IPurchaseOrderUpdateResponse { - numero: string - mensagem: string -} - -export default function PurchaseOrders (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'pedidocompra', - pluralName: 'pedidoscompra' - } - - return Object.assign(config, { - all: new All< - IPurchaseOrderResponse, - IPurchaseOrderFilters, - IPurchaseOrderInfos - >().all, - find: new Find().find, - findBy: new FindBy< - IPurchaseOrderResponse, - IPurchaseOrderFilters, - IPurchaseOrderInfos - >().findBy, - create: new Create().create, - update: new Update< - IPurchaseOrderUpdateContent, - IPurchaseOrderUpdateResponse - >().update - }) -} diff --git a/src/entities/serviceInvoices.ts b/src/entities/serviceInvoices.ts deleted file mode 100644 index 7fe9681..0000000 --- a/src/entities/serviceInvoices.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import FindBy from '../core/functions/findBy' -import Create from '../core/functions/create' - -import IUFs from './types/uf' - -type ISituacaoNumber = '0' | '1' | '2' | '3' - -type ISituacaoName = 'Todas' | 'Pendente' | 'Emitida' | 'Cancelada' - -export interface IServiceInvoice { - data?: Date - vendedor?: string - numero_rps?: string - reter_iss?: 'S' | 'N' - desconto?: number - cliente: { - nome: string - cnpj: string - ie?: string - im?: string - endereco: string - numero: string - complemento?: string - bairro: string - cep: string - cidade: string - uf: IUFs - fone?: string - email: string - } - servicos: { - servico: { - descricao: string - valor: number - codigo: string - } - } - parcelas?: { - parcela: { - dias?: number - data?: Date - vlr: number - obs?: string - forma?: string - } - }[] -} - -export interface IServiceInvoiceFilters { - dataEmissao?: string - situacao?: ISituacaoNumber -} - -export type IServiceInvoiceInfos = Record - -export interface IServiceInvoiceCreateResponse { - numero: string -} - -export interface IServiceInvoiceSendResponse { - serie: string - numero: string - numeronfse: string - situacao: ISituacaoName - cliente: { - name: string - cnpj: string - email: string - } - dataEmissao: string - valorNota: number - linkNFSe: string - codigoVerificacao: string -} - -export interface IServiceInvoiceResponse { - serie: string - numero: string - numeroNFSe?: string - situacao: ISituacaoName - contato: string - dataEmissao: string - valorNota: number - linkNFSe: string - codigoVerificacao: string -} - -export default function ServiceInvoices (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'notaservico', - pluralName: 'notasservico' - } - - const send = async ( - numero: number | string, - serie: number | string, - options?: { - raw?: boolean - } - ) => { - const createMethod = new Create< - Record, - IServiceInvoiceSendResponse - >({ - ...config, - endpoint: `${config.singularName}/${numero}/${serie}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - // @TODO: see how to reuse the code below - if (options) { - if (raw) { - return await createMethod.create({}, { raw: true }) - } else { - return await createMethod.create({}, { raw: false }) - } - } else { - return await createMethod.create({}) - } - } - - return Object.assign(config, { - all: new All< - IServiceInvoiceResponse, - IServiceInvoiceFilters, - IServiceInvoiceInfos - >().all, - find: new Find().find, - findBy: new FindBy< - IServiceInvoiceResponse, - IServiceInvoiceFilters, - IServiceInvoiceInfos - >().findBy, - create: new Create().create, - send - }) -} diff --git a/src/entities/shopCategories.ts b/src/entities/shopCategories.ts deleted file mode 100644 index 9bad35a..0000000 --- a/src/entities/shopCategories.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { IApiInstance } from '../core/interfaces/method' - -import All from '../core/functions/all' -import Find from '../core/functions/find' -import Create from '../core/functions/create' -import Update from '../core/functions/update' - -export interface IShopCategory { - idCategoria: number - idVinculoLoja: number - descricaoVinculo: string -} - -export type IShopCategoryFilters = Record - -export type IShopCategoryInfos = Record - -export interface IShopCategoryResponse { - idCategoria: number - idVinculoLoja: string - descricaoVinculo: string -} - -export default function ShopCategories (api: IApiInstance, raw: boolean) { - const config = { - api, - raw, - singularName: 'categoriaLoja', - pluralName: 'categoriasLoja' - } - - const all = async ( - idLoja: number | string, - options?: { raw?: boolean; page?: number } - ) => { - const allMethod = new All< - IShopCategoryResponse, - IShopCategoryFilters, - IShopCategoryInfos - >({ - ...config, - endpoint: `categoriasLoja/${idLoja}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - if (options) { - if (raw && options.page) { - return await allMethod.all({ raw: true, page: options.page }) - } - - if (raw) { - return await allMethod.all({ raw: true }) - } - - if (options.page) { - return await allMethod.all({ page: options.page }) - } - } else { - return await allMethod.all({ raw: false }) - } - } - - const find = async ( - idLoja: number | string, - idCategoria: number | string, - options?: { raw?: boolean } - ) => { - const findMethod = new Find({ - ...config, - endpoint: `categoriasLoja/${idLoja}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - if (raw) { - return await findMethod.find(idCategoria, { raw: true }) - } else { - return await findMethod.find(idCategoria, { raw: false }) - } - } - - const create = async ( - idLoja: number | string, - data: IShopCategory, - options?: { - raw?: boolean - } - ) => { - const createMethod = new Create({ - ...config, - endpoint: `categoriasLoja/${idLoja}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - if (raw) { - return await createMethod.create(data, { raw: true }) - } else { - return await createMethod.create(data, { raw: false }) - } - } - - const update = async ( - idLoja: number | string, - idCategoria: number | string, - data: IShopCategory, - options?: { - raw?: boolean - } - ) => { - const updateMethod = new Update({ - ...config, - endpoint: `categoriasLoja/${idLoja}` - }) - - const raw = options && options.raw !== undefined ? options.raw : config.raw - - if (raw) { - return await updateMethod.update(idCategoria, data, { raw: true }) - } else { - return await updateMethod.update(idCategoria, data, { raw: false }) - } - } - - return Object.assign(config, { - all, - find, - create, - update - }) -} diff --git a/src/entities/situacoes/__tests__/create-response.ts b/src/entities/situacoes/__tests__/create-response.ts new file mode 100644 index 0000000..9fffa76 --- /dev/null +++ b/src/entities/situacoes/__tests__/create-response.ts @@ -0,0 +1,12 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + idModuloSistema: 6423808065, + nome: 'Finalizado', + idHerdado: 0, + cor: '#E9DC40' +} diff --git a/src/entities/situacoes/__tests__/delete-response.ts b/src/entities/situacoes/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/situacoes/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/situacoes/__tests__/find-response.ts b/src/entities/situacoes/__tests__/find-response.ts new file mode 100644 index 0000000..ecb8115 --- /dev/null +++ b/src/entities/situacoes/__tests__/find-response.ts @@ -0,0 +1,8 @@ +export default { + data: { + id: 9, + nome: 'Em aberto', + idHerdado: 0, + cor: '#E9DC40' + } +} diff --git a/src/entities/situacoes/__tests__/index.spec.ts b/src/entities/situacoes/__tests__/index.spec.ts new file mode 100644 index 0000000..fba6521 --- /dev/null +++ b/src/entities/situacoes/__tests__/index.spec.ts @@ -0,0 +1,82 @@ +import { Chance } from 'chance' +import { Situacoes } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Situacoes entity', () => { + let repository: InMemoryBlingRepository + let entity: Situacoes + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Situacoes(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idSituacao = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idSituacao }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes', + id: String(idSituacao) + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idSituacao = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idSituacao }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes', + id: String(idSituacao) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idSituacao = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idSituacao, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes', + id: String(idSituacao), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/situacoes/__tests__/update-response.ts b/src/entities/situacoes/__tests__/update-response.ts new file mode 100644 index 0000000..dd32e3c --- /dev/null +++ b/src/entities/situacoes/__tests__/update-response.ts @@ -0,0 +1,68 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + descricao: 'Alugel do apartamento A102', + data: '2023-02-19', + numero: '25', + valor: 59.99, + situacao: 1 as const, + contato: { + id: 12345678 + }, + dataFim: '2024-05', + tipoManutencao: 1 as const, + emitirOrdemServico: false, + observacoes: '', + vendedor: { + id: 12345678, + comissao: { + aliquota: 0.5, + numeroParcelas: 1 + } + }, + categoria: { + id: 12345678 + }, + desconto: { + valor: 4.99, + dataFim: '2023-02' + }, + contaContabil: { + id: 12345678 + }, + formaPagamento: { + id: 12345678 + }, + notaFiscal: { + mes: 2 as const, + gerar: 1 as const, + descontarImpostoRenda: 1 as const, + texto: 'Exemplo de texto.', + cfop: '5.556', + iss: { + descontar: false, + aliquota: 2.5 + }, + item: { + codigoServico: '14.13', + produto: { + id: 12345678 + } + } + }, + cobranca: { + dataBase: '2023-02-22', + contato: { + id: 12345678 + }, + vencimento: { + tipo: 1 as const, + dia: 10, + periodicidade: 1 as const + } + } +} diff --git a/src/entities/situacoes/index.ts b/src/entities/situacoes/index.ts new file mode 100644 index 0000000..a156146 --- /dev/null +++ b/src/entities/situacoes/index.ts @@ -0,0 +1,89 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com situações. + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es + */ +export class Situacoes extends Entity { + /** + * Remove uma situação. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es/delete_situacoes__idSituacao_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'situacoes', + id: String(params.idSituacao) + }) + } + + /** + * Obtém uma situação. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es/get_situacoes__idSituacao_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'situacoes', + id: String(params.idSituacao) + }) + } + + /** + * Cria uma situação. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es/post_situacoes + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'situacoes', + body + }) + } + + /** + * Altera uma situação. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es/put_situacoes__idSituacao_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idSituacao, ...body } = params + + return await this.repository.replace({ + endpoint: 'situacoes', + id: String(idSituacao), + body + }) + } +} diff --git a/src/entities/situacoes/interfaces/create.interface.ts b/src/entities/situacoes/interfaces/create.interface.ts new file mode 100644 index 0000000..41962f8 --- /dev/null +++ b/src/entities/situacoes/interfaces/create.interface.ts @@ -0,0 +1,10 @@ +export interface ICreateBody { + idModuloSistema?: number + nome?: string + idHerdado?: number + cor?: string +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/situacoes/interfaces/delete.interface.ts b/src/entities/situacoes/interfaces/delete.interface.ts new file mode 100644 index 0000000..c3aa629 --- /dev/null +++ b/src/entities/situacoes/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID da situação + */ + idSituacao: number +} diff --git a/src/entities/situacoes/interfaces/find.interface.ts b/src/entities/situacoes/interfaces/find.interface.ts new file mode 100644 index 0000000..18be148 --- /dev/null +++ b/src/entities/situacoes/interfaces/find.interface.ts @@ -0,0 +1,15 @@ +export interface IFindParams { + /** + * ID da situação + */ + idSituacao: number +} + +export interface IFindResponse { + data: { + id: number + nome: string + idHerdado?: number + cor?: string + } +} diff --git a/src/entities/situacoes/interfaces/update.interface.ts b/src/entities/situacoes/interfaces/update.interface.ts new file mode 100644 index 0000000..03df5f9 --- /dev/null +++ b/src/entities/situacoes/interfaces/update.interface.ts @@ -0,0 +1,17 @@ +export interface IUpdateParams { + /** + * ID da situação + */ + idSituacao: number +} + +export interface IUpdateBody { + idModuloSistema?: number + nome?: string + idHerdado?: number + cor?: string +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/situacoesModulos/__tests__/get-module-actions-response.ts b/src/entities/situacoesModulos/__tests__/get-module-actions-response.ts new file mode 100644 index 0000000..d988c72 --- /dev/null +++ b/src/entities/situacoesModulos/__tests__/get-module-actions-response.ts @@ -0,0 +1,9 @@ +export default { + data: [ + { + id: 6, + nome: 'estornarEstoque', + descricao: 'Estornar estoque' + } + ] +} diff --git a/src/entities/situacoesModulos/__tests__/get-module-situations-response.ts b/src/entities/situacoesModulos/__tests__/get-module-situations-response.ts new file mode 100644 index 0000000..005c52f --- /dev/null +++ b/src/entities/situacoesModulos/__tests__/get-module-situations-response.ts @@ -0,0 +1,10 @@ +export default { + data: [ + { + id: 9, + nome: 'Em aberto', + idHerdado: 0, + cor: '#E9DC40' + } + ] +} diff --git a/src/entities/situacoesModulos/__tests__/get-module-transitions-response.ts b/src/entities/situacoesModulos/__tests__/get-module-transitions-response.ts new file mode 100644 index 0000000..8f13266 --- /dev/null +++ b/src/entities/situacoesModulos/__tests__/get-module-transitions-response.ts @@ -0,0 +1,20 @@ +export default { + data: [ + { + id: 9, + ativo: true, + acoes: [12, 15], + modulo: { + id: 12345678 + }, + situacaoOrigem: { + id: 9, + nome: 'Em aberto' + }, + situacaoDestino: { + id: 9, + nome: 'Em aberto' + } + } + ] +} diff --git a/src/entities/situacoesModulos/__tests__/get-modules-response.ts b/src/entities/situacoesModulos/__tests__/get-modules-response.ts new file mode 100644 index 0000000..229817b --- /dev/null +++ b/src/entities/situacoesModulos/__tests__/get-modules-response.ts @@ -0,0 +1,10 @@ +export default { + data: [ + { + id: 12345678, + nome: 'Vendas', + descricao: 'Pedidos de Venda', + criarSituacoes: false + } + ] +} diff --git a/src/entities/situacoesModulos/__tests__/index.spec.ts b/src/entities/situacoesModulos/__tests__/index.spec.ts new file mode 100644 index 0000000..43f1b4a --- /dev/null +++ b/src/entities/situacoesModulos/__tests__/index.spec.ts @@ -0,0 +1,77 @@ +import { Chance } from 'chance' +import { SituacoesModulos } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import getModuleActionsResponse from './get-module-actions-response' +import getModuleSituationsResponse from './get-module-situations-response' +import getModuleTransitionsResponse from './get-module-transitions-response' +import getModulesResponse from './get-modules-response' + +const chance = Chance() + +describe('Situações - Módulos entity', () => { + let repository: InMemoryBlingRepository + let entity: SituacoesModulos + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new SituacoesModulos(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get modules successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getModulesResponse) + + const response = await entity.getModules() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/modulos' + }) + expect(response).toBe(getModulesResponse) + }) + + it('should get module situations successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idModuloSistema = chance.natural() + repository.setResponse(getModuleSituationsResponse) + + const response = await entity.getModuleSituations({ idModuloSistema }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/modulos', + id: String(idModuloSistema) + }) + expect(response).toBe(getModuleSituationsResponse) + }) + + it('should get module actions successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idModuloSistema = chance.natural() + repository.setResponse(getModuleActionsResponse) + + const response = await entity.getModuleActions({ idModuloSistema }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/modulos', + id: `${idModuloSistema}/acoes` + }) + expect(response).toBe(getModuleActionsResponse) + }) + + it('should get module transitions successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idModuloSistema = chance.natural() + repository.setResponse(getModuleTransitionsResponse) + + const response = await entity.getModuleTransitions({ idModuloSistema }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/modulos', + id: `${idModuloSistema}/transicoes` + }) + expect(response).toBe(getModuleTransitionsResponse) + }) +}) diff --git a/src/entities/situacoesModulos/index.ts b/src/entities/situacoesModulos/index.ts new file mode 100644 index 0000000..32d4c9b --- /dev/null +++ b/src/entities/situacoesModulos/index.ts @@ -0,0 +1,92 @@ +import { Entity } from '../@shared/entity' +import { + IGetModuleActionsParams, + IGetModuleActionsResponse +} from './interfaces/get-module-actions.interface' +import { + IGetModuleSituationsParams, + IGetModuleSituationsResponse +} from './interfaces/get-module-situations.interface' +import { + IGetModuleTransitionsParams, + IGetModuleTransitionsResponse +} from './interfaces/get-module-transitions.interface' +import { IGetModulesResponse } from './interfaces/get-modules.interface' + +/** + * Entidade para interação com Situações - Módulos. + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20M%C3%B3dulos + */ +export class SituacoesModulos extends Entity { + /** + * Obtém módulos. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20M%C3%B3dulos/get_situacoes_modulos + */ + public async getModules(): Promise { + return await this.repository.index({ + endpoint: 'situacoes/modulos' + }) + } + + /** + * Obtém situações de um módulo. + * + * @param {IGetModuleSituationsParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20M%C3%B3dulos/get_situacoes_modulos__idModuloSistema_ + */ + public async getModuleSituations( + params: IGetModuleSituationsParams + ): Promise { + return await this.repository.show({ + endpoint: 'situacoes/modulos', + id: String(params.idModuloSistema) + }) + } + + /** + * Obtém as ações de um módulo. + * + * @param {IGetModuleActionsParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20M%C3%B3dulos/get_situacoes_modulos__idModuloSistema__acoes + */ + public async getModuleActions( + params: IGetModuleActionsParams + ): Promise { + return await this.repository.show({ + endpoint: 'situacoes/modulos', + id: `${params.idModuloSistema}/acoes` + }) + } + + /** + * Obtém as transições de um módulo. + * + * @param {IGetModuleTransitionsParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20M%C3%B3dulos/get_situacoes_modulos__idModuloSistema__transicoes + */ + public async getModuleTransitions( + params: IGetModuleTransitionsParams + ): Promise { + return await this.repository.show({ + endpoint: 'situacoes/modulos', + id: `${params.idModuloSistema}/transicoes` + }) + } +} diff --git a/src/entities/situacoesModulos/interfaces/get-module-actions.interface.ts b/src/entities/situacoesModulos/interfaces/get-module-actions.interface.ts new file mode 100644 index 0000000..b019b38 --- /dev/null +++ b/src/entities/situacoesModulos/interfaces/get-module-actions.interface.ts @@ -0,0 +1,14 @@ +export interface IGetModuleActionsParams { + /** + * ID do módulo do sistema + */ + idModuloSistema: number +} + +export interface IGetModuleActionsResponse { + data: { + id: number + nome: string + descricao: string + }[] +} diff --git a/src/entities/situacoesModulos/interfaces/get-module-situations.interface.ts b/src/entities/situacoesModulos/interfaces/get-module-situations.interface.ts new file mode 100644 index 0000000..54d5ef3 --- /dev/null +++ b/src/entities/situacoesModulos/interfaces/get-module-situations.interface.ts @@ -0,0 +1,15 @@ +export interface IGetModuleSituationsParams { + /** + * ID do módulo do sistema + */ + idModuloSistema: number +} + +export interface IGetModuleSituationsResponse { + data: { + id: number + nome: string + idHerdado?: number + cor?: string + }[] +} diff --git a/src/entities/situacoesModulos/interfaces/get-module-transitions.interface.ts b/src/entities/situacoesModulos/interfaces/get-module-transitions.interface.ts new file mode 100644 index 0000000..57a50c1 --- /dev/null +++ b/src/entities/situacoesModulos/interfaces/get-module-transitions.interface.ts @@ -0,0 +1,23 @@ +export interface IGetModuleTransitionsParams { + /** + * ID do módulo do sistema + */ + idModuloSistema: number +} + +export interface IGetModuleTransitionsResponse { + data: { + id: number + ativo?: boolean + acoes?: number[] + modulo?: { id: number } + situacaoOrigem: { + id: number + nome: string + } + situacaoDestino: { + id: number + nome: string + } + }[] +} diff --git a/src/entities/situacoesModulos/interfaces/get-modules.interface.ts b/src/entities/situacoesModulos/interfaces/get-modules.interface.ts new file mode 100644 index 0000000..f1ad451 --- /dev/null +++ b/src/entities/situacoesModulos/interfaces/get-modules.interface.ts @@ -0,0 +1,8 @@ +export interface IGetModulesResponse { + data: { + id: number + nome: string + descricao: string + criarSituacoes: boolean + }[] +} diff --git a/src/entities/situacoesTransicoes/__tests__/create-response.ts b/src/entities/situacoesTransicoes/__tests__/create-response.ts new file mode 100644 index 0000000..0f7dc02 --- /dev/null +++ b/src/entities/situacoesTransicoes/__tests__/create-response.ts @@ -0,0 +1,21 @@ +export default { + data: { + id: 12345678 + } +} + +export const createRequestBody = { + ativo: true, + acoes: [12, 15], + modulo: { + id: 12345678 + }, + situacaoOrigem: { + id: 9, + nome: 'Em aberto' + }, + situacaoDestino: { + id: 9, + nome: 'Em aberto' + } +} diff --git a/src/entities/situacoesTransicoes/__tests__/delete-response.ts b/src/entities/situacoesTransicoes/__tests__/delete-response.ts new file mode 100644 index 0000000..7b85954 --- /dev/null +++ b/src/entities/situacoesTransicoes/__tests__/delete-response.ts @@ -0,0 +1 @@ +export default null diff --git a/src/entities/situacoesTransicoes/__tests__/find-response.ts b/src/entities/situacoesTransicoes/__tests__/find-response.ts new file mode 100644 index 0000000..6979177 --- /dev/null +++ b/src/entities/situacoesTransicoes/__tests__/find-response.ts @@ -0,0 +1,18 @@ +export default { + data: { + id: 9, + ativo: true, + acoes: [12, 15], + modulo: { + id: 12345678 + }, + situacaoOrigem: { + id: 9, + nome: 'Em aberto' + }, + situacaoDestino: { + id: 9, + nome: 'Em aberto' + } + } +} diff --git a/src/entities/situacoesTransicoes/__tests__/index.spec.ts b/src/entities/situacoesTransicoes/__tests__/index.spec.ts new file mode 100644 index 0000000..469955f --- /dev/null +++ b/src/entities/situacoesTransicoes/__tests__/index.spec.ts @@ -0,0 +1,82 @@ +import { Chance } from 'chance' +import { SituacoesTransicoes } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import createResponse, { createRequestBody } from './create-response' +import deleteResponse from './delete-response' +import findResponse from './find-response' +import updateResponse, { updateRequestBody } from './update-response' + +const chance = Chance() + +describe('Situações - Transições entity', () => { + let repository: InMemoryBlingRepository + let entity: SituacoesTransicoes + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new SituacoesTransicoes(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should delete successfully', async () => { + const idTransicao = chance.natural() + const spy = jest.spyOn(repository, 'destroy') + repository.setResponse(deleteResponse) + + const response = await entity.delete({ idTransicao }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/transicoes', + id: String(idTransicao) + }) + expect(response).toBe(deleteResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idTransicao = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idTransicao }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/transicoes', + id: String(idTransicao) + }) + expect(response).toBe(findResponse) + }) + + it('should create successfully', async () => { + const spy = jest.spyOn(repository, 'store') + repository.setResponse(createResponse) + + const response = await entity.create(createRequestBody) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/transicoes', + body: createRequestBody + }) + expect(response).toBe(createResponse) + }) + + it('should update successfully', async () => { + const spy = jest.spyOn(repository, 'replace') + const idTransicao = chance.natural() + repository.setResponse(updateResponse) + + const response = await entity.update({ + idTransicao, + ...updateRequestBody + }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'situacoes/transicoes', + id: String(idTransicao), + body: updateRequestBody + }) + expect(response).toBe(updateResponse) + }) +}) diff --git a/src/entities/situacoesTransicoes/__tests__/update-response.ts b/src/entities/situacoesTransicoes/__tests__/update-response.ts new file mode 100644 index 0000000..e643896 --- /dev/null +++ b/src/entities/situacoesTransicoes/__tests__/update-response.ts @@ -0,0 +1,21 @@ +export default { + data: { + id: 12345678 + } +} + +export const updateRequestBody = { + ativo: true, + acoes: [12, 15], + modulo: { + id: 12345678 + }, + situacaoOrigem: { + id: 9, + nome: 'Em aberto' + }, + situacaoDestino: { + id: 9, + nome: 'Em aberto' + } +} diff --git a/src/entities/situacoesTransicoes/index.ts b/src/entities/situacoesTransicoes/index.ts new file mode 100644 index 0000000..df1df3d --- /dev/null +++ b/src/entities/situacoesTransicoes/index.ts @@ -0,0 +1,89 @@ +import { Entity } from '../@shared/entity' +import { ICreateBody, ICreateResponse } from './interfaces/create.interface' +import { IDeleteParams } from './interfaces/delete.interface' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { + IUpdateBody, + IUpdateParams, + IUpdateResponse +} from './interfaces/update.interface' + +/** + * Entidade para interação com Situações - Transições. + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20Transi%C3%A7%C3%B5es + */ +export class SituacoesTransicoes extends Entity { + /** + * Remove uma transição. + * + * @param {IDeleteParams} params Parâmetros da remoção. + * + * @returns {Promise} Não há retorno. + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20Transi%C3%A7%C3%B5es/delete_situacoes_transicoes__idTransicao_ + */ + public async delete(params: IDeleteParams): Promise { + return await this.repository.destroy({ + endpoint: 'situacoes/transicoes', + id: String(params.idTransicao) + }) + } + + /** + * Obtém uma transição. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20Transi%C3%A7%C3%B5es/get_situacoes_transicoes__idTransicao_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'situacoes/transicoes', + id: String(params.idTransicao) + }) + } + + /** + * Cria uma transição. + * + * @param {ICreateBody} body O conteúdo para a criação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20Transi%C3%A7%C3%B5es/post_situacoes_transicoes + */ + public async create(body: ICreateBody): Promise { + return await this.repository.store({ + endpoint: 'situacoes/transicoes', + body + }) + } + + /** + * Altera uma transição. + * + * @param {IUpdateParams & IUpdateBody} params Os parâmetros da atualização. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Situa%C3%A7%C3%B5es%20-%20Transi%C3%A7%C3%B5es/put_situacoes_transicoes__idTransicao_ + */ + public async update( + params: IUpdateParams & IUpdateBody + ): Promise { + const { idTransicao, ...body } = params + + return await this.repository.replace({ + endpoint: 'situacoes/transicoes', + id: String(idTransicao), + body + }) + } +} diff --git a/src/entities/situacoesTransicoes/interfaces/create.interface.ts b/src/entities/situacoesTransicoes/interfaces/create.interface.ts new file mode 100644 index 0000000..1238834 --- /dev/null +++ b/src/entities/situacoesTransicoes/interfaces/create.interface.ts @@ -0,0 +1,17 @@ +export interface ICreateBody { + ativo?: boolean + acoes?: number[] + modulo?: { id: number } + situacaoOrigem: { + id: number + nome: string + } + situacaoDestino: { + id: number + nome: string + } +} + +export interface ICreateResponse { + data: { id: number } +} diff --git a/src/entities/situacoesTransicoes/interfaces/delete.interface.ts b/src/entities/situacoesTransicoes/interfaces/delete.interface.ts new file mode 100644 index 0000000..ddab0db --- /dev/null +++ b/src/entities/situacoesTransicoes/interfaces/delete.interface.ts @@ -0,0 +1,6 @@ +export interface IDeleteParams { + /** + * ID da transição + */ + idTransicao: number +} diff --git a/src/entities/situacoesTransicoes/interfaces/find.interface.ts b/src/entities/situacoesTransicoes/interfaces/find.interface.ts new file mode 100644 index 0000000..85b4718 --- /dev/null +++ b/src/entities/situacoesTransicoes/interfaces/find.interface.ts @@ -0,0 +1,23 @@ +export interface IFindParams { + /** + * ID da transição + */ + idTransicao: number +} + +export interface IFindResponse { + data: { + id: number + ativo?: boolean + acoes?: number[] + modulo?: { id: number } + situacaoOrigem: { + id: number + nome: string + } + situacaoDestino: { + id: number + nome: string + } + } +} diff --git a/src/entities/situacoesTransicoes/interfaces/update.interface.ts b/src/entities/situacoesTransicoes/interfaces/update.interface.ts new file mode 100644 index 0000000..c968c90 --- /dev/null +++ b/src/entities/situacoesTransicoes/interfaces/update.interface.ts @@ -0,0 +1,24 @@ +export interface IUpdateParams { + /** + * ID da transição + */ + idTransicao: number +} + +export interface IUpdateBody { + ativo?: boolean + acoes?: number[] + modulo?: { id: number } + situacaoOrigem: { + id: number + nome: string + } + situacaoDestino: { + id: number + nome: string + } +} + +export interface IUpdateResponse { + data: { id: number } +} diff --git a/src/entities/types/contribuinte.ts b/src/entities/types/contribuinte.ts deleted file mode 100644 index 7e63a75..0000000 --- a/src/entities/types/contribuinte.ts +++ /dev/null @@ -1,3 +0,0 @@ -type IContribuinte = '1' | '2' | '9' - -export default IContribuinte diff --git a/src/entities/types/origem.ts b/src/entities/types/origem.ts deleted file mode 100644 index 50812e2..0000000 --- a/src/entities/types/origem.ts +++ /dev/null @@ -1,3 +0,0 @@ -type IOrigem = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' - -export default IOrigem diff --git a/src/entities/types/tipoFrete.ts b/src/entities/types/tipoFrete.ts deleted file mode 100644 index 3271def..0000000 --- a/src/entities/types/tipoFrete.ts +++ /dev/null @@ -1,3 +0,0 @@ -type ITipoFrete = 'D' | 'R' - -export default ITipoFrete diff --git a/src/entities/types/tipoPessoa.ts b/src/entities/types/tipoPessoa.ts deleted file mode 100644 index 36e33bc..0000000 --- a/src/entities/types/tipoPessoa.ts +++ /dev/null @@ -1,3 +0,0 @@ -type ITipoPessoa = 'F' | 'J' | 'E' - -export default ITipoPessoa diff --git a/src/entities/types/un.ts b/src/entities/types/un.ts deleted file mode 100644 index c22fa7b..0000000 --- a/src/entities/types/un.ts +++ /dev/null @@ -1,3 +0,0 @@ -type IUn = 'pc' | 'un' | 'cx' - -export default IUn diff --git a/src/entities/usuarios/__tests__/change-password-response.ts b/src/entities/usuarios/__tests__/change-password-response.ts new file mode 100644 index 0000000..c513968 --- /dev/null +++ b/src/entities/usuarios/__tests__/change-password-response.ts @@ -0,0 +1,3 @@ +export default null + +export const updateRequestBody = 'string' diff --git a/src/entities/usuarios/__tests__/index.spec.ts b/src/entities/usuarios/__tests__/index.spec.ts new file mode 100644 index 0000000..14abb1e --- /dev/null +++ b/src/entities/usuarios/__tests__/index.spec.ts @@ -0,0 +1,65 @@ +import { Chance } from 'chance' +import { Usuarios } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import changePasswordResponse from './change-password-response' +import recoverPasswordResponse from './recover-password-response' +import validateHashResponse from './validate-hash-response' + +const chance = Chance() + +describe('Usuários entity', () => { + let repository: InMemoryBlingRepository + let entity: Usuarios + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Usuarios(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should validate hash successfully', async () => { + const hash = chance.word() + const spy = jest.spyOn(repository, 'index') + repository.setResponse(validateHashResponse) + + const response = await entity.validateHash({ hash }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'usuarios/verificar-hash', + params: { hash } + }) + expect(response).toBe(validateHashResponse) + }) + + it('should change password successfully', async () => { + const spy = jest.spyOn(repository, 'update') + const password = chance.word() + repository.setResponse(changePasswordResponse) + + const response = await entity.changePassword(password) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'usuarios/redefinir-senha', + id: '', + body: password + }) + expect(response).toBe(changePasswordResponse) + }) + + it('should recover password successfully', async () => { + const spy = jest.spyOn(repository, 'store') + const email = chance.email() + repository.setResponse(recoverPasswordResponse) + + const response = await entity.recoverPassword(email) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'usuarios/recuperar-senha', + body: email + }) + expect(response).toBe(recoverPasswordResponse) + }) +}) diff --git a/src/entities/usuarios/__tests__/recover-password-response.ts b/src/entities/usuarios/__tests__/recover-password-response.ts new file mode 100644 index 0000000..95f20f6 --- /dev/null +++ b/src/entities/usuarios/__tests__/recover-password-response.ts @@ -0,0 +1,10 @@ +export default { + data: [ + { + message: + 'Caso o e-mail informado esteja cadastrado em nosso sistema, uma mensagem com instruções para a troca de senha será enviada.' + } + ] +} + +export const createRequestBody = 'email' diff --git a/src/entities/usuarios/__tests__/validate-hash-response.ts b/src/entities/usuarios/__tests__/validate-hash-response.ts new file mode 100644 index 0000000..7a18175 --- /dev/null +++ b/src/entities/usuarios/__tests__/validate-hash-response.ts @@ -0,0 +1,7 @@ +export default { + data: [ + { + valido: true + } + ] +} diff --git a/src/entities/usuarios/index.ts b/src/entities/usuarios/index.ts new file mode 100644 index 0000000..27f38d0 --- /dev/null +++ b/src/entities/usuarios/index.ts @@ -0,0 +1,69 @@ +import { Entity } from '../@shared/entity' +import { IRecoverPasswordResponse } from './interfaces/recover-password.interface' +import { + IValidateHashParams, + IValidateHashResponse +} from './interfaces/validate-hash.interface' + +/** + * Entidade para interação com Usuários. + * + * @see https://developer.bling.com.br/referencia#/Usu%C3%A1rios + */ +export class Usuarios extends Entity { + /** + * Valida o hash recebido. + * + * @param {IValidateHashParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Usu%C3%A1rios/get_usuarios_verificar_hash + */ + public async validateHash( + params: IValidateHashParams + ): Promise { + return await this.repository.index({ + endpoint: 'usuarios/verificar-hash', + params: { hash: params.hash } + }) + } + + /** + * Redefine senha do usuário. + * + * @param {string} password A nova senha. + * + * @return {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Usu%C3%A1rios/patch_usuarios_redefinir_senha + */ + public async changePassword(password: string): Promise { + return await this.repository.update({ + endpoint: 'usuarios/redefinir-senha', + id: '', + body: password + }) + } + + /** + * Envia solicitação de recuperação de senha. + * + * @param {string} email O e-mail para solicitar a recuperação. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Usu%C3%A1rios/post_usuarios_recuperar_senha + */ + public async recoverPassword( + email: string + ): Promise { + return await this.repository.store({ + endpoint: 'usuarios/recuperar-senha', + body: email + }) + } +} diff --git a/src/entities/usuarios/interfaces/recover-password.interface.ts b/src/entities/usuarios/interfaces/recover-password.interface.ts new file mode 100644 index 0000000..9bc96d6 --- /dev/null +++ b/src/entities/usuarios/interfaces/recover-password.interface.ts @@ -0,0 +1,3 @@ +export interface IRecoverPasswordResponse { + data: { message: string }[] +} diff --git a/src/entities/usuarios/interfaces/validate-hash.interface.ts b/src/entities/usuarios/interfaces/validate-hash.interface.ts new file mode 100644 index 0000000..e4a9dda --- /dev/null +++ b/src/entities/usuarios/interfaces/validate-hash.interface.ts @@ -0,0 +1,7 @@ +export interface IValidateHashParams { + hash: string +} + +export interface IValidateHashResponse { + data: { valido?: boolean }[] +} diff --git a/src/entities/vendedores/__tests__/find-response.ts b/src/entities/vendedores/__tests__/find-response.ts new file mode 100644 index 0000000..d48dad6 --- /dev/null +++ b/src/entities/vendedores/__tests__/find-response.ts @@ -0,0 +1,20 @@ +export default { + data: { + id: 12345678, + descontoLimite: 10.12, + loja: { + id: 12345678 + }, + contato: { + id: 12345678, + nome: 'Vendedor', + situacao: 'A' + }, + comissoes: [ + { + descontoMaximo: 10, + aliquota: 2 + } + ] + } +} diff --git a/src/entities/vendedores/__tests__/get-response.ts b/src/entities/vendedores/__tests__/get-response.ts new file mode 100644 index 0000000..70737f3 --- /dev/null +++ b/src/entities/vendedores/__tests__/get-response.ts @@ -0,0 +1,14 @@ +export default { + data: { + id: 12345678, + descontoLimite: 10.12, + loja: { + id: 12345678 + }, + contato: { + id: 12345678, + nome: 'Vendedor', + situacao: 'A' + } + } +} diff --git a/src/entities/vendedores/__tests__/index.spec.ts b/src/entities/vendedores/__tests__/index.spec.ts new file mode 100644 index 0000000..e675220 --- /dev/null +++ b/src/entities/vendedores/__tests__/index.spec.ts @@ -0,0 +1,57 @@ +import { Chance } from 'chance' +import { Vendedores } from '..' +import { InMemoryBlingRepository } from '../../../repositories/bling-in-memory.repository' +import findResponse from './find-response' +import getResponse from './get-response' + +const chance = Chance() + +describe('Vendedores entity', () => { + let repository: InMemoryBlingRepository + let entity: Vendedores + + beforeEach(() => { + repository = new InMemoryBlingRepository() + entity = new Vendedores(repository) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should get successfully', async () => { + const spy = jest.spyOn(repository, 'index') + repository.setResponse(getResponse) + + const response = await entity.get() + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'vendedores', + params: { + limite: undefined, + pagina: undefined, + nomeContato: undefined, + situacaoContato: undefined, + idContato: undefined, + idLoja: undefined, + dataAlteracaoInicial: undefined, + dataAlteracaoFinal: undefined + } + }) + expect(response).toBe(getResponse) + }) + + it('should find successfully', async () => { + const spy = jest.spyOn(repository, 'show') + const idVendedor = chance.natural() + repository.setResponse(findResponse) + + const response = await entity.find({ idVendedor }) + + expect(spy).toHaveBeenCalledWith({ + endpoint: 'vendedores', + id: String(idVendedor) + }) + expect(response).toBe(findResponse) + }) +}) diff --git a/src/entities/vendedores/index.ts b/src/entities/vendedores/index.ts new file mode 100644 index 0000000..6183ec2 --- /dev/null +++ b/src/entities/vendedores/index.ts @@ -0,0 +1,57 @@ +import { Entity } from '../@shared/entity' +import { IFindParams, IFindResponse } from './interfaces/find.interface' +import { IGetParams, IGetResponse } from './interfaces/get.interface' + +/** + * Entidade para interação com vendedores. + * + * @see https://developer.bling.com.br/referencia#/Vendedores + */ +export class Vendedores extends Entity { + /** + * Obtém vendedores. + * + * @param {IGetParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Vendedores/get_vendedores + */ + public async get(params?: IGetParams): Promise { + return await this.repository.index({ + endpoint: 'vendedores', + params: { + pagina: params?.pagina, + limite: params?.limite, + nomeContato: params?.nomeContato, + situacaoContato: params?.situacaoContato, + idContato: params?.idContato, + idLoja: params?.idLoja, + dataAlteracaoInicial: this.prepareStringOrDateParam( + params?.dataAlteracaoInicial + ), + dataAlteracaoFinal: this.prepareStringOrDateParam( + params?.dataAlteracaoFinal + ) + } + }) + } + + /** + * Obtém um vendedor. + * + * @param {IFindParams} params Parâmetros da busca. + * + * @returns {Promise} + * @throws {BlingApiException|BlingInternalException} + * + * @see https://developer.bling.com.br/referencia#/Vendedores/get_vendedores__idVendedor_ + */ + public async find(params: IFindParams): Promise { + return await this.repository.show({ + endpoint: 'vendedores', + id: String(params.idVendedor) + }) + } +} diff --git a/src/entities/vendedores/interfaces/find.interface.ts b/src/entities/vendedores/interfaces/find.interface.ts new file mode 100644 index 0000000..f9f577b --- /dev/null +++ b/src/entities/vendedores/interfaces/find.interface.ts @@ -0,0 +1,25 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IFindParams { + /** + * ID do vendedor + */ + idVendedor: number +} + +export interface IFindResponse { + data: { + id?: number + descontoLimite?: number + loja?: { id: number } + contato: { + id: number + nome: string + situacao: ISituacao + } + comissoes: { + descontoMaximo: number + aliquota: number + }[] + } +} diff --git a/src/entities/vendedores/interfaces/get.interface.ts b/src/entities/vendedores/interfaces/get.interface.ts new file mode 100644 index 0000000..b89bf10 --- /dev/null +++ b/src/entities/vendedores/interfaces/get.interface.ts @@ -0,0 +1,49 @@ +import { ISituacao } from '../types/situacao.type' + +export interface IGetParams { + /** + * N° da página da listagem + */ + pagina?: number + /** + * Quantidade de registros que devem ser exibidos por página + */ + limite?: number + /** + * Nome do contato do vendedor + */ + nomeContato?: string + /** + * Situação do contato do vendedor + */ + situacaoContato?: ISituacao + /** + * ID do contato do vendedor + */ + idContato?: number + /** + * ID da loja vinculada ao vendedor + */ + idLoja?: number + /** + * Data de alteração inicial + */ + dataAlteracaoInicial?: Date | string + /** + * Data de alteração final + */ + dataAlteracaoFinal?: Date | string +} + +export interface IGetResponse { + data: { + id?: number + descontoLimite?: number + loja?: { id: number } + contato: { + id: number + nome: string + situacao: ISituacao + } + } +} diff --git a/src/entities/vendedores/types/situacao.type.ts b/src/entities/vendedores/types/situacao.type.ts new file mode 100644 index 0000000..7352a0f --- /dev/null +++ b/src/entities/vendedores/types/situacao.type.ts @@ -0,0 +1,9 @@ +/** + * Tipagem referente à situação do vendedor. + * + * - `A`: Ativo + * - `I`: Inativo + * - `S`: Sem movimento + * - `E`: Excluído + */ +export type ISituacao = 'A' | 'I' | 'S' | 'E' diff --git a/src/exceptions/bling-api.exception.ts b/src/exceptions/bling-api.exception.ts new file mode 100644 index 0000000..21b6b23 --- /dev/null +++ b/src/exceptions/bling-api.exception.ts @@ -0,0 +1,27 @@ +import { IDefaultErrorResponse } from '../entities/@shared/interfaces/error.interface' + +/** + * Exceção lançada quando há um erro na chamada API ao Bling. + */ +export class BlingApiException extends Error { + /** @property A resposta da requisição. */ + private readonly rawResponse: IDefaultErrorResponse + + /** + * Constrói o objeto. + * + * @param response A resposta crua da requisição. + */ + constructor(response: IDefaultErrorResponse) { + super(response.error.description) + + this.rawResponse = response + } + + /** + * Obtém a resposta da requisição. + */ + public get response() { + return this.rawResponse + } +} diff --git a/src/exceptions/bling-internal.exception.ts b/src/exceptions/bling-internal.exception.ts new file mode 100644 index 0000000..5ce65fc --- /dev/null +++ b/src/exceptions/bling-internal.exception.ts @@ -0,0 +1,4 @@ +/** + * Exceção lançada quando há um erro interno na biblioteca. + */ +export class BlingInternalException extends Error {} diff --git a/src/helpers/functions/convert-date-to-string.ts b/src/helpers/functions/convert-date-to-string.ts new file mode 100644 index 0000000..fd25c8f --- /dev/null +++ b/src/helpers/functions/convert-date-to-string.ts @@ -0,0 +1,5 @@ +/** + * Converte um objeto `Date` para o formato `YYYY-MM-DD`. + */ +export default (date: Date) => + `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` diff --git a/src/helpers/types/newable.type.ts b/src/helpers/types/newable.type.ts new file mode 100644 index 0000000..ca529a8 --- /dev/null +++ b/src/helpers/types/newable.type.ts @@ -0,0 +1,4 @@ +/** + * Tipagem representativa de uma classe que possui construtor. + */ +export type Newable = new (...args: any[]) => T diff --git a/src/providers/ioc.ts b/src/providers/ioc.ts new file mode 100644 index 0000000..8c4e68c --- /dev/null +++ b/src/providers/ioc.ts @@ -0,0 +1,14 @@ +import { BlingRepository } from '../repositories/bling.repository' +import { IBlingRepository } from '../repositories/bling.repository.interface' + +/** + * Obtém a instância do repositório para injeção de dependência. + * + * @returns {IBlingRepository} + */ +export function getRepository(accessToken: string): IBlingRepository { + return new BlingRepository({ + baseUrl: 'https://www.bling.com.br/Api/v3', + accessToken + }) +} diff --git a/src/repositories/bling-in-memory.repository.ts b/src/repositories/bling-in-memory.repository.ts new file mode 100644 index 0000000..b1656c8 --- /dev/null +++ b/src/repositories/bling-in-memory.repository.ts @@ -0,0 +1,178 @@ +import { + IBlingRepository, + IDefaultHeaders, + IDefaultParams, + IDestroyOptions, + IIndexOptions, + IReplaceOptions, + IShowOptions, + IStoreOptions, + IUpdateOptions +} from './bling.repository.interface' + +/** + * Repositório em memória para testes. + */ +export class InMemoryBlingRepository implements IBlingRepository { + /** @property {any} response A resposta definida ao fazer uma requisição. */ + private response: any + + /** @property {any} indexResponse A resposta definida para o método `index`. */ + private indexResponse: any = null + + /** @property {any} showResponse A resposta definida para o método `show`. */ + private showResponse: any = null + + /** @property {any} storeResponse A resposta definida para o método `store`. */ + private storeResponse: any = null + + /** @property {any} updateResponse A resposta definida para o método `update`. */ + private updateResponse: any = null + + /** @property {any} replaceResponse A resposta definida para o método `replace`. */ + private replaceResponse: any = null + + /** @property {any} destroyResponse A resposta definida para o método `destroy`. */ + private destroyResponse: any = null + + /** + * Define a resposta de _mock_ padrão. + * + * @param response A resposta a ser retornada. + */ + public setResponse(response: any) { + this.response = response + } + + /** + * Define a resposta de _mock_ para o método `index`. + * + * @param response A resposta a ser retornada. + */ + public setIndexResponse(response: any) { + this.indexResponse = response + } + + /** + * Define a resposta de _mock_ para o método `show`. + * + * @param response A resposta a ser retornada. + */ + public setShowResponse(response: any) { + this.showResponse = response + } + + /** + * Define a resposta de _mock_ para o método `store`. + * + * @param response A resposta a ser retornada. + */ + public setStoreResponse(response: any) { + this.storeResponse = response + } + + /** + * Define a resposta de _mock_ para o método `update`. + * + * @param response A resposta a ser retornada. + */ + public setUpdateResponse(response: any) { + this.updateResponse = response + } + + /** + * Define a resposta de _mock_ para o método `replace`. + * + * @param response A resposta a ser retornada. + */ + public setReplaceResponse(response: any) { + this.replaceResponse = response + } + + /** + * Define a resposta de _mock_ para o método `destroy`. + * + * @param response A resposta a ser retornada. + */ + public setDestroyResponse(response: any) { + this.destroyResponse = response + } + + /** + * @inheritdoc + */ + async index< + IIndexBody, + IIndexResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IIndexOptions + ): Promise { + return this.indexResponse ?? this.response + } + + /** + * @inheritdoc + */ + async show< + IShowResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >(options: IShowOptions): Promise { + return this.showResponse ?? this.response + } + + /** + * @inheritdoc + */ + async store< + IStoreBody, + IStoreResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IStoreOptions + ): Promise { + return this.storeResponse ?? this.response + } + + /** + * @inheritdoc + */ + async update< + IUpdateBody, + IUpdateResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IUpdateOptions + ): Promise { + return this.updateResponse ?? this.response + } + + /** + * @inheritdoc + */ + async replace< + IReplaceBody, + IReplaceResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IReplaceOptions + ): Promise { + return this.replaceResponse ?? this.response + } + + /** + * @inheritdoc + */ + async destroy< + IDestroyResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >(options: IDestroyOptions): Promise { + return this.destroyResponse ?? this.response + } +} diff --git a/src/repositories/bling.repository.interface.ts b/src/repositories/bling.repository.interface.ts new file mode 100644 index 0000000..586c1b4 --- /dev/null +++ b/src/repositories/bling.repository.interface.ts @@ -0,0 +1,159 @@ +/** + * Tipo padrão de _query parameters_. + */ +export type IDefaultParams = Record< + string, + number | number[] | string | string[] | Date | undefined +> + +/** + * Tipo padrão de _headers_. + */ +export type IDefaultHeaders = Record + +/** + * Opções padrões para uma chamada API. + */ +export interface IDefaultOptions { + endpoint: string + params?: IParams + headers?: IHeaders + shouldIncludeHeadersInResponse?: true +} + +/** + * Opções para uma chamada do tipo _index_. + */ +export interface IIndexOptions + extends IDefaultOptions { + body?: IBody +} + +/** + * Opções para uma chamada do tipo _show_. + */ +export interface IShowOptions + extends IDefaultOptions { + id: string +} + +/** + * Opções para uma chamada do tipo _store_. + */ +export interface IStoreOptions + extends IDefaultOptions { + body: IBody +} + +/** + * Opções para uma chamada do tipo _update_. + */ +export interface IUpdateOptions + extends IDefaultOptions { + id: string + body: IBody +} + +/** + * Opções para uma chamada do tipo _replace_. + */ +export interface IReplaceOptions + extends IDefaultOptions { + id: string + body: IBody +} + +/** + * Opções para uma chamada do tipo _destroy_. + */ +export interface IDestroyOptions + extends IDefaultOptions { + id: string +} + +/** + * Interface padrão de implementação de chamada da API do Bling. + */ +export interface IBlingRepository { + /** + * Realiza uma chamada GET **sem** a identificação de um recurso. + * + * @param options Opções da chamada de API. + */ + index: < + IIndexBody, + IIndexResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IIndexOptions + ) => Promise + + /** + * Realiza uma chamada GET **com** a identificação de um recurso. + * + * @param options Opções da chamada de API. + */ + show: < + IShowResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IShowOptions + ) => Promise + + /** + * Realiza uma chamada POST para criação de um recurso. + * + * @param options Opções da chamada de API. + */ + store: < + IStoreBody, + IStoreResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IStoreOptions + ) => Promise + + /** + * Realiza uma chamada PATCH para a atualização de um recurso. + * + * @param options Opções da chamada de API. + */ + update: < + IUpdateBody, + IUpdateResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IUpdateOptions + ) => Promise + + /** + * Realiza uma chamada PUT para a atualização de um recurso. + * + * @param options Opções da chamada de API. + */ + replace: < + IReplaceBody, + IReplaceResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IReplaceOptions + ) => Promise + + /** + * Realiza uma chamada DELETE para deleção de um recurso. + * + * @param options Opções da chamada de API. + */ + destroy: < + IDestroyResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IDestroyOptions + ) => Promise +} diff --git a/src/repositories/bling.repository.ts b/src/repositories/bling.repository.ts new file mode 100644 index 0000000..99029f6 --- /dev/null +++ b/src/repositories/bling.repository.ts @@ -0,0 +1,265 @@ +import axios, { AxiosError, AxiosInstance } from 'axios' +import { IDefaultErrorResponse } from '../entities/@shared/interfaces/error.interface' +import { BlingApiException } from '../exceptions/bling-api.exception' +import { BlingInternalException } from '../exceptions/bling-internal.exception' +import { + IBlingRepository, + IDefaultHeaders, + IDefaultParams, + IDestroyOptions, + IIndexOptions, + IReplaceOptions, + IShowOptions, + IStoreOptions, + IUpdateOptions +} from './bling.repository.interface' + +interface IBlingRepositoryProps { + /** + * A URL base para chamada da API. + */ + baseUrl: string + + /** + * O _token_ de autenticação. + */ + accessToken: string +} + +/** + * Repositório para acesso à API do Bling. + */ +export class BlingRepository implements IBlingRepository { + /** @property Propriedades da classe. */ + private props: IBlingRepositoryProps + + /** @property A instância `axios` para chamadas API. */ + private api: AxiosInstance + + /** + * Constrói o objeto. + * + * @param props As propriedades da classe. + */ + constructor(props: IBlingRepositoryProps) { + this.props = props + + this.api = axios.create({ + baseURL: this.props.baseUrl + }) + + this.api.interceptors.request.use((config) => { + config.headers.Authorization = `Bearer ${this.props.accessToken}` + return config + }) + } + + /** + * @inheritDoc + * + * @throws {BlingApiException|BlingInternalException} + */ + public async index< + IIndexBody, + IIndexResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IIndexOptions + ): Promise { + return await this.api + .get(`${options.endpoint}`, { + params: options.params, + headers: options.headers, + data: options.body + }) + .then((response) => + options.shouldIncludeHeadersInResponse + ? { + headers: response.headers, + ...response.data + } + : response.data + ) + .catch((error: AxiosError) => + this.defaultCatchBehavior(error, options.endpoint) + ) + } + + /** + * @inheritDoc + * + * @throws {BlingApiException|BlingInternalException} + */ + public async show< + IShowResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >(options: IShowOptions): Promise { + const endpoint = `${options.endpoint}/${options.id}` + return await this.api + .get(endpoint, { + params: options.params, + headers: options.headers + }) + .then((response) => + options.shouldIncludeHeadersInResponse + ? { + headers: response.headers, + ...response.data + } + : response.data + ) + .catch((error: AxiosError) => + this.defaultCatchBehavior(error, endpoint) + ) + } + + /** + * @inheritDoc + * + * @throws {BlingApiException|BlingInternalException} + */ + public async store< + IStoreBody, + IStoreResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IStoreOptions + ): Promise { + return await this.api + .post(`${options.endpoint}`, options.body, { + params: options.params, + headers: options.headers + }) + .then((response) => + options.shouldIncludeHeadersInResponse + ? { + headers: response.headers, + ...response.data + } + : response.data + ) + .catch((error: AxiosError) => + this.defaultCatchBehavior(error, options.endpoint) + ) + } + + /** + * @inheritDoc + * + * @throws {BlingApiException|BlingInternalException} + */ + public async update< + IUpdateBody, + IUpdateResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IUpdateOptions + ): Promise { + const endpoint = `${options.endpoint}/${options.id}` + return await this.api + .patch(endpoint, options.body, { + params: options.params, + headers: options.headers + }) + .then((response) => + options.shouldIncludeHeadersInResponse + ? { + headers: response.headers, + ...response.data + } + : response.data + ) + .catch((error: AxiosError) => + this.defaultCatchBehavior(error, endpoint) + ) + } + + /** + * @inheritDoc + * + * @throws {BlingApiException|BlingInternalException} + */ + public async replace< + IReplaceBody, + IReplaceResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >( + options: IReplaceOptions + ): Promise { + const endpoint = `${options.endpoint}/${options.id}` + return await this.api + .patch(endpoint, options.body, { + params: options.params, + headers: options.headers + }) + .then((response) => + options.shouldIncludeHeadersInResponse + ? { + headers: response.headers, + ...response.data + } + : response.data + ) + .catch((error: AxiosError) => + this.defaultCatchBehavior(error, endpoint) + ) + } + + /** + * @inheritDoc + * + * @throws {BlingApiException|BlingInternalException} + */ + public async destroy< + IDestroyResponse, + IParams extends IDefaultParams = IDefaultParams, + IHeaders extends IDefaultHeaders = IDefaultHeaders + >(options: IDestroyOptions): Promise { + const endpoint = `${options.endpoint}/${options.id}` + return await this.api + .delete(endpoint, { + params: options.params, + headers: options.headers + }) + .then((response) => + options.shouldIncludeHeadersInResponse + ? { + headers: response.headers, + ...response.data + } + : response.data + ) + .catch((error: AxiosError) => + this.defaultCatchBehavior(error, endpoint) + ) + } + + /** + * Trata os erros da API de forma padrão. + * + * @param rawError Erro do axios. + * @param endpoint _Endpoint_ de chamada. + * + * @returns {never} + * @throws {BlingApiException|BlingInternalException} + */ + private defaultCatchBehavior( + rawError: AxiosError, + endpoint: string + ): never { + const data = rawError.response?.data + + if (!data) { + throw new BlingInternalException( + `Não foi possível realizar a chamada HTTP: ${rawError.config?.method} ${endpoint}` + ) + } + + throw new BlingApiException(data) + } +} diff --git a/test/config/bling.ts b/test/config/bling.ts deleted file mode 100644 index 80cb7f8..0000000 --- a/test/config/bling.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Overall configuration -import { Bling, IBlingError } from '../../src/bling' -import { config } from 'dotenv' - -// Generators -import billsToPay from '../generators/billsToPay' -import billsToReceive from '../generators/billsToReceive' -import categories from '../generators/categories' -import contacts from '../generators/contacts' -import deposits from '../generators/deposits' -import paymentMethods from '../generators/paymentMethods' -import purchaseOrders from '../generators/purchaseOrders' - -// Setup -config() - -const testApiKey = process.env.BLING_API_KEY as string -const bling = new Bling(testApiKey) - -const exampleContactId = process.env.EXAMPLE_CONTACT_ID as string -const exampleSupplierId = process.env.EXAMPLE_SUPPLIER_ID as string -const exampleSupplierProductId = process.env - .EXAMPLE_SUPPLIER_PRODUCT_ID as string - -const generators = { - categories, - contacts, - billsToPay, - billsToReceive, - deposits, - paymentMethods, - purchaseOrders -} - -const defaultBeforeEach = (time = 400) => - new Promise((resolve) => setTimeout(resolve, time)) - -export { - Bling, - bling, - IBlingError, - exampleContactId, - exampleSupplierId, - exampleSupplierProductId, - generators, - defaultBeforeEach -} diff --git a/test/entities/all.spec.js b/test/entities/all.spec.js deleted file mode 100644 index 64ddabb..0000000 --- a/test/entities/all.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import { bling, defaultBeforeEach } from '../config/bling' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach(500) -}) - -const table = [ - 'billsToPay', - 'billsToReceive', - 'categories', - 'commercialProposals', - 'contacts', - // 'contracts', - 'ctes', - 'deposits', - 'invoices', - 'orders', - 'paymentMethods', - 'productGroups', - 'products', - 'purchaseOrders', - 'shopCategories' -] - -test.each(table)( - 'should get all %s when calling `.all()` method with raw option settled to false', - async (name) => { - await expect(bling[name]().all({ raw: false })).resolves.toBeDefined() - } -) - -test.each(table)( - 'should get all %s when calling `.all()` method with raw option settled to true', - async (name) => { - await expect(bling[name]().all({ raw: true })).resolves.toBeDefined() - } -) - -test.each(table)( - 'should get all %s when calling `.all()` method without raw option', - async (name) => { - await expect(bling[name]().all()).resolves.toBeDefined() - } -) - -test.each(table)( - 'should get a single page of %s when calling `.all()` method with page option', - async (name) => { - await expect(bling[name]().all({ page: 5 })).resolves.toBeDefined() - } -) diff --git a/test/entities/contacts/create.spec.js b/test/entities/contacts/create.spec.js deleted file mode 100644 index 805b9fe..0000000 --- a/test/entities/contacts/create.spec.js +++ /dev/null @@ -1,171 +0,0 @@ -import { bling, defaultBeforeEach } from '../../config/bling' -import { CPF } from 'cpf_cnpj' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -const testError = (err, message, status, code) => { - expect(err.message).toBe(message) - expect(err.status).toBe(status) - expect(err.code).toBe(code) -} - -test.concurrent( - "shouldn't create a contact when calling `.create()` method without any parameters", - async () => { - try { - await bling.contacts().create() - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_CREATE_DATA' - ) - } - } -) - -test.concurrent( - "shouldn't create a contact when calling `.create()` method with data as string", - async () => { - try { - await bling.contacts().create('') - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_CREATE_DATA' - ) - } - } -) - -test.concurrent( - "shouldn't create a contact when calling `.create()` method with data as number", - async () => { - try { - await bling.contacts().create(123) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_CREATE_DATA' - ) - } - } -) - -test.concurrent( - "shouldn't create a contact when calling `.create()` method with data as array", - async () => { - try { - await bling.contacts().create([]) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_CREATE_DATA' - ) - } - } -) - -test.concurrent( - "shouldn't create a contact when calling `.create()` method with data as an empty object", - async () => { - try { - await bling.contacts().create({}) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_CREATE_DATA' - ) - } - } -) - -test("shouldn't create a contact when calling `.create()` method with missing name", async () => { - try { - await bling.contacts().create({ - cpf_cnpj: CPF.generate(), - tipoPessoa: 'F', - contribuinte: '9' - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'Error on create method after request call', - '400', - 'ERR_CREATE_METHOD_FAILURE' - ) - } -}) - -test("shouldn't create a contact when calling `.create()` method with missing cpf_cnpj", async () => { - try { - await bling.contacts().create({ - nome: 'Usuário Teste', - tipoPessoa: 'F', - contribuinte: '9' - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'Error on create method after request call', - '400', - 'ERR_CREATE_METHOD_FAILURE' - ) - } -}) - -test("shouldn't create a contact when calling `.create()` method with missing tipoPessoa", async () => { - try { - await bling.contacts().create({ - nome: 'Usuário Teste', - cpf_cnpj: CPF.generate(), - contribuinte: '9' - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'Error on create method after request call', - '400', - 'ERR_CREATE_METHOD_FAILURE' - ) - } -}) - -test("shouldn't create a contact when calling `.create()` method with missing contribuinte", async () => { - try { - await bling.contacts().create({ - nome: 'Usuário Teste', - cpf_cnpj: CPF.generate(), - tipoPessoa: 'F' - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'Error on create method after request call', - '400', - 'ERR_CREATE_METHOD_FAILURE' - ) - } -}) diff --git a/test/entities/contacts/find.test.ts b/test/entities/contacts/find.test.ts deleted file mode 100644 index 2f128a0..0000000 --- a/test/entities/contacts/find.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - bling, - IBlingError, - exampleContactId, - defaultBeforeEach -} from '../../config/bling' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -const testError = (err: IBlingError) => { - expect(err.message).toEqual('Error on find method after request call') - expect(err.code).toEqual('ERR_FIND_METHOD_FAILURE') -} - -test.concurrent( - "shouldn't find a contact when calling `.find()` method without defining params", - async () => { - try { - await bling.contacts().find(exampleContactId) - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } - } -) - -test.concurrent( - 'should find a contact when calling `.find()` method with an existent id', - async () => { - await expect( - bling - .contacts() - .find(exampleContactId, { params: { identificador: '2' }, raw: false }) - ).resolves.toBeDefined() - await expect( - bling - .contacts() - .find(exampleContactId, { params: { identificador: '2' }, raw: true }) - ).resolves.toBeDefined() - } -) - -test("shouldn't find a contact when calling `.find()` method with an inexistent id with beautified response", async () => { - try { - await bling - .contacts() - .find('0', { params: { identificador: '1' }, raw: false }) - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } -}) - -test("shouldn't find a contact when calling `.find()` method with an inexistent CPF/CNPJ with beautified response", async () => { - try { - await bling - .contacts() - .find('0', { params: { identificador: '2' }, raw: false }) - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } -}) - -test("shouldn't find a contact when calling `.find()` method with an inexistent id with raw response", async () => { - try { - await bling - .contacts() - .find('0', { params: { identificador: '1' }, raw: true }) - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } -}) - -test("shouldn't find a contact when calling `.find()` method with an inexistent CPF/CNPJ with raw response", async () => { - try { - await bling - .contacts() - .find('0', { params: { identificador: '2' }, raw: true }) - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } -}) diff --git a/test/entities/contacts/findBy.test.ts b/test/entities/contacts/findBy.test.ts deleted file mode 100644 index b44419f..0000000 --- a/test/entities/contacts/findBy.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { bling, defaultBeforeEach } from '../../config/bling' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -test('should find contacts when calling `.findBy()` method with options', async () => { - await expect( - bling.contacts().findBy({ - filters: { tipoPessoa: 'F', dataInclusao: '01/08/2021 TO 31/08/2021' } - }) - ).resolves.toBeDefined() -}) diff --git a/test/entities/contacts/update.spec.js b/test/entities/contacts/update.spec.js deleted file mode 100644 index 8c98cab..0000000 --- a/test/entities/contacts/update.spec.js +++ /dev/null @@ -1,206 +0,0 @@ -import { bling, defaultBeforeEach, generators } from '../../config/bling' -import { CPF } from 'cpf_cnpj' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -const testError = (err, message, status, code) => { - expect(err.message).toBe(message) - expect(err.status).toBe(status) - expect(err.code).toBe(code) -} - -test("shouldn't update a contact when calling `.update()` method with data as string", async () => { - try { - const contact = await bling.contacts().create(generators.contacts()) - await bling.contacts().update(contact[0].id, '') - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_UPDATE_DATA' - ) - } -}) - -test("shouldn't update a contact when calling `.update()` method with data as number", async () => { - try { - const contact = await bling.contacts().create(generators.contacts()) - await bling.contacts().update(contact[0].id, 123) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_UPDATE_DATA' - ) - } -}) - -test("shouldn't update a contact when calling `.update()` method with data as array", async () => { - try { - const contact = await bling.contacts().create(generators.contacts()) - await bling.contacts().update(contact[0].id, []) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_UPDATE_DATA' - ) - } -}) - -test("shouldn't update a contact when calling `.update()` method with data as an empty object", async () => { - try { - const contact = await bling.contacts().create(generators.contacts()) - await bling.contacts().update(contact[0].id, {}) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "data" argument must be a not empty object', - '500', - 'ERR_INCORRECT_UPDATE_DATA' - ) - } -}) - -test.concurrent( - "shouldn't update a contact when calling `.update()` method with id as an empty string", - async () => { - try { - await bling.contacts().update('', { - cpf_cnpj: CPF.generate() - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "id" argument must be a number or string', - '500', - 'ERR_INCORRECT_UPDATE_ID' - ) - } - } -) - -test.concurrent( - "shouldn't update a contact when calling `.update()` method with an inexistent id", - async () => { - try { - await bling.contacts().update(0, { - cpf_cnpj: CPF.generate() - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "id" argument must be a number or string', - '500', - 'ERR_INCORRECT_UPDATE_ID' - ) - } - } -) - -test.concurrent( - "shouldn't update a contact when calling `.update()` method with id as null", - async () => { - try { - await bling.contacts().update(null, { - cpf_cnpj: CPF.generate() - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "id" argument must be a number or string', - '500', - 'ERR_INCORRECT_UPDATE_ID' - ) - } - } -) - -test.concurrent( - "shouldn't update a contact when calling `.update()` method with id as undefined", - async () => { - try { - await bling.contacts().update(null, { - cpf_cnpj: CPF.generate() - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "id" argument must be a number or string', - '500', - 'ERR_INCORRECT_UPDATE_ID' - ) - } - } -) - -test.concurrent( - "shouldn't update a contact when calling `.update()` method with id as object", - async () => { - try { - await bling.contacts().update( - {}, - { - cpf_cnpj: CPF.generate() - } - ) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "id" argument must be a number or string', - '500', - 'ERR_INCORRECT_UPDATE_ID' - ) - } - } -) - -test.concurrent( - "shouldn't update a contact when calling `.update()` method with id as array", - async () => { - try { - await bling.contacts().update([], { - cpf_cnpj: CPF.generate() - }) - expect(false).toBe(true) - } catch (err) { - testError( - err, - 'The "id" argument must be a number or string', - '500', - 'ERR_INCORRECT_UPDATE_ID' - ) - } - } -) - -test('should update a contact when calling `.update()` method with proper parameters', async () => { - const rawContact = generators.contacts() - const contact = await bling.contacts().create(rawContact) - - await expect( - bling.contacts().update(contact[0].id, { - nome: 'Usuário atualizado', - cpf_cnpj: rawContact.cpf_cnpj, - contribuinte: rawContact.contribuinte, - tipoPessoa: rawContact.tipoPessoa, - ie_rg: rawContact.ie_rg - }) - ).resolves.toBeDefined() -}) diff --git a/test/entities/create.spec.js b/test/entities/create.spec.js deleted file mode 100644 index 7a76fa5..0000000 --- a/test/entities/create.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import { bling, defaultBeforeEach, generators } from '../config/bling' - -jest.setTimeout(60000) - -const table = Object.keys(generators) -const runs = 3 - -beforeEach(() => { - return defaultBeforeEach() -}) - -for (let i = 0; i < runs; i++) { - test.each(table)( - 'should create an entity of type "%s" when calling `.create()` method', - async (name) => { - const entity = generators[name]() - - try { - const promise = await bling[name]().create(entity) - - expect(promise).toBeDefined() - expect(promise).toBeTruthy() - } catch (err) { - console.log( - `name: ${name}, ${JSON.stringify(entity)}, ${JSON.stringify( - err.data.errors - )}` - ) - expect(false).toBe(true) - } - } - ) -} diff --git a/test/entities/find.spec.js b/test/entities/find.spec.js deleted file mode 100644 index 830e376..0000000 --- a/test/entities/find.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import { bling, defaultBeforeEach } from '../config/bling' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -const table = [ - 'commercialProposals', - 'contacts', - 'deposits', - 'orders', - 'products', - 'purchaseOrders' -] - -const testIdError = (err) => { - expect(err.message).toEqual('The "id" argument must be a number or string.') - expect(err.status).toEqual('500') - expect(err.code).toEqual('ERR_INCORRECT_FIND_ID') -} - -test.concurrent.each(table)( - "shouldn't find a %s when calling `.find()` method without any parameters", - async (name) => { - try { - await bling[name]().find() - expect(false).toBe(true) - } catch (err) { - testIdError(err) - } - } -) - -test.concurrent.each(table)( - "shouldn't find a %s when calling `.find()` method with parameter as undefined", - async (name) => { - try { - await bling[name]().find(undefined) - expect(false).toBe(true) - } catch (err) { - testIdError(err) - } - } -) - -test.concurrent.each(table)( - "shouldn't find a %s when calling `.find()` method with parameter as null", - async (name) => { - try { - await bling[name]().find(null) - expect(false).toBe(true) - } catch (err) { - testIdError(err) - } - } -) - -test.concurrent.each(table)( - "shouldn't find a %s when calling `.find()` method with parameter as empty string", - async (name) => { - try { - await bling[name]().find('') - expect(false).toBe(true) - } catch (err) { - testIdError(err) - } - } -) diff --git a/test/entities/findBy.spec.js b/test/entities/findBy.spec.js deleted file mode 100644 index ad681c9..0000000 --- a/test/entities/findBy.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import { bling, defaultBeforeEach } from '../config/bling' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -const table = [ - 'commercialProposals', - 'contacts', - 'deposits', - 'orders', - 'products', - 'purchaseOrders' -] - -const testError = (err) => { - expect(err.message).toEqual('No options passed to `.findBy()` method') - expect(err.status).toEqual('500') - expect(err.code).toEqual('ERR_INCORRECT_FINDBY_OPTIONS') -} - -test.concurrent.each(table)( - "shouldn't find %s when calling `.findBy()` method without options", - async (name) => { - try { - await bling[name]().findBy() - expect(false).toBe(true) - } catch (err) { - testError(err) - } - } -) diff --git a/test/entities/products/delete.test.ts b/test/entities/products/delete.test.ts deleted file mode 100644 index b5dbcbd..0000000 --- a/test/entities/products/delete.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { bling } from '../../config/bling' -import { v4 as uuidv4 } from 'uuid' - -jest.setTimeout(100000) - -test('should delete a product when calling `.delete()` method with an existent id', async () => { - const product = await bling.products().create({ - descricao: 'Produto Teste', - codigo: uuidv4() - }) - - if (product[0].codigo) { - await expect( - bling.products().delete(product[0].codigo) - ).resolves.toBeDefined() - } else { - expect(false).toBe(true) - } -}) diff --git a/test/entities/products/findBySupplierCode.test.ts b/test/entities/products/findBySupplierCode.test.ts deleted file mode 100644 index 03d784f..0000000 --- a/test/entities/products/findBySupplierCode.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - bling, - exampleSupplierId, - exampleSupplierProductId, - defaultBeforeEach -} from '../../config/bling' - -jest.setTimeout(60000) - -beforeEach(() => { - return defaultBeforeEach() -}) - -test('should find products by supplier code when calling `.findBySupplierCode()` method without raw option', async () => { - await expect( - bling - .products() - .findBySupplierCode(exampleSupplierProductId, exampleSupplierId) - ).resolves.toBeDefined() -}) - -test('should find products by supplier code when calling `.findBySupplierCode()` method with raw option settled up to true', async () => { - await expect( - bling - .products() - .findBySupplierCode(exampleSupplierProductId, exampleSupplierId, { - raw: true - }) - ).resolves.toBeDefined() -}) - -test('should find products by supplier code when calling `.findBySupplierCode()` method with raw option settled up to false', async () => { - await expect( - bling - .products() - .findBySupplierCode(exampleSupplierProductId, exampleSupplierId, { - raw: false - }) - ).resolves.toBeDefined() -}) - -test('should find products by supplier code when calling `.findBySupplierCode()` method with options without raw option', async () => { - await expect( - bling - .products() - .findBySupplierCode(exampleSupplierProductId, exampleSupplierId, { - params: { - estoque: 'S', - imagem: 'S' - } - }) - ).resolves.toBeDefined() -}) - -test('should find products by supplier code when calling `.findBySupplierCode()` method with options with raw option settled up to true', async () => { - await expect( - bling - .products() - .findBySupplierCode(exampleSupplierProductId, exampleSupplierId, { - params: { estoque: 'S', imagem: 'S' }, - raw: true - }) - ).resolves.toBeDefined() -}) - -test('should find products by supplier code when calling `.findBySupplierCode()` method with options with raw option settled up to false', async () => { - await expect( - bling - .products() - .findBySupplierCode(exampleSupplierProductId, exampleSupplierId, { - params: { estoque: 'S', imagem: 'S' }, - raw: false - }) - ).resolves.toBeDefined() -}) diff --git a/test/generators/billsToPay.js b/test/generators/billsToPay.js deleted file mode 100644 index 5feee47..0000000 --- a/test/generators/billsToPay.js +++ /dev/null @@ -1,136 +0,0 @@ -import chance from 'chance' - -import nullable from './helpers/nullable' -import stringifyDate from './helpers/stringifyDate' - -import { CPF, CNPJ } from 'cpf_cnpj' - -const seed = chance() - -const generator = () => { - const obj = {} - - const dateToday = new Date() - - const dataEmissaoDate = seed.date({ - min: new Date(dateToday.getFullYear() - 2, 0), - max: new Date() - }) - const dataEmissao = stringifyDate(dataEmissaoDate) - - const vencimentoOriginalDate = nullable( - seed.date({ - min: dataEmissaoDate, - max: new Date( - dataEmissaoDate.getFullYear(), - dataEmissaoDate.getMonth() + 1, - dataEmissaoDate.getDate() + 10 - ) - }) - ) - const vencimentoOriginal = stringifyDate(vencimentoOriginalDate) - - const competenciaDate = nullable( - seed.date({ - max: dataEmissaoDate - }) - ) - const competencia = stringifyDate(competenciaDate) - - obj.dataEmissao = dataEmissao - obj.vencimentoOriginal = vencimentoOriginal - obj.competencia = competencia - obj.nroDocumento = nullable( - seed.string({ length: 25, alpha: false, symbols: false }), - 90 - ) - obj.valor = seed.floating({ fixed: 2, min: 0, max: 1000 }) - obj.historico = nullable(seed.paragraph()) - // obj.categoria = nullable(seed.sentence({ words: 3 })) - // obj.portador = nullable(seed.word()) - // obj.idFormaPagamento = '' - const ocorrenciaTipo = seed.pickone(['U', 'P', 'M', 'T', 'S', 'A', 'E']) - const diaVencimento = seed.integer({ min: 1, max: 31 }) - const nroParcelas = seed.integer({ min: 1, max: 200 }) - const diaSemanaVencimento = seed.pickone([1, 2, 3, 4, 5, 6, 7]) - - obj.ocorrencia = { - ocorrenciaTipo, - diaVencimento: ['P', 'M', 'T', 'S', 'A'].includes(ocorrenciaTipo) - ? diaVencimento - : null, - nroParcelas: ocorrenciaTipo === 'P' ? nroParcelas : null, - diaSemanaVencimento: - ocorrenciaTipo === 'E' - ? diaSemanaVencimento - : nullable(diaSemanaVencimento) - } - - if (seed.bool()) { - const tipoPessoa = seed.pickone(['F', 'J']) - let cpfCnpj - - if (tipoPessoa === 'F') { - cpfCnpj = CPF.generate() - } else { - cpfCnpj = CNPJ.generate() - } - - obj.fornecedor = { - nome: seed.sentence(), - // id = '' - cpf_cnpj: cpfCnpj, - tipoPessoa, - ie_rg: nullable( - seed.string({ alpha: false, symbols: false, length: 18 }) - ), - endereco: nullable(seed.address()), - numero: nullable(seed.string({ symbols: false })), - complemento: nullable(seed.sentence()), - cidade: nullable(seed.city()), - bairro: nullable(seed.province()), - cep: nullable(seed.zip()), - uf: nullable( - seed.pickone([ - 'AC', - 'AL', - 'AP', - 'AM', - 'BA', - 'CE', - 'DF', - 'ES', - 'GO', - 'MA', - 'MT', - 'MS', - 'MG', - 'PA', - 'PB', - 'PR', - 'PE', - 'PI', - 'RJ', - 'RN', - 'RS', - 'RO', - 'RR', - 'SC', - 'SP', - 'SE', - 'TO' - ]) - ), - email: nullable(seed.email()), - fone: nullable(seed.string({ length: 8, alpha: false, symbols: false })), - celular: nullable( - seed.string({ length: 9, alpha: false, symbols: false }), - 10 - ) - } - } - - return obj -} - -export default generator diff --git a/test/generators/billsToReceive.js b/test/generators/billsToReceive.js deleted file mode 100644 index 1d29393..0000000 --- a/test/generators/billsToReceive.js +++ /dev/null @@ -1,107 +0,0 @@ -import chance from 'chance' - -import nullable from './helpers/nullable' -import stringifyDate from './helpers/stringifyDate' -import uf from './template/uf' - -import { CPF, CNPJ } from 'cpf_cnpj' - -const seed = chance() - -const generator = () => { - const obj = {} - - const dateToday = new Date() - - const dataEmissaoDate = seed.date({ - min: new Date(dateToday.getFullYear() - 2, 0), - max: new Date() - }) - const dataEmissao = stringifyDate(dataEmissaoDate) - - const vencimentoOriginalDate = nullable( - seed.date({ - min: dataEmissaoDate, - max: new Date( - dataEmissaoDate.getFullYear(), - dataEmissaoDate.getMonth() + 1, - dataEmissaoDate.getDate() + 10 - ) - }) - ) - const vencimentoOriginal = stringifyDate(vencimentoOriginalDate) - - const competenciaDate = nullable( - seed.date({ - max: dataEmissaoDate - }) - ) - const competencia = stringifyDate(competenciaDate) - - obj.dataEmissao = dataEmissao - obj.vencimentoOriginal = vencimentoOriginal - obj.competencia = competencia - obj.nroDocumento = nullable( - seed.string({ length: 25, alpha: false, symbols: false, numeric: true }), - 90 - ) - obj.valor = seed.floating({ fixed: 2, min: 0, max: 1000 }) - obj.historico = nullable(seed.paragraph()) - // obj.categoria = nullable(seed.sentence({ words: 3 })) - // obj.portador = nullable(seed.word()) - // obj.idFormaPagamento = '' - const ocorrenciaTipo = seed.pickone(['U', 'P', 'M', 'T', 'S', 'A', 'E']) - const diaVencimento = seed.integer({ min: 1, max: 31 }) - const nroParcelas = seed.integer({ min: 1, max: 200 }) - const diaSemanaVencimento = seed.pickone([1, 2, 3, 4, 5, 6, 7]) - - obj.ocorrencia = { - ocorrenciaTipo, - diaVencimento: ['P', 'M', 'T', 'S', 'A'].includes(ocorrenciaTipo) - ? diaVencimento - : null, - nroParcelas: ocorrenciaTipo === 'P' ? nroParcelas : null, - diaSemanaVencimento: - ocorrenciaTipo === 'E' - ? diaSemanaVencimento - : nullable(diaSemanaVencimento) - } - - const tipoPessoa = seed.pickone(['F', 'J']) - let cpfCnpj - - if (tipoPessoa === 'F') { - cpfCnpj = CPF.generate() - } else { - cpfCnpj = CNPJ.generate() - } - - obj.cliente = { - nome: seed.sentence(), - // id = '' - cpf_cnpj: cpfCnpj, - tipoPessoa, - ie_rg: nullable( - seed.string({ alpha: false, symbols: false, numeric: true, length: 18 }) - ), - endereco: nullable(seed.address()), - numero: nullable( - seed.string({ alpha: true, symbols: false, numeric: true }) - ), - complemento: nullable(seed.sentence()), - cidade: nullable(seed.city()), - bairro: nullable(seed.province()), - cep: nullable(seed.zip()), - uf: nullable(uf()), - email: nullable(seed.email()), - fone: nullable(seed.string({ length: 8, alpha: false, symbols: false })), - celular: nullable( - seed.string({ length: 9, alpha: false, symbols: false }), - 10 - ) - } - - return obj -} - -export default generator diff --git a/test/generators/categories.js b/test/generators/categories.js deleted file mode 100644 index 4c7f1a5..0000000 --- a/test/generators/categories.js +++ /dev/null @@ -1,14 +0,0 @@ -// Global imports -import chance from 'chance' -// import nullable from './helpers/nullable' - -// Config -const seed = chance() - -// Generator -const generator = () => ({ - descricao: seed.sentence() - // idCategoriaPai: nullable(seed.integer({ min: 0, max: 10 }), 10) -}) - -export default generator diff --git a/test/generators/contacts.js b/test/generators/contacts.js deleted file mode 100644 index d41281e..0000000 --- a/test/generators/contacts.js +++ /dev/null @@ -1,45 +0,0 @@ -// Global imports -import chance from 'chance' -import nullable from './helpers/nullable' - -// Template attributes -import uf from './template/uf' -import tipoPessoa from './template/tipoPessoa' -import cpfCnpj from './template/cpfCnpj' -import contribuinte from './template/contribuinte' - -// Config -const seed = chance() - -// Generator -const generator = () => { - const tipoPessoaValue = tipoPessoa() - - // Avoid create cases with ie_rg !== null - const rawContribuinteVal = contribuinte() - const contribuinteVal = rawContribuinteVal === '1' ? '2' : rawContribuinteVal - - return { - nome: seed.sentence({ words: 3 }), - fantasia: nullable(seed.sentence({ words: 3 }), 30), - tipoPessoa: tipoPessoaValue, - contribuinte: contribuinteVal, - cpf_cnpj: cpfCnpj(tipoPessoaValue), - ie_rg: contribuinteVal === '2' && tipoPessoaValue !== 'E' ? 'ISENTO' : null, - endereco: seed.street(), - numero: nullable(seed.integer({ min: 1, max: 500 }), 30), - complemento: seed.sentence(), - bairro: seed.province(), - cep: seed.zip(), - cidade: nullable(seed.city(), 30), - uf: nullable(uf()), - fone: seed.string({ length: 8, alpha: false }), - celular: nullable(seed.string({ length: 9, alpha: false }), 10), - email: nullable(seed.email()), - emailNfe: nullable(seed.email(), 15), - site: nullable(seed.domain()), - obs: nullable(seed.paragraph()) - } -} - -export default generator diff --git a/test/generators/deposits.js b/test/generators/deposits.js deleted file mode 100644 index 5a02ab1..0000000 --- a/test/generators/deposits.js +++ /dev/null @@ -1,26 +0,0 @@ -// Global imports -import chance from 'chance' -import nullable from './helpers/nullable' - -// Config -const seed = chance() - -// Generator -const generator = () => { - const obj = {} - - obj.descricao = seed.sentence({ words: 5 }) - obj.desconsiderarSaldo = nullable(seed.bool({ likelihood: 20 })) - obj.depositoPadrao = nullable(seed.bool({ likelihood: 5 })) - obj.situacao = seed.pickone(['A', 'I']) - - Object.keys(obj).forEach((key) => { - if (obj[key] === null) { - delete obj[key] - } - }) - - return obj -} - -export default generator diff --git a/test/generators/helpers/nullable.ts b/test/generators/helpers/nullable.ts deleted file mode 100644 index bc494cd..0000000 --- a/test/generators/helpers/nullable.ts +++ /dev/null @@ -1,19 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Return the desired value with a probability to be null given a likelihood. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @param {T} value The value desired to be returned. - * @param {number} likelihood The likelihood to return the value itself. - * - * @returns {T|null} The value itself or null. - */ -export default (value: T, likelihood = 50): T | null => { - return seed.bool({ likelihood }) ? value : null -} diff --git a/test/generators/helpers/stringifyDate.ts b/test/generators/helpers/stringifyDate.ts deleted file mode 100644 index ecc5047..0000000 --- a/test/generators/helpers/stringifyDate.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Return a stringified form of Date object. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @param {Date} date The date object to be stringified - * - * @returns {string} The stringified date. - */ -export default (date: Date) => { - if (date) { - return `${String(date.getDate()).padStart(2, '0')}/${String( - date.getMonth() + 1 - ).padStart(2, '0')}/${date.getFullYear()}` - } else { - return date - } -} diff --git a/test/generators/nfces.js b/test/generators/nfces.js deleted file mode 100644 index 9007a3c..0000000 --- a/test/generators/nfces.js +++ /dev/null @@ -1,183 +0,0 @@ -// Global imports -import chance from 'chance' -import nullable from './helpers/nullable' -import stringifyDate from './helpers/stringifyDate' -import { CNPJ } from 'cpf_cnpj' - -// Template attributes -import uf from './template/uf' -import tipoPessoa from './template/tipoPessoa' -import cpfCnpj from './template/cpfCnpj' -import contribuinte from './template/contribuinte' -import tipoFrete from './template/tipoFrete' -import un from './template/un' -import origem from './template/origem' - -// Config -const seed = chance() -const dateToday = new Date() - -// Generator -const nfce = () => { - const dataOperacaoDate = seed.date({ - min: new Date(dateToday.getFullYear() - 2, 0), - max: new Date() - }) - - const dataOperacao = stringifyDate(dataOperacaoDate) - - const tipoPessoaValue = tipoPessoa() - - const data = { - tipo: seed.pickone(['E', 'S']), - numero_loja: nullable( - seed.string({ length: 6, alpha: false, numeric: true, symbols: false }) - ), - nat_operacao: nullable(seed.sentence({ words: 5 })), - data_operacao: nullable(dataOperacao), - nome: seed.sentence({ words: 3 }), - tipo_pessoa: tipoPessoaValue, - cpf_cnpj: nullable(cpfCnpj(tipoPessoaValue)), - ie_rg: nullable(seed.integer({ min: 100000000000, max: 199999999999 }), 30), - contribuinte: nullable(contribuinte), - endereco: seed.street(), - numero: nullable(seed.integer({ min: 1, max: 500 }), 30), - complemento: nullable(seed.sentence()), - bairro: seed.province(), - cep: seed.zip(), - cidade: nullable(seed.city(), 30), - uf: uf(), - pais: nullable( - seed.string({ length: 10, alpha: true, numeric: false, symbols: false }) - ), - fone: nullable(seed.string({ length: 8, alpha: false })), - email: nullable(seed.email()), - vlr_frete: nullable(seed.floating({ fixed: 2, min: 0, max: 10000 })), - vlr_seguro: nullable(seed.floating({ fixed: 2, min: 0, max: 10000 })), - vlr_despesas: nullable(seed.floating({ fixed: 2, min: 0, max: 10000 })), - vlr_desconto: nullable(seed.floating({ fixed: 2, min: 0, max: 10000 })), - obs: seed.paragraph() - } - - // Transporte - if (seed.bool()) { - data.transporte = { - transportadora: nullable(seed.sentence({ words: 2 })), - cpf_cnpj: nullable(CNPJ.generate()), - ie_rg: nullable( - seed.integer({ min: 100000000000, max: 199999999999 }), - 30 - ), - endereco: nullable(seed.street()), - cidade: nullable(seed.city(), 30), - uf: nullable(uf()), - placa: nullable( - seed.string({ length: 7, alpha: true, numeric: true, symbols: false }) - ), - uf_veiculo: nullable(uf()), - marca: nullable(seed.string({ length: 10, symbols: false })), - tipo_frete: nullable(tipoFrete()), - qtde_volumes: nullable(seed.integer({ min: 1, max: 10 })), - especie: nullable(seed.sentence({ words: 2 })), - numero: nullable(seed.string({ length: 10 })), - peso_bruto: nullable(seed.floating({ fixed: 2, min: 0.1, max: 10 })), - peso_liquido: nullable(seed.floating({ fixed: 2, min: 0.1, max: 10 })), - servico_correios: nullable(seed.string({ length: 10, symbols: false })) - } - - if (seed.bool({ likelihood: 50 })) { - data.transporte.dados_etiqueta = { - nome: nullable(seed.sentence({ words: 3 })), - endereco: nullable(seed.street()), - numero: nullable(seed.integer({ min: 1, max: 500 }), 30), - complemento: nullable(seed.sentence()), - bairro: nullable(seed.province()), - municipio: nullable(seed.city(), 30), - uf: nullable(uf()), - cep: nullable(seed.zip()) - } - } - } - - // Itens - if (seed.bool()) { - const numItens = seed.integer({ min: 1, max: 4 }) - data.itens = [] - - for (let i = 0; i < numItens; i++) { - data.itens.push({ - item: { - codigo: nullable(seed.string({ length: 15, symbols: false })), - descricao: seed.sentence({ words: 5 }), - un: un(), - qtde: seed.integer({ min: 1, max: 25 }), - vlr_unit: seed.floating({ fixed: 2, min: 0, max: 1000 }), - tipo: seed.pickone(['P', 'S']), - peso_bruto: nullable(seed.floating({ fixed: 2, min: 0.1, max: 10 })), - peso_liq: nullable(seed.floating({ fixed: 2, min: 0.1, max: 10 })), - class_fiscal: nullable( - seed.string({ length: 8, symbols: false, alpha: false }) - ), - cest: nullable( - seed.string({ length: 8, symbols: false, alpha: false }) - ), - cod_servico: nullable( - seed.string({ length: 8, symbols: false, alpha: false }) - ), - origem: origem() - } - }) - } - } - - // Parcelas - if (seed.bool({ likelihood: 30 })) { - const numParcelas = seed.integer({ min: 1, max: 4 }) - - const dataParcelaDate = seed.date({ - min: new Date(dateToday.getFullYear() - 2, 0), - max: new Date() - }) - - const dataParcela = stringifyDate(dataParcelaDate) - - const diasOrData = seed.bool() - - data.parcelas = [] - - for (let i = 0; i < numParcelas; i++) { - data.parcelas.push({ - parcela: { - dias: nullable(diasOrData ? seed.integer({ min: 1, max: 30 }) : null), - data: nullable(!diasOrData ? dataParcela : null), - vlr: nullable(seed.floating({ fixed: 2, min: 0.1, max: 1000 })), - obs: nullable(seed.sentence({ words: 10 })), - forma: nullable(seed.sentence({ words: 2 })) - } - }) - } - } - - // NF produtor rural referenciada - if (seed.bool({ likelihood: 10 })) { - data.nf_produtor_rural_referenciada = { - numero: nullable(seed.string({ length: 10, symbols: false })), - serie: nullable(seed.string({ length: 2, symbols: false })), - ano_mes_emissao: nullable( - seed.string({ length: 4, alpha: false, symbols: false }) - ) - } - } - - // Intermediador - if (seed.bool({ likelihood: 10 })) { - data.intermediador = { - cnpj: nullable(CNPJ.generate()), - nomeUsuario: seed.string({ length: 10, symbols: false }) - } - } - - return data -} - -export default nfce diff --git a/test/generators/paymentMethods.js b/test/generators/paymentMethods.js deleted file mode 100644 index d6858aa..0000000 --- a/test/generators/paymentMethods.js +++ /dev/null @@ -1,57 +0,0 @@ -import chance from 'chance' -import nullable from './helpers/nullable' - -import { CNPJ } from 'cpf_cnpj' - -const seed = chance() - -const paymentMethod = () => { - const situacao = nullable(seed.pickone(['0', '1'])) - let padrao - - if (!situacao || situacao === '0') { - padrao = '0' - } else { - padrao = nullable(seed.pickone(['0', '1'])) - } - - return { - descricao: seed.sentence({ words: 10 }), - codigofiscal: nullable( - seed.pickone([ - '1', - '2', - '3', - '4', - '5', - '10', - '11', - '12', - '13', - '14', - '15', - '90', - '99' - ]) - ), - condicao: nullable(seed.sentence({ words: 5 })), - destino: nullable(seed.pickone(['1', '2', '3'])), - padrao, - situacao, - dadoscartao: { - bandeira: nullable( - seed.pickone(['1', '2', '3', '4', '5', '6', '7', '8', '9', '99']) - ), - tipointegracao: nullable(seed.pickone(['1', '2'])), - cnpjcredenciadora: CNPJ.generate(), - autoliquidacao: nullable(seed.pickone(['1', '2'])) - }, - dadostaxas: { - valoraliquota: nullable(seed.floating({ min: 0, max: 1000, fixed: 2 })), - valorfixo: nullable(seed.floating({ min: 0, max: 1000, fixed: 2 })), - prazo: nullable(seed.integer({ min: 0, max: 100 })) - } - } -} - -export default paymentMethod diff --git a/test/generators/purchaseOrders.js b/test/generators/purchaseOrders.js deleted file mode 100644 index 0ca32b3..0000000 --- a/test/generators/purchaseOrders.js +++ /dev/null @@ -1,120 +0,0 @@ -import chance from 'chance' -import { CPF, CNPJ } from 'cpf_cnpj' - -import nullable from './helpers/nullable' -import stringifyDate from './helpers/stringifyDate' -import cidades from './template/cidade' -import contribuinte from './template/contribuinte' - -import ufs from './template/uf' - -const seed = chance() - -const generator = () => { - const tipopessoa = seed.pickone(['F', 'J']) - - let cpfcnpj - - if (tipopessoa === 'F') { - cpfcnpj = CPF.generate() - } else { - cpfcnpj = CNPJ.generate() - } - - const uf = ufs() - const cidade = cidades(uf) - const rawContribuinteVal = nullable(contribuinte()) - const contribuinteVal = rawContribuinteVal === '1' ? '2' : rawContribuinteVal - - const entity = { - numeropedido: seed.string({ - symbols: false, - alpha: false, - numeric: true, - length: 10 - }), - datacompra: nullable(stringifyDate(seed.date({ max: new Date(2025, 0) }))), - dataprevista: nullable( - stringifyDate(seed.date({ max: new Date(2025, 0) })) - ), - ordemcompra: nullable(seed.string({ length: 30 })), - desconto: nullable(seed.integer({ min: 0, max: 100 })), - observacoes: nullable(seed.paragraph()), - observacaointerna: nullable(seed.paragraph()), - // idcategoria?: number - fornecedor: { - // id: nullable(seed.integer({min: 0})), - nome: seed.sentence({ words: 7 }), - tipopessoa, - cpfcnpj, - contribuinte: contribuinteVal, - ie: contribuinteVal ? (contribuinteVal === '2' ? 'ISENTO' : null) : null, - endereco: seed.street(), - endereconro: nullable( - seed.string({ length: 5, symbols: false, alpha: true, numeric: true }) - ), - complemento: nullable(seed.sentence({ words: 5 })), - bairro: seed.province(), - uf, - cidade, - cep: seed.string({ - length: 8, - alpha: false, - symbols: false, - numeric: true - }), - fone: seed.string({ - length: 8, - alpha: false, - numeric: true, - symbols: false - }), - celular: nullable(seed.string({ length: 9, alpha: false }), 10), - email: seed.email() - } - } - - const numItems = seed.integer({ min: 1, max: 10 }) - entity.itens = [] - - for (let i = 0; i < numItems; i++) { - const item = { - codigo: seed.string({ - symbols: false, - numeric: true, - alpha: true, - length: 20 - }), - descricao: seed.sentence({ words: 7 }), - un: seed.pickone(['pc', 'un', 'cx']), - qtde: seed.integer({ min: 1, max: 1000 }), - valor: seed.floating({ min: 0, max: 1000, fixed: 10 }) - } - - entity.itens.push({ item }) - } - - if (seed.bool({ likelihood: 30 })) { - entity.transporte = { - transportador: seed.sentence({ words: 5 }), - freteporconta: seed.pickone(['R', 'D', 'T', '3', '4', 'S']), - qtdvolumes: seed.integer({ min: 0, max: 11 }), - frete: seed.floating({ min: 0, fixed: 2, max: 1000 }) - } - } - - // if (seed.bool({ likelihood: 10 })) { - // parcelas?: { - // parcela?: { - // nrodias: number - // valor: number - // obs?: string - // idformapagamento: number - // }[] - // } - // } - - return entity -} - -export default generator diff --git a/test/generators/serviceInvoices.js b/test/generators/serviceInvoices.js deleted file mode 100644 index aeaf1ab..0000000 --- a/test/generators/serviceInvoices.js +++ /dev/null @@ -1,91 +0,0 @@ -// Global imports -import chance from 'chance' -import nullable from './helpers/nullable' -import stringifyDate from './helpers/stringifyDate' - -// Template attributes -import cpfCnpj from './template/cpfCnpj' -import uf from './template/uf' - -// Config -const seed = chance() -const dateToday = new Date() - -// Generator -const generator = () => { - const dataCriacaoDate = seed.date({ - min: new Date(dateToday.getFullYear() - 2, 0), - max: new Date() - }) - - const dataCriacao = stringifyDate(dataCriacaoDate) - - const data = { - data: nullable(dataCriacao), - vendedor: seed.sentence({ words: 3 }), - numero_rps: nullable(seed.string({ length: 6, symbols: false })), - reter_inss: nullable(seed.pickone(['S', 'N'])), - desconto: nullable(seed.floating({ fixed: 2, min: 0, max: 1000000 })), - cliente: { - nome: seed.sentence({ words: 3 }), - cnpj: cpfCnpj('J'), - ie: nullable(seed.integer({ min: 100000000000, max: 199999999999 }), 30), - im: nullable(seed.integer({ min: 100000000000, max: 199999999999 }), 30), - endereco: seed.street(), - numero: seed.integer({ min: 1, max: 500 }), - complemento: nullable(seed.sentence()), - bairro: seed.province(), - cep: seed.zip(), - cidade: seed.city(), - uf: uf(), - fone: nullable(seed.string({ length: 8, alpha: false })), - email: nullable(seed.email()) - } - } - - // Serviços - const numServicos = seed.integer({ min: 1, max: 4 }) - data.servicos = [] - - for (let i = 0; i < numServicos; i++) { - data.servicos.push({ - servico: { - descricao: seed.sentence({ words: 5 }), - valor: seed.floating({ fixed: 2, min: 0, max: 1000 }), - codigo: seed.string({ length: 15, symbols: false }) - } - }) - } - - // Parcelas - if (seed.bool({ likelihood: 30 })) { - const numParcelas = seed.integer({ min: 1, max: 4 }) - - const dataParcelaDate = seed.date({ - min: new Date(dateToday.getFullYear() - 2, 0), - max: new Date() - }) - - const dataParcela = stringifyDate(dataParcelaDate) - - const diasOrData = seed.bool() - - data.parcelas = [] - - for (let i = 0; i < numParcelas; i++) { - data.parcelas.push({ - parcela: { - dias: nullable(diasOrData ? seed.integer({ min: 1, max: 30 }) : null), - data: nullable(!diasOrData ? dataParcela : null), - vlr: nullable(seed.floating({ fixed: 2, min: 0.1, max: 1000 })), - obs: nullable(seed.sentence({ words: 10 })), - forma: nullable(seed.sentence({ words: 2 })) - } - }) - } - } - - return data -} - -export default generator diff --git a/test/generators/shopCategories.js b/test/generators/shopCategories.js deleted file mode 100644 index b8f3dcf..0000000 --- a/test/generators/shopCategories.js +++ /dev/null @@ -1,14 +0,0 @@ -// Global imports -import chance from 'chance' - -// Config -const seed = chance() - -// Generator -const generator = (idCategoria, idVinculoLoja) => ({ - idCategoria, - idVinculoLoja, - descricaoVinculo: seed.sentence({ words: 5 }) -}) - -export default generator diff --git a/test/generators/template/cidade.ts b/test/generators/template/cidade.ts deleted file mode 100644 index 973a848..0000000 --- a/test/generators/template/cidade.ts +++ /dev/null @@ -1,134 +0,0 @@ -import chance from 'chance' -import ufs from './uf' - -const seed = chance() - -const cities = [ - { - sigla: 'AC', - cidade: 'Acrelândia' - }, - { - sigla: 'AL', - cidade: 'Água Branca' - }, - { - sigla: 'AM', - cidade: 'Alvarães' - }, - { - sigla: 'AP', - cidade: 'Cutias' - }, - { - sigla: 'BA', - cidade: 'Acajutiba' - }, - { - sigla: 'CE', - cidade: 'Acarape' - }, - { - sigla: 'DF', - cidade: 'Brasília' - }, - { - sigla: 'ES', - cidade: 'Água Doce do Norte' - }, - { - sigla: 'GO', - cidade: 'Alexânia' - }, - { - sigla: 'MA', - cidade: 'Água Doce do Maranhão' - }, - { - sigla: 'MG', - cidade: 'Abaeté' - }, - { - sigla: 'MS', - cidade: 'Amambaí' - }, - { - sigla: 'MT', - cidade: 'Alta Floresta' - }, - { - sigla: 'PA', - cidade: 'Acará' - }, - { - sigla: 'PB', - cidade: 'Aguiar' - }, - { - sigla: 'PE', - cidade: 'Afrânio' - }, - { - sigla: 'PI', - cidade: 'Alagoinha do Piauí' - }, - { - sigla: 'PR', - cidade: 'Agudos do Sul' - }, - { - sigla: 'RJ', - cidade: 'Arraial do Cabo' - }, - { - sigla: 'RN', - cidade: 'Água Nova' - }, - { - sigla: 'RO', - cidade: 'Alto Paraíso' - }, - { - sigla: 'RR', - cidade: 'Boa Vista' - }, - { - sigla: 'RS', - cidade: 'Aceguá' - }, - { - sigla: 'SC', - cidade: 'Abdon Batista' - }, - { - sigla: 'SE', - cidade: 'Amparo de São Francisco' - }, - { - sigla: 'SP', - cidade: 'Adamantina' - }, - { - sigla: 'TO', - cidade: 'Abreulândia' - } -] - -/** - * Generates a Brazilian city name given a Brazilian UF. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @param {string} uf The UF to choose a city in. - * - * @returns {string} The city name. - */ -export default (uf: ReturnType) => { - const filteredCities = cities - .filter((city) => city.sigla === uf) - .map((city) => city.cidade) - - return seed.pickone(filteredCities) -} diff --git a/test/generators/template/contribuinte.ts b/test/generators/template/contribuinte.ts deleted file mode 100644 index 987c39c..0000000 --- a/test/generators/template/contribuinte.ts +++ /dev/null @@ -1,19 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Generates a random value for the attribute "contribuinte" among the default ones. - * - * The default values are the following: - * - 1: Contribuinte do ICMS - * - 2: Contribuinte isento do ICMS - * - 9: Não contribuinte - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @returns {string} One of the default values. - */ -export default () => seed.pickone(['1', '2', '9']) diff --git a/test/generators/template/cpfCnpj.ts b/test/generators/template/cpfCnpj.ts deleted file mode 100644 index 8f81035..0000000 --- a/test/generators/template/cpfCnpj.ts +++ /dev/null @@ -1,23 +0,0 @@ -const { CNPJ, CPF } = require('cpf_cnpj') - -/** - * Generates either a random CPF, or a random CNPJ, or just a random null value given a person type. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @param {string} tipoPessoa The person type in Bling standards, either being 'F', 'J', or 'E'. - * - * @returns {string|null} The generated CPF, or the generated CNPJ, or null. - */ -export default (tipoPessoa: 'J' | 'F' | unknown): string | null => { - switch (tipoPessoa) { - case 'J': - return CNPJ.generate() - case 'F': - return CPF.generate() - default: - return null - } -} diff --git a/test/generators/template/origem.ts b/test/generators/template/origem.ts deleted file mode 100644 index c72c05c..0000000 --- a/test/generators/template/origem.ts +++ /dev/null @@ -1,16 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Generates a random value for the "origem" attribute among the default ones. - * - * The default values are 0, 1, 2, 3, 4, 5, 6, 7, and 8. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @returns {string} One of the default values. - */ -export default () => seed.pickone(['0', '1', '2', '3', '4', '5', '6', '7', '8']) diff --git a/test/generators/template/tipoFrete.ts b/test/generators/template/tipoFrete.ts deleted file mode 100644 index cef257e..0000000 --- a/test/generators/template/tipoFrete.ts +++ /dev/null @@ -1,16 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Generates a random value for the "tipoFrete" attribute among the default ones. - * - * The default values are D and R. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @returns {string} One of the default values. - */ -export default () => seed.pickone(['D', 'R']) diff --git a/test/generators/template/tipoPessoa.ts b/test/generators/template/tipoPessoa.ts deleted file mode 100644 index 640a131..0000000 --- a/test/generators/template/tipoPessoa.ts +++ /dev/null @@ -1,19 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Generates a random value for the "tipoPessoa" attribute among the default ones. - * - * The default values are - * - F: pessoa física - * - J: pessoa jurídica - * - E: estrangeiro - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @returns {string} One of the default values. - */ -export default () => seed.pickone(['F', 'J', 'E']) diff --git a/test/generators/template/uf.ts b/test/generators/template/uf.ts deleted file mode 100644 index 1714876..0000000 --- a/test/generators/template/uf.ts +++ /dev/null @@ -1,47 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Generates a random Brazilian UF value. - * - * One of the following values is generated: - * AC, AL, AP, AM, BA, CE, DF, ES, GO, MA, MT, MS, MG, PA, PB, PR, PE, PI, RJ, - * RN, RS, RO, RR, SC, SP, SE, TO. - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @returns {string} A Brazilian UF. - */ -export default () => - seed.pickone([ - 'AC', - 'AL', - 'AP', - 'AM', - 'BA', - 'CE', - 'DF', - 'ES', - 'GO', - 'MA', - 'MT', - 'MS', - 'MG', - 'PA', - 'PB', - 'PR', - 'PE', - 'PI', - 'RJ', - 'RN', - 'RS', - 'RO', - 'RR', - 'SC', - 'SP', - 'SE', - 'TO' - ]) diff --git a/test/generators/template/un.ts b/test/generators/template/un.ts deleted file mode 100644 index c76d857..0000000 --- a/test/generators/template/un.ts +++ /dev/null @@ -1,19 +0,0 @@ -import chance from 'chance' - -const seed = chance() - -/** - * Generates a random value for the "un" attribute among the default ones. - * - * One of the following values is generated: - * - pc: peça - * - un: unidade - * - cx: caixa - * - * @author Alexandre Batistella - * @version 1.0.0 - * @since 4.0.0 - * - * @returns {string} One of the default values. - */ -export default () => seed.pickone(['pc', 'un', 'cx']) diff --git a/test/package/main-module.spec.js b/test/package/main-module.spec.js deleted file mode 100644 index 29fcd8f..0000000 --- a/test/package/main-module.spec.js +++ /dev/null @@ -1,117 +0,0 @@ -import { Bling } from '../../src/bling' - -import { config } from 'dotenv' -config() - -const testApiKey = process.env.BLING_API_KEY - -jest.setTimeout(60000) - -test.concurrent( - "it shouldn't create a Bling entity when missing constructor argument", - async () => { - expect(() => { - const bling = new Bling() - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - - expect(() => { - const bling = Bling.create() - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - } -) - -test.concurrent( - "it shouldn't create a Bling entity when passing API key as number", - async () => { - expect(() => { - const bling = new Bling(1234) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - - expect(() => { - const bling = Bling.create(1234) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - } -) - -test.concurrent( - "it shouldn't create a Bling entity when passing API key as object", - async () => { - expect(() => { - const bling = new Bling({}) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - - expect(() => { - const bling = Bling.create({}) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - } -) - -test.concurrent( - "it shouldn't create a Bling entity when passing API key as array", - async () => { - expect(() => { - const bling = new Bling([]) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - - expect(() => { - const bling = Bling.create([]) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - } -) - -test.concurrent( - "it shouldn't create a Bling entity when passing API key as null", - async () => { - expect(() => { - const bling = new Bling(null) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - - expect(() => { - const bling = Bling.create(null) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - } -) - -test.concurrent( - "it shouldn't create a Bling entity when passing API key as undefined", - async () => { - expect(() => { - const bling = new Bling(undefined) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - - expect(() => { - const bling = Bling.create(undefined) - return bling - }).toThrow("The API key wasn't correctly provided for Bling connection.") - } -) - -test.concurrent( - 'it should create a Bling entity when passing a proper API key', - async () => { - expect(() => { - const bling = new Bling(testApiKey) - return bling - }).not.toThrow( - "The API key wasn't correctly provided for Bling connection." - ) - - expect(() => { - const bling = Bling.create(testApiKey) - return bling - }).not.toThrow( - "The API key wasn't correctly provided for Bling connection." - ) - } -) diff --git a/test/package/main-module.test.ts b/test/package/main-module.test.ts deleted file mode 100644 index 7dc84a3..0000000 --- a/test/package/main-module.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Bling, IBlingError } from '../config/bling' - -jest.setTimeout(50000) - -const testError = (err: IBlingError) => { - expect(err.message).toEqual( - 'Error on all method during request call: Request failed with status code 401' - ) - expect(err.code).toEqual('ERR_GET_REQUEST_FAILURE') - expect(err.status).toEqual('401') -} - -test.concurrent( - 'should fail when an ordinary request is made with a bad API key with the instantiated module', - async () => { - const bling = new Bling('1234') - - try { - await bling.products().all() - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } - } -) - -test.concurrent( - 'should fail when an ordinary request is made with a bad API key with the functional module', - async () => { - const bling = Bling.create('1234') - - try { - await bling.products().all() - expect(false).toBe(true) - } catch (err) { - testError(err as IBlingError) - } - } -) diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..a31e0bd --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["src/**/*.spec.ts", "src/entities/**/__tests__"] +} diff --git a/tsconfig.json b/tsconfig.json index 1ee9d6c..7a93ec7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,16 +3,14 @@ "target": "es6", "module": "commonjs", "declaration": true, + "removeComments": true, "outDir": "./lib", "strict": true, - "esModuleInterop": true, - "allowJs": true + "sourceMap": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./" }, - "include": [ - "src" - ], - "exclude": [ - "node_modules", - "test/*" - ] + "include": ["src/**/*"], + "exclude": ["node_modules"] }