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
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
repos:
- repo: local
hooks:
- id: check-scrum-commit-msg
name: "Valida formato 'SCRUM-XX' no commit"
language: system
entry: bash -c 'if ! grep -qE "^SCRUM-[0-9]+" "$1"; then echo "Erro-> Commit deve começar com SCRUM-XX (ex-> SCRUM-81)"; exit 1; fi'
args: [".git/COMMIT_EDITMSG"]
stages: [commit-msg]
6 changes: 5 additions & 1 deletion stratify/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.quantum.stratify.config;


import com.quantum.stratify.config.jwt.JwtAuthenticationEntryPoint;
import com.quantum.stratify.config.jwt.JwtAuthorizationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand All @@ -17,6 +15,9 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import com.quantum.stratify.config.jwt.JwtAuthenticationEntryPoint;
import com.quantum.stratify.config.jwt.JwtAuthorizationFilter;


@EnableMethodSecurity
@EnableWebMvc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.quantum.stratify.entities;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "dim_status_historico")
public class StatusHistorico {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "id_user_story", nullable = false)
private UserStory userStory;

@Column(name = "data_retrabalho")
private LocalDateTime dataRetrabalho;

@ManyToOne
@JoinColumn(name = "id_status", nullable = false)
private Status status;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,44 @@
public interface FatoEficienciaUserStoryRepository extends JpaRepository<FatoEficienciaUserStory, Long> {
FatoEficienciaUserStory findByUserStoryId(Long userStoryId);

@Query("SELECT DISTINCT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio)) " +
@Query("SELECT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio), count(sh)) " +
"FROM FatoEficienciaUserStory fe " +
"LEFT JOIN fe.userStory us " +
"JOIN fe.userStory us " +
"LEFT JOIN StatusHistorico sh "+
"ON sh.userStory.id = us.id " +
"WHERE fe.projeto.id = :projetoId " +
"GROUP BY us.id, us.assunto ")
List<TempoMedioPorProjetoDTO> findByProjetoId(@Param("projetoId") Long projetoId);

@Query("SELECT DISTINCT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio)) " +
@Query("SELECT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio), count(sh)) " +
"FROM FatoEficienciaUserStory fe " +
"LEFT JOIN fe.userStory us " +
"JOIN fe.userStory us " +
"LEFT JOIN StatusHistorico sh "+
"ON sh.userStory.id = us.id " +
"GROUP BY us.id, us.assunto" )
List<TempoMedioPorProjetoDTO> getAll();

@Query("SELECT DISTINCT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio)) " +
@Query("SELECT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio), count(sh)) " +
"FROM FatoEficienciaUserStory fe " +
"LEFT JOIN fe.userStory us " +
"JOIN fe.userStory us " +
"LEFT JOIN StatusHistorico sh "+
"ON sh.userStory.id = us.id " +
"WHERE fe.projeto.id = :projetoId AND fe.usuario.id = :usuarioId " +
"group by us.id, us.assunto")
List<TempoMedioPorProjetoDTO> findByProjetoIdAndUsuarioId(@Param("projetoId") Long projetoId, @Param("usuarioId") Long usuarioId);

@Query("SELECT DISTINCT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio)) " +
@Query("SELECT new com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO( " +
"us.id, us.assunto, AVG(fe.tempoMedio), count(sh)) " +
"FROM FatoEficienciaUserStory fe " +
"LEFT JOIN fe.userStory us " +
"JOIN fe.userStory us " +
"LEFT JOIN StatusHistorico sh "+
"ON sh.userStory.id = us.id " +
"WHERE fe.usuario.id = :usuarioId " +
"group by us.id, us.assunto")
List<TempoMedioPorProjetoDTO> findByUsuarioId(@Param("usuarioId")Long usuarioId);


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.quantum.stratify.repositories;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.quantum.stratify.entities.StatusHistorico;

import java.util.List;

@Repository
public interface StatusHistoricoRepository extends JpaRepository<StatusHistorico, Long> {

}

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;
import com.quantum.stratify.web.dtos.UsuarioDTO;
Expand Down Expand Up @@ -40,4 +42,8 @@ List<UsuarioDTO> findUsuarioByProjetoAndGestor(
ORDER BY u.nome
""")
List<UsuarioPorRoleDTO> findByRole(@Param("role") Role role);
@Transactional
@Modifying
@Query("UPDATE Usuario u SET u.gestor = null WHERE u.gestor.id = ?1")
void setRolesGestorToNull(Long idUsuario);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.quantum.stratify.services;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;

import com.opencsv.CSVWriter;
import com.quantum.stratify.web.dtos.PercentualStatusUsuarioDTO;
import com.quantum.stratify.web.dtos.QuantidadeCardsPorSprintDTO;
import com.quantum.stratify.web.dtos.QuantidadeCardsPorTagDTO;
import com.quantum.stratify.web.dtos.ResponseQuantidadeCardsByPeriodo;
import com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class CsvDownloadService {

@Autowired
private UserStoryService userStoryService;

@Autowired
private FatoUserStoryTemporaisService fatoUserStoryTemporaisService;

@Autowired
private UserStoryTagService userStoryTagService;

@Autowired
private UserStoryStatusService userStoryStatusService;

@Autowired
private FatoEficienciaUserStoryService fatoEficienciaUserStoryService;
public InputStreamResource generateCsv(Long projectId, Long userId, String exportType) {
List<String[]> csvData;

switch (exportType.toLowerCase()) {
case "cardsporsprint": {
List<QuantidadeCardsPorSprintDTO> lista = userStoryService.getQuantidadeUserStoriesBySprint(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Sprint", "Quantidade"});
for (QuantidadeCardsPorSprintDTO dto : lista) {
csvData.add(new String[]{dto.getSprint(), String.valueOf(dto.getQuantidade())});
}
log.info("ExportType=cardsporsprint → Encontrados {} registros para projeto={} usuário={}", lista.size(), projectId, userId);
break;
}
case "cardsporperiodo": {
List<ResponseQuantidadeCardsByPeriodo> lista = fatoUserStoryTemporaisService.getUserStoriesByPeriodoAndUser(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Período", "Criadas", "Finalizadas"});
for (ResponseQuantidadeCardsByPeriodo dto : lista) {
csvData.add(new String[]{
dto.periodo(),
String.valueOf(dto.quantidadeCriadas()),
String.valueOf(dto.quantidadeFinalizadas())
});
}
break;
}
case "cardsporetiqueta": {
List<QuantidadeCardsPorTagDTO> lista = userStoryTagService.getQuantidadeUserStoriesByTag(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Tag", "Quantidade"});
for (QuantidadeCardsPorTagDTO dto : lista) {
csvData.add(new String[]{dto.getNomeTag(), String.valueOf(dto.getQuantidade())});
}
break;
}
case "cardsporstatus": {
List<PercentualStatusUsuarioDTO> lista = userStoryStatusService.getPercentualUserStoriesPorStatus(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Status", "Percentual"});
for (PercentualStatusUsuarioDTO dto : lista) {
csvData.add(new String[]{dto.getNomeStatus(), String.valueOf(dto.getPercentual())});
}
break;
}
case "tempomedio": {
List<TempoMedioPorProjetoDTO> lista = fatoEficienciaUserStoryService.getTempoMedioFiltrado(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"ID User Story", "Descrição", "Tempo Médio", "Quantidade Retrabalho"});
for (TempoMedioPorProjetoDTO dto : lista) {
csvData.add(new String[]{
String.valueOf(dto.idUserStory()),
dto.descricao(),
String.valueOf(dto.tempoMedio()),
String.valueOf(dto.quantidadeRetrabalhos())
});
}
break;
}
default:
throw new IllegalArgumentException("Tipo de exportação inválido: " + exportType);
}

ByteArrayOutputStream out = new ByteArrayOutputStream();
try (CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
for (String[] line : csvData) {
writer.writeNext(line);
}
} catch (IOException e) {
throw new RuntimeException("Erro ao gerar CSV", e);
}

return new InputStreamResource(new ByteArrayInputStream(out.toByteArray()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ public Usuario alterarRole(Long idUsuario, Role novaRole) {
if (novaRole == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Role inválida.");
}


if(usuario.getRole() == Role.GESTOR && novaRole != Role.GESTOR ) {
try{
usuarioRepository.setRolesGestorToNull(idUsuario);

}catch (Exception e) {
System.out.println("Erro no update...........");
}
}
usuario.setRole(novaRole);
return usuarioRepository.save(usuario);
}
Expand Down Expand Up @@ -187,6 +197,7 @@ public List<UsuarioInfoDTO> listarUsuariosInfo() {
return usuarioRepository.findAll().stream()
.map(u -> new UsuarioInfoDTO(
u.getId(),
u.getNome(),
u.getEmail(),
u.getGestor() != null ? u.getGestor().getNome() : null, // aqui
u.getRole(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.quantum.stratify.web.controllers;

import java.io.IOException;

import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.quantum.stratify.services.CsvDownloadService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@RestController
@RequestMapping("/download")
@CrossOrigin("*")
@Slf4j
@Tag(name = "CSV Download", description = "Exportação de relatórios em formato CSV")
public class CsvDownloadController {

private final CsvDownloadService csvDownloadService;

@Operation(summary = "Exporta dados filtrados em formato CSV")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Arquivo CSV gerado com sucesso"),
@ApiResponse(responseCode = "400", description = "Parâmetros inválidos"),
@ApiResponse(responseCode = "404", description = "Dados não encontrados"),
@ApiResponse(responseCode = "500", description = "Erro interno ao gerar o CSV")
})
@GetMapping("/csv")
public ResponseEntity<InputStreamResource> downloadCsv(
@Parameter(description = "Tipo de exportação (ex: cardsporsprint, cardsporperiodo, etc.)", required = true)
@RequestParam String exportType,

@Parameter(description = "ID do projeto (opcional)")
@RequestParam(required = false) Long projectId,

@Parameter(description = "ID do usuário (opcional)")
@RequestParam(required = false) Long userId
) throws IOException {

String filename = exportType + ".csv";
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
headers.add(HttpHeaders.CONTENT_TYPE, "text/csv");

InputStreamResource resource = csvDownloadService.generateCsv(projectId, userId, exportType);
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.quantum.stratify.web.dtos;

public record TempoMedioPorProjetoDTO(Long idUserStory, String descricao, Long tempoMedio) {
public TempoMedioPorProjetoDTO(Long idUserStory, String descricao, Double tempoMedio) {
this(idUserStory, descricao, tempoMedio != null ? tempoMedio.longValue() : 0L);
public record TempoMedioPorProjetoDTO(Long idUserStory, String descricao, Long tempoMedio, Long quantidadeRetrabalhos) {
public TempoMedioPorProjetoDTO(Long idUserStory, String descricao, Double tempoMedio, Long quantidadeRetrabalhos) {
this(idUserStory, descricao, tempoMedio != null ? tempoMedio.longValue() : 0L, quantidadeRetrabalhos);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
@AllArgsConstructor
public class UsuarioInfoDTO {
private Long id;
private String nome;
private String email;
private String gestorNome;
private Role role;
Expand Down
2 changes: 1 addition & 1 deletion stratify/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spring.jpa.properties.hibernate.generate_statistics=true
spring.datasource.hikari.minimum-idle=1
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.idle-timeout=100
spring.liquibase.change-log=src/main/resources/db/changelog/V2.0.sql
spring.liquibase.change-log=src/main/resources/db/changelog/V3.0.sql
spring.liquibase.enabled=true
spring.liquibase.default-schema=public

Expand Down
Loading