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);