Skip to content
Open
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
49 changes: 49 additions & 0 deletions backend/src/controllers/reportController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextFunction, Request, Response } from 'express';
import ReportService from '../services/reportService';
import { HttpCode } from "../erros/erro.config";
import { InvalidDateRange } from '../erros/ReportErros';


export default class ReportController{

public static async getMonthlySpongeReport(req: Request, res: Response, next: NextFunction): Promise<any>{
const { startDate, endDate } = req.body;

let sDate: Date;
let eDate: Date;

try {
if (startDate) {
const parsedStart = new Date(startDate);
if (isNaN(parsedStart.getTime())) throw new Error("startDate inválida");
// Ajusta para primeiro dia do mês
sDate = new Date(parsedStart.getFullYear(), parsedStart.getMonth(), 1);
} else {
sDate = new Date("2025-01-01"); // data mínima possível
}

if (endDate) {
const parsedEnd = new Date(endDate);
if (isNaN(parsedEnd.getTime())) throw new Error("endDate inválida");
// Ajusta para primeiro dia do mês seguinte (fim exclusivo)
eDate = new Date(parsedEnd.getFullYear(), parsedEnd.getMonth() + 1, 1);
} else {
const now = new Date();
eDate = new Date(now.getFullYear(), now.getMonth() + 1, 1);
}

if(startDate > endDate){
throw new InvalidDateRange();
}

const report = await ReportService.getMonthlySpongeReport(
sDate,
eDate
);

res.status(HttpCode.OK).json(report);
}catch(e: any){
next(e);
}
}
}
21 changes: 21 additions & 0 deletions backend/src/erros/ReportErros.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { HttpCode, HttpError } from "./erro.config";

/**
* @extends HttpError
* @description Erro de relatório não encontrado (nenhum dado retornado)
*/
export class ReportNotFound extends HttpError {
constructor(message: string = 'Nenhum dado encontrado para o período informado') {
super({ status: HttpCode.NOT_FOUND, message });
}
}

/**
* @extends HttpError
* @description Erro de validação de data (startDate maior que endDate)
*/
export class InvalidDateRange extends HttpError {
constructor(message: string = 'A data de início não pode ser maior que a data de fim.') {
super({ status: HttpCode.BAD_REQUEST, message });
}
}
2 changes: 2 additions & 0 deletions backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import path from 'path';
import ErrorHandler from './middlewares/errorHandler';
import metricsRouter from './routes/metricsRoutes';
import registrationRouter from './routes/registrationRoutes';
import reportRouter from './routes/reportRoutes';

/**
* Define endpoints mapeados
Expand Down Expand Up @@ -41,6 +42,7 @@ export default (app: Express): void => {
.use(eventRouter)
.use(metricsRouter)
.use(registrationRouter)
.use(reportRouter)


// Rota padrão
Expand Down
28 changes: 28 additions & 0 deletions backend/src/routes/reportRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Router } from 'express';
import ReportController from '../controllers/reportController';
import AuthMiddleware from '../middlewares/authMiddleware';

const reportRouter = Router();
/**
* @route POST /reports/monthly-sponge
* @description Retorna o total de esponjas coletadas por mês, agrupando os depósitos aprovados
* por ano e mês com base na data de criação (`created_at`).
*
* Os resultados serão ordenados por **ano** e **mês** em ordem **ascendente**,
* ou seja, do mês mais antigo para o mais recente.
*
* **Parâmetros Recebidos** | **Resultado**
* --------------------------------|-------------------------------------
* Nenhum | Retorna todo o histórico de depósitos aprovados.
* `startDate` (formato `YYYY-MM`) | Retorna os dados desde o mês e ano informados até o presente momento.
* `startDate` e `endDate` (formato `YYYY-MM`) | Retorna o intervalo entre os meses e anos informados.
*
* @param {string} [startDate] Data inicial no formato `YYYY-MM` (opcional)
* @param {string} [endDate] Data final no formato `YYYY-MM` (opcional)
*
* @returns {Array<{ year: number, month: number, total_sponges_collected: number }>}
*/
reportRouter.post('/reports/monthly-sponge', AuthMiddleware.ensureAdmin, ReportController.getMonthlySpongeReport);


export default reportRouter;
2 changes: 1 addition & 1 deletion backend/src/routes/userRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RequestHandler, Router } from 'express';
import { Router } from 'express';
import UserController from '../controllers/userController';
import AuthMiddleware from '../middlewares/authMiddleware';

Expand Down
92 changes: 92 additions & 0 deletions backend/src/services/reportService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import DatabaseConnection from '../database/connection/DatabaseConnection';
import { ReportNotFound } from '../erros/ReportErros';

const knex = DatabaseConnection.getInstance();


export default class ReportService {

/**
* @description Retorna o total de esponjas coletadas por mês, agrupando os depósitos aprovados
* por ano e mês com base na data de criação (`created_at`).
*
* Os resultados serão ordenados por **ano** e **mês** em ordem **ascendente**,
* ou seja, do mês mais antigo para o mais recente.
*
* **Parâmetros Recebidos** | **Resultado**
* --------------------------------|-------------------------------------
* Nenhum | Retorna todo o histórico de depósitos aprovados.
* `startDate` (formato `YYYY-MM`) | Retorna os dados desde o mês e ano informados até o presente momento.
* `startDate` e `endDate` (formato `YYYY-MM`) | Retorna o intervalo entre os meses e anos informados.
*
* @param {string} [startDate] Data inicial no formato `YYYY-MM` (opcional)
* @param {string} [endDate] Data final no formato `YYYY-MM` (opcional)
*
* @returns {Promise<Array<{ year: number, month: number, total_sponges_collected: number }>>}
*/
public static async getMonthlySpongeReport(
startDate: Date,
endDate: Date
): Promise<Array<{ year: number, month: number, total_sponges_collected: number }>>
{
const rows = await knex('Deposit')
.select(
knex.raw('EXTRACT(YEAR FROM created_at) AS year'),
knex.raw('EXTRACT(MONTH FROM created_at) AS month'),
)
.sum('amountSponges AS total_sponges_collected')
.where('status', 'APROVADO')
.andWhere('created_at', '>=', startDate)
.andWhere('created_at', '<', endDate)
.groupByRaw('year, month')
.orderByRaw('year, month');

if (!rows || rows.length === 0) {
throw new ReportNotFound();
}

const filledResults = [];

// Início do primeiro mês
let current = new Date(startDate.getFullYear(), startDate.getMonth(), 1);

for (const row of rows) {
const rowYear = Number(row.year);
const rowMonth = Number(row.month);

// Data do mês retornado
const rowDate = new Date(rowYear, rowMonth - 1, 1);

// Preencher meses faltando até o mês atual da row
while (current < rowDate) {
filledResults.push({
year: current.getFullYear(),
month: current.getMonth() + 1,
total_sponges_collected: 0,
});
current.setMonth(current.getMonth() + 1);
}

// Adiciona o mês retornado do banco
filledResults.push({
year: rowYear,
month: rowMonth,
total_sponges_collected: Number(row.total_sponges_collected),
});

// Move para o próximo mês
current.setMonth(current.getMonth() + 1);
}

// Preenche até endDate, se necessário
while (current < endDate) {
filledResults.push({
year: current.getFullYear(),
month: current.getMonth() + 1,
total_sponges_collected: 0,
});
current.setMonth(current.getMonth() + 1);
}
return filledResults;
}
}