diff --git a/src/main/java/com/primefactorsolutions/model/Employee.java b/src/main/java/com/primefactorsolutions/model/Employee.java index e128132..1d3df41 100644 --- a/src/main/java/com/primefactorsolutions/model/Employee.java +++ b/src/main/java/com/primefactorsolutions/model/Employee.java @@ -1,147 +1,167 @@ - package com.primefactorsolutions.model; - import com.google.common.collect.Lists; - import jakarta.persistence.*; - import lombok.AllArgsConstructor; - import lombok.Data; - import lombok.EqualsAndHashCode; - import lombok.NoArgsConstructor; - import org.springframework.security.core.GrantedAuthority; - import org.springframework.security.core.userdetails.UserDetails; +package com.primefactorsolutions.model; - import java.time.LocalDate; - import java.util.Collection; +import com.google.common.collect.Lists; +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; - @Data - @Entity - @AllArgsConstructor - @NoArgsConstructor - @EqualsAndHashCode(callSuper = true) - public class Employee extends BaseEntity implements UserDetails { - private String username; - private String firstName; - private String lastName; - private LocalDate birthday; - private String birthCity; - private String age; +import java.time.LocalDate; +import java.util.Collection; - private String residenceAddress; - private String localAddress; - private String phoneNumber; - private String personalEmail; - private String position; - @ManyToOne - @JoinColumn(name = "team_id", nullable = false) - private Team team; - private String emergencyCName; - private String emergencyCAddress; - private String emergencyCPhone; - private String emergencyCEmail; - private String numberOfChildren; +@Data +@Entity +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class Employee extends BaseEntity implements UserDetails { - private String ci; - private String issuedIn; + private String username; + @NotNull(message = "El nombre no puede estar vacío") + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El nombre solo debe contener letras") + private String firstName; + @NotNull(message = "El apellido no puede estar vacío") + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El apellido solo debe contener letras") + private String lastName; + private LocalDate birthday; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "La ciudad de nacimiento solo debe contener letras") + private String birthCity; + private String age; + @Size(max = 100, message = "La dirección de residencia no debe exceder 100 caracteres") + private String residenceAddress; + @Size(max = 100, message = "La dirección local no debe exceder 100 caracteres") + private String localAddress; + @Pattern(regexp = "^[0-9]+$", message = "El número de teléfono debe contener solo números") + private String phoneNumber; + @Email(message = "El correo personal no tiene un formato válido") + private String personalEmail; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El cargo solo debe contener letras") + private String position; + @ManyToOne + @JoinColumn(name = "team_id", nullable = false) + private Team team; + @Size(max = 100, message = "El nombre de contacto de emergencia no debe exceder 100 caracteres") + private String emergencyCName; + @Size(max = 100, message = "La dirección de contacto de emergencia no debe exceder 100 caracteres") + private String emergencyCAddress; + @Pattern(regexp = "^[0-9]+$", message = "El teléfono de contacto de emergencia debe contener solo números") + private String emergencyCPhone; + @Email(message = "El correo de contacto de emergencia no tiene un formato válido") + private String emergencyCEmail; + @Pattern(regexp = "^[0-9]+$", message = "La cantidad de hijos debe contener solo números") + private String numberOfChildren; + @Pattern(regexp = "^[0-9]+$", message = "El CI debe contener solo números") + private String ci; + private String issuedIn; + @Size(max = 100, message = "El título no debe exceder 100 caracteres") + private String pTitle1; + private String pTitle2; + private String pTitle3; - private String pTitle1; - private String pTitle2; - private String pTitle3; + private String pStudy1; + private String pStudy2; + private String pStudy3; - private String pStudy1; - private String pStudy2; - private String pStudy3; + private String certification1; + private String certification2; + private String certification3; + private String certification4; + @Size(max = 255, message = "El reconocimiento no debe exceder 255 caracteres") + private String recognition; + @Size(max = 500, message = "Los logros no deben exceder 500 caracteres") + private String achievements; - private String certification1; - private String certification2; - private String certification3; - private String certification4; + private String language; + private String languageLevel; + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "El código debe contener solo letras y números") + private String cod; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El lead manager solo debe contener letras") + private String leadManager; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El proyecto solo debe contener letras") + private String project; - private String recognition; - private String achievements; + private LocalDate dateOfEntry; + private LocalDate dateOfExit; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El tipo de contrato solo debe contener letras") + private String contractType; + @Pattern(regexp = "^[0-9]+$", message = "La antigüedad debe contener solo números") + private String seniority; + @Pattern(regexp = "^[0-9]+(\\.[0-9]{1,2})?$", message = "El salario debe ser un número con hasta dos decimales") + private String salary; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "El nombre del banco solo debe contener letras") + private String bankName; + @Pattern(regexp = "^[0-9]+$", message = "El número de cuenta debe contener solo números") + private String accountNumber; - private String language; - private String languageLevel; + private String gpss; + private String sss; + @Pattern(regexp = "^[a-zA-Z ]+$", message = "Los derechohabientes solo deben contener letras") + private String beneficiaries; - private String cod; - private String leadManager; - private String project; + @Column(columnDefinition = "TEXT") + private String profileImage; + @Enumerated(EnumType.STRING) + private Status status; - private LocalDate dateOfEntry; - private LocalDate dateOfExit; - - private String contractType; - private String seniority; - private String salary; - - private String bankName; - private String accountNumber; - - private String gpss; - private String sss; - private String beneficiaries; - - @Column(columnDefinition = "TEXT") - private String profileImage; - @Enumerated(EnumType.STRING) - private Status status; - - @Override - public Collection getAuthorities() { - return Lists.newArrayList(); - } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return this.username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - public enum Status { - ACTIVE, - INACTIVE - } - @Enumerated(EnumType.STRING) - private MaritalStatus maritalStatus; - public enum MaritalStatus { - SINGLE, - MARRIED, - WIDOWED, - DIVORCED - } - @Enumerated(EnumType.STRING) - private Gender gender; - public enum Gender { - MALE, - FEMALE - } - - public Status getStatus() { - - return status; - } - public void setStatus(final Status status) { - this.status = status; - } + @Override + public Collection getAuthorities() { + return Lists.newArrayList(); } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public enum Status { + ACTIVE, + INACTIVE + } + + @Enumerated(EnumType.STRING) + private MaritalStatus maritalStatus; + + public enum MaritalStatus { + SINGLE, + MARRIED, + WIDOWED, + DIVORCED + } + + @Enumerated(EnumType.STRING) + private Gender gender; + + public enum Gender { + MALE, + FEMALE + } +} diff --git a/src/main/java/com/primefactorsolutions/views/EmployeeView.java b/src/main/java/com/primefactorsolutions/views/EmployeeView.java index 448b5b5..43e01f7 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeeView.java @@ -1,10 +1,10 @@ package com.primefactorsolutions.views; -import com.primefactorsolutions.model.Employee; -import com.primefactorsolutions.model.Team; +import com.primefactorsolutions.model.*; import com.primefactorsolutions.service.EmployeeService; import com.primefactorsolutions.service.ReportService; import com.primefactorsolutions.service.TeamService; +import com.primefactorsolutions.service.TimeOffRequestService; import com.vaadin.componentfactory.pdfviewer.PdfViewer; import com.vaadin.flow.component.ClickEvent; import com.vaadin.flow.component.Component; @@ -24,6 +24,9 @@ import com.vaadin.flow.component.textfield.EmailField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MemoryBuffer; +import com.vaadin.flow.data.binder.Result; +import com.vaadin.flow.data.binder.ValueContext; +import com.vaadin.flow.data.converter.Converter; import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.router.*; import com.vaadin.flow.server.StreamResource; @@ -36,6 +39,8 @@ import org.vaadin.firitin.form.BeanValidationForm; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.List; import java.util.UUID; @@ -49,8 +54,10 @@ public class EmployeeView extends BeanValidationForm implements HasUrl private final EmployeeService employeeService; private final ReportService reportService; + private final TimeOffRequestService requestService; private final TeamService teamService; + // TODO: campo usado para registrar al empleado en LDAP. Este campo podria estar en otro form eventualmente. private final TextField username = createTextField("Username: ", 30, true); private final TextField firstName = createTextField("Nombres: ", 30, true); @@ -140,10 +147,12 @@ public class EmployeeView extends BeanValidationForm implements HasUrl public EmployeeView(final EmployeeService employeeService, final ReportService reportService, - final TeamService teamService) { + final TeamService teamService, + final TimeOffRequestService requestService) { super(Employee.class); this.employeeService = employeeService; this.reportService = reportService; + this.requestService = requestService; this.teamService = teamService; saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); @@ -156,12 +165,18 @@ public class EmployeeView extends BeanValidationForm implements HasUrl phoneNumber.addValueChangeListener(e -> validatePhoneNumber(phoneNumber, e.getValue())); emergencyCPhone.setValueChangeMode(ValueChangeMode.EAGER); emergencyCPhone.addValueChangeListener(e -> validatePhoneNumber(emergencyCPhone, e.getValue())); + + firstName.setValueChangeMode(ValueChangeMode.EAGER); + firstName.addValueChangeListener(e -> validateNameField(firstName, e.getValue())); + lastName.setValueChangeMode(ValueChangeMode.EAGER); + lastName.addValueChangeListener(e -> validateNameField(lastName, e.getValue())); + createTeamComboBox(); + configureUpload(); saveButton.setVisible(true); editButton.setVisible(true); reportButton.setVisible(true); birthday.addValueChangeListener(event -> calculateAge()); - createTeamComboBox(); reportButton.addClickListener((ComponentEventListener>) buttonClickEvent -> { var employee = getEntity(); @@ -174,6 +189,15 @@ public class EmployeeView extends BeanValidationForm implements HasUrl initDialog(); } + private void validateNameField(TextField textField, String value) { + if (!value.matches("^[a-zA-ZáéíóúÁÉÍÓÚñÑ\\s]*$")) { + textField.setInvalid(true); + textField.setErrorMessage("Este campo solo debe contener letras."); + } else { + textField.setInvalid(false); + } + } + private void calculateAge() { if (birthday.getValue() != null) { int currentYear = java.time.LocalDate.now().getYear(); @@ -244,7 +268,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl ComboBox comboBox = new ComboBox<>("Estado"); comboBox.setItems(Employee.Status.values()); comboBox.setItemLabelGenerator(Employee.Status::name); - comboBox.setRequiredIndicatorVisible(true); // Indicador de campo requerido + comboBox.setRequiredIndicatorVisible(true); return comboBox; } @@ -276,6 +300,14 @@ public class EmployeeView extends BeanValidationForm implements HasUrl team.setWidthFull(); } + private ComboBox createComboBox(final String label, final T[] items) { + ComboBox comboBox = new ComboBox<>(label); + comboBox.setItems(items); + comboBox.setItemLabelGenerator(Object::toString); + comboBox.setWidthFull(); + return comboBox; + } + private ComboBox createGenderComboBox() { ComboBox comboBox = new ComboBox<>("Genero"); comboBox.setItems(Employee.Gender.values()); @@ -288,12 +320,33 @@ public class EmployeeView extends BeanValidationForm implements HasUrl return !firstName.isEmpty() && !lastName.isEmpty() && status.getValue() != null; } + private void setVacationDuration(Employee employee, TimeOffRequest request, LocalDate referenceDate) { + double yearsOfService = ChronoUnit.YEARS.between(employee.getDateOfEntry(), referenceDate); + request.setAvailableDays(calculateAvailableDays(yearsOfService)); + } + + private double calculateAvailableDays(double yearsOfService) { + if (yearsOfService > 10) { + return 30.0; + } else if (yearsOfService > 5) { + return 20.0; + } else if (yearsOfService > 1) { + return 15.0; + } else { + return 0.0; + } + } + private void saveEmployee() { if (validateForm()) { Employee employee = getEntity(); employee.setStatus(status.getValue()); employee.setAge(age.getValue()); + if (employee.getDateOfEntry() != null) { + processTimeOffRequests(employee); + } + employeeService.createOrUpdate(employee); Notification.show(NOTIFICATION_SAVE_SUCCESS); getUI().ifPresent(ui -> ui.navigate(EmployeesListView.class)); @@ -302,13 +355,60 @@ public class EmployeeView extends BeanValidationForm implements HasUrl } } + private void processTimeOffRequests(Employee employee) { + if (hasTimeOffRequests(employee)) { + updateExistingTimeOffRequests(employee); + } else { + createAndSaveTimeOffRequests(employee); + } + } + + private boolean hasTimeOffRequests(Employee employee) { + return !requestService.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.VACACION_GESTION_ACTUAL).isEmpty() || + !requestService.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.VACACION_GESTION_ANTERIOR).isEmpty(); + } + + private void updateExistingTimeOffRequests(Employee employee) { + updateTimeOffRequest(employee, TimeOffRequestType.VACACION_GESTION_ANTERIOR, -1, 729); + updateTimeOffRequest(employee, TimeOffRequestType.VACACION_GESTION_ACTUAL, 0, 1094); + } + + private void updateTimeOffRequest(Employee employee, TimeOffRequestType category, int yearOffset, int daysToAdd) { + List requests = requestService.findByEmployeeAndCategory(employee.getId(), category); + if (!requests.isEmpty()) { + TimeOffRequest request = requests.getLast(); + LocalDate newBaseDate = LocalDate.of(LocalDate.now().getYear() + yearOffset, employee.getDateOfEntry().getMonth(), employee.getDateOfEntry().getDayOfMonth()); + request.setExpiration(newBaseDate.plusDays(daysToAdd)); + setVacationDuration(employee, request, newBaseDate); + requestService.saveTimeOffRequest(request); + } + } + + private void createAndSaveTimeOffRequests(Employee employee) { + requestService.saveTimeOffRequest(createTimeOffRequest(employee, TimeOffRequestType.VACACION_GESTION_ANTERIOR, -1, 729)); + requestService.saveTimeOffRequest(createTimeOffRequest(employee, TimeOffRequestType.VACACION_GESTION_ACTUAL, 0, 1094)); + } + + private TimeOffRequest createTimeOffRequest(Employee employee, TimeOffRequestType category, int yearOffset, int daysToAdd) { + LocalDate baseDate = LocalDate.of(LocalDate.now().getYear() + yearOffset, employee.getDateOfEntry().getMonth(), employee.getDateOfEntry().getDayOfMonth()); + LocalDate expirationDate = baseDate.plusDays(daysToAdd); + + TimeOffRequest request = new TimeOffRequest(); + request.setCategory(category); + request.setState(TimeOffRequestStatus.APROBADO); + request.setEmployee(employee); + request.setExpiration(expirationDate); + setVacationDuration(employee, request, baseDate); + + return request; + } + private void enableEditMode() { setFieldsEditable(); saveButton.setVisible(true); editButton.setVisible(false); } - @Override public void setParameter(final BeforeEvent beforeEvent, final String action) { final RouteParameters params = beforeEvent.getRouteParameters(); @@ -323,7 +423,6 @@ public class EmployeeView extends BeanValidationForm implements HasUrl UUID employeeId = UUID.fromString(s); var employee = employeeService.getEmployee(employeeId); setEntityWithEnabledSave(employee); - team.setValue(employee.getTeam()); if ("edit".equals(action) && !s.isEmpty()) { saveButton.setVisible(true); @@ -490,5 +589,4 @@ public class EmployeeView extends BeanValidationForm implements HasUrl saveButton, editButton, reportButton, dialog ); } -} - +} \ No newline at end of file