Browse Source

Se agrego la funcionalidad para poder exportar los tickets a una hoja de calculo

main
mramirezg 2 months ago
parent
commit
95aa5228e9
3 changed files with 155 additions and 38 deletions
  1. +5
    -2
      src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java
  2. +11
    -2
      src/main/java/mx/gob/jumapacelaya/models/Ticket.java
  3. +139
    -34
      src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java

+ 5
- 2
src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java View File

@ -58,7 +58,7 @@ public class RedmineClient {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
String responseBody = response.body(); String responseBody = response.body();
log.info(responseBody);
//log.info(responseBody);
tickets.addAll(parseTickets(responseBody)); tickets.addAll(parseTickets(responseBody));
} else { } else {
System.err.println("Error en la respuesta: " + response.statusCode()); System.err.println("Error en la respuesta: " + response.statusCode());
@ -144,6 +144,7 @@ public class RedmineClient {
int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0; int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0;
String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : ""; String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : "";
String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : ""; String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : "";
Double estimatedHrs = issue.has("estimated_hours") && !issue.get("estimated_hours").isJsonNull() ? issue.get("estimated_hours").getAsDouble() : 0.0;
// Status // Status
String status = "Unknown"; String status = "Unknown";
@ -188,7 +189,9 @@ public class RedmineClient {
dateUpdate != null ? dateUpdate.toString() : "", dateUpdate != null ? dateUpdate.toString() : "",
autor, autor,
trackerId, trackerId,
"Tipo Desconocido"
"Tipo Desconocido",
estimatedHrs
); );
// Log para verificar JSON de ticket // Log para verificar JSON de ticket


+ 11
- 2
src/main/java/mx/gob/jumapacelaya/models/Ticket.java View File

@ -18,12 +18,12 @@ public class Ticket {
private User autor; private User autor;
private Integer trackerId; private Integer trackerId;
private String type; private String type;
private Double estimatedHrs;
public Ticket(int id, String subject, String description, String status, public Ticket(int id, String subject, String description, String status,
String dateCreate, String dateClose, String updateOn, String dateCreate, String dateClose, String updateOn,
User autor, Integer trackerId, String type) {
User autor, Integer trackerId, String type, Double estimatedHrs) {
this.id = id; this.id = id;
this.subject = subject; this.subject = subject;
this.description = description; this.description = description;
@ -36,6 +36,7 @@ public class Ticket {
this.autor = autor; this.autor = autor;
this.trackerId = trackerId; this.trackerId = trackerId;
this.type = type; this.type = type;
this.estimatedHrs = estimatedHrs;
} }
@ -124,6 +125,14 @@ public class Ticket {
return this.dateClose; return this.dateClose;
} }
public Double getEstimatedHrs() {
return estimatedHrs;
}
public void setEstimatedHrs(Double estimatedHrs) {
this.estimatedHrs = estimatedHrs;
}
public static class User { public static class User {
private String username; private String username;


+ 139
- 34
src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java View File

@ -11,6 +11,7 @@ import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3; import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
@ -25,8 +26,11 @@ import com.vaadin.flow.component.popover.Popover;
import com.vaadin.flow.component.popover.PopoverPosition; import com.vaadin.flow.component.popover.PopoverPosition;
import com.vaadin.flow.component.richtexteditor.RichTextEditor; import com.vaadin.flow.component.richtexteditor.RichTextEditor;
import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.api.RedmineClient; import mx.gob.jumapacelaya.api.RedmineClient;
import mx.gob.jumapacelaya.api.ServerProperties; import mx.gob.jumapacelaya.api.ServerProperties;
@ -36,8 +40,18 @@ import mx.gob.jumapacelaya.models.Ticket;
import mx.gob.jumapacelaya.services.LdapService; import mx.gob.jumapacelaya.services.LdapService;
import mx.gob.jumapacelaya.services.UserService; import mx.gob.jumapacelaya.services.UserService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate; import java.time.LocalDate;
@ -54,11 +68,13 @@ import java.util.stream.Stream;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class ActDiariaView extends VerticalLayout { public class ActDiariaView extends VerticalLayout {
private static final Logger log = LoggerFactory.getLogger(ActDiariaView.class);
private final RedmineClient redmineClient; private final RedmineClient redmineClient;
private final UserService userService; private final UserService userService;
private final Grid<Ticket> grid; private final Grid<Ticket> grid;
private final HorizontalLayout showColumnsLyt; private final HorizontalLayout showColumnsLyt;
private final Button btnColumns; private final Button btnColumns;
private final Button btnExport;
private final HorizontalLayout opcionesLyt; private final HorizontalLayout opcionesLyt;
private Checkbox chkSoloAbiertos; private Checkbox chkSoloAbiertos;
private DatePicker fechaDesde; private DatePicker fechaDesde;
@ -126,20 +142,6 @@ public class ActDiariaView extends VerticalLayout {
.setAutoWidth(true) .setAutoWidth(true)
.setKey("tiempoEst"); .setKey("tiempoEst");
grid.addColumn(Ticket::getSubject)
.setHeader("Asunto")
.setWidth("25rem")
.setKey("asunto");
grid.addColumn(ticket ->
ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "")
.setHeader("Autor")
.setAutoWidth(true)
.setKey("autor");
grid.addColumn(createStatusRender()).setHeader("Estado")
.setKey("estado");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getDateCreate(); LocalDateTime fecha = ticket.getDateCreate();
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
@ -161,6 +163,29 @@ public class ActDiariaView extends VerticalLayout {
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaActualizacion"); .setKey("fechaActualizacion");
grid.addColumn(ticket -> {
if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
LocalDateTime inicio = ticket.getDateCreateLocal();
LocalDateTime fin = ticket.getDateCloseLocal();
long horasReales = ChronoUnit.HOURS.between(inicio, fin);
Double horasEstimadas = ticket.getEstimatedHrs();
if (horasEstimadas != null) {
return horasReales <= horasEstimadas ? "✅ En tiempo" : "⚠️ Excedido";
} else {
return "Sin estimación";
}
} else {
return "";
}
}).setHeader("Situación").setAutoWidth(true).setKey("cumplimientoHoras");
grid.addColumn(createStatusRender())
.setHeader("Estado")
.setKey("estado");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
if (ticket.getDateCreateLocal() != null) { if (ticket.getDateCreateLocal() != null) {
LocalDateTime fechaInicio = ticket.getDateCreateLocal(); LocalDateTime fechaInicio = ticket.getDateCreateLocal();
@ -174,6 +199,20 @@ public class ActDiariaView extends VerticalLayout {
} }
}).setHeader("Duración").setAutoWidth(true).setKey("duracion"); }).setHeader("Duración").setAutoWidth(true).setKey("duracion");
grid.addColumn(ticket ->
ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "")
.setHeader("Autor")
.setAutoWidth(true)
.setKey("autor");
grid.addColumn(Ticket::getSubject)
.setHeader("Asunto")
.setWidth("25rem")
.setKey("asunto");
grid.addComponentColumn(ticket -> { grid.addComponentColumn(ticket -> {
Button btnVer = new Button(new Icon(VaadinIcon.EYE)); Button btnVer = new Button(new Icon(VaadinIcon.EYE));
btnVer.addClickListener(event -> showDescription(ticket)); btnVer.addClickListener(event -> showDescription(ticket));
@ -187,7 +226,12 @@ public class ActDiariaView extends VerticalLayout {
// Botón para mostrar/ocultar columnas // Botón para mostrar/ocultar columnas
btnColumns = new Button(VaadinIcon.GRID_H.create()); btnColumns = new Button(VaadinIcon.GRID_H.create());
showColumnsLyt = new HorizontalLayout(btnColumns);
// Botón para exportar
btnExport = new Button("Exportar", LineAwesomeIcon.FILE_EXCEL.create());
btnExport.addClickListener(e -> exportTicketsCerrados());
showColumnsLyt = new HorizontalLayout(btnExport, btnColumns);
showColumnsLyt.setJustifyContentMode(JustifyContentMode.END);
showColumnsLyt.setWidthFull();
HorizontalLayout columnsSelectorLyt = new HorizontalLayout(); HorizontalLayout columnsSelectorLyt = new HorizontalLayout();
columnsSelectorLyt.setAlignItems(Alignment.END); columnsSelectorLyt.setAlignItems(Alignment.END);
Popover popover = new Popover(); Popover popover = new Popover();
@ -217,7 +261,7 @@ public class ActDiariaView extends VerticalLayout {
} }
}); });
}); });
Set<String> defaultColumns = Set.of("tipo","asunto","autor","estado","fechaCreacion","fechaCierre");
Set<String> defaultColumns = Set.of("tipo","tiempoEst","fechaCreacion","fechaCierre","cumplimientoHoras","estado","duracion","autor","asunto");
chkColumns.setValue(defaultColumns); chkColumns.setValue(defaultColumns);
popover.add(heading, chkColumns); popover.add(heading, chkColumns);
@ -262,27 +306,21 @@ public class ActDiariaView extends VerticalLayout {
// Estilos basados en el estado del ticket // Estilos basados en el estado del ticket
switch (ticket.getStatus().toLowerCase()) { switch (ticket.getStatus().toLowerCase()) {
case "análisis":
span.getElement().getStyle().set("color","orange");
break;
case "desarrollo":
span.getElement().getStyle().set("color","blue");
break;
case "rechazada":
span.getElement().getStyle().set("color","red");
case "rechazado":
String theme1 = String.format("badge %s", "error");
span.getElement().setAttribute("theme",theme1);
break; break;
case "cerrada":
span.getElement().getStyle().set("color","grey");
break;
case "solicitado":
span.getElement().getStyle().set("color","purple");
case "resuelto","cerrado":
String theme2 = String.format("badge %s", "success");
span.getElement().setAttribute("theme",theme2);
break; break;
case "qa": case "qa":
span.getElement().getStyle().set("color","#C21DF2");
String theme3 = String.format("badge %s", "warning");
span.getElement().setAttribute("theme",theme3);
break; break;
default: default:
span.getElement().getStyle().set("color","green");
break;
String theme4 = String.format("badge %s", "");
span.getElement().setAttribute("theme",theme4);
} }
return span; return span;
@ -348,4 +386,71 @@ public class ActDiariaView extends VerticalLayout {
dialog.open(); dialog.open();
} }
// Metodo para exportar los tickets cerrados a un archivo de Excel
private void exportTicketsCerrados() {
try {
RedmineUser user = userService.getRedmineUser();
List<Ticket> ticketsCerrados = redmineClient.getTickets(user, true, 0, 10000);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Tickets cerrados");
String[] headers = {"ID","Tipo","Estado","Autor","Asunto","Fecha Creación","Fecha Cierre","Fecha actualización","Duración","Situación"};
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
}
int rowNum = 1;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
for (Ticket ticket : ticketsCerrados) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(ticket.getId());
row.createCell(1).setCellValue(ticket.getType());
row.createCell(2).setCellValue(ticket.getStatus());
row.createCell(3).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "");
row.createCell(4).setCellValue(ticket.getSubject());
row.createCell(5).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : "");
row.createCell(6).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : "");
row.createCell(7).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : "");
if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(8).setCellValue(dias + " dias");
} else {
row.createCell(8).setCellValue("");
}
if (ticket.getEstimatedHrs() != null && ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long horasReales = ChronoUnit.HOURS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(9).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido");
} else {
row.createCell(9).setCellValue("Sin estimación");
}
}
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
workbook.write(out);
workbook.close();
StreamResource resource = new StreamResource("tickets_cerrados.xlsx", () -> new ByteArrayInputStream(out.toByteArray()));
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry().registerResource(resource);
UI.getCurrent().getPage().executeJs("window.open('" + registration.getResourceUri().toString() + "','_blank')");
} catch (Exception e) {
log.error("Error exportando tickets", e);
Notification.show("Error al exportar los tickets", 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
}
} }

Loading…
Cancel
Save