2 Commits

4 changed files with 264 additions and 140 deletions
Split View
  1. BIN
      src/main/bundles/dev.bundle
  2. +71
    -57
      src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java
  3. +21
    -11
      src/main/java/mx/gob/jumapacelaya/models/Ticket.java
  4. +172
    -72
      src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java

BIN
src/main/bundles/dev.bundle View File


+ 71
- 57
src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java View File

@ -3,6 +3,8 @@ package mx.gob.jumapacelaya.api;
import com.google.gson.*;
import mx.gob.jumapacelaya.models.RedmineUser;
import mx.gob.jumapacelaya.models.Ticket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@ -12,6 +14,9 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
@ -21,11 +26,11 @@ import java.util.Map;
@Component
public class RedmineClient {
private static final Logger log = LoggerFactory.getLogger(RedmineClient.class);
private static final int PAGE_SIZE = 25;
//private static final int PAGE_SIZE = 25;
static String REDMINE_URL;
static String API_KEY;
public static final Gson GSON = new Gson();
public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) {
REDMINE_URL = redmineUrl;
@ -52,7 +57,9 @@ public class RedmineClient {
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
tickets.addAll(parseTickets(response.body()));
String responseBody = response.body();
//log.info(responseBody);
tickets.addAll(parseTickets(responseBody));
} else {
System.err.println("Error en la respuesta: " + response.statusCode());
}
@ -133,16 +140,13 @@ public class RedmineClient {
for (JsonElement issueElement : issues) {
JsonObject issue = issueElement.getAsJsonObject();
// Verifica y obtiene el ID
// ID, subject, descripción
int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0;
// Verifica y obtiene el subject
String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : "";
// Verifica y obtiene la descripción
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;
// Verifica y obtiene el status
// Status
String status = "Unknown";
if (issue.has("status") && !issue.get("status").isJsonNull()) {
JsonObject statusObject = issue.getAsJsonObject("status");
@ -151,57 +155,21 @@ public class RedmineClient {
}
}
// Verifica y obtiene la fecha de creación
String dateString = issue.has("created_on") && !issue.get("created_on").isJsonNull() ? issue.get("created_on").getAsString() : "";
LocalDate dateCreate = null;
if (!dateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateCreate = LocalDate.parse(dateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha: " + dateString);
e.printStackTrace();
}
}
// Verifica y obtiene la fecha de cierre
String closeDateString = issue.has("closed_on") && !issue.get("closed_on").isJsonNull() ? issue.get("closed_on").getAsString() : "";
LocalDate dateClose = null;
if (!closeDateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateClose = LocalDate.parse(closeDateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha de cierre: " + closeDateString);
e.printStackTrace();
}
}
// Verifica y obtiene la fecha de actualizacion
String updateDateString = issue.has("updated_on") && !issue.get("updated_on").isJsonNull() ? issue.get("updated_on").getAsString() : "";
LocalDate dateUpdate = null;
if (!updateDateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateUpdate = LocalDate.parse(updateDateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha de actualización: " + updateDateString);
e.printStackTrace();
}
}
// Parse fechas correctamente
LocalDateTime dateCreate = parseDateTime(issue, "created_on");
LocalDateTime dateUpdate = parseDateTime(issue, "updated_on");
LocalDateTime dateClose = parseDateTime(issue, "closed_on");
// Autor
Ticket.User autor = null;
if (issue.has("author") && !issue.get("author").isJsonNull()) {
JsonObject authorObj = issue.getAsJsonObject("author");
if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) {
String authorName = authorObj.get("name").getAsString();
autor = new Ticket.User(authorName);
autor = new Ticket.User(authorObj.get("name").getAsString());
}
}
//Verifica y obtiene el ID del tipo de ticket
// Tracker ID
Integer trackerId = null;
if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) {
JsonObject trackerObject = issue.getAsJsonObject("tracker");
@ -210,14 +178,26 @@ public class RedmineClient {
}
}
// Agrega el ticket a la lista
tickets.add(new Ticket(id, subject, description, status,
// Crear ticket
Ticket ticketObj = new Ticket(
id,
subject,
description,
status,
dateCreate != null ? dateCreate.toString() : "",
dateClose != null ? dateClose.toString() : "",
dateUpdate != null ? dateUpdate.toString() : "",
autor,
trackerId, "Tipo Desconocido"));
trackerId,
"Tipo Desconocido",
estimatedHrs
);
// Log para verificar JSON de ticket
//System.out.println(GSON.toJson(ticketObj));
tickets.add(ticketObj);
}
} else {
System.out.println("La respuesta JSON no contiene la clave 'issues'");
@ -229,6 +209,7 @@ public class RedmineClient {
return tickets;
}
public RedmineUser getMyAccount(String username) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/my/account.json"))
@ -306,7 +287,7 @@ public class RedmineClient {
public RedmineUser getUserByUsername(String username) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username ))
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username))
.header("Content-Type", "application/json")
.header("X-Redmine-API-Key", API_KEY)
.build();
@ -369,4 +350,37 @@ public class RedmineClient {
System.err.println(response.body());
}
}
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class,
(JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) ->
new JsonPrimitive(src.toString()))
.registerTypeAdapter(LocalDateTime.class,
(JsonDeserializer<LocalDateTime>) (json, typeOf, context) ->
LocalDateTime.parse(json.getAsString()))
.setPrettyPrinting()
.create();
// Método auxiliar para parsear echas con hora
private LocalDateTime parseDateTime(JsonObject issue, String fieldName) {
if (!issue.has(fieldName) || issue.get(fieldName).isJsonNull()) {
return null;
}
String dateStr = issue.get(fieldName).getAsString();
try {
// Si viene con 'Z' al final (UTC), convertir a LocalDateTime
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr);
}
} catch (DateTimeParseException ex) {
System.err.println("Error al parsear fecha '" + fieldName + "': " + dateStr);
return null;
}
}
}

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

@ -5,6 +5,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class Ticket {
private final int id;
@ -17,12 +18,12 @@ public class Ticket {
private User autor;
private Integer trackerId;
private String type;
private Double estimatedHrs;
public Ticket(int id, String subject, String description, String status,
String dateCreate, String dateClose, String updateOn,
User autor, Integer trackerId, String type) {
User autor, Integer trackerId, String type, Double estimatedHrs) {
this.id = id;
this.subject = subject;
this.description = description;
@ -35,6 +36,7 @@ public class Ticket {
this.autor = autor;
this.trackerId = trackerId;
this.type = type;
this.estimatedHrs = estimatedHrs;
}
@ -49,16 +51,16 @@ public class Ticket {
return null;
}
try {
OffsetDateTime odt = OffsetDateTime.parse(dateStr);
return odt.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
} catch (Exception e) {
try {
LocalDate ld = LocalDate.parse(dateStr);
return ld.atStartOfDay();
} catch (Exception ex) {
System.err.println("Error al parsear fecha: " + dateStr);
return null;
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
}
} catch (Exception e) {
System.err.println("Error al parsear fecha: " + dateStr);
return null;
}
}
@ -123,6 +125,14 @@ public class Ticket {
return this.dateClose;
}
public Double getEstimatedHrs() {
return estimatedHrs;
}
public void setEstimatedHrs(Double estimatedHrs) {
this.estimatedHrs = estimatedHrs;
}
public static class User {
private String username;


+ 172
- 72
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.grid.Grid;
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.H3;
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.richtexteditor.RichTextEditor;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.api.RedmineClient;
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.UserService;
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.text.SimpleDateFormat;
import java.time.LocalDate;
@ -54,13 +68,15 @@ import java.util.stream.Stream;
@CssImport("./themes/sistema-mantenimiento/styles.css")
public class ActDiariaView extends VerticalLayout {
private static final Logger log = LoggerFactory.getLogger(ActDiariaView.class);
private final RedmineClient redmineClient;
private final UserService userService;
private final Grid<Ticket> grid;
private final HorizontalLayout showColumnsLyt;
private final Button btnColumns;
private final Button btnExport;
private final HorizontalLayout opcionesLyt;
private final Checkbox chkVerCerrados;
private Checkbox chkSoloAbiertos;
private DatePicker fechaDesde;
private DatePicker fechaHasta;
private Button btnBuscar;
@ -72,7 +88,7 @@ public class ActDiariaView extends VerticalLayout {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
// Configuracion de las opciones de arriba
// Layout de opciones
opcionesLyt = new HorizontalLayout();
opcionesLyt.setWidthFull();
opcionesLyt.setMargin(false);
@ -83,26 +99,33 @@ public class ActDiariaView extends VerticalLayout {
.set("padding", "1rem")
.set("margin", "1rem auto");
// Checkbox para ver solo abiertos
chkSoloAbiertos = new Checkbox("Ver solo abiertos");
chkSoloAbiertos.setValue(false); // inicial: mostrar cerrados
fechaDesde = new DatePicker("Fecha desde:");
fechaHasta = new DatePicker("Fecha hasta:");
btnBuscar = new Button("Buscar");
chkVerCerrados = new Checkbox();
chkVerCerrados.setLabel("Ver solo abiertos");
chkVerCerrados.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue().equals(false);
// Listener del checkbox
chkSoloAbiertos.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue(); // marcado = abiertos
loadTickets(soloAbiertos);
fechaDesde.setEnabled(false);
fechaHasta.setEnabled(false);
btnBuscar.setEnabled(false);
fechaDesde.setEnabled(!soloAbiertos);
fechaHasta.setEnabled(!soloAbiertos);
btnBuscar.setEnabled(!soloAbiertos);
});
fechaDesde = new DatePicker("Fecha desde:");
// Estado inicial
fechaDesde.setEnabled(true);
fechaHasta = new DatePicker("Fecha hasta:");
fechaHasta.setEnabled(true);
btnBuscar.setEnabled(true);
btnBuscar = new Button("Buscar");
opcionesLyt.add(chkVerCerrados,fechaDesde,fechaHasta,btnBuscar);
// Cargar tickets iniciales (cerrados)
loadTickets(false);
opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar);
// Configuración de columnas del grid
grid.addColumn(Ticket::getId).setHeader("No.")
@ -114,49 +137,54 @@ public class ActDiariaView extends VerticalLayout {
.setAutoWidth(true)
.setKey("tipo");
grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId()))
.setHeader("Tiempo estimado")
.setAutoWidth(true)
.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 -> {
LocalDateTime fecha = ticket.getDateCreate();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha creación")
.setAutoWidth(true)
.setKey("fechaCreacion");
grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getDateClose();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha cierre")
.setAutoWidth(true)
.setKey("fechaCierre");
grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getUpdateOn();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha actualización")
.setAutoWidth(true)
.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 -> {
if (ticket.getDateCreateLocal() != null) {
@ -172,6 +200,19 @@ public class ActDiariaView extends VerticalLayout {
}).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 -> {
Button btnVer = new Button(new Icon(VaadinIcon.EYE));
btnVer.addClickListener(event -> showDescription(ticket));
@ -179,18 +220,18 @@ public class ActDiariaView extends VerticalLayout {
return btnVer;
});
//grid.addColumn(buttonTicketComponentRenderer()).setAutoWidth(true);
grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT);
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);
//grid.getStyle().set("opacity", "0.8");
//grid.setAllRowsVisible(false);
grid.setSizeFull();
// Mostrar boton para elegir que columnas mostrar
/**/btnColumns = new Button(VaadinIcon.GRID_H.create());
/**/showColumnsLyt = new HorizontalLayout(btnColumns);
// Botón para mostrar/ocultar columnas
btnColumns = new Button(VaadinIcon.GRID_H.create());
// 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();
columnsSelectorLyt.setAlignItems(Alignment.END);
Popover popover = new Popover();
@ -220,46 +261,44 @@ 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);
popover.add(heading, chkColumns);
/****/
// Ajustar tamaño del Grid y Layout
// Ajustar tamaños
grid.setSizeFull();
setSizeFull();
add(opcionesLyt, showColumnsLyt, grid);
//expand(grid);
setMargin(false);
loadTickets(true);
}
// NOTA: Ajuste en la carga de tickets para que ahora haga LazyLoad al cargar todos los ticktes cerrados
private void loadTickets(boolean soloAbiertos) {
RedmineUser user = userService.getRedmineUser();
// Invertir parámetro para que coincida con RedmineClient
boolean paramCliente = !soloAbiertos;
grid.setItems(
query -> {
int offset = query.getOffset();
int limit = query.getLimit();
try {
List<Ticket> ticketsPage = redmineClient.getTickets(user, soloAbiertos, offset, limit);
List<Ticket> ticketsPage = redmineClient.getTickets(user, paramCliente, offset, limit);
return ticketsPage.stream();
} catch (Exception e) {
e.printStackTrace();
return Stream.empty();
return Stream.empty();
}
},
query -> {
return redmineClient.getTotalTickets(user, soloAbiertos);
}
query -> redmineClient.getTotalTickets(user, paramCliente)
);
}
private ComponentRenderer<Span, Ticket> createStatusRender() {
return new ComponentRenderer<>(ticket -> {
// Creamos un Span para mostrar el estado
@ -267,27 +306,21 @@ public class ActDiariaView extends VerticalLayout {
// Estilos basados en el estado del ticket
switch (ticket.getStatus().toLowerCase()) {
case "análisis":
span.getElement().getStyle().set("color","orange");
case "rechazado":
String theme1 = String.format("badge %s", "error");
span.getElement().setAttribute("theme",theme1);
break;
case "desarrollo":
span.getElement().getStyle().set("color","blue");
break;
case "rechazada":
span.getElement().getStyle().set("color","red");
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;
case "qa":
span.getElement().getStyle().set("color","#C21DF2");
String theme3 = String.format("badge %s", "warning");
span.getElement().setAttribute("theme",theme3);
break;
default:
span.getElement().getStyle().set("color","green");
break;
String theme4 = String.format("badge %s", "");
span.getElement().setAttribute("theme",theme4);
}
return span;
@ -353,4 +386,71 @@ public class ActDiariaView extends VerticalLayout {
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