diff --git a/src/controllers/taxonomias-controller.js b/src/controllers/taxonomias-controller.js index 7040b72..253eab9 100644 --- a/src/controllers/taxonomias-controller.js +++ b/src/controllers/taxonomias-controller.js @@ -1341,9 +1341,9 @@ export const editarVariedade = (request, response, next) => { // //////////////////AUTORES////////////////// export const cadastrarAutores = (request, response, next) => { - let { nome, iniciais } = request.body; + let { nome, observacao } = request.body; nome = limparEspacos(nome); - iniciais = limparEspacos(iniciais); + observacao = limparEspacos(observacao); const callback = transaction => Promise.resolve() .then(() => Autor.findOne({ where: { @@ -1356,7 +1356,7 @@ export const cadastrarAutores = (request, response, next) => { throw new BadRequestExeption(513); } }) - .then(() => Autor.create({ nome, iniciais }, transaction)); + .then(() => Autor.create({ nome, observacao }, transaction)); sequelize.transaction(callback) .then(autorCriado => { if (!autorCriado) { @@ -1409,7 +1409,7 @@ export const buscarAutores = async (request, response, next) => { ]; const result = await Autor.findAndCountAll({ - attributes: ['id', 'nome', 'iniciais'], + attributes: ['id', 'nome', 'observacao'], order, limit: limite, offset, @@ -1476,9 +1476,9 @@ export const excluirAutores = (request, response, next) => { }; export const editarAutores = (request, response, next) => { - let { nome, iniciais } = request.body; + let { nome, observacao } = request.body; nome = limparEspacos(nome); - iniciais = limparEspacos(iniciais); + observacao = limparEspacos(observacao); const autorId = parseInt(request.params.autor_id); const callback = transaction => Promise.resolve() @@ -1493,7 +1493,7 @@ export const editarAutores = (request, response, next) => { throw new BadRequestExeption(517); } }) - .then(() => Autor.update({ nome, iniciais }, { + .then(() => Autor.update({ nome, observacao }, { where: { id: autorId, }, diff --git a/src/database/migration/20260211011159_fix_remove_autores_duplicados.ts b/src/database/migration/20260211011159_fix_remove_autores_duplicados.ts new file mode 100644 index 0000000..80ded0b --- /dev/null +++ b/src/database/migration/20260211011159_fix_remove_autores_duplicados.ts @@ -0,0 +1,105 @@ +import { Knex } from 'knex' + +type DupGroupRow = { + nome: string + observacao: string | null + keep_id: number + ids: number[] + qtde: number +} + +export async function run(knex: Knex): Promise { + await knex.transaction(async trx => { + const hasIniciais = await trx.schema.hasColumn('autores', 'iniciais') + const hasObservacao = await trx.schema.hasColumn('autores', 'observacao') + + if (hasIniciais && !hasObservacao) { + await trx.schema.alterTable('autores', table => { + table.renameColumn('iniciais', 'observacao') + }) + } + + const hasObservacaoNow = await trx.schema.hasColumn('autores', 'observacao') + + if (hasObservacaoNow) { + await trx.schema.alterTable('autores', table => { + table.string('observacao', 500).nullable().alter() + }) + } + + const dupGroups = (await trx('autores') + .select([ + 'nome', + 'observacao', + trx.raw('MIN(id)::int as keep_id'), + trx.raw('ARRAY_AGG(id ORDER BY id) as ids'), + trx.raw('COUNT(*)::int as qtde') + ]) + .groupBy(['nome', 'observacao']) + .havingRaw('COUNT(*) > 1')) as unknown as DupGroupRow[] + + if (!dupGroups.length) return + + const pairs: Array<{ keep_id: number; drop_id: number }> = [] + + for (const g of dupGroups) { + const keepId = Number(g.keep_id) + const ids = (g.ids ?? []).map(n => Number(n)).filter(Number.isFinite) + + for (const id of ids) { + if (id !== keepId) pairs.push({ keep_id: keepId, drop_id: id }) + } + } + + if (!pairs.length) return + + await trx.raw('DROP TABLE IF EXISTS autor_merge') + + await trx.schema.createTable('autor_merge', table => { + table.integer('keep_id').notNullable() + table.integer('drop_id').notNullable().primary() + }) + + await trx('autor_merge').insert(pairs) + + await trx.raw(` + UPDATE especies e + SET autor_id = m.keep_id + FROM autor_merge m + WHERE e.autor_id = m.drop_id + `) + + await trx.raw(` + UPDATE sub_especies se + SET autor_id = m.keep_id + FROM autor_merge m + WHERE se.autor_id = m.drop_id + `) + + await trx.raw(` + UPDATE variedades v + SET autor_id = m.keep_id + FROM autor_merge m + WHERE v.autor_id = m.drop_id + `) + + const hasSubFamiliasAutorId = await trx.schema.hasColumn('sub_familias', 'autor_id') + + if (hasSubFamiliasAutorId) { + await trx.raw(` + UPDATE sub_familias sf + SET autor_id = m.keep_id + FROM autor_merge m + WHERE sf.autor_id = m.drop_id + `) + } + + await trx.raw(` + DELETE FROM autores a + USING autor_merge m + WHERE a.id = m.drop_id + `) + + await trx.schema.dropTable('autor_merge') + }) +} diff --git a/src/models/Autor.js b/src/models/Autor.js index 0287ed9..7848efa 100644 --- a/src/models/Autor.js +++ b/src/models/Autor.js @@ -24,8 +24,8 @@ export default (Sequelize, DataTypes) => { type: DataTypes.STRING(200), allowNull: false, }, - iniciais: { - type: DataTypes.STRING(200), + observacao: { + type: DataTypes.STRING(500), allowNull: true, }, }; diff --git a/src/routes/taxonomias.js b/src/routes/taxonomias.js index 3c88bee..8aa16c5 100644 --- a/src/routes/taxonomias.js +++ b/src/routes/taxonomias.js @@ -38,8 +38,7 @@ const generosOrdenacaoMiddleware = criaOrdenacaoMiddleware(['genero', 'familia', const especiesOrdenacaoMiddleware = criaOrdenacaoMiddleware(['especie', 'reino', 'familia', 'genero', 'familia', 'autor'], 'nome', 'asc'); const subEspeciesOrdenacaoMiddleware = criaOrdenacaoMiddleware(['subespecie', 'reino', 'familia', 'genero', 'especie', 'autor'], 'nome', 'asc'); const variedadesOrdenacaoMiddleware = criaOrdenacaoMiddleware(['variedade', 'reino', 'familia', 'genero', 'especie', 'autor'], 'nome', 'asc'); -const autorOrdenacaoMiddleware = criaOrdenacaoMiddleware(['autor', 'iniciais'], 'nome', 'asc'); - +const autorOrdenacaoMiddleware = criaOrdenacaoMiddleware(['autor', 'observacao'], 'nome', 'asc'); /** * @swagger * tags: @@ -1534,10 +1533,15 @@ export default app => { * properties: * nome: * type: string + * observacao: + * type: string + * nullable: true + * maxLength: 500 * required: * - nome * example: * nome: "A. Author" + * observacao: "Observação opcional sobre o autor" * responses: * 201: * description: Autor cadastrado com sucesso @@ -1548,6 +1552,7 @@ export default app => { * example: * id: 1 * nome: "A. Author" + * observacao: "Observação opcional sobre o autor" * '400': * $ref: '#/components/responses/BadRequest' * '401': @@ -1600,9 +1605,10 @@ export default app => { * type: integer * nome: * type: string - * iniciais: + * observacao: * type: string * nullable: true + * maxLength: 500 * '400': * $ref: '#/components/responses/BadRequest' * '401': diff --git a/src/validators/autor-atualiza.js b/src/validators/autor-atualiza.js index 8724754..d85fb1e 100644 --- a/src/validators/autor-atualiza.js +++ b/src/validators/autor-atualiza.js @@ -2,19 +2,22 @@ export default { nome: { in: 'body', isString: true, - isEmpty: false, + notEmpty: true, isLength: { options: [{ min: 3 }], }, }, - iniciais: { + observacao: { in: 'body', isString: true, optional: true, + isLength: { + options: [{ max: 500 }], + }, }, autor_id: { in: 'params', isInt: true, - isEmpty: false, + notEmpty: true, }, }; diff --git a/src/validators/autor-cadastro.js b/src/validators/autor-cadastro.js index 89e8243..5f0554c 100644 --- a/src/validators/autor-cadastro.js +++ b/src/validators/autor-cadastro.js @@ -7,7 +7,7 @@ export default { options: [{ min: 3 }], }, }, - iniciais: { + observacao: { in: 'body', isString: true, optional: true,