├── .env ├── .gitignore ├── src ├── routes │ ├── routes.md │ ├── rotinaVerificacaoEstoque.js │ ├── fluxoRoutes.js │ └── produtosRoutes.js ├── models │ ├── models.md │ ├── produtosModel.js │ └── controleFluxoModel.js ├── controllers │ ├── controllers.md │ ├── fluxoController.js │ └── produtosController.js ├── config │ ├── config.md │ ├── config.js │ └── createDB.js ├── services │ └── validate.js ├── app.js └── index │ └── index.html ├── .eslintrc.json ├── server.js ├── criarBaseDadosTeste.js ├── package.json ├── test └── valitade.test.js ├── public └── metodosHTTP.js ├── README.md ├── produtos.js └── Postman Collection └── API-Estoque.postman_collection.json /.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/routes/routes.md: -------------------------------------------------------------------------------- 1 | Arquivo para configurar as rotas. -------------------------------------------------------------------------------- /src/models/models.md: -------------------------------------------------------------------------------- 1 | Cada modelo de dados fica guardado aqui. -------------------------------------------------------------------------------- /src/controllers/controllers.md: -------------------------------------------------------------------------------- 1 | Nesta pasta ficam todos os arquivos que funcionam como controladores de dados. -------------------------------------------------------------------------------- /src/config/config.md: -------------------------------------------------------------------------------- 1 | Arquivo para configuraçoes iniciais da aplicação 2 | 3 | Arquivo de configuração das blibliotecas 4 | Criar um para cada bliblioteca 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": "airbnb-base", 7 | "overrides": [], 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "linebreak-style": ["error", "windows"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import app from './src/app.js' 2 | 3 | 4 | const porta = process.env.PORT || 3000; 5 | 6 | 7 | 8 | app.listen(porta,() => { 9 | // http://localhost:3000/ 10 | console.log(`Servidor escutando em http://localhost:${porta}/`); 11 | }) 12 | // app.listen(porta, '0.0.0.0', () => { 13 | // console.log(`Servidor escutando em http://0.0.0.0:${porta}/`); 14 | // }); 15 | -------------------------------------------------------------------------------- /src/routes/rotinaVerificacaoEstoque.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import produtosController from "../controllers/produtosController.js"; 3 | 4 | const rotinaEstoque = express.Router(); 5 | 6 | 7 | const nomeRota = "produtosQuantidadeMinima"; 8 | 9 | rotinaEstoque.route(`/${nomeRota}/`) 10 | .get(produtosController.listarProdutosComQuantidadeMinima); 11 | 12 | 13 | export default rotinaEstoque; -------------------------------------------------------------------------------- /src/services/validate.js: -------------------------------------------------------------------------------- 1 | function validate(obj, qtd) { 2 | if (qtd < obj.min_qtd) { 3 | return { 4 | bool: true, 5 | message: 'A quantidade informada está abaixo do limite mínimo permitido.', 6 | min_qtd: obj.min_qtd, 7 | }; 8 | } if (qtd > obj.max_qtd) { 9 | return { 10 | bool: true, 11 | message: 'A quantidade informada está acima do limite máximo permitido.', 12 | max_qtd: obj.max_qtd, 13 | }; 14 | } 15 | return { bool: false }; 16 | } 17 | 18 | export default validate; 19 | -------------------------------------------------------------------------------- /src/routes/fluxoRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import controleFluxo from "../controllers/fluxoController.js"; 3 | const fluxoRoutes = express.Router(); 4 | 5 | 6 | const nomeRota = "fluxo"; 7 | 8 | fluxoRoutes.route(`/${nomeRota}/entrada/:id`) 9 | .post(controleFluxo.registrarEntrada); 10 | 11 | fluxoRoutes.route(`/${nomeRota}/saida/:id`) 12 | .post(controleFluxo.registrarSaida); 13 | 14 | fluxoRoutes.route(`/${nomeRota}/`).get(controleFluxo.read); 15 | 16 | fluxoRoutes.route(`/${nomeRota}/:produto_id`).get(controleFluxo.read_produto_id); 17 | 18 | export default fluxoRoutes; -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | import Sequelize from "sequelize"; 2 | import createDataBase from "./createDB.js"; 3 | 4 | const DB = "estoquedatabase";// Cuidado ao escolher o nome do banco ! 5 | const usuario = "root"; 6 | const senha = "meusql"; 7 | 8 | const sequelize = new Sequelize(DB, usuario, senha, { 9 | host: "localhost", 10 | dialect: "mysql", 11 | }); 12 | 13 | 14 | let dbStatusCreate = false; 15 | console.log(dbStatusCreate); 16 | 17 | if (dbStatusCreate) { 18 | await createDataBase(DB,usuario,senha); 19 | dbStatusCreate = false; 20 | console.log(dbStatusCreate); 21 | 22 | } 23 | 24 | 25 | export default sequelize; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/config/createDB.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // Cria uma bando de dados se o mesmo nao existir 5 | import mysql from "mysql2"; 6 | 7 | 8 | async function createDataBase(dbName,dbUser,dbPassword) { 9 | 10 | 11 | const connection = mysql.createConnection({ 12 | host: "localhost", 13 | user: dbUser, 14 | password: dbPassword, 15 | }); 16 | 17 | 18 | connection.query( 19 | `CREATE DATABASE IF NOT EXISTS ${dbName}`, 20 | function (err, results) { 21 | console.log(results); 22 | console.log(err||"None"); 23 | } 24 | ); 25 | 26 | 27 | connection.end(); 28 | } 29 | 30 | export default createDataBase; -------------------------------------------------------------------------------- /criarBaseDadosTeste.js: -------------------------------------------------------------------------------- 1 | import sequelize from "./src/config/config.js"; 2 | import Produto from "./src/models/produtosModel.js"; 3 | import Movimentacao from "./src/models/controleFluxoModel.js"; 4 | import { listaJSONplus } from "./produtos.js"; 5 | 6 | 7 | sequelize.authenticate().then(() => { 8 | console.log("Conectado com sucesso!"); 9 | }).catch((erro) => { 10 | console.log("Falha ao se conectar:", erro); 11 | }); 12 | 13 | 14 | 15 | await sequelize.sync({ force: true }); 16 | 17 | listaJSONplus.forEach(async input => { 18 | 19 | const produto = await Produto.create(input); 20 | await Movimentacao.create({ 21 | nome:produto.nome, 22 | tipo:"entrada", 23 | produto_id:produto.id, 24 | qtd: produto.qtd, 25 | data: new Date() 26 | }) 27 | 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /src/models/produtosModel.js: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize'; 2 | import sequelize from '../config/config.js'; 3 | 4 | // Esquema Produtos 5 | const Produto = sequelize.define('produtos', { 6 | id: { 7 | type: Sequelize.INTEGER, 8 | autoIncrement: true, 9 | primaryKey: true, 10 | }, 11 | nome: { 12 | type: Sequelize.STRING, 13 | allowNull: false, 14 | }, 15 | marca: { 16 | type: Sequelize.STRING, 17 | allowNull: false, 18 | }, 19 | qtd: { 20 | type: Sequelize.INTEGER, 21 | allowNull: false, 22 | defaultValue: 0, 23 | }, 24 | min_qtd: { 25 | type: Sequelize.INTEGER, 26 | allowNull: false, 27 | defaultValue: 30, 28 | }, 29 | max_qtd: { 30 | type: Sequelize.INTEGER, 31 | allowNull: false, 32 | defaultValue: 999, 33 | }, 34 | }); 35 | 36 | export default Produto; 37 | -------------------------------------------------------------------------------- /src/routes/produtosRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import produtosController from "../controllers/produtosController.js"; 3 | 4 | const produtosRouter = express.Router(); 5 | 6 | const nomeRota = "cadastroProdutos"; 7 | 8 | produtosRouter.route(`/${nomeRota}`) 9 | .post(produtosController.create) 10 | .get(produtosController.read); 11 | 12 | //http://localhost:3000/cadastroProdutos/pagina?itensPorPagina=10&pagina=1 13 | produtosRouter.route(`/${nomeRota}/pagina`) 14 | .get(produtosController.readQuery); 15 | 16 | // http://localhost:3000/cadastroProdutos/busca?nome=&marca=&qtd= 17 | 18 | produtosRouter.route(`/${nomeRota}/busca`) 19 | .get(produtosController.find); 20 | 21 | produtosRouter.route(`/${nomeRota}/:id`) 22 | .put(produtosController.update) 23 | .delete(produtosController.delete) 24 | .get(produtosController.readById); 25 | 26 | export default produtosRouter; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "controle-estoque-api", 3 | "version": "1.0.0", 4 | "description": "API para controle de estoque de produtos relacionados a informática", 5 | "main": "server.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node server.js", 9 | "dev": "nodemon server.js", 10 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" 11 | }, 12 | "keywords": [ 13 | "API", 14 | "NodeJS", 15 | "express", 16 | "sequelize", 17 | "mysql" 18 | ], 19 | "author": "Fabrício Neves", 20 | "license": "MIT", 21 | "dependencies": { 22 | "cors": "^2.8.5", 23 | "express": "^4.18.2", 24 | "mysql2": "^3.2.0", 25 | "sequelize": "^6.29.3" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^8.40.0", 29 | "eslint-config-airbnb-base": "^15.0.0", 30 | "eslint-plugin-import": "^2.27.5", 31 | "jest": "28.1.0", 32 | "nodemon": "^2.0.22" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import fs from 'fs'; 3 | import cors from 'cors'; 4 | // eslint-disable-next-line import/extensions 5 | import produtosRouter from './routes/produtosRoutes.js'; 6 | // eslint-disable-next-line import/extensions 7 | import rotinaEstoque from './routes/rotinaVerificacaoEstoque.js'; 8 | // eslint-disable-next-line import/extensions 9 | import fluxoRoutes from './routes/fluxoRoutes.js'; 10 | 11 | /** Add arquivo de index.js */ 12 | const html = fs.readFileSync('./src/index/index.html', 'utf8'); 13 | 14 | const app = express(); 15 | 16 | /** add o middleware Json */ 17 | app.use(express.json()); 18 | 19 | /** habilita CORS */ 20 | app.use(cors({ 21 | origin: '*', 22 | })); 23 | 24 | app.get('/', (req, res) => { 25 | res.status(200).send(html); 26 | }); 27 | 28 | /** Add Rotas */ 29 | app.use(produtosRouter); 30 | app.use(rotinaEstoque); 31 | app.use(fluxoRoutes); 32 | 33 | export default app; 34 | -------------------------------------------------------------------------------- /src/models/controleFluxoModel.js: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize'; 2 | import sequelize from '../config/config.js'; 3 | // import Produto from './produtosModel.js'; 4 | 5 | const Movimentacao = sequelize.define('Movimentacao', { 6 | nome: { 7 | type: Sequelize.STRING, 8 | allowNull: true, 9 | }, 10 | produto_id: { 11 | type: Sequelize.INTEGER, 12 | allowNull: true, 13 | }, 14 | tipo: { 15 | type: Sequelize.ENUM('entrada', 'saida'), 16 | allowNull: false, 17 | }, 18 | qtd: { 19 | type: Sequelize.INTEGER, 20 | allowNull: false, 21 | }, 22 | data: { 23 | type: Sequelize.DATE, 24 | allowNull: false, 25 | }, 26 | }); 27 | 28 | // Produto.hasMany(Movimentacao, { 29 | // onDelete: 'cascade', 30 | // hooks: true, 31 | // foreignKey: { 32 | // name: 'produto_id', 33 | // allowNull: false 34 | // } 35 | // }); 36 | 37 | // Movimentacao.belongsTo(Produto, { 38 | // foreignKey: { 39 | // name: 'produto_id', 40 | // allowNull: false 41 | // } 42 | // }); 43 | 44 | export default Movimentacao; 45 | -------------------------------------------------------------------------------- /test/valitade.test.js: -------------------------------------------------------------------------------- 1 | import validate from "../src/services/validate"; 2 | 3 | describe('validate', () => { 4 | it('deve retornar false se a quantidade estiver dentro dos limites', () => { 5 | const obj = { min_qtd: 5, max_qtd: 10 }; 6 | const qtd = 8; 7 | 8 | const result = validate(obj, qtd); 9 | 10 | expect(result.bool).toBe(false); 11 | }); 12 | 13 | it('deve retornar true e a mensagem correta se a quantidade for menor que o limite mínimo', () => { 14 | const obj = { min_qtd: 5, max_qtd: 10 }; 15 | const qtd = 3; 16 | 17 | const result = validate(obj, qtd); 18 | 19 | expect(result.bool).toBe(true); 20 | expect(result.message).toBe('A quantidade informada está abaixo do limite mínimo permitido.'); 21 | expect(result.min_qtd).toBe(5); 22 | }); 23 | 24 | it('deve retornar true e a mensagem correta se a quantidade for maior que o limite máximo', () => { 25 | const obj = { min_qtd: 5, max_qtd: 10 }; 26 | const qtd = 12; 27 | 28 | const result = validate(obj, qtd); 29 | 30 | expect(result.bool).toBe(true); 31 | expect(result.message).toBe('A quantidade informada está acima do limite máximo permitido.'); 32 | expect(result.max_qtd).toBe(10); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /public/metodosHTTP.js: -------------------------------------------------------------------------------- 1 | // Revisar todos os itens !!! 2 | 3 | class ProdutosAPI { 4 | baseURL = "http://localhost:3000/cadastroProdutos"; 5 | 6 | async getItem() { 7 | try { 8 | const response = await fetch(this.baseURL); 9 | return response.json(); 10 | } catch (error) { 11 | console.error(error); 12 | throw error; 13 | } 14 | } 15 | 16 | async createItem(item) { 17 | try { 18 | const response = await fetch(this.baseURL, { 19 | method: "POST", 20 | headers: { 21 | "Content-Type": "application/json", 22 | }, 23 | body: JSON.stringify(item), 24 | }); 25 | return response.json(); 26 | } catch (error) { 27 | console.error(error); 28 | throw error; 29 | } 30 | } 31 | 32 | async updateItem(id, item) { 33 | try { 34 | const response = await fetch(`${this.baseURL}/${id}`, { 35 | method: "PUT", 36 | headers: { 37 | "Content-Type": "application/json", 38 | }, 39 | body: JSON.stringify(item), 40 | }); 41 | return response.json(); 42 | } catch (error) { 43 | console.error(error); 44 | throw error; 45 | } 46 | } 47 | 48 | async deleteItem(id) { 49 | try { 50 | const response = await fetch(`${this.baseURL}/${id}`, { 51 | method: "DELETE", 52 | }); 53 | return response.json(); 54 | } catch (error) { 55 | console.error(error); 56 | throw error; 57 | } 58 | } 59 | 60 | async searchItems({ nome, marca, qtd }) { 61 | const params = new URLSearchParams({ 62 | nome: nome || "", 63 | marca: marca || "", 64 | qtd: qtd || "", 65 | }); 66 | 67 | const url = `${this.baseURL}/busca?${params.toString()}`; 68 | 69 | try { 70 | const response = await fetch(url); 71 | return response.json(); 72 | } catch (error) { 73 | console.error(error); 74 | throw error; 75 | } 76 | } 77 | 78 | async getItensPagination({ itensPorPagina = 10, pagina = 1 }) { 79 | const params = new URLSearchParams({ 80 | itensPorPagina: itensPorPagina, 81 | pagina: pagina, 82 | }); 83 | 84 | const url = `${this.baseURL}/pagina?${params.toString()}`; 85 | 86 | try { 87 | const response = await fetch(url); 88 | return response.json(); 89 | } catch (error) { 90 | console.error(error); 91 | throw error; 92 | } 93 | } 94 | 95 | async getItensById(id) { 96 | try { 97 | const response = await fetch(`${this.baseURL}/${id}`); 98 | return response.json(); 99 | } catch (error) { 100 | console.error(error); 101 | throw error; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/controllers/fluxoController.js: -------------------------------------------------------------------------------- 1 | import Movimentacao from '../models/controleFluxoModel.js'; 2 | import Produto from '../models/produtosModel.js'; 3 | class controleFluxo { 4 | 5 | //**Create Entrada */ 6 | static async entrada(produto, qtd, data_in) { 7 | 8 | data_in = data_in ? data_in : new Date(); 9 | 10 | try { 11 | await Movimentacao.create({ 12 | nome: produto.nome, 13 | tipo: "entrada", 14 | produto_id: produto.id, 15 | qtd: qtd, 16 | data: data_in 17 | }) 18 | return true; 19 | } catch (err) { 20 | console.log(err); 21 | return false; 22 | } 23 | 24 | } 25 | //**Create Saída */ 26 | static async saida(produto, qtd, data_out) { 27 | 28 | data_out = data_out ? data_out : new Date(); 29 | 30 | try { 31 | await Movimentacao.create({ 32 | nome: produto.nome, 33 | tipo: "saida", 34 | produto_id: produto.id, 35 | qtd: -(qtd), 36 | data: data_out 37 | }) 38 | return true; 39 | } catch (err) { 40 | console.log(err); 41 | return false; 42 | } 43 | 44 | } 45 | 46 | //** Registro Nova Entrada */ 47 | static async registrarEntrada(req, res) { 48 | const { id } = req.params; 49 | const { qtd_in, data } = req.body; 50 | 51 | try { 52 | const produto = await Produto.findOne({ where: { id } }); 53 | if (!produto) { 54 | return res.status(404).json({ isSuccess: false, message: 'Produto não encontrado.' }); 55 | } 56 | 57 | if (!qtd_in || qtd_in <= 0) { 58 | return res.status(409).json({ isSuccess: false, message: 'Quantidade de saída inválida.' }); 59 | } 60 | const qtd_total = produto.qtd + qtd_in; 61 | 62 | await produto.update({ qtd: qtd_total }); 63 | await controleFluxo.entrada(produto, qtd_in, data); 64 | res.status(200).json({ isSuccess: true, message: 'Entrada registrada com sucesso.', qtd_total: qtd_total }); 65 | } catch (error) { 66 | console.error(error); 67 | res.status(500).json({ isSuccess: false, message: 'Ocorreu um erro ao registrar a entrada.' }); 68 | } 69 | } 70 | //** Registro Nova Saída */ 71 | static async registrarSaida(req, res) { 72 | const { id } = req.params; 73 | const { qtd_out, data } = req.body; 74 | 75 | try { 76 | const produto = await Produto.findOne({ where: { id } }); 77 | if (!produto) { 78 | return res.status(404).json({ isSuccess: false, message: 'Produto não encontrado.' }); 79 | } 80 | const { qtd } = produto; 81 | if (!qtd_out || qtd_out <= 0) { 82 | return res.status(409).json({ isSuccess: false, message: 'Quantidade de saída inválida.' }); 83 | } 84 | if (qtd < qtd_out) { 85 | return res.status(409).json({ isSuccess: false, message: 'Quantidade indisponível para saída.' }); 86 | } 87 | const qtd_total = qtd - qtd_out; 88 | console.log(qtd_total); 89 | const result = await produto.update({ qtd: qtd_total }); 90 | if (result) { 91 | const movimentacao = await controleFluxo.saida(produto, qtd_out, data); 92 | if (!movimentacao) { 93 | return res.status(500).json({ isSuccess: false, message: 'Erro ao registrar saída na movimentação.' }); 94 | } 95 | return res.status(200).json({ isSuccess: true, message: 'Saída registrada com sucesso.', qtd_total: qtd_total }); 96 | } 97 | } catch (error) { 98 | res.status(500).json({ isSuccess: false, message: error.message }); 99 | } 100 | } 101 | 102 | //**READ-ALL-ITEMS */ 103 | static async read(req, res) { 104 | try { 105 | const Mov = await Movimentacao.findAll(); 106 | res.status(200).json({ isSuccess: true, message: Mov }); 107 | } catch (error) { 108 | res.status(500).json({ isSuccess: false, message: error }); 109 | } 110 | } 111 | //**READ produto_id */ 112 | static async read_produto_id(req, res) { 113 | const { produto_id } = req.params; 114 | console.log(produto_id); 115 | try { 116 | const registro = await Movimentacao.findAll({ where: { produto_id } }); 117 | if (registro) { 118 | res.status(200).json({ isSuccess: true, message: registro }); 119 | } else { 120 | res.status(404).send({ isSuccess: false, message: 'Produtos não encontrados.' }); 121 | } 122 | } catch (error) { 123 | res.status(500).send({ isSuccess: false, message: error }); 124 | } 125 | } 126 | 127 | } 128 | 129 | export default controleFluxo; -------------------------------------------------------------------------------- /src/controllers/produtosController.js: -------------------------------------------------------------------------------- 1 | import Sequelize, { Op } from 'sequelize'; 2 | 3 | import Produto from '../models/produtosModel.js'; 4 | import controleFluxo from './fluxoController.js'; 5 | 6 | class produtosController { 7 | // READ 8 | static async read(req, res) { 9 | try { 10 | const produtos = await Produto.findAll(); 11 | res.status(200).json(produtos); 12 | } catch (error) { 13 | res.status(500).send(error); 14 | } 15 | } 16 | 17 | // READ-QUERY 18 | static async readQuery(req, res) { 19 | try { 20 | const itensPorPagina = Number(req.query.itensPorPagina) || 10; // Valor padrão é 10 21 | const pagina = Number(req.query.pagina) || 1; // Valor padrão é 1 22 | const offset = (pagina - 1) * itensPorPagina; 23 | console.log(itensPorPagina, pagina, offset); 24 | const produtos = await Produto.findAndCountAll({ 25 | where: {}, 26 | limit: itensPorPagina, 27 | offset, 28 | }); 29 | const totalItens = produtos.count; 30 | const totalPaginas = Math.ceil(totalItens / itensPorPagina); 31 | res.status(200).json({ 32 | produtos: produtos.rows, 33 | paginaAtual: pagina, 34 | totalPaginas, 35 | }); 36 | } catch (error) { 37 | console.log(error); 38 | res.status(500).send(error); 39 | } 40 | } 41 | 42 | // READ-BY-ID 43 | static async readById(req, res) { 44 | const { id } = req.params; 45 | try { 46 | const produto = await Produto.findOne({ where: { id } }); 47 | if (produto) { 48 | res.status(200).json(produto); 49 | } else { 50 | res.status(404).send('Produto não encontrado.'); 51 | } 52 | } catch (error) { 53 | res.status(500).send(error); 54 | } 55 | } 56 | 57 | // CREATE 58 | static async create(req, res) { 59 | const novoProduto = req.body; 60 | const { qtd, nome } = novoProduto; 61 | 62 | const produto = await Produto.findOne({ where: { nome } }); 63 | 64 | if (produto) { 65 | return res.status(409).send('Produto já existe.'); 66 | } if (Number(qtd) < 0) { 67 | return res.status(409).send('Quantidada precisa ser maior que 0'); 68 | } 69 | 70 | try { 71 | const produtoCriado = await Produto.create(novoProduto); 72 | if (qtd) { 73 | await controleFluxo.entrada(produtoCriado, qtd); 74 | } 75 | res.status(200).json(produtoCriado); 76 | } catch (error) { 77 | res.status(500).send(error); 78 | } 79 | } 80 | 81 | // UPDATE 82 | static async update(req, res) { 83 | const { id } = req.params; 84 | const { 85 | nome, marca, min_qtd, max_qtd, 86 | } = req.body; 87 | 88 | if (!nome && !marca && !min_qtd && !max_qtd) { 89 | return res.status(400).send('Pelo menos um campo de nome, marca, min_qtd ou max_qtd é obrigatório para atualização.'); 90 | } 91 | 92 | try { 93 | const produto = await Produto.findOne({ where: { id } }); 94 | 95 | if (produto) { 96 | await produto.update({ 97 | nome, marca, min_qtd, max_qtd, 98 | }); 99 | res.status(200).json(produto); 100 | } else { 101 | res.status(404).send('Produto não encontrado.'); 102 | } 103 | } catch (error) { 104 | res.status(500).send(error); 105 | } 106 | } 107 | 108 | // DELETE 109 | static async delete(req, res) { 110 | const { id } = req.params; 111 | try { 112 | const produto = await Produto.findOne({ where: { id } }); 113 | const { qtd } = produto; 114 | if (produto) { 115 | if (qtd) { 116 | await controleFluxo.saida(produto, qtd); 117 | } 118 | await produto.destroy(); 119 | res.json({ message: 'Produto_deletado', produto }); 120 | } else { 121 | res.status(404).send('Produto não encontrado.'); 122 | } 123 | } catch (error) { 124 | console.log(error); 125 | res.status(500).send(error); 126 | } 127 | } 128 | 129 | // FIND 130 | static async find(req, res) { 131 | const { query } = req; 132 | const objSQLQuery = {}; 133 | 134 | // Construção do Objeto de Procura. 135 | for (const key in query) { 136 | objSQLQuery[key] = { 137 | 138 | [Op.like]: `${query[key]}%`, 139 | }; 140 | } 141 | try { 142 | const produtos = await Produto.findAll({ 143 | where: objSQLQuery, 144 | 145 | }); 146 | res.status(200).json(produtos); 147 | } catch (error) { 148 | res.status(500).json({ error: error.message }); 149 | } 150 | } 151 | 152 | // Listar Protudos Abaixo da quantidade Mínima 153 | static async listarProdutosComQuantidadeMinima(req, res) { 154 | try { 155 | const produtos = await Produto.findAll({ 156 | where: Sequelize.literal('qtd < min_qtd'), 157 | }); 158 | 159 | if (produtos.length > 0) { 160 | return res.status(200).json(produtos); 161 | } 162 | return res.status(404).json({ message: 'Nenhum produto encontrado com quantidade mínima abaixo do limite.' }); 163 | } catch (error) { 164 | return res.status(500).json({ message: error.message }); 165 | } 166 | } 167 | } 168 | 169 | export default produtosController; 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API de cadastro de produtos 2 | API desenvolvida em Node.js com Express e banco de dados MySQL, utilizando a biblioteca Sequelize para ORM. Permite o registro de entrada e saída de produtos, atualizando a quantidade total em estoque e registrando tambem as movimentações de produtos. 3 | 4 | ## ♻ Atualização 5 | 6 | Foram adicionadas novas funcionalidades à API. Para ter acesso às novidades, utilize o comando "git pull" em seu repositório. Entre as atualizações, destacam-se: 7 | - Registro de entrada e saída de mercadorias. 8 | - Inclusão de método e modelo no banco para definir valores máximos e mínimos de produtos no estoque. 9 | 10 | OBS: Como não foi criado um arquivo de migração, e este projeto esta em constante aprimoramento. Considere recriar as tabelas usando o comando **"node criarBaseDadosTeste.js"** , agora com 46 itens. 11 | 12 | ## 🔝 Metas 13 | - [ ] Forçar um estilo de programação (Airbnb). 14 | 15 | - [ ] Criar testes unitários. 16 | 17 | - [ ] Add ao projeto o Sequelize CLI e adcionar um arquivo de migração. 18 | ## 🏃♂️ Motivações e Objetivos 19 | Esta API foi desenvolvida para atender as especificações do cliente [Pedro Marins](https://github.com/pedromarins). Que tem como a demanda de funcionalidades : 20 | 21 | ### Lista de funcionalidades 22 | 23 | #### Básico 24 | - Ter uma lista de produtos com um identificador para cada item. 25 | - Ler a lista de produtos. 26 | - Poder adicionar um item na lista de produtos. 27 | - Poder remover um item da lista de produtos. 28 | - Adicionar quantidade de itens na lista de produtos. 29 | - Poder alterar a quantidade de itens de um produto específico. 30 | - Consultar um item na lista de produtos para saber a quantidade disponível. 31 | 32 | #### Avançado 33 | - Poder adicionar um limite mínimo e máximo para cada item da lista de produtos. 34 | - Rotina para verificar se alguma quantidade de itens está abaixo do limite mínimo. 35 | 36 | #### Extra 37 | - Armazenar cada transação na lista de produtos. 38 | ## 📚 Dependências 39 | ### Blibliotecas & FrameWorks 40 | - express 41 | - mysql2 42 | - sequelize 43 | - nodemon (somente para desenvolvimento) 44 | 45 | ### Banco de dados 46 | - MySQL 47 | 48 | ## 🏗 Instalação 49 | Clone este repositório. 50 | Na pasta raiz do projeto, execute o comando **npm install** para instalar as dependências. 51 | Configure as variáveis de ambiente no arquivo **config.js** com as informações do banco de dados. 52 | Execute o comando **npm start** para rodar a aplicação. 53 | 54 | ## 🎲 Banco de Dados 55 | Para configurar corretamente o banco de dados é necessario alterar as variáveis no arquivo **src/config/config.js**. 56 | ```javascript 57 | const DB = "NomeDoBanco"; // somente letras e numeros 58 | const usuario = "root"; 59 | const senha = "123"; 60 | ``` 61 | Para criar um banco de dados para teste execute o comando **"node criarBaseDadosTeste.js"** , para criar 46 itens. 62 | 63 | ## 🔀 Rotas de produtos 64 | A API possui as seguintes rotas: 65 | 66 | ### POST /cadastroProdutos 67 | Cria um novo produto. 68 | 69 | Exemplo de requisição: 70 | 71 | ```json 72 | { 73 | "nome": "Produto A", 74 | "marca": "Marca A", 75 | "qtd": 10, 76 | "min_qtd" : 30, 77 | "max_qtd" : 999 78 | } 79 | ``` 80 | * Usando esse método automaticamente registra a quantidade como entrada. 81 | 82 | 83 | ### GET /cadastroProdutos 84 | Retorna todos os produtos. 85 | 86 | ### GET /cadastroProdutos/pagina?itensPorPagina=10&pagina=1 87 | Retorna uma lista de produtos paginados. É possível passar os parâmetros **itensPorPagina** e **pagina** para definir a quantidade de itens por página e a página desejada. 88 | 89 | ### GET /cadastroProdutos/busca?nome=&marca= 90 | Retorna uma lista de produtos filtrados por **nome**, **marca**, Passando os parâmetros **nome**, **marca** para realizar a busca. 91 | 92 | ### GET /cadastroProdutos/:id 93 | Retorna um produto específico pelo seu **ID**. 94 | 95 | ### PUT /cadastroProdutos/:id 96 | Atualiza um produto existente. 97 | 98 | Exemplo de requisição: 99 | 100 | ```json 101 | { 102 | "nome": "Produto B", 103 | "marca": "Marca B", 104 | "min_qtd" : 30, 105 | "max_qtd" : 999 106 | } 107 | ``` 108 | * Quantidade não pode ser atualizada por esta rota. 109 | 110 | ### DELETE /cadastroProdutos/:id 111 | Deleta um produto existente. 112 | 113 | * Usando esse método automaticamente registra toda a quantidade desse item como saída. 114 | 115 | ### GET /produtosQuantidadeMinima 116 | Retorna uma lista de produtos com quantidade abaixo do limite mínimo definido no cadastro do produto. 117 | 118 | ## 🛒 Rotas de entrada e saída de mercadorias. 119 | 120 | ### POST /fluxo/entrada/:id 121 | 122 | Registra a entrada de um determinado produto no estoque. O parâmetro **:id** corresponde ao ID do produto que será atualizado. O corpo da requisição deve conter um objeto JSON com as chaves **qtd_in** (quantidade que será adicionada ao estoque) e **data** (data e hora da entrada no formato "YYYY-MM-DD HH:mm:ss"). 123 | 124 | ```json 125 | { 126 | "qtd_in" : 30, 127 | "data" : "YYYY-MM-DD HH:mm:ss" 128 | } 129 | ``` 130 | 131 | ### POST /fluxo/saida/:id 132 | 133 | Registra a saída de um determinado produto no estoque. O parâmetro **:id** corresponde ao ID do produto que será atualizado. O corpo da requisição deve conter um objeto JSON com as chaves **qtd_out** (quantidade que será retirada do estoque) e **data** (data e hora da saída no formato "YYYY-MM-DD HH:mm:ss"). 134 | 135 | ```json 136 | { 137 | "qtd_in" : 30, 138 | "data" : "YYYY-MM-DD HH:mm:ss" 139 | } 140 | ``` 141 | 142 | ### GET /fluxo 143 | 144 | Retorna uma lista de todas as movimentações registradas. 145 | ### GET /fluxo/:produto_id 146 | 147 | Retorna uma lista de movimentações de um produto específico com base no seu id. 148 | 149 | ## 💻 Postman 150 | 151 | Dentro da pasta **Postman Collection** está salvo o arquivo JSON usado para testar essa API usando o programa [Postman](https://www.postman.com/downloads/) -------------------------------------------------------------------------------- /src/index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |Esta é uma API desenvolvida em Node.js com Express e banco de dados MySQL, utilizando a biblioteca Sequelize 102 | para ORM. Ela permite o registro de entrada e saída de produtos, atualizando a quantidade total em estoque e 103 | registrando também as movimentações de produtos.
104 |Se você está interessado em dar uma olhada no código, pode encontrar o repositório no GitHub.
106 |Sinta-se livre para explorar e contribuir para o desenvolvimento desta API!
107 || Método HTTP | 111 |Rota da API | 112 |Descrição | 113 |
|---|---|---|
| POST | 116 |/cadastroProdutos | 117 |Cria um novo produto. | 118 |
| GET | 121 |/cadastroProdutos | 122 |Retorna todos os produtos. | 123 |
| GET | 126 |/cadastroProdutos/pagina?itensPorPagina=10&pagina=1 | 127 |Retorna uma lista de produtos paginados. É possível passar os parâmetros itensPorPagina e 128 | pagina 129 | para definir a quantidade de itens por página e a página desejada. 130 | | 131 |
| GET | 134 |/cadastroProdutos/busca?nome=&marca= | 135 |Retorna uma lista de produtos filtrados por nome , marca . Passando os 136 | parâmetros nome , 137 | marca para realizar a busca. 138 | | 139 |
| GET | 142 |/cadastroProdutos/:id | 143 |Retorna um produto específico pelo seu ID . | 144 |
| PUT | 147 |/cadastroProdutos/:id | 148 |Atualiza um produto existente. Quantidade não pode ser atualizada por esta rota. | 149 |
| DELETE | 152 |/cadastroProdutos/:id | 153 |Deleta um produto existente. Usando esse método automaticamente registra toda a quantidade desse item 154 | como saída. | 155 |
| GET | 158 |/produtosQuantidadeMinima | 159 |Retorna uma lista de produtos com quantidade abaixo do limite mínimo definido no cadastro do produto. 160 | | 161 |
| POST | 164 |/fluxo/entrada/:id | 165 |Registra a entrada de um determinado produto no estoque. O parâmetro :id corresponde ao ID 166 | do produto que será atualizado. O corpo da requisição deve conter um objeto JSON com as chaves 167 | qtd_in (quantidade que será adicionada ao estoque) e data (data e hora da 168 | entrada no formato "YYYY-MM-DD HH:mm:ss"). 169 | | 170 |
| POST | 173 |/fluxo/saida/:id | 174 |Registra a saída de um determinado produto no estoque. O parâmetro :id corresponde ao ID 175 | do produto 176 | que será atualizado. O corpo da requisição deve conter um objeto JSON com as chaves qtd_out 177 | 178 | (quantidade que será retirada do estoque) e data (data e hora da saída no formato 179 | "YYYY-MM-DD 180 | HH:mm:ss"). | 181 |
| GET | 184 |/fluxo | 185 |Retorna uma lista de todas as movimentações registradas. | 186 |
| GET | 189 |/fluxo/:id | 190 |Retorna uma lista de movimentações de um produto específico com base no seu id. | 191 |