From e1d360b5f244f90117d5f5da5a73105d389900ea Mon Sep 17 00:00:00 2001 From: Moran <105233020+feliperm17@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:58:47 -0300 Subject: [PATCH 1/3] =?UTF-8?q?fix(darwincore):=20corrige=20exporta=C3=A7?= =?UTF-8?q?=C3=A3o=20CSV=20e=20associa=C3=A7=C3=B5es=20do=20modelo=20(#371?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/darwincore-controller.js | 105 ++++++++++++++--------- src/resources/darwincore/cabecalho.js | 2 +- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/controllers/darwincore-controller.js b/src/controllers/darwincore-controller.js index 3c24561c..c2a591f4 100644 --- a/src/controllers/darwincore-controller.js +++ b/src/controllers/darwincore-controller.js @@ -19,6 +19,7 @@ const { Subfamilia, Autor, Coletor, + ColetorComplementar, Variedade, Subespecie, ColecaoAnexa, @@ -47,6 +48,21 @@ function obtemNomeArquivoCsv() { return `hcf_${data}.csv`; } +function csvEscape(valor) { + if (valor === null || valor === undefined) { + return ''; + } + const str = String(valor); + if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) { + return `"${str.replace(/"/g, '""')}"`; + } + return str; +} + +function csvLinha(campos) { + return campos.map(csvEscape).join(','); +} + const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { const entidadeTombo = await Tombo.findAll({ limit, @@ -87,6 +103,7 @@ const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { 'relevo_id', 'solo_id', 'coletor_id', + 'cidade_id', ], include: [ { @@ -177,6 +194,10 @@ const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { { model: Coletor, }, + { + model: ColetorComplementar, + as: 'coletor_complementar', + }, { model: Genero, }, @@ -210,8 +231,7 @@ const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { const paisCodigo = tombo?.cidade?.estado?.paise?.sigla ?? ''; const paranaNome = tombo?.cidade?.estado?.nome ?? ''; const cidadeNome = tombo?.cidade?.nome ?? ''; - const vegetacao - = tombo.locais_coletum && tombo.locais_coletum.vegetacao ? tombo.locais_coletum.vegetacao.nome : ''; + const vegetacao = tombo.vegetaco ? tombo.vegetaco.nome : ''; const familiaNome = tombo.familia ? tombo.familia.nome : ''; const generoNome = tombo.genero ? tombo.genero.nome : ''; const especieNome = tombo.especy ? tombo.especy.nome : ''; @@ -223,17 +243,20 @@ const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { if (tombo.variedade) { menorNomeCientifico = tombo.variedade.nome; } - if (tombo.especy && tombo.especy.autore) { - autores = tombo.especy.autore.nome; + if (tombo.especy && tombo.especy.autor) { + autores = tombo.especy.autor.nome; } - if (tombo.sub_especy && tombo.sub_especy.autore) { - autores += `| ${tombo.sub_especy.autore.nome}`; + if (tombo.sub_especy && tombo.sub_especy.autor) { + autores += `| ${tombo.sub_especy.autor.nome}`; } - if (tombo.variedade && tombo.variedade.autore) { - autores += `| ${tombo.variedade.autore.nome}`; + if (tombo.variedade && tombo.variedade.autor) { + autores += `| ${tombo.variedade.autor.nome}`; } - if (tombo.coletores && tombo.coletores.length > 0) { - coletores = tombo.coletores.map(coletor => padronizarNomeDarwincore(coletor.nome)).join(' | '); + if (tombo.coletore) { + coletores = padronizarNomeDarwincore(tombo.coletore.nome); + if (tombo.coletor_complementar?.complementares) { + coletores += ` | ${tombo.coletor_complementar.complementares}`; + } } if (tombo.data_coleta_ano) { dataColeta = tombo.data_coleta_ano; @@ -281,20 +304,21 @@ const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { tombo.tombos_fotos.forEach(foto => { const dataAtualizacao = format(tombo.updated_at, 'yyyy-MM-dd'); - let linha = [ - `PreservedSpecimen\tColecao\tpt\t${dataAtualizacao}\t02.032.297/0005-26\t`, - 'UTFPR\tHerbario da Universidade Tecnologica Federal do Parana – Campus Campo Mourao – HCF\t', - `${license}\tUTFPR\t{"barcode":${foto.codigo_barra}}\tBr:UTFPR:HCF:${tombo.hcf.toString()}`, - `${foto.id.toString()}\t${tombo.hcf.toString()}${foto.id.toString()}\t${coletores}\t`, - `${tombo.numero_coleta}\t\t${tombo.observacao}\t${dataColeta}\t`, - `${tombo.data_coleta_ano}\t${tombo.data_coleta_mes}\t${tombo.data_coleta_dia}\t`, - `${vegetacao}\t${'América do Sul'}\t${paisNome}\t${paisCodigo}\t${paranaNome}\t`, - `${cidadeNome}\t${tombo.altitude}\t${tombo.altitude}\t\t\t${tombo.latitude}\t`, - `${tombo.longitude}\t${'WGS84'}\t${'GPS'}\t${'Plantae'}\t${familiaNome}\t${generoNome}\t`, - `${especieNome}\t${menorNomeCientifico}\t${tombo.nome_cientifico}\t${autores}\t${tombo.taxon}`, - `\t${tombo.nomes_populares}\t\t${nomeTipo}\t${nomeIdentificador}\t${dataIdentificacao}`, - `\t${identificationQualifier}`, - ].join(''); + const campos = [ + 'PreservedSpecimen', 'Colecao', 'pt', dataAtualizacao, '02.032.297/0005-26', + 'UTFPR', 'Herbario da Universidade Tecnologica Federal do Parana – Campus Campo Mourao – HCF', + license, 'UTFPR', `{"barcode":"${foto.codigo_barra}"}`, `Br:UTFPR:HCF:${tombo.hcf}`, + tombo.hcf, coletores, + tombo.numero_coleta, '', tombo.observacao, dataColeta, + tombo.data_coleta_ano, tombo.data_coleta_mes, tombo.data_coleta_dia, + vegetacao, 'América do Sul', paisNome, paisCodigo, paranaNome, + cidadeNome, tombo.altitude, tombo.altitude, '', '', tombo.latitude, + tombo.longitude, 'WGS84', 'GPS', 'Plantae', familiaNome, generoNome, + especieNome, menorNomeCientifico, tombo.nome_cientifico, autores, tombo.taxon, + tombo.nomes_populares, '', nomeTipo, nomeIdentificador, dataIdentificacao, + identificationQualifier, + ]; + let linha = csvLinha(campos); linha = linha.replace(/(null|undefined)/g, ''); linhasProcessadas.push(`${linha.replace(/[\r\n]/g, '')}\n`); @@ -302,20 +326,21 @@ const obterModeloDarwinCoreLotes = async (limit, offset, request, response) => { } else { const dataAtualizacao = format(tombo.updated_at, 'yyyy-MM-dd'); - let linha = [ - `PreservedSpecimen\tColecao\tpt\t${dataAtualizacao}\t02.032.297/0005-26\t`, - 'UTFPR\tHerbario da Universidade Tecnologica Federal do Parana – Campus Campo Mourao – HCF\t', - `${license}\tUTFPR\t{"barcode":}\tBr:UTFPR:HCF:${tombo.hcf.toString()}`, - `\t${tombo.hcf.toString()}\t${coletores}\t`, - `${tombo.numero_coleta}\t\t${tombo.observacao}\t${dataColeta}\t`, - `${tombo.data_coleta_ano}\t${tombo.data_coleta_mes}\t${tombo.data_coleta_dia}\t`, - `${vegetacao}\t${'América do Sul'}\t${paisNome}\t${paisCodigo}\t${paranaNome}\t`, - `${cidadeNome}\t${tombo.altitude}\t${tombo.altitude}\t\t\t${tombo.latitude}\t`, - `${tombo.longitude}\t${'WGS84'}\t${'GPS'}\t${'Plantae'}\t${familiaNome}\t${generoNome}\t`, - `${especieNome}\t${menorNomeCientifico}\t${tombo.nome_cientifico}\t${autores}\t${tombo.taxon}`, - `\t${tombo.nomes_populares}\t\t${nomeTipo}\t${nomeIdentificador}\t${dataIdentificacao}`, - `\t${identificationQualifier}`, - ].join(''); + const campos = [ + 'PreservedSpecimen', 'Colecao', 'pt', dataAtualizacao, '02.032.297/0005-26', + 'UTFPR', 'Herbario da Universidade Tecnologica Federal do Parana – Campus Campo Mourao – HCF', + license, 'UTFPR', '{"barcode":""}', `Br:UTFPR:HCF:${tombo.hcf}`, + tombo.hcf, coletores, + tombo.numero_coleta, '', tombo.observacao, dataColeta, + tombo.data_coleta_ano, tombo.data_coleta_mes, tombo.data_coleta_dia, + vegetacao, 'América do Sul', paisNome, paisCodigo, paranaNome, + cidadeNome, tombo.altitude, tombo.altitude, '', '', tombo.latitude, + tombo.longitude, 'WGS84', 'GPS', 'Plantae', familiaNome, generoNome, + especieNome, menorNomeCientifico, tombo.nome_cientifico, autores, tombo.taxon, + tombo.nomes_populares, '', nomeTipo, nomeIdentificador, dataIdentificacao, + identificationQualifier, + ]; + let linha = csvLinha(campos); linha = linha.replace(/null|undefined/g, ''); @@ -330,9 +355,7 @@ export const obterModeloDarwinCore = async (request, response, next) => { const limit = request.query.limit > 1000 ? 1000 : request.query.limit || 1000; - const quantidadeTombos = await Tombo.count( - { distinct: true }, - ); + const quantidadeTombos = await Tombo.count({ col: 'hcf' }); const cabecalho = colunasComoLinhaUnica(); diff --git a/src/resources/darwincore/cabecalho.js b/src/resources/darwincore/cabecalho.js index 44eebe6c..7e7869a2 100644 --- a/src/resources/darwincore/cabecalho.js +++ b/src/resources/darwincore/cabecalho.js @@ -52,5 +52,5 @@ const colunas = [ export default colunas; export function colunasComoLinhaUnica() { - return `${colunas.join('\t')}\n`; + return `${colunas.join(',')}\n`; } From 190e595ab2db504ec1ced316551a6bceb62080d5 Mon Sep 17 00:00:00 2001 From: Lucas Dos Santos Vaz <52181258+luscas18@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:32:15 -0300 Subject: [PATCH 2/3] Corrigindo a ficha tombo com a listagem dos identificadores (#377) --- src/controllers/fichas-tombos-controller.js | 13 +++- src/controllers/tombos-controller.js | 81 ++++++++++++--------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/controllers/fichas-tombos-controller.js b/src/controllers/fichas-tombos-controller.js index 0addb97c..9a08b35b 100644 --- a/src/controllers/fichas-tombos-controller.js +++ b/src/controllers/fichas-tombos-controller.js @@ -235,9 +235,16 @@ export default function fichaTomboController(request, response, next) { const dataTombo = new Date(tombo.data_tombo); const romanoDataTombo = (`${dataTombo.getDate()}/${romanos[dataTombo.getMonth()]}/${dataTombo.getFullYear()}`); - const identificador = tombo.identificadores?.[0]?.nome - && tombo.identificadores?.[0]?.nome.toLowerCase() !== 'não-identificado' - ? tombo.identificadores?.[0]?.nome + const identificador = tombo.identificadores && tombo.identificadores.length > 0 + ? tombo.identificadores + .filter(ident => ident.nome && ident.nome.toLowerCase() !== 'não-identificado') + .sort((a, b) => { + const ordemA = a.tombos_identificadores?.ordem || 0; + const ordemB = b.tombos_identificadores?.ordem || 0; + return ordemA - ordemB; + }) + .map(ident => ident.nome) + .join('; ') : ''; const romanoDataIdentificacao = formataDataIdentificacao( diff --git a/src/controllers/tombos-controller.js b/src/controllers/tombos-controller.js index 671ac3fc..552831c6 100644 --- a/src/controllers/tombos-controller.js +++ b/src/controllers/tombos-controller.js @@ -681,48 +681,54 @@ export const desativar = (request, response, next) => { export const listagem = (request, response, next) => { const { pagina, limite, offset } = request.paginacao; const { - nome_cientifico: nomeCientifico, hcf, tipo, nome_popular: nomePopular, situacao, + nome_cientifico: nomeCientifico, + hcf, + tipo, + nome_popular: nomePopular, + situacao, + codigo_barra_foto, } = request.query; + let where = { rascunho: false, }; if (nomeCientifico) { - where = { - ...where, - nome_cientifico: { [Op.iLike]: `%${nomeCientifico}%` }, - }; + where.nome_cientifico = { [Op.iLike]: `%${nomeCientifico}%` }; } - if (hcf) { - where = { - ...where, - hcf, - }; + where.hcf = hcf; } - if (tipo) { - where = { - ...where, - tipo_id: tipo, - }; + where.tipo_id = tipo; } - if (nomePopular) { - where = { - ...where, - nomes_populares: { [Op.iLike]: `%${nomePopular}%` }, - }; + where.nomes_populares = { [Op.iLike]: `%${nomePopular}%` }; } - if (situacao) { - where = { - ...where, - situacao, - }; + where.situacao = situacao; + } + + let include = [ + { + model: Coletor, + attributes: ['id', 'nome'], + required: false, + }, + ]; + + if (codigo_barra_foto) { + include.push({ + model: TomboFoto, + where: { + codigo_barra: codigo_barra_foto, + }, + attributes: ['id', 'codigo_barra', 'tombo_hcf'], + required: true, + }); } - let retorno = { // eslint-disable-line + let retorno = { metadados: { total: 0, pagina, @@ -730,12 +736,17 @@ export const listagem = (request, response, next) => { }, tombos: [], }; + Promise.resolve() - .then(() => Tombo.count({ where })) + .then(() => Tombo.count({ + where, + include: codigo_barra_foto ? include : [], + distinct: true, + })) .then(total => { retorno.metadados.total = total; }) - .then(() => Tombo.findAndCountAll({ + .then(() => Tombo.findAll({ attributes: [ 'hcf', 'nomes_populares', @@ -744,21 +755,19 @@ export const listagem = (request, response, next) => { 'data_coleta_mes', 'data_coleta_ano', 'created_at', + 'coletor_id', + 'tipo_id', ], - include: { - model: Coletor, - attributes: ['id', 'nome'], - required: false, - }, + include, where, + subQuery: false, order: [['hcf', 'DESC']], limit: limite, offset, })) .then(listaTombos => { - retorno.tombos = listaTombos.rows; - response.status(codigos.LISTAGEM) - .json(retorno); + retorno.tombos = listaTombos; + response.status(codigos.LISTAGEM).json(retorno); }) .catch(next); }; From 69869f9edd6be2fa07e6575ee89b06e078691bea Mon Sep 17 00:00:00 2001 From: Moran <105233020+feliperm17@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:58:10 -0300 Subject: [PATCH 3/3] =?UTF-8?q?Core=C3=A7=C3=A3o=20campos:=20Family,=20Sta?= =?UTF-8?q?te=20or=20Prov.,=20Elevation=20e=20Notes=20(#378)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/splinker-controller.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/controllers/splinker-controller.js b/src/controllers/splinker-controller.js index 07e9e997..b4b3ad2c 100644 --- a/src/controllers/splinker-controller.js +++ b/src/controllers/splinker-controller.js @@ -45,6 +45,7 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { 'data_identificacao_ano', 'data_coleta_dia', 'observacao', + 'descricao', 'nomes_populares', 'numero_coleta', 'updated_at', @@ -138,7 +139,8 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { tombos.forEach(tombo => { const kingdom = tombo.familia?.reino?.nome || '\t'; - const family = tombo.familia?.nome || '\t'; + const familyName = tombo.familia?.nome; + const family = (familyName && familyName.toLowerCase() !== 'indeterminada') ? familyName : '\t'; const genus = tombo.genero?.nome || '\t'; const species = tombo.especy?.nome || '\t'; const scientificNameAuthor = tombo.especy?.autor?.nome || '\t'; @@ -156,7 +158,7 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { const collectorName = tombo.coletore?.nome || '\t'; const collectorNumber = tombo.numero_coleta || '\t'; const country = tombo.locais_coletum?.cidade?.estado?.paise?.nome || '\t'; - const stateOrProvince = tombo.locais_coletum?.cidade?.estado?.sigla || '\t'; + const stateOrProvince = tombo.locais_coletum?.cidade?.estado?.sigla?.trim() || '\t'; const city = tombo.locais_coletum?.cidade?.nome || '\t'; const locality = tombo.locais_coletum?.descricao || '\t'; const latitude = tombo.latitude @@ -165,7 +167,7 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { const longitude = tombo.longitude ? converteDecimalParaGrausMinutosSegundos(tombo.longitude, true, true) : '\t'; - const elevation = tombo.altitude + ' m' || '\t'; + const elevation = tombo.altitude ? `${tombo.altitude} m` : '\t'; const identificationDate = [ tombo.data_identificacao_ano, tombo.data_identificacao_mes?.toString().padStart(2, '0'), @@ -174,11 +176,11 @@ const obterModeloSPlinkerLotes = async (limit, offset, request, response) => { .filter(Boolean) .join('-'); const identifierName = tombo.identificadores - ? tombo.identificadores.map(i => i.nome).join(', ') + ? tombo.identificadores.map(i => i.nome).join(';') : '\t'; const notes = tombo.tombos_fotos?.length > 0 - ? `${tombo.tombos_fotos.map(foto => `[BARCODE=${foto.codigo_barra}]`).join(' , ')} ${tombo.observacao || '\t'}` - : tombo.observacao || '\t'; + ? `${tombo.tombos_fotos.map(foto => `[BARCODE=${foto.codigo_barra}]`).join(' , ')} ${tombo.descricao || '\t'}` + : tombo.descricao || '\t'; const linha = [ kingdom,