refactor timeoff
All checks were successful
Builder / Build-Project (push) Successful in 2m59s

This commit is contained in:
alex 2024-12-30 19:41:39 -05:00
parent 9266a5f634
commit 87418e03e5
16 changed files with 1404 additions and 1957 deletions

Binary file not shown.

View File

@ -1,7 +1,6 @@
package com.primefactorsolutions.model;
public enum DocumentType {
TODOS,
CARNET_DE_IDENTIDAD,
RECIBOS_DE_PAGO,
CONTRATO_DE_TRABAJO,

View File

@ -1,13 +1,15 @@
package com.primefactorsolutions.model;
import com.google.common.collect.Lists;
import java.time.LocalDate;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.IsoFields;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.IntStream;
public record Week(LocalDate from, LocalDate to) {
@Override
@ -25,11 +27,15 @@ public record Week(LocalDate from, LocalDate to) {
public static List<Week> getLastWeeks(final int numberOfWeeks) {
final int weekNumber = LocalDate.now().get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
LocalDate from = getFirstDayOfWeek(weekNumber);
final ArrayList<Week> result = Lists.newArrayList();
return IntStream.range(0, numberOfWeeks).mapToObj(idx -> {
LocalDate from = getFirstDayOfWeek(weekNumber - idx);
return new Week(from, from.plusDays(6));
}).toList();
for (int i = 0; i < numberOfWeeks; i++) {
result.add(new Week(from, from.plusDays(6)));
from = from.minusDays(7);
}
return result;
}
private static LocalDate getFirstDayOfWeek(final int weekNumber) {

View File

@ -0,0 +1,44 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.views.util.AuthUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.spring.security.AuthenticationContext;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.Optional;
import java.util.UUID;
@SuppressWarnings("deprecation")
public abstract class BaseEntityForm<T> extends BeanValidationForm<T> {
private final AuthenticationContext authenticationContext;
public BaseEntityForm(final AuthenticationContext authenticationContext, final Class<T> entityType) {
super(entityType);
this.authenticationContext = authenticationContext;
}
@Override
public HorizontalLayout getToolbar() {
return new HorizontalLayout(getCancelButton(), getSaveButton());
}
protected Button getCancelButton() {
final Button cancelButton = new Button("Cancel");
cancelButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
UI.getCurrent().getPage().getHistory().back());
return cancelButton;
}
protected boolean isRoleAdmin() {
return AuthUtils.isAdmin(this.authenticationContext);
}
protected Optional<UUID> getEmployeeId() {
return AuthUtils.getEmployeeId(this.authenticationContext);
}
}

View File

@ -24,7 +24,6 @@ import elemental.json.JsonObject;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.springframework.security.core.userdetails.UserDetails;
import org.vaadin.firitin.form.BeanValidationForm;
import com.vaadin.flow.spring.security.AuthenticationContext;
import java.io.ByteArrayInputStream;
@ -39,7 +38,7 @@ import java.io.InputStream;
@Scope("prototype")
@PageTitle("Document")
@Route(value = "/documents/:documentId?/:action?", layout = MainLayout.class)
public class DocumentView extends BeanValidationForm<Document> implements HasUrlParameter<String> {
public class DocumentView extends BaseEntityForm<Document> implements HasUrlParameter<String> {
private final TextField fileName = new TextField("Nombre del documento");
private final ComboBox<DocumentType> documentType = new ComboBox<>("Tipo de documento");
private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Empleado");
@ -50,17 +49,18 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
private final EmployeeService employeeService;
private final AuthenticationContext authContext;
private boolean fileUploaded = false;
private Button saveButton;
private Button viewDocumentButton;
public DocumentView(final DocumentService documentService,
public DocumentView(final AuthenticationContext authenticationContext,
final DocumentService documentService,
final EmployeeService employeeService,
final AuthenticationContext authContext) {
super(Document.class);
super(authenticationContext, Document.class);
this.documentService = documentService;
this.employeeService = employeeService;
this.authContext = authContext;
initializeView();
this.setSavedHandler(this::saveDocument);
}
private void initializeView() {
@ -68,18 +68,6 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
configureUploadButton();
}
protected Button createSaveButton() {
saveButton = new Button("Guardar");
saveButton.addClickListener(event -> saveDocument());
return saveButton;
}
protected Button createCloseButton() {
Button closeButton = new Button("Salir");
closeButton.addClickListener(event -> closeForm());
return closeButton;
}
protected Button createViewDocumentButton() {
viewDocumentButton = new Button("Ver documento");
viewDocumentButton.setEnabled(false);
@ -135,13 +123,8 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
}
}
private void navigateToDocumentsListView() {
getUI().ifPresent(ui -> ui.navigate(DocumentsListView.class));
}
private void saveDocument() {
private void saveDocument(final Document document) {
if (isFormValid()) {
Document document = getEntity();
document.setFileName(fileName.getValue());
document.setDocumentType(documentType.getValue());
document.setEmployee(employeeComboBox.getValue());
@ -149,17 +132,12 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
setDocumentCreator(document);
documentService.saveDocument(document);
Notification.show("Archivo guardado correctamente.");
clearForm();
getUI().ifPresent(ui -> ui.navigate(DocumentsListView.class));
} else {
Notification.show("Error al guardar: Por favor, complete todos los campos y cargue un archivo.");
}
}
private void closeForm() {
navigateToDocumentsListView();
}
private boolean isFormValid() {
return !fileName.isEmpty()
&& documentType.getValue() != null
@ -167,15 +145,6 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
&& fileUploaded;
}
private void clearForm() {
fileName.clear();
documentType.clear();
employeeComboBox.clear();
fileUploaded = false;
uploadButton.getElement().setPropertyJson("files", Json.createArray());
viewDocumentButton.setEnabled(false);
}
private byte[] readFileData() {
try {
return buffer.getInputStream().readAllBytes();
@ -197,23 +166,10 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
fileUploaded = true;
}
private void updateSaveButtonState() {
boolean isModified = !fileName.getValue().equals(getEntity().getFileName())
|| documentType.getValue() != getEntity().getDocumentType()
|| employeeComboBox.getValue() != getEntity().getEmployee()
|| fileUploaded;
saveButton.setEnabled(isModified);
}
private void configureComponents() {
setFileNameProperties();
setDocumentTypeProperties();
setEmployeeComboBoxProperties();
fileName.addValueChangeListener(e -> updateSaveButtonState());
documentType.addValueChangeListener(e -> updateSaveButtonState());
employeeComboBox.addValueChangeListener(e -> updateSaveButtonState());
uploadButton.addSucceededListener(e -> updateSaveButtonState());
uploadButton.getElement().addEventListener("file-remove", event -> updateSaveButtonState());
}
private void configureUploadButton() {
@ -223,29 +179,14 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
fileUploaded = true;
Notification.show("Archivo cargado correctamente.");
viewDocumentButton.setEnabled(true);
updateSaveButtonState();
});
uploadButton.getElement().addEventListener("file-remove", event -> {
fileUploaded = false;
Notification.show("Archivo eliminado.");
viewDocumentButton.setEnabled(false);
updateSaveButtonState();
});
}
private void configureViewOrEditAction(final String action, final String documentIdString) {
if ("edit".equals(action) && !documentIdString.isEmpty()) {
setFieldsReadOnly(false);
preLoadFile(getEntity());
viewDocumentButton.setEnabled(true);
} else if ("view".equals(action) && !documentIdString.isEmpty()) {
setFieldsReadOnly(true);
preLoadFile(getEntity());
saveButton.setEnabled(false);
viewDocumentButton.setEnabled(true);
}
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
@ -257,18 +198,30 @@ public class DocumentView extends BeanValidationForm<Document> implements HasUrl
assert documentIdString != null;
UUID documentId = UUID.fromString(documentIdString);
Document document = documentService.getDocument(documentId);
setEntity(document);
employeeComboBox.setValue(document.getEmployee());
preLoadFile(document);
configureViewOrEditAction(action, documentIdString);
if ("edit".equals(action) && !documentIdString.isEmpty()) {
setEntityWithEnabledSave(document);
setFieldsReadOnly(false);
preLoadFile(document);
viewDocumentButton.setEnabled(true);
} else if ("view".equals(action) && !documentIdString.isEmpty()) {
setEntity(document);
setFieldsReadOnly(true);
preLoadFile(document);
viewDocumentButton.setEnabled(true);
}
}
}
@Override
protected List<Component> getFormComponents() {
HorizontalLayout buttonLayout = new HorizontalLayout();
final HorizontalLayout buttonLayout = new HorizontalLayout();
buttonLayout.add(uploadButton, createViewDocumentButton());
buttonLayout.setSpacing(true);
return List.of(fileName, documentType, employeeComboBox, pdfOnlyMessage, buttonLayout, createCloseButton());
return List.of(documentType, fileName, employeeComboBox, pdfOnlyMessage, buttonLayout);
}
}

View File

@ -11,6 +11,7 @@ import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.grid.GridSortOrder;
@ -60,7 +61,7 @@ public class DocumentsListView extends BaseView {
}
private void initializeView() {
getCurrentPageLayout().add(createActionButton("Añadir documento", this::navigateToAddDocumentView));
getCurrentPageLayout().add(createActionButton("Añadir documento", this::navigateToAddDocumentView, true));
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createDocumentTypeFilter());
@ -105,16 +106,22 @@ public class DocumentsListView extends BaseView {
});
}
private Button createActionButton(final String label, final Runnable onClickAction) {
private Button createActionButton(final String label, final Runnable onClickAction, final boolean isPrimary) {
Button actionButton = new Button(label);
if (isPrimary) {
actionButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
}
actionButton.addClickListener(event -> onClickAction.run());
return actionButton;
}
private ComboBox<DocumentType> createDocumentTypeFilter() {
documentTypeFilter = new ComboBox<>("Tipo de documento");
documentTypeFilter.setClearButtonVisible(true);
documentTypeFilter.setPlaceholder("Seleccionar ...");
documentTypeFilter.setItems(DocumentType.values());
documentTypeFilter.setValue(DocumentType.values()[0]);
documentTypeFilter.addValueChangeListener(event -> {
updateDocumentGrid(event.getValue(), employeeFilter.getValue());
});
@ -123,26 +130,19 @@ public class DocumentsListView extends BaseView {
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
employeeFilter.setPlaceholder("Seleccionar ...");
employeeFilter.setClearButtonVisible(true);
List<Employee> employees = employeeService.findAllEmployees();
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeLabel);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event -> {
updateDocumentGrid(documentTypeFilter.getValue(), event.getValue());
});
return employeeFilter;
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private String getEmployeeLabel(final Employee employee) {
return employee.getFirstName().equals("TODOS")
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
return employee.getFirstName() + " " + employee.getLastName();
}
private void navigateToEditDocumentView(final Document document) {

View File

@ -6,9 +6,8 @@ import com.primefactorsolutions.views.admin.TimeOffListView;
import com.primefactorsolutions.views.assessment.AssessmentsListView;
import com.primefactorsolutions.views.assessment.CandidatesListView;
import com.primefactorsolutions.views.assessment.QuestionsListView;
import com.primefactorsolutions.views.timeoff.TimeOffNewRequestView;
import com.primefactorsolutions.views.timeoff.TimeOffPendingRequestsListView;
import com.primefactorsolutions.views.timeoff.TimeOffRequestListView;
import com.primefactorsolutions.views.timeoff.TimeOffRequestsListView;
import com.primefactorsolutions.views.timeoff.TimeOffSummaryListView;
import com.primefactorsolutions.views.timesheet.TimesheetListView;
import com.primefactorsolutions.views.timesheet.TimesheetReportView;
import com.vaadin.flow.component.Component;
@ -142,10 +141,6 @@ public class MainLayout extends AppLayout {
if (isAdmin(authContext)) {
SideNavItem admin = new SideNavItem("Admin");
admin.setPrefixComponent(LineAwesomeIcon.BUILDING.create());
admin.addItem(new SideNavItem("Employees", EmployeesListView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create()));
admin.addItem(new SideNavItem("Documents", DocumentsListView.class,
LineAwesomeIcon.FILE_ALT_SOLID.create()));
admin.addItem(new SideNavItem("Calendario", TimeOffListView.class,
LineAwesomeIcon.CALENDAR.create()));
nav.addItem(admin);
@ -163,15 +158,10 @@ public class MainLayout extends AppLayout {
final SideNavItem timeOff = new SideNavItem("Time-off");
timeOff.setPrefixComponent(LineAwesomeIcon.PLANE_DEPARTURE_SOLID.create());
timeOff.addItem(new SideNavItem("Vacations", TimeOffRequestListView.class,
timeOff.addItem(new SideNavItem("Vacations", TimeOffSummaryListView.class,
LineAwesomeIcon.UMBRELLA_BEACH_SOLID.create()));
timeOff.addItem(new SideNavItem("Add TimeOff", TimeOffNewRequestView.class,
LineAwesomeIcon.CALENDAR_PLUS.create()));
if (isAdmin(authContext)) {
timeOff.addItem(new SideNavItem("Pending Requests", TimeOffPendingRequestsListView.class,
LineAwesomeIcon.LIST_ALT.create()));
}
timeOff.addItem(new SideNavItem("Requests", TimeOffRequestsListView.class,
LineAwesomeIcon.LIST_ALT.create()));
final SideNavItem timesheet = new SideNavItem("Timesheet");
timesheet.setPrefixComponent(LineAwesomeIcon.HOURGLASS_START_SOLID.create());
@ -183,8 +173,12 @@ public class MainLayout extends AppLayout {
LineAwesomeIcon.ID_CARD_SOLID.create()));
}
final SideNavItem profile = new SideNavItem("Profile", ProfileView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create());
final SideNavItem profile = new SideNavItem("Employee");
profile.setPrefixComponent(LineAwesomeIcon.USER_TIE_SOLID.create());
profile.addItem(new SideNavItem("Profiles", EmployeesListView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create()));
profile.addItem(new SideNavItem("Documents", DocumentsListView.class,
LineAwesomeIcon.FILE_ALT_SOLID.create()));
nav.addItem(profile);
nav.addItem(timesheet);

View File

@ -7,7 +7,9 @@ import com.primefactorsolutions.views.Constants;
import com.primefactorsolutions.views.EmployeeView;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
@ -45,8 +47,8 @@ public class EmployeesListView extends BaseView {
private void setupView() {
configureTable();
getCurrentPageLayout().add(createAddEmployeeButton());
getCurrentPageLayout().add(createExportButton());
final HorizontalLayout hl = new HorizontalLayout(createAddEmployeeButton(), createExportButton());
getCurrentPageLayout().add(hl);
getCurrentPageLayout().add(table);
}
@ -143,18 +145,23 @@ public class EmployeesListView extends BaseView {
}
private void addEditButtonColumn(final String label, final ButtonClickHandler handler) {
table.addComponentColumn(employee -> createButton(label, () -> handler.handle(employee)));
table.addComponentColumn(employee -> createButton(label, () -> handler.handle(employee), false));
}
private Button createButton(final String label, final Runnable onClickAction) {
private Button createButton(final String label, final Runnable onClickAction, final boolean isPrimary) {
Button button = new Button(label);
if (isPrimary) {
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
}
button.addClickListener(event -> onClickAction.run());
return button;
}
private Button createAddEmployeeButton() {
return createButton("Add Employee", this::navigateToAddEmployeeView);
return createButton("Add Employee", this::navigateToAddEmployeeView, true);
}
private void navigateToEditView(final Employee employee) {

View File

@ -1,560 +0,0 @@
package com.primefactorsolutions.views.timeoff;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.TimeOffService;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
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;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Request")
@Route(value = "/time-off/requests/new", layout = MainLayout.class)
public class TimeOffNewRequestView extends VerticalLayout {
private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Empleado");
private final ComboBox<TimeOffRequestType> categoryComboBox = new ComboBox<>("Categoría");
private final NumberField availableDaysField = new NumberField("Días disponibles");
private final DatePicker startDatePicker = new DatePicker("Fecha de inicio");
private final DatePicker endDatePicker = new DatePicker("Fecha final");
private final NumberField daysToBeTakenField = new NumberField("Días a tomar");
private final NumberField balanceDaysField = new NumberField("Días de saldo");
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TimeOffService timeOffService;
private final Binder<TimeOffRequest> binder;
private TimeOff timeoff;
private Employee employee;
private LocalDate endDate;
private Button saveButton;
private Button closeButton;
public TimeOffNewRequestView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TimeOffService timeOffService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.timeOffService = timeOffService;
this.binder = new Binder<>(TimeOffRequest.class);
initializeView();
}
private void initializeView() {
requestService.updateRequestStatuses();
configureFormFields();
configureButtons();
configureBinder();
setupFormLayout();
configureInitialFieldStates();
}
private void configureInitialFieldStates() {
categoryComboBox.setEnabled(false);
startDatePicker.setEnabled(false);
endDatePicker.setEnabled(false);
availableDaysField.setReadOnly(true);
daysToBeTakenField.setReadOnly(true);
balanceDaysField.setReadOnly(true);
}
private void configureFormFields() {
employeeComboBox.setItems(employeeService.findAllEmployees());
employeeComboBox.setItemLabelGenerator(emp -> emp.getFirstName() + " " + emp.getLastName());
employeeComboBox.addValueChangeListener(event -> {
employee = event.getValue();
handleEmployeeSelection(event.getValue());
});
categoryComboBox.addValueChangeListener(event -> {
onCategoryChange(event.getValue());
handleCategorySelection(event.getValue());
});
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() {
binder.forField(employeeComboBox)
.bind(TimeOffRequest::getEmployee, TimeOffRequest::setEmployee);
binder.forField(categoryComboBox)
.bind(TimeOffRequest::getCategory, TimeOffRequest::setCategory);
binder.forField(availableDaysField)
.bind(TimeOffRequest::getAvailableDays, TimeOffRequest::setAvailableDays);
binder.forField(startDatePicker)
.bind(TimeOffRequest::getStartDate, TimeOffRequest::setStartDate);
binder.forField(endDatePicker)
.bind(TimeOffRequest::getEndDate, TimeOffRequest::setEndDate);
binder.forField(daysToBeTakenField)
.bind(TimeOffRequest::getDaysToBeTake, TimeOffRequest::setDaysToBeTake);
binder.forField(balanceDaysField)
.bind(TimeOffRequest::getDaysBalance, TimeOffRequest::setDaysBalance);
binder.setBean(new TimeOffRequest());
}
private void handleEmployeeSelection(final Employee selectedEmployee) {
if (selectedEmployee != null) {
categoryComboBox.clear();
availableDaysField.clear();
startDatePicker.clear();
endDatePicker.clear();
daysToBeTakenField.clear();
balanceDaysField.clear();
categoryComboBox.setEnabled(true);
startDatePicker.setEnabled(false);
endDatePicker.setEnabled(false);
filterCategories(selectedEmployee);
}
}
private void filterCategories(final Employee employee) {
categoryComboBox.clear();
List<TimeOffRequest> employeeRequests = requestService.findRequestsByEmployeeId(employee.getId());
List<TimeOffRequestType> allCategories = Arrays.asList(TimeOffRequestType.values());
List<TimeOffRequestType> availableCategories = allCategories.stream()
.filter(category -> isCategoryAvailable(employeeRequests, category))
.filter(category -> isCategoryAllowedByGender(category, employee.getGender()))
.filter(category -> shouldIncludeVacationGestionActual(employeeRequests, category))
.filter(category -> shouldIncludeVacationGestionAnterior(employeeRequests, category))
.toList();
categoryComboBox.setItems(availableCategories);
}
private boolean shouldIncludeVacationGestionActual(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category != TimeOffRequestType.VACACION_GESTION_ACTUAL) {
return true;
}
return employeeRequests.stream()
.anyMatch(request -> request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
&& request.getDaysBalance() == 0
&& request.getState() == TimeOffRequestStatus.TOMADO);
}
private boolean shouldIncludeVacationGestionAnterior(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category != TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
return true;
}
return employeeRequests.stream()
.noneMatch(request -> request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
&& request.getDaysBalance() == 0);
}
private void onCategoryChange(final TimeOffRequestType selectedCategory) {
if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
startDatePicker.setEnabled(true);
endDatePicker.setEnabled(true);
} else {
startDatePicker.setEnabled(true);
endDatePicker.setEnabled(false);
}
}
private boolean isCategoryAvailable(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() == null) {
return false;
}
List<TimeOffRequest> requestsByCategory = employeeRequests.stream()
.filter(request -> request.getCategory() == category)
.toList();
if (requestsByCategory.isEmpty()) {
return true;
}
TimeOffRequest latestRequest = requestsByCategory.stream()
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.orElse(null);
boolean isSpecialCategory = category == TimeOffRequestType.PERMISOS_DE_SALUD
|| category == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| category == TimeOffRequestType.VACACION_GESTION_ANTERIOR;
if (isSpecialCategory) {
return (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getDaysBalance() > 0)
|| latestRequest.getState() == TimeOffRequestStatus.RECHAZADO
|| (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getDaysBalance() == 0
&& latestRequest.getExpiration().isBefore(LocalDate.now()));
} else {
return (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getExpiration().isBefore(LocalDate.now()))
|| latestRequest.getState() == TimeOffRequestStatus.RECHAZADO;
}
}
private boolean isCategoryAllowedByGender(final TimeOffRequestType category, final Employee.Gender gender) {
if (gender == Employee.Gender.MALE) {
return category != TimeOffRequestType.MATERNIDAD
&& category != TimeOffRequestType.DIA_DE_LA_MADRE
&& category != TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
&& category != TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL;
} else {
return category != TimeOffRequestType.DIA_DEL_PADRE
&& category != TimeOffRequestType.PATERNIDAD;
}
}
private void handleCategorySelection(final TimeOffRequestType selectedCategory) {
if (selectedCategory != null) {
updateAvailableDays(selectedCategory);
}
}
private void updateAvailableDays(final TimeOffRequestType selectedCategory) {
timeoff = timeOffService.findVacationByCategory(selectedCategory);
UUID employeeId = employeeComboBox.getValue().getId();
List<TimeOffRequest> requests = requestService.findByEmployeeAndCategory(employeeId, selectedCategory);
if (timeoff != null) {
TimeOffRequest requestWithBalance = requests.stream()
.filter(request -> request.getDaysBalance() > 0
&& request.getState() != TimeOffRequestStatus.VENCIDO
&& request.getState() != TimeOffRequestStatus.RECHAZADO)
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.orElse(null);
if (requestWithBalance != null) {
if (requestWithBalance.getState() == TimeOffRequestStatus.TOMADO
&& requestWithBalance.getDaysBalance() > 0) {
availableDaysField.setValue(requestWithBalance.getDaysBalance());
} else {
availableDaysField.setValue(timeoff.getDuration());
}
} else if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
LocalDate dateOfEntry = employeeComboBox.getValue().getDateOfEntry();
LocalDate currentDate = LocalDate.now();
long yearsOfService = ChronoUnit.YEARS.between(dateOfEntry, currentDate);
if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
yearsOfService -= 1;
}
if (yearsOfService > 10) {
availableDaysField.setValue(30.0);
} else if (yearsOfService > 5) {
availableDaysField.setValue(20.0);
} else if (yearsOfService > 1) {
availableDaysField.setValue(15.0);
} else {
availableDaysField.setValue(0.0);
}
} else {
availableDaysField.setValue(timeoff.getDuration());
}
setDatePickerLimits(timeoff);
}
}
private void setDatePickerLimits(final TimeOff timeoff) {
LocalDate startDate;
endDate = null;
UUID employeeId = employee.getId();
List<TimeOffRequest> previousRequests
= requestService.findByEmployeeAndCategory(employeeId, timeoff.getCategory());
int startYear = calculateStartYear(previousRequests);
startDate = determineStartDate(timeoff, startYear);
if (startDate.isBefore(LocalDate.now())) {
startDate = determineStartDate(timeoff, startYear + 1);
}
if (startDate != null) {
if (timeoff.getExpiration() != null) {
endDate = startDate.plusDays(timeoff.getExpiration().intValue() - 1);
} else {
endDate = LocalDate.of(startDate.getYear(), 12, 31);
}
} else {
startDate = LocalDate.now();
}
setPickerValues(timeoff, startDate);
setPickerLimits(startDate, endDate);
}
private int calculateStartYear(final List<TimeOffRequest> previousRequests) {
if (previousRequests.isEmpty()) {
return LocalDate.now().getYear();
}
int lastRequestYear = previousRequests.stream()
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.map(request -> request.getStartDate().getYear())
.orElse(LocalDate.now().getYear());
if (previousRequests.getLast().getState() != TimeOffRequestStatus.RECHAZADO) {
lastRequestYear = lastRequestYear + 1;
}
int currentYear = LocalDate.now().getYear();
return Math.max(lastRequestYear, currentYear);
}
private LocalDate determineStartDate(final TimeOff timeoff, final int startYear) {
if (timeoff.getCategory() == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() != null) {
return LocalDate.of(startYear, employee.getBirthday().getMonth(), employee.getBirthday().getDayOfMonth());
}
if (timeoff.getDate() != null) {
return LocalDate.of(startYear, timeoff.getDate().getMonthValue(), timeoff.getDate().getDayOfMonth());
}
return LocalDate.now();
}
private void setPickerValues(final TimeOff timeoff, final LocalDate startDate) {
startDatePicker.setValue(startDate);
if ((timeoff.getDuration() != null && timeoff.getDuration() == 0.5)
|| timeoff.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| timeoff.getCategory() == TimeOffRequestType.CUMPLEAÑOS) {
endDatePicker.setValue(startDate);
} else {
int durationDays = (timeoff.getDuration() != null ? timeoff.getDuration().intValue() - 1 : 0);
endDatePicker.setValue(startDate.plusDays(durationDays));
}
}
private void setPickerLimits(final LocalDate startDate, final LocalDate endDate) {
startDatePicker.setMin(startDate);
startDatePicker.setMax(endDate);
endDatePicker.setMin(startDate);
endDatePicker.setMax(endDate);
}
private void updateDatePickerMinValues() {
LocalDate startDate = startDatePicker.getValue();
if (availableDaysField.getValue() != null) {
if (availableDaysField.getValue() == 0.5) {
endDatePicker.setValue(startDate.plusDays(0));
} else {
endDatePicker.setValue(startDate.plusDays(availableDaysField.getValue().intValue() - 1));
}
calculateDays();
}
}
private void calculateDays() {
LocalDate startDate = startDatePicker.getValue();
LocalDate endDate = endDatePicker.getValue();
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);
double balanceDays = calculateBalanceDays(availableDays, daysToBeTakenField.getValue());
balanceDaysField.setValue(balanceDays);
if (balanceDays < 0.0) {
clearFields();
}
if (daysToBeTakenField.getValue() > 10
&& (categoryComboBox.getValue() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
|| categoryComboBox.getValue() == TimeOffRequestType.VACACION_GESTION_ACTUAL)) {
clearFields();
}
}
}
private boolean areDatesValid(final LocalDate startDate, final LocalDate endDate) {
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 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) {
if (timeoff.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| timeoff.getCategory() == TimeOffRequestType.CUMPLEAÑOS
|| timeoff.getCategory() == TimeOffRequestType.DIA_DEL_PADRE
|| timeoff.getCategory() == TimeOffRequestType.DIA_DE_LA_MADRE
|| timeoff.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
|| timeoff.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL) {
daysToBeTakenField.setValue(0.5);
} else {
daysToBeTakenField.setValue(daysToBeTaken);
}
}
private double calculateBalanceDays(final double availableDays, final double daysToBeTaken) {
return availableDays - daysToBeTaken;
}
private void clearFields() {
daysToBeTakenField.clear();
balanceDaysField.clear();
endDatePicker.clear();
}
private void configureButtons() {
saveButton = new Button("Guardar", event -> saveRequest());
closeButton = new Button("Salir", event -> closeForm());
}
private void setupFormLayout() {
add(
new H3("Añadir solicitud de vacaciones"),
employeeComboBox,
categoryComboBox,
availableDaysField,
startDatePicker,
endDatePicker,
daysToBeTakenField,
balanceDaysField,
new HorizontalLayout(saveButton, closeButton)
);
}
private void saveRequest() {
if (!binder.validate().isOk()) {
Notification.show("Rellene correctamente todos los campos obligatorios.");
return;
}
if (!validateForm()) {
Notification.show("Por favor, complete los campos antes de guardar");
return;
}
TimeOffRequest request = prepareRequest();
if (request.getCategory() == TimeOffRequestType.VACACION_GESTION_ACTUAL) {
handleVacationRequest(request);
} else {
handleExistingRequests(request);
}
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() {
TimeOffRequest request = binder.getBean();
request.setStartDate(startDatePicker.getValue());
request.setAvailableDays(availableDaysField.getValue());
request.setExpiration(endDate != null ? endDate : endDatePicker.getValue());
request.setState(TimeOffRequestStatus.SOLICITADO);
return request;
}
private void handleExistingRequests(final TimeOffRequest request) {
List<TimeOffRequest> existingRequests =
requestService.findByEmployeeAndCategory(employee.getId(), request.getCategory());
int maxRequests = request.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD
&& !request.getCategory().name().startsWith("VACACION")
&& request.getCategory() != TimeOffRequestType.CUMPLEAÑOS
? 2 : 1;
if (existingRequests.size() >= maxRequests) {
existingRequests.stream()
.min(Comparator.comparing(TimeOffRequest::getStartDate))
.ifPresent(oldestRequest -> requestService.deleteTimeOffRequest(oldestRequest.getId()));
}
}
private void handleVacationRequest(final TimeOffRequest request) {
List<TimeOffRequest> existingRequests = requestService.findByEmployeeAndCategory(
employee.getId(),
TimeOffRequestType.VACACION_GESTION_ACTUAL
);
if (!existingRequests.isEmpty()) {
TimeOffRequest existingRequest = existingRequests.getFirst();
existingRequest.setCategory(TimeOffRequestType.VACACION_GESTION_ANTERIOR);
requestService.saveTimeOffRequest(existingRequest);
}
}
private boolean validateForm() {
return employeeComboBox.getValue() != null
&& categoryComboBox.getValue() != null
&& startDatePicker.getValue() != null
&& endDatePicker.getValue() != null;
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate(TimeOffRequestListView.class));
}
}

View File

@ -1,508 +0,0 @@
package com.primefactorsolutions.views.timeoff;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.TimeOffService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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.contextmenu.MenuItem;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.menubar.MenuBar;
import com.vaadin.flow.component.menubar.MenuBarVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.function.ValueProvider;
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 com.vaadin.flow.spring.security.AuthenticationContext;
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.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
import static com.primefactorsolutions.views.util.MenuBarUtils.createIconItem;
@SpringComponent
@Scope("prototype")
@PageTitle("Requests")
@Route(value = "/time-off/requests", layout = MainLayout.class)
@PermitAll
public class TimeOffRequestListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final TimeOffService timeOffService;
private final PagingGrid<Employee> requestGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<TimeOffRequestType> categoryFilter;
private ComboBox<Status> stateFilter;
public TimeOffRequestListView(
final AuthenticationContext authenticationContext,
final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService,
final TimeOffService timeOffService) {
super(authenticationContext);
this.requestService = requestService;
this.employeeService = employeeService;
this.teamService = teamService;
this.timeOffService = timeOffService;
initializeView();
refreshGeneralRequestGrid(null, null, null, null);
}
private void initializeView() {
requestService.updateRequestStatuses();
if (isRoleAdmin()) {
final Button downloadButton = new Button("Descargar reporte", event -> downloadReport());
getCurrentPageLayout().add(downloadButton);
}
setupFilters();
setupRequestGrid();
getCurrentPageLayout().add(requestGrid);
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
hl.add(createCategoryFilter());
hl.add(createStateFilter());
getCurrentPageLayout().add(hl);
}
private void setupRequestGrid() {
requestGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
requestGrid.addColumn(this::getTeamName).setHeader("Equipo");
requestGrid.addColumn(this::getEmployeeStatus).setHeader("Estado del empleado");
requestGrid.addColumn(this::getCategory).setHeader("Categoria");
requestGrid.addColumn(this::getGeneralTotal).setHeader("Total general");
requestGrid.addComponentColumn((ValueProvider<Employee, Component>) employee -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem view = createIconItem(menuBar, VaadinIcon.EYE, "View");
view.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
navigateToTimeOffRequestView(employee.getId()));
return menuBar;
});
requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestGrid.setPageSize(PAGE_SIZE);
}
private void refreshGeneralRequestGrid(final Employee employee,
final Team team,
final TimeOffRequestType category,
final Status state) {
requestGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestGrid.getPageSize());
return fetchFilteredEmployees(start, pageSize, employee, team, category, state);
});
requestGrid.getDataProvider().refreshAll();
}
private List<Employee> fetchFilteredEmployees(final int start,
final int pageSize,
final Employee employee,
final Team team,
final TimeOffRequestType category,
final Status state) {
List<Employee> filteredEmployees = employeeService.findAllEmployees();
if (employee != null) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> emp.getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> emp.getTeam() != null && emp.getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (category != null) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> {
Optional<TimeOffRequest> request = requestService
.findByEmployeeAndState(emp.getId(), TimeOffRequestStatus.EN_USO);
return request.isPresent() && request.get().getCategory() == category;
})
.collect(Collectors.toList());
}
if (state != null) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> {
Optional<TimeOffRequest> request = requestService
.findByEmployeeAndState(emp.getId(), TimeOffRequestStatus.EN_USO);
return state == Status.EN_DESCANSO ? request.isPresent() : request.isEmpty();
})
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, filteredEmployees.size());
return filteredEmployees.subList(start, end);
}
private String getEmployeeFullName(final Employee employee) {
return employee.getFirstName() + " " + employee.getLastName();
}
private String getTeamName(final Employee employee) {
Team team = employee.getTeam();
return team != null ? team.getName() : "Sin asignar";
}
private String getTeamLabel(final Team team) {
return team.getName();
}
private String getEmployeeStatus(final Employee employee) {
Optional<TimeOffRequest> activeRequest = requestService
.findByEmployeeAndState(employee.getId(), TimeOffRequestStatus.EN_USO);
return activeRequest.isPresent() ? "EN_DESCANSO" : "EN_FUNCIONES";
}
private String getCategory(final Employee employee) {
Optional<TimeOffRequest> activeRequest = requestService
.findByEmployeeAndState(employee.getId(), TimeOffRequestStatus.EN_USO);
return activeRequest.map(request -> request.getCategory().toString()).orElse("");
}
private String getGeneralTotal(final Employee employee) {
List<TimeOffRequest> employeeRequests = requestService.findRequestsByEmployeeId(employee.getId());
List<TimeOff> timeOffs = timeOffService.findVacations();
List<Double> vacationDays = calculateVacationDays(employee);
double utilizedVacationCurrentDays = vacationDays.get(1);
List<TimeOffRequest> vacationCurrentRequests = requestService
.findByEmployeeAndCategory(employee.getId(), 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<TimeOffRequest> vacationPreviousRequests = requestService
.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.VACACION_GESTION_ANTERIOR);
if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) {
utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance();
}
double totalVacationPreviousDays = vacationDays.getFirst()
- (vacationDays.getFirst() - utilizedVacationPreviousDays);
double totalUtilized = calculateTotalUtilized(employeeRequests);
double totalVacations = totalVacationCurrentDays + totalVacationPreviousDays;
double totalAvailable = calculateTotalAvailable(timeOffs, 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);
}
private Set<TimeOffRequestType> getExcludedCategories() {
return Set.of(
TimeOffRequestType.MATERNIDAD,
TimeOffRequestType.PATERNIDAD,
TimeOffRequestType.MATRIMONIO,
TimeOffRequestType.DUELO_1ER_GRADO,
TimeOffRequestType.DUELO_2ER_GRADO,
TimeOffRequestType.DIA_DEL_PADRE,
TimeOffRequestType.DIA_DE_LA_MADRE
);
}
private Set<TimeOffRequestType> getGenderSpecificExclusions() {
return Set.of(
TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL,
TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL
);
}
private double calculateTotalUtilized(final List<TimeOffRequest> employeeRequests) {
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)
.filter(request -> request.getStartDate() != null && (
request.getStartDate().getYear() == currentYear
|| (request.getCategory().name().startsWith("VACACION")
&& request.getStartDate().getYear() == currentYear - 1)
))
.mapToDouble(request -> request.getDaysToBeTake() != null ? request.getDaysToBeTake() : 0.0)
.sum();
}
private List<Double> calculateVacationDays(final Employee employee) {
List<Double> vacationDays = new ArrayList<>();
if (employee.getDateOfEntry() != null) {
LocalDate entryDate = employee.getDateOfEntry();
LocalDate today = LocalDate.now();
boolean hasAnniversaryPassed = entryDate.getMonthValue() < today.getMonthValue()
|| (entryDate.getMonthValue() == today.getMonthValue() && entryDate.getDayOfMonth()
<= today.getDayOfMonth());
LocalDate previousVacationYearDate;
LocalDate currentVacationYearDate;
if (hasAnniversaryPassed) {
previousVacationYearDate = LocalDate.of(
today.getYear() - 1,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
currentVacationYearDate = LocalDate.of(
today.getYear(),
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
} else {
previousVacationYearDate = LocalDate.of(
today.getYear() - 2,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
currentVacationYearDate = LocalDate.of(
today.getYear() - 1,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
}
vacationDays.add(calculateVacationDaysSinceEntry(entryDate, previousVacationYearDate));
vacationDays.add(calculateVacationDaysSinceEntry(entryDate, currentVacationYearDate));
} else {
vacationDays.add(0.0);
vacationDays.add(0.0);
}
return vacationDays;
}
private double calculateTotalAvailable(final List<TimeOff> timeOffs, final List<TimeOffRequest> employeeRequests,
final Employee employee) {
Set<TimeOffRequestType> excludedCategories = getExcludedCategories();
Set<TimeOffRequestType> genderSpecificExclusions = getGenderSpecificExclusions();
Set<TimeOffRequestType> employeeRequestCategories = employeeRequests.stream()
.map(TimeOffRequest::getCategory)
.collect(Collectors.toSet());
double healthLicence = 2;
List<TimeOffRequest> healthRequests = requestService
.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.PERMISOS_DE_SALUD);
if (healthRequests != null && !healthRequests.isEmpty()) {
healthLicence = healthRequests.getLast().getDaysBalance();
}
double totalAvailable = timeOffs.stream()
.filter(Objects::nonNull)
.filter(vacation -> vacation.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.filter(vacation -> shouldIncludeVacation(
vacation,
excludedCategories,
genderSpecificExclusions,
employee, employeeRequestCategories
))
.mapToDouble(vacation -> vacation.getDuration() != null ? vacation.getDuration() : 0.0)
.sum();
return totalAvailable + healthLicence;
}
private double calculateVacationDaysSinceEntry(final LocalDate dateOfEntry, final LocalDate date) {
int yearsOfService = dateOfEntry != null ? Period.between(dateOfEntry, date).getYears() : 0;
if (yearsOfService > 10) {
return 30;
}
if (yearsOfService > 5) {
return 20;
}
if (yearsOfService > 1) {
return 15;
}
return 0;
}
private boolean shouldIncludeVacation(final TimeOff timeoff,
final Set<TimeOffRequestType> excludedCategories,
final Set<TimeOffRequestType> genderSpecificExclusions,
final Employee employee,
final Set<TimeOffRequestType> employeeRequestCategories) {
if (excludedCategories.contains(timeoff.getCategory())
&& !employeeRequestCategories.contains(timeoff.getCategory())) {
return false;
}
return isFemale(employee) || !genderSpecificExclusions.contains(timeoff.getCategory());
}
private boolean isFemale(final Employee employee) {
return employee.getGender() == Employee.Gender.FEMALE;
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
final List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employeeFilter.setPlaceholder("TODOS");
employeeFilter.setClearButtonVisible(true);
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
event.getValue(),
teamFilter.getValue(),
categoryFilter.getValue(),
stateFilter.getValue()
)
);
return employeeFilter;
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
teamFilter.setPlaceholder("TODOS");
teamFilter.setClearButtonVisible(true);
final List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
employeeFilter.getValue(),
event.getValue(),
categoryFilter.getValue(),
stateFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Category");
categoryFilter.setClearButtonVisible(true);
categoryFilter.setPlaceholder("TODOS");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue(),
stateFilter.getValue()
)
);
return categoryFilter;
}
private ComboBox<Status> createStateFilter() {
stateFilter = new ComboBox<>("Estado del empleado");
stateFilter.setPlaceholder("TODOS");
stateFilter.setClearButtonVisible(true);
stateFilter.setItems(Status.values());
stateFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
categoryFilter.getValue(),
event.getValue()
)
);
return stateFilter;
}
private enum Status {
EN_DESCANSO,
EN_FUNCIONES
}
private void navigateToTimeOffRequestView(final UUID idEmployee) {
getUI().ifPresent(ui -> ui.navigate(TimeOffRequestsByEmployeeView.class, idEmployee.toString()));
}
private ByteArrayInputStream generateExcelReport(final List<Employee> 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<Employee> 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());
}
}

View File

@ -3,112 +3,515 @@ package com.primefactorsolutions.views.timeoff;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.TimeOffService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
@SuppressWarnings("deprecation")
import static com.primefactorsolutions.views.util.ComponentUtils.withFullWidth;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Request")
@Route(value = "/requests/:requestId?/:action?", layout = MainLayout.class)
public class TimeOffRequestView extends BeanValidationForm<TimeOffRequest> implements HasUrlParameter<String> {
public class TimeOffRequestView extends BaseEntityForm<TimeOffRequest> implements HasUrlParameter<String> {
private final ComboBox<TimeOffRequestStatus> state = new ComboBox<>("Estado de la solicitud");
private final DatePicker expiration = new DatePicker("Vencimiento");
private final DatePicker startDate = new DatePicker("Fecha de inicio");
private final DatePicker endDate = new DatePicker("Fecha de fin");
private final NumberField availableDays = new NumberField("Días disponibles");
private final NumberField daysToBeTake = new NumberField("Días a tomar");
private final ComboBox<TimeOffRequestType> category = withFullWidth(new ComboBox<>("Categoría"));
private final ComboBox<TimeOffRequestStatus> state = withFullWidth(new ComboBox<>("Estado de la solicitud"));
private final DatePicker expiration = withFullWidth(new DatePicker("Vencimiento"));
private final DatePicker startDate = withFullWidth(new DatePicker("Fecha de inicio"));
private final DatePicker endDate = withFullWidth(new DatePicker("Fecha de fin"));
private final NumberField daysToBeTake = withFullWidth(new NumberField("Días a tomar"));
private final NumberField daysBalance = withFullWidth(new NumberField("Días disponibles"));
private final TimeOffRequestService requestService;
private final TimeOffService timeOffService;
private final TimeOffRequestService timeOffRequestService;
private final EmployeeService employeeService;
private TimeOffRequest request;
private Employee employee;
public TimeOffRequestView(final TimeOffRequestService requestService,
public TimeOffRequestView(final AuthenticationContext authenticationContext,
final TimeOffRequestService timeOffRequestService,
final TimeOffService timeOffService,
final EmployeeService employeeService) {
super(TimeOffRequest.class);
this.requestService = requestService;
super(authenticationContext, TimeOffRequest.class);
this.timeOffService = timeOffService;
this.timeOffRequestService = timeOffRequestService;
this.employeeService = employeeService;
state.setItems(List.of(TimeOffRequestStatus.values()));
this.setSavedHandler(this::saveRequest);
initView();
}
private void initView() {
category.addValueChangeListener(event -> {
onCategoryChange(event.getValue());
handleCategorySelection(event.getValue());
});
startDate.addValueChangeListener(event -> {
LocalDate selectedDate = event.getValue();
if (selectedDate != null && (selectedDate.getDayOfWeek().getValue() == 6
|| selectedDate.getDayOfWeek().getValue() == 7)) {
startDate.setValue(selectedDate.minusDays(1));
}
updateDatePickerMinValues();
});
endDate.addValueChangeListener(event -> {
if (endDate.getValue() != null) {
endDate.setMin(endDate.getValue());
}
final LocalDate selectedDate = event.getValue();
if (selectedDate != null && (selectedDate.getDayOfWeek().getValue() == 6
|| selectedDate.getDayOfWeek().getValue() == 7)) {
endDate.setValue(selectedDate.minusDays(1));
}
calculateDays();
});
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String requestIdString = params.get("requestId").orElse(null);
final UUID employeeId = getEmployeeId().get();
employee = employeeService.getEmployee(employeeId);
filterCategories(employee);
if ("new".equals(action)) {
setEntityWithEnabledSave(new TimeOffRequest());
} else {
assert requestIdString != null;
UUID requestId = UUID.fromString(requestIdString);
request = requestService.findTimeOffRequest(requestId);
UUID employeeId = request.getEmployee().getId();
employee = employeeService.getEmployee(employeeId);
setEntity(request);
configureViewOrEditAction(action);
TimeOffRequest timeOffRequest = timeOffRequestService.findTimeOffRequest(requestId);
if ("edit".equals(action)) {
setEntityWithEnabledSave(timeOffRequest);
setFieldsReadOnly(false);
} else if ("view".equals(action)) {
setEntity(timeOffRequest);
setFieldsReadOnly(true);
}
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(
createEmployeeHeader(),
createTeamHeader(),
createCategoryHeader(),
category,
state,
expiration,
startDate,
endDate,
availableDays,
daysBalance,
daysToBeTake
);
}
private void filterCategories(final Employee employee) {
category.clear();
List<TimeOffRequest> employeeRequests = timeOffRequestService.findRequestsByEmployeeId(employee.getId());
List<TimeOffRequestType> allCategories = Arrays.asList(TimeOffRequestType.values());
List<TimeOffRequestType> availableCategories = allCategories.stream()
.filter(category -> isCategoryAvailable(employeeRequests, category))
.filter(category -> isCategoryAllowedByGender(category, employee.getGender()))
.filter(category -> shouldIncludeVacationGestionActual(employeeRequests, category))
.filter(category -> shouldIncludeVacationGestionAnterior(employeeRequests, category))
.toList();
category.setItems(availableCategories);
}
private boolean shouldIncludeVacationGestionActual(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category != TimeOffRequestType.VACACION_GESTION_ACTUAL) {
return true;
}
return employeeRequests.stream()
.anyMatch(request -> request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
&& request.getDaysBalance() == 0
&& request.getState() == TimeOffRequestStatus.TOMADO);
}
private boolean shouldIncludeVacationGestionAnterior(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category != TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
return true;
}
return employeeRequests.stream()
.noneMatch(request -> request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
&& request.getDaysBalance() == 0);
}
private void onCategoryChange(final TimeOffRequestType selectedCategory) {
if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
startDate.setEnabled(true);
endDate.setEnabled(true);
} else {
startDate.setEnabled(true);
endDate.setEnabled(false);
}
}
private boolean isCategoryAvailable(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() == null) {
return false;
}
List<TimeOffRequest> requestsByCategory = employeeRequests.stream()
.filter(request -> request.getCategory() == category)
.toList();
if (requestsByCategory.isEmpty()) {
return true;
}
TimeOffRequest latestRequest = requestsByCategory.stream()
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.orElse(null);
boolean isSpecialCategory = category == TimeOffRequestType.PERMISOS_DE_SALUD
|| category == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| category == TimeOffRequestType.VACACION_GESTION_ANTERIOR;
if (isSpecialCategory) {
return (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getDaysBalance() > 0)
|| latestRequest.getState() == TimeOffRequestStatus.RECHAZADO
|| (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getDaysBalance() == 0
&& latestRequest.getExpiration().isBefore(LocalDate.now()));
} else {
return (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getExpiration().isBefore(LocalDate.now()))
|| latestRequest.getState() == TimeOffRequestStatus.RECHAZADO;
}
}
private boolean isCategoryAllowedByGender(final TimeOffRequestType category, final Employee.Gender gender) {
if (gender == Employee.Gender.MALE) {
return category != TimeOffRequestType.MATERNIDAD
&& category != TimeOffRequestType.DIA_DE_LA_MADRE
&& category != TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
&& category != TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL;
} else {
return category != TimeOffRequestType.DIA_DEL_PADRE
&& category != TimeOffRequestType.PATERNIDAD;
}
}
private void handleCategorySelection(final TimeOffRequestType selectedCategory) {
if (selectedCategory != null) {
updateAvailableDays(selectedCategory);
}
}
private void updateAvailableDays(final TimeOffRequestType selectedCategory) {
final TimeOff timeoff = timeOffService.findVacationByCategory(selectedCategory);
final List<TimeOffRequest> requests =
timeOffRequestService.findByEmployeeAndCategory(employee.getId(), selectedCategory);
final TimeOffRequest requestWithBalance = requests.stream()
.filter(request -> request.getDaysBalance() > 0
&& request.getState() != TimeOffRequestStatus.VENCIDO
&& request.getState() != TimeOffRequestStatus.RECHAZADO)
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.orElse(null);
if (requestWithBalance != null) {
if (requestWithBalance.getState() == TimeOffRequestStatus.TOMADO
&& requestWithBalance.getDaysBalance() > 0) {
daysBalance.setValue(requestWithBalance.getDaysBalance());
} else {
daysBalance.setValue(timeoff.getDuration());
}
} else if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
final LocalDate dateOfEntry = employee.getDateOfEntry();
final LocalDate currentDate = LocalDate.now();
long yearsOfService = ChronoUnit.YEARS.between(dateOfEntry, currentDate);
if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
yearsOfService -= 1;
}
if (yearsOfService > 10) {
daysBalance.setValue(30.0);
} else if (yearsOfService > 5) {
daysBalance.setValue(20.0);
} else if (yearsOfService > 1) {
daysBalance.setValue(15.0);
} else {
daysBalance.setValue(0.0);
}
} else {
daysBalance.setValue(timeoff.getDuration());
}
setDatePickerLimits(timeoff);
}
private void setDatePickerLimits(final TimeOff timeoff) {
LocalDate startDateValue;
LocalDate endDateValue = null;
final UUID employeeId = employee.getId();
final List<TimeOffRequest> previousRequests
= timeOffRequestService.findByEmployeeAndCategory(employeeId, timeoff.getCategory());
final int startYear = calculateStartYear(previousRequests);
startDateValue = determineStartDate(timeoff, startYear);
if (startDateValue.isBefore(LocalDate.now())) {
startDateValue = determineStartDate(timeoff, startYear + 1);
}
if (startDateValue != null) {
if (timeoff.getExpiration() != null) {
endDateValue = startDateValue.plusDays(timeoff.getExpiration().intValue() - 1);
} else {
endDateValue = LocalDate.of(startDateValue.getYear(), 12, 31);
}
} else {
startDateValue = LocalDate.now();
}
setPickerValues(timeoff, startDateValue);
setPickerLimits(startDateValue, endDateValue);
}
private int calculateStartYear(final List<TimeOffRequest> previousRequests) {
if (previousRequests.isEmpty()) {
return LocalDate.now().getYear();
}
int lastRequestYear = previousRequests.stream()
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.map(request -> request.getStartDate().getYear())
.orElse(LocalDate.now().getYear());
if (previousRequests.getLast().getState() != TimeOffRequestStatus.RECHAZADO) {
lastRequestYear = lastRequestYear + 1;
}
int currentYear = LocalDate.now().getYear();
return Math.max(lastRequestYear, currentYear);
}
private LocalDate determineStartDate(final TimeOff timeoff, final int startYear) {
if (timeoff.getCategory() == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() != null) {
return LocalDate.of(startYear, employee.getBirthday().getMonth(), employee.getBirthday().getDayOfMonth());
}
if (timeoff.getDate() != null) {
return LocalDate.of(startYear, timeoff.getDate().getMonthValue(), timeoff.getDate().getDayOfMonth());
}
return LocalDate.now();
}
private void setPickerValues(final TimeOff timeoff, final LocalDate startDateValue) {
startDate.setValue(startDateValue);
if ((timeoff.getDuration() != null && timeoff.getDuration() == 0.5)
|| timeoff.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| timeoff.getCategory() == TimeOffRequestType.CUMPLEAÑOS) {
endDate.setValue(startDateValue);
} else {
int durationDays = (timeoff.getDuration() != null ? timeoff.getDuration().intValue() - 1 : 0);
endDate.setValue(startDateValue.plusDays(durationDays));
}
}
private void setPickerLimits(final LocalDate startDateValue, final LocalDate endDateValue) {
startDate.setMin(startDateValue);
startDate.setMax(endDateValue);
endDate.setMin(startDateValue);
endDate.setMax(endDateValue);
}
private void updateDatePickerMinValues() {
LocalDate startDateValue = startDate.getValue();
if (daysBalance.getValue() != null) {
if (daysBalance.getValue() == 0.5) {
endDate.setValue(startDateValue.plusDays(0));
} else {
endDate.setValue(startDateValue.plusDays(daysBalance.getValue().intValue() - 1));
}
calculateDays();
}
}
private void calculateDays() {
LocalDate startDateValue = startDate.getValue();
LocalDate endDateValue = endDate.getValue();
Double availableDaysValue = daysBalance.getValue();
if (areDatesValid(startDateValue, endDateValue)) {
long workDays = countWorkDaysBetween(startDateValue, endDateValue);
daysToBeTake.setValue((double) workDays);
daysBalance.setValue(daysBalance.getValue() - workDays);
double daysToBeTaken = calculateDaysBetween(startDateValue, endDateValue);
setDaysToBeTakenField(daysToBeTaken);
double balanceDays = calculateBalanceDays(availableDaysValue, daysToBeTake.getValue());
daysBalance.setValue(balanceDays);
if (balanceDays < 0.0) {
clearFields();
}
if (daysToBeTake.getValue() > 10
&& (category.getValue() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
|| category.getValue() == TimeOffRequestType.VACACION_GESTION_ACTUAL)) {
clearFields();
}
}
}
private void clearFields() {
daysToBeTake.clear();
daysBalance.clear();
endDate.clear();
}
private boolean areDatesValid(final LocalDate startDate, final LocalDate endDate) {
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 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) {
final TimeOffRequest timeOffRequest = getEntity();
if (timeOffRequest.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| timeOffRequest.getCategory() == TimeOffRequestType.CUMPLEAÑOS
|| timeOffRequest.getCategory() == TimeOffRequestType.DIA_DEL_PADRE
|| timeOffRequest.getCategory() == TimeOffRequestType.DIA_DE_LA_MADRE
|| timeOffRequest.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
|| timeOffRequest.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL) {
daysToBeTake.setValue(0.5);
} else {
daysToBeTake.setValue(daysToBeTaken);
}
}
private double calculateBalanceDays(final double availableDays, final double daysToBeTaken) {
return availableDays - daysToBeTaken;
}
private void setFieldsReadOnly(final boolean option) {
state.setReadOnly(option);
expiration.setReadOnly(option);
startDate.setReadOnly(option);
endDate.setReadOnly(option);
availableDays.setReadOnly(option);
daysBalance.setReadOnly(option);
daysToBeTake.setReadOnly(option);
}
private void saveRequest(final TimeOffRequest request) {
if (isFormValid()) {
setRequestFieldValues(request);
requestService.saveTimeOffRequest(request);
if (!isFormValid()) {
Notification.show("Por favor, complete los campos antes de guardar");
return;
}
prepareRequest(request);
if (request.getCategory() == TimeOffRequestType.VACACION_GESTION_ACTUAL) {
handleVacationRequest(request);
} else {
handleExistingRequests(request);
}
long differentDays = ChronoUnit.DAYS.between(LocalDate.now(), request.getStartDate());
if (differentDays >= -15 && differentDays <= 90) {
timeOffRequestService.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 void setRequestFieldValues(final TimeOffRequest request) {
request.setState(state.getValue());
request.setExpiration(expiration.getValue());
private void prepareRequest(final TimeOffRequest request) {
request.setStartDate(startDate.getValue());
request.setEndDate(endDate.getValue());
request.setAvailableDays(availableDays.getValue());
request.setDaysToBeTake(daysToBeTake.getValue());
request.setAvailableDays(daysBalance.getValue());
request.setExpiration(endDate.getValue());
request.setState(TimeOffRequestStatus.SOLICITADO);
}
private void handleExistingRequests(final TimeOffRequest request) {
final List<TimeOffRequest> existingRequests =
timeOffRequestService.findByEmployeeAndCategory(employee.getId(), request.getCategory());
int maxRequests = request.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD
&& !request.getCategory().name().startsWith("VACACION")
&& request.getCategory() != TimeOffRequestType.CUMPLEAÑOS
? 2 : 1;
if (existingRequests.size() >= maxRequests) {
existingRequests.stream()
.min(Comparator.comparing(TimeOffRequest::getStartDate))
.ifPresent(oldestRequest -> timeOffRequestService.deleteTimeOffRequest(oldestRequest.getId()));
}
}
private void handleVacationRequest(final TimeOffRequest request) {
final List<TimeOffRequest> existingRequests = timeOffRequestService.findByEmployeeAndCategory(
employee.getId(),
TimeOffRequestType.VACACION_GESTION_ACTUAL
);
if (!existingRequests.isEmpty()) {
TimeOffRequest existingRequest = existingRequests.getFirst();
existingRequest.setCategory(TimeOffRequestType.VACACION_GESTION_ANTERIOR);
timeOffRequestService.saveTimeOffRequest(existingRequest);
}
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate(TimeOffRequestsByEmployeeView.class, employee.getId().toString()));
getUI().ifPresent(ui -> ui.navigate(TimeOffRequestsListView.class));
}
private boolean isFormValid() {
@ -116,27 +519,7 @@ public class TimeOffRequestView extends BeanValidationForm<TimeOffRequest> imple
&& expiration.getValue() != null
&& startDate.getValue() != null
&& endDate.getValue() != null
&& availableDays.getValue() != null
&& daysBalance.getValue() != null
&& daysToBeTake.getValue() != null;
}
private void configureViewOrEditAction(final String action) {
if ("edit".equals(action) && !request.getId().toString().isEmpty()) {
setFieldsReadOnly(false);
} else if ("view".equals(action) && !request.getId().toString().isEmpty()) {
setFieldsReadOnly(true);
}
}
private H3 createEmployeeHeader() {
return new H3("Empleado: " + employee.getFirstName() + " " + employee.getLastName());
}
private H3 createTeamHeader() {
return new H3("Equipo: " + employee.getTeam().getName());
}
private H3 createCategoryHeader() {
return new H3("Categoría: " + request.getCategory());
}
}

View File

@ -12,6 +12,7 @@ import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.icon.VaadinIcon;
@ -46,36 +47,43 @@ import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("Pending Requests")
@Route(value = "/time-off/requests/pending", layout = MainLayout.class)
@PageTitle("Time-Off Requests")
@Route(value = "/time-off/requests", layout = MainLayout.class)
@PermitAll
public class TimeOffPendingRequestsListView extends BaseView {
public class TimeOffRequestsListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<TimeOffRequest> pendingRequestsGrid = new PagingGrid<>();
private final PagingGrid<TimeOffRequest> requestsGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<TimeOffRequestType> categoryFilter;
public TimeOffPendingRequestsListView(final AuthenticationContext authenticationContext,
final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
public TimeOffRequestsListView(final AuthenticationContext authenticationContext,
final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
super(authenticationContext);
this.requestService = requestService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeView();
refreshGeneralPendingRequestsGrid(null, null, null);
refreshGeneralRequestsGrid(null, null, null);
}
private void initializeView() {
Button downloadButton = new Button("Descargar reporte de rechazos", event -> downloadReport());
getCurrentPageLayout().add(downloadButton);
final Button newRequestButton = new Button("Crear nueva peticion", event -> navigateToAddNew());
newRequestButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
final Button downloadReportButton = new Button("Descargar reporte de rechazos", event -> downloadReport());
final HorizontalLayout hl = new HorizontalLayout(newRequestButton, downloadReportButton);
getCurrentPageLayout().add(hl);
setupFilters();
setupPendingRequestsGrid();
setupRequestsGrid();
}
private void navigateToAddNew() {
getUI().ifPresent(ui -> ui.navigate(TimeOffRequestView.class, "new"));
}
private void setupFilters() {
@ -87,11 +95,11 @@ public class TimeOffPendingRequestsListView extends BaseView {
getCurrentPageLayout().add(hl);
}
private void setupPendingRequestsGrid() {
pendingRequestsGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
pendingRequestsGrid.addColumn(this::getTeamName).setHeader("Equipo");
pendingRequestsGrid.addColumn(this::getCategory).setHeader("Categoría");
pendingRequestsGrid.addComponentColumn((ValueProvider<TimeOffRequest, Component>) timeOffRequest -> {
private void setupRequestsGrid() {
requestsGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
requestsGrid.addColumn(this::getTeamName).setHeader("Equipo");
requestsGrid.addColumn(this::getCategory).setHeader("Categoría");
requestsGrid.addComponentColumn((ValueProvider<TimeOffRequest, Component>) timeOffRequest -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem approveItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.CHECK, "Aprobar");
@ -103,58 +111,58 @@ public class TimeOffPendingRequestsListView extends BaseView {
return menuBar;
});
pendingRequestsGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
pendingRequestsGrid.setPageSize(PAGE_SIZE);
requestsGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestsGrid.setPageSize(PAGE_SIZE);
getCurrentPageLayout().add(pendingRequestsGrid);
getCurrentPageLayout().add(requestsGrid);
}
private void actionForRequest(final UUID selectedRequestId, final TimeOffRequestStatus status) {
TimeOffRequest request = requestService.findTimeOffRequest(selectedRequestId);
request.setState(status);
requestService.saveTimeOffRequest(request);
refreshGeneralPendingRequestsGrid(null, null, null);
refreshGeneralRequestsGrid(null, null, null);
}
private void refreshGeneralPendingRequestsGrid(final Employee employee,
final Team team,
final TimeOffRequestType category) {
pendingRequestsGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * pendingRequestsGrid.getPageSize());
return fetchFilteredPendingRequests(start, pageSize, employee, team, category);
private void refreshGeneralRequestsGrid(final Employee employee,
final Team team,
final TimeOffRequestType category) {
requestsGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestsGrid.getPageSize());
return fetchFilteredRequests(start, pageSize, employee, team, category);
});
pendingRequestsGrid.getDataProvider().refreshAll();
requestsGrid.getDataProvider().refreshAll();
}
private List<TimeOffRequest> fetchFilteredPendingRequests(final int start,
final int pageSize,
final Employee employee,
final Team team,
final TimeOffRequestType category) {
List<TimeOffRequest> filteredPendingRequests
= requestService.findRequestsByState(TimeOffRequestStatus.SOLICITADO);
private List<TimeOffRequest> fetchFilteredRequests(final int start,
final int pageSize,
final Employee employee,
final Team team,
final TimeOffRequestType category) {
List<TimeOffRequest> filteredRequests
= requestService.findAllTimeOffRequests();
if (employee != null) {
filteredPendingRequests = filteredPendingRequests.stream()
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null) {
filteredPendingRequests = filteredPendingRequests.stream()
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getEmployee().getTeam() != null
&& emp.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (category != null) {
filteredPendingRequests = filteredPendingRequests.stream()
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getCategory().equals(category))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, filteredPendingRequests.size());
return filteredPendingRequests.subList(start, end);
int end = Math.min(start + pageSize, filteredRequests.size());
return filteredRequests.subList(start, end);
}
private String getEmployeeFullName(final TimeOffRequest request) {
@ -163,8 +171,7 @@ public class TimeOffPendingRequestsListView extends BaseView {
}
private String getEmployeeFullNameLabel(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
return employee.getFirstName() + " " + employee.getLastName();
}
private String getTeamName(final TimeOffRequest request) {
@ -173,7 +180,7 @@ public class TimeOffPendingRequestsListView extends BaseView {
}
private String getTeamLabel(final Team team) {
return "TODOS".equals(team.getName()) ? "TODOS" : team.getName();
return team.getName();
}
private String getCategory(final TimeOffRequest request) {
@ -183,13 +190,13 @@ public class TimeOffPendingRequestsListView extends BaseView {
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
employeeFilter.setClearButtonVisible(true);
employeeFilter.setPlaceholder("TODOS");
employeeFilter.setPlaceholder("Seleccionar ...");
final List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullNameLabel);
employeeFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
refreshGeneralRequestsGrid(
event.getValue(),
teamFilter.getValue(),
categoryFilter.getValue()
@ -201,12 +208,12 @@ public class TimeOffPendingRequestsListView extends BaseView {
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
teamFilter.setClearButtonVisible(true);
teamFilter.setPlaceholder("TODOS");
teamFilter.setPlaceholder("Seleccionar ...");
final List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
refreshGeneralRequestsGrid(
employeeFilter.getValue(),
event.getValue(),
categoryFilter.getValue()
@ -217,11 +224,11 @@ public class TimeOffPendingRequestsListView extends BaseView {
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Categoría");
categoryFilter.setPlaceholder("TODOS");
categoryFilter.setPlaceholder("Seleccionar ...");
categoryFilter.setClearButtonVisible(true);
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
refreshGeneralRequestsGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue()

View File

@ -6,6 +6,7 @@ import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimesheetService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.NumberField;
@ -13,10 +14,10 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.datepicker.VDatePicker;
import org.vaadin.firitin.form.BeanValidationForm;
import java.time.LocalDate;
import java.time.YearMonth;
@ -25,13 +26,12 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@SuppressWarnings("deprecation")
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Horas Trabajadas")
@Route(value = "/timesheet/:hours-workedId?/:action?", layout = MainLayout.class)
public class TimesheetEntryView extends BeanValidationForm<TimesheetEntry> implements HasUrlParameter<String> {
public class TimesheetEntryView extends BaseEntityForm<TimesheetEntry> implements HasUrlParameter<String> {
private final ComboBox<Team> team = new ComboBox<>("Equipo");
private final ComboBox<Employee> employee = new ComboBox<>("Empleado");
private final ComboBox<String> task = new ComboBox<>("Tarea");
@ -45,10 +45,11 @@ public class TimesheetEntryView extends BeanValidationForm<TimesheetEntry> imple
private TimesheetEntry timesheetEntry;
public TimesheetEntryView(final TimesheetService timesheetService,
public TimesheetEntryView(final AuthenticationContext authenticationContext,
final TimesheetService timesheetService,
final EmployeeService employeeService,
final TeamService teamService) {
super(TimesheetEntry.class);
super(authenticationContext, TimesheetEntry.class);
this.timesheetService = timesheetService;
this.employeeService = employeeService;
this.teamService = teamService;

View File

@ -176,7 +176,7 @@ public class TimesheetListView extends BaseView {
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
employeeFilter.setPlaceholder("Seleccionar empleado ...");
employeeFilter.setPlaceholder("Seleccionar ...");
employeeFilter.setClearButtonVisible(true);
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
@ -214,7 +214,7 @@ public class TimesheetListView extends BaseView {
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
final List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamFilter.setPlaceholder("Seleccionar equipo ...");
teamFilter.setPlaceholder("Seleccionar ...");
teamFilter.setClearButtonVisible(true);
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);

View File

@ -0,0 +1,13 @@
package com.primefactorsolutions.views.util;
import com.vaadin.flow.component.HasSize;
import lombok.experimental.UtilityClass;
@UtilityClass
public class ComponentUtils {
public static <T extends HasSize> T withFullWidth(final T component) {
component.setWidthFull();
return component;
}
}