diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..3146b66
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -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]
\ No newline at end of file
diff --git a/stratify/pom.xml b/stratify/pom.xml
index bc2a1b6..01a4ffe 100644
--- a/stratify/pom.xml
+++ b/stratify/pom.xml
@@ -135,7 +135,11 @@
org.springframework.boot
spring-boot-starter-validation
-
+
+ com.opencsv
+ opencsv
+ 5.7.1
+
org.springframework.boot
spring-boot-starter-mail
diff --git a/stratify/src/main/java/com/quantum/stratify/config/SpringSecurityConfig.java b/stratify/src/main/java/com/quantum/stratify/config/SpringSecurityConfig.java
index 341c43b..397e03d 100644
--- a/stratify/src/main/java/com/quantum/stratify/config/SpringSecurityConfig.java
+++ b/stratify/src/main/java/com/quantum/stratify/config/SpringSecurityConfig.java
@@ -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;
@@ -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
diff --git a/stratify/src/main/java/com/quantum/stratify/entities/StatusHistorico.java b/stratify/src/main/java/com/quantum/stratify/entities/StatusHistorico.java
new file mode 100644
index 0000000..cbb8626
--- /dev/null
+++ b/stratify/src/main/java/com/quantum/stratify/entities/StatusHistorico.java
@@ -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;
+}
\ No newline at end of file
diff --git a/stratify/src/main/java/com/quantum/stratify/repositories/FatoEficienciaUserStoryRepository.java b/stratify/src/main/java/com/quantum/stratify/repositories/FatoEficienciaUserStoryRepository.java
index 75681d2..7d01704 100644
--- a/stratify/src/main/java/com/quantum/stratify/repositories/FatoEficienciaUserStoryRepository.java
+++ b/stratify/src/main/java/com/quantum/stratify/repositories/FatoEficienciaUserStoryRepository.java
@@ -14,35 +14,44 @@
public interface FatoEficienciaUserStoryRepository extends JpaRepository {
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 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 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 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 findByUsuarioId(@Param("usuarioId")Long usuarioId);
+
}
\ No newline at end of file
diff --git a/stratify/src/main/java/com/quantum/stratify/repositories/StatusHistoricoRepository.java b/stratify/src/main/java/com/quantum/stratify/repositories/StatusHistoricoRepository.java
new file mode 100644
index 0000000..2d54d43
--- /dev/null
+++ b/stratify/src/main/java/com/quantum/stratify/repositories/StatusHistoricoRepository.java
@@ -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 {
+
+}
+
diff --git a/stratify/src/main/java/com/quantum/stratify/repositories/UsuarioRepository.java b/stratify/src/main/java/com/quantum/stratify/repositories/UsuarioRepository.java
index 2164292..edc0565 100644
--- a/stratify/src/main/java/com/quantum/stratify/repositories/UsuarioRepository.java
+++ b/stratify/src/main/java/com/quantum/stratify/repositories/UsuarioRepository.java
@@ -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;
@@ -40,4 +42,8 @@ List findUsuarioByProjetoAndGestor(
ORDER BY u.nome
""")
List findByRole(@Param("role") Role role);
+ @Transactional
+ @Modifying
+ @Query("UPDATE Usuario u SET u.gestor = null WHERE u.gestor.id = ?1")
+ void setRolesGestorToNull(Long idUsuario);
}
diff --git a/stratify/src/main/java/com/quantum/stratify/services/CsvDownloadService.java b/stratify/src/main/java/com/quantum/stratify/services/CsvDownloadService.java
new file mode 100644
index 0000000..8294997
--- /dev/null
+++ b/stratify/src/main/java/com/quantum/stratify/services/CsvDownloadService.java
@@ -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 csvData;
+
+ switch (exportType.toLowerCase()) {
+ case "cardsporsprint": {
+ List 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 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 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 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 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()));
+ }
+}
diff --git a/stratify/src/main/java/com/quantum/stratify/services/UsuarioService.java b/stratify/src/main/java/com/quantum/stratify/services/UsuarioService.java
index 8bd9430..d743f12 100644
--- a/stratify/src/main/java/com/quantum/stratify/services/UsuarioService.java
+++ b/stratify/src/main/java/com/quantum/stratify/services/UsuarioService.java
@@ -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);
}
@@ -187,6 +197,7 @@ public List 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(),
diff --git a/stratify/src/main/java/com/quantum/stratify/web/controllers/CsvDownloadController.java b/stratify/src/main/java/com/quantum/stratify/web/controllers/CsvDownloadController.java
new file mode 100644
index 0000000..676a3ed
--- /dev/null
+++ b/stratify/src/main/java/com/quantum/stratify/web/controllers/CsvDownloadController.java
@@ -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 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);
+ }
+}
diff --git a/stratify/src/main/java/com/quantum/stratify/web/dtos/TempoMedioPorProjetoDTO.java b/stratify/src/main/java/com/quantum/stratify/web/dtos/TempoMedioPorProjetoDTO.java
index df70ca9..0e691ff 100644
--- a/stratify/src/main/java/com/quantum/stratify/web/dtos/TempoMedioPorProjetoDTO.java
+++ b/stratify/src/main/java/com/quantum/stratify/web/dtos/TempoMedioPorProjetoDTO.java
@@ -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);
}
}
diff --git a/stratify/src/main/java/com/quantum/stratify/web/dtos/UsuarioInfoDTO.java b/stratify/src/main/java/com/quantum/stratify/web/dtos/UsuarioInfoDTO.java
index f91d005..5a055b1 100644
--- a/stratify/src/main/java/com/quantum/stratify/web/dtos/UsuarioInfoDTO.java
+++ b/stratify/src/main/java/com/quantum/stratify/web/dtos/UsuarioInfoDTO.java
@@ -10,6 +10,7 @@
@AllArgsConstructor
public class UsuarioInfoDTO {
private Long id;
+ private String nome;
private String email;
private String gestorNome;
private Role role;
diff --git a/stratify/src/main/resources/application.properties b/stratify/src/main/resources/application.properties
index 73f63b5..39df7aa 100644
--- a/stratify/src/main/resources/application.properties
+++ b/stratify/src/main/resources/application.properties
@@ -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
diff --git a/stratify/src/main/resources/db/changelog/V3.0.sql b/stratify/src/main/resources/db/changelog/V3.0.sql
new file mode 100644
index 0000000..fe3739d
--- /dev/null
+++ b/stratify/src/main/resources/db/changelog/V3.0.sql
@@ -0,0 +1,110 @@
+-- liquibase formatted sql
+
+-- changeset santo:1748049584373-1
+CREATE TABLE "fato_eficiencia_user_story" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 115) NOT NULL, "id_usuario" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "id_user_story" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "tempo_medio" FLOAT8, "id_projeto" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, CONSTRAINT "fato_eficiencia_user_story_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-2
+CREATE TABLE "fato_progresso_user_stories" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "id_tag" BIGINT, "id_projeto" BIGINT, "id_status" BIGINT, "quantidade_user_stories" BIGINT, CONSTRAINT "fato_progresso_user_stories_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-3
+CREATE TABLE "dim_tag" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 21) NOT NULL, "nome" VARCHAR(255) NOT NULL, CONSTRAINT "dim_tag_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-4
+CREATE TABLE "dim_projeto" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 29) NOT NULL, "nome" VARCHAR(255) NOT NULL, CONSTRAINT "dim_projeto_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-5
+CREATE TABLE "dim_status" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 8) NOT NULL, "tipo" VARCHAR(255) NOT NULL, CONSTRAINT "dim_status_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-6
+CREATE TABLE "fato_status_user_story" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 28) NOT NULL, "id_status" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "id_projeto" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "quantidade_user_story" INTEGER, CONSTRAINT "fato_status_user_story_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-7
+CREATE TABLE "fato_tag_user_story" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 39) NOT NULL, "id_tag" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "id_projeto" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "quantidade_user_story" INTEGER, CONSTRAINT "fato_tag_user_story_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-8
+CREATE TABLE "dim_user_story" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 105) NOT NULL, "assunto" VARCHAR(255), "criado_em" TIMESTAMP WITH TIME ZONE, "finalizado_em" TIMESTAMP WITH TIME ZONE, "bloqueado" BOOLEAN, "encerrado" BOOLEAN, "data_limite" TIMESTAMP WITH TIME ZONE, "id_status" BIGINT NOT NULL, "id_taiga" BIGINT NOT NULL, "id_usuario" BIGINT NOT NULL, "id_projeto" BIGINT NOT NULL, "id_sprint" BIGINT, CONSTRAINT "dim_user_story_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-9
+CREATE TABLE "dim_usuario" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 54) NOT NULL, "nome" VARCHAR(100), "email" VARCHAR(100), "senha" VARCHAR(255), "role" VARCHAR(20) DEFAULT 'OPERADOR' NOT NULL, "is_enable" BOOLEAN DEFAULT FALSE NOT NULL, "id_gestor" BIGINT, "require_reset" BOOLEAN DEFAULT FALSE, CONSTRAINT "dim_usuario_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-10
+CREATE TABLE "dim_periodo" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 20) NOT NULL, "nome" VARCHAR(100), CONSTRAINT "dim_periodo_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-11
+CREATE TABLE "fato_user_story_temporais" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 37) NOT NULL, "id_usuario" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "id_periodo" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "id_projeto" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, "quantidade_user_stories_finalizadas" INTEGER, "quantidade_user_stories_criadas" INTEGER, CONSTRAINT "fato_user_story_temporais_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-12
+CREATE TABLE "dim_sprint" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 7) NOT NULL, "nome" VARCHAR(20) NOT NULL, CONSTRAINT "dim_sprint_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-13
+CREATE TABLE "dim_status_historico" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 4) NOT NULL, "id_user_story" BIGINT NOT NULL, "data_retrabalho" TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), "id_status" BIGINT NOT NULL, CONSTRAINT "dim_status_historico_pkey" PRIMARY KEY ("id"));
+
+-- changeset santo:1748049584373-14
+ALTER TABLE "dim_usuario" ADD CONSTRAINT "dim_usuario_unique" UNIQUE ("email");
+
+-- changeset santo:1748049584373-15
+CREATE TABLE "relacionamento_projeto_usuario" ("id_usuario" BIGINT NOT NULL, "id_projeto" BIGINT NOT NULL, CONSTRAINT "pk_relacionamento_pu" PRIMARY KEY ("id_usuario", "id_projeto"));
+
+-- changeset santo:1748049584373-16
+CREATE TABLE "relacionamento_tag_user_story" ("id_tag" BIGINT NOT NULL, "id_user_story" BIGINT NOT NULL, CONSTRAINT "pk_relacionamento_tag_user_story" PRIMARY KEY ("id_tag", "id_user_story"));
+
+-- changeset santo:1748049584373-17
+ALTER TABLE "dim_user_story" ADD CONSTRAINT "fk_dus_projeto" FOREIGN KEY ("id_projeto") REFERENCES "dim_projeto" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-18
+ALTER TABLE "dim_user_story" ADD CONSTRAINT "fk_dus_status" FOREIGN KEY ("id_status") REFERENCES "dim_status" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-19
+ALTER TABLE "dim_user_story" ADD CONSTRAINT "fk_dus_usuario" FOREIGN KEY ("id_usuario") REFERENCES "dim_usuario" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-20
+ALTER TABLE "fato_status_user_story" ADD CONSTRAINT "fk_ft_projeto_us" FOREIGN KEY ("id_projeto") REFERENCES "dim_projeto" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-21
+ALTER TABLE "fato_tag_user_story" ADD CONSTRAINT "fk_ft_projeto_us" FOREIGN KEY ("id_projeto") REFERENCES "dim_projeto" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-22
+ALTER TABLE "fato_user_story_temporais" ADD CONSTRAINT "fk_ft_projeto_us_temporais" FOREIGN KEY ("id_projeto") REFERENCES "dim_projeto" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-23
+ALTER TABLE "fato_status_user_story" ADD CONSTRAINT "fk_ft_st_us" FOREIGN KEY ("id_status") REFERENCES "dim_status" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-24
+ALTER TABLE "fato_tag_user_story" ADD CONSTRAINT "fk_ft_tag_us" FOREIGN KEY ("id_tag") REFERENCES "dim_tag" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-25
+ALTER TABLE "fato_user_story_temporais" ADD CONSTRAINT "fk_ft_us_periodo_temporais" FOREIGN KEY ("id_periodo") REFERENCES "dim_periodo" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-26
+ALTER TABLE "fato_eficiencia_user_story" ADD CONSTRAINT "fk_ft_user_story_us_eficiencia" FOREIGN KEY ("id_user_story") REFERENCES "dim_user_story" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-27
+ALTER TABLE "fato_eficiencia_user_story" ADD CONSTRAINT "fk_ft_usuario_us_eficiencia" FOREIGN KEY ("id_usuario") REFERENCES "dim_usuario" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-28
+ALTER TABLE "dim_usuario" ADD CONSTRAINT "fk_id_gestor_usuario" FOREIGN KEY ("id_gestor") REFERENCES "dim_usuario" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+-- changeset santo:1748049584373-29
+ALTER TABLE "fato_eficiencia_user_story" ADD CONSTRAINT "fk_projeto" FOREIGN KEY ("id_projeto") REFERENCES "dim_projeto" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+-- changeset santo:1748049584373-30
+ALTER TABLE "relacionamento_projeto_usuario" ADD CONSTRAINT "fk_relacionamento_pu_projeto" FOREIGN KEY ("id_projeto") REFERENCES "dim_projeto" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-31
+ALTER TABLE "relacionamento_projeto_usuario" ADD CONSTRAINT "fk_relacionamento_pu_usuario" FOREIGN KEY ("id_usuario") REFERENCES "dim_usuario" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-32
+ALTER TABLE "relacionamento_tag_user_story" ADD CONSTRAINT "fk_relacionamento_tu_tag" FOREIGN KEY ("id_tag") REFERENCES "dim_tag" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-33
+ALTER TABLE "relacionamento_tag_user_story" ADD CONSTRAINT "fk_relacionamento_tu_user_story" FOREIGN KEY ("id_user_story") REFERENCES "dim_user_story" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-34
+ALTER TABLE "dim_user_story" ADD CONSTRAINT "fk_sprint_us" FOREIGN KEY ("id_sprint") REFERENCES "dim_sprint" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;
+
+-- changeset santo:1748049584373-35
+ALTER TABLE "dim_status_historico" ADD CONSTRAINT "fk_staus_historico" FOREIGN KEY ("id_status") REFERENCES "dim_status" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+-- changeset santo:1748049584373-36
+ALTER TABLE "dim_status_historico" ADD CONSTRAINT "fk_user_story_status_historico" FOREIGN KEY ("id_user_story") REFERENCES "dim_user_story" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;
+
diff --git a/stratify/src/test/java/com/quantum/stratify/services/CsvDownloadTest.java b/stratify/src/test/java/com/quantum/stratify/services/CsvDownloadTest.java
new file mode 100644
index 0000000..9a00b4f
--- /dev/null
+++ b/stratify/src/test/java/com/quantum/stratify/services/CsvDownloadTest.java
@@ -0,0 +1,73 @@
+package com.quantum.stratify.services;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.core.io.InputStreamResource;
+
+import com.quantum.stratify.web.dtos.QuantidadeCardsPorSprintDTO;
+import com.quantum.stratify.web.dtos.ResponseQuantidadeCardsByPeriodo;
+
+class CsvDownloadServiceTest {
+
+ @Mock
+ private UserStoryService userStoryService;
+ @Mock
+ private FatoUserStoryTemporaisService fatoUserStoryTemporaisService;
+ @Mock
+ private UserStoryTagService userStoryTagService;
+ @Mock
+ private UserStoryStatusService userStoryStatusService;
+ @Mock
+ private FatoEficienciaUserStoryService fatoEficienciaUserStoryService;
+
+ @InjectMocks
+ private CsvDownloadService csvDownloadService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void shouldGenerateCsvForCardsporsprint() throws Exception {
+ when(userStoryService.getQuantidadeUserStoriesBySprint(1L, 1L)).thenReturn(
+ List.of(new QuantidadeCardsPorSprintDTO("Sprint 1", 5L))
+ );
+
+ InputStreamResource result = csvDownloadService.generateCsv(1L, 1L, "cardsporsprint");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(result.getInputStream()));
+
+ assertEquals("\"Sprint\",\"Quantidade\"", reader.readLine());
+ assertEquals("\"Sprint 1\",\"5\"", reader.readLine());
+ }
+
+ @Test
+ void shouldGenerateCsvForCardsporperiodo() throws Exception {
+ when(fatoUserStoryTemporaisService.getUserStoriesByPeriodoAndUser(1L, 1L)).thenReturn(
+ List.of(new ResponseQuantidadeCardsByPeriodo("2024-01", 3, 2))
+ );
+
+ InputStreamResource result = csvDownloadService.generateCsv(1L, 1L, "cardsporperiodo");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(result.getInputStream()));
+
+ assertEquals("\"Período\",\"Criadas\",\"Finalizadas\"", reader.readLine());
+ assertEquals("\"2024-01\",\"3\",\"2\"", reader.readLine());
+ }
+
+ @Test
+ void shouldThrowForInvalidExportType() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> csvDownloadService.generateCsv(1L, 1L, "invalido"));
+ assertEquals("Tipo de exportação inválido: invalido", ex.getMessage());
+ }
+}
diff --git a/stratify/src/test/java/com/quantum/stratify/web/controllers/FatoEficienciaUserStoryControllerTest.java b/stratify/src/test/java/com/quantum/stratify/web/controllers/FatoEficienciaUserStoryControllerTest.java
index 54204e1..f93f734 100644
--- a/stratify/src/test/java/com/quantum/stratify/web/controllers/FatoEficienciaUserStoryControllerTest.java
+++ b/stratify/src/test/java/com/quantum/stratify/web/controllers/FatoEficienciaUserStoryControllerTest.java
@@ -18,6 +18,8 @@ public class FatoEficienciaUserStoryControllerTest {
private FatoEficienciaUserStoryService service;
private FatoEficienciaUserStoryController controller;
+
+
@BeforeEach
public void setup() {
service = mock(FatoEficienciaUserStoryService.class);
@@ -30,8 +32,8 @@ public void testGetTempoMedioPorProjetoFiltrado_comUsuario() {
Long projetoId = 1L;
Long usuarioId = 2L;
List mockList = Arrays.asList(
- new TempoMedioPorProjetoDTO(10L, "UserStory A", 5.0),
- new TempoMedioPorProjetoDTO(11L, "UserStory B", 6.0)
+ new TempoMedioPorProjetoDTO(10L, "UserStory A", 5.0,1L),
+ new TempoMedioPorProjetoDTO(11L, "UserStory B", 6.0,1L)
);
when(service.getTempoMedioFiltrado(projetoId, usuarioId)).thenReturn(mockList);
@@ -51,7 +53,7 @@ public void testGetTempoMedioPorProjetoFiltrado_comUsuario() {
public void testGetTempoMedioPorProjetoFiltrado_semUsuario() {
Long projetoId = 1L;
List mockList = Arrays.asList(
- new TempoMedioPorProjetoDTO(20L, "UserStory C", 7.0)
+ new TempoMedioPorProjetoDTO(20L, "UserStory C", 7.0,1L)
);
when(service.getTempoMedioFiltrado(projetoId, null)).thenReturn(mockList);
@@ -72,7 +74,7 @@ public void testGetTempoMedioPorProjetoFiltrado_semUsuario() {
public void testGetTempoMedioPorProjetoFiltrado_somenteUsuario() {
Long usuarioId = 2L;
List mockList = Arrays.asList(
- new TempoMedioPorProjetoDTO(30L, "UserStory D", 8.5)
+ new TempoMedioPorProjetoDTO(30L, "UserStory D", 8.5,1L)
);
when(service.getTempoMedioFiltrado(null, usuarioId)).thenReturn(mockList);
@@ -91,7 +93,7 @@ public void testGetTempoMedioPorProjetoFiltrado_somenteUsuario() {
@Test
public void testGetTempoMedioPorProjetoFiltrado_semParametros() {
List mockList = Arrays.asList(
- new TempoMedioPorProjetoDTO(40L, "UserStory E", 4.2)
+ new TempoMedioPorProjetoDTO(40L, "UserStory E", 4.2,1L)
);
when(service.getTempoMedioFiltrado(null, null)).thenReturn(mockList);