diff --git a/Reporte_Vacaciones.xlsx b/Reporte_Vacaciones.xlsx new file mode 100644 index 0000000..8ce8134 Binary files /dev/null and b/Reporte_Vacaciones.xlsx differ diff --git a/package.json b/package.json index 3395e11..209a580 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -19,29 +19,30 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@babel/preset-react": "7.25.7", + "@preact/signals-react-transform": "0.4.0", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" @@ -49,7 +50,7 @@ "vaadin": { "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -64,34 +65,35 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@babel/preset-react": "7.25.7", + "@preact/signals-react-transform": "0.4.0", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" }, - "hash": "1a0f17d48b329307b5862bc57499307d1b89f7d89260121c2b7189f76957c436" + "hash": "2dc40a4f634ae025081ca2239cba00b14a35fe94ab78ac0a4dd3023d882081d5" }, "overrides": { "@vaadin/bundles": "$@vaadin/bundles", diff --git a/src/main/java/com/primefactorsolutions/model/DocumentType.java b/src/main/java/com/primefactorsolutions/model/DocumentType.java index 96c8ce3..10251fc 100644 --- a/src/main/java/com/primefactorsolutions/model/DocumentType.java +++ b/src/main/java/com/primefactorsolutions/model/DocumentType.java @@ -1,36 +1,35 @@ package com.primefactorsolutions.model; public enum DocumentType { - All, - ID_CARD, - PAY_STUB, - PAY_SLIPS, - EMPLOYMENT_CONTRACT, - WORK_CERTIFICATES, + TODOS, + CARNET_DE_IDENTIDAD, + RECIBOS_DE_PAGO, + CONTRATO_DE_TRABAJO, + CERTIFICADO_DE_TRABAJO, NDA, - MEMORANDUMS, - CONTRACT_APPROVAL_MTEPS, - BACKGROUND_CHECK_CERTIFICATE, - PRE_EMPLOYMENT_EVALUATION, - INSURANCE_REGISTRATION_FORM, - INSURANCE_CANCELLATION_FORM, - PROFESSIONAL_DEGREE_1, - PROFESSIONAL_CERTIFICATE_1, - PROFESSIONAL_DEGREE_2, - PROFESSIONAL_CERTIFICATE_2, - PROFESSIONAL_DEGREE_3, - PROFESSIONAL_CERTIFICATE_3, - GENERAL_LABOR_REGULATIONS, - REMOTE_WORK_GUIDELINES, - SAFETY_REGULATIONS, - HUMAN_RESOURCES_GUIDELINES, - ADMINISTRATION_FUNCTIONS_MANUAL, - ENGINEERING_FUNCTIONS_MANUAL, - GENERAL_LABOR_LAW, - SUPREME_DECREE, - REGULATORY_RESOLUTION, - COMPLEMENTARY_REGULATION, - HEALTH_SAFETY_LAW, - INTERNSHIP_RULES, - OTHER + MEMORÁNDUMS, + APROBACIÓN_DE_CONTRATO_MTEPS, + CERTIFICADO_DE_ANTECEDENTES, + EVALUACIÓN_PRE_EMPLEO, + FORMULARIO_DE_INSCRIPCIÓN_AL_SEGURO, + FORMULARIO_DE_CANCELACIÓN_DE_SEGURO, + TÍTULO_PROFESIONAL_1, + CERTIFICACIÓN_PROFESIONAL_1, + TÍTULO_PROFESIONAL_2, + CERTIFICACIÓN_PROFECIONAL_2, + TÍTULO_PROFESIONAL_3, + CERTIFICACIÓN_PROFECIONAL_3, + NORMATIVA_LABORAL_GENERAL, + NORMAS_DE_TRABAJO_REMOTO, + NORMAS_DE_SEGURIDAD, + INSTRUCTIVOS_DE_RECURSOS_HUMANOS, + MANUAL_DE_FUNCIONES_DE_ADMINISTRACIÓN, + MANUAL_DE_FUNCIONES_DE_INGENIERÍA, + LEY_GENERAL_DEL_TRABAJO, + DECRETOS_SUPREMOS, + RESOLUCIONES_O_DISPOSICIONES_REGLAMENTARIAS, + NORMATIVA_COMPLEMENTARIA, + LEY_GRAL_DE_HIGIENE_SALUD_SEGURIDAD_OCUPACIONAL_Y_BIENESTAR, + NORMATIVA_REGLAMENTARIA_PARA_DESARROLLO_DE_PASANTÍAS, + OTROS } diff --git a/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java b/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java index 6f0a2d0..3c8d1e2 100644 --- a/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java +++ b/src/main/java/com/primefactorsolutions/model/TimeOffRequestStatus.java @@ -10,7 +10,4 @@ public enum TimeOffRequestStatus { VENCIDO, SOLICITADO, - EN_REVISION, - COMPLETADO, - CANCELADO, } diff --git a/src/main/java/com/primefactorsolutions/views/DocumentView.java b/src/main/java/com/primefactorsolutions/views/DocumentView.java index 49c44a3..b1bbad9 100644 --- a/src/main/java/com/primefactorsolutions/views/DocumentView.java +++ b/src/main/java/com/primefactorsolutions/views/DocumentView.java @@ -39,9 +39,9 @@ import java.io.InputStream; @PageTitle("Document") @Route(value = "/documents/:documentId?/:action?", layout = MainLayout.class) public class DocumentView extends BeanValidationForm implements HasUrlParameter { - private final TextField fileName = new TextField("Document Name"); - private final ComboBox documentType = new ComboBox<>("Document Type"); - private final ComboBox employeeComboBox = new ComboBox<>("Employee"); + private final TextField fileName = new TextField("Nombre del documento"); + private final ComboBox documentType = new ComboBox<>("Tipo de documento"); + private final ComboBox employeeComboBox = new ComboBox<>("Empleado"); private final MemoryBuffer buffer = new MemoryBuffer(); private final Upload uploadButton = new Upload(buffer); private final DocumentService documentService; @@ -68,19 +68,19 @@ public class DocumentView extends BeanValidationForm implements HasUrl } protected Button createSaveButton() { - saveButton = new Button("Save"); + saveButton = new Button("Guardar"); saveButton.addClickListener(event -> saveDocument()); return saveButton; } protected Button createCloseButton() { - Button closeButton = new Button("Close"); + Button closeButton = new Button("Salir"); closeButton.addClickListener(event -> closeForm()); return closeButton; } protected Button createViewDocumentButton() { - viewDocumentButton = new Button("View Document"); + viewDocumentButton = new Button("Ver documento"); viewDocumentButton.setEnabled(false); viewDocumentButton.addClickListener(event -> viewDocument()); return viewDocumentButton; @@ -130,7 +130,7 @@ public class DocumentView extends BeanValidationForm implements HasUrl ui.getPage().open(registration.getResourceUri().toString()); }); } catch (IOException e) { - Notification.show("Error reading file."); + Notification.show("Error al leer el archivo."); } } @@ -148,10 +148,10 @@ public class DocumentView extends BeanValidationForm implements HasUrl setDocumentCreator(document); documentService.saveDocument(document); - Notification.show("File saved successfully."); + Notification.show("Archivo guardado correctamente."); clearForm(); } else { - Notification.show("Save failed: Please complete all fields and upload a file."); + Notification.show("Error al guardar: Por favor, complete todos los campos y cargue un archivo."); } } @@ -179,7 +179,7 @@ public class DocumentView extends BeanValidationForm implements HasUrl try { return buffer.getInputStream().readAllBytes(); } catch (IOException e) { - Notification.show("Error reading file data."); + Notification.show("Error al leer los datos del archivo."); return new byte[0]; } } @@ -220,13 +220,13 @@ public class DocumentView extends BeanValidationForm implements HasUrl uploadButton.setAcceptedFileTypes(".pdf"); uploadButton.addSucceededListener(event -> { fileUploaded = true; - Notification.show("File uploaded successfully."); + Notification.show("Archivo cargado correctamente."); viewDocumentButton.setEnabled(true); updateSaveButtonState(); }); uploadButton.getElement().addEventListener("file-remove", event -> { fileUploaded = false; - Notification.show("File removed."); + Notification.show("Archivo eliminado."); viewDocumentButton.setEnabled(false); updateSaveButtonState(); }); diff --git a/src/main/java/com/primefactorsolutions/views/DocumentsListView.java b/src/main/java/com/primefactorsolutions/views/DocumentsListView.java index 39803d0..6e247fb 100644 --- a/src/main/java/com/primefactorsolutions/views/DocumentsListView.java +++ b/src/main/java/com/primefactorsolutions/views/DocumentsListView.java @@ -47,7 +47,7 @@ public class DocumentsListView extends BaseView { } private void initializeView() { - getCurrentPageLayout().add(createActionButton("Add Document", this::navigateToAddDocumentView)); + getCurrentPageLayout().add(createActionButton("Añadir documento", this::navigateToAddDocumentView)); final HorizontalLayout hl = new HorizontalLayout(); hl.add(createDocumentTypeFilter()); @@ -61,7 +61,10 @@ public class DocumentsListView extends BaseView { private void configureDocumentGrid() { documentGrid.setColumns("fileName", "documentType", "creator"); - documentGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Employee"); + documentGrid.getColumnByKey("fileName").setHeader("Nombre archivo"); + documentGrid.getColumnByKey("documentType").setHeader("Tipo"); + documentGrid.getColumnByKey("creator").setHeader("Creador"); + documentGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Empleado"); addActionColumns(); configurePagination(); } @@ -73,9 +76,9 @@ public class DocumentsListView extends BaseView { } private void addActionColumns() { - addDocumentActionColumn("View", this::navigateToDocumentView); - addDocumentActionColumn("Edit", this::navigateToEditDocumentView); - addDocumentActionColumn("Download", this::downloadDocument); + addDocumentActionColumn("Ver", this::navigateToDocumentView); + addDocumentActionColumn("Editar", this::navigateToEditDocumentView); + addDocumentActionColumn("Descargar", this::downloadDocument); } private void addDocumentActionColumn(final String label, final DocumentActionHandler handler) { @@ -89,7 +92,7 @@ public class DocumentsListView extends BaseView { } private ComboBox createDocumentTypeFilter() { - documentTypeFilter = new ComboBox<>("Document Type"); + documentTypeFilter = new ComboBox<>("Tipo de documento"); documentTypeFilter.setItems(DocumentType.values()); documentTypeFilter.setValue(DocumentType.values()[0]); documentTypeFilter.addValueChangeListener(event -> { @@ -99,7 +102,7 @@ public class DocumentsListView extends BaseView { } private ComboBox createEmployeeFilter() { - employeeFilter = new ComboBox<>("Employee"); + employeeFilter = new ComboBox<>("Empleado"); List employees = employeeService.findAllEmployees(); employees.addFirst(createAllEmployeesOption()); employeeFilter.setItems(employees); @@ -113,12 +116,13 @@ public class DocumentsListView extends BaseView { private Employee createAllEmployeesOption() { Employee allEmployeesOption = new Employee(); - allEmployeesOption.setFirstName("All"); + allEmployeesOption.setFirstName("TODOS"); return allEmployeesOption; } private String getEmployeeLabel(final Employee employee) { - return employee.getFirstName().equals("All") ? "All" : employee.getFirstName() + " " + employee.getLastName(); + return employee.getFirstName().equals("TODOS") + ? "TODOS" : employee.getFirstName() + " " + employee.getLastName(); } private void navigateToEditDocumentView(final Document document) { diff --git a/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java b/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java index 5e8c437..0e27062 100644 --- a/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/RequestEmployeeView.java @@ -4,6 +4,7 @@ import com.primefactorsolutions.model.*; import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.TimeOffRequestService; import com.primefactorsolutions.service.VacationService; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.html.Div; @@ -16,11 +17,21 @@ import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.HasUrlParameter; 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 com.vaadin.flow.spring.annotation.SpringComponent; import jakarta.annotation.security.PermitAll; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.springframework.context.annotation.Scope; import org.vaadin.firitin.components.grid.PagingGrid; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; import java.time.LocalDate; import java.time.Period; import java.util.*; @@ -130,43 +141,47 @@ public class RequestEmployeeView extends Div implements HasUrlParameter Employee employee = employeeService.getEmployee(employeeId); boolean isMale = employee.getGender() == Employee.Gender.MALE; int currentYear = LocalDate.now().getYear(); + LocalDate currentDate = LocalDate.now(); List vacations = vacationService.findVacations(); - double healthLicence = 2; - List healthRequests = requestService - .findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD); - if (healthRequests != null && !healthRequests.isEmpty()) { - healthLicence = healthRequests.getLast().getDaysBalance(); - } + double healthLicence = getHealthLicence(employeeId); double totalFixedAndMovableHolidays = calculateHolidayDays(vacations); double totalPersonalDays = calculatePersonalDays(vacations, isMale); List vacationDays = calculateVacationDays(employee); - double utilizedVacationCurrentDays = vacationDays.get(1); - List vacationCurrentRequests = requestService - .findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ACTUAL); - if (vacationCurrentRequests != null && !vacationCurrentRequests.isEmpty()) { - utilizedVacationCurrentDays = vacationCurrentRequests.getLast().getDaysBalance(); - } - double totalVacationCurrentDays = vacationDays.get(1) - (vacationDays.get(1) - utilizedVacationCurrentDays); - - double utilizedVacationPreviousDays = vacationDays.get(0); - List vacationPreviousRequests = requestService - .findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ANTERIOR); - if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) { - utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance(); - } - double totalVacationPreviousDays = vacationDays.getFirst() - - (vacationDays.getFirst() - utilizedVacationPreviousDays); + double totalVacationCurrentDays = calculateUtilizedVacationDays( + vacationDays.get(1), + TimeOffRequestType.VACACION_GESTION_ACTUAL + ); + double totalVacationPreviousDays = calculateUtilizedVacationDays( + vacationDays.get(0), + TimeOffRequestType.VACACION_GESTION_ANTERIOR + ); double utilizedFixedAndMovableHolidays = calculateHolidayUtilizedDays(currentYear); double utilizedPersonalDays = calculatePersonalDaysUtilized(isMale, currentYear); - double remainingHolidayDays = totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays; - double remainingPersonalDays = (totalPersonalDays - utilizedPersonalDays) + healthLicence; - double remainingVacationDays = totalVacationCurrentDays + totalVacationPreviousDays; + double remainingHolidayDays = calculateRemainingHolidayDays( + totalFixedAndMovableHolidays, + utilizedFixedAndMovableHolidays, + employee.getDateOfExit(), + currentDate + ); + double remainingPersonalDays = calculateRemainingPersonalDays( + totalPersonalDays, + utilizedPersonalDays, + healthLicence, + employee.getDateOfExit(), + currentDate + ); + double remainingVacationDays = calculateRemainingVacationDays( + totalVacationCurrentDays, + totalVacationPreviousDays, + employee.getDateOfExit(), + currentDate + ); double totalAvailableDays = remainingHolidayDays + remainingPersonalDays + remainingVacationDays; @@ -178,6 +193,51 @@ public class RequestEmployeeView extends Div implements HasUrlParameter ); } + private double getHealthLicence(final UUID employeeId) { + List healthRequests = requestService + .findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD); + return healthRequests != null && !healthRequests.isEmpty() ? healthRequests.getLast().getDaysBalance() : 2; + } + + private double calculateUtilizedVacationDays(final double vacationDays, final TimeOffRequestType requestType) { + List vacationRequests = requestService.findByEmployeeAndCategory(employeeId, requestType); + if (vacationRequests != null && !vacationRequests.isEmpty()) { + return vacationRequests.getLast().getDaysBalance(); + } + return vacationDays; + } + + private double calculateRemainingVacationDays(final double totalVacationCurrentDays, + final double totalVacationPreviousDays, + final LocalDate exitDate, + final LocalDate currentDate) { + if (exitDate == null || exitDate.isAfter(currentDate)) { + return totalVacationCurrentDays + totalVacationPreviousDays; + } + return 0; + } + + private double calculateRemainingHolidayDays(final double totalFixedAndMovableHolidays, + final double utilizedFixedAndMovableHolidays, + final LocalDate exitDate, + final LocalDate currentDate) { + if (exitDate == null || exitDate.isAfter(currentDate)) { + return totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays; + } + return 0; + } + + private double calculateRemainingPersonalDays(final double totalPersonalDays, + final double utilizedPersonalDays, + final double healthLicence, + final LocalDate exitDate, + final LocalDate currentDate) { + if (exitDate == null || exitDate.isAfter(currentDate)) { + return (totalPersonalDays - utilizedPersonalDays) + healthLicence; + } + return 0; + } + private double calculateHolidayDays(final List vacations) { return vacations.stream() .filter(req -> req.getType() != Vacation.Type.OTHER) @@ -246,7 +306,8 @@ public class RequestEmployeeView extends Div implements HasUrlParameter private double calculateHolidayUtilizedDays(final int year) { return requests.stream() .filter(this::verificationIsHoliday) - .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO) + .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO + || req.getState() == TimeOffRequestStatus.APROBADO) .filter(req -> getStartDateYear(req) == year) .mapToDouble(TimeOffRequest::getDaysToBeTake) .sum(); @@ -255,7 +316,8 @@ public class RequestEmployeeView extends Div implements HasUrlParameter private double calculatePersonalDaysUtilized(final boolean isMale, final int year) { return requests.stream() .filter(req -> !verificationIsHoliday(req)) - .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO) + .filter(req -> req.getState() == TimeOffRequestStatus.TOMADO + || req.getState() == TimeOffRequestStatus.APROBADO) .filter(req -> !getStandardExclusions().contains(req.getCategory())) .filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory()))) .filter(req -> !req.getCategory().name().startsWith("VACACION")) @@ -294,9 +356,10 @@ public class RequestEmployeeView extends Div implements HasUrlParameter private HorizontalLayout createActionButtons() { Button viewButton = createButton("Ver", () -> navigateToViewRequest(request)); Button editButton = createButton("Editar", () -> navigateToEditRequest(request)); + Button downloadButton = new Button("Descargar reporte", event -> downloadReport()); Button closeButton = new Button("Salir", event -> navigateToRequestsListView()); - return new HorizontalLayout(viewButton, editButton, closeButton); + return new HorizontalLayout(viewButton, editButton, downloadButton, closeButton); } private Button createButton(final String caption, final Runnable action) { @@ -406,18 +469,164 @@ public class RequestEmployeeView extends Div implements HasUrlParameter return existingRequest.isEmpty(); } + private String getDateString(final LocalDate date) { + return (date != null) ? date.toString() : ""; + } + + private ByteArrayInputStream generatePdfReport() { + try (PDDocument document = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + PDPage page = new PDPage(); + document.addPage(page); + Employee employee = employeeService.getEmployee(employeeId); + + PDPageContentStream contentStream = null; + try { + contentStream = new PDPageContentStream(document, page); + + contentStream.setFont(PDType1Font.TIMES_BOLD, 18); + contentStream.beginText(); + contentStream.newLineAtOffset(200, 750); + contentStream.showText("Reporte de Vacaciones"); + contentStream.endText(); + + contentStream.setFont(PDType1Font.TIMES_ROMAN, 14); + contentStream.beginText(); + contentStream.newLineAtOffset(50, 700); + contentStream.showText("Empleado: " + employee.getFirstName() + " " + employee.getLastName()); + contentStream.newLineAtOffset(0, -15); + contentStream.showText("Equipo: " + employee.getTeam().getName()); + contentStream.endText(); + + float tableTopY = 650; + float margin = 50; + float cellHeight = 20; + String[] headers = {"Categoría", "Estado", "Fecha de Inicio", "Fecha de Fin", "Días a Tomar"}; + int columns = headers.length; + + float[] columnWidths = new float[columns]; + for (int i = 0; i < columns; i++) { + columnWidths[i] = getMaxColumnWidth(headers[i], requests, i); + } + + contentStream.setFont(PDType1Font.TIMES_BOLD, 10); + float currentX = margin; + for (int i = 0; i < columns; i++) { + contentStream.addRect(currentX, tableTopY, columnWidths[i], -cellHeight); + contentStream.beginText(); + contentStream.newLineAtOffset(currentX + 5, tableTopY - 15); + contentStream.showText(headers[i]); + contentStream.endText(); + currentX += columnWidths[i]; + } + contentStream.stroke(); + + contentStream.setFont(PDType1Font.TIMES_ROMAN, 10); + float currentY = tableTopY - cellHeight; + for (TimeOffRequest request : requests) { + String startDate = getDateString(request.getStartDate()); + String endDate = getDateString(request.getEndDate()); + + String[] rowData = { + request.getCategory().name() != null ? request.getCategory().name() : "", + request.getState().name() != null ? request.getState().name() : "", + startDate, + endDate, + String.valueOf(request.getDaysToBeTake() != null ? request.getDaysToBeTake() : 0) + }; + + currentX = margin; + for (int i = 0; i < columns; i++) { + contentStream.addRect(currentX, currentY, columnWidths[i], -cellHeight); + contentStream.beginText(); + contentStream.newLineAtOffset(currentX + 5, currentY - 15); + contentStream.showText(rowData[i]); + contentStream.endText(); + currentX += columnWidths[i]; + } + contentStream.stroke(); + currentY -= cellHeight; + + if (currentY < 50) { + contentStream.close(); + page = new PDPage(); + document.addPage(page); + contentStream = new PDPageContentStream(document, page); + currentY = 750; + } + } + } finally { + if (contentStream != null) { + contentStream.close(); + } + } + document.save(out); + return new ByteArrayInputStream(out.toByteArray()); + } catch (IOException e) { + throw new UncheckedIOException("Error al generar el reporte", e); + } + } + + private float getMaxColumnWidth(final String header, final List requests, final int columnIndex) { + float maxWidth = header.length(); + for (TimeOffRequest request : requests) { + String value = switch (columnIndex) { + case 0 -> request.getCategory().name(); + case 1 -> request.getState().name(); + case 2 -> getDateString(request.getStartDate()); + case 3 -> getDateString(request.getEndDate()); + case 4 -> String.valueOf(request.getDaysToBeTake()); + default -> ""; + }; + if (value != null) { + maxWidth = Math.max(maxWidth, value.length()); + } + } + return maxWidth * 7; + } + + private StreamResource generateVacationReport() { + Employee employee = employeeService.getEmployee(employeeId); + String fileName = String.format("%s_%s-reporte_de_vacaciones_%s.pdf", + employee.getFirstName(), + employee.getLastName(), + LocalDate.now()); + + ByteArrayInputStream pdfStream = generatePdfReport(); + + return new StreamResource(fileName, () -> pdfStream) + .setContentType("application/pdf") + .setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + } + + private void downloadReport() { + StreamResource resource = generateVacationReport(); + getUI().ifPresent(ui -> openDocumentStream(resource, ui)); + } + + private void openDocumentStream(final StreamResource resource, final UI ui) { + StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource); + ui.getPage().open(registration.getResourceUri().toString()); + } + @Override public void setParameter(final BeforeEvent event, final String parameter) { employeeId = UUID.fromString(parameter); Employee employee = employeeService.getEmployee(employeeId); requests = requestService.findRequestsByEmployeeId(employeeId); - setViewTitle(employee.getFirstName() + " " + employee.getLastName(), employee.getTeam().getName()); + setViewTitle( + employee.getFirstName() + " " + employee.getLastName(), + employee.getTeam().getName(), + employee.getDateOfExit() + ); requestGrid.setItems(requests); initializeView(); } - private void setViewTitle(final String employeeName, final String employeeTeam) { + private void setViewTitle(final String employeeName, final String employeeTeam, final LocalDate dateOfExit) { addComponentAsFirst(new H3("Nombre del empleado: " + employeeName)); addComponentAtIndex(1, new H3("Equipo: " + employeeTeam)); + if (dateOfExit != null) { + addComponentAtIndex(2, new H3("Descontado a cero en fecha " + dateOfExit + " por pago de finiquito.")); + } } } diff --git a/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java b/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java index 4d56f10..4f5ae22 100644 --- a/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java +++ b/src/main/java/com/primefactorsolutions/views/RequestRegisterView.java @@ -19,6 +19,7 @@ import jakarta.annotation.security.PermitAll; import org.springframework.context.annotation.Scope; import java.time.LocalDate; +import java.time.DayOfWeek; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Comparator; @@ -91,8 +92,25 @@ public class RequestRegisterView extends VerticalLayout { onCategoryChange(event.getValue()); handleCategorySelection(event.getValue()); }); - startDatePicker.addValueChangeListener(event -> updateDatePickerMinValues()); - endDatePicker.addValueChangeListener(event -> calculateDays()); + startDatePicker.addValueChangeListener(event -> { + LocalDate selectedDate = event.getValue(); + if (selectedDate != null && (selectedDate.getDayOfWeek().getValue() == 6 + || selectedDate.getDayOfWeek().getValue() == 7)) { + startDatePicker.setValue(selectedDate.minusDays(1)); + } + updateDatePickerMinValues(); + }); + endDatePicker.addValueChangeListener(event -> { + if (startDatePicker.getValue() != null) { + endDatePicker.setMin(startDatePicker.getValue()); + } + LocalDate selectedDate = event.getValue(); + if (selectedDate != null && (selectedDate.getDayOfWeek().getValue() == 6 + || selectedDate.getDayOfWeek().getValue() == 7)) { + endDatePicker.setValue(selectedDate.minusDays(1)); + } + calculateDays(); + }); } private void configureBinder() { @@ -167,7 +185,7 @@ public class RequestRegisterView extends VerticalLayout { private void onCategoryChange(final TimeOffRequestType selectedCategory) { if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL - || selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) { + || selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) { startDatePicker.setEnabled(true); endDatePicker.setEnabled(true); } else { @@ -178,6 +196,10 @@ public class RequestRegisterView extends VerticalLayout { private boolean isCategoryAvailable(final List employeeRequests, final TimeOffRequestType category) { + if (category == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() == null) { + return false; + } + List requestsByCategory = employeeRequests.stream() .filter(request -> request.getCategory() == category) .toList(); @@ -199,8 +221,8 @@ public class RequestRegisterView extends VerticalLayout { && latestRequest.getDaysBalance() > 0) || latestRequest.getState() == TimeOffRequestStatus.RECHAZADO || (latestRequest.getState() == TimeOffRequestStatus.TOMADO - && latestRequest.getDaysBalance() == 0 - && latestRequest.getExpiration().isBefore(LocalDate.now())); + && latestRequest.getDaysBalance() == 0 + && latestRequest.getExpiration().isBefore(LocalDate.now())); } else { return (latestRequest.getState() == TimeOffRequestStatus.TOMADO && latestRequest.getExpiration().isBefore(LocalDate.now())) @@ -375,6 +397,13 @@ public class RequestRegisterView extends VerticalLayout { Double availableDays = availableDaysField.getValue(); if (areDatesValid(startDate, endDate)) { + long workDays = countWorkDaysBetween(startDate, endDate); + + daysToBeTakenField.setValue((double) workDays); + + balanceDaysField.setValue(availableDaysField.getValue() - workDays); + + double daysToBeTaken = calculateDaysBetween(startDate, endDate); setDaysToBeTakenField(daysToBeTaken); @@ -384,6 +413,12 @@ public class RequestRegisterView extends VerticalLayout { if (balanceDays < 0.0) { clearFields(); } + + if (daysToBeTakenField.getValue() > 10 + && (categoryComboBox.getValue() == TimeOffRequestType.VACACION_GESTION_ANTERIOR + || categoryComboBox.getValue() == TimeOffRequestType.VACACION_GESTION_ACTUAL)) { + clearFields(); + } } } @@ -391,8 +426,19 @@ public class RequestRegisterView extends VerticalLayout { return startDate != null && endDate != null; } + private long countWorkDaysBetween(final LocalDate startDate, final LocalDate endDate) { + return startDate.datesUntil(endDate.plusDays(1)) + .filter(date -> date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY) + .count(); + } + private double calculateDaysBetween(final LocalDate startDate, final LocalDate endDate) { - return java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) + 1; + return startDate.datesUntil(endDate.plusDays(1)) + .filter(date -> { + DayOfWeek day = date.getDayOfWeek(); + return day != DayOfWeek.SATURDAY && day != DayOfWeek.SUNDAY; + }) + .count(); } private void setDaysToBeTakenField(final double daysToBeTaken) { @@ -456,9 +502,16 @@ public class RequestRegisterView extends VerticalLayout { handleExistingRequests(request); } - requestService.saveTimeOffRequest(request); - Notification.show("Solicitud guardada correctamente."); - closeForm(); + long differentDays = ChronoUnit.DAYS.between(LocalDate.now(), request.getStartDate()); + if (differentDays >= -15 && differentDays <= 90) { + requestService.saveTimeOffRequest(request); + Notification.show("Solicitud guardada correctamente."); + closeForm(); + } else { + Notification.show( + "La fecha de inicio debe encontrarse dentro del rango de 15 días a 3 meses de anticipación." + ); + } } private TimeOffRequest prepareRequest() { diff --git a/src/main/java/com/primefactorsolutions/views/RequestsListView.java b/src/main/java/com/primefactorsolutions/views/RequestsListView.java index 12b1715..c911edc 100644 --- a/src/main/java/com/primefactorsolutions/views/RequestsListView.java +++ b/src/main/java/com/primefactorsolutions/views/RequestsListView.java @@ -5,17 +5,26 @@ import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.TeamService; import com.primefactorsolutions.service.TimeOffRequestService; import com.primefactorsolutions.service.VacationService; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; 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 com.vaadin.flow.spring.annotation.SpringComponent; import jakarta.annotation.security.PermitAll; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.context.annotation.Scope; import org.vaadin.firitin.components.grid.PagingGrid; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; import java.time.LocalDate; import java.time.Period; import java.util.*; @@ -67,7 +76,6 @@ public class RequestsListView extends BaseView { hl.add(createEmployeeFilter()); hl.add(createTeamFilter()); hl.add(createStateFilter()); - getCurrentPageLayout().add(hl); } @@ -95,8 +103,9 @@ public class RequestsListView extends BaseView { Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE); } }); + Button downloadButton = new Button("Descargar reporte", event -> downloadReport()); Button closeButton = new Button("Salir", event -> navigateToMainView()); - return new HorizontalLayout(viewButton, closeButton); + return new HorizontalLayout(viewButton, downloadButton, closeButton); } private void refreshGeneralRequestGrid(final Employee employee, @@ -159,7 +168,7 @@ public class RequestsListView extends BaseView { private String getEmployeeStatus(final Employee employee) { Optional activeRequest = requestService .findByEmployeeAndState(employee.getId(), TimeOffRequestStatus.EN_USO); - return activeRequest.isPresent() ? "EN_DESCANSO" : "ACTIVO"; + return activeRequest.isPresent() ? "EN_DESCANSO" : "EN_FUNCIONES"; } private String getGeneralTotal(final Employee employee) { @@ -191,6 +200,13 @@ public class RequestsListView extends BaseView { double totalAvailable = calculateTotalAvailable(vacations, employeeRequests, employee); double generalTotal = totalAvailable + totalVacations - totalUtilized; + + if (employee.getDateOfExit() != null + && (employee.getDateOfExit().isBefore(LocalDate.now()) + || employee.getDateOfExit().isEqual(LocalDate.now()))) { + generalTotal = 0; + } + return String.valueOf(generalTotal); } @@ -217,6 +233,8 @@ public class RequestsListView extends BaseView { int currentYear = LocalDate.now().getYear(); return employeeRequests.stream() .filter(Objects::nonNull) + .filter(request -> request.getState() == TimeOffRequestStatus.APROBADO + || request.getState() == TimeOffRequestStatus.TOMADO) .filter(request -> request.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD) .filter(request -> request.getCategory() != TimeOffRequestType.VACACION_GESTION_ACTUAL) .filter(request -> request.getCategory() != TimeOffRequestType.VACACION_GESTION_ANTERIOR) @@ -388,7 +406,7 @@ public class RequestsListView extends BaseView { private enum Status { TODOS, EN_DESCANSO, - ACTIVO + EN_FUNCIONES } private Employee createAllEmployeesOption() { @@ -410,4 +428,50 @@ public class RequestsListView extends BaseView { private void navigateToTimeOffRequestView(final UUID idEmployee) { getUI().ifPresent(ui -> ui.navigate("requests/" + idEmployee.toString())); } + + private ByteArrayInputStream generateExcelReport(final List employees) { + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("REPORTE_GENERAL_DE_VACACIONES"); + Row headerRow = sheet.createRow(0); + + String[] headers = {"Empleado", "Equipo", "Estado", "Total Horas"}; + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + } + + int rowIndex = 1; + for (Employee employee : employees) { + Row row = sheet.createRow(rowIndex++); + row.createCell(0).setCellValue(getEmployeeFullName(employee)); + row.createCell(1).setCellValue(getTeamName(employee)); + row.createCell(2).setCellValue(getEmployeeStatus(employee)); + row.createCell(3).setCellValue(getGeneralTotal(employee)); + } + + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + workbook.write(out); + return new ByteArrayInputStream(out.toByteArray()); + } + } catch (IOException e) { + throw new UncheckedIOException("Error al generar el archivo Excel", e); + } + } + + private StreamResource generateGeneralVacationReport() { + List employees = employeeService.findAllEmployees(); + ByteArrayInputStream excelStream = generateExcelReport(employees); + return new StreamResource("reporte_general_de_vacaciones_" + LocalDate.now() + ".xlsx", + () -> excelStream); + } + + private void downloadReport() { + StreamResource resource = generateGeneralVacationReport(); + getUI().ifPresent(ui -> openDocumentStream(resource, ui)); + } + + private void openDocumentStream(final StreamResource resource, final UI ui) { + StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource); + ui.getPage().open(registration.getResourceUri().toString()); + } } \ No newline at end of file