Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions src/controllers/relatorios-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const {
Estado,
Pais,
TomboFoto,
Variedade,
Subespecie,
} = models;

/// ////// Relatório de Inventário de Espécies //////////
Expand Down Expand Up @@ -291,7 +293,7 @@ export const obtemDadosDoRelatorioDeColetaIntervaloDeData = async (req, res, nex
{
[Op.between]: [
Sequelize.literal(`TO_DATE('${dataInicio.slice(0, 10)}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim || new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim ? dataFim.slice(0, 10) : new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
],
},
),
Expand Down Expand Up @@ -403,7 +405,7 @@ export const obtemDadosDoRelatorioDeColetaPorColetorEIntervaloDeData = async (re
{
[Op.between]: [
Sequelize.literal(`TO_DATE('${dataInicio.slice(0, 10)}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim || new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim ? dataFim.slice(0, 10) : new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
],
},
),
Expand Down Expand Up @@ -530,7 +532,7 @@ export const obtemDadosDoRelatorioDeLocalDeColeta = async (req, res, next) => {
{
[Op.between]: [
Sequelize.literal(`TO_DATE('${dataInicio.slice(0, 10)}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim || new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim ? dataFim.slice(0, 10) : new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
],
},
),
Expand All @@ -546,6 +548,8 @@ export const obtemDadosDoRelatorioDeLocalDeColeta = async (req, res, next) => {
'familia_id',
'especie_id',
'genero_id',
'variedade_id',
'sub_especie_id',
'nome_cientifico',
'data_coleta_ano',
'data_coleta_mes',
Expand Down Expand Up @@ -576,6 +580,28 @@ export const obtemDadosDoRelatorioDeLocalDeColeta = async (req, res, next) => {
},
],
},
{
model: Variedade,
attributes: ['id', 'nome'],
include: [
{
model: Autor,
attributes: ['id', 'nome'],
as: 'autor',
},
],
},
{
model: Subespecie,
attributes: ['id', 'nome'],
include: [
{
model: Autor,
attributes: ['id', 'nome'],
as: 'autor',
},
],
},
{
model: LocalColeta,
attributes: ['id', 'descricao'],
Expand Down Expand Up @@ -748,7 +774,7 @@ export const obtemDadosDoRelatorioDeCodigoDeBarras = async (req, res, next) => {
{
[Op.between]: [
Sequelize.literal(`TO_DATE('${dataInicio.slice(0, 10)}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim || new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim ? dataFim.slice(0, 10) : new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
],
},
),
Expand Down Expand Up @@ -815,7 +841,7 @@ export const obtemDadosDoRelatorioDeQuantidade = async (req, res, next) => {
{
[Op.between]: [
Sequelize.literal(`TO_DATE('${dataInicio.slice(0, 10)}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim || new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
Sequelize.literal(`TO_DATE('${(dataFim ? dataFim.slice(0, 10) : new Date().toISOString().slice(0, 10))}', 'YYYY-MM-DD')`),
],
},
),
Expand Down
143 changes: 143 additions & 0 deletions src/database/migration/20260311154039_corrige-duplicatas-variedades.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Knex } from 'knex'

function normalize(value: unknown): string {
const safeValue = typeof value === 'string' || typeof value === 'number' ? String(value) : ''

return safeValue
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
.replace(/\s+/g, ' ')
.toLowerCase()
}

type SnapshotItem = {
tomboId: number
key: string
}

type NovaVariedade = {
key: string
nome: string
familia_id: number
genero_id: number
especie_id: number
autor_id: number | null
}

export async function run(knex: Knex): Promise<void> {
await knex.transaction(async trx => {
const tombos = await trx('tombos as t')
.join('familias as f', 't.familia_id', 'f.id')
.join('generos as g', 't.genero_id', 'g.id')
.join('especies as e', 't.especie_id', 'e.id')
.leftJoin('variedades as v', 't.variedade_id', 'v.id')
.select(
't.hcf as tombo_id',
't.familia_id',
't.genero_id',
't.especie_id',
'v.nome as variedade_nome',
'v.autor_id as variedade_autor_id',
'f.nome as familia_nome',
'g.nome as genero_nome',
'e.nome as especie_nome'
)
.whereNotNull('t.familia_id')
.whereNotNull('t.genero_id')
.whereNotNull('t.especie_id')
.whereNotNull('v.nome')

const snapshot: SnapshotItem[] = []
const variedadesMap = new Map<string, NovaVariedade>()

for (const row of tombos) {
const key = [
normalize(row.variedade_nome),
normalize(row.familia_nome),
normalize(row.genero_nome),
normalize(row.especie_nome),
row.variedade_autor_id
].join('|')

snapshot.push({
tomboId: Number(row.tombo_id),
key
})

if (!variedadesMap.has(key)) {
variedadesMap.set(key, {
key,
nome: String(row.variedade_nome).trim(),
familia_id: Number(row.familia_id),
genero_id: Number(row.genero_id),
especie_id: Number(row.especie_id),
autor_id: row.variedade_autor_id ? Number(row.variedade_autor_id) : null
})
}
}

const novasVariedades = Array.from(variedadesMap.values())
const tomboIds = snapshot.map(item => item.tomboId)

if (tomboIds.length > 0) {
await trx('tombos')
.whereIn('hcf', tomboIds)
.update({ variedade_id: null })
}

await trx('variedades').del()

const inserted = await trx('variedades')
.insert(
novasVariedades.map(item => ({
nome: item.nome,
familia_id: item.familia_id,
genero_id: item.genero_id,
especie_id: item.especie_id,
autor_id: item.autor_id
}))
)
.returning([
'id',
'nome',
'familia_id',
'genero_id',
'especie_id',
'autor_id'
])

const newIdMap = new Map<string, number>()

for (const row of inserted) {
const matchingTombo = tombos.find(
t =>
Number(t.familia_id) === Number(row.familia_id)
&& Number(t.genero_id) === Number(row.genero_id)
&& Number(t.especie_id) === Number(row.especie_id)
&& normalize(t.variedade_nome) === normalize(row.nome)
&& (t.variedade_autor_id ? Number(t.variedade_autor_id) : null) === (row.autor_id ? Number(row.autor_id) : null)
)

const key = [
normalize(row.nome),
normalize(matchingTombo?.familia_nome),
normalize(matchingTombo?.genero_nome),
normalize(matchingTombo?.especie_nome),
row.autor_id ? Number(row.autor_id) : null
].join('|')

newIdMap.set(key, Number(row.id))
}

for (const item of snapshot) {
const novaVariedadeId = newIdMap.get(item.key)

if (novaVariedadeId) {
await trx('tombos')
.where('hcf', item.tomboId)
.update({ variedade_id: novaVariedadeId })
}
}
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Knex } from 'knex'

function normalize(str: string | null | undefined) {
if (!str) return ''
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim()
}

export async function run(knex: Knex): Promise<void> {
await knex.transaction(async trx => {
// 1. BUSCA OS DADOS CONFORME ESTÃO NO TOMBO
const tombos = await trx('tombos as t')
.join('familias as f', 't.familia_id', 'f.id')
.join('generos as g', 't.genero_id', 'g.id')
.join('especies as e', 't.especie_id', 'e.id')
.join('sub_especies as sub', 't.sub_especie_id', 'sub.id')
.select(
't.hcf as tombo_id',
't.familia_id',
't.genero_id',
't.especie_id', // Esta é a espécie que manda na reconstrução
'sub.nome as sub_nome',
'sub.autor_id as sub_autor_id',
'f.nome as familia_nome',
'g.nome as genero_nome',
'e.nome as especie_nome'
)
.whereNotNull('t.familia_id')
.whereNotNull('t.genero_id')
.whereNotNull('t.especie_id')
.whereNotNull('sub.nome')
.where('sub.nome', '<>', '')
.whereRaw('TRIM(sub.nome) <> \'\'')

type NovaSubespecie = {
key: string
nome: string
familia_id: number
genero_id: number
especie_id: number
autor_id: number | null
}

const uniqueSubespecies = new Map<string, NovaSubespecie>()
const tombosToUpdate = new Map<string, number[]>()

for (const row of tombos) {
const key = [
normalize(row.sub_nome),
normalize(row.familia_nome),
normalize(row.genero_nome),
normalize(row.especie_nome),
row.sub_autor_id
].join('|')

if (!uniqueSubespecies.has(key)) {
uniqueSubespecies.set(key, {
key,
nome: row.sub_nome,
familia_id: row.familia_id,
genero_id: row.genero_id,
especie_id: row.especie_id, // Vincula à espécie definida no tombo
autor_id: row.sub_autor_id
})
}

if (!tombosToUpdate.has(key)) {
tombosToUpdate.set(key, [])
}
tombosToUpdate.get(key)!.push(row.tombo_id)
}

const chunkSize = 1000

// 2. RESET E RECONSTRUÇÃO TOTAL
// Desconecta todos os tombos antes de apagar a tabela antiga
await trx('tombos')
.whereNotNull('sub_especie_id')
.update({ sub_especie_id: null })

// Limpa a tabela antiga
await trx('sub_especies').del()

if (uniqueSubespecies.size > 0) {
const itemsToInsert = Array.from(uniqueSubespecies.values()).map(sub => ({
nome: sub.nome,
familia_id: sub.familia_id,
genero_id: sub.genero_id,
especie_id: sub.especie_id,
autor_id: sub.autor_id
}))

for (let i = 0; i < itemsToInsert.length; i += chunkSize) {
const chunk = itemsToInsert.slice(i, i + chunkSize)
await trx('sub_especies').insert(chunk)
}

// 3. REATRIBUIÇÃO DOS NOVOS IDS
const newSubEspecies = await trx('sub_especies as sub')
.join('familias as f', 'sub.familia_id', 'f.id')
.join('generos as g', 'sub.genero_id', 'g.id')
.join('especies as e', 'sub.especie_id', 'e.id')
.select(
'sub.id',
'sub.nome as sub_nome',
'sub.autor_id as sub_autor_id',
'f.nome as familia_nome',
'g.nome as genero_nome',
'e.nome as especie_nome'
)

const newIdMap = new Map<string, number>()
for (const row of newSubEspecies) {
const key = [
normalize(row.sub_nome),
normalize(row.familia_nome),
normalize(row.genero_nome),
normalize(row.especie_nome),
row.sub_autor_id
].join('|')
newIdMap.set(key, row.id)
}

for (const [key, tomboIds] of tombosToUpdate.entries()) {
const newId = newIdMap.get(key)
if (newId !== undefined && tomboIds.length > 0) {
for (let i = 0; i < tomboIds.length; i += chunkSize) {
const chunk = tomboIds.slice(i, i + chunkSize)
await trx('tombos')
.whereIn('hcf', chunk)
.update({ sub_especie_id: newId })
}
}
}
}
})
}
Loading
Loading