diff --git a/package.json b/package.json index 3395e11..209a580 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -19,29 +19,30 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@babel/preset-react": "7.25.7", + "@preact/signals-react-transform": "0.4.0", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" @@ -49,7 +50,7 @@ "vaadin": { "dependencies": { "@f0rce/ace-widget": "1.0.2", - "@polymer/polymer": "3.5.1", + "@polymer/polymer": "3.5.2", "@vaadin-component-factory/vcf-pdf-viewer": "2.0.1", "@vaadin/bundles": "24.5.1", "@vaadin/common-frontend": "0.0.19", @@ -64,34 +65,35 @@ "@vaadin/vaadin-usage-statistics": "2.1.3", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "2.29.3", - "lit": "3.1.4", + "lit": "3.2.1", "print-js": "1.6.0", "proj4": "2.12.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.23.1" + "react-router-dom": "6.26.2" }, "devDependencies": { - "@babel/preset-react": "7.24.7", - "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@vitejs/plugin-react": "4.3.1", - "async": "3.2.5", - "glob": "10.4.1", + "@babel/preset-react": "7.25.7", + "@preact/signals-react-transform": "0.4.0", + "@rollup/plugin-replace": "6.0.1", + "@rollup/pluginutils": "5.1.2", + "@types/react": "18.3.11", + "@types/react-dom": "18.3.1", + "@vitejs/plugin-react": "4.3.3", + "async": "3.2.6", + "glob": "10.4.5", "rollup-plugin-brotli": "3.1.0", "rollup-plugin-visualizer": "5.12.0", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "5.4.5", - "vite": "5.3.3", - "vite-plugin-checker": "0.6.4", + "typescript": "5.6.3", + "vite": "5.4.9", + "vite-plugin-checker": "0.8.0", "workbox-build": "7.1.1", "workbox-core": "7.1.0", "workbox-precaching": "7.1.0" }, - "hash": "1a0f17d48b329307b5862bc57499307d1b89f7d89260121c2b7189f76957c436" + "hash": "2dc40a4f634ae025081ca2239cba00b14a35fe94ab78ac0a4dd3023d882081d5" }, "overrides": { "@vaadin/bundles": "$@vaadin/bundles", diff --git a/src/main/java/com/primefactorsolutions/model/Employee.java b/src/main/java/com/primefactorsolutions/model/Employee.java index bc83005..02a9e14 100644 --- a/src/main/java/com/primefactorsolutions/model/Employee.java +++ b/src/main/java/com/primefactorsolutions/model/Employee.java @@ -27,6 +27,7 @@ public class Employee extends BaseEntity implements UserDetails { @NotNull(message = "El apellido no puede estar vacío") @Pattern(regexp = "^[a-zA-Z ]+$", message = "El apellido solo debe contener letras") private String lastName; + @MinAge(18) private LocalDate birthday; @Pattern(regexp = "^[a-zA-Z ,]+$", message = "La ciudad de nacimiento solo debe contener letras, espacios o comas") private String birthCity; @@ -101,6 +102,7 @@ public class Employee extends BaseEntity implements UserDetails { private String bankName; @Pattern(regexp = "^[0-9]+$", message = "El número de cuenta debe contener solo números") private String accountNumber; + private String customContractType; private String gpss; private String sss; @@ -113,6 +115,10 @@ public class Employee extends BaseEntity implements UserDetails { @Enumerated(EnumType.STRING) private Status status; + public void setCustomContractType(final String customContractType) { + this.customContractType = customContractType; + } + @Override public Collection getAuthorities() { return Lists.newArrayList(); @@ -180,7 +186,7 @@ public class Employee extends BaseEntity implements UserDetails { CONTRATO_PLAZO_FIJO, CONSULTORIA_INTERNA, CONSULTORIA_EXTERNA, - MIXTO, + CONTRATO_MIXTO, OTROS } diff --git a/src/main/java/com/primefactorsolutions/model/MinAge.java b/src/main/java/com/primefactorsolutions/model/MinAge.java new file mode 100644 index 0000000..41ad740 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/MinAge.java @@ -0,0 +1,17 @@ +package com.primefactorsolutions.model; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = MinAgeValidator.class) +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface MinAge { + String message() default "El empleado debe ser mayor de {value} años"; + Class[] groups() default {}; + Class[] payload() default {}; + int value(); +} diff --git a/src/main/java/com/primefactorsolutions/model/MinAgeValidator.java b/src/main/java/com/primefactorsolutions/model/MinAgeValidator.java new file mode 100644 index 0000000..5c453f2 --- /dev/null +++ b/src/main/java/com/primefactorsolutions/model/MinAgeValidator.java @@ -0,0 +1,24 @@ +package com.primefactorsolutions.model; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; + +public class MinAgeValidator implements ConstraintValidator { + private int minAge; + + @Override + public void initialize(final MinAge constraintAnnotation) { + this.minAge = constraintAnnotation.value(); + } + + @Override + public boolean isValid(final LocalDate birthday, final ConstraintValidatorContext context) { + if (birthday == null) { + return true; + } + return ChronoUnit.YEARS.between(birthday, LocalDate.now()) >= minAge; + } +} diff --git a/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java b/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java index a72f647..0957e0e 100644 --- a/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java +++ b/src/main/java/com/primefactorsolutions/repositories/EmployeeRepository.java @@ -11,5 +11,6 @@ public interface EmployeeRepository extends JpaRepository { Optional findByUsername(String username); Optional findByPersonalEmail(String personalEmail); List findAllByTeamId(UUID teamId); + //Optional findByTeamIdAndLeadManagerTrue(UUID teamId); List findByTeamName(String teamName); } \ No newline at end of file diff --git a/src/main/java/com/primefactorsolutions/service/EmployeeService.java b/src/main/java/com/primefactorsolutions/service/EmployeeService.java index 1f374e8..f426d01 100644 --- a/src/main/java/com/primefactorsolutions/service/EmployeeService.java +++ b/src/main/java/com/primefactorsolutions/service/EmployeeService.java @@ -55,7 +55,6 @@ public class EmployeeService { Optional leadManager = teamMembers.stream() .filter(e -> Strings.isNullOrEmpty(e.getLeadManager())) .findFirst(); - return leadManager.map(employee -> employee.getFirstName() + " " + employee.getLastName()) .orElse("No asignado"); } diff --git a/src/main/java/com/primefactorsolutions/views/EmployeeView.java b/src/main/java/com/primefactorsolutions/views/EmployeeView.java index 642ef2f..0c3c60e 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeeView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeeView.java @@ -15,9 +15,7 @@ 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.dialog.Dialog; -import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.html.H3; -import com.vaadin.flow.component.html.Image; +import com.vaadin.flow.component.html.*; import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; @@ -106,10 +104,11 @@ public class EmployeeView extends BeanValidationForm implements HasUrl private final VDatePicker dateOfEntry = new VDatePicker("Fecha de Ingreso"); private final VDatePicker dateOfExit = new VDatePicker("Fecha de Retiro"); private final ComboBox contractType = createContractTypeComboBox(); + private final TextField customContractType = createCustomContractTypeField(); private final TextField seniority = createTextField("Antiguedad", 30, false); private final TextField salaryTotal = createTextField("Salario Total", 10, false); private final TextField salaryBasic = createTextField("Salario Basico", 10, false); - private final TextField antiguedad = createTextField("Descuento por Antiguedad", 10, false); + private final TextField antiguedad = createTextField("Bono de Antiguedad", 10, false); private final TextField bonoProfesional = createTextField("Bono Profesional", 30, false); private final TextField bankName = createTextField("Banco", 30, false); private final TextField accountNumber = createTextField("Nro. de Cuenta", 30, false); @@ -124,11 +123,12 @@ public class EmployeeView extends BeanValidationForm implements HasUrl private static final String PHONE_NUMBER_ERROR_MESSAGE = "El teléfono debe contener solo números."; private final Button saveButton = new Button(SAVE_BUTTON_TEXT, e -> saveEmployee()); private final Button editButton = new Button(EDIT_BUTTON_TEXT, e -> enableEditMode()); - private final Button reportButton = new Button("Generar Ficha"); + private final Button reportButton = new Button("Generar Ficha de Contratación"); private final Dialog dialog = new Dialog(); private final PdfViewer pdfViewer = new PdfViewer(); private final H2 infoPer = new H2("Información Personal"); private final H3 infoGenr = new H3("Información General"); + private final H5 imagenSub = new H5("Insertar una imagen .jpg:"); private final H3 contEmerg = new H3("Contacto de Emergencia"); private final H2 infProf = new H2("Información Profesional"); private final H3 titulos = new H3("Titulos Profesionales y Estudios Realizados"); @@ -178,7 +178,17 @@ public class EmployeeView extends BeanValidationForm implements HasUrl editButton.setVisible(true); reportButton.setVisible(true); birthday.addValueChangeListener(event -> calculateAge()); - birthday.setMax(java.time.LocalDate.now().minusYears(18)); + birthday.setMin(LocalDate.now().minusYears(100)); + birthday.setMax(LocalDate.now().minusYears(18)); + birthday.addValueChangeListener(event -> { + LocalDate selectedDate = event.getValue(); + if (selectedDate != null && ChronoUnit.YEARS.between(selectedDate, LocalDate.now()) < 18) { + birthday.setInvalid(true); + birthday.setErrorMessage("El empleado debe ser mayor o tener 18 años"); + } else { + birthday.setInvalid(false); + } + }); salaryTotal.addValueChangeListener(event -> calculateSalaryTotal()); dateOfEntry.addValueChangeListener(event -> calculateSeniority()); dateOfExit.addValueChangeListener(event -> { @@ -373,10 +383,30 @@ public class EmployeeView extends BeanValidationForm implements HasUrl comboBox.setItemLabelGenerator(Employee.ContractType::name); comboBox.setRequiredIndicatorVisible(true); comboBox.setWidth("300px"); - comboBox.setMinWidth("200px"); + comboBox.addValueChangeListener(event -> handleContractTypeChange(event.getValue())); return comboBox; } + private TextField createCustomContractTypeField() { + TextField textField = new TextField("Especificar Tipo de Contrato"); + textField.setPlaceholder("Ingrese el tipo de contrato..."); + textField.setVisible(false); + textField.setWidth("300px"); + return textField; + } + + private void handleContractTypeChange(final Employee.ContractType selectedType) { + if (selectedType == Employee.ContractType.OTROS) { + customContractType.setVisible(true); + customContractType.setRequired(true); + } else { + customContractType.setVisible(false); + customContractType.clear(); + customContractType.setRequired(false); + } + } + + private VerticalLayout createContentLayout() { VerticalLayout contentLayout = new VerticalLayout(); contentLayout.setWidth("100%"); @@ -454,6 +484,12 @@ public class EmployeeView extends BeanValidationForm implements HasUrl employee.setBonoProfesional(bonoProfesional.getValue()); employee.setAntiguedad(antiguedad.getValue()); employee.setSalarytotal((salaryTotal.getValue())); + employee.setContractType(contractType.getValue()); + if (contractType.getValue() == Employee.ContractType.OTROS) { + employee.setCustomContractType(customContractType.getValue()); + } else { + employee.setCustomContractType(null); + } employeeService.createOrUpdate(employee); Notification.show(NOTIFICATION_SAVE_SUCCESS); getUI().ifPresent(ui -> ui.navigate(EmployeesListView.class)); @@ -478,7 +514,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl editButton.setVisible(false); setFieldsEditable(); upload.setVisible(true); - salaryTotal.setValue(String.valueOf(true)); + salaryTotal.setValue(String.valueOf(0.0)); } else { UUID employeeId = UUID.fromString(s); var employee = employeeService.getEmployee(employeeId); @@ -565,6 +601,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl dateOfEntry.setReadOnly(true); dateOfExit.setReadOnly(true); contractType.setReadOnly(true); + customContractType.setReadOnly(true); seniority.setReadOnly(true); salaryTotal.setReadOnly(true); salaryBasic.setReadOnly(true); @@ -627,6 +664,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl dateOfEntry.setReadOnly(false); dateOfExit.setReadOnly(false); contractType.setReadOnly(false); + customContractType.setReadOnly(false); seniority.setReadOnly(false); salaryTotal.setReadOnly(false); salaryBasic.setReadOnly(false); @@ -646,6 +684,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl username, infoPer, infoGenr, + imagenSub, upload, profileImagePreview, firstName, lastName, gender, status, @@ -661,7 +700,7 @@ public class EmployeeView extends BeanValidationForm implements HasUrl idioma, language1, language1Level, language2, language2Level, infoAdm, cod, position, team, leadManager, - infoCont, dateOfEntry, dateOfExit, contractType, seniority, + infoCont, dateOfEntry, dateOfExit, seniority, contractType, customContractType, salaryBasic, bonoProfesional, antiguedad, salaryTotal, datBanc, bankName, accountNumber, datGest, gpss, sss, beneficiarie1, beneficiarie2, diff --git a/src/main/java/com/primefactorsolutions/views/EmployeesListView.java b/src/main/java/com/primefactorsolutions/views/EmployeesListView.java index d910b87..31799b2 100644 --- a/src/main/java/com/primefactorsolutions/views/EmployeesListView.java +++ b/src/main/java/com/primefactorsolutions/views/EmployeesListView.java @@ -3,15 +3,22 @@ package com.primefactorsolutions.views; import com.primefactorsolutions.model.Employee; import com.primefactorsolutions.service.EmployeeService; import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.html.Anchor; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.spring.annotation.SpringComponent; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.vaadin.firitin.components.grid.PagingGrid; import com.vaadin.flow.component.grid.GridSortOrder; import com.vaadin.flow.data.provider.SortDirection; import jakarta.annotation.security.PermitAll; import org.springframework.context.annotation.Scope; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.List; @SpringComponent @@ -33,9 +40,92 @@ public class EmployeesListView extends BaseView { private void setupView() { configureTable(); getCurrentPageLayout().add(createAddEmployeeButton()); + getCurrentPageLayout().add(createExportButton()); getCurrentPageLayout().add(table); } + private Button createExportButton() { + StreamResource excelResource = new StreamResource("employees.xlsx", this::generateExcel); + Anchor downloadLink = new Anchor(excelResource, "Export Employees"); + downloadLink.getElement().setAttribute("download", true); // Forzar descarga + return new Button("Exportar como Excel", e -> getCurrentPageLayout().add(downloadLink)); + } + + private ByteArrayInputStream generateExcel() { + List employees = employeeService.findAllEmployees(); + try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { + Sheet sheet = workbook.createSheet("Employees"); + Row headerRow = sheet.createRow(0); + String[] headers = { + "ID", "Nombres", "Apellidos", "Status", "Genero", "Fecha de Nacimiento", "Edad", + "Ciudad y Pais de Nacimiento", "Dirección de Residencia", "Departamento y Provincia de Residencia", + "Marital Status", + "Numero de Hijos", "CI", "Expedido en", "Teléfono", "E-mail", + "Teléfono Laboral", "E-mail Laboral", "Codigo de Empleado", "Cargo", + "Equipo", "Lead/Manager", "Fecha de Ingreso", "Fecha de Retiro", "Tipo de Contrato", + "Otro Tipo de Contrato", "Antiguedad", "Salario Total" + }; + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + cell.setCellStyle(style); + } + int rowIndex = 1; + for (Employee employee : employees) { + Row row = sheet.createRow(rowIndex++); + row.createCell(0).setCellValue(employee.getId().toString()); + row.createCell(1).setCellValue(employee.getFirstName()); + row.createCell(2).setCellValue(employee.getLastName()); + row.createCell(3).setCellValue(employee.getStatus().toString()); + row.createCell(4).setCellValue(employee.getGender() != null ? employee.getGender().toString() : ""); + row.createCell(5).setCellValue(employee.getBirthday() != null ? employee.getBirthday() + .toString() : ""); + row.createCell(6).setCellValue(employee.getAge() != null ? employee.getAge().toString() : ""); + row.createCell(7).setCellValue(employee.getBirthCity() != null ? employee.getBirthCity() : ""); + row.createCell(8).setCellValue(employee.getResidenceAddress() != null ? employee + .getResidenceAddress() : ""); + row.createCell(9).setCellValue(employee.getLocalAddress() != null ? employee.getLocalAddress() : ""); + row.createCell(10).setCellValue(employee.getMaritalStatus() != null ? employee.getMaritalStatus() + .toString() : ""); + row.createCell(11).setCellValue(employee.getNumberOfChildren() != null ? employee + .getNumberOfChildren().toString() : ""); + row.createCell(12).setCellValue(employee.getCi() != null ? employee.getCi() : ""); + row.createCell(13).setCellValue(employee.getIssuedIn() != null ? employee.getIssuedIn() : ""); + row.createCell(14).setCellValue(employee.getPhoneNumber() != null ? employee.getPhoneNumber() : ""); + row.createCell(15).setCellValue(employee.getPersonalEmail() != null ? employee + .getPersonalEmail() : ""); + row.createCell(16).setCellValue(employee.getPhoneNumberProfesional() != null ? employee + .getPhoneNumberProfesional() : ""); + row.createCell(17).setCellValue(employee.getProfesionalEmail() != null ? employee + .getProfesionalEmail() : ""); + row.createCell(18).setCellValue(employee.getCod() != null ? employee.getCod() : ""); + row.createCell(19).setCellValue(employee.getPosition() != null ? employee.getPosition() : ""); + row.createCell(20).setCellValue(employee.getTeam() != null ? employee.getTeam().getName() : ""); + row.createCell(21).setCellValue(employee.getLeadManager() != null ? employee.getLeadManager() : ""); + row.createCell(22).setCellValue(employee.getDateOfEntry() != null ? employee.getDateOfEntry() + .toString() : ""); + row.createCell(23).setCellValue(employee.getDateOfExit() != null ? employee.getDateOfExit() + .toString() : ""); + row.createCell(24).setCellValue(employee.getContractType() != null ? employee.getContractType() + .toString() : ""); + row.createCell(25).setCellValue(employee.getCustomContractType() != null ? employee + .getCustomContractType() : ""); + row.createCell(26).setCellValue(employee.getSeniority() != null ? employee.getSeniority() : ""); + row.createCell(27).setCellValue(employee.getSalarytotal() != null ? employee.getSalarytotal() : ""); + } + workbook.write(out); + return new ByteArrayInputStream(out.toByteArray()); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private void configureTable() { table.setColumns("firstName", "lastName", "status"); addEditButtonColumn("View", this::navigateToEmployeeView); @@ -54,6 +144,7 @@ public class EmployeesListView extends BaseView { } private Button createAddEmployeeButton() { + return createButton("Add Employee", this::navigateToAddEmployeeView); } @@ -66,6 +157,7 @@ public class EmployeesListView extends BaseView { } private void navigateToAddEmployeeView() { + getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, "new")); } diff --git a/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java b/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java index e702791..b191f99 100644 --- a/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java +++ b/src/main/java/com/primefactorsolutions/views/HoursWorkedView.java @@ -133,7 +133,7 @@ public class HoursWorkedView extends BeanValidationForm implements } protected Button createCloseButton() { - Button closeButton = new Button("Cerrar"); + Button closeButton = new Button("Cancelar"); closeButton.addClickListener(event -> closeForm()); return closeButton; }