Skip to content

Commit 01bd477

Browse files
authored
Feat: criando rota de busca de tombos por periodo (#391)
1 parent 8b88666 commit 01bd477

2 files changed

Lines changed: 247 additions & 6 deletions

File tree

src/controllers/tombos-controller.js

Lines changed: 194 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export const cadastro = (request, response, next) => {
157157
}
158158
return undefined;
159159
})
160-
// //////////////CRIA COLECOES ANEXAS///////////
160+
// //////////////CRIA COLECOES ANEXAS///////////
161161
.then(() => {
162162
if (colecoesAnexas) {
163163
const object = pick(colecoesAnexas, ['tipo', 'observacoes']);
@@ -174,7 +174,7 @@ export const cadastro = (request, response, next) => {
174174
}
175175
return undefined;
176176
})
177-
// ///////// VALIDA A TAXONOMIA E A INSERE NO NOME CIENTIFICO //////////
177+
// ///////// VALIDA A TAXONOMIA E A INSERE NO NOME CIENTIFICO //////////
178178
.then(() => {
179179
if (taxonomia && taxonomia.familia_id) {
180180
return Familia.findOne({
@@ -296,7 +296,7 @@ export const cadastro = (request, response, next) => {
296296
}
297297
return undefined;
298298
})
299-
// /////////// CADASTRA TOMBO /////////////
299+
// /////////// CADASTRA TOMBO /////////////
300300
.then(() => {
301301
let jsonTombo = {
302302
numero_coleta: principal.numero_coleta,
@@ -373,7 +373,7 @@ export const cadastro = (request, response, next) => {
373373
}
374374
return Tombo.create(jsonTombo, { transaction });
375375
})
376-
// //////////// CADASTRA A ALTERACAO ///////////
376+
// //////////// CADASTRA A ALTERACAO ///////////
377377
.then(tombo => {
378378
if (!tombo) {
379379
throw new BadRequestExeption(408);
@@ -419,7 +419,7 @@ export const cadastro = (request, response, next) => {
419419
return alteracaoTomboCriado;
420420
});
421421
})
422-
// /////////////// CADASTRA O INDETIFICADOR ///////////////
422+
// /////////////// CADASTRA O INDETIFICADOR ///////////////
423423
.then(alteracaoTomboCriado => {
424424
if (!alteracaoTomboCriado) {
425425
throw new BadRequestExeption(409);
@@ -1782,4 +1782,193 @@ export const verificarCoordenada = async (request, response, next) => {
17821782
}
17831783
};
17841784

1785+
export const relatorioPorPeriodo = async (request, response, next) => {
1786+
try {
1787+
const { data_inicio, data_fim, granularidade } = request.query;
1788+
1789+
if (!data_inicio || !data_fim || !granularidade) {
1790+
return response.status(400).json({
1791+
error: {
1792+
message: 'Parâmetros obrigatórios: data_inicio, data_fim, granularidade',
1793+
},
1794+
});
1795+
}
1796+
1797+
// Validar granularidade
1798+
if (!['dia', 'semana', 'mes', 'ano'].includes(granularidade)) {
1799+
return response.status(400).json({
1800+
error: {
1801+
message: 'Granularidade inválida. Use: dia, semana, mes ou ano',
1802+
},
1803+
});
1804+
}
1805+
1806+
const dataInicio = new Date(data_inicio);
1807+
const dataFim = new Date(data_fim);
1808+
1809+
if (isNaN(dataInicio.getTime()) || isNaN(dataFim.getTime())) {
1810+
return response.status(400).json({
1811+
error: {
1812+
message: 'Datas inválidas. Use o formato YYYY-MM-DD',
1813+
},
1814+
});
1815+
}
1816+
1817+
if (dataInicio > dataFim) {
1818+
return response.status(400).json({
1819+
error: {
1820+
message: 'Data de início deve ser menor ou igual à data de fim',
1821+
},
1822+
});
1823+
}
1824+
1825+
// Calcular diferenças para validar granularidade
1826+
const diffMs = dataFim - dataInicio;
1827+
const diffDias = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
1828+
const diffSemanas = Math.ceil(diffDias / 7);
1829+
const diffMeses = Math.ceil((dataFim.getFullYear() - dataInicio.getFullYear()) * 12 + (dataFim.getMonth() - dataInicio.getMonth()));
1830+
const diffAnos = dataFim.getFullYear() - dataInicio.getFullYear();
1831+
1832+
// Validar granularidade baseada no período
1833+
let granularidadePermitida = 'ano';
1834+
if (diffDias <= 30) {
1835+
granularidadePermitida = 'dia';
1836+
} else if (diffSemanas <= 30) {
1837+
granularidadePermitida = 'semana';
1838+
} else if (diffMeses <= 30) {
1839+
granularidadePermitida = 'mes';
1840+
} else {
1841+
granularidadePermitida = 'ano';
1842+
}
1843+
1844+
// Mapear granularidades para verificação hierárquica
1845+
const granularidadeHierarquia = { dia: 0, semana: 1, mes: 2, ano: 3 };
1846+
const granularidadeSolicitada = granularidadeHierarquia[granularidade];
1847+
const granularidadeMaximaPermitida = granularidadeHierarquia[granularidadePermitida];
1848+
1849+
if (granularidadeSolicitada < granularidadeMaximaPermitida) {
1850+
return response.status(400).json({
1851+
error: {
1852+
message: `Período muito grande para granularidade '${granularidade}'. Máximo: ${diffDias} dias. Use granularidade '${granularidadePermitida}' ou maior.`,
1853+
restricoes: {
1854+
difDias: diffDias,
1855+
difSemanas: diffSemanas,
1856+
difMeses: diffMeses,
1857+
difAnos: diffAnos,
1858+
granularidadePermitida: granularidadePermitida,
1859+
},
1860+
},
1861+
});
1862+
}
1863+
1864+
let query = '';
1865+
1866+
if (granularidade === 'dia') {
1867+
query = `
1868+
WITH date_series AS (
1869+
SELECT generate_series($1::date, $2::date, '1 day'::interval)::date AS data_p
1870+
),
1871+
counts AS (
1872+
SELECT
1873+
TO_DATE(CONCAT(data_coleta_ano, '-', LPAD(data_coleta_mes::text, 2, '0'), '-', LPAD(data_coleta_dia::text, 2, '0')), 'YYYY-MM-DD') AS data_c,
1874+
COUNT(*) AS qtd
1875+
FROM tombos
1876+
WHERE rascunho = false
1877+
AND data_coleta_ano IS NOT NULL
1878+
AND data_coleta_mes IS NOT NULL
1879+
AND data_coleta_dia IS NOT NULL
1880+
GROUP BY 1
1881+
)
1882+
SELECT
1883+
TO_CHAR(ds.data_p, 'DD/MM/YYYY') AS periodo,
1884+
COALESCE(c.qtd, 0) AS quantidade
1885+
FROM date_series ds
1886+
LEFT JOIN counts c ON ds.data_p = c.data_c
1887+
ORDER BY ds.data_p ASC
1888+
`;
1889+
} else if (granularidade === 'semana') {
1890+
query = `
1891+
WITH date_series AS (
1892+
SELECT generate_series(date_trunc('week', $1::date), date_trunc('week', $2::date), '1 week'::interval)::date AS data_p
1893+
),
1894+
counts AS (
1895+
SELECT
1896+
date_trunc('week', TO_DATE(CONCAT(data_coleta_ano, '-', LPAD(data_coleta_mes::text, 2, '0'), '-', LPAD(data_coleta_dia::text, 2, '0')), 'YYYY-MM-DD'))::date AS data_c,
1897+
COUNT(*) AS qtd
1898+
FROM tombos
1899+
WHERE rascunho = false
1900+
AND data_coleta_ano IS NOT NULL
1901+
AND data_coleta_mes IS NOT NULL
1902+
AND data_coleta_dia IS NOT NULL
1903+
GROUP BY 1
1904+
)
1905+
SELECT
1906+
'Semana ' || LPAD((ROW_NUMBER() OVER(ORDER BY ds.data_p))::text, 2, '0') AS periodo,
1907+
COALESCE(c.qtd, 0) AS quantidade
1908+
FROM date_series ds
1909+
LEFT JOIN counts c ON ds.data_p = c.data_c
1910+
ORDER BY ds.data_p ASC
1911+
`;
1912+
} else if (granularidade === 'mes') {
1913+
query = `
1914+
WITH date_series AS (
1915+
SELECT generate_series(date_trunc('month', $1::date), date_trunc('month', $2::date), '1 month'::interval)::date AS data_p
1916+
),
1917+
counts AS (
1918+
SELECT
1919+
date_trunc('month', TO_DATE(CONCAT(t.data_coleta_ano, '-', LPAD(t.data_coleta_mes::text, 2, '0'), '-01'), 'YYYY-MM-DD'))::date AS data_c,
1920+
COUNT(*) AS qtd
1921+
FROM tombos t
1922+
WHERE t.rascunho = false
1923+
AND t.data_coleta_ano IS NOT NULL
1924+
AND t.data_coleta_mes IS NOT NULL
1925+
GROUP BY 1
1926+
)
1927+
SELECT
1928+
TO_CHAR(ds.data_p, 'MM/YYYY') AS periodo,
1929+
COALESCE(c.qtd, 0) AS quantidade
1930+
FROM date_series ds
1931+
LEFT JOIN counts c ON ds.data_p = c.data_c
1932+
ORDER BY ds.data_p ASC
1933+
`;
1934+
} else if (granularidade === 'ano') {
1935+
query = `
1936+
WITH date_series AS (
1937+
SELECT generate_series(date_trunc('year', $1::date), date_trunc('year', $2::date), '1 year'::interval)::date AS data_p
1938+
),
1939+
counts AS (
1940+
SELECT
1941+
data_coleta_ano AS ano_c,
1942+
COUNT(*) AS qtd
1943+
FROM tombos t
1944+
WHERE t.rascunho = false
1945+
AND t.data_coleta_ano IS NOT NULL
1946+
GROUP BY 1
1947+
)
1948+
SELECT
1949+
TO_CHAR(ds.data_p, 'YYYY') AS periodo,
1950+
COALESCE(c.qtd, 0) AS quantidade
1951+
FROM date_series ds
1952+
LEFT JOIN counts c ON EXTRACT(YEAR FROM ds.data_p) = c.ano_c
1953+
ORDER BY ds.data_p ASC
1954+
`;
1955+
}
1956+
1957+
const resultado = await sequelize.query(query, {
1958+
bind: [data_inicio, data_fim],
1959+
type: models.Sequelize.QueryTypes.SELECT,
1960+
});
1961+
1962+
// Mapear resultados para o formato esperado
1963+
const dados = resultado.map(item => ({
1964+
periodo: item.periodo,
1965+
quantidade: parseInt(item.quantidade, 10),
1966+
}));
1967+
1968+
return response.status(200).json(dados);
1969+
} catch (err) {
1970+
return next(err);
1971+
}
1972+
};
1973+
17851974
export default {};

src/routes/tombos.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
alteracao, getNumeroColetor, getUltimoNumeroTombo, getCodigoBarraTombo,
1010
editarCodigoBarra, getUltimoNumeroCodigoBarras, postCodigoBarraTombo,
1111
verificarCoordenada, getUltimoCodigoBarra, deletarCodigoBarras, listagemTombosPorIdentificador,
12+
relatorioPorPeriodo,
1213
} from '../controllers/tombos-controller';
1314
import exportarTombosController from '../controllers/tombos-exportacoes-controller';
1415
import criaJsonMiddleware from '../middlewares/json-middleware';
@@ -20,7 +21,6 @@ import coletorCadastro from '../validators/coletor-cadastro';
2021
import cadastrarTipoEsquema from '../validators/tipo-cadastro';
2122
import cadastrarTomboEsquema from '../validators/tombo-cadastro';
2223
import listagemTombo from '../validators/tombo-listagem';
23-
2424
/**
2525
* @swagger
2626
* tags:
@@ -73,6 +73,58 @@ export default app => {
7373
]);
7474

7575
/**
76+
* @swagger
77+
* /tombos/relatorio-periodo:
78+
* get:
79+
* summary: Gera relatório de tombos por período com granularidade configurável
80+
* tags: [Tombos]
81+
* parameters:
82+
* - in: query
83+
* name: data_inicio
84+
* required: true
85+
* schema:
86+
* type: string
87+
* format: date
88+
* description: Data de início do período (formato YYYY-MM-DD)
89+
* - in: query
90+
* name: data_fim
91+
* required: true
92+
* schema:
93+
* type: string
94+
* format: date
95+
* description: Data de fim do período (formato YYYY-MM-DD)
96+
* - in: query
97+
* name: granularidade
98+
* required: true
99+
* schema:
100+
* type: string
101+
* enum: [dia, semana, mes, ano]
102+
* description: Granularidade do agrupamento dos dados
103+
* responses:
104+
* 200:
105+
* description: Relatório gerado com sucesso
106+
* content:
107+
* application/json:
108+
* schema:
109+
* type: array
110+
* items:
111+
* type: object
112+
* properties:
113+
* periodo:
114+
* type: string
115+
* quantidade:
116+
* type: integer
117+
* '400':
118+
* description: Parâmetros inválidos
119+
* '500':
120+
* $ref: '#/components/responses/InternalServerError'
121+
*/
122+
app.route('/tombos/relatorio-periodo')
123+
.get([
124+
relatorioPorPeriodo,
125+
]);
126+
127+
/**
76128
* @swagger
77129
* /tombos/numeroColetor/{idColetor}:
78130
* get:

0 commit comments

Comments
 (0)