diff --git a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java index 7fb61dd..5dcaf46 100644 --- a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java +++ b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java @@ -39,42 +39,72 @@ public class RedmineClient { } //AQUI OBTENGO LOS TICKETS DESDE REDMINE - public List getTickets(RedmineUser user, boolean includeClosed, int offset, int limit) { + public List getTickets(RedmineUser user, boolean includeClosed, + int offset, int limit, + LocalDate fechaDesde, + LocalDate fechaHasta) { + List tickets = new ArrayList<>(); HttpClient client = HttpClient.newHttpClient(); - // Muestra solamnete los que estan cerrados - String statusFilter = includeClosed ? "&status_id=closed" : "&status_id=open"; + // Filtrar tickets abiertos o cerrados + String statusFilter = includeClosed ? "&status_id=open" : "&status_id=closed"; + + StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey() + + statusFilter + "&offset=" + offset + "&limit=" + limit); + // Filtro por fechas + DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; + if (fechaDesde != null) { + url.append("&created_on=%3E%3D").append(fechaDesde.format(formatter)); + } + if (fechaHasta != null) { + url.append("&created_on=%3C%3D").append(fechaHasta.format(formatter)); + } HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() - + statusFilter + "&offset=" + offset + "&limit=" + limit)) + .uri(URI.create(url.toString())) .header("Content-Type", "application/json") .build(); - System.out.println(request.toString()); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { - String responseBody = response.body(); - //log.info(responseBody); - tickets.addAll(parseTickets(responseBody)); + tickets.addAll(parseTickets(response.body())); } else { System.err.println("Error en la respuesta: " + response.statusCode()); } } catch (Exception e) { e.printStackTrace(); } + return tickets; } + public int getTotalTickets(RedmineUser user, + boolean includeClosed, + LocalDate fechaDesde, + LocalDate fechaHasta) { - public int getTotalTickets(RedmineUser user, boolean includedClose) { HttpClient client = HttpClient.newHttpClient(); - String statusFilter = includedClose ? "&status_id=closed" : "&status_id=open"; + + // Filtrar tickets abiertos o cerrados + String statusFilter = includeClosed ? "&status_id=open" : "&status_id=closed"; + + StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey() + + statusFilter + "&limit=1"); + + // Filtro por fechas + DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; + if (fechaDesde != null) { + url.append("&created_on=%3E%3D").append(fechaDesde.format(formatter)); + } + if (fechaHasta != null) { + url.append("&created_on=%3C%3D").append(fechaHasta.format(formatter)); + } + HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + statusFilter + "&limit=1")) + .uri(URI.create(url.toString())) .header("Content-Type", "application/json") .build(); @@ -83,6 +113,8 @@ public class RedmineClient { if (response.statusCode() == 200) { JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject(); return jsonObject.get("total_count").getAsInt(); + } else { + System.err.println("Error en la respuesta: " + response.statusCode()); } } catch (Exception e) { e.printStackTrace(); @@ -90,6 +122,7 @@ public class RedmineClient { return 0; } + //AQUI OBTENGO LOS TICKETS DESDE REDMINE /*public List getTicketsAuthor(RedmineUser user, boolean includeClosed) { List tickets = new ArrayList<>(); @@ -383,4 +416,22 @@ public class RedmineClient { return null; } } + + + public List getAllTickets(RedmineUser user, boolean abiertos, + LocalDate fechaDesde, LocalDate fechaHasta) { + List allTickets = new ArrayList<>(); + int offset = 0; + int limit = 100; // Redmine suele permitir hasta 100 por request (puedes probar 1000) + + List batch; + do { + batch = getTickets(user, abiertos, offset, limit, fechaDesde, fechaHasta); + allTickets.addAll(batch); + offset += limit; + } while (!batch.isEmpty()); + + return allTickets; + } + } diff --git a/src/main/java/mx/gob/jumapacelaya/models/Ticket.java b/src/main/java/mx/gob/jumapacelaya/models/Ticket.java index 18b1b2a..725746a 100644 --- a/src/main/java/mx/gob/jumapacelaya/models/Ticket.java +++ b/src/main/java/mx/gob/jumapacelaya/models/Ticket.java @@ -6,6 +6,7 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.List; public class Ticket { private final int id; @@ -50,14 +51,22 @@ public class Ticket { if (dateStr == null || dateStr.isEmpty()) { return null; } + + List formatters = List.of( + DateTimeFormatter.ISO_OFFSET_DATE_TIME, + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") + ); + + for (DateTimeFormatter formatter : formatters) { + try { + return LocalDateTime.parse(dateStr, formatter); + } catch (Exception ignored) {} + } + try { - 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")); - } + LocalDate ld = LocalDate.parse(dateStr); + return ld.atStartOfDay(); } catch (Exception e) { System.err.println("Error al parsear fecha: " + dateStr); return null; diff --git a/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java b/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java index 3acf0e9..cdf546b 100644 --- a/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java +++ b/src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java @@ -121,6 +121,7 @@ public class ActDiariaView extends VerticalLayout { fechaDesde.setEnabled(true); fechaHasta.setEnabled(true); btnBuscar.setEnabled(true); + btnBuscar.addClickListener(e -> filtrarTickets()); // Cargar tickets iniciales (cerrados) loadTickets(false); @@ -283,18 +284,26 @@ public class ActDiariaView extends VerticalLayout { grid.setItems( query -> { - int offset = query.getOffset(); - int limit = query.getLimit(); - try { - List ticketsPage = redmineClient.getTickets(user, paramCliente, offset, limit); - return ticketsPage.stream(); + return redmineClient.getTickets( + user, + chkSoloAbiertos.getValue(), + query.getOffset(), + query.getLimit(), + fechaDesde.getValue(), + fechaHasta.getValue() + ).stream(); } catch (Exception e) { e.printStackTrace(); return Stream.empty(); } }, - query -> redmineClient.getTotalTickets(user, paramCliente) + query -> redmineClient.getTotalTickets( + user, + chkSoloAbiertos.getValue(), + fechaDesde.getValue(), + fechaHasta.getValue() + ) ); } @@ -393,12 +402,19 @@ public class ActDiariaView extends VerticalLayout { try { RedmineUser user = userService.getRedmineUser(); - List ticketsCerrados = redmineClient.getTickets(user, true, 0, 10000); + // Traer TODOS los tickets cerrados, respetando fechas si están seleccionadas + List ticketsCerrados = redmineClient.getAllTickets( + user, + false, // false = cerrados + fechaDesde.getValue(), + fechaHasta.getValue() + ); 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"}; + String[] headers = {"ID","Tipo","Tiempo estimado","Fecha creación","Fecha cierre", + "Fecha actualización","Situación","Estado","Duración","Autor","Asunto"}; Row headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { Cell cell = headerRow.createCell(i); @@ -412,25 +428,26 @@ public class ActDiariaView extends VerticalLayout { 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) : ""); + row.createCell(2).setCellValue(ticket.getEstimatedHrs() + " horas"); + row.createCell(3).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : ""); + row.createCell(4).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : ""); + row.createCell(5).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : ""); + row.createCell(7).setCellValue(ticket.getStatus()); + row.createCell(9).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : ""); + row.createCell(10).setCellValue(ticket.getSubject()); if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) { long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal()); - row.createCell(8).setCellValue(dias + " dias"); + row.createCell(8).setCellValue(dias + " días"); } 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"); + row.createCell(6).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido"); } else { - row.createCell(9).setCellValue("Sin estimación"); + row.createCell(6).setCellValue("Sin estimación"); } } @@ -442,10 +459,14 @@ public class ActDiariaView extends VerticalLayout { workbook.write(out); workbook.close(); - StreamResource resource = new StreamResource("tickets_cerrados.xlsx", () -> new ByteArrayInputStream(out.toByteArray())); + 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')"); + 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); @@ -453,4 +474,10 @@ public class ActDiariaView extends VerticalLayout { .addThemeVariants(NotificationVariant.LUMO_ERROR); } } + + + private void filtrarTickets() { + boolean soloAbiertos = chkSoloAbiertos.getValue(); + loadTickets(soloAbiertos); + } }