From 4d554fd40bb032fb32ca3c2280f1372223c448a9 Mon Sep 17 00:00:00 2001 From: Matheus Coitinho Date: Thu, 14 Aug 2025 20:19:25 -0300 Subject: [PATCH 1/6] adiciona funcionalidades para cadastro e listagem de locais de coleta --- src/controllers/locais-coleta-controller.js | 48 ++++++++- src/routes/locais.js | 103 ++++++++++++++++++++ src/validators/localColeta-cadastro.js | 25 +++++ src/validators/localColeta-listagem.js | 8 ++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/validators/localColeta-cadastro.js create mode 100644 src/validators/localColeta-listagem.js diff --git a/src/controllers/locais-coleta-controller.js b/src/controllers/locais-coleta-controller.js index 771a7ef3..c65fe274 100644 --- a/src/controllers/locais-coleta-controller.js +++ b/src/controllers/locais-coleta-controller.js @@ -1,9 +1,11 @@ +import { pick } from 'lodash'; + import BadRequestExeption from '../errors/bad-request-exception'; import models from '../models'; import codigos from '../resources/codigos-http'; const { - Relevo, Solo, Vegetacao, sequelize, + Relevo, Solo, Vegetacao, LocalColeta, Cidade, FaseSucessional, sequelize, } = models; export const cadastrarSolo = (request, response, next) => { @@ -120,4 +122,48 @@ export const buscarVegetacoes = (request, response, next) => { .catch(next); }; +export const cadastrarLocalColeta = async (request, response, next) => { + try { + const dados = pick(request.body, ['descricao', 'complemento', 'cidade_id', 'fase_sucessional_id']); + const localColeta = await LocalColeta.create(dados); + response.status(201).json(localColeta); + } catch (error) { + next(error); + } +}; + +export const buscarLocaisColeta = async (request, response, next) => { + try { + const { cidadeId } = request.query; + const { limite, pagina } = request.paginacao; + const offset = (pagina - 1) * limite; + + const where = {}; + if (cidadeId) { + where.cidade_id = cidadeId; + } + + const { count, rows } = await LocalColeta.findAndCountAll({ + where, + include: [ + { model: Cidade }, + { model: FaseSucessional }, + ], + limit: limite, + offset, + }); + + response.status(200).json({ + metadados: { + total: count, + pagina, + limite, + }, + locaisColeta: rows, + }); + } catch (error) { + next(error); + } +}; + export default {}; diff --git a/src/routes/locais.js b/src/routes/locais.js index 0e5fa934..b095a036 100644 --- a/src/routes/locais.js +++ b/src/routes/locais.js @@ -1,5 +1,8 @@ +import listagensMiddleware from '../middlewares/listagens-middleware'; import tokensMiddleware, { TIPOS_USUARIOS } from '../middlewares/tokens-middleware'; import validacoesMiddleware from '../middlewares/validacoes-middleware'; +import localColetaCadastroEsquema from '../validators/localColeta-cadastro'; +import localColetaListagemEsquema from '../validators/localColeta-listagem'; import nomeEsquema from '../validators/nome-obrigatorio'; const controller = require('../controllers/locais-coleta-controller'); @@ -233,4 +236,104 @@ export default app => { .get([ controller.buscarVegetacoes, ]); + + /** + * @swagger + * /locais-coleta: + * post: + * summary: Cadastra um novo local de coleta + * tags: [Locais] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * descricao: + * type: string + * complemento: + * type: string + * cidade_id: + * type: integer + * fase_sucessional_id: + * type: integer + * required: + * - descricao + * - cidade_id + * example: + * descricao: "Próximo ao rio" + * complemento: "Entrada pela fazenda" + * cidade_id: 1 + * fase_sucessional_id: 2 + * responses: + * 201: + * description: Local de coleta cadastrado com sucesso + * '400': + * $ref: '#/components/responses/BadRequest' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + */ + app.route('/locais-coleta') + .post([ + tokensMiddleware([ + TIPOS_USUARIOS.CURADOR, + TIPOS_USUARIOS.OPERADOR, + ]), + validacoesMiddleware(localColetaCadastroEsquema), + controller.cadastrarLocalColeta, + ]) + /** + * @swagger + * /locais-coleta: + * get: + * summary: Lista os locais de coleta + * tags: [Locais] + * parameters: + * - in: query + * name: cidadeId + * schema: + * type: integer + * description: Filtrar por ID da cidade + * - in: query + * name: pagina + * schema: + * type: integer + * description: Número da página + * - in: query + * name: limite + * schema: + * type: integer + * description: Quantidade de resultados por página + * responses: + * 200: + * description: Lista de locais de coleta + * content: + * application/json: + * schema: + * type: object + * properties: + * metadados: + * type: object + * locaisColeta: + * type: array + * items: + * type: object + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + */ + .get([ + tokensMiddleware([ + TIPOS_USUARIOS.CURADOR, + TIPOS_USUARIOS.OPERADOR, + TIPOS_USUARIOS.IDENTIFICADOR, + ]), + listagensMiddleware, + validacoesMiddleware(localColetaListagemEsquema), + controller.buscarLocaisColeta, + ]); }; diff --git a/src/validators/localColeta-cadastro.js b/src/validators/localColeta-cadastro.js new file mode 100644 index 00000000..6f1c8530 --- /dev/null +++ b/src/validators/localColeta-cadastro.js @@ -0,0 +1,25 @@ +export default { + descricao: { + in: ['body'], + isString: true, + notEmpty: true, + errorMessage: 'Descrição é obrigatória.', + }, + complemento: { + in: ['body'], + isString: true, + optional: true, + }, + cidade_id: { + in: ['body'], + isInt: true, + notEmpty: true, + errorMessage: 'ID da cidade é obrigatório e deve ser um número inteiro.', + }, + fase_sucessional_id: { + in: ['body'], + isInt: true, + optional: true, + errorMessage: 'ID da fase sucessional deve ser um número inteiro.', + }, +}; diff --git a/src/validators/localColeta-listagem.js b/src/validators/localColeta-listagem.js new file mode 100644 index 00000000..8149d4a0 --- /dev/null +++ b/src/validators/localColeta-listagem.js @@ -0,0 +1,8 @@ +export default { + cidadeId: { + in: ['query'], + isInt: true, + optional: true, + errorMessage: 'ID da cidade deve ser um número inteiro.', + }, +}; From acb50af8b5dd1e3cd5ec12f0eb23f73a072685ff Mon Sep 17 00:00:00 2001 From: Matheus Coitinho Date: Thu, 14 Aug 2025 20:24:08 -0300 Subject: [PATCH 2/6] =?UTF-8?q?adiciona=20lodash=20como=20depend=C3=AAncia?= =?UTF-8?q?=20no=20projeto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e0dd9559..3308bbfa 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "handlebars": "^4.7.8", "jsonwebtoken": "9.0.2", "knex": "2.5.1", + "lodash": "^4.17.21", "moment": "^2.24.0", "moment-timezone": "^0.5.21", "morgan": "1.10.0", From b947eeaeb253ce74158fbd3430ec229bf30c9022 Mon Sep 17 00:00:00 2001 From: Matheus Coitinho Date: Sun, 17 Aug 2025 23:09:29 -0300 Subject: [PATCH 3/6] =?UTF-8?q?adiciona=20campo=20descricao=5Flocal=5Fcole?= =?UTF-8?q?ta=20e=20atualiza=20valida=C3=A7=C3=B5es=20no=20cadastro=20de?= =?UTF-8?q?=20tombos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/tombos-controller.js | 42 +++++++++++--------------- src/models/Tombo.js | 4 +++ src/resources/errors/500-taxonomias.js | 1 + src/validators/tombo-cadastro.js | 7 ++--- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/controllers/tombos-controller.js b/src/controllers/tombos-controller.js index b1e50352..7b7ef209 100644 --- a/src/controllers/tombos-controller.js +++ b/src/controllers/tombos-controller.js @@ -104,22 +104,21 @@ export const cadastro = (request, response, next) => { return undefined; }) .then(() => { - let json = {}; - // /////////CRIA LOCAL DE COLETA//////////// - if (paisagem) { - json = pick(paisagem, ['descricao', 'solo_id', 'relevo_id', 'vegetacao_id', 'fase_sucessional_id']); - } - if (localidade.complemento) { - json.complemento = localidade.complemento; + if(!localidade || !localidade.complemento){ + throw new BadRequestExeption(400); } - json.cidade_id = localidade.cidade_id; - return LocalColeta.create(json, { transaction }); + return LocalColeta.findOne({ + where: { + id: localidade.complemento, + }, + transaction, + }); }) .then(localColeta => { if (!localColeta) { - throw new BadRequestExeption(400); + throw new BadRequestExeption(533); } - principal.local_coleta_id = localColeta.id; + return undefined; }) // //////////////CRIA COLECOES ANEXAS/////////// .then(() => { @@ -271,11 +270,15 @@ export const cadastro = (request, response, next) => { data_coleta_mes: principal.data_coleta.mes, data_coleta_ano: principal.data_coleta.ano, numero_coleta: principal.numero_coleta, - local_coleta_id: principal.local_coleta_id, + local_coleta_id: localidade.complemento, cor: principal.cor, coletor_id: coletor, }; + if(paisagem.descricao) { + jsonTombo.descricao_local_coleta = paisagem.descricao; + } + if (observacoes) { jsonTombo.observacao = observacoes; } @@ -1022,8 +1025,6 @@ export const obterTombo = async (request, response, next) => { let resposta = {}; let dadosTombo = {}; - // eslint-disable-next-line - // console.error(id); Promise.resolve() .then(() => Tombo.findOne({ @@ -1052,6 +1053,7 @@ export const obterTombo = async (request, response, next) => { 'data_identificacao_dia', 'data_identificacao_mes', 'data_identificacao_ano', + 'descricao_local_coleta', ], include: [ { @@ -1187,22 +1189,12 @@ export const obterTombo = async (request, response, next) => { vegetacaoInicial: tombo.vegetaco !== null ? tombo.vegetaco?.nome : '', faseInicial: tombo.locais_coletum !== null && tombo.locais_coletum?.fase_sucessional !== null ? tombo.locais_coletum?.fase_sucessional?.numero : '', - // coletoresInicial: tombo.coletores.map((coletor) => ({ - // key: `${coletor.id}`, - // label: coletor.nome, - // })), coletor: tombo.coletore ? { id: tombo.coletore?.id, nome: tombo.coletore?.nome, } : null, - // coletorComplementar: tombo.coletorComplementar - // ? { - // hcf: tombo.coletorComplementar.hcf, - // complementares: tombo.coletorComplementar.complementares, - // } - // : '', colecaoInicial: tombo.colecoes_anexa !== null ? tombo.colecoes_anexa?.tipo : '', complementoInicial: tombo.localizacao !== null && tombo.localizacao !== undefined ? tombo.localizacao?.complemento : '', hcf: tombo.hcf, @@ -1211,6 +1203,7 @@ export const obterTombo = async (request, response, next) => { observacao: tombo.observacao !== null ? tombo.observacao : '', tipo: tombo.tipo !== null ? tombo.tipo?.nome : '', numero_coleta: tombo.numero_coleta, + descricao_local_coleta: tombo.descricao_local_coleta !== null ? tombo.descricao_local_coleta : '', herbario: tombo.herbario !== null ? `${tombo.herbario?.sigla} - ${tombo.herbario?.nome}` : '', localizacao: { latitude: tombo.latitude !== null ? tombo.latitude : '', @@ -1231,6 +1224,7 @@ export const obterTombo = async (request, response, next) => { complemento: tombo.locais_coletum?.complemento !== null ? tombo.locais_coletum?.complemento : '', }, local_coleta: { + id: tombo.locais_coletum !== null ? tombo.locais_coletum?.id : '', descricao: tombo.locais_coletum !== null && tombo.locais_coletum?.descricao !== null ? tombo.locais_coletum.descricao : '', solo: tombo.solo !== null ? tombo.solo?.nome : '', relevo: tombo.relevo !== null ? tombo.relevo?.nome : '', diff --git a/src/models/Tombo.js b/src/models/Tombo.js index 44ac13ad..2cf1bb12 100644 --- a/src/models/Tombo.js +++ b/src/models/Tombo.js @@ -261,6 +261,10 @@ export default (Sequelize, DataTypes) => { type: DataTypes.INTEGER, allowNull: true, }, + descricao_local_coleta: { + type: DataTypes.TEXT, + allowNull: true, + }, }; const options = { diff --git a/src/resources/errors/500-taxonomias.js b/src/resources/errors/500-taxonomias.js index 21df4ab2..9c5705bb 100644 --- a/src/resources/errors/500-taxonomias.js +++ b/src/resources/errors/500-taxonomias.js @@ -31,4 +31,5 @@ export default { 530: 'Vegetação não encontrada.', 531: 'Fase sucessional não encontrada.', 532: 'Autor não encontrado.', + 533: 'Local de coleta não encontrado.', }; diff --git a/src/validators/tombo-cadastro.js b/src/validators/tombo-cadastro.js index 487362b4..7b5e619e 100644 --- a/src/validators/tombo-cadastro.js +++ b/src/validators/tombo-cadastro.js @@ -108,11 +108,8 @@ export default { }, 'json.localidade.complemento': { in: 'body', - isString: true, - optional: true, - isLength: { - options: [{ min: 3 }], - }, + isInt: true, + isEmpty: false, }, 'json.paisagem.solo_id': { in: 'body', From 8d654bb48e07c786c980bbb4d121b74fd39817ef Mon Sep 17 00:00:00 2001 From: Matheus Coitinho Date: Thu, 21 Aug 2025 16:21:39 -0300 Subject: [PATCH 4/6] =?UTF-8?q?refatora=20campos=20de=20descri=C3=A7=C3=A3?= =?UTF-8?q?o=20e=20valida=C3=A7=C3=B5es=20nos=20controladores=20e=20modelo?= =?UTF-8?q?s=20de=20tombos=20e=20locais=20de=20coleta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - src/controllers/locais-coleta-controller.js | 8 +++----- src/controllers/tombos-controller.js | 8 ++++---- src/models/Tombo.js | 2 +- src/validators/localColeta-listagem.js | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 3308bbfa..e0dd9559 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "handlebars": "^4.7.8", "jsonwebtoken": "9.0.2", "knex": "2.5.1", - "lodash": "^4.17.21", "moment": "^2.24.0", "moment-timezone": "^0.5.21", "morgan": "1.10.0", diff --git a/src/controllers/locais-coleta-controller.js b/src/controllers/locais-coleta-controller.js index c65fe274..0467ed91 100644 --- a/src/controllers/locais-coleta-controller.js +++ b/src/controllers/locais-coleta-controller.js @@ -1,5 +1,4 @@ -import { pick } from 'lodash'; - +import pick from '~/helpers/pick'; import BadRequestExeption from '../errors/bad-request-exception'; import models from '../models'; import codigos from '../resources/codigos-http'; @@ -135,8 +134,7 @@ export const cadastrarLocalColeta = async (request, response, next) => { export const buscarLocaisColeta = async (request, response, next) => { try { const { cidadeId } = request.query; - const { limite, pagina } = request.paginacao; - const offset = (pagina - 1) * limite; + const { limite, pagina, offset } = request.paginacao; const where = {}; if (cidadeId) { @@ -159,7 +157,7 @@ export const buscarLocaisColeta = async (request, response, next) => { pagina, limite, }, - locaisColeta: rows, + resultado: rows, }); } catch (error) { next(error); diff --git a/src/controllers/tombos-controller.js b/src/controllers/tombos-controller.js index 7b7ef209..5b05f1e9 100644 --- a/src/controllers/tombos-controller.js +++ b/src/controllers/tombos-controller.js @@ -104,7 +104,7 @@ export const cadastro = (request, response, next) => { return undefined; }) .then(() => { - if(!localidade || !localidade.complemento){ + if(!localidade?.complemento){ throw new BadRequestExeption(400); } return LocalColeta.findOne({ @@ -276,7 +276,7 @@ export const cadastro = (request, response, next) => { }; if(paisagem.descricao) { - jsonTombo.descricao_local_coleta = paisagem.descricao; + jsonTombo.descricao = paisagem.descricao; } if (observacoes) { @@ -1053,7 +1053,7 @@ export const obterTombo = async (request, response, next) => { 'data_identificacao_dia', 'data_identificacao_mes', 'data_identificacao_ano', - 'descricao_local_coleta', + 'descricao', ], include: [ { @@ -1203,7 +1203,7 @@ export const obterTombo = async (request, response, next) => { observacao: tombo.observacao !== null ? tombo.observacao : '', tipo: tombo.tipo !== null ? tombo.tipo?.nome : '', numero_coleta: tombo.numero_coleta, - descricao_local_coleta: tombo.descricao_local_coleta !== null ? tombo.descricao_local_coleta : '', + descricao: tombo.descricao !== null ? tombo.descricao : '', herbario: tombo.herbario !== null ? `${tombo.herbario?.sigla} - ${tombo.herbario?.nome}` : '', localizacao: { latitude: tombo.latitude !== null ? tombo.latitude : '', diff --git a/src/models/Tombo.js b/src/models/Tombo.js index 2cf1bb12..0759eadb 100644 --- a/src/models/Tombo.js +++ b/src/models/Tombo.js @@ -261,7 +261,7 @@ export default (Sequelize, DataTypes) => { type: DataTypes.INTEGER, allowNull: true, }, - descricao_local_coleta: { + descricao: { type: DataTypes.TEXT, allowNull: true, }, diff --git a/src/validators/localColeta-listagem.js b/src/validators/localColeta-listagem.js index 8149d4a0..9a19683e 100644 --- a/src/validators/localColeta-listagem.js +++ b/src/validators/localColeta-listagem.js @@ -1,5 +1,5 @@ export default { - cidadeId: { + cidade_id: { in: ['query'], isInt: true, optional: true, From 4c2f4731f30d5c5d69ac529ef285b1ff38115be9 Mon Sep 17 00:00:00 2001 From: Matheus Coitinho Date: Thu, 21 Aug 2025 16:31:00 -0300 Subject: [PATCH 5/6] adiciona funcionalidades para buscar e atualizar locais de coleta pelo ID --- src/controllers/locais-coleta-controller.js | 56 ++++++++ src/controllers/tombos-controller.js | 4 +- src/routes/locais.js | 142 ++++++++++++++++++++ 3 files changed, 200 insertions(+), 2 deletions(-) diff --git a/src/controllers/locais-coleta-controller.js b/src/controllers/locais-coleta-controller.js index 0467ed91..a751644f 100644 --- a/src/controllers/locais-coleta-controller.js +++ b/src/controllers/locais-coleta-controller.js @@ -1,4 +1,5 @@ import pick from '~/helpers/pick'; + import BadRequestExeption from '../errors/bad-request-exception'; import models from '../models'; import codigos from '../resources/codigos-http'; @@ -164,4 +165,59 @@ export const buscarLocaisColeta = async (request, response, next) => { } }; +export const buscarLocalColetaPorId = async (request, response, next) => { + try { + const { id } = request.params; + + const localColeta = await LocalColeta.findOne({ + where: { id }, + include: [ + { model: Cidade }, + { model: FaseSucessional }, + ], + }); + + if (!localColeta) { + response.status(404).json({ + mensagem: 'Local de coleta não encontrado.', + }); + return; + } + + response.status(200).json(localColeta); + } catch (error) { + next(error); + } +}; + +export const atualizarLocalColeta = async (request, response, next) => { + try { + const { id } = request.params; + const dados = pick(request.body, ['descricao', 'complemento', 'cidade_id', 'fase_sucessional_id']); + + const [updated] = await LocalColeta.update(dados, { + where: { id }, + }); + + if (updated === 0) { + response.status(404).json({ + mensagem: 'Local de coleta não encontrado.', + }); + return; + } + + const localColetaAtualizado = await LocalColeta.findOne({ + where: { id }, + include: [ + { model: Cidade }, + { model: FaseSucessional }, + ], + }); + + response.status(200).json(localColetaAtualizado); + } catch (error) { + next(error); + } +}; + export default {}; diff --git a/src/controllers/tombos-controller.js b/src/controllers/tombos-controller.js index 5b05f1e9..b220aab0 100644 --- a/src/controllers/tombos-controller.js +++ b/src/controllers/tombos-controller.js @@ -104,7 +104,7 @@ export const cadastro = (request, response, next) => { return undefined; }) .then(() => { - if(!localidade?.complemento){ + if (!localidade?.complemento) { throw new BadRequestExeption(400); } return LocalColeta.findOne({ @@ -275,7 +275,7 @@ export const cadastro = (request, response, next) => { coletor_id: coletor, }; - if(paisagem.descricao) { + if (paisagem.descricao) { jsonTombo.descricao = paisagem.descricao; } diff --git a/src/routes/locais.js b/src/routes/locais.js index b095a036..bf4c5ea7 100644 --- a/src/routes/locais.js +++ b/src/routes/locais.js @@ -336,4 +336,146 @@ export default app => { validacoesMiddleware(localColetaListagemEsquema), controller.buscarLocaisColeta, ]); + + /** + * @swagger + * /locais-coleta/{id}: + * get: + * summary: Busca um local de coleta pelo ID + * tags: [Locais] + * description: Retorna os dados de um local de coleta específico. + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID do local de coleta + * responses: + * 200: + * description: Dados do local de coleta encontrados + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: integer + * descricao: + * type: string + * complemento: + * type: string + * nullable: true + * cidade_id: + * type: integer + * fase_sucessional_id: + * type: integer + * nullable: true + * cidade: + * type: object + * properties: + * id: + * type: integer + * nome: + * type: string + * fase_sucessional: + * type: object + * nullable: true + * properties: + * id: + * type: integer + * nome: + * type: string + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * $ref: '#/components/responses/NotFound' + * '500': + * $ref: '#/components/responses/InternalServerError' + * put: + * summary: Atualiza um local de coleta + * tags: [Locais] + * description: Atualiza os dados de um local de coleta existente. + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID do local de coleta + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * descricao: + * type: string + * description: Descrição do local de coleta + * complemento: + * type: string + * description: Complemento do local de coleta + * cidade_id: + * type: integer + * description: ID da cidade + * fase_sucessional_id: + * type: integer + * description: ID da fase sucessional + * required: + * - descricao + * - cidade_id + * example: + * descricao: "Próximo ao córrego" + * complemento: "Entrada pelo portão principal" + * cidade_id: 2 + * fase_sucessional_id: 3 + * responses: + * 200: + * description: Local de coleta atualizado com sucesso + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: integer + * descricao: + * type: string + * complemento: + * type: string + * cidade_id: + * type: integer + * fase_sucessional_id: + * type: integer + * '400': + * $ref: '#/components/responses/BadRequest' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * $ref: '#/components/responses/NotFound' + * '500': + * $ref: '#/components/responses/InternalServerError' + */ + app.route('/locais-coleta/:id') + .get([ + tokensMiddleware([ + TIPOS_USUARIOS.CURADOR, + TIPOS_USUARIOS.OPERADOR, + TIPOS_USUARIOS.IDENTIFICADOR, + ]), + controller.buscarLocalColetaPorId, + ]) + .put([ + tokensMiddleware([ + TIPOS_USUARIOS.CURADOR, + TIPOS_USUARIOS.OPERADOR, + ]), + validacoesMiddleware(localColetaCadastroEsquema), + controller.atualizarLocalColeta, + ]); }; From a2a372c5f9f7dc4ce99c39c451a266ef7f19494c Mon Sep 17 00:00:00 2001 From: Matheus Coitinho Date: Fri, 22 Aug 2025 19:16:50 -0300 Subject: [PATCH 6/6] =?UTF-8?q?adiciona=20funcionalidade=20para=20deletar?= =?UTF-8?q?=20locais=20de=20coleta=20com=20verifica=C3=A7=C3=A3o=20de=20to?= =?UTF-8?q?mbos=20associados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/locais-coleta-controller.js | 65 +++++++++++++++++++-- src/routes/locais.js | 44 ++++++++++++++ 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/controllers/locais-coleta-controller.js b/src/controllers/locais-coleta-controller.js index a751644f..2efe595f 100644 --- a/src/controllers/locais-coleta-controller.js +++ b/src/controllers/locais-coleta-controller.js @@ -5,7 +5,7 @@ import models from '../models'; import codigos from '../resources/codigos-http'; const { - Relevo, Solo, Vegetacao, LocalColeta, Cidade, FaseSucessional, sequelize, + Relevo, Solo, Vegetacao, LocalColeta, Cidade, FaseSucessional, Estado, Pais, sequelize, } = models; export const cadastrarSolo = (request, response, next) => { @@ -134,7 +134,7 @@ export const cadastrarLocalColeta = async (request, response, next) => { export const buscarLocaisColeta = async (request, response, next) => { try { - const { cidadeId } = request.query; + const { cidade_id: cidadeId } = request.query; const { limite, pagina, offset } = request.paginacao; const where = {}; @@ -145,7 +145,15 @@ export const buscarLocaisColeta = async (request, response, next) => { const { count, rows } = await LocalColeta.findAndCountAll({ where, include: [ - { model: Cidade }, + { model: Cidade, + include: [ + { model: Estado, + include: [ + Pais, + ], + }, + ], + }, { model: FaseSucessional }, ], limit: limite, @@ -172,7 +180,15 @@ export const buscarLocalColetaPorId = async (request, response, next) => { const localColeta = await LocalColeta.findOne({ where: { id }, include: [ - { model: Cidade }, + { model: Cidade, + include: [ + { model: Estado, + include: [ + Pais, + ], + }, + ], + }, { model: FaseSucessional }, ], }); @@ -220,4 +236,45 @@ export const atualizarLocalColeta = async (request, response, next) => { } }; +export const deletarLocalColeta = async (request, response, next) => { + try { + const { id } = request.params; + + const localColeta = await LocalColeta.findOne({ + where: { id }, + }); + + if (!localColeta) { + response.status(404).json({ + mensagem: 'Local de coleta não encontrado.', + }); + return; + } + + const { Tombo } = models; + const tombosAssociados = await Tombo.count({ + where: { + local_coleta_id: id, + ativo: true, + }, + }); + + if (tombosAssociados > 0) { + response.status(400).json({ + mensagem: `Não é possível excluir o local de coleta. Existem ${tombosAssociados} tombo(s) associado(s) a este local.`, + }); + return; + } + + await LocalColeta.destroy({ + where: { id }, + }); + + response.status(204).send(); + + } catch (error) { + next(error); + } +}; + export default {}; diff --git a/src/routes/locais.js b/src/routes/locais.js index bf4c5ea7..21b41d30 100644 --- a/src/routes/locais.js +++ b/src/routes/locais.js @@ -477,5 +477,49 @@ export default app => { ]), validacoesMiddleware(localColetaCadastroEsquema), controller.atualizarLocalColeta, + ]) + /** + * @swagger + * /locais-coleta/{id}: + * delete: + * summary: Remove um local de coleta + * tags: [Locais] + * description: Remove um local de coleta pelo ID. Verifica se não há tombos associados antes de remover. + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID do local de coleta + * responses: + * 204: + * description: Local de coleta removido com sucesso + * '400': + * description: Não é possível excluir - existem tombos associados + * content: + * application/json: + * schema: + * type: object + * properties: + * mensagem: + * type: string + * example: + * mensagem: "Não é possível excluir o local de coleta. Existem 5 tombo(s) associado(s) a este local." + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * $ref: '#/components/responses/NotFound' + * '500': + * $ref: '#/components/responses/InternalServerError' + */ + .delete([ + tokensMiddleware([ + TIPOS_USUARIOS.CURADOR, + TIPOS_USUARIOS.OPERADOR, + ]), + controller.deletarLocalColeta, ]); };